异步并发数限度

/** * 关键点 * 1. new promise 一经创立,立刻执行 * 2. 应用 Promise.resolve().then 能够把工作加到微工作队列,避免立刻执行迭代办法 * 3. 微工作处理过程中,产生的新的微工作,会在同一事件循环内,追加到微工作队列里 * 4. 应用 race 在某个工作实现时,持续增加工作,放弃工作依照最大并发数进行执行 * 5. 工作实现后,须要从 doingTasks 中移出 */function limit(count, array, iterateFunc) {  const tasks = []  const doingTasks = []  let i = 0  const enqueue = () => {    if (i === array.length) {      return Promise.resolve()    }    const task = Promise.resolve().then(() => iterateFunc(array[i++]))    tasks.push(task)    const doing = task.then(() => doingTasks.splice(doingTasks.indexOf(doing), 1))    doingTasks.push(doing)    const res = doingTasks.length >= count ? Promise.race(doingTasks) : Promise.resolve()    return res.then(enqueue)  };  return enqueue().then(() => Promise.all(tasks))}// testconst timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i))limit(2, [1000, 1000, 1000, 1000], timeout).then((res) => {  console.log(res)})

实现some办法

Array.prototype.mySome=function(callback, context = window){             var len = this.length,                 flag=false,           i = 0;             for(;i < len; i++){                if(callback.apply(context, [this[i], i , this])){                    flag=true;                    break;                }              }             return flag;        }        // var flag=arr.mySome((v,index,arr)=>v.num>=10,obj)        // console.log(flag);

实现ES6的extends

