乐趣区

关于javascript:js手写前端需要掌握的点

throttle(节流)

高频工夫触发, 但 n 秒内只会执行一次, 所以节流会浓缩函数的执行频率。

const throttle = (fn, time) => {
  let flag = true;
  return function() {if (!flag) return;
    flag = false;
    setTimeout(() => {fn.apply(this, arguments);
      flag = true;
    }, time);
  }
}

节流常利用于鼠标一直点击触发、监听滚动事件。

手写 new 操作符

在调用 new 的过程中会产生以上四件事件:

(1)首先创立了一个新的空对象

(2)设置原型,将对象的原型设置为函数的 prototype 对象。

(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象增加属性)

(4)判断函数的返回值类型,如果是值类型,返回创立的对象。如果是援用类型,就返回这个援用类型的对象。

function objectFactory() {
  let newObject = null;
  let constructor = Array.prototype.shift.call(arguments);
  let result = null;
  // 判断参数是否是一个函数
  if (typeof constructor !== "function") {console.error("type error");
    return;
  }
  // 新建一个空对象,对象的原型为构造函数的 prototype 对象
  newObject = Object.create(constructor.prototype);
  // 将 this 指向新建对象,并执行函数
  result = constructor.apply(newObject, arguments);
  // 判断返回对象
  let flag = result && (typeof result === "object" || typeof result === "function");
  // 判断返回后果
  return flag ? result : newObject;
}
// 应用办法
objectFactory(构造函数, 初始化参数);

实现数组元素求和

  • arr=[1,2,3,4,5,6,7,8,9,10],求和
let arr=[1,2,3,4,5,6,7,8,9,10]
let sum = arr.reduce((total,i) => total += i,0);
console.log(sum);
  • arr=[1,2,3,[[4,5],6],7,8,9],求和
var = arr=[1,2,3,[[4,5],6],7,8,9]
let arr= arr.toString().split(',').reduce((total,i) => total += Number(i),0);
console.log(arr);

递归实现:

let arr = [1, 2, 3, 4, 5, 6] 

function add(arr) {if (arr.length == 1) return arr[0] 
    return arr[0] + add(arr.slice(1)) 
}
console.log(add(arr)) // 21

实现 Object.is

Object.is不会转换被比拟的两个值的类型,这点和 === 更为类似,他们之间也存在一些区别

  • NaN=== 中是不相等的,而在 Object.is 中是相等的
  • +0- 0 在=== 中是相等的,而在 Object.is 中是不相等的
Object.is = function (x, y) {if (x === y) {
    // 当前情况下,只有一种状况是非凡的,即 +0 -0
    // 如果 x !== 0,则返回 true
    // 如果 x === 0,则须要判断 + 0 和 -0,则能够间接应用 1/+0 === Infinity 和 1/-0 === -Infinity 来进行判断
    return x !== 0 || 1 / x === 1 / y;
  }

  // x !== y 的状况下,只须要判断是否为 NaN,如果 x!==x,则阐明 x 是 NaN,同理 y 也一样
  // x 和 y 同时为 NaN 时,返回 true
  return x !== x && y !== y;
};

递归反转链表

// node 节点
class Node {constructor(element,next) {
    this.element = element
    this.next = next
  } 
}

class LinkedList {constructor() {
   this.head = null // 默认应该指向第一个节点
   this.size = 0 // 通过这个长度能够遍历这个链表
 }
 // 减少 O(n)
 add(index,element) {if(arguments.length === 1) {
     // 向开端增加
     element = index // 以后元素等于传递的第一项
     index = this.size // 索引指向最初一个元素
   }
  if(index < 0 || index > this.size) {throw new Error('增加的索引不失常')
  }
  if(index === 0) {
    // 间接找到头部 把头部改掉 性能更好
    let head = this.head
    this.head = new Node(element,head)
  } else {
    // 获取以后头指针
    let current = this.head
    // 不停遍历 直到找到最初一项 增加的索引是 1 就找到第 0 个的 next 赋值
    for (let i = 0; i < index-1; i++) { // 找到它的前一个
      current = current.next
    }
    // 让创立的元素指向上一个元素的下一个
    // 看图了解 next 层级 ![](http://img-repo.poetries.top/images/20210522115056.png)
    current.next = new Node(element,current.next) // 让以后元素指向下一个元素的 next
  }

  this.size++;
 }
 // 删除 O(n)
 remove(index) {if(index < 0 || index >= this.size) {throw new Error('删除的索引不失常')
  }
  this.size--
  if(index === 0) {
    let head = this.head
    this.head = this.head.next // 挪动指针地位

    return head // 返回删除的元素
  }else {
    let current = this.head
    for (let i = 0; i < index-1; i++) { // index- 1 找到它的前一个
      current = current.next
    }
    let returnVal = current.next // 返回删除的元素
    // 找到待删除的指针的上一个 current.next.next 
    // 如删除 200,100=>200=>300 找到 200 的上一个 100 的 next 的 next 为 300,把 300 赋值给 100 的 next 即可
    current.next = current.next.next 

    return returnVal
  }
 }
 // 查找 O(n)
 get(index) {if(index < 0 || index >= this.size) {throw new Error('查找的索引不失常')
  }
  let current = this.head
  for (let i = 0; i < index; i++) {current = current.next}
  return current
 }
 reverse() {
  const reverse = head=>{if(head == null || head.next == null) {return head}
    let newHead = reverse(head.next)
    // 从这个链表的最初一个开始反转,让以后下一个元素的 next 指向本人,本人指向 null
    // ![](http://img-repo.poetries.top/images/20210522161710.png)
    // 刚开始反转的是最初两个
    head.next.next = head
    head.next = null

    return newHead
  }
  return reverse(this.head)
 }
}

let ll = new LinkedList()

ll.add(1)
ll.add(2)
ll.add(3)
ll.add(4)

// console.dir(ll,{depth: 1000})

console.log(ll.reverse())

实现数组的扁平化

(1)递归实现

一般的递归思路很容易了解,就是通过循环递归的形式,一项一项地去遍历,如果每一项还是一个数组,那么就持续往下遍历,利用递归程序的办法,来实现数组的每一项的连贯:

let arr = [1, [2, [3, 4, 5]]];
function flatten(arr) {let result = [];

  for(let i = 0; i < arr.length; i++) {if(Array.isArray(arr[i])) {result = result.concat(flatten(arr[i]));
    } else {result.push(arr[i]);
    }
  }
  return result;
}
flatten(arr);  //  [1, 2, 3, 4,5]

(2)reduce 函数迭代

从下面一般的递归函数中能够看出,其实就是对数组的每一项进行解决,那么其实也能够用 reduce 来实现数组的拼接,从而简化第一种办法的代码,革新后的代码如下所示:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {return arr.reduce(function(prev, next){return prev.concat(Array.isArray(next) ? flatten(next) : next)
    }, [])
}
console.log(flatten(arr));//  [1, 2, 3, 4,5]

(3)扩大运算符实现

