写点什么

前端高频手写面试题集锦

  • 2022-12-15
    浙江
  • 本文字数:22649 字

    阅读完需:约 74 分钟

手写深度比较 isEqual

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


  • 递归退出条件:

  • 被比较的是两个值类型变量,直接用“===”判断

  • 被比较的两个变量之一为null,直接判断另一个元素是否也为null

  • 提前结束递推:

  • 两个变量keys数量不同

  • 传入的两个参数是同一个变量

  • 递推工作:深度比较每一个key


function isEqual(obj1, obj2){    //其中一个为值类型或null    if(!isObject(obj1) || !isObject(obj2)){        return obj1 === obj2;    }
//判断是否两个参数是同一个变量 if(obj1 === obj2){ return true; }
//判断keys数是否相等 const obj1Keys = Object.keys(obj1); const obj2Keys = Object.keys(obj2); if(obj1Keys.length !== obj2Keys.length){ return false; }
//深度比较每一个key for(let key in obj1){ if(!isEqual(obj1[key], obj2[key])){ return false; } }
return true;}
复制代码

实现 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;};
复制代码

实现观察者模式

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


观察者需要放到被观察者中,被观察者的状态变化需要通知观察者 我变化了 内部也是基于发布订阅模式,收集观察者,状态变化后要主动通知观察者


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('被欺负了');
复制代码

实现节流函数(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 时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。

  • 函数节流 :使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。

实现 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 六大情况用法


// 情况1Promise.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.3function 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所有apiconst 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, rejectconst 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
复制代码

实现 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]
复制代码


参考 前端进阶面试题详细解答

实现 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;};
复制代码

实现 Array.isArray 方法

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

对象数组如何去重

根据每个对象的某一个具体属性来进行去重


const responseList = [  { id: 1, a: 1 },  { id: 2, a: 2 },  { id: 3, a: 3 },  { id: 1, a: 4 },];const result = responseList.reduce((acc, cur) => {    const ids = acc.map(item => item.id);    return ids.includes(cur.id) ? acc : [...acc, cur];}, []);console.log(result); // -> [ { id: 1, a: 1}, {id: 2, a: 2}, {id: 3, a: 3} ]
复制代码

实现 JSON.parse

var json = '{"name":"cxk", "age":25}';var obj = eval("(" + json + ")");
复制代码


此方法属于黑魔法,极易容易被 xss 攻击,还有一种new Function大同小异。

实现 find 方法

  • find 接收一个方法作为参数,方法内部返回一个条件

  • find 会遍历所有的元素,执行你给定的带有条件返回值的函数

  • 符合该条件的元素会作为 find 方法的返回值

  • 如果遍历结束还没有符合该条件的元素,则返回 undefined


var users = [  {id: 1, name: '张三'},  {id: 2, name: '张三'},  {id: 3, name: '张三'},  {id: 4, name: '张三'}]
Array.prototype.myFind = function (callback) { // var callback = function (item, index) { return item.id === 4 } for (var i = 0; i < this.length; i++) { if (callback(this[i], i)) { return this[i] } }}
var ret = users.myFind(function (item, index) { return item.id === 2})
console.log(ret)
复制代码

手写 Promise.race

该方法的参数是 Promise 实例数组, 然后其 then 注册的回调方法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行. 因为 Promise 的状态只能改变一次, 那么我们只需要把 Promise.race 中产生的 Promise 对象的 resolve 方法, 注入到数组中的每一个 Promise 实例中的回调函数中即可.


Promise.race = function (args) {  return new Promise((resolve, reject) => {    for (let i = 0, len = args.length; i < len; i++) {      args[i].then(resolve, reject)    }  })}
复制代码

手写防抖函数

函数防抖是指在事件被触发 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); };}
复制代码

实现 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)
复制代码

实现数组扁平化 flat 方法

题目描述: 实现一个方法使多维数组变成一维数组


let ary = [1, [2, [3, [4, 5]]], 6];let str = JSON.stringify(ary);
复制代码


第 0 种处理:直接的调用


arr_flat = arr.flat(Infinity);
复制代码


第一种处理


ary = str.replace(/(\[|\])/g, '').split(',');
复制代码


第二种处理


str = str.replace(/(\[\]))/g, '');str = '[' + str + ']';ary = JSON.parse(str);
复制代码


第三种处理:递归处理


let result = [];let fn = function(ary) {  for(let i = 0; i < ary.length; i++) }{    let item = ary[i];    if (Array.isArray(ary[i])){      fn(item);    } else {      result.push(item);    }  }}
复制代码


第四种处理:用 reduce 实现数组的 flat 方法


function flatten(ary) {    return ary.reduce((pre, cur) => {        return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);    }, []);}let ary = [1, 2, [3, 4], [5, [6, 7]]]console.log(flatten(ary))
复制代码


第五种处理:能用迭代的思路去实现


function flatten(arr) {  if (!arr.length) return;  while (arr.some((item) => Array.isArray(item))) {    arr = [].concat(...arr);  }  return arr;}// console.log(flatten([1, 2, [1, [2, 3, [4, 5, [6]]]]]));
复制代码


第六种处理:扩展运算符


while (ary.some(Array.isArray)) {  ary = [].concat(...ary);}
复制代码

数组去重方法汇总

首先:我知道多少种去重方式


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

实现 Promise

