# 常用工具函数

# 防抖(debounce)

函数防抖指的是一定时间内没有再次触发函数,就执行该函数,否则重新计时。

const debounce = (fn, delay = 1000) => {
  let timer
  return function (){
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, delay);
  };
};

// 使用
document.body.addEventListener('mousemove',debounce((e)=>{
  console.log(this,e,'mousemove-debounce')
},1000))
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 节流(throttle)

函数节流指的是规定某个时间内只能执行一次函数

  • 定时器
/**
 * 
 * 当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,
 * 直到定时器执行,然后执行函数,清空定时器。
 */
function throttle(callback, wait) {
    let timer
    return function(...args) {
        if(!timer) {
            timer = setTimeout(()=>{
                timer = null
                callback.call(this,args)
            },wait)
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • 时间戳
// 时间戳方式
/**
使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),
如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。
*/
function throttle(callback, wait) {
    let start = 0
    return function(...args) {
        const now = +new Date()
        if(now-start >= wait ) {
            callback.call(this,args)
            start = now
        }

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 函数克里化

柯里化就是把接受「多个参数」的函数变换成接受一个「单一参数」的函数,并且返回接受「余下参数」返回结果的一种应用。 原理是利用闭包把传入参数保存起来,当传入参数的数量足够执行函数时,就开始执行函数

  • 延迟计算 (用闭包把传入参数保存起来,当传入参数的数量足够执行函数时,开始执行函数)
  • 动态创建函数 (参数不够时会返回接受剩下参数的函数)
  • 参数复用(每个参数可以多次复用)
//写法一
let currying = (fn, ...args) =>
            fn.length > args.length ?
            (...arguments) => currying(fn, ...args, ...arguments) :
            fn(...args)

//写法二
function curry(func){
    return function curried(...args){
        //关键知识点:function.length 用来获取函数到形参个数
        //补充:arguments.length 获取到是实参个数
        if(args.length>=func.length){
            return func.apply(this,args)
        }
        return function (...args2){
            return curried.apply(this,args.concat(args2))
        }
    }
}

//-测试-
function sum (a, b, c) {
  return a + b + c
}
const curriedSum = curry(sum)
console.log(curriedSum(1, 2, 3))
console.log(curriedSum(1)(2,3))
console.log(curriedSum(1)(2)(3))

//写法三
const curry = (func, ...args) => {
  // 获取函数的参数个数
  const fnLen = func.length
  return function (...innerArgs) {
    innerArgs = args.concat(innerArgs)
    // 参数未搜集足的话,继续递归搜集
    if (innerArgs.length < fnLen) {
      return curry.call(this, func, ...innerArgs)
    } else {
      // 否则拿着搜集的参数调用func
      func.apply(this, innerArgs)
    }
  }
}

// -测试-
const add=curry((num1,num2,num3)=>{
console.log(num1+ num2+num3)
})

add(1)(2)(3//6  
add(1,2,3)  //6
add(1)(2,3) //6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

# 深浅拷贝

# 浅拷贝

  • Object.assign()

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。

let obj1 = { person: {name: "kobe", age: 41},sports:'basketball' };
let obj2 = Object.assign({}, obj1);
obj2.person.name = "wade";
obj2.sports = 'football'
console.log(obj1); // { person: { name: 'wade', age: 41 }, sports: 'basketball' }

1
2
3
4
5
6
  • for 循环
    const _shallowClone = target => {
                // 补全代码
               const obj={}
               for(const key in target){
                   if(target.hasOwnProperty(key)){
                       obj[key]=target[key]
                   }
               }
                return obj
            }
1
2
3
4
5
6
7
8
9
10
  • 展开运算符...

展开运算符是一个 es6 / es2015特性,它提供了一种非常方便的方式来执行浅拷贝,这与 Object.assign ()的功能相同。

let obj1 = { name: 'Kobe', address:{x:100,y:100}}
let obj2= {... obj1}
obj1.address.x = 200;
obj1.name = 'wade'
console.log('obj2',obj2) // obj2 { name: 'Kobe', address: { x: 200, y: 100 } }

1
2
3
4
5
6
  • 数组方法concat()和slice()
//4.Array.prototype.concat()
let arr = [1, 3, {
    username: 'kobe'
    }];
let arr2 = arr.concat();    
arr2[2].username = 'wade';
console.log(arr); //[ 1, 3, { username: 'wade' } ]

//5.Array.prototype.slice()
let arr = [1, 3, {
    username: ' kobe'
    }];
let arr3 = arr.slice();
arr3[2].username = 'wade'
console.log(arr); // [ 1, 3, { username: 'wade' } ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • 函数库lodash的_.clone方法

该函数库也有提供_.clone用来做 Shallow Copy,后面我们会再介绍利用这个库实现深拷贝。

var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.clone(obj1);
console.log(obj1.b.f === obj2.b.f);// true

1
2
3
4
5
6
7
8
9

# 深拷贝

# JSON.parse(JSON.stringify())

利用JSON.stringify将对象转成JSON字符串,再用JSON.parse把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。 这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则,因为这两者基于JSON.stringify和JSON.parse处理后,得到的正则就不再是正则(变为空对象),得到的函数就不再是函数(变为null)了。

  • 缺点:
  1. 他无法实现函数、RegExp等特殊对象的克隆

  2. 会抛弃对象的constructor,所有的构造函数会指向Object

  3. 对象有循环引用,会报错

let arr = [1, 3, {
    username: ' kobe'
}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan'; 
console.log(arr, arr4)

1
2
3
4
5
6
7

# 函数库lodash的_.cloneDeep方法

该函数库也有提供_.cloneDeep用来做 Deep Copy

var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
1
2
3
4
5
6
7
8

# 深拷贝(面试可用版)

  • 判断类型是否为原始类型,如果是,无需拷贝,直接返回
  • 为避免出现循环引用,拷贝对象时先判断存储空间中是否存在当前对象,如果有就直接返回
  • 开辟一个存储空间,来存储当前对象和拷贝对象的对应关系
  • 对引用类型递归拷贝直到属性为原始类型
const deepClone = (target, cache = new WeakMap()) => {
    if(target === null || typeof target !== 'object') {
        return target
    }
    if(cache.get(target)) {
        return target
    }
    const copy = Array.isArray(target) ? [] : {}
    cache.set(target, copy)
    Object.keys(target).forEach(key => copy[key] = deepClone(target[key], cache))
    return copy
}

1
2
3
4
5
6
7
8
9
10
11
12
13
  • 缺点:无法拷贝函数、Map、Set、正则等其他类型

# 深拷贝(尤雨溪)

function find(list, f) {
    return list.filter(f)[0]
}

function deepCopy(obj, cache = []) {
    // just return if obj is immutable value
    if (obj === null || typeof obj !== 'object') {
        return obj
    }
    // if obj is hit, it is in circular structure
    const hit = find(cache, c => c.original === obj)
    if (hit) {
        return hit.copy
    }
    const copy = Array.isArray(obj) ? [] : {}
    // put the copy into cache at first
    // because we want to refer it in recursive deepCopy
    cache.push({
        original: obj,
        copy
    })
    Object.keys(obj).forEach(key => copy[key] = deepCopy(obj[key], cache))

    return copy
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 深拷贝(考虑Date、RegExp等对象)

const _completeDeepClone = (target, map = new Map()) => {
    // 补全代码
    //参数如果为空
    if(target===null) return target;
    //参数如果不是对象类型,而是基本数据类型
    if(typeof target!=='object') return target;
    //参数为其他数据类型
    const cons = target.constructor;
    if(/^(Function|RegExp|Date|Map|Set)$/i.test(cons)){
        return new cons(target);
    } 
    const cloneTarget = Array.isArray(target)? []:{};
    //如果存在循环引用,直接返回当前循环引用的值,否则,将其加入map,
    if(map.get(target)) return map.get(target);
    map.set(target,cloneTarget);
    for(let key in target){
        cloneTarget[key] = _completeDeepClone(target[key],map)
    }
    return cloneTarget;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 深拷贝(复杂版)

如何写出一个惊艳面试官的深拷贝? (opens new window)

const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]';

const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';

const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];


function forEach(array, iteratee) {
    let index = -1;
    const length = array.length;
    while (++index < length) {
        iteratee(array[index], index);
    }
    return array;
}

function isObject(target) {
    const type = typeof target;
    return target !== null && (type === 'object' || type === 'function');
}

function getType(target) {
    return Object.prototype.toString.call(target);
}

function getInit(target) {
    const Ctor = target.constructor;
    return new Ctor();
}

function cloneSymbol(targe) {
    return Object(Symbol.prototype.valueOf.call(targe));
}

function cloneReg(targe) {
    const reFlags = /\w*$/;
    const result = new targe.constructor(targe.source, reFlags.exec(targe));
    result.lastIndex = targe.lastIndex;
    return result;
}

function cloneFunction(func) {
    const bodyReg = /(?<={)(.|\n)+(?=})/m;
    const paramReg = /(?<=\().+(?=\)\s+{)/;
    const funcString = func.toString();
    if (func.prototype) {
        const param = paramReg.exec(funcString);
        const body = bodyReg.exec(funcString);
        if (body) {
            if (param) {
                const paramArr = param[0].split(',');
                return new Function(...paramArr, body[0]);
            } else {
                return new Function(body[0]);
            }
        } else {
            return null;
        }
    } else {
        return eval(funcString);
    }
}

function cloneOtherType(targe, type) {
    const Ctor = targe.constructor;
    switch (type) {
        case boolTag:
        case numberTag:
        case stringTag:
        case errorTag:
        case dateTag:
            return new Ctor(targe);
        case regexpTag:
            return cloneReg(targe);
        case symbolTag:
            return cloneSymbol(targe);
        case funcTag:
            return cloneFunction(targe);
        default:
            return null;
    }
}

function clone(target, map = new WeakMap()) {

    // 克隆原始类型
    if (!isObject(target)) {
        return target;
    }

    // 初始化
    const type = getType(target);
    let cloneTarget;
    if (deepTag.includes(type)) {
        cloneTarget = getInit(target, type);
    } else {
        return cloneOtherType(target, type);
    }

    // 防止循环引用
    if (map.get(target)) {
        return map.get(target);
    }
    map.set(target, cloneTarget);

    // 克隆set
    if (type === setTag) {
        target.forEach(value => {
            cloneTarget.add(clone(value, map));
        });
        return cloneTarget;
    }

    // 克隆map
    if (type === mapTag) {
        target.forEach((value, key) => {
            cloneTarget.set(key, clone(value, map));
        });
        return cloneTarget;
    }

    // 克隆对象和数组
    const keys = type === arrayTag ? undefined : Object.keys(target);
    forEach(keys || target, (value, key) => {
        if (keys) {
            key = value;
        }
        cloneTarget[key] = clone(target[key], map);
    });

    return cloneTarget;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144

# 深拷贝(高性能版)

头条面试官:你知道如何实现高性能版本的深拷贝嘛? (opens new window)

const MY_IMMER = Symbol('my-immer1')

const isPlainObject = value => {
  if (
    !value ||
    typeof value !== 'object' ||
    {}.toString.call(value) != '[object Object]'
  ) {
    return false
  }
  var proto = Object.getPrototypeOf(value)
  if (proto === null) {
    return true
  }
  var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor
  return (
    typeof Ctor == 'function' &&
    Ctor instanceof Ctor &&
    Function.prototype.toString.call(Ctor) ===
      Function.prototype.toString.call(Object)
  )
}

const isProxy = value => !!value && !!value[MY_IMMER]

function produce(baseState, fn) {
  const proxies = new Map()
  const copies = new Map()

  const objectTraps = {
    get(target, key) {
      if (key === MY_IMMER) return target
      const data = copies.get(target) || target
      return getProxy(data[key])
    },
    set(target, key, val) {
      const copy = getCopy(target)
      const newValue = getProxy(val)
      // 这里的判断用于拿 proxy 的 target
      // 否则直接 copy[key] = newValue 的话外部拿到的对象是个 proxy
      copy[key] = isProxy(newValue) ? newValue[MY_IMMER] : newValue
      return true
    }
  }

  const getProxy = data => {
    if (isProxy(data)) {
      return data
    }
    if (isPlainObject(data) || Array.isArray(data)) {
      if (proxies.has(data)) {
        return proxies.get(data)
      }
      const proxy = new Proxy(data, objectTraps)
      proxies.set(data, proxy)
      return proxy
    }
    return data
  }

  const getCopy = data => {
    if (copies.has(data)) {
      return copies.get(data)
    }
    const copy = Array.isArray(data) ? data.slice() : { ...data }
    copies.set(data, copy)
    return copy
  }

  const isChange = data => {
    if (proxies.has(data) || copies.has(data)) return true
  }

  const finalize = data => {
    if (isPlainObject(data) || Array.isArray(data)) {
      if (!isChange(data)) {
        return data
      }
      const copy = getCopy(data)
      Object.keys(copy).forEach(key => {
        copy[key] = finalize(copy[key])
      })
      return copy
    }
    return data
  }

  const proxy = getProxy(baseState)
  fn(proxy)
  return finalize(baseState)
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

# 实现一个 compose 函数

// 用法如下:
function fn1(x) {
  return x + 1;
}
function fn2(x) {
  return x + 2;
}
function fn3(x) {
  return x + 3;
}
function fn4(x) {
  return x + 4;
}
const a = compose(fn1, fn2, fn3, fn4);
console.log(a(1)); // 1+4+3+2+1=11

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • 实现方式
function compose(...fn) {
  if (!fn.length) return (v) => v;
  if (fn.length === 1) return fn[0];
  return fn.reduce(
    (pre, cur) =>
      (...args) =>
        pre(cur(...args))
  );
}
1
2
3
4
5
6
7
8
9

# 用正则实现 trim()

  • 去掉首尾空格
String.prototype.trim = function(){
    return this.replace(/^\s+|\s+$/g, '')
}
//或者 
function trim(string){
    return string.replace(/^\s+|\s+$/g, '')
}

1
2
3
4
5
6
7
8

# 解析 url 参数

  • 根据name获取url上的search参数值

const getQueryByName = (name) => {
  const queryNameRegex = new RegExp(`[?&]${name}=([^&]*)(&|$)`)
  const queryNameMatch = window.location.search.match(queryNameRegex)
  // 一般都会通过decodeURIComponent解码处理
  return queryNameMatch ? decodeURIComponent(queryNameMatch[1]) : ''
}

// https://www.baidu.com/?name=%E5%89%8D%E7%AB%AF%E8%83%96%E5%A4%B4%E9%B1%BC&sex=boy

console.log(getQueryByName('name'), getQueryByName('sex')) // 前端胖头鱼 boy

1
2
3
4
5
6
7
8
9
10
11
12

# 函数柯理化

function curry(fn, args) {
  // 获取函数需要的参数长度
  let length = fn.length;

  args = args || [];

  return function() {
    let subArgs = args.slice(0);

    // 拼接得到现有的所有参数
    for (let i = 0; i < arguments.length; i++) {
      subArgs.push(arguments[i]);
    }

    // 判断参数的长度是否已经满足函数所需参数的长度
    if (subArgs.length >= length) {
      // 如果满足,执行函数
      return fn.apply(this, subArgs);
    } else {
      // 如果不满足,递归返回科里化的函数,等待参数的传入
      return curry.call(this, fn, subArgs);
    }
  };
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  • es6 实现

function curry(fn, ...args) {
  return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}

1
2
3
4
5