常见手写面试题-自用

128 阅读5分钟

1.手写call和apply

call和apply的区别在于传参不同:依次传入\数组传入

Function.prototype.myCall = function(context) {
  // 形参context默认是第一个参数 即我们要改成的目标this对象,没传就是window
  context = context || window
  // 我们调用的时候 obj.fn.call(obj2) 这个this指向fn 就是原函数
  // 把这个原函数保存到context上,调用的时候自然就改变了this指向
  context.fn = this
  let result = arguments.length>1? 
    context.fn(...[...arguments].slice(1)):
    context.fn()
  delete context.fn
  return result
}
Function.prototype.myApply = function(context) {
  // 思路一样 只是对参数处理不同
  context = context || window
  context.fn = this
  let result = arguments.length>1? 
    context.fn(...arguments[1]):
    context.fn()
  delete context.fn
  return result
}

2.手写bind

apply和上边两个的区别在于不会立即执行,而是返回了一个指定了this指向的函数,另外还要支持柯里化

Function.prototype.myBind = function() {
  // 一样 保存原方法
  const _this = this
  // 拿参数
  const arg = [...arguments]
  // 第一项 为新this
  const newThis = arg.shift()
  // 返回新的函数
  return function() {
    // 支持柯里化传参
    const newArg = [...arg, ...arguments]
    return _this.apply(newThis, newArg)
  }
}

3.手写new

// * 1. 结构上:创建一个空对象,作为返回的对象实例
// * 2. 属性上:将生成空对象的原型对象指向了构造函数prototype
// * 3. 关系上:将当前的实例对象赋值给了内部的this
// * 4. 生命周期上:执行了构造函数的初始化代码
function myNew() {
  const constructor = Array.prototype.shift.call(arguments) 
  const obj = Object.create(constructor.prototype)
  const result = constructor.call(obj, ...arguments)
  return result instanceof Object? result: obj
}

4.手写instanceof

右边构造函数的prototype 有没有出现在左边的原型链上

function myInstanceOf(left, right) {
  // es6不推荐直接访问__proto__
  let proto = Object.getPrototypeOf(left)
  let prototype = right.prototype
  while(true) {
    if(!proto) return false
    if(proto === prototype) return true
    proto = Object.getPrototypeOf(proto)
  }
}

5.手写Object.create()

创造一个对象,并把该对象的__proto__指向传入参数的prototype,利用new 因为new的时候会把构造函数的prototype放到实例的__proto__上

function create(params) {
  function Sub() {}
  Sub.prototype = params
  return new Sub()
}

6.手写promise

    1. promise状态 - pending | fulfilled | rejected executor: new Promise的时候立即执行,接收两个参数 resolve | reject
    1. promise默认状态?状态是如何流转的? - 默认:pending 状态流转:pending => fufilled | pending => rejected 内部维护成功value:undefined | thenable | promise 内部维护失败变量reason
    1. promise返回值? - then方法:接收onFulfilled 和 onRejected 如果then时,promise已经成功,执行onFulfilled,参数value 如果then时,promise已经失败,执行onRejected,参数reson 如果then中有异常,执行onRejected


手写注意点:链式调用的时候需要内部返回一个promise,这个promise需要跟x(onfulfilled的结果)作比较,因为在实例化过程中内部拿不到promise2 所以需要开异步,我们只能通过settimeout实现,所以跟实际的promise执行顺序有差异。链式调用的时候then里边如果返回的是一个promise,需要调用他的then方法去获取他的value值(如果resolve了一个promise 需要递归),并将结果赋给promise2,直到x不为object或者function

 class myPromise {
      constructor(executor) {
        this.status = myPromise.pending
        this.value = undefined
        this.reason = undefined
        this.onFulFilledCallbacks = []
        this.onRejectedCallbacks =[]
        let resolve = value => {
          if(this.status === myPromise.pending) {
            this.status = myPromise.fullFilled
            this.value = value
            this.onFulFilledCallbacks.forEach(element => {
              element()
            });
          }
        }
        let reject = reason => {
          if(this.status === myPromise.pending) {
            this.status = myPromise.rejected
            this.reason = reason
            this.onRejectedCallbacks.forEach(element => {
              element()
            })
          }
        }
        try {
          executor(resolve, reject)
        } catch (error) {
          reject(error)
        }
      }
      then(onFulFilled, onRejected) {
        onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : v => v
        onRejected = typeof onRejected === 'function' ? onRejected : err => {
            throw err
        }
        let promise2 = new myPromise((resolve, reject) => {
          if(this.status === myPromise.fullFilled) {
            setTimeout(() => {  // 坑点 这里因为在resolvePromise中用到了promise2参数,实例化没完成拿不到,所以开个异步
              let x = onFulFilled(this.value)
              resolvePromise(x, promise2, resolve, reject)
            },0);
          }
          if(this.status === myPromise.rejected) {
            setTimeout(() => {
              let x = onRejected(this.reason)
              resolvePromise(x, promise2, resolve, reject)
            })
          }
          if(this.status === myPromise.pending) {
              this.onFulFilledCallbacks.push(() => {
                let x = onFulFilled(this.value) 
                resolvePromise(x, promise2, resolve, reject)
              })
              this.onRejectedCallbacks.push(() => {
                let x = onRejected(this.reason)
                resolvePromise(x, promise2, resolve, reject)
              })
          }
        })
        return promise2
      }
    }
    myPromise.pending = 'PENDING'
    myPromise.fullFilled = 'FULLFILLED'
    myPromise.rejected = 'REJEDCTED'

    function resolvePromise(x, promise2, resolve, reject) {
      // 如果then返回值是调用者本身 则报循环引用错误
      if(x === promise2) {
        let err = new TypeError('Chaining cycle detected for promise #<Promise>')
        return reject(err)
      }
      if(x !== null &&(typeof x === 'object' || typeof x === 'function')) {
        try {
          let then = x.then
          if(then) {
            // 如果then里边返回了一个promise 那么直接调用内层promise的then方法获取value值
            // (如果里边resolve的还是promise 递归调用then,并将原始的promise2的resolve层层传递,直到返回基础数据类型或者不返回,将值传给resolve并调用)
            // 这个y是内层promise 的value值(有可能resolve(new Promise()))
            then.call(x, y => {
              resolvePromise(y, promise2, resolve, reject)
            }, err => {
              reject(err)
            })
          } else {
            resolve(x)
          }
        } catch (error) {
          reject(error)
        }
      } else {
        resolve(x)
      }
    }

