js代码片段

getValue

给定一个对象或数组,函数将返回指定路径的值,否则为 null。

const getValue = (obj, path) => path
    .replace(/\[([^[\]]*)]/g, '.$1.')
    .split('.')
    .filter(prop => prop !== '')
    .reduce((prev, next) => (
        prev instanceof Object ? prev[next] : undefined
    ), obj)

getValue({ a: { b: c: 'd' } }, 'a.b.c') // = d
getValue({ a: { b: c: [1, 2] } }, 'a.b.c[1]') // = 2

clamp

确保值在指定范围内,否则“clamp”到最接近的最小值和最大值。

const clamp = (min, max, value) => {
    if (min > max) throw new Error('min cannot be greater than max');
    return value < min ?
        min :
        value > max ?
        max :
        value
}

clamp(0, 10, -5) // = 0
clamp(0, 10, 20) // = 10

sleep

在执行下一个操作之前等待指定的持续时间(以毫秒为单位)。

const sleep = duration => (
    new Promise(resolve => {
        const timeoutHandle = setTimeout(() => {
            clearTimeout(timeoutHandle)
            resolve()
        }, duration)
    })
)

await sleep(1000)

groupBy

根据键控功能对对象中的相关项进行分组和索引。

const groupBy = (fn, list) => (
    list.reduce((prev, next) => ({
        ...prev,
        [fn(next)]: [...(prev[fn(next)] || []), next]
    }), {})
)

groupBy(vehicle => vehicle.make, [{
        make: 'tesla',
        model: '3'
    },
    {
        make: 'tesla',
        model: 'y'
    },
    {
        make: 'ford',
        model: 'mach-e'
    },
])

// {
//   tesla: [ { make: 'tesla', model: '3' }, { make: 'tesla', model: 'y' } ],
//   ford: [ { make: 'ford', model: 'mach-e' } ]
// }

collectBy

根据键控功能创建包含相关项目的子列表。

import groupBy from './groupBy'

const collectBy = (fn, list) => Object.values(groupBy(fn, list))

collectBy(vehicle => vehicle.make, [
    { make: 'tesla', model: '3' },
    { make: 'tesla', model: 'y' },
    { make: 'ford', model: 'mach-e' },
])

// [
//   [ { make: 'tesla', model: '3' }, { make: 'tesla', model: 'y' } ],
//   [ { make: 'ford', model: 'mach-e' } ]
// ]

获取列表的第一个元素。这个函数对于编写干净易读的代码很有用。

const head = list => list[0]

head([1, 2, 3]) // = 1
head([]) // = undefined

tail

获取列表中除第一个元素之外的所有元素。这个函数对于编写干净易读的代码很有用。


const tail = list => list.slice(1)

tail([1, 2, 3]) // = [2, 3]
tail([]) // = []

flatten

通过递归地从嵌套子列表中提取所有项目来创建一个平面列表。

const flatten = list => list.reduce((prev, next) => ([
    ...prev,
    ...(Array.isArray(next) ? flatten(next) : [next])
]), [])

flatten([[1, 2, [3, 4], 5, [6, [7, 8]]]])

// [
//   1, 2, 3, 4,
//   5, 6, 7, 8
// ]

intersectionBy

查找键控函数定义的两个列表中存在的所有值。

const intersectionBy = (fn, listA, listB) => {
    const b = new Set(listB.map(fn))

    return listA.filter(val => b.has(fn(val)))
}

intersectionBy(v => v, [1, 2, 3], [2, 3, 4]) // = [2, 3]
intersectionBy(v => v, [{ a: 1 }, { a: 2 }], [{ a: 2}, { a: 3 }, { a: 4 }]) // = [{ a: 2 }]

indexBy

通过键控函数确定的值对列表中的每个元素进行索引。

const indexBy = (fn, list) => list.reduce((pre, next) => ({
    ...pre,
    [fn(next)]: next
}), {})

indexBy(val => val.a, [{ a: 1 }, { a: 2 }, { a: 3 }])

// { '1': { a: 1 }, '2': { a: 2 }, '3': { a: 3 } }

differenceBy

查找第一个列表中不存在于第二个列表中的所有项目,由键控功能确定。

import indexBy from './indexBy'

const differenceBy = (fn, listA, listB) => {
    const bIndex = indexBy(fn, listB)

    return listA.filter(val => !bIndex[fn(val)])
}

differenceBy(val => val, [1, 2, 3], [3, 4, 5])
// [ 1, 2 ]

differenceBy(
    vehicle => vehicle.make,
    [{ make: 'tesla' }, { make: 'ford' }, { make: 'gm' }],
    [{ make: 'tesla' }, { make: 'bmw' }, { make: 'audi' }],
)

// [ { make: 'ford' }, { make: 'gm' } ]

recoverWith

如果给定函数抛出错误,则返回默认值。

const recoverWith = async (defaultValue, fn, ...args) => {
    try {
        const result = await fn(...args)
        return result
    } catch (_e) {
        return defaultValue
    }
}

recoverWith('A', val => val, 'B') // = B

recoverWith('A', () => { throw new Error() }) // A

distance

计算两点 p1 和 p2 之间的距离。

const distance = ([x0, y0], [x1, y1]) => (
    Math.hypot(x1 - x0, y1 - y0)
)

distance([0, 1], [5, 4]) // = 5.8309518948453

dropWhile

从列表中删除元素,从第一个元素开始,直到满足 som 谓词。

const dropWhile = (pred, list) => {
    let index = 0
    list.every(elem => {
        index++
        return pred(elem)
    })

    return list.slice(index-1)
}

dropWhile(val => (val < 5), [1, 2, 3, 4, 5, 6, 7]) // = [ 5, 6, 7 ]

sumBy

给定一些产生每个元素的单独值的函数,计算列表中所有元素的总和。

const sumBy = (fn, list) => list.reduce((prev, next) => prev + fn(next), 0)

sumBy(product => product.price, [
    { name: 'pizza', price: 10 },
    { name: 'pepsi', price: 5 },
    { name: 'salad', price: 5 }
]) // = 20

升序

给定一个评估函数,创建一个升序比较器函数。

const ascending = fn => (a, b) => {
    const valA = fn(a)
    const valB = fn(b)
    return valA < valB ? -1 : valA > valB ? 1 : 0
}

const byPrice = ascending(val => val.price)

[{ price: 300 }, { price: 100 }, { price: 200 }].sort(byPrice)

// [ { price: 100 }, { price: 200 }, { price: 300 } ]

降序

给定一个评估函数,创建一个降序比较器函数。

const descending = fn => (a, b) => {
    const valA = fn(b)
    const valB = fn(a)
    return valA < valB ? -1 : valA > valB ? 1 : 0;
}

const byPrice = descending(val => val.price)

[{ price: 300 }, { price: 100 }, { price: 200 }].sort(byPrice)

// [ { price: 300 }, { price: 200 }, { price: 100 } ]

findKey

在满足给定predicate的索引中找到第一个键值。

const findKey = (predicate, index) => Object
    .keys(index)
    .find(key => predicate(index[key], key, index))

findKey(
    car => !car.available,
    {
        tesla: { available: true },
        ford: { available: false },
        gm: { available: true }
    },
) // = ford

bifurcateBy

将给定列表的值拆分为两个列表,一个包含predicate函数评估为真值的值,另一个包含假值。

const bifurcateBy = (predicate, list) => list.reduce((acc, val, i) => (
    acc[predicate(val, i) ? 0 : 1].push(val), acc),
    [[], []]
)

bifurcateBy(val => val > 0, [-1, 2, -3, 4])

// [ [ 2, 4 ], [ -1, -3 ] ]

pipe

执行从左到右的功能组合。所有额外的参数都将传递给列表中的第一个函数,因此可以有任何数量。结果将在第二个传递,第二个的结果将传递给第三个,……以此类推,直到处理完所有函数。

const pipe = (functions, ...args) => (
    functions.reduce(
    (prev, next) => Array.isArray(prev) ? next(...prev) : next(prev),
    args)
)

pipe([Math.abs, Math.floor, val => -val], 4.20) // = -4
pipe([(a, b) => a - b, Math.abs], 5, 10) // = 5

检查日期是否为工作日

const isWeekday = d => d.getDay() % 6 !== 0

isWeekday(new Date(2022, 7, 14)) // = false
isWeekday(new Date(2022, 7, 16)) // = true

反转字符串

const reverse = s => s.split('').reverse().join('')

reverse('mkimq') // = qmikm

检查一个数字是否为偶数。

const isEven = n => n % 2 === 0

isEven(2) // = true
isEven(3) // = false

大写字符串

const capitalize = s => s.charAt(0).toUpperCase() + s.slice(1)

capitalize('mkimq') // = Mkimq

检查数组是否为空

const isArrayEmpty = arr => Array.isArray(arr) && !arr.length

isArrayEmpty([]) // = true
isArrayEmpty([1, 2, 3]) // = false

检查对象/数组是否为空

const isObjectEmpty = obj => obj && Object.keys(obj).length === 0

isObjectEmpty({}) // = true
isObjectEmpty({ foo: 'bar' }) // = false

随机生成整数

const randomInteger = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min

randomInteger(1, 10) // = 7

生成随机布尔值

const randomBoolean = () => Math.random() >= 0.5

randomBoolean() // = true

切换布尔值

切换布尔值,变假为真,变真为假。

const toggleBoolean = val => !val

toggleBoolean(false) // = true

转换

将字符串转换为带“-”的连字字符串。

const slugify = str => str.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]+/g, '')

slugify('Hello World') // = hello-world

生成随数组组合

随机生成一组任何类型的数组。

const shuffleArray = arr => arr.sort(() => Math.random() - 0.5)

shuffleArray([1, 2, 3, 4]) // = [2, 4, 1, 3]

将连字字符串转换为骆峰字符串

const snakeToCamel = s => s.toLowerCase().replace(/(_\w)/g, (w) => w.toUpperCase().substring(1))

snakeToCamel('foo_bar') // fooBar

随机数字符串

根据当前时间生成随机数字符串。

const randomNumberString = () => new Date().getTime() + Math.random().toString(36).slice(2)

randomNumberString() // = 1657782974681lupy1hcmb8e

将数字转换为字符/字母

const numberToLetter = value => String.fromCharCode(94 + value)

numberToLetter(4) // = b

生成随机的十六进制颜色

const randomHexColor = () => `#${Math.floor(Math.random() * 0xffffff).toString(16).padEnd(6, '0')}`

randomHexColor() // = #a9f759

输入一个值,返回其数据类型

function type(para) {
    return Object.prototype.toString.call(para)
}

数组去重

function unique1(arr) {
    return [...new Set(arr)]
}

function unique2(arr) {
    var obj = {};
    return arr.filter(ele => {
        if (!obj[ele]) {
            obj[ele] = true;
            return true;
        }
    })
}

function unique3(arr) {
    var result = [];
    arr.forEach(ele => {
        if (result.indexOf(ele) == -1) {
            result.push(ele)
        }
    })
    return result;
}

字符串去重

String.prototype.unique = function () {
    var obj = {},
        str = '',
        len = this.length;
    for (var i = 0; i < len; i++) {
        if (!obj[this[i]]) {
            str += this[i];
            obj[this[i]] = true;
        }
    }
    return str;
}

//去除连续的字符串
function uniq(str) {
    return str.replace(/(\w)\1+/g, '$1')
}

深拷贝 浅拷贝

// 简单粗暴版本
function deepClone(obj) {
    let res = {};
    // 类型判断的通用方法
    function getType(obj) {
        return Object.prototype.toString.call(obj).replaceAll(new RegExp(/\[|\]|object /g), "");
    }
    const type = getType(obj);
    const reference = ["Set", "WeakSet", "Map", "WeakMap", "RegExp", "Date", "Error"];
    if (type === "Object") {
        for (const key in obj) {
            if (Object.hasOwnProperty.call(obj, key)) {
                res[key] = deepClone(obj[key]);
            }
        }
    } else if (type === "Array") {
        console.log('array obj', obj);
        obj.forEach((e, i) => {
            res[i] = deepClone(e);
        });
    }
    else if (type === "Date") {
        res = new Date(obj);
    } else if (type === "RegExp") {
        res = new RegExp(obj);
    } else if (type === "Map") {
        res = new Map(obj);
    } else if (type === "Set") {
        res = new Set(obj);
    } else if (type === "WeakMap") {
        res = new WeakMap(obj);
    } else if (type === "WeakSet") {
        res = new WeakSet(obj);
    }else if (type === "Error") {
        res = new Error(obj);
    }
     else {
        res = obj;
    }
    return res;
}

