前端二面手写面试题总结

这篇博客总结了前端面试中常见的手写题目,包括计算数组交集、实现Promise、每隔一秒打印数字、大整数相加、格式化数字、Function.prototype.bind、模板引擎、instanceof、JSON.stringify等。此外,还探讨了Promise相关方法的实现,如resolve、reject、finally、all、allSettle、race等,并讲解了深拷贝的多种实现方式。
摘要由CSDN通过智能技术生成

给定两个数组,写一个方法来计算它们的交集

例如:给定 nums1 = [1, 2, 2, 1],nums2 = [2, 2],返回 [2, 2]。

function union (arr1, arr2) {
   
  return arr1.filter(item => {
   
      return arr2.indexOf(item) > - 1;
  })
}
 const a = [1, 2, 2, 1];
 const b = [2, 3, 2];
 console.log(union(a, b)); // [2, 2]

手写 Promise.then

then 方法返回一个新的 promise 实例,为了在 promise 状态发生变化时(resolve / reject 被调用时)再执行 then 里的函数,我们使用一个 callbacks 数组先把传给then的函数暂存起来,等状态改变时再调用。

那么,怎么保证后一个 **then** 里的方法在前一个 **then**(可能是异步)结束之后再执行呢? 我们可以将传给 then 的函数和新 promiseresolve 一起 push 到前一个 promisecallbacks 数组中,达到承前启后的效果:

  • 承前:当前一个 promise 完成后,调用其 resolve 变更状态,在这个 resolve 里会依次调用 callbacks 里的回调,这样就执行了 then 里的方法了
  • 启后:上一步中,当 then 里的方法执行完成后,返回一个结果,如果这个结果是个简单的值,就直接调用新 promiseresolve,让其状态变更,这又会依次调用新 promisecallbacks 数组里的方法,循环往复。。如果返回的结果是个 promise,则需要等它完成之后再触发新 promiseresolve,所以可以在其结果的 then 里调用新 promiseresolve
then(onFulfilled, onReject){
   
    // 保存前一个promise的this
    const self = this; 
    return new MyPromise((resolve, reject) => {
   
      // 封装前一个promise成功时执行的函数
      let fulfilled = () => {
   
        try{
   
          const result = onFulfilled(self.value); // 承前
          return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); //启后
        }catch(err){
   
          reject(err)
        }
      }
      // 封装前一个promise失败时执行的函数
      let rejected = () => {
   
        try{
   
          const result = onReject(self.reason);
          return result instanceof MyPromise? result.then(resolve, reject) : reject(result);
        }catch(err){
   
          reject(err)
        }
      }
      switch(self.status){
   
        case PENDING: 
          self.onFulfilledCallbacks.push(fulfilled);
          self.onRejectedCallbacks.push(rejected);
          break;
        case FULFILLED:
          fulfilled();
          break;
        case REJECT:
          rejected();
          break;
      }
    })
   }

注意:

  • 连续多个 then 里的回调方法是同步注册的,但注册到了不同的 callbacks 数组中,因为每次 then 都返回新的 promise 实例(参考上面的例子和图)
  • 注册完成后开始执行构造函数中的异步事件,异步完成之后依次调用 callbacks 数组中提前注册的回调

实现每隔一秒打印 1,2,3,4

// 使用闭包实现
for (var i = 0; i < 5; i++) {
   
  (function(i) {
   
    setTimeout(function() {
   
      console.log(i);
    }, i * 1000);
  })(i);
}
// 使用 let 块级作用域
for (let i = 0; i < 5; i++) {
   
  setTimeout(function() {
   
    console.log(i);
  }, i * 1000);
}

实现非负大整数相加

JavaScript对数值有范围的限制,限制如下:

Number.MAX_VALUE // 1.7976931348623157e+308
Number.MAX_SAFE_INTEGER // 9007199254740991
Number.MIN_VALUE // 5e-324
Number.MIN_SAFE_INTEGER // -9007199254740991

如果想要对一个超大的整数(> Number.MAX_SAFE_INTEGER)进行加法运算,但是又想输出一般形式,那么使用 + 是无法达到的,一旦数字超过 Number.MAX_SAFE_INTEGER 数字会被立即转换为科学计数法,并且数字精度相比以前将会有误差。

实现一个算法进行大数的相加:

function sumBigNumber(a, b) {
   
  let res = '';
  let temp = 0;

  a = a.split('');
  b = b.split('');

  while (a.length || b.length || temp) {
   
    temp += ~~a.pop() + ~~b.pop();
    res = (temp % 10) + res;
    temp  = temp > 9
  }
  return res.replace(/^0+/, '');
}

其主要的思路如下:

  • 首先用字符串的方式来保存大数,这样数字在数学表示上就不会发生变化
  • 初始化res,temp来保存中间的计算结果,并将两个字符串转化为数组,以便进行每一位的加法运算
  • 将两个数组的对应的位进行相加,两个数相加的结果可能大于10,所以可能要仅为,对10进行取余操作,将结果保存在当前位
  • 判断当前位是否大于9,也就是是否会进位,若是则将temp赋值为true,因为在加法运算中,true会自动隐式转化为1,以便于下一次相加
  • 重复上述操作,直至计算结束

将数字每千分位用逗号隔开

数字有小数版本:

let format = n => {
   
    let num = n.toString() // 转成字符串
    let decimals = ''
        // 判断是否有小数
    num.indexOf('.') > -1 ? decimals = num.split('.')[1] : decimals
    let len = num.length
    if (len <= 3) {
   
        return num
    } else {
   
        let temp = ''
        let remainder = len % 3
        decimals ? temp = '.' + decimals : temp
        if (remainder > 0) {
    // 不是3的整数倍
            return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',') + temp
        } else {
    // 是3的整数倍
            return num.slice(0, len).match(/\d{3}/g).join(',') + temp 
        }
    }
}
format(12323.33)  // '12,323.33'

数字无小数版本:

let format = n => {
   
    let num = n.toString() 
    let len = num.length
    if (len <= 3) {
   
        return num
    } else {
   
        let remainder = len % 3
        if (remainder > 0) {
    // 不是3的整数倍
            return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',') 
        } else {
    // 是3的整数倍
            return num.slice(0, len).match(/\d{3}/g).join(',') 
        }
    }
}
format(1232323)  // '1,232,323'

Function.prototype.bind

Function.prototype.bind = function(context, ...args) {
   
  if (typeof this !== 'function') {
   
    throw new Error("Type Error");
  }
  // 保存this的值
  var self = this;

  return function F() {
   
    // 考虑new的情况
    if(this instanceof F) {
   
      return new self(...args, ...arguments)
    }
    return self.apply(context, [...args, ...arguments])
  }
}

模板引擎实现

let template = '我是{
  {name}},年龄{
  {age}},性别{
  {sex}}';
let data = {
  name: '姓名',
  age: 18
}
render(template, data); // 我是姓名,年龄18,性别undefined


function render(template, data) {
   
  const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
  if (reg.test(template)) {
    // 判断模板里是否有模板字符串
    const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段
    template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
    return render(template, data); // 递归的渲染并返回渲染后的结构
  }
  return template; // 如果模板没有模板字符串直接返回
}


参考:前端手写面试题详细解答

实现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)
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值