6.1手写Promise.resolve(),Promise.reject()

myPromise.resolve = function(value) {
  return new myPromise((resolve, reject) => {
    resolve(value)
  })
}
myPromise.reject = function(err) {
  return new myPromise((resolve, reject) => {
    reject(err)
  })
}

6.2 手写Promise.race()

myPromise.race= function (promises) {
  return new myPromise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      promises[i].then(resolve, reject)
    }
  })
}

6.3手写Promise.all()

myPromise.all = function(promises) {
  let i = 0
  let arr = []

  function pocessData(j, res, resolve) {
    arr[j] = res
    i++
    if(i === promises.length) {
      resolve(arr)
    }
  }

  return new myPromise((resolve, reject) => {
    for (let j = 0; j < promises.length; j++) {
      promises[j].then(res => {
        pocessData(j, res, resolve)
      }).catch(err => {
        reject(err)
      })
    }
  })
}

6.4 一道promise面试题

Promise.resolve(0)    
    .then(res => {    
        console.log(res); 
        return Promise.resolve(4)
    })
    .then(res => {    
        console.log(res);
    })
Promise.resolve()     
    .then(() => {     
        console.log(1);
    })
    .then((res) => {  
        console.log(2);
    })
    .then(() => {     
        console.log(3);
    })
    .then(() => {    
        console.log(5);
    })
    .then(() => {   
        console.log(6);
    })


第一次执行:同步代码 微任务队列 micro:[log(0), log(1)]
第二次执行:微任务队列顺序执行 打印 0,1 此时micro: [return Promise.resole(4),log(2)]
第三次执行: 打印2 micro:[resolve(4).then(), log(3)]
第四次执行:打印3 micro:[log(4), log(5)]
之后顺序打印 4,5,6

7防抖函数

  • 市面上很常见的一种实现方式,这次手写的时候还是遇见了一些问题,主要在于这个传参获取arguments,之前以为是要类似于柯里化一样去传参 debounce(fn)(arg),但是这样就成了一个iife失去监听执行的功能。
  • 正当百思不得其解的时候突然发现这其实就是在传递监听的当前对象,如window.addEventListener('scroll', (e)=> {e.target}),其实就是把隐藏的参数e传给了debounce return出来的函数 所以通过apply去传参
function debounce(fn, delay) {
      let timmer = null
      return  function() {
        let context = this
        let arg = arguments
        if(timmer) {
          clearTimeout(timmer)
          timmer = null
        }
        timmer = setTimeout(() => {
           fn.apply(context, arg)
        },delay)
      }
    }

8节流函数

 function throttle(fn, delay) {
      let currentTime = Date.now()
      return function() {
        let context = this
        let arg = arguments
        let now = Date.now()
        if(now - currentTime > delay) {
          currentTime = Date.now();
          fn.apply(context, arg)
        }
      }
    }

9数组去重

    // 1 es6
    Array.from(new Set(arr))
    // 2
    function uniqueArray(arr) {
      let map = {}
      let res = []
      for (let i = 0; i < arr.length; i++) {
        if(!map.hasOwnProperty(arr[i])) {
          map[arr[i]] = 1
          res.push(arr[i])
        }
      }
      return res
    }

10数组扁平化

    // for循环递归
    function flat(arr) {
      let result = []
      for (let i = 0; i < arr.length; i++) {
        let el = arr[i];
        if(Array.isArray(el)) {
          result = result.concat(flat(el))
        } else {
          result.push(el)
        }
      }
      return result
    }
   // reduce递归
   function flats(arr) {
      return arr.reduce((pre, cur) => {
        return pre.concat(Array.isArray(cur)? flats(cur) :cur)
      }, [])
    }
  // some 配合展开运算符
  function flatss(arr) {
      while(arr.some(el => Array.isArray(el))) {
        arr = [].concat(...arr)
      }
      return arr
    }
  // 原生方法
  arr.flat(infinity)

11深拷贝

 // 无法拷贝 function undefined symbol
 JSON.parse((JSON.stringify()))
 // 递归拷贝
 function deepClone(obj) {
    if(obj === null || typeof obj !== 'object') {
      return obj
    }
    let result = Array.isArray(obj)? [] : {}
    for (const key in obj) {
      result[key] = deepClone(obj[key])
    }
    return result
   }