// 合并冗余代码
function deepClone(obj) {
    let res = null;
    // 类型判断的通用方法
    function getType(obj) {
        return Object.prototype.toString.call(obj).replaceAll(new RegExp(/\[|\]|object /g), "");
    }
    const type = getType(obj);
    const reference = ["Set", "WeakSet", "Map", "WeakMap", "RegExp", "Date", "Error"];
    if (type === "Object") {
        res = {};
        for (const key in obj) {
            if (Object.hasOwnProperty.call(obj, key)) {
                res[key] = deepClone(obj[key]);
            }
        }
    } else if (type === "Array") {
        console.log('array obj', obj);
        res = [];
        obj.forEach((e, i) => {
            res[i] = deepClone(e);
        });
    }
    // 优化此部分冗余判断
    // else if (type === "Date") {
    //     res = new Date(obj);
    // } else if (type === "RegExp") {
    //     res = new RegExp(obj);
    // } else if (type === "Map") {
    //     res = new Map(obj);
    // } else if (type === "Set") {
    //     res = new Set(obj);
    // } else if (type === "WeakMap") {
    //     res = new WeakMap(obj);
    // } else if (type === "WeakSet") {
    //     res = new WeakSet(obj);
    // }else if (type === "Error") {
    //   res = new Error(obj);
    //}
    else if (reference.includes(type)) {
        res = new obj.constructor(obj);
    } else {
        res = obj;
    }
    return res;
}

// 进一步优化
// 判断类型的方法移到外部,避免递归过程中多次执行
const judgeType = origin => {
    return Object.prototype.toString.call(origin).replaceAll(new RegExp(/\[|\]|object /g), "");
};
const reference = ["Set", "WeakSet", "Map", "WeakMap", "RegExp", "Date", "Error"];
function deepClone(obj) {
    // 定义新的对象,最后返回
     //通过 obj 的原型创建对象
    const cloneObj = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));

    // 遍历对象,克隆属性
    for (let key of Reflect.ownKeys(obj)) {
        const val = obj[key];
        const type = judgeType(val);
        if (reference.includes(type)) {
            newObj[key] = new val.constructor(val);
        } else if (typeof val === "object" && val !== null) {
            // 递归克隆
            newObj[key] = deepClone(val);
        } else {
            // 基本数据类型和function
            newObj[key] = val;
        }
    }
    return newObj;
}

// 上面代码的基础上进行了改造
function deepClone(obj) {
    let res = null;
    const reference = [Date, RegExp, Set, WeakSet, Map, WeakMap, Error];
    if (reference.includes(obj?.constructor)) {
        res = new obj.constructor(obj);
    } else if (Array.isArray(obj)) {
        res = [];
        obj.forEach((e, i) => {
            res[i] = deepClone(e);
        });
    } else if (typeof obj === "object" && obj !== null) {
        res = {};
        for (const key in obj) {
            if (Object.hasOwnProperty.call(obj, key)) {
                res[key] = deepClone(obj[key]);
            }
        }
    } else {
        res = obj;
    }
    return res;
}

// 最后,还有循环引用问题,避免出现无线循环的问题。

// 我们用hash来存储已经加载过的对象,如果已经存在的对象,就直接返回。
function deepClone(obj, hash = new WeakMap()) {
    if (hash.has(obj)) {
        return obj;
    }
    let res = null;
    const reference = [Date, RegExp, Set, WeakSet, Map, WeakMap, Error];

    if (reference.includes(obj?.constructor)) {
        res = new obj.constructor(obj);
    } else if (Array.isArray(obj)) {
        res = [];
        obj.forEach((e, i) => {
            res[i] = deepClone(e);
        });
    } else if (typeof obj === "object" && obj !== null) {
        res = {};
        for (const key in obj) {
            if (Object.hasOwnProperty.call(obj, key)) {
                res[key] = deepClone(obj[key]);
            }
        }
        hash.set(obj, res);
    } else {
        res = obj;
    }
    return res;
}

//深克隆(深克隆不考虑函数)
function deepClone(obj, result) {
    var result = result || {};
    for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) {
            if (typeof obj[prop] == 'object' && obj[prop] !== null) {
                // 引用值(obj/array)且不为null
                if (Object.prototype.toString.call(obj[prop]) == '[object Object]') {
                    // 对象
                    result[prop] = {};
                } else {
                    // 数组
                    result[prop] = [];
                }
                deepClone(obj[prop], result[prop])
    } else {
        // 原始值或func
        result[prop] = obj[prop]
    }
  }
}
return result;
}

// 深浅克隆是针对引用值
function deepClone(target) {
    if (typeof (target) !== 'object') {
        return target;
    }
    var result;
    if (Object.prototype.toString.call(target) == '[object Array]') {
        // 数组
        result = []
    } else {
        // 对象
        result = {};
    }
    for (var prop in target) {
        if (target.hasOwnProperty(prop)) {
            result[prop] = deepClone(target[prop])
        }
    }
    return result;
}
// 无法复制函数
var o1 = jsON.parse(jsON.stringify(obj1));

reverse底层原理和扩展

// 改变原数组
Array.prototype.myReverse = function () {
    var len = this.length;
    for (var i = 0; i < len; i++) {
        var temp = this[i];
        this[i] = this[len - 1 - i];
        this[len - 1 - i] = temp;
    }
    return this;
}

圣杯模式的继承

function inherit(Target, Origin) {
    function F() {};
    F.prototype = Origin.prototype;
    Target.prototype = new F();
    Target.prototype.constructor = Target;
    // 最终的原型指向
    Target.prop.uber = Origin.prototype;
}

找出字符串中第一次只出现一次的字母

String.prototype.firstAppear = function () {
    var obj = {},
        len = this.length;
    for (var i = 0; i < len; i++) {
        if (obj[this[i]]) {
            obj[this[i]]++;
        } else {
            obj[this[i]] = 1;
        }
    }
    for (var prop in obj) {
       if (obj[prop] == 1) {
         return prop;
       }
    }
}

找元素的第n级父元素

function parents(ele, n) {
    while (ele && n) {
        ele = ele.parentElement ? ele.parentElement : ele.parentNode;
        n--;
    }
    return ele;
}

返回元素的第n个兄弟节点

function retSibling(e, n) {
    while (e && n) {
        if (n > 0) {
            if (e.nextElementSibling) {
                e = e.nextElementSibling;
            } else {
                for (e = e.nextSibling; e && e.nodeType !== 1; e = e.nextSibling);
            }
            n--;
        } else {
            if (e.previousElementSibling) {
                e = e.previousElementSibling;
            } else {
                for (e = e.previousElementSibling; e && e.nodeType !== 1; e = e.previousElementSibling);
            }
            n++;
        }
    }
    return e;
}

封装mychildren,解决浏览器的兼容问题

function myChildren(e) {
    var children = e.childNodes,
        arr = [],
        len = children.length;
    for (var i = 0; i < len; i++) {
        if (children[i].nodeType === 1) {
            arr.push(children[i])
        }
    }
    return arr;
}

判断元素有没有子元素

function hasChildren(e) {
    var children = e.childNodes,
        len = children.length;
    for (var i = 0; i < len; i++) {
        if (children[i].nodeType === 1) {
            return true;
        }
    }
    return false;
}

一个元素插入到另一个元素的后面

Element.prototype.insertAfter = function (target, elen) {
    var nextElen = elen.nextElenmentSibling;
    if (nextElen == null) {
        this.appendChild(target);
    } else {
        this.insertBefore(target, nextElen);
    }
}

返回当前的时间(年月日时分秒)

function getDateTime() {
    var date = new Date(),
        year = date.getFullYear(),
        month = date.getMonth() + 1,
        day = date.getDate(),
        hour = date.getHours() + 1,
        minute = date.getMinutes(),
        second = date.getSeconds();
        month = checkTime(month);
        day = checkTime(day);
        hour = checkTime(hour);
        minute = checkTime(minute);
        second = checkTime(second);

     function checkTime(i) {
        if (i < 10) {
                i = "0" + i;
       }
      return i;
    }
    return "" + year + "年" + month + "月" + day + "日" + hour + "时" + minute + "分" + second + "秒"
}

获得视口的尺寸

function getViewportOffset() {
    if (window.innerWidth) {
        return {
            w: window.innerWidth,
            h: window.innerHeight
        }
    } else {
        // ie8及其以下
        if (document.compatMode === "BackCompat") {
            // 怪异模式
            return {
                w: document.body.clientWidth,
                h: document.body.clientHeight
            }
        } else {
            // 标准模式
            return {
                w: document.documentElement.clientWidth,
                h: document.documentElement.clientHeight
            }
        }
    }
}

获取任一元素的任意属性

function getStyle(elem, prop) {
    return window.getComputedStyle ? window.getComputedStyle(elem, null)[prop] : elem.currentStyle[prop]
}

绑定事件的兼容代码

function addEvent(elem, type, handle) {
    if (elem.addEventListener) { //非ie和非ie9
        elem.addEventListener(type, handle, false);
    } else if (elem.attachEvent) { //ie6到ie8
        elem.attachEvent('on' + type, function () {
            handle.call(elem);
        })
    } else {
        elem['on' + type] = handle;
    }
}

解绑事件

function removeEvent(elem, type, handle) {
    if (elem.removeEventListener) { //非ie和非ie9
        elem.removeEventListener(type, handle, false);
    } else if (elem.detachEvent) { //ie6到ie8
        elem.detachEvent('on' + type, handle);
    } else {
        elem['on' + type] = null;
    }
}

取消冒泡的兼容代码

function stopBubble(e) {
    if (e && e.stopPropagation) {
        e.stopPropagation();
    } else {
        window.event.cancelBubble = true;
    }
}

检验字符串是否是回文

function isPalina(str) {
    if (Object.prototype.toString.call(str) !== '[object String]') {
        return false;
    }
    var len = str.length;
    for (var i = 0; i < len / 2; i++) {
        if (str[i] != str[len - 1 - i]) {
            return false;
        }
    }
    return true;
}

检验字符串是否是回文

function isPalindrome(str) {
    str = str.replace(/\W/g, '').toLowerCase();
    console.log(str)
    return (str == str.split('').reverse().join(''))
}

兼容getElementsByClassName方法

Element.prototype.getElementsByClassName = Document.prototype.getElementsByClassName = function (_className) {
    var allDomArray = document.getElementsByTagName('*');
    var lastDomArray = [];
    function trimSpace(strClass) {
        var reg = /\s+/g;
        return strClass.replace(reg, ' ').trim()
    }
    for (var i = 0; i < allDomArray.length; i++) {
        var classArray = trimSpace(allDomArray[i].className).split(' ');
        for (var j = 0; j < classArray.length; j++) {
            if (classArray[j] == _className) {
                lastDomArray.push(allDomArray[i]);
                break;
            }
        }
    }
    return lastDomArray;
}

运动函数

function animate(obj, json, callback) {
    clearInterval(obj.timer);
    var speed,
        current;
    obj.timer = setInterval(function () {
        var lock = true;
        for (var prop in json) {
            if (prop == 'opacity') {
                current = parseFloat(window.getComputedStyle(obj, null)[prop]) * 100;
            } else {
                current = parseInt(window.getComputedStyle(obj, null)[prop]);
            }
            speed = (json[prop] - current) / 7;
            speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);

            if (prop == 'opacity') {
                obj.style[prop] = (current + speed) / 100;
            } else {
                obj.style[prop] = current + speed + 'px';
            }
            if (current != json[prop]) {
                lock = false;
            }
        }
        if (lock) {
            clearInterval(obj.timer);
            typeof callback == 'function' ? callback() : '';
        }
    }, 30)
}

弹性运动

function ElasticMovement(obj, target) {
    clearInterval(obj.timer);
    var iSpeed = 40,
        a, u = 0.8;
    obj.timer = setInterval(function () {
        a = (target - obj.offsetLeft) / 8;
        iSpeed = iSpeed + a;
        iSpeed = iSpeed * u;
        if (Math.abs(iSpeed) <= 1 && Math.abs(a) <= 1) {
            console.log('over')
            clearInterval(obj.timer);
            obj.style.left = target + 'px';
        } else {
            obj.style.left = obj.offsetLeft + iSpeed + 'px';
        }
    }, 30);
}

封装自己的forEach方法

Array.prototype.myForEach = function (func, obj) {
    var len = this.length;
    var _this = arguments[1] ? arguments[1] : window;
    // var _this=arguments[1]||window;
    for (var i = 0; i < len; i++) {
        func.call(_this, this[i], i, this)
    }
}

