# Promise API

# Promise.all

  • 接受一个promise数组,并返回一个新的promise
  • 当所有的promise都成功resolve,新的promise才resolve
  • 如果任意一个promise被reject,由 Promise.all 返回的 promise 就会立即 reject,并且带有的就是这个 error。
  Promise.myAll = (promises)=>{
    let count = 0 
    let arr = []
    let length = promises.length
    return new Promise((resolve,reject)=>{
      promises.forEach((item,index) => {
        Promise.resolve(item).then(res=>{
          arr[index]=res
          count++
          if(count===length){
            resolve(arr)
          }
        }).catch(err=>{
          reject(err)
        })
      });
    })
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# Promise.race

  • 等待第一个settled的promise并resolve其结果或者reject其error
  Promise.myRace = (promises) => {
    return new Promise((resolve, reject) => {
      promises.forEach((item) => {
        Promise.resolve(item).then(resolve).catch(reject);
      });
    });
  }
1
2
3
4
5
6
7

# Promise.any

  • 等待第一个fulfilled的promise,并将其返回
  • 如果promise都rejected,那么reject个error对象
  Promise.myAny = (promises) => {
    let count = 0;
    let length = promises.length;
    return new Promise((resolve, reject) => {
      promises.forEach((item) => {
        Promise.resolve(item)
          .then(resolve)
          .catch(() => {
            count++;
            if (count === length) {
              reject({ error: '没有promise成功' });
            }
          });
      });
    });
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Promise.allSettled

Promise.allSettled 等待所有的 promise 都被 settle,无论结果如何。结果数组具有

  • {status:"fulfilled", value:result} 对于成功的响应,
  • {status:"rejected", reason:error} 对于 error。
  Promise.myAllSettled = function (promises) {
    let arr = [];
    let count = 0;
    let length = promises.length;
    return new Promise((resolve, reject) => {
      promises.forEach((item, i) => {
        Promise.resolve(item)
          .then((res) => {
            arr[i] = { status: 'fulfilled', value: res };
            count++;
            if (count === length) {
              resolve(arr);
            }
          })
          .catch((error) => {
            arr[i] = { status: 'rejected', reason: error };
            count++;
            if (count === length) {
              resolve(arr);
            }
          });
      });
    })
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# call,apply,bind

# call

call(obj,...args)

  • 改变this指向,指向函数第一个参数
  • 函数执行
  Function.prototype.myCall = function (context, ...args) {
    if (typeof this !== 'function') {
      return new Error('调用对象必须是函数');
    }
    let ctx = context || windows;
    let fn = Symbol();
    ctx[fn] = this;
    args = args ? args : [];
    const res = args.length > 0 ? ctx[fn](...args) : ctx[fn]();
    delete ctx[fn];
    return res;
  }
1
2
3
4
5
6
7
8
9
10
11
12

# apply

apply(obj,args=[])

  • 和call唯一的区别是,参数是类数组对象
  Function.prototype.myCall = function (context, args=[]) {
    if (typeof this !== 'function') {
      return new Error('调用对象必须是函数');
    }
    let ctx = context || windows;
    let fn = Symbol();
    ctx[fn] = this;
    const res = args.length > 0 ? ctx[fn](...args) : ctx[fn]();
    delete ctx[fn];
    return res;
  }
1
2
3
4
5
6
7
8
9
10
11

# bind

  • 改变this指向
  • 返回一个函数
  Function.prototype.myBind = function (context) {
    if (typeof this !== 'function') {
      throw new Error('调用对象必须是函数');
    }
    let self = this;
    const args = [...arguments].slice(1);
    return function bingFn() {
      return self.apply(this instanceof bingFn ? this : context, args.concat(...arguments));
    }
  }
1
2
3
4
5
6
7
8
9
10

# instanceof

myInstanceof(target,origin)

  • 在target原型链上是否能找到origin
  function myInstanceof(target, origin) {
    if (typeof target !== 'object' || target === null) {
      return false;
    }
    if (typeof origin !== 'function') {
      throw new Error('origin must be function');
    }
    let proto = Object.getPrototypeOf(target);
    while (proto) {
      if (proto === origin.prototype) {
        return true;
      }
      proto = Object.getPrototypeOf(proto);
    }
    return false;
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# new

  • 创建一个新对象,并让this指向这个对象
  • 新对象的__proto__指向函数的prototype
  • 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象引用
  function myNew(fn) {
    let obj = {};
    if (fn.prototype !== null) {
      obj.__proto__ = fn.prototype;
    }
    let args = [...arguments].slice(1);
    let res = fn.apply(obj, args);
    if ((typeof res === 'object' || typeof res === 'function') && res !== null) {
      return res;
    }
    return obj;
  }
1
2
3
4
5
6
7
8
9
10
11
12

# 柯里化

  • 柯里化是一种函数的转换,将一个函数从可调用的f(a,b,c)转换为可调用的f(a)(b)(c)
  • 柯里化不会调用函数,只是对函数转换
  function curry(fn) {
    let length = fn.length;
    return function _curry(...args) {
      if (args.length >= length) {
        return fn.apply(this, args);
      } else {
        return function (...args2) {
          return _curry.apply(this, args2.concat(args));
        }
      }
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12

# 防抖与节流

# 防抖

  • 事件f被触发后n毫秒后回调,如果n毫秒内又被触发,则使用最新的参数等待n毫秒后调用f
  function debounce(fn, ms) {
    let timer;
    return function () {
      clearTimeout(timer);
      timer = setTimeout(() => fn.apply(this, arguments), ms);
    }
  }
1
2
3
4
5
6
7

# 节流

  • 在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
  function throttle(func, ms) {

    let isThrottled = false,
      savedArgs,
      savedThis;

    function wrapper() {

      if (isThrottled) { 
        savedArgs = arguments;
        savedThis = this;
        return;
      }
      isThrottled = true;

      func.apply(this, arguments); 

      setTimeout(function() {
        isThrottled = false; 
        if (savedArgs) {
          wrapper.apply(savedThis, savedArgs);
          savedArgs = savedThis = null;
        }
      }, ms);
    }

    return wrapper;
  }
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

# flat

  • flat(depth) 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回
  • depth默认为1

# ❤️ reduce + concat + isArray + recursivity

  function flatDeep(arr, depth = 1) {
    return depth > 0
      ? arr.reduce((cur, val) => cur.concat(Array.isArray(val) ? flatDeep(val, depth - 1) : val), [])
      : arr.slice();
  }
1
2
3
4
5

# ❤️ forEach+isArray+push+recursivity

  function eachFlat(arr = [], depth = 1) {
    const result = []; // 缓存递归结果
    // 开始递归
    (function flat(arr, depth) {
      // forEach 会自动去除数组空位
      arr.forEach((item) => {
        // 控制递归深度
        if (Array.isArray(item) && depth > 0) {
          // 递归数组
          flat(item, depth - 1);
        } else {
          // 缓存元素
          result.push(item);
        }
      });
    })(arr, depth);
    // 返回递归结果
    return result;
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 深浅拷贝

# 浅拷贝

  function shallowClone(target) {
    if (!(typeof target === 'object' && target !== null)) {
      return target;
    }
    let copyObj = {};
    for (let key in target) {
      if (target.hasOwnProperty(key)) {
        copyObj[key] = target[key];
      }
    }
    return copyObj;
  }
1
2
3
4
5
6
7
8
9
10
11
12

# 深拷贝

# ❤️ 乞丐版

  let copyObj = JSON.parse(JSON.stringify(obj))
1

这样写会有以下问题

  1. 一些特定属性会被 JSON.stringify 跳过
    • 函数属性
    • 值为undefined的属性
    • Symbol类型的键和值
  2. Date会变字符串,RegExp,Error对象会变空值
  3. NaN,+Infinity,-Infinity变成null
  4. 无法解决循环引用
  5. 无法拷贝不可枚举的属性
  let obj = {
    fn() {
      console.log('1234');
    },
    [Symbol('123')]: 123,
    name: undefined,
    time: new Date(),
    city: NaN,
    age: Infinity,
    error: new Error('1212'),
    reg: new RegExp('[^0-9]'),
  };

  console.log(JSON.parse(JSON.stringify(obj)));
  //输出
  // {
  //   "time": "2022-08-26T08:20:05.206Z",
  //   "city": null,
  //   "age": null,
  //   "error": {},
  //   "reg": {}
  // }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 一步步实现深拷贝 参考文章 (opens new window)

  • 首先在浅拷贝基础上,加入递归,完成一个简单的基础版
  function deepClone(target){
    if(typeof target === 'object'){
      let copyObj = {}
      for(let key in target){
        copyObj[key] = deepClone(target[key])
      }
      return copyObj
    }else{
      return target
    }
  }
1
2
3
4
5
6
7
8
9
10
11
  • 考虑数组
  function deepClone(target) {
    if (typeof target === 'object') {
      let copyObj = Array.isArray(target) ? [] : {};
      for (let key in target) {
        copyObj[key] = deepClone(target[key]);
      }
      return copyObj;
    } else {
      return target;
    }
  }
1
2
3
4
5
6
7
8
9
10
11
  • 解决循环引用

TIP

解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。

  function deepClone(target, map = new Map()) {
      if (typeof target === 'object') {
          let copyObj = Array.isArray(target) ? [] : {};
          if (map.get(target)) {
              return map.get(target);
          }
          map.set(target, copyObj);
          for (const key in target) {
              copyObj[key] = deepClone(target[key], map);
          }
          return copyObj;
      } else {
          return target;
      }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

修改下引用类型的判断

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