function B(name){  this.name = name;};function A(name,age){  //1.将A的原型指向B  Object.setPrototypeOf(A,B);  //2.用A的实例作为this调用B,失去继承B之后的实例,这一步相当于调用super  Object.getPrototypeOf(A).call(this, name)  //3.将A原有的属性增加到新实例上  this.age = age;   //4.返回新实例对象  return this;};var a = new A('poetry',22);console.log(a);

实现call办法

call做了什么:

  • 将函数设为对象的属性
  • 执行和删除这个函数
  • 指定this到函数并传入给定参数执行函数
  • 如果不传入参数,默认指向为 window
// 模仿 call bar.mycall(null);//实现一个call办法:// 原理:利用 context.xxx = self obj.xx = func-->obj.xx()Function.prototype.myCall = function(context = window, ...args) {  if (typeof this !== "function") {    throw new Error('type error')  }  // this-->func  context--> obj  args--> 传递过去的参数  // 在context上加一个惟一值不影响context上的属性  let key = Symbol('key')  context[key] = this; // context为调用的上下文,this此处为函数,将这个函数作为context的办法  // let args = [...arguments].slice(1)   //第一个参数为obj所以删除,伪数组转为数组  // 绑定参数 并执行函数  let result = context[key](...args);  // 革除定义的this 不删除会导致context属性越来越多  delete context[key];  // 返回后果   return result;};
//用法:f.call(obj,arg1)function f(a,b){ console.log(a+b) console.log(this.name)}let obj={ name:1}f.myCall(obj,1,2) //否则this指向window

实现map办法

  • 回调函数的参数有哪些,返回值如何解决
  • 不批改原来的数组
Array.prototype.myMap = function(callback, context){  // 转换类数组  var arr = Array.prototype.slice.call(this),//因为是ES5所以就不必...开展符了      mappedArr = [],       i = 0;  for (; i < arr.length; i++ ){    // 把以后值、索引、以后数组返回去。调用的时候传到函数参数中 [1,2,3,4].map((curr,index,arr))    mappedArr.push(callback.call(context, arr[i], i, this));  }  return mappedArr;}

实现reduce办法

  • 初始值不传怎么解决
  • 回调函数的参数有哪些,返回值如何解决。
Array.prototype.myReduce = function(fn, initialValue) {  var arr = Array.prototype.slice.call(this);  var res, startIndex;  res = initialValue ? initialValue : arr[0]; // 不传默认取数组第一项  startIndex = initialValue ? 0 : 1;  for(var i = startIndex; i < arr.length; i++) {    // 把初始值、以后值、索引、以后数组返回去。调用的时候传到函数参数中 [1,2,3,4].reduce((initVal,curr,index,arr))    res = fn.call(null, res, arr[i], i, this);   }  return res;}

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

实现Array.isArray办法

Array.myIsArray = function(o) {  return Object.prototype.toString.call(Object(o)) === '[object Array]';};console.log(Array.myIsArray([])); // true

实现节流函数(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工夫内再次触发的话,就会勾销之前的计时器而从新设置。这样一来,只有最初一次操作能被触发。
  • 函数节流 :使得肯定工夫内只触发一次函数。原理是通过判断是否达到肯定工夫来触发函数。

实现迭代器生成函数

咱们说迭代器对象全凭迭代器生成函数帮咱们生成。在ES6中,实现一个迭代器生成函数并不是什么难事儿,因为ES6早帮咱们思考好了全套的解决方案,内置了贴心的 生成器Generator)供咱们应用:

// 编写一个迭代器生成函数function *iteratorGenerator() {    yield '1号选手'    yield '2号选手'    yield '3号选手'}const iterator = iteratorGenerator()iterator.next()iterator.next()iterator.next()

丢进控制台,不负众望:

写一个生成器函数并没有什么难度,但在面试的过程中,面试官往往对生成器这种语法糖背地的实现逻辑更感兴趣。上面咱们要做的,不仅仅是写一个迭代器对象,而是用ES5去写一个可能生成迭代器对象的迭代器生成函数(解析在正文里):

// 定义生成器函数,入参是任意汇合function iteratorGenerator(list) {    // idx记录以后拜访的索引    var idx = 0    // len记录传入汇合的长度    var len = list.length    return {        // 自定义next办法        next: function() {            // 如果索引还没有超出汇合长度,done为false            var done = idx >= len            // 如果done为false,则能够持续取值            var value = !done ? list[idx++] : undefined            // 将以后值与遍历是否结束(done)返回            return {                done: done,                value: value            }        }    }}var iterator = iteratorGenerator(['1号选手', '2号选手', '3号选手'])iterator.next()iterator.next()iterator.next()

此处为了记录每次遍历的地位,咱们实现了一个闭包,借助自在变量来做咱们的迭代过程中的“游标”。

运行一下咱们自定义的迭代器,后果合乎预期:

实现数组的map办法

Array.prototype._map = function(fn) {   if (typeof fn !== "function") {        throw Error('参数必须是一个函数');    }    const res = [];    for (let i = 0, len = this.length; i < len; i++) {        res.push(fn(this[i]));    }    return res;}

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

手写节流函数

函数节流是指规定一个单位工夫,在这个单位工夫内,只能有一次触发事件的回调函数执行,如果在同一个单位工夫内某事件被触发屡次,只有一次能失效。节流能够应用在 scroll 函数的事件监听上,通过事件节流来升高事件调用的频率。

// 函数节流的实现;function throttle(fn, delay) {  let curTime = Date.now();  return function() {    let context = this,        args = arguments,        nowTime = Date.now();    // 如果两次工夫距离超过了指定工夫,则执行函数。    if (nowTime - curTime >= delay) {      curTime = Date.now();      return fn.apply(context, args);    }  };}

实现Event(event bus)

event bus既是node中各个模块的基石,又是前端组件通信的依赖伎俩之一,同时波及了订阅-公布设计模式,是十分重要的根底。

简略版:

class EventEmeitter {  constructor() {    this._events = this._events || new Map(); // 贮存事件/回调键值对    this._maxListeners = this._maxListeners || 10; // 设立监听下限  }}// 触发名为type的事件EventEmeitter.prototype.emit = function(type, ...args) {  let handler;  // 从贮存事件键值对的this._events中获取对应事件回调函数  handler = this._events.get(type);  if (args.length > 0) {    handler.apply(this, args);  } else {    handler.call(this);  }  return true;};// 监听名为type的事件EventEmeitter.prototype.addListener = function(type, fn) {  // 将type事件以及对应的fn函数放入this._events中贮存  if (!this._events.get(type)) {    this._events.set(type, fn);  }};

面试版:

class EventEmeitter {  constructor() {    this._events = this._events || new Map(); // 贮存事件/回调键值对    this._maxListeners = this._maxListeners || 10; // 设立监听下限  }}// 触发名为type的事件EventEmeitter.prototype.emit = function(type, ...args) {  let handler;  // 从贮存事件键值对的this._events中获取对应事件回调函数  handler = this._events.get(type);  if (args.length > 0) {    handler.apply(this, args);  } else {    handler.call(this);  }  return true;};// 监听名为type的事件EventEmeitter.prototype.addListener = function(type, fn) {  // 将type事件以及对应的fn函数放入this._events中贮存  if (!this._events.get(type)) {    this._events.set(type, fn);  }};// 触发名为type的事件EventEmeitter.prototype.emit = function(type, ...args) {  let handler;  handler = this._events.get(type);  if (Array.isArray(handler)) {    // 如果是一个数组阐明有多个监听者,须要顺次此触发外面的函数    for (let i = 0; i < handler.length; i++) {      if (args.length > 0) {        handler[i].apply(this, args);      } else {        handler[i].call(this);      }    }  } else {    // 单个函数的状况咱们间接触发即可    if (args.length > 0) {      handler.apply(this, args);    } else {      handler.call(this);    }  }  return true;};// 监听名为type的事件EventEmeitter.prototype.addListener = function(type, fn) {  const handler = this._events.get(type); // 获取对应事件名称的函数清单  if (!handler) {    this._events.set(type, fn);  } else if (handler && typeof handler === "function") {    // 如果handler是函数阐明只有一个监听者    this._events.set(type, [handler, fn]); // 多个监听者咱们须要用数组贮存  } else {    handler.push(fn); // 曾经有多个监听者,那么间接往数组里push函数即可  }};EventEmeitter.prototype.removeListener = function(type, fn) {  const handler = this._events.get(type); // 获取对应事件名称的函数清单  // 如果是函数,阐明只被监听了一次  if (handler && typeof handler === "function") {    this._events.delete(type, fn);  } else {    let postion;    // 如果handler是数组,阐明被监听屡次要找到对应的函数    for (let i = 0; i < handler.length; i++) {      if (handler[i] === fn) {        postion = i;      } else {        postion = -1;      }    }    // 如果找到匹配的函数,从数组中革除    if (postion !== -1) {      // 找到数组对应的地位,间接革除此回调      handler.splice(postion, 1);      // 如果革除后只有一个函数,那么勾销数组,以函数模式保留      if (handler.length === 1) {        this._events.set(type, handler[0]);      }    } else {      return this;    }  }};
实现具体过程和思路见实现event

实现数组元素求和

  • 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

实现类的继承

实现类的继承-简版

类的继承在几年前是重点内容,有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;
这是最举荐的一种形式,靠近完满的继承。

实现Array.of办法

Array.of()办法用于将一组值,转换为数组
  • 这个办法的次要目标,是补救数组构造函数Array()的有余。因为参数个数的不同,会导致Array()的行为有差别。
  • Array.of()基本上能够用来代替Array()new Array(),并且不存在因为参数不同而导致的重载。它的行为十分对立
Array.of(3, 11, 8) // [3,11,8]Array.of(3) // [3]Array.of(3).length // 1

实现

function ArrayOf(){  return [].slice.call(arguments);}

实现深拷贝

简洁版本

简略版:

const newObj = JSON.parse(JSON.stringify(oldObj));

局限性:

  • 他无奈实现对函数 、RegExp等非凡对象的克隆
  • 会摈弃对象的constructor,所有的构造函数会指向Object
  • 对象有循环援用,会报错

面试简版

function deepClone(obj) {    // 如果是 值类型 或 null,则间接return    if(typeof obj !== 'object' || obj === null) {      return obj    }    // 定义后果对象    let copy = {}    // 如果对象是数组,则定义后果数组    if(obj.constructor === Array) {      copy = []    }    // 遍历对象的key    for(let key in obj) {        // 如果key是对象的自有属性        if(obj.hasOwnProperty(key)) {          // 递归调用深拷贝办法          copy[key] = deepClone(obj[key])        }    }    return copy} 
调用深拷贝办法,若属性为值类型,则间接返回;若属性为援用类型,则递归遍历。这就是咱们在解这一类题时的外围的办法。

进阶版

  • 解决拷贝循环援用问题
  • 解决拷贝对应原型问题
// 递归拷贝 (类型判断)function deepClone(value,hash = new WeakMap){ // 弱援用,不必map,weakMap更适合一点  // null 和 undefiend 是不须要拷贝的  if(value == null){ return value;}  if(value instanceof RegExp) { return new RegExp(value) }  if(value instanceof Date) { return new Date(value) }  // 函数是不须要拷贝  if(typeof value != 'object') return value;  let obj = new value.constructor(); // [] {}  // 阐明是一个对象类型  if(hash.get(value)){    return hash.get(value)  }  hash.set(value,obj);  for(let key in value){ // in 会遍历以后对象上的属性 和 __proto__指代的属性    // 补拷贝 对象的__proto__上的属性    if(value.hasOwnProperty(key)){      // 如果值还有可能是对象 就持续拷贝      obj[key] = deepClone(value[key],hash);    }  }  return obj  // 辨别对象和数组 Object.prototype.toString.call}
// testvar o = {};o.x = o;var o1 = deepClone(o); // 如果这个对象拷贝过了 就返回那个拷贝的后果就能够了console.log(o1);

实现残缺的深拷贝

1. 简易版及问题

JSON.parse(JSON.stringify());
预计这个api能笼罩大多数的利用场景,没错,谈到深拷贝,我第一个想到的也是它。然而实际上,对于某些严格的场景来说,这个办法是有微小的坑的。问题如下:
  1. 无奈解决循环援用的问题。举个例子:
const a = {val:2};a.target = a;
拷贝a会呈现零碎栈溢出,因为呈现了有限递归的状况。
  1. 无奈拷贝一些非凡的对象,诸如 RegExp, Date, Set, Map
  2. 无奈拷贝函数(划重点)。

因而这个api先pass掉,咱们从新写一个深拷贝,简易版如下:

const deepClone = (target) => {  if (typeof target === 'object' && target !== null) {    const cloneTarget = Array.isArray(target) ? []: {};    for (let prop in target) {      if (target.hasOwnProperty(prop)) {          cloneTarget[prop] = deepClone(target[prop]);      }    }    return cloneTarget;  } else {    return target;  }}

当初,咱们以刚刚发现的三个问题为导向,一步步来欠缺、优化咱们的深拷贝代码。

2. 解决循环援用

当初问题如下:

let obj = {val : 100};obj.target = obj;deepClone(obj);//报错: RangeError: Maximum call stack size exceeded

这就是循环援用。咱们怎么来解决这个问题呢?

创立一个Map。记录下曾经拷贝过的对象,如果说曾经拷贝过,那间接返回它行了。
const isObject = (target) => (typeof target === 'object' || typeof target === 'function') && target !== null;const deepClone = (target, map = new Map()) => {   if(map.get(target))      return target;   if (isObject(target)) {     map.set(target, true);     const cloneTarget = Array.isArray(target) ? []: {};     for (let prop in target) {       if (target.hasOwnProperty(prop)) {           cloneTarget[prop] = deepClone(target[prop],map);       }     }     return cloneTarget;   } else {     return target;   } }

当初来试一试:

const a = {val:2};a.target = a;let newA = deepClone(a);console.log(newA)//{ val: 2, target: { val: 2, target: [Circular] } }
如同是没有问题了, 拷贝也实现了。但还是有一个潜在的坑, 就是map 上的 key 和 map 形成了强援用关系,这是相当危险的。我给你解释一下与之绝对的弱援用的概念你就明确了

在计算机程序设计中,弱援用与强援用绝对,

被弱援用的对象能够在任何时候被回收,而对于强援用来说,只有这个强援用还在,那么对象无奈被回收。拿下面的例子说,map 和 a始终是强援用的关系, 在程序完结之前,a 所占的内存空间始终不会被开释。

怎么解决这个问题?

很简略,让 map 的 key 和 map 形成弱援用即可。ES6给咱们提供了这样的数据结构,它的名字叫WeakMap,它是一种非凡的Map, 其中的键是弱援用的。其键必须是对象,而值能够是任意的

略微革新一下即可:

const deepClone = (target, map = new WeakMap()) => {  //...}

3. 拷贝非凡对象

可持续遍历

对于非凡的对象,咱们应用以下形式来甄别:

Object.prototype.toString.call(obj);

梳理一下对于可遍历对象会有什么后果:

["object Map"]["object Set"]["object Array"]["object Object"]["object Arguments"]

以这些不同的字符串为根据,咱们就能够胜利地甄别这些对象。

const getType = Object.prototype.toString.call(obj);const canTraverse = {  '[object Map]': true,  '[object Set]': true,  '[object Array]': true,  '[object Object]': true,  '[object Arguments]': true,};const deepClone = (target, map = new Map()) => {  if(!isObject(target))     return target;  let type = getType(target);  let cloneTarget;  if(!canTraverse[type]) {    // 解决不能遍历的对象    return;  }else {    // 这波操作相当要害,能够保障对象的原型不失落!    let ctor = target.prototype;    cloneTarget = new ctor();  }  if(map.get(target))     return target;  map.put(target, true);  if(type === mapTag) {    //解决Map    target.forEach((item, key) => {      cloneTarget.set(deepClone(key), deepClone(item));    })  }  if(type === setTag) {    //解决Set    target.forEach(item => {      target.add(deepClone(item));    })  }  // 解决数组和对象  for (let prop in target) {    if (target.hasOwnProperty(prop)) {        cloneTarget[prop] = deepClone(target[prop]);    }  }  return cloneTarget;}

不可遍历的对象

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

对于不可遍历的对象,不同的对象有不同的解决。

const handleRegExp = (target) => {  const { source, flags } = target;  return new target.constructor(source, flags);}const handleFunc = (target) => {  // 待会的重点局部}const handleNotTraverse = (target, tag) => {  const Ctor = targe.constructor;  switch(tag) {    case boolTag:    case numberTag:    case stringTag:    case errorTag:     case dateTag:      return new Ctor(target);    case regexpTag:      return handleRegExp(target);    case funcTag:      return handleFunc(target);    default:      return new Ctor(target);  }}

4. 拷贝函数

  • 尽管函数也是对象,然而它过于非凡,咱们独自把它拿进去拆解。
  • 提到函数,在JS种有两种函数,一种是一般函数,另一种是箭头函数。每个一般函数都是
  • Function的实例,而箭头函数不是任何类的实例,每次调用都是不一样的援用。那咱们只须要
  • 解决一般函数的状况,箭头函数间接返回它自身就好了。

那么如何来辨别两者呢?

答案是: 利用原型。箭头函数是不存在原型的。
const handleFunc = (func) => {  // 箭头函数间接返回本身  if(!func.prototype) return func;  const bodyReg = /(?<={)(.|\n)+(?=})/m;  const paramReg = /(?<=\().+(?=\)\s+{)/;  const funcString = func.toString();  // 别离匹配 函数参数 和 函数体  const param = paramReg.exec(funcString);  const body = bodyReg.exec(funcString);  if(!body) return null;  if (param) {    const paramArr = param[0].split(',');    return new Function(...paramArr, body[0]);  } else {    return new Function(body[0]);  }}

5. 残缺代码展现

const getType = obj => Object.prototype.toString.call(obj);const isObject = (target) => (typeof target === 'object' || typeof target === 'function') && target !== null;const canTraverse = {  '[object Map]': true,  '[object Set]': true,  '[object Array]': true,  '[object Object]': true,  '[object Arguments]': true,};const mapTag = '[object Map]';const setTag = '[object Set]';const boolTag = '[object Boolean]';const numberTag = '[object Number]';const stringTag = '[object String]';const symbolTag = '[object Symbol]';const dateTag = '[object Date]';const errorTag = '[object Error]';const regexpTag = '[object RegExp]';const funcTag = '[object Function]';const handleRegExp = (target) => {  const { source, flags } = target;  return new target.constructor(source, flags);}const handleFunc = (func) => {  // 箭头函数间接返回本身  if(!func.prototype) return func;  const bodyReg = /(?<={)(.|\n)+(?=})/m;  const paramReg = /(?<=\().+(?=\)\s+{)/;  const funcString = func.toString();  // 别离匹配 函数参数 和 函数体  const param = paramReg.exec(funcString);  const body = bodyReg.exec(funcString);  if(!body) return null;  if (param) {    const paramArr = param[0].split(',');    return new Function(...paramArr, body[0]);  } else {    return new Function(body[0]);  }}const handleNotTraverse = (target, tag) => {  const Ctor = target.constructor;  switch(tag) {    case boolTag:      return new Object(Boolean.prototype.valueOf.call(target));    case numberTag:      return new Object(Number.prototype.valueOf.call(target));    case stringTag:      return new Object(String.prototype.valueOf.call(target));    case symbolTag:      return new Object(Symbol.prototype.valueOf.call(target));    case errorTag:     case dateTag:      return new Ctor(target);    case regexpTag:      return handleRegExp(target);    case funcTag:      return handleFunc(target);    default:      return new Ctor(target);  }}const deepClone = (target, map = new WeakMap()) => {  if(!isObject(target))     return target;  let type = getType(target);  let cloneTarget;  if(!canTraverse[type]) {    // 解决不能遍历的对象    return handleNotTraverse(target, type);  }else {    // 这波操作相当要害,能够保障对象的原型不失落!    let ctor = target.constructor;    cloneTarget = new ctor();  }  if(map.get(target))     return target;  map.set(target, true);  if(type === mapTag) {    //解决Map    target.forEach((item, key) => {      cloneTarget.set(deepClone(key, map), deepClone(item, map));    })  }  if(type === setTag) {    //解决Set    target.forEach(item => {      cloneTarget.add(deepClone(item, map));    })  }  // 解决数组和对象  for (let prop in target) {    if (target.hasOwnProperty(prop)) {        cloneTarget[prop] = deepClone(target[prop], map);    }  }  return cloneTarget;}

实现prototype继承

所谓的原型链继承就是让新实例的原型等于父类的实例:

//父办法function SupperFunction(flag1){    this.flag1 = flag1;}//子办法function SubFunction(flag2){    this.flag2 = flag2;}//父实例var superInstance = new SupperFunction(true);//子继承父SubFunction.prototype = superInstance;//子实例var subInstance = new SubFunction(false);//子调用本人和父的属性subInstance.flag1;   // truesubInstance.flag2;   // false

实现AJAX申请

AJAX是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新以后网页的对应局部,而不必刷新整个网页。

创立AJAX申请的步骤:

  • 创立一个 XMLHttpRequest 对象。
  • 在这个对象上应用 open 办法创立一个 HTTP 申请,open 办法所须要的参数是申请的办法、申请的地址、是否异步和用户的认证信息。
  • 在发动申请前,能够为这个对象增加一些信息和监听函数。比如说能够通过 setRequestHeader 办法来为申请增加头信息。还能够为这个对象增加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变动时会触发onreadystatechange 事件,能够通过设置监听函数,来解决申请胜利后的后果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接管实现,这个时候能够通过判断申请的状态,如果状态是 2xx 或者 304 的话则代表返回失常。这个时候就能够通过 response 中的数据来对页面进行更新了。
  • 当对象的属性和监听函数设置实现后,最初调用 sent 办法来向服务器发动申请,能够传入参数作为发送的数据体。
const SERVER_URL = "/server";let xhr = new XMLHttpRequest();// 创立 Http 申请xhr.open("GET", SERVER_URL, true);// 设置状态监听函数xhr.onreadystatechange = function() {  if (this.readyState !== 4) return;  // 当申请胜利时  if (this.status === 200) {    handle(this.response);  } else {    console.error(this.statusText);  }};// 设置申请失败时的监听函数xhr.onerror = function() {  console.error(this.statusText);};// 设置申请头信息xhr.responseType = "json";xhr.setRequestHeader("Accept", "application/json");// 发送 Http 申请xhr.send(null);

实现async/await

剖析

// generator生成器  生成迭代器iterator// 默认这样写的类数组是不能被迭代的,短少迭代办法let likeArray = {'0': 1, '1': 2, '2': 3, '3': 4, length: 4}// // 应用迭代器使得能够开展数组// // Symbol有很多元编程办法,能够改js自身性能// likeArray[Symbol.iterator] = function () {//   // 迭代器是一个对象 对象中有next办法 每次调用next 都须要返回一个对象 {value,done}//   let index = 0//   return {//     next: ()=>{//       // 会主动调用这个办法//       console.log('index',index)//       return {//         // this 指向likeArray//         value: this[index],//         done: index++ === this.length//       }//     }//   }// }// let arr = [...likeArray]// console.log('arr', arr)// 应用生成器返回迭代器// likeArray[Symbol.iterator] = function *() {//   let index = 0//   while (index != this.length) {//     yield this[index++]//   }// }// let arr = [...likeArray]// console.log('arr', arr)// 生成器 碰到yield就会暂停// function *read(params) {//   yield 1;//   yield 2;// }// 生成器返回的是迭代器// let it = read()// console.log(it.next())// console.log(it.next())// console.log(it.next())// 通过generator来优化promise(promise的毛病是不停的链式调用)const fs = require('fs')const path = require('path')// const co = require('co') // 帮咱们执行generatorconst promisify = fn=>{  return (...args)=>{    return new Promise((resolve,reject)=>{      fn(...args, (err,data)=>{        if(err) {          reject(err)        }         resolve(data)      })    })  }}// promise化let asyncReadFile = promisify(fs.readFile)function * read() {  let content1 = yield asyncReadFile(path.join(__dirname,'./data/name.txt'),'utf8')  let content2 = yield asyncReadFile(path.join(__dirname,'./data/' + content1),'utf8')  return content2}// 这样写太繁琐 须要借助co来实现// let re = read()// let {value,done} = re.next()// value.then(data=>{//   // 除了第一次传参没有意义外 剩下的传参都赋予了上一次的返回值 //   let {value,done} = re.next(data) //   value.then(d=>{//     let {value,done} = re.next(d)//     console.log(value,done)//   })// }).catch(err=>{//   re.throw(err) // 手动抛出谬误 能够被try catch捕捉// })// 实现co原理function co(it) {// it 迭代器  return new Promise((resolve,reject)=>{    // 异步迭代 须要依据函数来实现    function next(data) {      // 递归得有停止条件      let {value,done} = it.next(data)      if(done) {        resolve(value) // 间接让promise变成胜利 用以后返回的后果      } else {        // Promise.resolve(value).then(data=>{        //   next(data)        // }).catch(err=>{        //   reject(err)        // })        // 简写        Promise.resolve(value).then(next,reject)      }    }    // 首次调用    next()  })}co(read()).then(d=>{  console.log(d)}).catch(err=>{  console.log(err,'--')})

整体看一下构造

function asyncToGenerator(generatorFunc) {    return function() {      const gen = generatorFunc.apply(this, arguments)      return new Promise((resolve, reject) => {        function step(key, arg) {          let generatorResult          try {            generatorResult = gen[key](arg)          } catch (error) {            return reject(error)          }          const { value, done } = generatorResult          if (done) {            return resolve(value)          } else {            return Promise.resolve(value).then(val => step('next', val), err => step('throw', err))          }        }        step("next")      })    }}

剖析

function asyncToGenerator(generatorFunc) {  // 返回的是一个新的函数  return function() {    // 先调用generator函数 生成迭代器    // 对应 var gen = testG()    const gen = generatorFunc.apply(this, arguments)    // 返回一个promise 因为内部是用.then的形式 或者await的形式去应用这个函数的返回值的    // var test = asyncToGenerator(testG)    // test().then(res => console.log(res))    return new Promise((resolve, reject) => {      // 外部定义一个step函数 用来一步一步的跨过yield的妨碍      // key有next和throw两种取值,别离对应了gen的next和throw办法      // arg参数则是用来把promise resolve进去的值交给下一个yield      function step(key, arg) {        let generatorResult        // 这个办法须要包裹在try catch中        // 如果报错了 就把promise给reject掉 内部通过.catch能够获取到谬误        try {          generatorResult = gen[key](arg)        } catch (error) {          return reject(error)        }        // gen.next() 失去的后果是一个 { value, done } 的构造        const { value, done } = generatorResult        if (done) {          // 如果曾经实现了 就间接resolve这个promise          // 这个done是在最初一次调用next后才会为true          // 以本文的例子来说 此时的后果是 { done: true, value: 'success' }          // 这个value也就是generator函数最初的返回值          return resolve(value)        } else {          // 除了最初完结的时候外,每次调用gen.next()          // 其实是返回 { value: Promise, done: false } 的构造,          // 这里要留神的是Promise.resolve能够承受一个promise为参数          // 并且这个promise参数被resolve的时候,这个then才会被调用          return Promise.resolve(            // 这个value对应的是yield前面的promise            value          ).then(            // value这个promise被resove的时候,就会执行next            // 并且只有done不是true的时候 就会递归的往下解开promise            // 对应gen.next().value.then(value => {            //    gen.next(value).value.then(value2 => {            //       gen.next()             //            //      // 此时done为true了 整个promise被resolve了             //      // 最内部的test().then(res => console.log(res))的then就开始执行了            //    })            // })            function onResolve(val) {              step("next", val)            },            // 如果promise被reject了 就再次进入step函数            // 不同的是,这次的try catch中调用的是gen.throw(err)            // 那么天然就被catch到 而后把promise给reject掉啦            function onReject(err) {              step("throw", err)            },          )        }      }      step("next")    })  }}