封装自己的filter方法

Array.prototype.myFilter = function (func, obj) {
    var len = this.length;
    var arr = [];
    var _this = arguments[1] || window;
    for (var i = 0; i < len; i++) {
        func.call(_this, this[i], i, this) && arr.push(this[i]);
    }
    return arr;
}

数组map方法

Array.prototype.myMap = function (func) {
    var arr = [];
    var len = this.length;
    var _this = arguments[1] || window;
    for (var i = 0; i < len; i++) {
        arr.push(func.call(_this, this[i], i, this));
    }
    return arr;
}

数组every方法

Array.prototype.myEvery = function (func) {
    var flag = true;
    var len = this.length;
    var _this = arguments[1] || window;
    for (var i = 0; i < len; i++) {
        if (func.apply(_this, [this[i], i, this]) == false) {
            flag = false;
            break;
        }
    }
    return flag;
}

数组reduce方法

Array.prototype.myReduce = function (func, initialValue) {
    var len = this.length,
        nextValue,
        i;
    if (!initialValue) {
        // 没有传第二个参数
        nextValue = this[0];
        i = 1;
    } else {
        // 传了第二个参数
        nextValue = initialValue;
        i = 0;
    }
    for (; i < len; i++) {
        nextValue = func(nextValue, this[i], i, this);
    }
    return nextValue;
}

获取url中的参数

function getWindonHref() {
    var sHref = window.location.href;
    var args = sHref.split('?');
    if (args[0] === sHref) {
        return '';
    }
    var hrefarr = args[1].split('#')[0].split('&');
    var obj = {};
    for (var i = 0; i < hrefarr.length; i++) {
        hrefarr[i] = hrefarr[i].split('=');
        obj[hrefarr[i][0]] = hrefarr[i][1];
    }
    return obj;
}

数组排序

// 快排 [left] + min + [right]
function quickArr(arr) {
    if (arr.length <= 1) {
        return arr;
    }
    var left = [],
        right = [];
    var pIndex = Math.floor(arr.length / 2);
    var p = arr.splice(pIndex, 1)[0];
    for (var i = 0; i < arr.length; i++) {
        if (arr[i] <= p) {
            left.push(arr[i]);
        } else {
            right.push(arr[i]);
        }
    }
    // 递归
    return quickArr(left).concat([p], quickArr(right));
}

