乐趣区

关于javascript:社招前端经典手写面试题合集

实现千位分隔符

// 保留三位小数
parseToMoney(1234.56); // return '1,234.56'
parseToMoney(123456789); // return '123,456,789'
parseToMoney(1087654.321); // return '1,087,654.321'

function parseToMoney(num) {num = parseFloat(num.toFixed(3));
  let [integer, decimal] = String.prototype.split.call(num, '.');
  integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,');
  return integer + '.' + (decimal ? decimal : '');
}

正则表达式(使用了正则的前向申明和反前向申明):

function parseToMoney(str){
    // 仅仅对地位进行匹配
    let re = /(?=(?!\b)(\d{3})+$)/g; 
   return str.replace(re,','); 
}

实现 filter 办法

Array.prototype.myFilter=function(callback, context=window){

  let len = this.length
      newArr = [],
      i=0

  for(; i < len; i++){if(callback.apply(context, [this[i], i , this])){newArr.push(this[i]);
    }
  }
  return newArr;
}

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

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

实现 bind 办法

bind 的实现比照其余两个函数稍微地简单了一点,波及到参数合并(相似函数柯里化),因为 bind 须要返回一个函数,须要判断一些边界问题,以下是 bind 的实现

  • bind 返回了一个函数,对于函数来说有两种形式调用,一种是间接调用,一种是通过 new 的形式,咱们先来说间接调用的形式
  • 对于间接调用来说,这里抉择了 apply 的形式实现,然而对于参数须要留神以下状况:因为 bind 能够实现相似这样的代码 f.bind(obj, 1)(2),所以咱们须要将两边的参数拼接起来
  • 最初来说通过 new 的形式,对于 new 的状况来说,不会被任何形式扭转 this,所以对于这种状况咱们须要疏忽传入的 this

简洁版本

  • 对于一般函数,绑定 this 指向
  • 对于构造函数,要保障原函数的原型对象上的属性不能失落
Function.prototype.myBind = function(context = window, ...args) {
  // this 示意调用 bind 的函数
  let self = this;

  // 返回了一个函数,...innerArgs 为理论调用时传入的参数
  let fBound = function(...innerArgs) {//this instanceof fBound 为 true 示意构造函数的状况。如 new func.bind(obj)
      // 当作为构造函数时,this 指向实例,此时 this instanceof fBound 后果为 true,能够让实例取得来自绑定函数的值
      // 当作为一般函数时,this 指向 window,此时后果为 false,将绑定函数的 this 指向 context
      return self.apply(
        this instanceof fBound ? this : context, 
        args.concat(innerArgs)
      );
  }

  // 如果绑定的是构造函数,那么须要继承构造函数原型属性和办法:保障原函数的原型对象上的属性不失落
  // 实现继承的形式: 应用 Object.create
  fBound.prototype = Object.create(this.prototype);
  return fBound;
}
// 测试用例

function Person(name, age) {console.log('Person name:', name);
  console.log('Person age:', age);
  console.log('Person this:', this); // 构造函数 this 指向实例对象
}

// 构造函数原型的办法
Person.prototype.say = function() {console.log('person say');
}

// 一般函数
function normalFun(name, age) {console.log('一般函数 name:', name); 
  console.log('一般函数 age:', age); 
  console.log('一般函数 this:', this);  // 一般函数 this 指向绑定 bind 的第一个参数 也就是例子中的 obj
}


var obj = {
  name: 'poetries',
  age: 18
}

// 先测试作为结构函数调用
var bindFun = Person.myBind(obj, 'poetry1') // undefined
var a = new bindFun(10) // Person name: poetry1、Person age: 10、Person this: fBound {}
a.say() // person say

// 再测试作为一般函数调用
var bindNormalFun = normalFun.myBind(obj, 'poetry2') // undefined
bindNormalFun(12) // 一般函数 name: poetry2 一般函数 age: 12 一般函数 this: {name: 'poetries', age: 18}

