乐趣区

关于前端:阿里前端二面经典手写面试题汇总

实现类的继承

实现类的继承 - 简版

类的继承在几年前是重点内容,有 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;

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

实现 instanceOf

思路:

  • 步骤 1:先获得以后类的原型,以后实例对象的原型链
  • ​步骤 2:始终循环(执行原型链的查找机制)

    • 获得以后实例对象原型链的原型链(proto = proto.__proto__,沿着原型链始终向上查找)
    • 如果 以后实例的原型链 __proto__ 上找到了以后类的原型prototype,则返回 true
    • 如果 始终找到 Object.prototype.__proto__ == nullObject 的基类 (null) 下面都没找到,则返回 false
// 实例.__ptoto__ === 类.prototype
function _instanceof(example, classFunc) {
    // 因为 instance 要检测的是某对象,须要有一个前置判断条件
    // 根本数据类型间接返回 false
    if(typeof example !== 'object' || example === null) return false;

    let proto = Object.getPrototypeOf(example);
    while(true) {if(proto == null) return false;

        // 在以后实例对象的原型链上,找到了以后类
        if(proto == classFunc.prototype) return true;
        // 沿着原型链__ptoto__一层一层向上查
        proto = Object.getPrototypeof(proto); // 等于 proto.__ptoto__
    }
}

console.log('test', _instanceof(null, Array)) // false
console.log('test', _instanceof([], Array)) // true
console.log('test', _instanceof('', Array)) // false
console.log('test', _instanceof({}, Object)) // true

实现一个队列

基于链表构造实现队列

const LinkedList = require('./ 实现一个链表构造')

// 用链表默认应用数组来模仿队列,性能更佳
class Queue {constructor() {this.ll = new LinkedList()
  }
  // 向队列中增加
  offer(elem) {this.ll.add(elem)
  }
  // 查看第一个
  peek() {return this.ll.get(0)
  }
  // 队列只能从头部删除
  remove() {return this.ll.remove(0)
  }
}

var queue = new Queue()

queue.offer(1)
queue.offer(2)
queue.offer(3)
var removeVal = queue.remove(3)

console.log(queue.ll,'queue.ll')
console.log(removeVal,'queue.remove')
console.log(queue.peek(),'queue.peek')

实现 every 办法

Array.prototype.myEvery=function(callback, context = window){
    var len=this.length,
        flag=true,
        i = 0;

    for(;i < len; i++){if(!callback.apply(context,[this[i], i , this])){
        flag=false;
        break;
      } 
    }
    return flag;
  }


  // var obj = {num: 1}
  // var aa=arr.myEvery(function(v,index,arr){
  //     return v.num>=12;
  // },obj)
  // console.log(aa)

实现 LRU 淘汰算法

LRU 缓存算法是一个十分经典的算法,在很多面试中常常问道,不仅仅包含前端面试

LRU 英文全称是 Least Recently Used,英译过去就是”最近起码应用“的意思。LRU 是一种罕用的页面置换算法,抉择最近最久未应用的页面予以淘汰。该算法赋予每个页面一个拜访字段,用来记录一个页面自上次被拜访以来所经验的工夫 t,当须淘汰一个页面时,抉择现有页面中其 t 值最大的,即最近起码应用的页面予以淘汰

艰深的解释:

如果咱们有一块内存,专门用来缓存咱们最近发拜访的网页,拜访一个新网页,咱们就会往内存中增加一个网页地址,随着网页的一直减少,内存存满了,这个时候咱们就须要思考删除一些网页了。这个时候咱们找到内存中最早拜访的那个网页地址,而后把它删掉。这一整个过程就能够称之为 LRU 算法

上图就很好的解释了 LRU 算法在干嘛了,其实非常简单,无非就是咱们往内存外面增加或者删除元素的时候,遵循 最近起码应用准则

应用场景

LRU 算法应用的场景十分多,这里简略举几个例子即可:

  • 咱们操作系统底层的内存治理,其中就包含有 LRU 算法
  • 咱们常见的缓存服务,比方 redis 等等
  • 比方浏览器的最近浏览记录存储
  • vue中的 keep-alive 组件应用了 LRU 算法