// 冒泡
function bubbleSort(arr) {
    for (var i = 0; i < arr.length - 1; i++) {
        for (var j = i + 1; j < arr.length; j++) {
            if (arr[i] > arr[j]) {
                var temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}

function bubbleSort(arr) {
    var len = arr.length;
    for (var i = 0; i < len - 1; i++) {
        for (var j = 0; j < len - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                var temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
    return arr;
}

遍历Dom树

// 给定页面上的DOM元素,将访问元素本身及其所有后代(不仅仅是它的直接子元素)
// 对于每个访问的元素,函数讲元素传递给提供的回调函数
function traverse(element, callback) {
    callback(element);
    var list = element.children;
    for (var i = 0; i < list.length; i++) {
        traverse(list[i], callback);
    }
}

原生js封装ajax

function ajax(method, url, callback, data, flag) {
    var xhr;
    flag = flag || true;
    method = method.toUpperCase();
    if (window.XMLHttpRequest) {
        xhr = new XMLHttpRequest();
    } else {
        xhr = new ActiveXObject('Microsoft.XMLHttp');
    }
    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4 && xhr.status == 200) {
            console.log(2)
            callback(xhr.responseText);
        }
    }

    if (method == 'GET') {
        var date = new Date(),
        timer = date.getTime();
        xhr.open('GET', url + '?' + data + '&timer' + timer, flag);
        xhr.send()
        } else if (method == 'POST') {
        xhr.open('POST', url, flag);
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        xhr.send(data);
    }
}

异步加载script

function loadScript(url, callback) {
    var oscript = document.createElement('script');
    if (oscript.readyState) { // ie8及以下版本
        oscript.onreadystatechange = function () {
            if (oscript.readyState === 'complete' || oscript.readyState === 'loaded') {
                callback();
            }
        }
    } else {
        oscript.onload = function () {
            callback()
        };
    }
    oscript.src = url;
    document.body.appendChild(oscript);
}

cookie管理

var cookie = {
    set: function (name, value, time) {
        document.cookie = name + '=' + value + '; max-age=' + time;
        return this;
    },
    remove: function (name) {
        return this.setCookie(name, '', -1);
    },
    get: function (name, callback) {
        var allCookieArr = document.cookie.split('; ');
        for (var i = 0; i < allCookieArr.length; i++) {
            var itemCookieArr = allCookieArr[i].split('=');
            if (itemCookieArr[0] === name) {
                return itemCookieArr[1]
            }
        }
        return undefined;
    }
}

实现bind()方法

Function.prototype.myBind = function (target) {
    var target = target || window;
    var _args1 = [].slice.call(arguments, 1);
    var self = this;
    var temp = function () {};
    var F = function () {
        var _args2 = [].slice.call(arguments, 0);
        var parasArr = _args1.concat(_args2);
        return self.apply(this instanceof temp ? this : target, parasArr)
    }
    temp.prototype = self.prototype;
    F.prototype = new temp();
    return F;
}

实现call()方法

Function.prototype.myCall = function () {
    var ctx = arguments[0] || window;
    ctx.fn = this;
    var args = [];
    for (var i = 1; i < arguments.length; i++) {
        args.push(arguments[i])
    }
    var result = ctx.fn(...args);
    delete ctx.fn;
    return result;
}

实现apply()方法

Function.prototype.myApply = function () {
    var ctx = arguments[0] || window;
    ctx.fn = this;
    if (!arguments[1]) {
        var result = ctx.fn();
        delete ctx.fn;
        return result;
    }
    var result = ctx.fn(...arguments[1]);
    delete ctx.fn;
    return result;
}

防抖

function debounce(handle, delay) {
    var timer = null;
    return function () {
        var _self = this,
            _args = arguments;
        clearTimeout(timer);
        timer = setTimeout(function () {
            handle.apply(_self, _args)
        }, delay)
    }
}

节流

function throttle(handler, wait) {
    var lastTime = 0;
    return function (e) {
        var nowTime = new Date().getTime();
        if (nowTime - lastTime > wait) {
            handler.apply(this, arguments);
            lastTime = nowTime;
        }
    }
}

requestAnimFrame兼容性方法

window.requestAnimFrame = (function () {
    return window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        function (callback) {
            window.setTimeout(callback, 1000 / 60);
        };
})();

cancelAnimFrame兼容性方法

window.cancelAnimFrame = (function () {
    return window.cancelAnimationFrame ||
        window.webkitCancelAnimationFrame ||
        window.mozCancelAnimationFrame ||
        function (id) {
            window.clearTimeout(id);
        };
})();

jsonp底层方法

function jsonp(url, callback) {
    var oscript = document.createElement('script');
    if (oscript.readyState) { // ie8及以下版本
        oscript.onreadystatechange = function () {
            if (oscript.readyState === 'complete' || oscript.readyState === 'loaded') {
                callback();
            }
        }
    } else {
        oscript.onload = function () {
            callback()
        };
    }
    oscript.src = url;
    document.body.appendChild(oscript);
}

获取url上的参数

function getUrlParam(sUrl, sKey) {
    var result = {};
    sUrl.replace(/(\w+)=(\w+)(?=[&|#])/g, function (ele, key, val) {
        if (!result[key]) {
            result[key] = val;
        } else {
            var temp = result[key];
            result[key] = [].concat(temp, val);
        }
    })
    if (!sKey) {
        return result;
    } else {
        return result[sKey] || '';
    }
}

格式化时间

function formatDate(t, str) {
    var obj = {
        yyyy: t.getFullYear(),
        yy: ("" + t.getFullYear()).slice(-2),
        M: t.getMonth() + 1,
        MM: ("0" + (t.getMonth() + 1)).slice(-2),
        d: t.getDate(),
        dd: ("0" + t.getDate()).slice(-2),
        H: t.getHours(),
        HH: ("0" + t.getHours()).slice(-2),
        h: t.getHours() % 12,
        hh: ("0" + t.getHours() % 12).slice(-2),
        m: t.getMinutes(),
        mm: ("0" + t.getMinutes()).slice(-2),
        s: t.getSeconds(),
        ss: ("0" + t.getSeconds()).slice(-2),
        w: ['日', '一', '二', '三', '四', '五', '六'][t.getDay()]
    };
    return str.replace(/([a-z]+)/ig, function ($1) {
        return obj[$1]
    });
}

函数柯里化

//是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术

function curryIt(fn) {
    var length = fn.length,
        args = [];
    var result = function (arg) {
        args.push(arg);
        length--;
        if (length <= 0) {
            return fn.apply(this, args);
        } else {
            return result;
        }
    }
    return result;
}

大数相加

function sumBigNumber(a, b) {
    var res = '', //结果
        temp = 0; //按位加的结果及进位
    a = a.split('');
    b = b.split('');
    while (a.length || b.length || temp) {
        //~~按位非 1.类型转换,转换成数字 2.~~undefined==0
        temp += ~~a.pop() + ~~b.pop();
        res = (temp % 10) + res;
        temp = temp > 9;
    }
    return res.replace(/^0+/, '');
}

单例模式

function getSingle(func) {
    var result;
    return function () {
        if (!result) {
            result = new func(arguments);
        }
        return result;
    }
}

复制到剪贴板

const copyToClipboard = (text) => navigator.clipboard?.writeText && navigator.clipboard.writeText(text)

检测黑暗模式

const isDarkMode = () => window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches

滚动到顶部

const scrollToTop = (element) => element.scrollIntoView({ behavior: "smooth", block: "start" })

滚动到底部

const scrollToBottom = (element) => element.scrollIntoView({ behavior: "smooth", block: "end" })

检测互联网带宽

navigator.connection.downlink

振动你的手机

//vibrating the device for 600 milliseconds
window.navigator.vibrate(600)

获取文件后缀名

/**
 * 获取文件后缀名
 * @param {String} filename
 */
export function getExt(filename) {
    if (typeof filename == 'string') {
        return filename
            .split('.')
            .pop()
            .toLowerCase()
    } else {
        throw new Error('filename must be a string type')
    }
}

生成随机字符串

/**
 * 生成随机id
 * @param {*} length
 * @param {*} chars
 */
export function uuid(length, chars) {
    chars =
        chars ||
        '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    length = length || 8
    var result = ''
    for (var i = length; i > 0; --i)
        result += chars[Math.floor(Math.random() * chars.length)]
    return result
}

对象转化为FormData对象

/**
 * 对象转化为formdata
 * @param {Object} object
 */

export function getFormData(object) {
    const formData = new FormData()
    Object.keys(object).forEach(key => {
        const value = object[key]
        if (Array.isArray(value)) {
            value.forEach((subValue, i) =>
                formData.append(key + `[${i}]`, subValue)
            )
        } else {
            formData.append(key, object[key])
        }
    })
    return formData
}

检查元素是否被聚焦

const hasFocus = (ele) => ele === document.activeElement;

获取元素的所有兄弟元素

const siblings = (ele) =>[].slice.call(ele.parentNode.children).filter((child) => child !== ele);

获取选定的文本

const getSelectedText = () => window.getSelection().toString();

返回上一个页面

history.back();
// Or
history.go(-1);
const clearCookies = () => document.cookie
.split(';')
.forEach((c) =>(document.cookie = c.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date().toUTCString()};path=/`)));
const cookies = document.cookie.split(';').map((item) => item.split('=')).reduce((acc, [k, v]) => (acc[k.trim().replace('"', '')] = v) && acc, {});

比较两个数组

// `a` and `b` are arrays
const isEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);
// Or
const isEqual = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
// Examples
isEqual([1, 2, 3], [1, 2, 3]); // true
isEqual([1, 2, 3], [1, '2', 3]); // false

将对象数组转换为单个对象

const toObject = (arr, key) => arr.reduce((a, b) => ({ ...a, [b[key]]: b }), {});
// Or
const toObject = (arr, key) => Object.fromEntries(arr.map((it) => [it[key], it]));
// Example
toObject([
{ id: '1', name: 'Alpha', gender: 'Male' },
{ id: '2', name: 'Bravo', gender: 'Male' },
{ id: '3', name: 'Charlie', gender: 'Female' }],
'id');
/*
{
'1': { id: '1', name: 'Alpha', gender: 'Male' },
'2': { id: '2', name: 'Bravo', gender: 'Male' },
'3': { id: '3', name: 'Charlie', gender: 'Female' }
}
*/

按对象数组的属性计数

const countBy = (arr, prop) => arr.reduce((prev, curr) => ((prev[curr[prop]] = ++prev[curr[prop]] || 1), prev), {});
// Example
countBy([
{ branch: 'audi', model: 'q8', year: '2019' },
{ branch: 'audi', model: 'rs7', year: '2020' },
{ branch: 'ford', model: 'mustang', year: '2019' },
{ branch: 'ford', model: 'explorer', year: '2020' },
{ branch: 'bmw', model: 'x7', year: '2020' },
],
'branch');
// { 'audi': 2, 'ford': 2, 'bmw': 1 }

检查数组是否为空

const isNotEmpty = (arr) => Array.isArray(arr) && Object.keys(arr).length > 0;
// Examples
isNotEmpty([]); // false
isNotEmpty([1, 2, 3]); // true

检查多个对象是否相等

const isEqual = (...objects) => objects.every((obj) => JSON.stringify(obj) === JSON.stringify(objects[0]));
// Examples
isEqual({ foo: 'bar' }, { foo: 'bar' }); // true
isEqual({ foo: 'bar' }, { bar: 'foo' }); // false

从对象数组中提取属性的值

const pluck = (objs, property) => objs.map((obj) => obj[property]);
// Example
pluck([
{ name: 'John', age: 20 },
{ name: 'Smith', age: 25 },
{ name: 'Peter', age: 30 },
],
'name');
// ['John', 'Smith', 'Peter']

反转对象的键和值

const invert = (obj) => Object.keys(obj).reduce((res, k) => Object.assign(res, { [obj[k]]: k }), {});
// Or
const invert = (obj) => Object.fromEntries(Object.entries(obj).map(([k, v]) => [v, k]));
// Example
invert({ a: '1', b: '2', c: '3' }); // { 1: 'a', 2: 'b', 3: 'c' }

从对象中删除所有空和未定义的属性

const removeNullUndefined = (obj) =>
Object.entries(obj)
.reduce((a, [k, v]) => (v == null ? a : ((a[k] = v), a)), {});
// Or
const removeNullUndefined = (obj) =>
Object.entries(obj)
.filter(([_, v]) => v != null)
.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {});
// Or
const removeNullUndefined = (obj) => Object.fromEntries(Object.entries(obj).filter(([_, v]) => v != null));
// Example
removeNullUndefined({
foo: null,
bar: undefined,
fuzz: 42});
// { fuzz: 42 }

按属性对对象进行排序

const sort = (obj) =>
Object.keys(obj)
.sort()
.reduce((p, c) => ((p[c] = obj[c]), p), {});
// Example
const colors = {
white: '#ffffff',
black: '#000000',
red: '#ff0000',
green: '#008000',
blue: '#0000ff',
};
sort(colors);
/*
{
black: '#000000',
blue: '#0000ff',
green: '#008000',
red: '#ff0000',
white: '#ffffff',
}
*/

检查一个对象是否是一个 Promise

const isPromise = (obj) =>
!!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';

检查对象是否为数组

const isArray = (obj) => Array.isArray(obj);

检查代码是否在 Node.js 中运行

const isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null;

检查代码是否在浏览器中运行

const isBrowser = typeof window === 'object' && typeof document === 'object';

将 URL 参数转换为对象

const getUrlParams = (query) =>Array.from(new URLSearchParams(query)).reduce((p, [k, v]) => Object.assign({}, p, { [k]: p[k] ? (Array.isArray(p[k]) ? p[k] : [p[k]]).concat(v) : v }),{});
// Examples
getUrlParams(location.search); // Get the parameters of the current URL
getUrlParams('foo=Foo&bar=Bar'); // { foo: "Foo", bar: "Bar" }
// Duplicate key
getUrlParams('foo=Foo&foo=Fuzz&bar=Bar'); // { foo: ["Foo", "Fuzz"], bar: "Bar" }

使用 Node crypto 模块生成随机字符串

const randomStr = () => require('crypto').randomBytes(32).toString('hex')

为 Promise 添加超时限制

function addTimeoutToPromise(targetPromise, timeout) {
    let timeoutHandle;
    const timeoutLimitPromise = new Promise((res, rej) => {
        timeoutHandle = setTimeout(
            () => rej(new Error("Timeout exceeded")),
            timeout
        );
    });
    return Promise.race([targetPromise, timeoutLimitPromise]).then((res) => {
        clearTimeout(timeoutHandle);
        return res;
    });
}

按顺序完成 Promise

function completeInSequence(promiseFactories) {
    return promiseFactories.reduce(
        (chain, promiseFactory) => chain.then(() => promiseFactory()),
        Promise.resolve()
    );
}

// 使用
completeInSequence([
    () => sleep(1000).then(() => console.log('1')),
    () => sleep(3000).then(() => console.log('2')),
    () => sleep(1000).then(() => console.log('3'))
])

只同时完成 N 个 Promise

function completePromisesInPool(
    promiseFactories,
    maxPoolSize
) {
    return new Promise((res) => {
        let nextPromise = 0;
        const runPromise = () => {
            nextPromise++;
            if (nextPromise > promiseFactories.length) {
                res();
                return;
            }
            return promiseFactories[nextPromise - 1]()
                .then(() => runPromise())
        }

        Array.from({
                length: maxPoolSize
            })
            .map(() => runPromise());
    })
}


completePromisesInPool([
    () => delay(1000).then(() => console.log('1')),
    () => delay(1000).then(() => console.log('2')),
    () => delay(1000).then(() => console.log('3')),
    () => delay(1000).then(() => console.log('4')),
    () => delay(1000).then(() => console.log('5')),
    () => delay(1000).then(() => console.log('6'))
], 2)

下载一个excel文档

function download(link, name) {
    if(!name) {
        name = link.slice(link.lastIndexOf('/') + 1)
    }
    const eleLink = document.createElement('a')
    eleLink.download = name
    eleLink.style.display = 'none'
    eleLink.href = link
    document.body.appendChild(eleLink)
    eleLink.click()
    document.body.removeChild(eleLink)
}

在浏览器中自定义下载一些内容

场景:我想下载一些DOM内容,我想下载一个JSON文件

/**
 * 浏览器下载静态文件
 * @param {String} name 文件名
 * @param {String} content 文件内容
 */
function downloadFile(name, content) {
    if (typeof name == 'undefined') {
        throw new Error('The first parameter name is a must')
    }
    if (typeof content == 'undefined') {
        throw new Error('The second parameter content is a must')
    }
    if (!(content instanceof Blob)) {
        content = new Blob([content])
    }
    const link = URL.createObjectURL(content)
    download(link, name)
}
//下载一个链接
function download(link, name) {
    if (!name) {//如果没有提供名字,从给的Link中截取最后一坨
        name =  link.slice(link.lastIndexOf('/') + 1)
    }
    let eleLink = document.createElement('a')
    eleLink.download = name
    eleLink.style.display = 'none'
    eleLink.href = link
    document.body.appendChild(eleLink)
    eleLink.click()
    document.body.removeChild(eleLink)
}

提供一个图片链接,点击下载

图片、pdf等文件,浏览器会默认执行预览,不能调用download方法进行下载,需要先把图片、pdf等文件转成blob,再调用download方法进行下载,转换的方式是使用axios请求对应的链接

//可以用来下载浏览器会默认预览的文件类型,例如mp4,jpg等
import axios from 'axios'
//提供一个link,完成文件下载,link可以是  http://xxx.com/xxx.xls
function downloadByLink(link,fileName){
    axios.request({
        url: link,
        responseType: 'blob' //关键代码,让axios把响应改成blob
    }).then(res => {
        const link=URL.createObjectURL(res.data)
        download(link, fileName)
    })
}

注意:会有同源策略的限制,需要配置转发

下载文件

// api 接口
// params 请求参数
// fileName 文件名

const downloadFile = (api, params, fileName, type = 'get') => {
  axios({
    method: type,
    url: api,
    responseType: 'blob',
    params: params
  }).then((res) => {
    let str = res.headers['content-disposition']
    if (!res || !str) {
      return
    }
    let suffix = ''
    // 截取文件名和文件类型
    if (str.lastIndexOf('.')) {
      fileName ? '' : fileName = decodeURI(str.substring(str.indexOf('=') + 1, str.lastIndexOf('.')))
      suffix = str.substring(str.lastIndexOf('.'), str.length)
    }
    //  如果支持微软的文件下载方式(ie10+浏览器)
    if (window.navigator.msSaveBlob) {
      try {
        const blobObject = new Blob([res.data]);
        window.navigator.msSaveBlob(blobObject, fileName + suffix);
      } catch (e) {
        console.log(e);
      }
    } else {
      //  其他浏览器
      let url = window.URL.createObjectURL(res.data)
      let link = document.createElement('a')
      link.style.display = 'none'
      link.href = url
      link.setAttribute('download', fileName + suffix)
      document.body.appendChild(link)
      link.click()
      document.body.removeChild(link)
      window.URL.revokeObjectURL(link.href);
    }
  }).catch((err) => {
    console.log(err.message);
  })
}

性能检测

有时你需要知道你的代码完成其执行需要多少时间。查看下面的代码示例,则可以帮助你完成检测工作。

const startTime = performance.now();
something();
const endTime = performance.now();
console.log("Cpde take ${startTime - endTime} milliseconds")

手机号脱敏

export const hideMobile = (mobile) => {
  return mobile.replace(/^(\d{3})\d{4}(\d{4})$/, "$1****$2")
}

开启全屏

export const launchFullscreen = (element) => {
  if (element.requestFullscreen) {
    element.requestFullscreen()
  } else if (element.mozRequestFullScreen) {
    element.mozRequestFullScreen()
  } else if (element.msRequestFullscreen) {
    element.msRequestFullscreen()
  } else if (element.webkitRequestFullscreen) {
    element.webkitRequestFullScreen()
  }
}

关闭全屏

export const exitFullscreen = () => {
  if (document.exitFullscreen) {
    document.exitFullscreen()
  } else if (document.msExitFullscreen) {
    document.msExitFullscreen()
  } else if (document.mozCancelFullScreen) {
    document.mozCancelFullScreen()
  } else if (document.webkitExitFullscreen) {
    document.webkitExitFullscreen()
  }
}

解析URL参数

export const getSearchParams = () => {
  const searchPar = new URLSearchParams(window.location.search)
  const paramsObj = {}
  for (const [key, value] of searchPar.entries()) {
    paramsObj[key] = value
  }
  return paramsObj
}

判断手机是Andoird还是IOS

export const getOSType=() => {
  let u = navigator.userAgent, app = navigator.appVersion;
  let isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1;
  let isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
  if (isIOS) {
    return 1;
  }
  if (isAndroid) {
    return 2;
  }
  return 3;
}

数组对象根据字段去重

export const uniqueArrayObject = (arr = [], key = 'id') => {
  if (arr.length === 0) return
  let list = []
  const map = {}
  arr.forEach((item) => {
    if (!map[item[key]]) {
      map[item[key]] = item
    }
  })
  list = Object.values(map)

  return list
}

滚动到页面顶部

export const scrollToTop = () => {
  const height = document.documentElement.scrollTop || document.body.scrollTop;
  if (height > 0) {
    window.requestAnimationFrame(scrollToTop);
    window.scrollTo(0, height - height / 8);
  }
}

滚动到元素位置

export const smoothScroll = element =>{
    document.querySelector(element).scrollIntoView({
        behavior: 'smooth'
    });
};

uuid

export const uuid = () => {
  const temp_url = URL.createObjectURL(new Blob())
  const uuid = temp_url.toString()
  URL.revokeObjectURL(temp_url) //释放这个url
  return uuid.substring(uuid.lastIndexOf('/') + 1)
}

金额格式化

// {number} number:要格式化的数字
// {number} decimals:保留几位小数
// {string} dec_point:小数点符号
// {string} thousands_sep:千分位符号

export const moneyFormat = (number, decimals, dec_point, thousands_sep) => {
  number = (number + '').replace(/[^0-9+-Ee.]/g, '')
  const n = !isFinite(+number) ? 0 : +number
  const prec = !isFinite(+decimals) ? 2 : Math.abs(decimals)
  const sep = typeof thousands_sep === 'undefined' ? ',' : thousands_sep
  const dec = typeof dec_point === 'undefined' ? '.' : dec_point
  let s = ''
  const toFixedFix = function(n, prec) {
    const k = Math.pow(10, prec)
    return '' + Math.ceil(n * k) / k
  }
  s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.')
  const re = /(-?\d+)(\d{3})/
  while (re.test(s[0])) {
    s[0] = s[0].replace(re, '$1' + sep + '$2')
  }

  if ((s[1] || '').length < prec) {
    s[1] = s[1] || ''
    s[1] += new Array(prec - s[1].length + 1).join('0')
  }
  return s.join(dec)
}

金额大写转换函数

function transform(tranvalue) {
  try {
    var i = 1;
    var dw2 = new Array("", "万", "亿"); //大单位
    var dw1 = new Array("拾", "佰", "仟"); //小单位
    var dw = new Array(
      "零",
      "壹",
      "贰",
      "叁",
      "肆",
      "伍",
      "陆",
      "柒",
      "捌",
      "玖"
    );
    //整数部分用
    //以下是小写转换成大写显示在合计大写的文本框中
    //分离整数与小数
    var source = splits(tranvalue);
    var num = source[0];
    var dig = source[1];
    //转换整数部分
    var k1 = 0; //计小单位
    var k2 = 0; //计大单位
    var sum = 0;
    var str = "";
    var len = source[0].length; //整数的长度
    for (i = 1; i <= len; i++) {
      var n = source[0].charAt(len - i); //取得某个位数上的数字
      var bn = 0;
      if (len - i - 1 >= 0) {
        bn = source[0].charAt(len - i - 1); //取得某个位数前一位上的数字
      }
      sum = sum + Number(n);
      if (sum != 0) {
        str = dw[Number(n)].concat(str); //取得该数字对应的大写数字,并插入到str字符串的前面
        if (n == "0") sum = 0;
      }
      if (len - i - 1 >= 0) {
        //在数字范围内
        if (k1 != 3) {
          //加小单位
          if (bn != 0) {
            str = dw1[k1].concat(str);
          }
          k1++;
        } else {
          //不加小单位,加大单位
          k1 = 0;
          var temp = str.charAt(0);
          if (temp == "万" || temp == "亿")
            //若大单位前没有数字则舍去大单位
            str = str.substr(1, str.length - 1);
          str = dw2[k2].concat(str);
          sum = 0;
        }
      }
      if (k1 == 3) {
        //小单位到千则大单位进一
        k2++;
      }
    }
    //转换小数部分
    var strdig = "";
    if (dig != "") {
      var n = dig.charAt(0);
      if (n != 0) {
        strdig += dw[Number(n)] + "角"; //加数字
      }
      var n = dig.charAt(1);
      if (n != 0) {
        strdig += dw[Number(n)] + "分"; //加数字
      }
    }
    str += "元" + strdig;
  } catch (e) {
    return "0元";
  }
  return str;
}
//拆分整数与小数
function splits(tranvalue) {
  var value = new Array("", "");
  temp = tranvalue.split(".");
  for (var i = 0; i < temp.length; i++) {
    value = temp;
  }
  return value;
}

模糊搜索

// list 原数组
// keyWord 查询的关键词
// attribute 数组需要检索属性

export const fuzzyQuery = (list, keyWord, attribute = 'name') => {
  const reg = new RegExp(keyWord)
  const arr = []
  for (let i = 0; i < list.length; i++) {
    if (reg.test(list[i][attribute])) {
      arr.push(list[i])
    }
  }
  return arr
}

遍历树节点

export const foreachTree = (data, callback, childrenName = 'children') => {
  for (let i = 0; i < data.length; i++) {
    callback(data[i])
    if (data[i][childrenName] && data[i][childrenName].length > 0) {
      foreachTree(data[i][childrenName], callback, childrenName)
    }
  }
}

检查当前 Tab 页是否在前台

const isBrowserTabInView = () => document.hidden;
isBrowserTabInView();

从日期中获取时间

const timeFromDate = date => date.toTimeString().slice(0, 8);
console.log(timeFromDate(new Date(2021, 0, 10, 17, 30, 0))); 
// Result: "17:30:00"
console.log(timeFromDate(new Date()));
// Result: will log the current time

保留小数点(非四舍五入)

使用 Math.pow() 方法,我们可以将一个数字截断到某个小数点。

const toFixed = (n, fixed) => ~~(Math.pow(10, fixed) * n) / Math.pow(10, fixed);
// Examples
toFixed(25.198726354, 1);       // 25.1
toFixed(25.198726354, 2);       // 25.19
toFixed(25.198726354, 3);       // 25.198
toFixed(25.198726354, 4);       // 25.1987
toFixed(25.198726354, 5);       // 25.19872
toFixed(25.198726354, 6);       // 25.198726

检查元素当前是否为聚焦状态

const elementIsInFocus = (el) => (el === document.activeElement);
elementIsInFocus(anyElement)
// Result: will return true if in focus, false if not in focus

检查浏览器是否支持触摸事件

const touchSupported = () => {
  ('ontouchstart' in window || window.DocumentTouch && document instanceof window.DocumentTouch);
}
console.log(touchSupported());
// Result: will return true if touch events are supported, false if not

检查当前用户是否为苹果设备

const isAppleDevice = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
console.log(isAppleDevice);
// Result: will return true if user is on an Apple device

获取所有参数平均值

const average = (...args) => args.reduce((a, b) => a + b) / args.length;
average(1, 2, 3, 4);
// Result: 2.5

转换华氏度/摄氏度。(这个应该很少在国内用到吧)

const celsiusToFahrenheit = (celsius) => celsius * 9/5 + 32;
const fahrenheitToCelsius = (fahrenheit) => (fahrenheit - 32) * 5/9;
// Examples
celsiusToFahrenheit(15);    // 59
celsiusToFahrenheit(0);     // 32
celsiusToFahrenheit(-20);   // -4
fahrenheitToCelsius(59);    // 15
fahrenheitToCelsius(32);    // 0

为元素添加on方法

Element.prototype.on = Element.prototype.addEventListener;

NodeList.prototype.on = function (event, fn) {[]['forEach'].call(this, function (el) {
        el.on(event, fn);
    });
    return this;
};

为元素添加trigger方法

Element.prototype.trigger = function(type, data) {
  var event = document.createEvent("htmlEvents");
  event.initEvent(type, true, true);
  event.data = data || {};
  event.eventName = type;
  event.target = this;
  this.dispatchEvent(event);
  return this;
};

NodeList.prototype.trigger = function(event) {
  []["forEach"].call(this, function(el) {
    el["trigger"](event);
  });
  return this;
};

加入收藏夹

function addFavorite(sURL, sTitle) {
  try {
    window.external.addFavorite(sURL, sTitle);
  } catch (e) {
    try {
      window.sidebar.addPanel(sTitle, sURL, "");
    } catch (e) {
      alert("加入收藏失败,请使用Ctrl+D进行添加");
    }
  }
}

提取页面代码中所有网址


var aa = document.documentElement.outerHTML
  .match(
    /(url\(|src=|href=)[\"\']*([^\"\'\(\)\<\>\[\] ]+)[\"\'\)]*|(http:\/\/[\w\-\.]+[^\"\'\(\)\<\>\[\] ]+)/gi
  )
  .join("\r\n")
  .replace(/^(src=|href=|url\()[\"\']*|[\"\'\>\) ]*$/gim, "");
alert(aa);

动态加载脚本文件

function appendscript(src, text, reload, charset) {
  var id = hash(src + text);
  if (!reload && in_array(id, evalscripts)) return;
  if (reload && $(id)) {
    $(id).parentNode.removeChild($(id));
  }

  evalscripts.push(id);
  var scriptNode = document.createElement("script");
  scriptNode.type = "text/JavaScript";
  scriptNode.id = id;
  scriptNode.charset = charset
    ? charset
    : BROWSER.firefox
    ? document.characterSet
    : document.charset;
  try {
    if (src) {
      scriptNode.src = src;
      scriptNode.onloadDone = false;
      scriptNode.onload = function() {
        scriptNode.onloadDone = true;
        jsLOADED[src] = 1;
      };
      scriptNode.onreadystatechange = function() {
        if (
          (scriptNode.readyState == "loaded" ||
            scriptNode.readyState == "complete") &&
          !scriptNode.onloadDone
        ) {
          scriptNode.onloadDone = true;
          jsLOADED[src] = 1;
        }
      };
    } else if (text) {
      scriptNode.text = text;
    }
    document.getElementsByTagName("head")[0].appendChild(scriptNode);
  } catch (e) {}
}

确认是否是键盘有效输入值

function checkKey(iKey) {
  if (iKey == 32 || iKey == 229) {
    return true;
  } /*空格和异常*/
  if (iKey > 47 && iKey < 58) {
    return true;
  } /*数字*/
  if (iKey > 64 && iKey < 91) {
    return true;
  } /*字母*/
  if (iKey > 95 && iKey < 108) {
    return true;
  } /*数字键盘1*/
  if (iKey > 108 && iKey < 112) {
    return true;
  } /*数字键盘2*/
  if (iKey > 185 && iKey < 193) {
    return true;
  } /*符号1*/
  if (iKey > 218 && iKey < 223) {
    return true;
  } /*符号2*/
  return false;
}

全角半角转换

//iCase: 0全到半,1半到全,其他不转化
function chgCase(sStr, iCase) {
  if (
    typeof sStr != "string" ||
    sStr.length <= 0 ||
    !(iCase === 0 || iCase == 1)
  ) {
    return sStr;
  }
  var i,
    oRs = [],
    iCode;
  if (iCase) {
    /*半->全*/
    for (i = 0; i < sStr.length; i += 1) {
      iCode = sStr.charCodeAt(i);
      if (iCode == 32) {
        iCode = 12288;
      } else if (iCode < 127) {
        iCode += 65248;
      }
      oRs.push(String.fromCharCode(iCode));
    }
  } else {
    /*全->半*/
    for (i = 0; i < sStr.length; i += 1) {
      iCode = sStr.charCodeAt(i);
      if (iCode == 12288) {
        iCode = 32;
      } else if (iCode > 65280 && iCode < 65375) {
        iCode -= 65248;
      }
      oRs.push(String.fromCharCode(iCode));
    }
  }
  return oRs.join("");
}

全角转换为半角函数

function toCDB(str) {
  var result = "";
  for (var i = 0; i < str.length; i++) {
    code = str.charCodeAt(i);
    if (code >= 65281 && code <= 65374) {
      result += String.fromCharCode(str.charCodeAt(i) - 65248);
    } else if (code == 12288) {
      result += String.fromCharCode(str.charCodeAt(i) - 12288 + 32);
    } else {
      result += str.charAt(i);
    }
  }
  return result;
}

半角转换为全角函数

function toDBC(str) {
  var result = "";
  for (var i = 0; i < str.length; i++) {
    code = str.charCodeAt(i);
    if (code >= 33 && code <= 126) {
      result += String.fromCharCode(str.charCodeAt(i) + 65248);
    } else if (code == 32) {
      result += String.fromCharCode(str.charCodeAt(i) + 12288 - 32);
    } else {
      result += str.charAt(i);
    }
  }
  return result;
}

版本对比

function compareVersion(v1, v2) {
  v1 = v1.split(".");
  v2 = v2.split(".");

  var len = Math.max(v1.length, v2.length);

  while (v1.length < len) {
    v1.push("0");
  }

  while (v2.length < len) {
    v2.push("0");
  }

  for (var i = 0; i < len; i++) {
    var num1 = parseInt(v1[i]);
    var num2 = parseInt(v2[i]);

    if (num1 > num2) {
      return 1;
    } else if (num1 < num2) {
      return -1;
    }
  }
  return 0;
}

压缩css样式代码

function compresscss(s) {
  //压缩代码
  s = s.replace(/\/\*(.|\n)*?\*\//g, ""); //删除注释
  s = s.replace(/\s*([\{\}\:\;\,])\s*/g, "$1");
  s = s.replace(/\,[\s\.\#\d]*\{/g, "{"); //容错处理
  s = s.replace(/;\s*;/g, ";"); //清除连续分号
  s = s.match(/^\s*(\S+(\s+\S+)*)\s*$/); //去掉首尾空白
  return s == null ? "" : s[1];
}

字符串长度截取

function cutstr(str, len) {
    var temp,
        icount = 0,
        patrn = /[^\x00-\xff]/,
        strre = "";
    for (var i = 0; i < str.length; i++) {
        if (icount < len - 1) {
            temp = str.substr(i, 1);
                if (patrn.exec(temp) == null) {
                   icount = icount + 1
            } else {
                icount = icount + 2
            }
            strre += temp
            } else {
            break;
        }
    }
    return strre + "..."
}

时间日期格式转换

Date.prototype.format = function(formatStr) {
  var str = formatStr;
  var Week = ["日", "一", "二", "三", "四", "五", "六"];
  str = str.replace(/yyyy|YYYY/, this.getFullYear());
  str = str.replace(
    /yy|YY/,
    this.getYear() % 100 > 9
      ? (this.getYear() % 100).toString()
      : "0" + (this.getYear() % 100)
  );
  str = str.replace(
    /MM/,
    this.getMonth() + 1 > 9
      ? (this.getMonth() + 1).toString()
      : "0" + (this.getMonth() + 1)
  );
  str = str.replace(/M/g, this.getMonth() + 1);
  str = str.replace(/w|W/g, Week[this.getDay()]);
  str = str.replace(
    /dd|DD/,
    this.getDate() > 9 ? this.getDate().toString() : "0" + this.getDate()
  );
  str = str.replace(/d|D/g, this.getDate());
  str = str.replace(
    /hh|HH/,
    this.getHours() > 9 ? this.getHours().toString() : "0" + this.getHours()
  );
  str = str.replace(/h|H/g, this.getHours());
  str = str.replace(
    /mm/,
    this.getMinutes() > 9
      ? this.getMinutes().toString()
      : "0" + this.getMinutes()
  );
  str = str.replace(/m/g, this.getMinutes());
  str = str.replace(
    /ss|SS/,
    this.getSeconds() > 9
      ? this.getSeconds().toString()
      : "0" + this.getSeconds()
  );
  str = str.replace(/s|S/g, this.getSeconds());
  return str;
};

// 或
Date.prototype.format = function(format) {
  var o = {
    "M+": this.getMonth() + 1, //month
    "d+": this.getDate(), //day
    "h+": this.getHours(), //hour
    "m+": this.getMinutes(), //minute
    "s+": this.getSeconds(), //second
    "q+": Math.floor((this.getMonth() + 3) / 3), //quarter
    S: this.getMilliseconds() //millisecond
  };
  if (/(y+)/.test(format))
    format = format.replace(
      RegExp.$1,
      (this.getFullYear() + "").substr(4 - RegExp.$1.length)
    );
  for (var k in o) {
    if (new RegExp("(" + k + ")").test(format))
      format = format.replace(
        RegExp.$1,
        RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length)
      );
  }
  return format;
};
alert(new Date().format("yyyy-MM-dd hh:mm:ss"));

判断是否以某个字符串结束

String.prototype.endWith = function(s) {
  var d = this.length - s.length;
  return d >= 0 && this.lastIndexOf(s) == d;
};

返回脚本内容

function evalscript(s) {
  if (s.indexOf("<script") == -1) return s;
  var p = /<script[^\>]*?>([^\x00]*?)<\/script>/gi;
  var arr = [];
  while ((arr = p.exec(s))) {
    var p1 = /<script[^\>]*?src=\"([^\>]*?)\"[^\>]*?(reload=\"1\")?(?:charset=\"([\w\-]+?)\")?><\/script>/i;
    var arr1 = [];
    arr1 = p1.exec(arr[0]);
    if (arr1) {
      appendscript(arr1[1], "", arr1[2], arr1[3]);
    } else {
      p1 = /<script(.*?)>([^\x00]+?)<\/script>/i;
      arr1 = p1.exec(arr[0]);
      appendscript("", arr1[2], arr1[1].indexOf("reload=") != -1);
    }
  }
  return s;
}

格式化CSS样式代码

function formatCss(s) {
  //格式化代码
  s = s.replace(/\s*([\{\}\:\;\,])\s*/g, "$1");
  s = s.replace(/;\s*;/g, ";"); //清除连续分号
  s = s.replace(/\,[\s\.\#\d]*{/g, "{");
  s = s.replace(/([^\s])\{([^\s])/g, "$1 {\n\t$2");
  s = s.replace(/([^\s])\}([^\n]*)/g, "$1\n}\n$2");
  s = s.replace(/([^\s]);([^\s\}])/g, "$1;\n\t$2");
  return s;
}

获取cookie值

function getCookie(name) {
  var arr = document.cookie.match(new RegExp("(^| )" + name + "=([^;]*)(;|$)"));
  if (arr != null) return unescape(arr[2]);
  return null;
}

获得URL中GET参数值

// 用法:如果地址是 test.htm?t1=1&t2=2&t3=3, 那么能取得:GET["t1"], GET["t2"], GET["t3"]
function getGet() {
  querystr = window.location.href.split("?");
  if (querystr[1]) {
    GETs = querystr[1].split("&");
    GET = [];
    for (i = 0; i < GETs.length; i++) {
      tmp_arr = GETs.split("=");
      key = tmp_arr[0];
      GET[key] = tmp_arr[1];
    }
  }
  return querystr[1];
}

获取URL上的参数

// 获取URL中的某参数值,不区分大小写
// 获取URL中的某参数值,不区分大小写,
// 默认是取'hash'里的参数,
// 如果传其他参数支持取‘search’中的参数
// @param {String} name 参数名称
export function getUrlParam(name, type = "hash") {
  let newName = name,
    reg = new RegExp("(^|&)" + newName + "=([^&]*)(&|$)", "i"),
    paramHash = window.location.hash.split("?")[1] || "",
    paramSearch = window.location.search.split("?")[1] || "",
    param;

  type === "hash" ? (param = paramHash) : (param = paramSearch);

  let result = param.match(reg);

  if (result != null) {
    return result[2].split("/")[0];
  }
  return null;
}

获取移动设备初始化大小

function getInitZoom() {
  if (!this._initZoom) {
    var screenWidth = Math.min(screen.height, screen.width);
    if (this.isAndroidMobileDevice() && !this.isNewChromeOnAndroid()) {
      screenWidth = screenWidth / window.devicePixelRatio;
    }
    this._initZoom = screenWidth / document.body.offsetWidth;
  }
  return this._initZoom;
}

检验URL链接是否有效

function getUrlState(URL) {
  var xmlhttp = new ActiveXObject("microsoft.xmlhttp");
  xmlhttp.Open("GET", URL, false);
  try {
    xmlhttp.Send();
  } catch (e) {
  } finally {
    var result = xmlhttp.responseText;
    if (result) {
      if (xmlhttp.Status == 200) {
        return true;
      } else {
        return false;
      }
    } else {
      return false;
    }
  }
}

判断是否安卓移动设备访问

function isAndroidMobileDevice() {
  return /android/i.test(navigator.userAgent.toLowerCase());
}

判断是否苹果移动设备访问

function isAppleMobileDevice() {
  return /iphone|ipod|ipad|Macintosh/i.test(navigator.userAgent.toLowerCase());
}

判断是否移动设备

function isMobile() {
  if (typeof this._isMobile === "boolean") {
    return this._isMobile;
  }
  var screenWidth = this.getScreenWidth();
  var fixViewPortsExperiment =
    rendererModel.runningExperiments.FixViewport ||
    rendererModel.runningExperiments.fixviewport;
  var fixViewPortsExperimentRunning =
    fixViewPortsExperiment && fixViewPortsExperiment.toLowerCase() === "new";
  if (!fixViewPortsExperiment) {
    if (!this.isAppleMobileDevice()) {
      screenWidth = screenWidth / window.devicePixelRatio;
    }
  }
  var isMobileScreenSize = screenWidth < 600;
  var isMobileUserAgent = false;
  this._isMobile = isMobileScreenSize && this.isTouchScreen();
  return this._isMobile;
}

判断是否是移动设备访问

function isMobileUserAgent() {
  return /iphone|ipod|android.*mobile|windows.*phone|blackberry.*mobile/i.test(
    window.navigator.userAgent.toLowerCase()
  );
}

判断鼠标是否移出事件

function isMouseout(e, handler) {
  if (e.type !== "mouseout") {
    return false;
  }
  var reltg = e.relatedTarget
    ? e.relatedTarget
    : e.type === "mouseout"
    ? e.toElement
    : e.fromElement;
  while (reltg && reltg !== handler) {
    reltg = reltg.parentNode;
  }
  return reltg !== handler;
}

判断是否Touch屏幕

function isTouchScreen() {
  return (
    "ontouchstart" in window ||
    (window.DocumentTouch && document instanceof DocumentTouch)
  );
}

判断是否为网址

function isURL(strUrl) {
  var regular = /^\b(((https?|ftp):\/\/)?[-a-z0-9]+(\.[-a-z0-9]+)*\.(?:com|edu|gov|int|mil|net|org|biz|info|name|museum|asia|coop|aero|[a-z][a-z]|((25[0-5])|(2[0-4]\d)|(1\d\d)|([1-9]\d)|\d))\b(\/[-a-z0-9_:\@&?=+,.!\/~%\$]*)?)$/i;
  if (regular.test(strUrl)) {
    return true;
  } else {
    return false;
  }
}

判断是否打开视窗

function isViewportOpen() {
  return !!document.getElementById("wixMobileViewport");
}

加载样式文件

function loadStyle(url) {
  try {
    document.createStyleSheet(url);
  } catch (e) {
    var cssLink = document.createElement("link");
    cssLink.rel = "stylesheet";
    cssLink.type = "text/css";
    cssLink.href = url;
    var head = document.getElementsByTagName("head")[0];
    head.appendChild(cssLink);
  }
}

替换地址栏

function locationReplace(url) {
  if (history.replaceState) {
    history.replaceState(null, document.title, url);
    history.go(0);
  } else {
    location.replace(url);
  }
}

解决offsetX兼容性问题

// 针对火狐不支持offsetX/Y
function getOffset(e) {
  var target = e.target, // 当前触发的目标对象
    eventCoord,
    pageCoord,
    offsetCoord;

  // 计算当前触发元素到文档的距离
  pageCoord = getPageCoord(target);

  // 计算光标到文档的距离
  eventCoord = {
    X: window.pageXOffset + e.clientX,
    Y: window.pageYOffset + e.clientY
  };

  // 相减获取光标到第一个定位的父元素的坐标
  offsetCoord = {
    X: eventCoord.X - pageCoord.X,
    Y: eventCoord.Y - pageCoord.Y
  };
  return offsetCoord;
}

function getPageCoord(element) {
  var coord = { X: 0, Y: 0 };
  // 计算从当前触发元素到根节点为止,
  // 各级 offsetParent 元素的 offsetLeft 或 offsetTop 值之和
  while (element) {
    coord.X += element.offsetLeft;
    coord.Y += element.offsetTop;
    element = element.offsetParent;
  }
  return coord;
}

打开一个窗体通用方法

function openWindow(url, windowName, width, height) {
  var x = parseInt(screen.width / 2.0) - width / 2.0;
  var y = parseInt(screen.height / 2.0) - height / 2.0;
  var isMSIE = navigator.appName == "Microsoft Internet Explorer";
  if (isMSIE) {
    var p = "resizable=1,location=no,scrollbars=no,width=";
    p = p + width;
    p = p + ",height=";
    p = p + height;
    p = p + ",left=";
    p = p + x;
    p = p + ",top=";
    p = p + y;
    retval = window.open(url, windowName, p);
  } else {
    var win = window.open(
      url,
      "ZyiisPopup",
      "top=" +
        y +
        ",left=" +
        x +
        ",scrollbars=" +
        scrollbars +
        ",dialog=yes,modal=yes,width=" +
        width +
        ",height=" +
        height +
        ",resizable=no"
    );
    eval("try { win.resizeTo(width, height); } catch(e) { }");
    win.focus();
  }
}

将键值对拼接成URL带参数


export default const fnParams2Url = obj=> {
    let aUrl = []
    let fnAdd = function(key, value) {
        return key + '=' + value
    }
    for (var k in obj) {
        aUrl.push(fnAdd(k, obj[k]))
    }

    return encodeURIComponent(aUrl.join('&'))
}

去掉url前缀

function removeUrlPrefix(a) {
    a = a
        .replace(//g, ":")
        .replace(//g, ".")
        .replace(//g, "/");
    while (
        trim(a)
        .toLowerCase()
        .indexOf("http://") == 0
    ) {
        a = trim(a.replace(/http:\/\//i, ""));
    }
    return a;
}

替换全部

String.prototype.replaceAll = function(s1, s2) {
  return this.replace(new RegExp(s1, "gm"), s2);
};

resize的操作

(function() {
    var fn = function() {
        var w = document.documentElement
            ? document.documentElement.clientWidth
            : document.body.clientWidth,
        r = 1255,
        b = Element.extend(document.body),
        classname = b.className;
        if (w < r) {
        //当窗体的宽度小于1255的时候执行相应的操作
        } else {
        //当窗体的宽度大于1255的时候执行相应的操作
        }
    };
    if (window.addEventListener) {
        window.addEventListener("resize", function() {
        fn();
        });
    } else if (window.attachEvent) {
        window.attachEvent("onresize", function() {
        fn();
        });
    }
    fn();
})();

设置cookie值

function setCookie(name, value, Hours) {
  var d = new Date();
  var offset = 8;
  var utc = d.getTime() + d.getTimezoneOffset() * 60000;
  var nd = utc + 3600000 * offset;
  var exp = new Date(nd);
  exp.setTime(exp.getTime() + Hours * 60 * 60 * 1000);
  document.cookie =
    name +
    "=" +
    escape(value) +
    ";path=/;expires=" +
    exp.toGMTString() +
    ";domain=360doc.com;";
}

设为首页

function setHomepage() {
  if (document.all) {
    document.body.style.behavior = "url(#default#homepage)";
    document.body.setHomePage("http://w3cboy.com");
  } else if (window.sidebar) {
    if (window.netscape) {
      try {
        netscape.security.PrivilegeManager.enablePrivilege(
          "UniversalXPConnect"
        );
      } catch (e) {
        alert(
          "该操作被浏览器拒绝,如果想启用该功能,请在地址栏内输入 about:config,然后将项 signed.applets.codebase_principal_support 值该为true"
        );
      }
    }
    var prefs = Components.classes[
      "@mozilla.org/preferences-service;1"
    ].getService(Components.interfaces.nsIPrefBranch);
    prefs.setCharPref("browser.startup.homepage", "http://w3cboy.com");
  }
}

按字母排序,对每行进行数组排序

function setSort() {
  var text = K1.value
    .split(/[\r\n]/)
    .sort()
    .join("\r\n"); //顺序
  var test = K1.value
    .split(/[\r\n]/)
    .sort()
    .reverse()
    .join("\r\n"); //反序
  K1.value = K1.value != text ? text : test;
}

清除脚本内容

function stripscript(s) {
  return s.replace(/<script.*?>.*?<\/script>/gi, "");
}

时间个性化输出功能

/*
1、< 60s, 显示为“刚刚”
2、>= 1min && < 60 min, 显示与当前时间差“XX分钟前”
3、>= 60min && < 1day, 显示与当前时间差“今天 XX:XX”
4、>= 1day && < 1year, 显示日期“XX月XX日 XX:XX”
5、>= 1year, 显示具体日期“XXXX年XX月XX日 XX:XX”
*/
function timeFormat(time) {
  var date = new Date(time),
    curDate = new Date(),
    year = date.getFullYear(),
    month = date.getMonth() + 10,
    day = date.getDate(),
    hour = date.getHours(),
    minute = date.getMinutes(),
    curYear = curDate.getFullYear(),
    curHour = curDate.getHours(),
    timeStr;

  if (year < curYear) {
    timeStr = year + "年" + month + "月" + day + "日 " + hour + ":" + minute;
  } else {
    var pastTime = curDate - date,
      pastH = pastTime / 3600000;

    if (pastH > curHour) {
      timeStr = month + "月" + day + "日 " + hour + ":" + minute;
    } else if (pastH >= 1) {
      timeStr = "今天 " + hour + ":" + minute + "分";
    } else {
      var pastM = curDate.getMinutes() - minute;
      if (pastM > 1) {
        timeStr = pastM + "分钟前";
      } else {
        timeStr = "刚刚";
      }
    }
  }
  return timeStr;
}

清除空格

String.prototype.trim = function() {
  var reExtraSpace = /^\s*(.*?)\s+$/;
  return this.replace(reExtraSpace, "$1");
};

// 清除左空格
function ltrim(s) {
  return s.replace(/^(\s*| *)/, "");
}

// 清除右空格
function rtrim(s) {
  return s.replace(/(\s*| *)$/, "");
}

随机数时间戳

function uniqueId() {
  var a = Math.random,
    b = parseInt;
  return (
    Number(new Date()).toString() + b(10 * a()) + b(10 * a()) + b(10 * a())
  );
}

实现utf8解码

function utf8_decode(str_data) {
  var tmp_arr = [],
    i = 0,
    ac = 0,
    c1 = 0,
    c2 = 0,
    c3 = 0;
  str_data += "";
  while (i < str_data.length) {
    c1 = str_data.charCodeAt(i);
    if (c1 < 128) {
      tmp_arr[ac++] = String.fromCharCode(c1);
      i++;
    } else if (c1 > 191 && c1 < 224) {
      c2 = str_data.charCodeAt(i + 1);
      tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));
      i += 2;
    } else {
      c2 = str_data.charCodeAt(i + 1);
      c3 = str_data.charCodeAt(i + 2);
      tmp_arr[ac++] = String.fromCharCode(
        ((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)
      );
      i += 3;
    }
  }
  return tmp_arr.join("");
}

校验是否包含中文字符(包括中文标点符号)

function haveCNChars(str){
    return /[\u4e00-\u9fa5]/.test(str);
}

清除所有中文字符

function clearCNCharsAndSpaces(str){
    return str.replace(/[\u4e00-\u9fa5 ]/g,'');
}

生成一个带有随机数的列表

Array.from({ length: 1000 }, Math.random)
// [ 0.6163093133259432, 0.8877401276499153, 0.4094354756035987, ...] - 1000 items

生成一个带有数字的列表

Array.from({ length: 1000 }, (v, i) => i)
// [0, 1, 2, 3, 4, 5, 6....999]

RGB→转换为十六进制

const rgb2hex = ([r, g, b]) =>
  `#${(1 << 24) + (r << 16) + (g << 8) + b}`.toString(16).substr(1);

rgb2hex([76, 11, 181]);
// #4c0bb5

转换十六进制→RGB

怎么把它转换回去?这是实现该目标的一种好方法。

const hex2rgb = hex =>
  [1, 3, 5].map((h) => parseInt(hex.substring(h, h + 2), 16));

hex2rgb("#4c0bb5");
// [76, 11, 181]

奇数或偶数

使用 位 运算的方式:

const value = 232;

if (value & 1) console.log("odd");
else console.log("even");
// even

距离过去到现在时间表示

有时我们需要打印6分钟前的日期,但不希望很大的库来完成。这里有一个小片段可以做到这一点:

const fromAgo = (date) => {
  const ms = Date.now() - date.getTime();
  const seconds = Math.round(ms / 1000);
  const minutes = Math.round(ms / 60000);
  const hours = Math.round(ms / 3600000);
  const days = Math.round(ms / 86400000);
  const months = Math.round(ms / 2592000000);
  const years = Math.round(ms / 31104000000);

  switch (true) {
    case seconds < 60:
      return `${seconds} second(s) ago"`;
    case minutes < 60:
      return `${minutes} minute(s) ago"`;
    case hours < 24:
      return `${hours} hour(s) ago"`;
    case days < 30:
      return `${days} day(s) ago`;
    case months < 12:
      return `${months} month(s) ago`;
    default:
      return `${years} year(s) ago`;
  }
};

const createdAt = new Date(2021, 0, 5);
fromAgo(createdAt); // 14 day(s) ago;

异步并发数限制


/**
 * 关键点
 * 1. new promise 一经创建,立即执行
 * 2. 使用 Promise.resolve().then 可以把任务加到微任务队列,防止立即执行迭代方法
 * 3. 微任务处理过程中,产生的新的微任务,会在同一事件循环内,追加到微任务队列里
 * 4. 使用 race 在某个任务完成时,继续添加任务,保持任务按照最大并发数进行执行
 * 5. 任务完成后,需要从 doingTasks 中移出
 */
function limit(count, array, iterateFunc) {
  const tasks = []
  const doingTasks = []
  let i = 0
  const enqueue = () => {
    if (i === array.length) {
      return Promise.resolve()
    }
    const task = Promise.resolve().then(() => iterateFunc(array[i++]))
    tasks.push(task)
    const doing = task.then(() => doingTasks.splice(doingTasks.indexOf(doing), 1))
    doingTasks.push(doing)
    const res = doingTasks.length >= count ? Promise.race(doingTasks) : Promise.resolve()
    return res.then(enqueue)
  };
  return enqueue().then(() => Promise.all(tasks))
}

// test
const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i))
limit(2, [1000, 1000, 1000, 1000], timeout).then((res) => {
  console.log(res)
})

异步串行 | 异步并行

// 字节面试题,实现一个异步加法
function asyncAdd(a, b, callback) {
  setTimeout(function () {
    callback(null, a + b);
  }, 500);
}

// 解决方案
// 1. promisify
const promiseAdd = (a, b) => new Promise((resolve, reject) => {
  asyncAdd(a, b, (err, res) => {
    if (err) {
      reject(err)
    } else {
      resolve(res)
    }
  })
})

// 2. 串行处理
async function serialSum(...args) {
  return args.reduce((task, now) => task.then(res => promiseAdd(res, now)), Promise.resolve(0))
}

// 3. 并行处理
async function parallelSum(...args) {
  if (args.length === 1) return args[0]
  const tasks = []
  for (let i = 0; i < args.length; i += 2) {
    tasks.push(promiseAdd(args[i], args[i + 1] || 0))
  }
  const results = await Promise.all(tasks)
  return parallelSum(...results)
}

// 测试
(async () => {
  console.log('Running...');
  const res1 = await serialSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
  console.log(res1)
  const res2 = await parallelSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
  console.log(res2)
  console.log('Done');
})()

vue reactive

// Dep module
class Dep {
  static stack = []
  static target = null
  deps = null

  constructor() {
    this.deps = new Set()
  }

  depend() {
    if (Dep.target) {
      this.deps.add(Dep.target)
    }
  }

  notify() {
    this.deps.forEach(w => w.update())
  }

  static pushTarget(t) {
    if (this.target) {
      this.stack.push(this.target)
    }
    this.target = t
  }

  static popTarget() {
    this.target = this.stack.pop()
  }
}

// reactive
function reactive(o) {
  if (o && typeof o === 'object') {
    Object.keys(o).forEach(k => {
      defineReactive(o, k, o[k])
    })
  }
  return o
}

function defineReactive(obj, k, val) {
  let dep = new Dep()
  Object.defineProperty(obj, k, {
    get() {
      dep.depend()
      return val
    },
    set(newVal) {
      val = newVal
      dep.notify()
    }
  })
  if (val && typeof val === 'object') {
    reactive(val)
  }
}

// watcher
class Watcher {
  constructor(effect) {
    this.effect = effect
    this.update()
  }

  update() {
    Dep.pushTarget(this)
    this.value = this.effect()
    Dep.popTarget()
    return this.value
  }
}

// 测试代码
const data = reactive({
  msg: 'aaa'
})

new Watcher(() => {
  console.log('===> effect', data.msg);
})

setTimeout(() => {
  data.msg = 'hello'
}, 1000)

uniapp 再次返回退出应用

let main = plus.android.runtimeMainActivity();
//为了防止快速点按返回键导致程序退出,所以重写quit方法改为隐藏至后台
plus.runtime.quit = function() {
    main.moveTaskToBack(false);
};
//重写toast方法如果内容为 ‘再次返回退出应用’ 就隐藏应用,其他正常toast
plus.nativeUI.toast = (function(str) {
    if (str =='再次返回退出应用') {
        plus.runtime.quit();
    } else {
        uni.showToast({
            title: '再次返回退出应用',
            icon: 'none'
        })
    }
});

函数重载

  • 定义
function addMethod(object, name, fn) {
    const old = object[name]
    object[name] = function (...args) {
        if (args.length === fn.length) {
            return fn.apply(this, args)
        } else if (typeof old === 'function') {
            return old.apply(this, args)
        }
    }
}

module.exports = addMethod
  • 使用
const addMethod = require('./addMethod')

const search = {}

addMethod(search, 'find', () => {
    console.log('查询所有用户')
})

addMethod(search, 'find', (name) => {
    console.log('按照姓名查询用户')
})

addMethod(search, 'find', (firstName, lastName) => {
    console.log('按照姓和名查询用户')
})

search.find('a', 'asdfa')

使用代理实现单例

export function singleton(className) {
    let ins
    return new Proxy(className, {
        construct(target, args) {
            if (!ins) {
                ins = new target(...args)
            }

            return ins
        }
    })
}

ajax进度封装

export function request(options = {}) {
    const { url, method = 'get', data = null, onProgress } = options

    // TODO XHR
    // return new Promise(resolve => {
    //     const xhr = new XMLHttpRequest()

    //     xhr.addEventListener('readystatechange', () => {
    //         if (xhr.readyState === xhr.DONE) resolve(xhr.responseText)
    //     })

    //     xhr.addEventListener("progress", e => {
    //         console.log(e.loaded, e.total)
    //         onProgress && onProgress({
    //             loaded: e.loaded,
    //             total: e.total
    //         })
    //     })

    //     xhr.upload.addEventListener('progress', e => {
    //         console.log(e.loaded, e.total)
    //     })

    //     xhr.open(method, url)
    //     xhr.send(data)
    // })

    // TODO FETCH
    return new Promise(async resolve => {
        const resp = await fetch(url, {
            method,
            body: data
        })

        const total = +resp.headers.get('content-length')
        const decoder = new TextDecoder()
        let body = ''
        const reader = resp.body.getReader()
        let loaded = 0

        while(true) {
            const { done, value } = await reader.read()
            if (done) break
            loaded += value.length
            body += decoder.decode(value)
            console.log(loaded, total)
            onProgress && onProgress({
                loaded,
                total
            })
        }

        resolve(body)
    })
}

给fetch实现超时功能

function createFetch(timeout) {
    return (resouce, options) => {
        const controller = new AbortController()
        options = options || {}
        options.signal = controller.signal

        setTimeout(() => {
            controller.abort()
        }, timeout)

        return fetch(resouce, options)
    }
}

// 使用
createFetch(100)('http://localhost:8080/api')

性能优化

// 不优化
for (let i = 0; i <= 100; i++) {
    const el = document.createElement('div')
    el.innerHTML = i
    document.body.appendChild(el)
}

// 优化
const fragment = document.createDocumentFragment()
for (let i = 0; i <= 100; i++) {
    const el = document.createElement('div')
    el.innerHTML = i
    fragment.appendChild(el)
}
document.body.appendChild(fragment)

手写memoize函数

function memoize(func) {
    const memoized = function (...args) {
        // 缓存是否存在
        const key = JSON.stringify(args)

        if (memoized.cache.has(key)) return memoized.cache.get(key)

        const result = func.apply(this, args)
        memoized.cache.set(key, result)
        return result
    }

    memoized.cache = new Map()

    return  memoized
}

function getArea(r) {
    console.log('执行了getArea');
    return Math.PI * r * r
}

const getAreaUseMemoize = memoize(getArea)

const r1 = getAreaUseMemoize(15)
const r2 = getAreaUseMemoize(15)
const r3 = getAreaUseMemoize(15)

// 输出
// 执行了getArea
// 706.8583470577034
// 706.8583470577034
// 706.8583470577034

深拷贝

function deepClone(obj) {
    return new Promise(resolve => {
        const { port1, port2 } = new MessageChannel()

        port1.postMessage(obj)

        port2.onmessage = (msg) => {
            resolve(msg.data)
        }
    })
}

可组合的散列数值,权限判断

// TODO 定义权限
// 可新增
const CREATE = 0b0001
// 可删除
const DELETE = 0b0010
// 可修改
const UPDATE = 0b0100
// 可浏览详情
const DETAIL = 0b1000

// 赋值权限
const result = CREATE | DELETE // 新增删除权限

// 判断是否有修改权限
(result & UPDATE) === UPDATE

// 删除 删除权限
result ^ DELETE

图片转base64

base64: 26个小写字母 + 26个大写字母 + 10个数字 + 1个'+' + 1个'/'

<input type="file" />
<img src="" alt="" id="preview">
const inp = document.querySelector('input')
const img = document.querySelector('#preview')

inp.onchange = function() {
    const reader = new FileReader()

    reader.readAsDataURL(this.files[0])

    reader.onload = function (e) {
        img.src = e.target.result
    }
}

不使用eval动态生成js

<script src="data:application/javascript,alert(1)"></script>

判断一个值是否是 Promise like

function isPromiseLike(value) {
    return (
        value !== null
        (typeof value === 'object' || typeof value === 'function') && typeof value.then === 'function'
    )
}

立即执行函数(IIFE)提升性能

var addEvent = (function () {
    if (ele.addEventListener) {
        return function (ele, eventName, handler) {
            ele.addEventListener(eventName, handler)
        }
    } else if (ele.attachEvent) {
        return function (ele, eventName, handler) {
            ele.attachEvent('on' + eventName, handler)
        }
    } else {
        return function (ele, eventName, handler) {
            ele['on' + eventName] = handler
        }
    }
})()
// 发出http请求,适配于浏览器和node环境
const request = (() => {
    if (typeof window !== 'undefined') {
        return (options) => {
            // 浏览器ajax
        }
    } else {
        return (options) => {
            // node的http
        }
    }
})()
const createRemoveSpace = () => {
    const reg = /\s/g,
        replacement = ''
    return str => str.replace(reg, replacement)
}

const createSpace = createRemoveSpace()
createSpace('dsdfaf dd dfdf')
createSpace('dsdfaf dd dfdf')
createSpace('dsdfaf dd dfdf')
createSpace('dsdfaf dd dfdf')

createSpace = null

消除异步的传染性

async function getUser() {
    return await fetch('https://my-json-server.typicode.com/typicode/demo/profile').then(resp => resp.json())
}

async function m1() {
    // other works
    return await getUser()
}

async function m2() {
    // other works
    return await m1()
}

async function m3() {
    // other works
    return await m2()
}
function getUser() {
    return fetch('https://my-json-server.typicode.com/typicode/demo/profile').then(resp => resp.json())
}

function m1() {
    // other works
    return getUser()
}

function m2() {
    // other works
    return m1()
}

function m3() {
    // other works
    return await m2()
}

function main() {
    const user = m3()
    console.log(user)
}

function run(func) {
    let cache = []
    let i = 0
    const _originalFetch = window.fetch
    window.fetch = (...args) => {
        if (cache[i]) {
            if (cache[i].status === 'fulfilled') {
                return cache[i].data
            } else if (cache[i].status === 'rejected') {
                throw cache[i].err
            }
        }

        const result = {
            status: 'pending',
            data: null,
            err: null
        }
        cache[i++] = result
        // 发送请求
        const prom = _originalFetch(...args).then(resp => resp.json()).then(resp => {
            result.status = 'fulfilled'
            result.data = resp
        }, err => {
            result.status = 'rejected'
            reesult.err = err
        })
        // 报错
        throw prom
    }

    try {
        func()
    } catch (err) {
        // 什么时候执行func
        if (err instanceof Promise) {
            const reRun = () => {
                i = 0
                func()
            }
            err.then(reRun, reRun)
        }
    }
}

并发任务控制

class SuperTask() {
    constructor(parallelCount = 2) {
        this.parallelCount = parallelCount // 并发数量
        this.runningCount = 0 // 正在运行的任务数
        this.tasks = []
    }

    add(task) {
        return new Promise((resolve, reject) => {
        this.tasks.push({
            task,
            resolve,
            reject
        })
        this._run()
        })
    }

    // 一次运行tasks队列的所有任务
    _run() {
        while(this.runningCout < this.parallelCout && this.tasks.length) {
        const {task, resolve, reject} = this.tasks.shift()
        task().then(resolve, reject).finally(() => {
            this.runningCout--
            this._run()
        })
        }
    }
}

function timeout(time) {
    return new Promise(resolve => {
        setTimeout(() =>{
        resolve()
        }, time)
    })
}

const superTask = new SuperTask()

function addTask(time, name) {
    superTask
        .add(() => timeout(time))
        .then(() => {
            console.log(`任务${name}完成`)
        })
}

addTask(10000, 1) // 10000ms后输出 任务1完成
addTask(5000, 2) // 5000ms后输出 任务2完成
addTask(3000, 3) // 8000ms后输出 任务3完成
addTask(4000, 4) // 1200ms后输出 任务4完成
addTask(5000, 5) // 1500ms后输出 任务5完成

实现arrange函数

/**
 * 实现一个 arrange 函数,可以进行时间和工作调度
 * 注意,这里的 wait do  均可以无限调用
 *
 * - 具体功能参考下列示例
 * - 在示例中调用到的方法都需要实现
 * - 下面示例中 `>` 表示在控制台中输出 (console.log)
 *
 * --- 示例 ---
 *
 * 示例一:
 * `arrange('William').execute();`
 * > William is notified
 *
 * 示例二:
 * `arrange('William').wait(5).do('commit').wait(5).do('push').execute();`
 * > William is notified
 * 等待 5s...
 * > Start to commit
 * 等待 5s
 * > Start to push
 *
 */


function arrange(str) {
    return new Arrange(str);
}
class Arrange {
    constructor(str) {
        this.str = str
        this.delay = 0
    }
    execute = function () {
        console.log(this.str)
        return this
    }
    wait = function (delay) {
        this.delay += delay * 1000
        return this
    }
    do = function (str2) {
        setTimeout(() => {
            console.log(str2)
        }, this.delay);
        return this
    }
}

arrange('William').wait(5).do('commit').wait(5).do('push').execute();

生成随机颜色的两种方式

const generateRandomHexColor = () => {
  return `#${Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, '0')}`
}
const generateRandomRGBA = () => {
  const r = Math.floor(Math.random() * 256)
  const g = Math.floor(Math.random() * 256)
  const b = Math.floor(Math.random() * 256)
  const a = Math.random().toFixed(2)

  return `rgba(${[ r, g, b, a ].join(',')})`
}

复制内容到剪贴板的两种方式

const copyToClipboard = (text) => navigator.clipboard && navigator.clipboard.writeText && navigator.clipboard.writeText(text)
const copyToClipboard = (content) => {
  const textarea = document.createElement("textarea")

  textarea.value = content
  document.body.appendChild(textarea)
  textarea.select()
  document.execCommand("Copy")
  textarea.remove()
}

获取URL中的查询参数

const parseQuery = (name) => {
  return new URL(window.location.href).searchParams.get(name)
}

Please wait for a while

const timeout = (timeout) => new Promise((rs) => setTimeout(rs, timeout))

打乱数组

const shuffle = (array) => array.sort(() => 0.5 - Math.random())

深拷贝一个对象

如何深拷贝一个对象?使用 structuredClone 使它变得非常容易。

const obj = {
  name: 'fatfish',
  node: {
    name: 'medium',
    node: {
      name: 'blog'
    }
  }
}

const cloneObj = structuredClone(obj)
cloneObj.name = '1111'
cloneObj.node.name = '22222'
console.log(cloneObj)
/*
{
    "name": "1111",
    "node": {
        "name": "22222",
        "node": {
            "name": "blog"
        }
    }
}
*/
console.log(obj)
/*
{
    "name": "fatfish",
    "node": {
        "name": "medium",
        "node": {
            "name": "blog"
        }
    }
}
*/

确保一个元素在可见区域内

const isElInViewport = (el) => {
  return new Promise(function(resolve) {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.target === el) {
          resolve(entry.isIntersecting)
        }
      })
    })

    observer.observe(el)
  })
}

