apply、call、bind的区别与精简实现

5,306 阅读3分钟

apply() 方法接收一个指定的this值和一个包含多个参数的数组来调用一个函数。

call() 方法接收一个指定的 this 值和一个参数列表来调用一个函数。

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用

使用 callapply 函数的时候要注意,如果传递给 this 的值不是一个对象,JavaScript 会尝试使用内部 ToObject 操作将其转换为对象。因此,如果传递的值是一个原始值比如 7'foo',那么就会使用相关构造函数将它转换为对象,所以原始值 7 会被转换为对象,像 new Number(7) 这样,而字符串 'foo' 转化成 new String('foo') 这样,例如:

function bar() {
  console.log(Object.prototype.toString.call(this));
}

//原始值 7 被隐式转换为对象
bar.call(7); // [object Number]
bar.call('foo'); // [object String]

apply

func.apply(thisArg, [argsArray])

  • thisArg必选的。在 func 函数运行时使用的 this 值。如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。
  • argsArray 可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。
Function.prototype.Apply = function (thisArg, args = Symbol.for('args')) {
  //Apply 函数总是被我们想改变this的函数调用,因此本函数内this总是指代调用函数
  
  //生成一个Symbol类型的唯一符号,用于将调用apply的函数挂载到指定对象上
  const fn = Symbol('fn')      
  thisArg[fn] = this  

  //通过对象调用函数,并传参
  args === Symbol.for('args') ? thisArg[fn]() : thisArg[fn](...args)

  //删除挂载到指定对象上的方法
  delete thisArg[fn]           
}

// 声明全局变量
var position = 'global'
var name = 'window'

function func(name) {
  console.log(this.position)
  console.log(name) 
}

const obj = {
  name: 'object',
  position: 'obj',
}

func.Apply(obj,[obj.name,null]) // obj  object
// 其中,Apply内this指向func

func.Apply(obj,[name,null]) // obj  window
// func中 name 只受传参影响

Symbol.for(key) 会根据给定的键 key,来从运行时的 symbol 注册表中找到对应的 symbol,如果找到了,则返回它,否则,新建一个与该键关联的 symbol,并放入全局 symbol 注册表中。

call

Function.prototype.Call = function (thisArg, ...args) {
  //这里this为这个方法的调用者
  const fn = Symbol('fn')
  thisArg[fn] = this || globalThis

  //通过对象调用函数,并传参
  args.length ? thisArg[fn](...args) : thisArg[fn]() 

  //删除挂载到指定对象上的方法
  delete thisArg[fn]      
}

func.Call(obj)  // obj undefined
func.Call(obj, 'test') // obj test

这里不使用arguments对象获取传入参数。arguments不是一个 Array ,除了length属性和索引元素之外没有任何Array属性。

使用剩余参数...args可以精简代码

bind

bind() 函数会创建一个新的绑定函数,它包装了原函数对象。调用绑定函数通常会导致执行包装函数。

简单版本

Function.prototype.Bind = function(thisArg, ...args){
  let self = this;
  let fBound = function(...args1){
    return self.apply(thisArg, [...args, ...args1]);
  }
  return fBound;
}

使用闭包保存了第一次绑定时的this值,同时使后续的绑定无效

Function.prototype.Bind = function(thisArg, ...args){
  let self = this;
  
  let fBound = function(...args1){
    //如果当前this为fBound的实例,表示是执行了new,指向this,否则指向bind对象
    return self.apply(this instanceof fBound ? this : thisArg, [...args, ...args1]);
  }
  //修改返回函数的 prototype 为绑定函数的 prototype,new出实例对象就可以继承绑定函数的原型中的成员
  fBound.prototype = this.prototype;
  return fBound;
}

这里补充了绑定的函数为构造函数时的情况

function foo(age, height) {
  console.log(this.name)       // obj
  console.log(age)             // 3
  console.log(height)          // 2
}
const obj = {
  name: 'obj',
  age: 3
}
foo.Bind(obj, obj.age)(2) // 绑定bind时同时传递一个参数

相关文章:
从零开始的前端筑基之旅(超级精细,持续更新~)
看完就能搞懂的this指向及箭头函数的讲解~

如果你收获了新知识,请给作者点个赞吧~