关于javascript:字节前端高频手写面试题持续更新中

2次阅读

共计 16493 个字符,预计需要花费 42 分钟才能阅读完成。

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);             // 1
add(1)(2);      // 3
add(1)(2)(3);// 6
add(1)(2, 3); // 6
add(1, 2)(3); // 6
add(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();             // 1
add(1)(2).toString();      // 3
add(1)(2)(3).toString();// 6
add(1)(2, 3).toString(); // 6
add(1, 2)(3).toString(); // 6
add(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') // 帮咱们执行 generator

const 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 module
class 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()
  }
}

// reactive
function 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)
  }
}

// watcher
class 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 很类似,不多赘述

// 模仿 apply
Function.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);
正文完
 0