const inView = await isElInViewport(document.body)
console.log(inView) // true

获取当前选中的文本

const getSelectedContent = () => window.getSelection().toString()

获取所有浏览器cookie

const getAllCookies = () => {
  return document.cookie.split(";").reduce(function(cookieObj, cookie) {
    const cookieParts = cookie.split("=")
    cookieObj[cookieParts[0].trim()] = cookieParts[1].trim()
    return cookieObj
  }, {})
}

删除指定名称的cookie

const clearCookie = (name) => {
  document.cookie = name + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
}

将多维数组转换为一维数组

const flatten = (array) => {
  return array.reduce((result, it) => {
    return result.concat(Array.isArray(it) ? flatten(it) : it)
  }, [])
}

使用代理实现单例

// 简单判断两个值是否相等
function isSame(value1, value2) {
    if (value1.length !== value2.length) {
        return false
    }

    for (let i = 0; i < value1.length; i++) {
        if (value1[i] !== value2[i]) {
            return false
        }
    }

    return true
}

function singleton(className) {
    let ins;
    let parameters;

    return new Proxy(className, {
        constructor(target, ...args) {
            if (!ins) {
                ins = new ClassName(target, ...args)
                parameters = args
            }

            if (!isSame(parameters, args)) {
                throw new Error('不能创建单例')
            }

            return ins
        }
    })
}

系统取色器

async function sysColor() {
    const dropper = new EyeDropper()

    try {
        const result = await dropper.open()
        console.log(result)
    } catch {
        console.log('用户取消')
    }
}
贡献者: mankueng