重学JS | 改变函数上下文的3种方式

326 阅读3分钟

这是我参与更文挑战的第8天,活动详情查看:更文挑战

[重学JavaScript系列文章连载中...]

在重学JS系列文章中,上一节学习了JS中的this指向问题。这节学习如何改变函数的上下文,实际上是改变函数体中this的指向。call()函数和apply()函数是为改变函数运行时上下文而存在的,但它们并不是从函数继承而来。bind()函数也能达到这个目的。下面看看三者的用法:

1. call()函数的用法

call()函数调用一个函数时,会将该函数的执行上下文改变为另一个对象。语法如下:

// Function : 调用的函数
// context : 新的对象上下文,函数中的this指向context,若context为null|undefined,则执行window
// arg1,arg2 : 参数列表  
Function.call(context,arg1,arg2,...)       

看个简单例子:

function add(x,y){
   return this.x+this.y
}
var obj = {
  x:10,
  y:20
}
add.call(obj) // 30

例子里的add()函数执行时,this指向了obj。可以粗俗的理解为add()函数挂载到obj对象上,执行完函数后,obj对象再删除了add()函数属性。下面通过代码模拟下这个过程:

Function.prototype.customeCall = function(cxt){
  // 不传context,或者null|undefined,默认指向window
  let context = cxt || window  
  // 将调用函数挂载到当前context
  context.fun = this
  // 获取除了context外的参数
  const args = Array.from(arguments).splice(1)
  // 执行调用函数
  const result = context.fun(...args)
  // 执行完后删除属性
  delete context.fun
  return result
}
// 按照例子执行
add.customeCall(obj) // 30

2. apply()函数的用法

apply()作用与call()函数是一致的,只是在传参形式上不同。

// [argArray] 这里传递的是数组
Function.apply(context,[argArray])

简单例子:

function add(x,y){
  return x + y
}
function mAdd(x,y){
  // 注意apply的传参为数组,与call()函数不一致
  return add.apply(this,[x,y]) 
}
mAdd(10,20) // 30

模拟下实现:

Function.prototype.customeApply = function(ctx,argArray){
  let context = ctx || window
  context.fun = this
  if(!Array.isArray(argArray)){
    throw new Error('参数必须为数组')
  }
  if(!argArray){
    return context.fun()
  }
  const result = context.fun(...argArray)
  delete context.fun
  return result
}

3. bind()函数的使用

bind()函数创建一个新函数,在调用时设置this关键字为提供的值,在执行新函数时,将给定的参数列表作为原函数的参数序列,从前往后匹配。语法如下:

Function.bind(context,arg1,arg2,...)

简单例子:

function add(x,y){
  return this.x+this.y
}
var obj = {
  x:10,
  y:20
`}`
// 函数上下文指向obj返回新函数
var newFun = add.bind(obj) 
newFun() // 30

模拟下实现:

Function.prototype.customeBind = function(context, ...rest) {
  if (typeof this !== 'function') 
    throw new TypeError('invalid invoked!');
  var self = this;
  return function F(...args) {
    if (this instanceof F) {
      return new self(...rest, ...args)
    }
    return self.apply(context, rest.concat(args))
  }
}

4. 总结

call()函数、apply()函数、bind()函数三者都会改变函数调用时的执行主体,修改this的指向。

call()函数、apply()两个函数是立即执行返回,而bind()函数是返回一个新函数,在任何使用可以调用。

apply()函数第二个入参为数组,与其他2个函数不一致。

当然箭头函数也可以改变内部上下文的指向,因为它本身就没有自己的上下文,这块后面单独讲。