梳理实现 LRU 思路

  • 特点剖析:

    • 咱们须要一块无限的存储空间,因为有限的化就没必要应用 LRU 算发删除数据了。
    • 咱们这块存储空间外面存储的数据须要是有序的,因为咱们必须要程序来删除数据,所以能够思考应用 ArrayMap 数据结构来存储,不能应用 Object,因为它是无序的。
    • 咱们可能删除或者增加以及获取到这块存储空间中的指定数据。
    • 存储空间存满之后,在增加数据时,会主动删除工夫最长远的那条数据。
  • 实现需求:

    • 实现一个 LRUCache 类型,用来充当存储空间
    • 采纳 Map 数据结构存储数据,因为它的存取时间复杂度为 O(1),数组为 O(n)
    • 实现 getset 办法,用来获取和增加数据
    • 咱们的存储空间有长度限度,所以无需提供删除办法,存储满之后,主动删除最长远的那条数据
    • 当应用 get 获取数据后,该条数据须要更新到最后面

具体实现

class LRUCache {constructor(length) {
    this.length = length; // 存储长度
    this.data = new Map(); // 存储数据}
  // 存储数据,通过键值对的形式
  set(key, value) {
    const data = this.data;
    if (data.has(key)) {data.delete(key)
    }

    data.set(key, value);

    // 如果超出了容量,则须要删除最久的数据
    if (data.size > this.length) {const delKey = data.keys().next().value;
      data.delete(delKey);
    }
  }
  // 获取数据
  get(key) {
    const data = this.data;
    // 未找到
    if (!data.has(key)) {return null;}
    const value = data.get(key); // 获取元素
    data.delete(key); // 删除元素
    data.set(key, value); // 从新插入元素

    return value // 返回获取的值
  }
}
var lruCache = new LRUCache(5);
  • set 办法:往 map 外面增加新数据,如果增加的数据存在了,则先删除该条数据,而后再增加。如果增加数据后超长了,则须要删除最长远的一条数据。data.keys().next().value 便是获取最初一条数据的意思。
  • get 办法:首先从 map 对象中拿出该条数据,而后删除该条数据,最初再从新插入该条数据,确保将该条数据挪动到最后面
// 测试

// 存储数据 set:lruCache.set('name', 'test');
lruCache.set('age', 10);
lruCache.set('sex', '男');
lruCache.set('height', 180);
lruCache.set('weight', '120');
console.log(lruCache);

持续插入数据,此时会超长,代码如下:

lruCache.set('grade', '100');
console.log(lruCache);

此时咱们发现存储工夫最久的 name 曾经被移除了,新插入的数据变为了最后面的一个。

咱们应用 get 获取数据,代码如下:

咱们发现此时 sex 字段曾经跑到最后面去了

总结

LRU 算法其实逻辑十分的简略,明确了原理之后实现起来十分的简略。最次要的是咱们须要应用什么数据结构来存储数据,因为 map 的存取十分快,所以咱们采纳了它,当然数组其实也能够实现的。还有一些小伙伴应用链表来实现 LRU,这当然也是能够的。

实现 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

参考 前端进阶面试题具体解答

实现一个繁难的 MVVM

实现一个繁难的 MVVM 我会分为这么几步来:

  1. 首先我会定义一个类 Vue,这个类接管的是一个options,那么其中可能有须要挂载的根元素的id,也就是el 属性;而后应该还有一个 data 属性,示意须要双向绑定的数据
  2. 其次我会定义一个 Dep 类,这个类产生的实例对象中会定义一个 subs 数组用来寄存所依赖这个属性的依赖,曾经增加依赖的办法 addSub,删除办法removeSub,还有一个notify 办法用来遍历更新它 subs 中的所有依赖,同时 Dep 类有一个动态属性 target 它用来示意以后的观察者,当后续进行依赖收集的时候能够将它增加到 dep.subs 中。
  3. 而后设计一个 observe 办法,这个办法接管的是传进来的 data,也就是options.data,外面会遍历data 中的每一个属性,并应用 Object.defineProperty() 来重写它的 getset,那么这外面呢能够应用 new Dep() 实例化一个 dep 对象,在 get 的时候调用其 addSub 办法增加以后的观察者 Dep.target 实现依赖收集,并且在 set 的时候调用 dep.notify 办法来告诉每一个依赖它的观察者进行更新
  4. 实现这些之后,咱们还须要一个 compile 办法来将 HTML 模版和数据联合起来。在这个办法中首先传入的是一个 node 节点,而后遍历它的所有子级,判断是否有 firstElmentChild,有的话则进行递归调用 compile 办法,没有firstElementChild 的话且该 child.innderHTML 用正则匹配满足有 /\{\{(.*)\}\}/ 项的话则示意有须要双向绑定的数据,那么就将用正则 new Reg('\\{\\{\\s*' + key + '\\s*\\}\\}', 'gm') 替换掉是其为 msg 变量。
  5. 实现变量替换的同时,还须要将 Dep.target 指向以后的这个 child,且调用一下this.opt.data[key],也就是为了触发这个数据的get 来对以后的 child 进行依赖收集,这样下次数据变动的时候就能告诉 child 进行视图更新了,不过在最初要记得将 Dep.target 指为 null 哦(其实在 Vue 中是有一个 targetStack 栈用来寄存 target 的指向的)
  6. 那么最初咱们只须要监听 documentDOMContentLoaded而后在回调函数中实例化这个 Vue 对象就能够了

coding :

须要留神的点:

  • childNodes会获取到所有的子节点以及文本节点(包含元素标签中的空白节点)
  • firstElementChild示意获取元素的第一个字元素节点,以此来辨别是不是元素节点,如果是的话则调用 compile 进行递归调用,否则用正则匹配
  • 这外面的正则真的不难,大家能够看一下

残缺代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>MVVM</title>
  </head>
  <body>
    <div id="app">
      <h3> 姓名 </h3>
      <p>{{name}}</p>
      <h3> 年龄 </h3>
      <p>{{age}}</p>
    </div>
  </body>
</html>
<script>
  document.addEventListener(
    "DOMContentLoaded",
    function () {let opt = { el: "#app", data: { name: "期待批改...", age: 20} };
      let vm = new Vue(opt);
      setTimeout(() => {opt.data.name = "jing";}, 2000);
    },
    false
  );
  class Vue {constructor(opt) {
      this.opt = opt;
      this.observer(opt.data);
      let root = document.querySelector(opt.el);
      this.compile(root);
    }
    observer(data) {Object.keys(data).forEach((key) => {let obv = new Dep();
        data["_" + key] = data[key];

        Object.defineProperty(data, key, {get() {Dep.target && obv.addSubNode(Dep.target);
            return data["_" + key];
          },
          set(newVal) {obv.update(newVal);
            data["_" + key] = newVal;
          },
        });
      });
    }
    compile(node) {[].forEach.call(node.childNodes, (child) => {if (!child.firstElementChild && /\{\{(.*)\}\}/.test(child.innerHTML)) {let key = RegExp.$1.trim();
          child.innerHTML = child.innerHTML.replace(new RegExp("\\{\\{\\s*" + key + "\\s*\\}\\}", "gm"),
            this.opt.data[key]
          );
          Dep.target = child;
          this.opt.data[key];
          Dep.target = null;
        } else if (child.firstElementChild) this.compile(child);
      });
    }
  }

  class Dep {constructor() {this.subNode = [];
    }
    addSubNode(node) {this.subNode.push(node);
    }
    update(newVal) {this.subNode.forEach((node) => {node.innerHTML = newVal;});
    }
  }
</script>

简化版 2

function update(){console.log('数据变动~~~ mock update view')
}
let obj = [1,2,3]
// 变异办法 push shift unshfit reverse sort splice pop
// Object.defineProperty
let oldProto = Array.prototype;
let proto = Object.create(oldProto); // 克隆了一分
['push','shift'].forEach(item=>{proto[item] = function(){update();
    oldProto[item].apply(this,arguments);
  }
})
function observer(value){ // proxy reflect
  if(Array.isArray(value)){
    // AOP
    return value.__proto__ = proto;
    // 重写 这个数组里的 push shift unshfit reverse sort splice pop
  }
  if(typeof value !== 'object'){return value;}
  for(let key in value){defineReactive(value,key,value[key]);
  }
}
function defineReactive(obj,key,value){observer(value); // 如果是对象 持续减少 getter 和 setter
  Object.defineProperty(obj,key,{get(){return value;},
    set(newValue){if(newValue !== value){observer(newValue);
            value = newValue;
            update();}
    }
  })
}
observer(obj); 
// AOP
// obj.name = {n:200}; // 数据变了 须要更新视图 深度监控
// obj.name.n = 100;
obj.push(123);
obj.push(456);
console.log(obj);

实现 JSONP 办法

利用 <script> 标签不受跨域限度的特点,毛病是只能反对 get 申请