这个办法的实现,采纳了扩大运算符和 some 的办法,两者独特应用,达到数组扁平化的目标:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {while (arr.some(item => Array.isArray(item))) {arr = [].concat(...arr);
    }
    return arr;
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

(4)split 和 toString

能够通过 split 和 toString 两个办法来独特实现数组扁平化,因为数组会默认带一个 toString 的办法,所以能够把数组间接转换成逗号分隔的字符串,而后再用 split 办法把字符串从新转换为数组,如上面的代码所示:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {return arr.toString().split(',');
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

通过这两个办法能够将多维数组间接转换成逗号连贯的字符串,而后再从新分隔成数组。

(5)ES6 中的 flat

咱们还能够间接调用 ES6 中的 flat 办法来实现数组扁平化。flat 办法的语法:arr.flat([depth])

其中 depth 是 flat 的参数,depth 是能够传递数组的开展深度(默认不填、数值是 1),即开展一层数组。如果层数不确定,参数能够传进 Infinity,代表不管多少层都要开展:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {return arr.flat(Infinity);
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

能够看出,一个嵌套了两层的数组,通过将 flat 办法的参数设置为 Infinity,达到了咱们预期的成果。其实同样也能够设置成 2,也能实现这样的成果。在编程过程中,如果数组的嵌套层数不确定,最好间接应用 Infinity,能够达到扁平化。(6)正则和 JSON 办法 在第 4 种办法中曾经应用 toString 办法,其中依然采纳了将 JSON.stringify 的办法先转换为字符串,而后通过正则表达式过滤掉字符串中的数组的方括号,最初再利用 JSON.parse 把它转换成数组:

let arr = [1, [2, [3, [4, 5]]], 6];
function flatten(arr) {let str = JSON.stringify(arr);
  str = str.replace(/(\[|\])/g, '');
  str = '[' + str + ']';
  return JSON.parse(str); 
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

实现一个 compose 函数

组合多个函数,从右到左,比方:compose(f, g, h) 最终失去这个后果 (...args) => f(g(h(...args))).

题目形容: 实现一个 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

实现代码如下

function compose(...funcs) {if (!funcs.length) return (v) => v;

  if (funcs.length === 1) {return funcs[0]
  }

  return funcs.reduce((a, b) => {return (...args) => a(b(...args)))
  }
}

compose创立了一个从右向左执行的数据流。如果要实现从左到右的数据流,能够间接更改 compose 的局部代码即可实现

  • 更换 Api 接口:把 reduce 改为reduceRight
  • 交互包裹地位:把 a(b(...args)) 改为b(a(...args))

数组去重办法汇总

首先: 我晓得多少种去重形式

1. 双层 for 循环

function distinct(arr) {for (let i=0, len=arr.length; i<len; i++) {for (let j=i+1; j<len; j++) {if (arr[i] == arr[j]) {arr.splice(j, 1);
                // splice 会扭转数组长度,所以要将数组长度 len 和下标 j 减一
                len--;
                j--;
            }
        }
    }
    return arr;
}

思维: 双重 for 循环是比拟蠢笨的办法,它实现的原理很简略:先定义一个蕴含原始数组第一个元素的数组,而后遍历原始数组,将原始数组中的每个元素与新数组中的每个元素进行比对,如果不反复则增加到新数组中,最初返回新数组;因为它的工夫复杂度是O(n^2),如果数组长度很大,效率会很低

2. Array.filter() 加 indexOf/includes

function distinct(a, b) {let arr = a.concat(b);
    return arr.filter((item, index)=> {//return arr.indexOf(item) === index
        return arr.includes(item)
    })
}

思维: 利用 indexOf 检测元素在数组中第一次呈现的地位是否和元素当初的地位相等,如果不等则阐明该元素是反复元素

3. ES6 中的 Set 去重

function distinct(array) {return Array.from(new Set(array));
}

思维: ES6 提供了新的数据结构 Set,Set 构造的一个个性就是成员值都是惟一的,没有反复的值。

4. reduce 实现对象数组去反复

var resources = [{ name: "张三", age: "18"},
    {name: "张三", age: "19"},
    {name: "张三", age: "20"},
    {name: "李四", age: "19"},
    {name: "王五", age: "20"},
    {name: "赵六", age: "21"}
]
var temp = {};
resources = resources.reduce((prev, curv) => {
 // 如果长期对象中有这个名字,什么都不做
 if (temp[curv.name]) { }else {
    // 如果长期对象没有就把这个名字加进去,同时把以后的这个对象退出到 prev 中
    temp[curv.name] = true;
    prev.push(curv);
 }
 return prev
}, []);
console.log("后果", resources);

这种办法是利用高阶函数 reduce 进行去重,这里只须要留神 initialValue 得放一个空数组[],不然没法push

实现一个双向绑定

defineProperty 版本

// 数据
const data = {text: 'default'};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 数据劫持
Object.defineProperty(data, 'text', {
  // 数据变动 --> 批改视图
  set(newVal) {
    input.value = newVal;
    span.innerHTML = newVal;
  }
});
// 视图更改 --> 数据变动
input.addEventListener('keyup', function(e) {data.text = e.target.value;});

proxy 版本

// 数据
const data = {text: 'default'};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 数据劫持
const handler = {set(target, key, value) {target[key] = value;
    // 数据变动 --> 批改视图
    input.value = value;
    span.innerHTML = value;
    return value;
  }
};
const proxy = new Proxy(data, handler);

// 视图更改 --> 数据变动
input.addEventListener('keyup', function(e) {proxy.text = e.target.value;});

参考:前端手写面试题具体解答

实现 Promise 相干办法

实现 Promise 的 resolve

实现 resolve 静态方法有三个要点:

  • 传参为一个 Promise, 则间接返回它。
  • 传参为一个 thenable 对象,返回的 Promise 会追随这个对象,采纳它的最终状态作为本人的状态。
  • 其余状况,间接返回以该值为胜利状态的 promise 对象。
Promise.resolve = (param) => {if(param instanceof Promise) return param;
  return new Promise((resolve, reject) => {if(param && param.then && typeof param.then === 'function') {
      // param 状态变为胜利会调用 resolve,将新 Promise 的状态变为胜利,反之亦然
      param.then(resolve, reject);
    }else {resolve(param);
    }
  })
}

实现 Promise.reject

Promise.reject 中传入的参数会作为一个 reason 一成不变地往下传, 实现如下:

Promise.reject = function (reason) {return new Promise((resolve, reject) => {reject(reason);
    });
}

实现 Promise.prototype.finally

后面的 promise 不论胜利还是失败,都会走到 finally 中,并且 finally 之后,还能够持续 then(阐明它还是一个 then 办法是要害),并且会将初始的promise 值一成不变的传递给前面的then.

Promise.prototype.finally 最大的作用

  • finally里的函数,无论如何都会执行,并会把后面的值一成不变传递给下一个 then 办法中
  • 如果 finally 函数中有 promise 等异步工作,会等它们全副执行结束,再联合之前的胜利与否状态,返回值

Promise.prototype.finally 六大状况用法

// 状况 1
Promise.resolve(123).finally((data) => { // 这里传入的函数,无论如何都会执行
  console.log(data); // undefined
})

// 状况 2 (这里,finally 办法相当于做了两头解决,起一个过渡的作用)
Promise.resolve(123).finally((data) => {console.log(data); // undefined
}).then(data => {console.log(data); // 123
})

// 状况 3 (这里只有 reject,都会走到下一个 then 的 err 中)
Promise.reject(123).finally((data) => {console.log(data); // undefined
}).then(data => {console.log(data);
}, err => {console.log(err, 'err'); // 123 err
})

// 状况 4 (一开始就胜利之后,会期待 finally 里的 promise 执行结束后,再把后面的 data 传递到下一个 then 中)
Promise.resolve(123).finally((data) => {console.log(data); // undefined
  return new Promise((resolve, reject) => {setTimeout(() => {resolve('ok');
    }, 3000)
  })
}).then(data => {console.log(data, 'success'); // 123 success
}, err => {console.log(err, 'err');
})

// 状况 5 (尽管一开始胜利,然而只有 finally 函数中的 promise 失败了,就会把其失败的值传递到下一个 then 的 err 中)
Promise.resolve(123).finally((data) => {console.log(data); // undefined
  return new Promise((resolve, reject) => {setTimeout(() => {reject('rejected');
    }, 3000)
  })
}).then(data => {console.log(data, 'success');
}, err => {console.log(err, 'err'); // rejected err
})

// 状况 6 (尽管一开始失败,然而也要等 finally 中的 promise 执行完,能力把一开始的 err 传递到 err 的回调中)
Promise.reject(123).finally((data) => {console.log(data); // undefined
  return new Promise((resolve, reject) => {setTimeout(() => {resolve('resolve');
    }, 3000)
  })
}).then(data => {console.log(data, 'success');
}, err => {console.log(err, 'err'); // 123 err
})

源码实现

Promise.prototype.finally = function (callback) {return this.then((data) => {
    // 让函数执行 外部会调用办法,如果办法是 promise,须要期待它实现
    // 如果以后 promise 执行时失败了,会把 err 传递到,err 的回调函数中
    return Promise.resolve(callback()).then(() => data); // data 上一个 promise 的胜利态
  }, err => {return Promise.resolve(callback()).then(() => {throw err; // 把之前的失败的 err,抛出去});
  })
}

实现 Promise.all

对于 all 办法而言,须要实现上面的外围性能:

  • 传入参数为一个空的可迭代对象,则间接进行resolve
  • 如果参数中有一个 promise 失败,那么 Promise.all 返回的 promise 对象失败。
  • 在任何状况下,Promise.all 返回的 promise 的实现状态的后果都是一个数组
Promise.all = function(promises) {return new Promise((resolve, reject) => {let result = [];
    let index = 0;
    let len = promises.length;
    if(len === 0) {resolve(result);
      return;
    }

    for(let i = 0; i < len; i++) {// 为什么不间接 promise[i].then, 因为 promise[i]可能不是一个 promise
      Promise.resolve(promise[i]).then(data => {result[i] = data;
        index++;
        if(index === len) resolve(result);
      }).catch(err => {reject(err);
      })
    }
  })
}

实现 promise.allsettle

MDN: Promise.allSettled()办法返回一个在所有给定的 promise曾经 fulfilledrejected后的promise,并带有一个对象数组,每个对象示意对应的promise` 后果

当您有多个彼此不依赖的异步工作胜利实现时,或者您总是想晓得每个 promise 的后果时,通常应用它。

【译】Promise.allSettledPromise.all 相似, 其参数承受一个 Promise 的数组, 返回一个新的 Promise, 惟一的不同在于, 其不会进行短路, 也就是说当 Promise 全副解决实现后咱们能够拿到每个Promise 的状态, 而不论其是否解决胜利。

用法 | 测试用例

let fs = require('fs').promises;

let getName = fs.readFile('./name.txt', 'utf8'); // 读取文件胜利
let getAge = fs.readFile('./age.txt', 'utf8');

Promise.allSettled([1, getName, getAge, 2]).then(data => {console.log(data);
});
// 输入后果
/*
    [{ status: 'fulfilled', value: 1},
    {status: 'fulfilled', value: 'zf'},
    {status: 'fulfilled', value: '11'},
    {status: 'fulfilled', value: 2}
    ]
*/

let getName = fs.readFile('./name123.txt', 'utf8'); // 读取文件失败
let getAge = fs.readFile('./age.txt', 'utf8');
// 输入后果
/*
    [{ status: 'fulfilled', value: 1},
    {
      status: 'rejected',
      value: [Error: ENOENT: no such file or directory, open './name123.txt'] {
        errno: -2,
        code: 'ENOENT',
        syscall: 'open',
        path: './name123.txt'
      }
    },
    {status: 'fulfilled', value: '11'},
    {status: 'fulfilled', value: 2}
  ]
*/

实现

function isPromise (val) {return typeof val.then === 'function'; // (123).then => undefined
}

Promise.allSettled = function(promises) {return new Promise((resolve, reject) => {let arr = [];
    let times = 0;
    const setData = (index, data) => {arr[index] = data;
      if (++times === promises.length) {resolve(arr);
      }
      console.log('times', times)
    }

    for (let i = 0; i < promises.length; i++) {let current = promises[i];
      if (isPromise(current)) {current.then((data) => {setData(i, { status: 'fulfilled', value: data});
        }, err => {setData(i, { status: 'rejected', value: err})
        })
      } else {setData(i, { status: 'fulfilled', value: current})
      }
    }
  })
}

实现 Promise.race

race 的实现相比之下就简略一些,只有有一个 promise 执行完,间接 resolve 并进行执行

Promise.race = function(promises) {return new Promise((resolve, reject) => {
    let len = promises.length;
    if(len === 0) return;
    for(let i = 0; i < len; i++) {Promise.resolve(promise[i]).then(data => {resolve(data);
        return;
      }).catch(err => {reject(err);
        return;
      })
    }
  })
}

实现一个简版 Promise

// 应用
var promise = new Promise((resolve,reject) => {if (操作胜利) {resolve(value)
    } else {reject(error)
    }
})
promise.then(function (value) {// success},function (value) {// failure})
function myPromise(constructor) {
    let self = this;
    self.status = "pending"   // 定义状态扭转前的初始状态
    self.value = undefined;   // 定义状态为 resolved 的时候的状态
    self.reason = undefined;  // 定义状态为 rejected 的时候的状态
    function resolve(value) {if(self.status === "pending") {
          self.value = value;
          self.status = "resolved";
       }
    }
    function reject(reason) {if(self.status === "pending") {
          self.reason = reason;
          self.status = "rejected";
       }
    }
    // 捕捉结构异样
    try {constructor(resolve,reject);
    } catch(e) {reject(e);
    }
}
// 增加 then 办法
myPromise.prototype.then = function(onFullfilled,onRejected) {
   let self = this;
   switch(self.status) {
      case "resolved":
        onFullfilled(self.value);
        break;
      case "rejected":
        onRejected(self.reason);
        break;
      default:       
   }
}

var p = new myPromise(function(resolve,reject) {resolve(1)
});
p.then(function(x) {console.log(x) // 1
})

应用 class 实现

class MyPromise {constructor(fn) {this.resolvedCallbacks = [];
    this.rejectedCallbacks = [];

    this.state = 'PENDING';
    this.value = '';

    fn(this.resolve.bind(this), this.reject.bind(this));

  }

  resolve(value) {if (this.state === 'PENDING') {
      this.state = 'RESOLVED';
      this.value = value;

      this.resolvedCallbacks.map(cb => cb(value));   
    }
  }

  reject(value) {if (this.state === 'PENDING') {
      this.state = 'REJECTED';
      this.value = value;

      this.rejectedCallbacks.map(cb => cb(value));
    }
  }

  then(onFulfilled, onRejected) {if (this.state === 'PENDING') {this.resolvedCallbacks.push(onFulfilled);
      this.rejectedCallbacks.push(onRejected);

    }

    if (this.state === 'RESOLVED') {onFulfilled(this.value);
    }

    if (this.state === 'REJECTED') {onRejected(this.value);
    }
  }
}

Promise 实现 - 具体

  • 能够把 Promise 看成一个状态机。初始是 pending 状态,能够通过函数 resolvereject,将状态转变为 resolved或者 rejected 状态,状态一旦扭转就不能再次变动。
  • then 函数会返回一个 Promise 实例,并且该返回值是一个新的实例而不是之前的实例。因为 Promise 标准规定除了 pending 状态,其余状态是不能够扭转的,如果返回的是一个雷同实例的话,多个 then 调用就失去意义了。
  • 对于 then来说,实质上能够把它看成是 flatMap
// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
// promise 接管一个函数参数,该函数会立刻执行
function MyPromise(fn) {
  let _this = this;
  _this.currentState = PENDING;
  _this.value = undefined;
  // 用于保留 then 中的回调,只有当 promise
  // 状态为 pending 时才会缓存,并且每个实例至少缓存一个
  _this.resolvedCallbacks = [];
  _this.rejectedCallbacks = [];

  _this.resolve = function (value) {if (value instanceof MyPromise) {
      // 如果 value 是个 Promise,递归执行
      return value.then(_this.resolve, _this.reject)
    }
    setTimeout(() => { // 异步执行,保障执行程序
      if (_this.currentState === PENDING) {
        _this.currentState = RESOLVED;
        _this.value = value;
        _this.resolvedCallbacks.forEach(cb => cb());
      }
    })
  };

  _this.reject = function (reason) {setTimeout(() => { // 异步执行,保障执行程序
      if (_this.currentState === PENDING) {
        _this.currentState = REJECTED;
        _this.value = reason;
        _this.rejectedCallbacks.forEach(cb => cb());
      }
    })
  }
  // 用于解决以下问题
  // new Promise(() => throw Error('error))
  try {fn(_this.resolve, _this.reject);
  } catch (e) {_this.reject(e);
  }
}

MyPromise.prototype.then = function (onResolved, onRejected) {
  var self = this;
  // 标准 2.2.7,then 必须返回一个新的 promise
  var promise2;
  // 标准 2.2.onResolved 和 onRejected 都为可选参数
  // 如果类型不是函数须要疏忽,同时也实现了透传
  // Promise.resolve(4).then().then((value) => console.log(value))
  onResolved = typeof onResolved === 'function' ? onResolved : v => v;
  onRejected = typeof onRejected === 'function' ? onRejected : r => throw r;

  if (self.currentState === RESOLVED) {return (promise2 = new MyPromise(function (resolve, reject) {
      // 标准 2.2.4,保障 onFulfilled,onRjected 异步执行
      // 所以用了 setTimeout 包裹下
      setTimeout(function () {
        try {var x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {reject(reason);
        }
      });
    }));
  }

  if (self.currentState === REJECTED) {return (promise2 = new MyPromise(function (resolve, reject) {setTimeout(function () {
        // 异步执行 onRejected
        try {var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {reject(reason);
        }
      });
    }));
  }

  if (self.currentState === PENDING) {return (promise2 = new MyPromise(function (resolve, reject) {self.resolvedCallbacks.push(function () {
        // 思考到可能会有报错,所以应用 try/catch 包裹
        try {var x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (r) {reject(r);
        }
      });

      self.rejectedCallbacks.push(function () {
        try {var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (r) {reject(r);
        }
      });
    }));
  }
};
// 标准 2.3
function resolutionProcedure(promise2, x, resolve, reject) {
  // 标准 2.3.1,x 不能和 promise2 雷同,防止循环援用
  if (promise2 === x) {return reject(new TypeError("Error"));
  }
  // 标准 2.3.2
  // 如果 x 为 Promise,状态为 pending 须要持续期待否则执行
  if (x instanceof MyPromise) {if (x.currentState === PENDING) {x.then(function (value) {
        // 再次调用该函数是为了确认 x resolve 的
        // 参数是什么类型,如果是根本类型就再次 resolve
        // 把值传给下个 then
        resolutionProcedure(promise2, value, resolve, reject);
      }, reject);
    } else {x.then(resolve, reject);
    }
    return;
  }
  // 标准 2.3.3.3.3
  // reject 或者 resolve 其中一个执行过得话,疏忽其余的
  let called = false;
  // 标准 2.3.3,判断 x 是否为对象或者函数
  if (x !== null && (typeof x === "object" || typeof x === "function")) {
    // 标准 2.3.3.2,如果不能取出 then,就 reject
    try {
      // 标准 2.3.3.1
      let then = x.then;
      // 如果 then 是函数,调用 x.then
      if (typeof then === "function") {
        // 标准 2.3.3.3
        then.call(
          x,
          y => {if (called) return;
            called = true;
            // 标准 2.3.3.3.1
            resolutionProcedure(promise2, y, resolve, reject);
          },
          e => {if (called) return;
            called = true;
            reject(e);
          }
        );
      } else {
        // 标准 2.3.3.4
        resolve(x);
      }
    } catch (e) {if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // 标准 2.3.4,x 为根本类型
    resolve(x);
  }
}

实现 Promisify

const fs = require('fs')
const path = require('path')

// node 中应用
// const fs = require('fs').promises 12.18 版
// const promisify = require('util').promisify

// 包装 node api promise 化 典型的高级函数
const promisify = fn=>{return (...args)=>{return new Promise((resolve,reject)=>{fn(...args, (err,data)=>{if(err) {reject(err)
        } 
        resolve(data)
      })
    })
  }
}

// const read = promisify(fs.readFile)

// read(path.join(__dirname, './promise.js'), 'utf8').then(d=>{//   console.log(d)
// })

// promise 化 node 所有 api
const promisifyAll = target=>{Reflect.ownKeys(target).forEach(key=>{if(typeof target[key] === 'function') {target[key+'Async'] = promisify(target[key])
    }
  })
  return target
}

// promise 化 fs 下的函数
const promisifyNew = promisifyAll(fs)

promisifyNew.readFileAsync(path.join(__dirname, './promise.js'), 'utf8').then(d=>{console.log(d)
})

module.exports = {
  promisify,
  promisifyAll
}

残缺实现 Promises/A+ 标准

/**
 * Promises/A+ 标准 实现一个 promise
 * https://promisesaplus.com/
*/

const EMUM = {
  PENDING: 'PENDING',
  FULFILLED: 'FULFILLED',
  REJECTED: 'REJECTED'
}

// x 返回值
// promise2 then 的时候 new 的 promise
// promise2 的 resolve, reject
const resolvePromise = (x, promise2, resolve, reject)=>{
  // 解析 promise 的值解析 promise2 是胜利还是失败 传递到上层 then
  if(x === promise2) {reject(new TypeError('类型谬误'))
  }
  // 这里的 x 如果是一个 promise 的话 可能是其余的 promise,可能调用了胜利 又调用了失败
  // 避免 resolve 的时候 又 throw err 抛出异样到 reject 了
  let called
  // 如果 x 是 promise 那么就采纳他的状态
  // 有 then 办法是 promise
  if(typeof x === 'object' && typeof x!== null || typeof x === 'function') {
    // x 是对象或函数
    try {
      let then = x.then // 缓存,不必屡次取值
      if(typeof then === 'function') {
        // 是 promise,调用 then 办法外面有 this,须要传入 this 为 x 能力取到 then 办法外面的值 this.value
        then.call(x, y=>{// 胜利
          // y 值可能也是一个 promise 如 resolve(new Promise()) 此时的 y ==new Promise()
          // 递归解析 y,直到拿到一般的值 resolve(x 进来)
          if(called) return;
          called = true;

          resolvePromise(y, promise2, resolve, reject)
        },r=>{// 一旦失败间接失败
          if(called) return;
          called = true;
          reject(r)
        })
      } else {
        // 一般对象不是 promise
        resolve(x)
      }
    } catch (e) {
      // 对象取值可能报错,用 defineProperty 定义 get 抛出异样
      if(called) return;
      called = true;
      reject(e)
    }
  } else {
    // x 是一般值
    resolve(x) // 间接胜利
  }

}
class myPromise {constructor(executor) {
    this.status = EMUM.PENDING // 以后状态
    this.value = undefined // resolve 接管值
    this.reason = undefined // reject 失败返回值

    /**
     * 同一个 promise 能够 then 屡次(公布订阅模式)
     * 调用 then 时 以后状态是期待态,须要将以后胜利或失败的回调寄存起来(订阅)* 调用 resolve 时 将订阅函数进行执行(公布)*/
    // 胜利队列
    this.onResolvedCallbacks = []
    // 失败队列
    this.onRejectedCallbacks = []
    const resolve = value =>{
      // 如果 value 是一个 promise,须要递归解析
      // 如 myPromise.resolve(new myPromise()) 须要解析 value
      if(value instanceof myPromise) {
        // 不停的解析 直到值不是 promise
        return value.then(resolve,reject)
      }

      if(this.status === EMUM.PENDING) {
        this.status = EMUM.FULFILLED
        this.value = value

        this.onResolvedCallbacks.forEach(fn=>fn())
      }
    }
    const reject = reason =>{if(this.status === EMUM.PENDING) {
        this.status = EMUM.REJECTED
        this.reason = reason

        this.onRejectedCallbacks.forEach(fn=>fn())
      }
    }
    try {executor(resolve,reject)
    } catch(e) {reject(e)
    }
  }
  then(onFulFilled, onRejected) {
    // 透传 解决默认不传的状况
    // new Promise((resolve,reject)=>{//   resolve(1)
    // }).then().then().then(d=>{})
    // new Promise((resolve,reject)=>{//   resolve(1)
    // }).then(v=>v).then(v=>v).then(d=>{})
    // new Promise((resolve,reject)=>{//   reject(1)
    // }).then().then().then(null, e=>{console.log(e)})
    // new Promise((resolve,reject)=>{//   reject(1)
    // }).then(null,e=>{throw e}).then(null,e=>{throw e}).then(null,e=>{console.log(e)})
    onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : v => v
    onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err}

    // 调用 then 创立一个新的 promise
    let promise2 = new myPromise((resolve,reject)=>{
      // 依据 value 判断是 resolve 还是 reject value 也可能是 promise
      if(this.status === EMUM.FULFILLED) {setTimeout(() => {
          try {
            // 胜利回调后果
            let x = onFulFilled(this.value)
            // 解析 promise
            resolvePromise(x, promise2,resolve,reject)
          } catch (error) {reject(error)
          }
        }, 0);
      }
      if(this.status === EMUM.REJECTED) {setTimeout(() => {
          try {let x = onRejected(this.reason)
            // 解析 promise
            resolvePromise(x, promise2,resolve,reject)
          } catch (error) {reject(error)
          }
        }, 0);
      }
      // 用户还未调用 resolve 或 reject 办法
      if(this.status === EMUM.PENDING) {this.onResolvedCallbacks.push(()=>{
          try {let x = onFulFilled(this.value)
            // 解析 promise
            resolvePromise(x, promise2,resolve,reject)
          } catch (error) {reject(error)
          }
        })
        this.onRejectedCallbacks.push(()=>{
          try {let x = onRejected(this.reason)
            // 解析 promise
            resolvePromise(x, promise2,resolve,reject)
          } catch (error) {reject(error)
          }
        })
      }
    })

    return promise2
  }
  catch(errCallback) {
    // 等同于没有胜利,把失败放进去而已
    return this.then(null, errCallback)
  }
  // myPromise.resolve 具备期待性能的 如果参数的 promise 会期待 promise 解析结束在向下执行
  static resolve(val) {return new myPromise((resolve,reject)=>{resolve(val)
    })
  }
  // myPromise.reject 间接将值返回
  static reject(reason) {return new myPromise((resolve,reject)=>{reject(reason)
    })
  }
  // finally 传入的函数 无论胜利或失败都执行
  // Promise.reject(100).finally(()=>{console.log(1)}).then(d=>console.log('success',d)).catch(er=>console.log('faild',er))
  // Promise.reject(100).finally(()=>new Promise()).then(d=>console.log(d)).catch(er=>)
  finally(callback) {return this.then((val)=>{return myPromise.resolve(callback()).then(()=>val)
    },(err)=>{return myPromise.resolve(callback()).then(()=>{throw err})
    })
  }
  // Promise.all
  static all(values) {return new myPromise((resolve,reject)=>{let resultArr = []
      let orderIndex = 0
      const processResultByKey = (value,index)=>{resultArr[index] = value 
        // 解决齐全部
        if(++orderIndex === values.length) {resolve(resultArr) // 解决实现的后果返回去
        }
      }
      for (let i = 0; i < values.length; i++) {const value = values[i];
        // 是 promise
        if(value && typeof value.then === 'function') {value.then((val)=>{processResultByKey(val,i)
          },reject)
        } else {
          // 不是 promise 状况
          processResultByKey(value,i)
        }
      }
    })
  }
  static race(promises) {
    // 采纳最新胜利或失败的作为后果
    return new myPromise((resolve,reject)=>{for (let i = 0; i < promises.length; i++) {let val = promises[i]
        if(val && typeof val.then === 'function') {
          // 任何一个 promise 先调用 resolve 或 reject 就返回后果了 也就是返回执行最快的那个 promise 的后果
          val.then(resolve,reject)
        }else{
          // 一般值
          resolve(val)
        }
      }
    })
  }
}


/**
 * ===== 测试用例 -====
 */
// let promise1 = new myPromise((resolve,reject)=>{//   setTimeout(() => {//     resolve('胜利')
//   }, 900);
// })

// promise1.then(val=>{//   console.log('success', val)
// },reason=>{//   console.log('fail', reason)
// })

/**
 * then 的应用形式 一般值象征不是 promise
 * 
 * 1、then 中的回调有两个办法 胜利或失败 他们的后果返回(一般值)会传递给外层的下一个 then 中
 * 2、能够在胜利或失败中抛出异样,走到下一次 then 的失败中
 * 3、返回的是一个 promsie,那么会用这个 promise 的状态作为后果,会用 promise 的后果向下传递
 * 4、错误处理,会默认先找离本人最新的错误处理,找不到就向下查找,找打了就执行
 */

// read('./name.txt').then(data=>{
//   return '123'
// }).then(data=>{//}).then(null,err=>{//})
// // .catch(err=>{ // catch 就是没有胜利的 promise

// // })

/**
 * promise.then 实现原理:通过每次返回一个新的 promise 来实现(promise 一旦胜利就不能失败,失败就不能胜利)* 
 */

// function read(data) {//   return new myPromise((resolve,reject)=>{//     setTimeout(() => {//       resolve(new myPromise((resolve,reject)=>resolve(data)))
//     }, 1000);
//   })
// }

// let promise2 = read({name: 'poetry'}).then(data=>{
//   return data
// }).then().then().then(data=>{//   console.log(data,'-data-')
// },(err)=>{//   console.log(err,'-err-')
// })

// finally 测试
// myPromise
//   .resolve(100)
//   .finally(()=>{//     return new myPromise((resolve,reject)=>setTimeout(() => {//       resolve(100)
//     }, 100))
//   })
//   .then(d=>console.log('finally success',d))
//   .catch(er=>console.log(er, 'finally err'))


/**
 * promise.all 测试
 * 
 * myPromise.all 解决并发问题 多个异步并发获取最终的后果
*/

// myPromise.all([1,2,3,4,new myPromise((resolve,reject)=>{//   setTimeout(() => {//     resolve('ok1')
//   }, 1000);
// }),new myPromise((resolve,reject)=>{//   setTimeout(() => {//     resolve('ok2')
//   }, 1000);
// })]).then(d=>{//   console.log(d,'myPromise.all.resolve')
// }).catch(err=>{//   console.log(err,'myPromise.all.reject')
// })


// 实现 promise 中断请求
let promise = new Promise((resolve,reject)=>{setTimeout(() => {
    // 模仿接口调用 ajax 调用超时
    resolve('胜利') 
  }, 10000);
})

function promiseWrap(promise) {
  // 包装一个 promise 能够管制原来的 promise 是胜利 还是失败
  let abort
  let newPromsie = new myPromise((resolve,reject)=>{abort = reject})
  // 只有管制 newPromsie 失败,就能够管制被包装的 promise 走向失败
  // Promise.race 任何一个先胜利或者失败 就能够取得后果
  let p = myPromise.race([promise, newPromsie])
  p.abort = abort

  return p
}

let newPromise = promiseWrap(promise)

setTimeout(() => {
  // 超过 3 秒超时
  newPromise.abort('申请超时')
}, 3000);

newPromise.then(d=>{console.log('d',d)
}).catch(err=>{console.log('err',err)
})


// 应用 promises-aplus-tests 测试写的 promise 是否标准
// 全局装置 cnpm i -g promises-aplus-tests
// 命令行执行 promises-aplus-tests promise.js
// 测试入口 产生提早对象
myPromise.defer = myPromise.deferred = function () {let dfd = {}
  dfd.promise = new myPromise((resolve,reject)=>{
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

// 提早对象用户
// ![](http://img-repo.poetries.top/images/20210509172817.png)
// promise 解决嵌套问题
// function readData(url) {//   let dfd = myPromise.defer()
//   fs.readFile(url, 'utf8', function (err,data) {//     if(err) {//       dfd.reject()
//     }
//     dfd.resolve(data)
//   })
//   return dfd.promise
// }
// readData().then(d=>{
//   return d
// })

module.exports = myPromise

模仿 new

new 操作符做了这些事:

  • 它创立了一个全新的对象
  • 它会被执行[[Prototype]](也就是__proto__)链接
  • 它使 this 指向新创建的对象
  • 通过 new 创立的每个对象将最终被 [[Prototype]] 链接到这个函数的 prototype 对象上
  • 如果函数没有返回对象类型 Object(蕴含 Functoin, Array, Date, RegExg, Error),那么 new 表达式中的函数调用将返回该对象援用
// objectFactory(name, 'cxk', '18')
function objectFactory() {const obj = new Object();
  const Constructor = [].shift.call(arguments);

  obj.__proto__ = Constructor.prototype;

  const ret = Constructor.apply(obj, arguments);

  return typeof ret === "object" ? ret : obj;
}

实现 add(1)(2)(3)

函数柯里化概念:柯里化(Currying)是把承受多个参数的函数转变为承受一个繁多参数的函数,并且返回承受余下的参数且返回后果的新函数的技术。

1)粗犷版

function add (a) {return function (b) {return function (c) {return a + b + c;}
}
}
console.log(add(1)(2)(3)); // 6

2)柯里化解决方案

  • 参数长度固定
var add = function (m) {var temp = function (n) {return add(m + n);
  }
  temp.toString = function () {return m;}
  return temp;
};
console.log(add(3)(4)(5)); // 12
console.log(add(3)(6)(9)(25)); // 43

对于 add(3)(4)(5),其执行过程如下:

  1. 先执行 add(3),此时 m =3,并且返回 temp 函数;
  2. 执行 temp(4),这个函数内执行 add(m+n),n 是此次传进来的数值 4,m 值还是上一步中的 3,所以 add(m+n)=add(3+4)=add(7),此时 m =7,并且返回 temp 函数
  3. 执行 temp(5),这个函数内执行 add(m+n),n 是此次传进来的数值 5,m 值还是上一步中的 7,所以 add(m+n)=add(7+5)=add(12),此时 m =12,并且返回 temp 函数
  4. 因为前面没有传入参数,等于返回的 temp 函数不被执行而是打印,理解 JS 的敌人都晓得对象的 toString 是批改对象转换字符串的办法,因而代码中 temp 函数的 toString 函数 return m 值,而 m 值是最初一步执行函数时的值 m =12,所以返回值是 12。
  5. 参数长度不固定
function add (...args) {
    // 求和
    return args.reduce((a, b) => a + b)
}
function currying (fn) {let args = []
    return function temp (...newArgs) {if (newArgs.length) {
            args = [
                ...args,
                ...newArgs
            ]
            return temp
        } else {let val = fn.apply(this, args)
            args = [] // 保障再次调用时清空
            return val
        }
    }
}
let addCurry = currying(add)
console.log(addCurry(1)(2)(3)(4, 5)())  //15
console.log(addCurry(1)(2)(3, 4, 5)())  //15
console.log(addCurry(1)(2, 3, 4, 5)())  //15

手写 Promise

const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

function MyPromise(fn) {
  // 保留初始化状态
  var self = this;

  // 初始化状态
  this.state = PENDING;

  // 用于保留 resolve 或者 rejected 传入的值
  this.value = null;

  // 用于保留 resolve 的回调函数
  this.resolvedCallbacks = [];

  // 用于保留 reject 的回调函数
  this.rejectedCallbacks = [];

  // 状态转变为 resolved 办法
  function resolve(value) {
    // 判断传入元素是否为 Promise 值,如果是,则状态扭转必须期待前一个状态扭转后再进行扭转
    if (value instanceof MyPromise) {return value.then(resolve, reject);
    }

    // 保障代码的执行程序为本轮事件循环的开端
    setTimeout(() => {
      // 只有状态为 pending 时能力转变,if (self.state === PENDING) {
        // 批改状态
        self.state = RESOLVED;

        // 设置传入的值
        self.value = value;

        // 执行回调函数
        self.resolvedCallbacks.forEach(callback => {callback(value);
        });
      }
    }, 0);
  }

  // 状态转变为 rejected 办法
  function reject(value) {
    // 保障代码的执行程序为本轮事件循环的开端
    setTimeout(() => {
      // 只有状态为 pending 时能力转变
      if (self.state === PENDING) {
        // 批改状态
        self.state = REJECTED;

        // 设置传入的值
        self.value = value;

        // 执行回调函数
        self.rejectedCallbacks.forEach(callback => {callback(value);
        });
      }
    }, 0);
  }

  // 将两个办法传入函数执行
  try {fn(resolve, reject);
  } catch (e) {
    // 遇到谬误时,捕捉谬误,执行 reject 函数
    reject(e);
  }
}

MyPromise.prototype.then = function(onResolved, onRejected) {
  // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
  onResolved =
    typeof onResolved === "function"
      ? onResolved
      : function(value) {return value;};

  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : function(error) {throw error;};

  // 如果是期待状态,则将函数退出对应列表中
  if (this.state === PENDING) {this.resolvedCallbacks.push(onResolved);
    this.rejectedCallbacks.push(onRejected);
  }

  // 如果状态曾经凝固,则间接执行对应状态的函数

  if (this.state === RESOLVED) {onResolved(this.value);
  }

  if (this.state === REJECTED) {onRejected(this.value);
  }
};

实现类的继承

实现类的继承 - 简版

类的继承在几年前是重点内容,有 n 种继承形式各有优劣,es6 遍及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深刻了解的去看红宝书即可,咱们目前只实现一种最现实的继承形式。

// 寄生组合继承
function Parent(name) {this.name = name}
Parent.prototype.say = function() {console.log(this.name + ` say`);
}
Parent.prototype.play = function() {console.log(this.name + ` play`);
}

function Child(name, parent) {
  // 将父类的构造函数绑定在子类上
  Parent.call(this, parent)
  this.name = name
}

/** 
 1. 这一步不必 Child.prototype = Parent.prototype 的起因是怕共享内存,批改父类原型对象就会影响子类
 2. 不必 Child.prototype = new Parent()的起因是会调用 2 次父类的构造方法(另一次是 call),会存在一份多余的父类实例属性
3. Object.create 是创立了父类原型的正本,与父类原型齐全隔离
*/
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function() {console.log(this.name + ` say`);
}

// 留神记得把子类的结构指向子类自身
Child.prototype.constructor = Child;
// 测试
var parent = new Parent('parent');
parent.say() 

var child = new Child('child');
child.say() 
child.play(); // 继承父类的办法

ES5 实现继承 - 具体

第一种形式是借助 call 实现继承

function Parent1(){this.name = 'parent1';}
function Child1(){Parent1.call(this);
    this.type = 'child1'    
}
console.log(new Child1);

这样写的时候子类尽管可能拿到父类的属性值,然而问题是父类中一旦存在办法那么子类无奈继承。那么引出上面的办法

第二种形式借助原型链实现继承:

function Parent2() {
    this.name = 'parent2';
    this.play = [1, 2, 3]
  }
  function Child2() {this.type = 'child2';}
  Child2.prototype = new Parent2();

  console.log(new Child2());

看似没有问题,父类的办法和属性都可能拜访,但实际上有一个潜在的有余。举个例子:

var s1 = new Child2();
  var s2 = new Child2();
  s1.play.push(4);
  console.log(s1.play, s2.play); // [1,2,3,4] [1,2,3,4]

明明我只扭转了 s1 的 play 属性,为什么 s2 也跟着变了呢?很简略,因为两个实例应用的是同一个原型对象

第三种形式:将前两种组合:

function Parent3 () {
    this.name = 'parent3';
    this.play = [1, 2, 3];
  }
  function Child3() {Parent3.call(this);
    this.type = 'child3';
  }
  Child3.prototype = new Parent3();
  var s3 = new Child3();
  var s4 = new Child3();
  s3.play.push(4);
  console.log(s3.play, s4.play); // [1,2,3,4] [1,2,3]

之前的问题都得以解决。然而这里又徒增了一个新问题,那就是 Parent3 的构造函数会多执行了一次(Child3.prototype = new Parent3();)。这是咱们不愿看到的。那么如何解决这个问题?

第四种形式: 组合继承的优化 1

function Parent4 () {
    this.name = 'parent4';
    this.play = [1, 2, 3];
  }
  function Child4() {Parent4.call(this);
    this.type = 'child4';
  }
  Child4.prototype = Parent4.prototype;

这里让将父类原型对象间接给到子类,父类构造函数只执行一次,而且父类属性和办法均能拜访,然而咱们来测试一下

var s3 = new Child4();
  var s4 = new Child4();
  console.log(s3)

子类实例的构造函数是 Parent4,显然这是不对的,应该是 Child4。

第五种形式(最举荐应用):优化 2

function Parent5 () {
    this.name = 'parent5';
    this.play = [1, 2, 3];
  }
  function Child5() {Parent5.call(this);
    this.type = 'child5';
  }
  Child5.prototype = Object.create(Parent5.prototype);
  Child5.prototype.constructor = Child5;

这是最举荐的一种形式,靠近完满的继承。

实现 forEach 办法

Array.prototype.myForEach = function(callback, context=window) {
  // this=>arr
  let self = this,  
      i = 0,
      len = self.length;

  for(;i<len;i++) {typeof callback == 'function' && callback.call(context,self[i], i)
   }
}

将 js 对象转化为树形构造

// 转换前:source = [{
            id: 1,
            pid: 0,
            name: 'body'
          }, {
            id: 2,
            pid: 1,
            name: 'title'
          }, {
            id: 3,
            pid: 2,
            name: 'div'
          }]
// 转换为: 
tree = [{
          id: 1,
          pid: 0,
          name: 'body',
          children: [{
            id: 2,
            pid: 1,
            name: 'title',
            children: [{
              id: 3,
              pid: 1,
              name: 'div'
            }]
          }
        }]

代码实现:

function jsonToTree(data) {
  // 初始化后果数组,并判断输出数据的格局
  let result = []
  if(!Array.isArray(data)) {return result}
  // 应用 map,将以后对象的 id 与以后对象对应存储起来
  let map = {};
  data.forEach(item => {map[item.id] = item;
  });
  // 
  data.forEach(item => {let parent = map[item.pid];
    if(parent) {(parent.children || (parent.children = [])).push(item);
    } else {result.push(item);
    }
  });
  return result;
}

应用 Promise 封装 AJAX 申请

// promise 封装实现:function getJSON(url) {
  // 创立一个 promise 对象
  let promise = new Promise(function(resolve, reject) {let xhr = new XMLHttpRequest();
    // 新建一个 http 申请
    xhr.open("GET", url, true);
    // 设置状态的监听函数
    xhr.onreadystatechange = function() {if (this.readyState !== 4) return;
      // 当申请胜利或失败时,扭转 promise 的状态
      if (this.status === 200) {resolve(this.response);
      } else {reject(new Error(this.statusText));
      }
    };
    // 设置谬误监听函数
    xhr.onerror = function() {reject(new Error(this.statusText));
    };
    // 设置响应的数据类型
    xhr.responseType = "json";
    // 设置申请头信息
    xhr.setRequestHeader("Accept", "application/json");
    // 发送 http 申请
    xhr.send(null);
  });
  return promise;
}

数组去重

const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}];
// => [1, '1', 17, true, false, 'true', 'a', {}, {}]
办法一:利用 Set
const res1 = Array.from(new Set(arr));
办法二:两层 for 循环 +splice
const unique1 = arr => {
  let len = arr.length;
  for (let i = 0; i < len; i++) {for (let j = i + 1; j < len; j++) {if (arr[i] === arr[j]) {arr.splice(j, 1);
        // 每删除一个树,j-- 保障 j 的值通过自加后不变。同时,len--,缩小循环次数晋升性能
        len--;
        j--;
      }
    }
  }
  return arr;
}
办法三:利用 indexOf
const unique2 = arr => {const res = [];
  for (let i = 0; i < arr.length; i++) {if (res.indexOf(arr[i]) === -1) res.push(arr[i]);
  }
  return res;
}

当然也能够用 include、filter,思路大同小异。

办法四:利用 include
const unique3 = arr => {const res = [];
  for (let i = 0; i < arr.length; i++) {if (!res.includes(arr[i])) res.push(arr[i]);
  }
  return res;
}
办法五:利用 filter
const unique4 = arr => {return arr.filter((item, index) => {return arr.indexOf(item) === index;
  });
}
办法六:利用 Map
const unique5 = arr => {const map = new Map();
  const res = [];
  for (let i = 0; i < arr.length; i++) {if (!map.has(arr[i])) {map.set(arr[i], true)
      res.push(arr[i]);
    }
  }
  return res;
}

数组中的数据依据 key 去重

给定一个任意数组,实现一个通用函数,让数组中的数据依据 key 排重:

const dedup = (data, getKey = () => {}) => {// todo}
let data = [{ id: 1, v: 1},
  {id: 2, v: 2},
  {id: 1, v: 1},
];

// 以 id 作为排重 key,执行函数失去后果
// data = [//   { id: 1, v: 1},
//   {id: 2, v: 2},
// ];

实现

const dedup = (data, getKey = () => {}) => {const dateMap = data.reduce((pre, cur) => {const key = getKey(cur)
        if (!pre[key]) {pre[key] = cur
        }
        return pre
    }, {})
    return Object.values(dateMap)
}

应用

let data = [{ id: 1, v: 1},
    {id: 2, v: 2},
    {id: 1, v: 1},
];
console.log(dedup(data, (item) => item.id))

// 以 id 作为排重 key,执行函数失去后果
// data = [//   { id: 1, v: 1},
//   {id: 2, v: 2},
// ];

手写 Promise.then

then 办法返回一个新的 promise 实例,为了在 promise 状态发生变化时(resolve / reject 被调用时)再执行 then 里的函数,咱们应用一个 callbacks 数组先把传给 then 的函数暂存起来,等状态扭转时再调用。

那么,怎么保障后一个 **then** 里的办法在前一个 **then**(可能是异步)完结之后再执行呢? 咱们能够将传给 then 的函数和新 promiseresolve 一起 push 到前一个 promisecallbacks 数组中,达到承前启后的成果:

  • 承前:以后一个 promise 实现后,调用其 resolve 变更状态,在这个 resolve 里会顺次调用 callbacks 里的回调,这样就执行了 then 里的办法了
  • 启后:上一步中,当 then 里的办法执行实现后,返回一个后果,如果这个后果是个简略的值,就间接调用新 promiseresolve,让其状态变更,这又会顺次调用新 promisecallbacks 数组里的办法,周而复始。。如果返回的后果是个 promise,则须要等它实现之后再触发新 promiseresolve,所以能够在其后果的 then 里调用新 promiseresolve
then(onFulfilled, onReject){
    // 保留前一个 promise 的 this
    const self = this; 
    return new MyPromise((resolve, reject) => {
      // 封装前一个 promise 胜利时执行的函数
      let fulfilled = () => {
        try{const result = onFulfilled(self.value); // 承前
          return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); // 启后
        }catch(err){reject(err)
        }
      }
      // 封装前一个 promise 失败时执行的函数
      let rejected = () => {
        try{const result = onReject(self.reason);
          return result instanceof MyPromise? result.then(resolve, reject) : reject(result);
        }catch(err){reject(err)
        }
      }
      switch(self.status){
        case PENDING: 
          self.onFulfilledCallbacks.push(fulfilled);
          self.onRejectedCallbacks.push(rejected);
          break;
        case FULFILLED:
          fulfilled();
          break;
        case REJECT:
          rejected();
          break;
      }
    })
   }

留神:

  • 间断多个 then 里的回调办法是同步注册的,但注册到了不同的 callbacks 数组中,因为每次 then 都返回新的 promise 实例(参考下面的例子和图)
  • 注册实现后开始执行构造函数中的异步事件,异步实现之后顺次调用 callbacks 数组中提前注册的回调
退出移动版