写点什么

js 手写前端需要掌握的点

  • 2022-10-24
    浙江
  • 本文字数:25611 字

    阅读完需:约 84 分钟

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 六大情况用法


// 情况1Promise.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.3function 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所有apiconst 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, rejectconst 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)); // 12console.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。


  • 参数长度不固定


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)())  //15console.log(addCurry(1)(2)(3, 4, 5)())  //15console.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 数组中提前注册的回调


用户头像

还未添加个人签名 2022-07-31 加入

还未添加个人简介

评论

发布
暂无评论
js手写前端需要掌握的点_JavaScript_helloworld1024fd_InfoQ写作社区