面试题手写call、apply、bind

关于手写 call、apply、bind 的一些个人见解及具体代码和注释,有错误的地方往评论以纠正

call

Function.prototype._call = function (uThis, ...args) {
  // 获取 this
  let _this = uThis === undefined || uThis === null ? globalThis : Object(uThis);
  // 方法的唯一键
  const key = Symbol()
  // 将方法挂载到 this
  _this[key] = this
  // 调用,获取返回值
  const res = _this[key](...args)
  // 删除方法键
  Reflect.deleteProperty(_this, key)
  return res
}
测试代码(通用),所有测试均在 node 环境下
obj = {
  name: '小王',
  age: 18
}

function test (doingSomething, makeSomething) {
  console.log(this);
  console.log(this.name + '  ' + this.age + '  ' + doingSomething + '  ' + makeSomething);
}

function test2 () {
  console.log(arguments);
}
测试
test2._call({}) // { [Symbol()]: [Function: test2] },[Arguments] {}
test2.call({}) // {}, [Arguments] {}
test2._call(null, '') // global, [Arguments] { '0': '' }
test2.call(null, '') // global, [Arguments] { '0': '' }
test2._call(obj, 'eating') // { name: '小王', age: 18, [Symbol()]: [Function: test2] }, [Arguments] { '0': 'eating' }
test2.call(obj, 'eating') // { name: '小王', age: 18 },[Arguments] { '0': 'eating' }

apply

使用 apply 传入的第二个参数需要为数组,但存在几种特殊的的参数情况:

  1. {} 对象有内容、无内容都可,通过 new 得到的也可

  2. undefined

  3. null
    注意点:有一些特殊情况第二个参数的值会发生转变

      传入参数                apply处理                 调用函数中
    {length:2}       =>[undefined,undefined]      =>[Arguments]{'0':undefined,length;1}
    undefined                =>[]                 =>[Arguments]{} 
    null                     =>[]                 =>[Arguments]{}
    new String('abc')    =>['a','b','c']          =>[Arguments]{ '0': 'a', '1': 'd', '2': 'v' }
    new Number、Set、Map      =>[]                 =>[Arguments]{}
    
总结:
  1. 类似数组的对象(一些通过 new 得到的对象加了 length 属性就属于类似数组的对象)、通过 new String() 得到的对象都会做转换
  2. 只要是对象一律符合传入参数的规则(通过 new 得到的也一样),一律转为空数组
  3. undefined、null一律转为空数组
    代码
Function.prototype._apply = function (uThis, args = []) {
  // 获取 this  
  let _this = uThis === undefined || uThis === null ? globalThis : Object(uThis);
  function isObject (args) {// 判断是否为对象
    // 注意: typeof null 虽然为 object 但 null 实际并不为对象,具体原因可以百度
    return typeof args === 'object' && args !== null
  }
  function isLikeArray (o) {  // 类数组对象的判断
    if (isObject(o) && Number.isFinite(o.length) && o.length >= 0 && o.length < 4294967296) {
      // 4294967296: 2^32
      return true
    } else {
      return false
    }
  }
  (function () {
    // 传入的第二个参数的处理
    if (isLikeArray(args)) {
      args = Array.from(args) // 转为数组
    }
    else if (isObject(args)) {
      args = []
    }
    else if (!Array.isArray(args)) {
      throw new TypeError('second argument to apply must be an array')
    }
  })(args)
  // const res = this.call(_this, ...args) // 可以通过这样调用减少代码量,就是会多一次获取this的步骤
  const key = Symbol()  // 用于临时存储函数
  _this[key] = this // 隐式绑定this指向到context上
  const res = _this[key](...args); // 执行函数并展开数组,传递函数参数
  Reflect.deleteProperty(_this, key)
  return res
}

测试

test1._apply(obj) // [Arguments] {}
test1.apply(obj)// [Arguments] {}
test2._apply(obj, { length: 2 }) // [Arguments] { '0': undefined, '1': undefined }
test2.apply(obj, { length: 2 }) // [Arguments] { '0': undefined, '1': undefined }
test._apply(obj, '') // throw new TypeError('second argument to apply must be an array')
test.apply(obj, '') // TypeError: second argument to apply must be an array
test._apply(obj, 1) // throw new TypeError('second argument to apply must be an array')
test.apply(obj, 1) // throw new TypeError('second argument to apply must be an array')
test._apply(obj, []) // { name: '小王', age: 18, [Symbol()]: [Function: test] },小王  18  undefined  undefined
test.apply(obj, []) // { name: '小王', age: 18 },小王  18  undefined  undefined
test._apply(obj, ['Eating']) // { name: '小王', age: 18, [Symbol()]: [Function: test] },小王  18  Eating  undefined
test.apply(obj, ['Eating']) // { name: '小王', age: 18 },小王  18  Eating  undefined

bind

Function.prototype._bind = function (uThis, ...args) {
  // 拿到方法
  const fn = this
  // 处理this
  uThis = uThis === undefined || uThis === null ? globalThis : Object(uThis);
  // 包装新方法
  const newFn = function (...newargs) {
    const byNew = this instanceof newFn // this 是否为 newFn 的实例 也就是返回的 newFn 是否通过 new 调用
    const _this = byNew ? this : Object(uThis) // new 调用就绑定到 this 上,否则就绑定到传入的 uThis 上
    // 改变 this 指向并调用
    return fn.call(_this, ...args, ...newargs)
  }
  if (fn.prototype) {
    // 复制源函数的 prototype 给 newFn 一些情况下函数没有 prototype ,比如箭头函数
    newFn.prototype = Object.create(fn.prototype);
  }
  return newFn
}
测试
test2._bind()() // global,[Arguments] {}
test2.bind()() // global,[Arguments] {}
test2._bind(null, 'eating')('make rice') // global, [Arguments] { '0': 'eating', '1': 'make rice' }
test2.bind(null, 'eating')('make rice') // // global, [Arguments] { '0': 'eating', '1': 'make rice' }
console.log(new (student._bind(obj))('小王', 18)); // student { name: '小王', age: 18 }
console.log(new (student.bind(obj))('小王', 18)); // student { name: '小王', age: 18 }

参考文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值