  • 创立 script 标签
  • 设置 script 标签的 src 属性,以问号传递参数,设置好回调函数 callback 名称
  • 插入到 html 文本中
  • 调用回调函数,res参数就是获取的数据
function jsonp({url,params,callback}) {return new Promise((resolve,reject)=>{let script = document.createElement('script')

    window[callback] = function (data) {resolve(data)
      document.body.removeChild(script)
    }
    var arr = []
    for(var key in params) {arr.push(`${key}=${params[key]}`)
    }
    script.type = 'text/javascript'
    script.src = `${url}?callback=${callback}&${arr.join('&')}`
    document.body.appendChild(script)
  })
}
// 测试用例
jsonp({
  url: 'http://suggest.taobao.com/sug',
  callback: 'getData',
  params: {
    q: 'iphone 手机',
    code: 'utf-8'
  },
}).then(data=>{console.log(data)})
  • 设置 CORS: Access-Control-Allow-Origin:*
  • postMessage

实现 Ajax

步骤

  • 创立 XMLHttpRequest 实例
  • 收回 HTTP 申请
  • 服务器返回 XML 格局的字符串
  • JS 解析 XML,并更新部分页面
  • 不过随着历史进程的推动,XML 曾经被淘汰,取而代之的是 JSON。

理解了属性和办法之后,依据 AJAX 的步骤,手写最简略的 GET 申请。

对象数组列表转成树形构造(解决菜单)

[
    {
        id: 1,
        text: '节点 1',
        parentId: 0 // 这里用 0 示意为顶级节点
    },
    {
        id: 2,
        text: '节点 1_1',
        parentId: 1 // 通过这个字段来确定子父级
    }
    ...
]

转成
[
    {
        id: 1,
        text: '节点 1',
        parentId: 0,
        children: [
            {
                id:2,
                text: '节点 1_1',
                parentId:1
            }
        ]
    }
]

实现代码如下:

function listToTree(data) {let temp = {};
  let treeData = [];
  for (let i = 0; i < data.length; i++) {temp[data[i].id] = data[i];
  }
  for (let i in temp) {if (+temp[i].parentId != 0) {if (!temp[temp[i].parentId].children) {temp[temp[i].parentId].children = [];}
      temp[temp[i].parentId].children.push(temp[i]);
    } else {treeData.push(temp[i]);
    }
  }
  return treeData;
}

异步串行 | 异步并行

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

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

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

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

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

手写深度比拟 isEqual

思路:深度比拟两个对象,就是要深度比拟对象的每一个元素。=> 递归

  • 递归退出条件:

    • 被比拟的是两个值类型变量,间接用“===”判断
    • 被比拟的两个变量之一为null,直接判断另一个元素是否也为null
  • 提前结束递推:

    • 两个变量 keys 数量不同
    • 传入的两个参数是同一个变量
  • 递推工作:深度比拟每一个key
function isEqual(obj1, obj2){
    // 其中一个为值类型或 null
    if(!isObject(obj1) || !isObject(obj2)){return obj1 === obj2;}

    // 判断是否两个参数是同一个变量
    if(obj1 === obj2){return true;}

    // 判断 keys 数是否相等
    const obj1Keys = Object.keys(obj1);
    const obj2Keys = Object.keys(obj2);
    if(obj1Keys.length !== obj2Keys.length){return false;}

    // 深度比拟每一个 key
    for(let key in obj1){if(!isEqual(obj1[key], obj2[key])){return false;}
    }

    return true;
}

数组中的数据依据 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},
// ];

实现节流函数(throttle)

节流函数原理: 指频繁触发事件时,只会在指定的时间段内执行事件回调,即触发事件间隔大于等于指定的工夫才会执行回调函数。总结起来就是:事件,依照一段时间的距离来进行触发

像 dom 的拖拽,如果用消抖的话,就会呈现卡顿的感觉,因为只在进行的时候执行了一次,这个时候就应该用节流,在肯定工夫内屡次执行,会晦涩很多

手写简版

应用工夫戳的节流函数会在第一次触发事件时立刻执行,当前每过 wait 秒之后才执行一次,并且最初一次触发事件不会被执行

工夫戳形式:

// func 是用户传入须要防抖的函数
// wait 是等待时间
const throttle = (func, wait = 50) => {
  // 上一次执行该函数的工夫
  let lastTime = 0
  return function(...args) {
    // 以后工夫
    let now = +new Date()
    // 将以后工夫和上一次执行函数工夫比照
    // 如果差值大于设置的等待时间就执行函数
    if (now - lastTime > wait) {
      lastTime = now
      func.apply(this, args)
    }
  }
}

setInterval(throttle(() => {console.log(1)
  }, 500),
  1
)

定时器形式:

应用定时器的节流函数在第一次触发时不会执行,而是在 delay 秒之后才执行,当最初一次进行触发后,还会再执行一次函数