var PromisePolyfill = (function () {  // 和reject不同的是resolve需要尝试展开thenable对象  function tryToResolve (value) {    if (this === value) {    // 主要是防止下面这种情况    // let y = new Promise(res => setTimeout(res(y)))      throw TypeError('Chaining cycle detected for promise!')    }
// 根据规范2.32以及2.33 对对象或者函数尝试展开 // 保证S6之前的 polyfill 也能和ES6的原生promise混用 if (value !== null && (typeof value === 'object' || typeof value === 'function')) { try { // 这里记录这次then的值同时要被try包裹 // 主要原因是 then 可能是一个getter, 也也就是说 // 1. value.then可能报错 // 2. value.then可能产生副作用(例如多次执行可能结果不同) var then = value.then
// 另一方面, 由于无法保证 then 确实会像预期的那样只调用一个onFullfilled / onRejected // 所以增加了一个flag来防止resolveOrReject被多次调用 var thenAlreadyCalledOrThrow = false if (typeof then === 'function') { // 是thenable 那么尝试展开 // 并且在该thenable状态改变之前this对象的状态不变 then.bind(value)( // onFullfilled function (value2) { if (thenAlreadyCalledOrThrow) return thenAlreadyCalledOrThrow = true tryToResolve.bind(this, value2)() }.bind(this),
// onRejected function (reason2) { if (thenAlreadyCalledOrThrow) return thenAlreadyCalledOrThrow = true resolveOrReject.bind(this, 'rejected', reason2)() }.bind(this) ) } else { // 拥有then 但是then不是一个函数 所以也不是thenable resolveOrReject.bind(this, 'resolved', value)() } } catch (e) { if (thenAlreadyCalledOrThrow) return thenAlreadyCalledOrThrow = true resolveOrReject.bind(this, 'rejected', e)() } } else { // 基本类型 直接返回 resolveOrReject.bind(this, 'resolved', value)() } }
function resolveOrReject (status, data) { if (this.status !== 'pending') return this.status = status this.data = data if (status === 'resolved') { for (var i = 0; i < this.resolveList.length; ++i) { this.resolveList[i]() } } else { for (i = 0; i < this.rejectList.length; ++i) { this.rejectList[i]() } } }
function Promise (executor) { if (!(this instanceof Promise)) { throw Error('Promise can not be called without new !') }
if (typeof executor !== 'function') { // 非标准 但与Chrome谷歌保持一致 throw TypeError('Promise resolver ' + executor + ' is not a function') }
this.status = 'pending' this.resolveList = [] this.rejectList = []
try { executor(tryToResolve.bind(this), resolveOrReject.bind(this, 'rejected')) } catch (e) { resolveOrReject.bind(this, 'rejected', e)() } }
Promise.prototype.then = function (onFullfilled, onRejected) { // 返回值穿透以及错误穿透, 注意错误穿透用的是throw而不是return,否则的话 // 这个then返回的promise状态将变成resolved即接下来的then中的onFullfilled // 会被调用, 然而我们想要调用的是onRejected if (typeof onFullfilled !== 'function') { onFullfilled = function (data) { return data } } if (typeof onRejected !== 'function') { onRejected = function (reason) { throw reason } }
var executor = function (resolve, reject) { setTimeout(function () { try { // 拿到对应的handle函数处理this.data // 并以此为依据解析这个新的Promise var value = this.status === 'resolved' ? onFullfilled(this.data) : onRejected(this.data) resolve(value) } catch (e) { reject(e) } }.bind(this)) }
// then 接受两个函数返回一个新的Promise // then 自身的执行永远异步与onFullfilled/onRejected的执行 if (this.status !== 'pending') { return new Promise(executor.bind(this)) } else { // pending return new Promise(function (resolve, reject) { this.resolveList.push(executor.bind(this, resolve, reject)) this.rejectList.push(executor.bind(this, resolve, reject)) }.bind(this)) } }
// for prmise A+ test Promise.deferred = Promise.defer = function () { var dfd = {} dfd.promise = new Promise(function (resolve, reject) { dfd.resolve = resolve dfd.reject = reject }) return dfd }
// for prmise A+ test if (typeof module !== 'undefined') { module.exports = Promise }
return Promise})()
PromisePolyfill.all = function (promises) { return new Promise((resolve, reject) => { const result = [] let cnt = 0 for (let i = 0; i < promises.length; ++i) { promises[i].then(value => { cnt++ result[i] = value if (cnt === promises.length) resolve(result) }, reject) } })}
PromisePolyfill.race = function (promises) { return new Promise((resolve, reject) => { for (let i = 0; i < promises.length; ++i) { promises[i].then(resolve, reject) } })}
复制代码

实现 Ajax

步骤


  • 创建 XMLHttpRequest 实例

  • 发出 HTTP 请求

  • 服务器返回 XML 格式的字符串

  • JS 解析 XML,并更新局部页面

  • 不过随着历史进程的推进,XML 已经被淘汰,取而代之的是 JSON。


了解了属性和方法之后,根据 AJAX 的步骤,手写最简单的 GET 请求。

实现 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') // undefinedvar a = new bindFun(10) // Person name: poetry1、Person age: 10、Person this: fBound {}a.say() // person say
// 再测试作为普通函数调用var bindNormalFun = normalFun.myBind(obj, 'poetry2') // undefinedbindNormalFun(12) // 普通函数name: poetry2 普通函数 age: 12 普通函数 this: {name: 'poetries', age: 18}
复制代码


注意: bind之后不能再次修改this的指向,bind多次后执行,函数this还是指向第一次bind的对象

实现 instanceOf

// 模拟 instanceoffunction instance_of(L, R) {  //L 表示左表达式,R 表示右表达式  var O = R.prototype; // 取 R 的显示原型  L = L.__proto__; // 取 L 的隐式原型  while (true) {    if (L === null) return false;    if (O === L)      // 这里重点:当 O 严格等于 L 时,返回 true      return true;    L = L.__proto__;  }}
复制代码


用户头像

还未添加个人签名 2022-07-31 加入

还未添加个人简介

评论

发布
暂无评论
前端高频手写面试题集锦_JavaScript_helloworld1024fd_InfoQ写作社区