Promise

// 模仿实现Promise// Promise利用三大伎俩解决回调天堂:// 1. 回调函数提早绑定// 2. 返回值穿透// 3. 谬误冒泡// 定义三种状态const PENDING = 'PENDING';      // 进行中const FULFILLED = 'FULFILLED';  // 已胜利const REJECTED = 'REJECTED';    // 已失败class Promise {  constructor(exector) {    // 初始化状态    this.status = PENDING;    // 将胜利、失败后果放在this上,便于then、catch拜访    this.value = undefined;    this.reason = undefined;    // 胜利态回调函数队列    this.onFulfilledCallbacks = [];    // 失败态回调函数队列    this.onRejectedCallbacks = [];    const resolve = value => {      // 只有进行中状态能力更改状态      if (this.status === PENDING) {        this.status = FULFILLED;        this.value = value;        // 胜利态函数顺次执行        this.onFulfilledCallbacks.forEach(fn => fn(this.value));      }    }    const reject = reason => {      // 只有进行中状态能力更改状态      if (this.status === PENDING) {        this.status = REJECTED;        this.reason = reason;        // 失败态函数顺次执行        this.onRejectedCallbacks.forEach(fn => fn(this.reason))      }    }    try {      // 立刻执行executor      // 把外部的resolve和reject传入executor,用户可调用resolve和reject      exector(resolve, reject);    } catch(e) {      // executor执行出错,将谬误内容reject抛出去      reject(e);    }  }  then(onFulfilled, onRejected) {    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;    onRejected = typeof onRejected === 'function'? onRejected :      reason => { throw new Error(reason instanceof Error ? reason.message : reason) }    // 保留this    const self = this;    return new Promise((resolve, reject) => {      if (self.status === PENDING) {        self.onFulfilledCallbacks.push(() => {          // try捕捉谬误          try {            // 模仿微工作            setTimeout(() => {              const result = onFulfilled(self.value);              // 分两种状况:              // 1. 回调函数返回值是Promise,执行then操作              // 2. 如果不是Promise,调用新Promise的resolve函数              result instanceof Promise ? result.then(resolve, reject) : resolve(result);            })          } catch(e) {            reject(e);          }        });        self.onRejectedCallbacks.push(() => {          // 以下同理          try {            setTimeout(() => {              const result = onRejected(self.reason);              // 不同点:此时是reject              result instanceof Promise ? result.then(resolve, reject) : resolve(result);            })          } catch(e) {            reject(e);          }        })      } else if (self.status === FULFILLED) {        try {          setTimeout(() => {            const result = onFulfilled(self.value);            result instanceof Promise ? result.then(resolve, reject) : resolve(result);          });        } catch(e) {          reject(e);        }      } else if (self.status === REJECTED) {        try {          setTimeout(() => {            const result = onRejected(self.reason);            result instanceof Promise ? result.then(resolve, reject) : resolve(result);          })        } catch(e) {          reject(e);        }      }    });  }  catch(onRejected) {    return this.then(null, onRejected);  }  static resolve(value) {    if (value instanceof Promise) {      // 如果是Promise实例,间接返回      return value;    } else {      // 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED      return new Promise((resolve, reject) => resolve(value));    }  }  static reject(reason) {    return new Promise((resolve, reject) => {      reject(reason);    })  }  static all(promiseArr) {    const len = promiseArr.length;    const values = new Array(len);    // 记录曾经胜利执行的promise个数    let count = 0;    return new Promise((resolve, reject) => {      for (let i = 0; i < len; i++) {        // Promise.resolve()解决,确保每一个都是promise实例        Promise.resolve(promiseArr[i]).then(          val => {            values[i] = val;            count++;            // 如果全副执行完,返回promise的状态就能够扭转了            if (count === len) resolve(values);          },          err => reject(err),        );      }    })  }  static race(promiseArr) {    return new Promise((resolve, reject) => {      promiseArr.forEach(p => {        Promise.resolve(p).then(          val => resolve(val),          err => reject(err),        )      })    })  }}

请实现一个 add 函数,满足以下性能