留神:bind之后不能再次批改 this 的指向,bind屡次后执行,函数 this 还是指向第一次 bind 的对象

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

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

实现事件总线联合 Vue 利用

Event Bus(Vue、Flutter 等前端框架中有出镜)和 Event Emitter(Node 中有出镜)出场的“剧组”不同,然而它们都对应一个独特的角色—— 全局事件总线

全局事件总线,严格来说不能说是观察者模式,而是公布 - 订阅模式。它在咱们日常的业务开发中利用十分广。

如果只能选一道题,那这道题肯定是 Event Bus/Event Emitter 的代码实现——我都说这么分明了,这个知识点到底要不要把握、须要把握到什么水平,就看各位本人的了。

在 Vue 中应用 Event Bus 来实现组件间的通信

Event Bus/Event Emitter 作为全局事件总线,它起到的是一个 沟通桥梁 的作用。咱们能够把它了解为一个事件核心,咱们所有事件的订阅 / 公布都不能由订阅方和公布方“私下沟通”,必须要委托这个事件核心帮咱们实现。

在 Vue 中,有时候 A 组件和 B 组件中距离了很远,看似没什么关系,但咱们心愿它们之间可能通信。这种状况下除了求助于 Vuex 之外,咱们还能够通过 Event Bus 来实现咱们的需要。

创立一个 Event Bus(实质上也是 Vue 实例)并导出:

const EventBus = new Vue()
export default EventBus

在主文件里引入EventBus,并挂载到全局:

import bus from 'EventBus 的文件门路'
Vue.prototype.bus = bus

订阅事件:

// 这里 func 指 someEvent 这个事件的监听函数
this.bus.$on('someEvent', func)

公布(触发)事件:

// 这里 params 指 someEvent 这个事件被触发时回调函数接管的入参
this.bus.$emit('someEvent', params)

大家会发现,整个调用过程中,没有呈现具体的发布者和订阅者(比方下面的 PrdPublisherDeveloperObserver),全程只有 bus 这个货色一个人在疯狂刷存在感。这就是全局事件总线的特点——所有事件的公布 / 订阅操作,必须经由事件核心,禁止所有“私下交易”!

上面,咱们就一起来实现一个Event Bus(留神看正文里的解析):

class EventEmitter {constructor() {
    // handlers 是一个 map,用于存储事件与回调之间的对应关系
    this.handlers = {}}

  // on 办法用于装置事件监听器,它承受指标事件名和回调函数作为参数
  on(eventName, cb) {
    // 先检查一下指标事件名有没有对应的监听函数队列
    if (!this.handlers[eventName]) {
      // 如果没有,那么首先初始化一个监听函数队列
      this.handlers[eventName] = []}

    // 把回调函数推入指标事件的监听函数队列里去
    this.handlers[eventName].push(cb)
  }

  // emit 办法用于触发指标事件,它承受事件名和监听函数入参作为参数
  emit(eventName, ...args) {
    // 查看指标事件是否有监听函数队列
    if (this.handlers[eventName]) {
      // 如果有,则一一调用队列里的回调函数
      this.handlers[eventName].forEach((callback) => {callback(...args)
      })
    }
  }

  // 移除某个事件回调队列里的指定回调函数
  off(eventName, cb) {const callbacks = this.handlers[eventName]
    const index = callbacks.indexOf(cb)
    if (index !== -1) {callbacks.splice(index, 1)
    }
  }

  // 为事件注册单次监听器
  once(eventName, cb) {
    // 对回调函数进行包装,使其执行结束主动被移除
    const wrapper = (...args) => {cb.apply(...args)
      this.off(eventName, wrapper)
    }
    this.on(eventName, wrapper)
  }
}

