【手写系列】call,apply与bind以及详细解析

199 阅读4分钟

关于手写 call,apply 与 bind,我有如下总结,因为我也是看完课程后的总结。所以学之前,先建议看看黑马新出的前端面试题中关于call,apply与bind,一起来看看吧~~~

call

call是一个函数, 可以改变函数的this指向, 先来看看call的基本使用吧!

let obj = { name: '刘德华' }

function fn(num1, num2) {
  console.log(this);
  return num1 + num2
}

const res = fn.call(obj, 1, 2)
console.log('两数之和为', res);

可以看出call函数改变了fn函数的this指向变为obj,并且可以传递参数序列

第一步(改变this)

接下来我将定义一个函数myCall来实现call所具有的逻辑:

//因为只有函数才能调用call方法,所以把myCall放到Function构造函数的原型上,来供所有的实例函数使用。
//这里outObj,args分别为,要把原函数this改为outObj和参数序列
Function.prototype.myCall = function (outObj, ...args) {
  //这里我在outObj对象上追加一属性f把this赋值给f  (这里this指向函数调用者fn)
  outObj.f = this
  //f在其中充当一个转接变量,outObj.f()相当于outObj直接调用了fn函数,
  //由此可得出fn里的this就会指向outObj,也就是obj
  outObj.f()

}
let obj = { name: '刘德华' }
function fn(num1, num2) {
  console.log(this);
  return num1 + num2
}
const res = fn.myCall(obj, 1, 2)
console.log('两数之和为', res);

详细讲解,看上方代码注释!!!上述代码的 f (中转键),有可能与传入对象重名,这样肯定是不严谨的,所以我使用Symbol:

第二步(Symbol优化)

symbol 是一种基本数据类型,每个从 Symbol() 返回的 symbol 值都是唯一的。

Function.prototype.myCall = function (outObj, ...args) {
  let key = Symbol('key')
  //这里不能outObj.key,因为这样就如上述代码,没得变化了
  outObj[key] = this
  //根据剩余参数,args为数组,所以需要...args
  const res = outObj[key](...args)
  delete outObj[key]
  //最后返回原函数的返回结果
  return res
}
let obj = { name: '刘德华' }
function fn(num1, num2) {
  console.log(this);
  return num1 + num2
}
const res = fn.myCall(obj, 1, 2)
console.log('两数之和为', res);

上述代码使用delete outObj[key]就是清除向对象里面追加的fn函数。 如下

第三步(考虑其他情况)

根据call方法:当你要改变this的指向为bull或undefind时,fn函数的this指向将会变为Window

所以我有了自己的想法:

Function.prototype.myCall = function (outObj, ...args) {
  let key = Symbol('key')
  //当你要改变this的指向为bull或undefind时
  if (outObj === null || outObj === undefined) {
    //突然想到Window也是对象,一葫芦画瓢
    window[key] = this
    const res = window[key](...args)
    delete window[key]
    return res
  } else {
    outObj[key] = this
    const res = outObj[key](...args)
    //清除多余函数
    delete outObj[key]
    return res
  }

}
let obj = { name: '刘德华' }
function fn(num1, num2) {
  console.log(this);
  return num1 + num2
}
const res = fn.myCall(obj, 1, 2)
console.log('两数之和为', res);

apply

apply与call的区别就在于,传递的参数形式不同, 掌握call,对于apply也就差不多了。同样定义函数myApply:

Function.prototype.myApply = function (outObj, arr) {
  let key = Symbol('key')
  if (outObj === null || outObj === undefined) {
    window[key] = this
    const res = window[key](...arr)
    delete window[key]
    return res
  } else {
    outObj[key] = this
    const res = outObj[key](...arr)
    //清除多余函数
    delete outObj[key]
    return res
  }
}
let obj = { name: 'xjy' }
function fn(num1, num2) {
  console.log(this);
  return num1 + num2
}
const res = fn.myApply(undefined, [1, 2])
console.log('两数之和为', res);

同样也考虑了改变this的指向为bull或undefind时的情况

bind

bind可能有两种方式:

第一种

第一种就是上述call与apply的实现方式一样

Function.prototype.myBind = function (outObj, ...a) {
  let key = Symbol('key')
  if (outObj === null || outObj === undefined) {
    window[key] = this
    return (...b) => {
      const res = window[key](...a, ...b)
      delete window[key]
      return res
    }
  } else {
    outObj[key] = this
    return (...b) => {
      const res = outObj[key](...a, ...b)
      delete outObj[key]
      return res
    }
  }
}
let obj = { name: 'xjy' }
function fn(num1, num2, num3) {
  console.log(this);
  return num1 + num2 + num3
}
const res = fn.myBind(null, 1, 2, 10)
console.log('两数之和为', res(3));

上述代码之所以return一个回调函数,是因为bind调用不是立即执行, 然后后面传递的参数是优先于const res = fn.myBind(null, 1, 2, 10)后再console.log('两数之和为', res(3));所以有一定的顺序传递。

第二种

这种方式比较简便,在使用了一次call改变this指向。

Function.prototype.myBind = function (outObj, ...a) {
  return (...b) => {
    //因为回调函数是箭头函数,所以this指向fn函数
    return this.call(outObj, ...a, ...b)
  }

}
let obj = { name: 'xjy' }
function fn(num1, num2, num3) {
  console.log(this);
  return num1 + num2 + num3
}
const res = fn.myBind(obj, 1, 2, 10)
console.log('两数之和为', res(3));

这里不需要考虑null与undefind的原因是,使用了call方法,上述已经讲过call本生改变this指向为null与undefind时,原函数的this就会指向window。

最后

上述就是手写call,apply与bind的全部内容,本人也是观看视频写下的总结!,上述内容可能还有缺陷,欢迎指出。个人博客XieJinYang的博客 (gitee.io)