add(1);             // 1add(1)(2);      // 3add(1)(2)(3);// 6add(1)(2, 3); // 6add(1, 2)(3); // 6add(1, 2, 3); // 6
function add(...args) {  // 在外部申明一个函数,利用闭包的个性保留并收集所有的参数值  let fn = function(...newArgs) {   return add.apply(null, args.concat(newArgs))  }  // 利用toString隐式转换的个性,当最初执行时隐式转换,并计算最终的值返回  fn.toString = function() {    return args.reduce((total,curr)=> total + curr)  }  return fn}

考点:

  • 应用闭包, 同时要对JavaScript 的作用域链(原型链)有深刻的了解
  • 重写函数的 toSting()办法
// 测试,调用toString办法触发求值add(1).toString();             // 1add(1)(2).toString();      // 3add(1)(2)(3).toString();// 6add(1)(2, 3).toString(); // 6add(1, 2)(3).toString(); // 6add(1, 2, 3).toString(); // 6

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

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

基于Generator函数实现async/await原理

外围:传递给我一个Generator函数,把函数中的内容基于Iterator迭代器的特点一步步的执行
function readFile(file) {    return new Promise(resolve => {        setTimeout(() => {            resolve(file);    }, 1000);    })};function asyncFunc(generator) {    const iterator = generator(); // 接下来要执行next  // data为第一次执行之后的返回后果,用于传给第二次执行  const next = (data) => {        let { value, done } = iterator.next(data); // 第二次执行,并接管第一次的申请后果 data    if (done) return; // 执行结束(到第三次)间接返回    // 第一次执行next时,yield返回的 promise实例 赋值给了 value    value.then(data => {      next(data); // 当第一次value 执行结束且胜利时,执行下一步(并把第一次的后果传递下一步)    });  }  next();};asyncFunc(function* () {    // 生成器函数:控制代码一步步执行   let data = yield readFile('a.js'); // 等这一步骤执行执行胜利之后,再往下走,没执行完的时候,间接返回  data = yield readFile(data + 'b.js');  return data;})

实现Vue reactive响应式

// Dep moduleclass Dep {  static stack = []  static target = null  deps = null  constructor() {    this.deps = new Set()  }  depend() {    if (Dep.target) {      this.deps.add(Dep.target)    }  }  notify() {    this.deps.forEach(w => w.update())  }  static pushTarget(t) {    if (this.target) {      this.stack.push(this.target)    }    this.target = t  }  static popTarget() {    this.target = this.stack.pop()  }}// reactivefunction reactive(o) {  if (o && typeof o === 'object') {    Object.keys(o).forEach(k => {      defineReactive(o, k, o[k])    })  }  return o}function defineReactive(obj, k, val) {  let dep = new Dep()  Object.defineProperty(obj, k, {    get() {      dep.depend()      return val    },    set(newVal) {      val = newVal      dep.notify()    }  })  if (val && typeof val === 'object') {    reactive(val)  }}// watcherclass Watcher {  constructor(effect) {    this.effect = effect    this.update()  }  update() {    Dep.pushTarget(this)    this.value = this.effect()    Dep.popTarget()    return this.value  }}// 测试代码const data = reactive({  msg: 'aaa'})new Watcher(() => {  console.log('===> effect', data.msg);})setTimeout(() => {  data.msg = 'hello'}, 1000)

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

实现观察者模式

观察者模式(基于公布订阅模式) 有观察者,也有被观察者

观察者须要放到被观察者中,被观察者的状态变动须要告诉观察者 我变动了 外部也是基于公布订阅模式,收集观察者,状态变动后要被动告诉观察者

class Subject { // 被观察者 学生  constructor(name) {    this.state = 'happy'    this.observers = []; // 存储所有的观察者  }  // 收集所有的观察者  attach(o){ // Subject. prototype. attch    this.observers.push(o)  }  // 更新被观察者 状态的办法  setState(newState) {    this.state = newState; // 更新状态    // this 指被观察者 学生    this.observers.forEach(o => o.update(this)) // 告诉观察者 更新它们的状态  }}class Observer{ // 观察者 父母和老师  constructor(name) {    this.name = name  }  update(student) {    console.log('以后' + this.name + '被告诉了', '以后学生的状态是' + student.state)  }}let student = new Subject('学生'); let parent = new Observer('父母'); let teacher = new Observer('老师'); // 被观察者存储观察者的前提,须要先接收观察者student. attach(parent); student. attach(teacher); student. setState('被欺侮了');

实现一个双向绑定

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

手写防抖函数

函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则从新计时。这能够应用在一些点击申请的事件上,防止因为用户的屡次点击向后端发送屡次申请。

// 函数防抖的实现function debounce(fn, wait) {  let timer = null;  return function() {    let context = this,        args = arguments;    // 如果此时存在定时器的话,则勾销之前的定时器从新记时    if (timer) {      clearTimeout(timer);      timer = null;    }    // 设置定时器,使事件间隔指定事件后执行    timer = setTimeout(() => {      fn.apply(context, args);    }, wait);  };}

实现深拷贝

  • 浅拷贝: 浅拷贝指的是将一个对象的属性值复制到另一个对象,如果有的属性的值为援用类型的话,那么会将这个援用的地址复制给对象,因而两个对象会有同一个援用类型的援用。浅拷贝能够应用 Object.assign 和开展运算符来实现。
  • 深拷贝: 深拷贝绝对浅拷贝而言,如果遇到属性值为援用类型的时候,它新建一个援用类型并将对应的值复制给它,因而对象取得的一个新的援用类型而不是一个原有类型的援用。深拷贝对于一些对象能够应用 JSON 的两个函数来实现,然而因为 JSON 的对象格局比 js 的对象格局更加严格,所以如果属性值里边呈现函数或者 Symbol 类型的值时,会转换失败

(1)JSON.stringify()

  • JSON.parse(JSON.stringify(obj))是目前比拟罕用的深拷贝办法之一,它的原理就是利用JSON.stringifyjs对象序列化(JSON字符串),再应用JSON.parse来反序列化(还原)js对象。
  • 这个办法能够简略粗犷的实现深拷贝,然而还存在问题,拷贝的对象中如果有函数,undefined,symbol,当应用过JSON.stringify()进行解决之后,都会隐没。
let obj1 = {  a: 0,              b: {                 c: 0                 }            };let obj2 = JSON.parse(JSON.stringify(obj1));obj1.a = 1;obj1.b.c = 1;console.log(obj1); // {a: 1, b: {c: 1}}console.log(obj2); // {a: 0, b: {c: 0}}

(2)函数库lodash的_.cloneDeep办法

该函数库也有提供_.cloneDeep用来做 Deep Copy

var _ = require('lodash');var obj1 = {    a: 1,    b: { f: { g: 1 } },    c: [1, 2, 3]};var obj2 = _.cloneDeep(obj1);console.log(obj1.b.f === obj2.b.f);// false

(3)手写实现深拷贝函数

// 深拷贝的实现function deepCopy(object) {  if (!object || typeof object !== "object") return;  let newObject = Array.isArray(object) ? [] : {};  for (let key in object) {    if (object.hasOwnProperty(key)) {      newObject[key] =        typeof object[key] === "object" ? deepCopy(object[key]) : object[key];    }  }  return newObject;}

数组去重办法汇总

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

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

实现apply办法

apply原理与call很类似,不多赘述

// 模仿 applyFunction.prototype.myapply = function(context, arr) {  var context = Object(context) || window;  context.fn = this;  var result;  if (!arr) {    result = context.fn();  } else {    var args = [];    for (var i = 0, len = arr.length; i < len; i++) {      args.push("arr[" + i + "]");    }    result = eval("context.fn(" + args + ")");  }  delete context.fn;  return result;};

原生实现

function ajax() {  let xhr = new XMLHttpRequest() //实例化,以调用办法  xhr.open('get', 'https://www.google.com')  //参数2,url。参数三:异步  xhr.onreadystatechange = () => {  //每当 readyState 属性扭转时,就会调用该函数。    if (xhr.readyState === 4) {  //XMLHttpRequest 代理以后所处状态。      if (xhr.status >= 200 && xhr.status < 300) {  //200-300申请胜利        let string = request.responseText        //JSON.parse() 办法用来解析JSON字符串,结构由字符串形容的JavaScript值或对象        let object = JSON.parse(string)      }    }  }  request.send() //用于理论收回 HTTP 申请。不带参数为GET申请}

将js对象转化为树形构造

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

代码实现:

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

原型继承

这里只写寄生组合继承了,两头还有几个演变过去的继承但都有一些缺点

function Parent() {  this.name = 'parent';}function Child() {  Parent.call(this);  this.type = 'children';}Child.prototype = Object.create(Parent.prototype);Child.prototype.constructor = Child;

实现forEach办法

Array.prototype.myForEach = function(callback, context=window) {  // this=>arr  let self = this,        i = 0,      len = self.length;  for(;i<len;i++) {    typeof callback == 'function' && callback.call(context,self[i], i)   }}

实现迭代器生成函数

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

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

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

实现斐波那契数列

// 递归function fn (n){    if(n==0) return 0    if(n==1) return 1    return fn(n-2)+fn(n-1)}// 优化function fibonacci2(n) {    const arr = [1, 1, 2];    const arrLen = arr.length;    if (n <= arrLen) {        return arr[n];    }    for (let i = arrLen; i < n; i++) {        arr.push(arr[i - 1] + arr[ i - 2]);    }    return arr[arr.length - 1];}// 非递归function fn(n) {    let pre1 = 1;    let pre2 = 1;    let current = 2;    if (n <= 2) {        return current;    }    for (let i = 2; i < n; i++) {        pre1 = pre2;        pre2 = current;        current = pre1 + pre2;    }    return current;}

Promise.race

Promise.race = function(promiseArr) {  return new Promise((resolve, reject) => {    promiseArr.forEach(p => {      // 如果不是Promise实例须要转化为Promise实例      Promise.resolve(p).then(        val => resolve(val),        err => reject(err),      )    })  })}

滚动加载

原理就是监听页面滚动事件,剖析clientHeightscrollTopscrollHeight三者的属性关系。

window.addEventListener('scroll', function() {  const clientHeight = document.documentElement.clientHeight;  const scrollTop = document.documentElement.scrollTop;  const scrollHeight = document.documentElement.scrollHeight;  if (clientHeight + scrollTop >= scrollHeight) {    // 检测到滚动至页面底部,进行后续操作    // ...  }}, false);