在日常的开发中,大家用到 EventBus/EventEmitter 往往提供比这五个办法多的多的多的办法。但在面试过程中,如果大家可能残缺地实现出这五个办法,曾经十分能够阐明问题了,因而楼上这个 EventBus 心愿大家能够熟练掌握。学有余力的同学

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

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

实现一个 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))

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

实现 Ajax

步骤

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

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

实现一个双向绑定

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;});

实现 redux-thunk

redux-thunk 能够利用 redux 中间件让 redux 反对异步的 action

// 如果 action 是个函数,就调用这个函数
// 如果 action 不是函数,就传给下一个中间件
// 发现 action 是函数就调用
const thunk = ({dispatch, getState}) => (next) => (action) => {if (typeof action === 'function') {return action(dispatch, getState);
  }

  return next(action);
};
export default thunk

实现迭代器生成函数

咱们说 迭代器对象 全凭 迭代器生成函数 帮咱们生成。在 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()

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

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

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

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

实现 apply 办法

思路: 利用 this 的上下文个性。apply其实就是改一下参数的问题

Function.prototype.myApply = function(context = window, args) {
  // 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); // 这里和 call 传参不一样

  // 革除定义的 this 不删除会导致 context 属性越来越多
  delete context[key]; 

  // 返回后果
  return result;
}
// 应用
function f(a,b){console.log(a,b)
 console.log(this.name)
}
let obj={name:'张三'}
f.myApply(obj,[1,2])  //arguments[1]

实现一个迭代器生成函数

ES6 对迭代器的实现

JS 原生的汇合类型数据结构,只有 Array(数组)和Object(对象);而ES6 中,又新增了 MapSet。四种数据结构各自有着本人特地的外部实现,但咱们仍期待以同样的一套规定去遍历它们,所以 ES6 在推出新数据结构的同时也推出了一套 对立的接口机制 ——迭代器(Iterator)。

ES6约定,任何数据结构只有具备 Symbol.iterator 属性(这个属性就是 Iterator 的具体实现,它实质上是以后数据结构默认的迭代器生成函数),就能够被遍历——精确地说,是被 for...of... 循环和迭代器的 next 办法遍历。事实上,for...of...的背地正是对 next 办法的重复调用。

在 ES6 中,针对 ArrayMapSetStringTypedArray、函数的 arguments 对象、NodeList 对象这些原生的数据结构都能够通过for...of... 进行遍历。原理都是一样的,此处咱们拿最简略的数组进行举例,当咱们用 for...of... 遍历数组时:

const arr = [1, 2, 3]
const len = arr.length
for(item of arr) {console.log(` 以后元素是 ${item}`)
}

之所以可能按程序一次一次地拿到数组里的每一个成员,是因为咱们借助数组的 Symbol.iterator 生成了它对应的迭代器对象,通过重复调用迭代器对象的 next 办法拜访了数组成员,像这样:

const arr = [1, 2, 3]
// 通过调用 iterator,拿到迭代器对象
const iterator = arr[Symbol.iterator]()

// 对迭代器对象执行 next,就能一一拜访汇合的成员
iterator.next()
iterator.next()
iterator.next()

丢进控制台,咱们能够看到 next 每次会按程序帮咱们拜访一个汇合成员:

for...of... 做的事件,根本等价于上面这通操作:

// 通过调用 iterator,拿到迭代器对象
const iterator = arr[Symbol.iterator]()

// 初始化一个迭代后果
let now = {done: false}

// 循环往外迭代成员
while(!now.done) {now = iterator.next()
    if(!now.done) {console.log(` 当初遍历到了 ${now.value}`)
    }
}

能够看出,for...of...其实就是 iterator 循环调用换了种写法。在 ES6 中咱们之所以可能开心地用 for...of... 遍历各种各种的汇合,全靠迭代器模式在背地给力。

ps:此处举荐浏览迭代协定 (opens new window),置信大家读过后会对迭代器在 ES6 中的实现有更深的了解。

数组去重办法汇总

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

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

退出移动版