function throttle(func, delay){
  var timer = null;
  returnfunction(){
    var context = this;
    var args = arguments;
    if(!timer){timer = setTimeout(function(){func.apply(context, args);
        timer = null;
      },delay);
    }
  }
}

实用场景:

  • DOM 元素的拖拽性能实现(mousemove
  • 搜寻联想(keyup
  • 计算鼠标挪动的间隔(mousemove
  • Canvas 模仿画板性能(mousemove
  • 监听滚动事件判断是否到页面底部主动加载更多
  • 拖拽场景:固定工夫内只执行一次,避免超高频次触发地位变动
  • 缩放场景:监控浏览器resize
  • 动画场景:防止短时间内屡次触发动画引起性能问题

总结

  • 函数防抖:将几次操作合并为一次操作进行。原理是保护一个计时器,规定在 delay 工夫后触发函数,然而在 delay 工夫内再次触发的话,就会勾销之前的计时器而从新设置。这样一来,只有最初一次操作能被触发。
  • 函数节流:使得肯定工夫内只触发一次函数。原理是通过判断是否达到肯定工夫来触发函数。

实现一个 sleep 函数,比方 sleep(1000) 意味着期待 1000 毫秒

// 应用 promise 来实现 sleep
const sleep = (time) => {return new Promise(resolve => setTimeout(resolve, time))
}

sleep(1000).then(() => {// 这里写你的骚操作})

树形构造转成列表(解决菜单)

[
    {
        id: 1,
        text: '节点 1',
        parentId: 0,
        children: [
            {
                id:2,
                text: '节点 1_1',
                parentId:1
            }
        ]
    }
]
转成
[
    {
        id: 1,
        text: '节点 1',
        parentId: 0 // 这里用 0 示意为顶级节点
    },
    {
        id: 2,
        text: '节点 1_1',
        parentId: 1 // 通过这个字段来确定子父级
    }
    ...
]

实现代码如下:

function treeToList(data) {let res = [];
  const dfs = (tree) => {tree.forEach((item) => {if (item.children) {dfs(item.children);
        delete item.children;
      }
      res.push(item);
    });
  };
  dfs(data);
  return res;
}

实现 lodash 的 chunk 办法 – 数组按指定长度拆分

题目

/**
 * @param input
 * @param size
 * @returns {Array}
 */
_.chunk(['a', 'b', 'c', 'd'], 2)
// => [['a', 'b'], ['c', 'd']]

_.chunk(['a', 'b', 'c', 'd'], 3)
// => [['a', 'b', 'c'], ['d']]

_.chunk(['a', 'b', 'c', 'd'], 5)
// => [['a', 'b', 'c', 'd']]

_.chunk(['a', 'b', 'c', 'd'], 0)
// => []

实现

function chunk(arr, length) {let newArr = [];
  for (let i = 0; i < arr.length; i += length) {newArr.push(arr.slice(i, i + length));
  }
  return newArr;
}

设计一个办法提取对象中所有 value 大于 2 的键值对并返回最新的对象

实现:

var obj = {a: 1, b: 3, c: 4}
foo(obj) // {b: 3, c: 4}

办法有很多种,这里提供一种比拟简洁的写法,用到了 ES10Object.fromEntries()

var obj = {a: 1, b: 3, c: 4}
function foo (obj) {
  return Object.fromEntries(Object.entries(obj).filter(([key, value]) => value > 2)
  )
}
var obj2 = foo(obj) // {b: 3, c: 4}
console.log(obj2)
// ES8 中 Object.entries()的作用:var obj = {a: 1, b: 2}
var entries = Object.entries(obj); // [['a', 1], ['b', 2]]
// ES10 中 Object.fromEntries()的作用:Object.fromEntries(entries); // {a: 1, b: 2}

实现一个链表构造

链表构造

看图了解 next 层级

// 链表 从头尾删除、减少 性能比拟好
// 分为很多类 罕用单向链表、双向链表

// js 模仿链表构造:增删改查

// 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 层级
    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
 }
}


var ll = new LinkedList()

ll.add(0,100) // Node {ellement: 100, next: null}
ll.add(0,200) // Node {element: 200, next: Node { element: 100, next: null} }
ll.add(1,500) // Node {element: 200,next: Node { element: 100, next: Node { element: 500, next: null} } }
ll.add(300)
ll.remove(0)

console.log(ll.get(2),'get')
console.log(ll.head)

module.exports = LinkedList

数组去重办法汇总

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

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

退出移动版