欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

手写call、apply、bind

程序员文章站 2022-03-01 13:05:56
...

call apply bind是面试中经常被问到的问题,也是很考验js基础是否扎实的一道题,它们的作用都是改变函数的this指向,也就是改变当前的作用域。其用法也都差不多,只是传参方式有所不同,但如果你只告诉面试官这些的话,那面试官也就仅仅只知道你会用,那么我们不止要知其然,更要知其所以然。

  1. 语法
fun.call(thisArg, param1, param2, ...)
fun.apply(thisArg, [param1, param2, ...])
fun.bind(thisArg, param1, param2, ...)

参数:
thisArg: 执行作用域
param1, param2, … :(可选)都是传递给fun的参数,只是写法不同

  1. 原理
    调用这些api的必须是函数,因为它们都是挂在Function对象上的三个方法,只有函数才有这些方法。
    Object.prototype.toString()就是一个函数所以我们经常能看到这样的写法:Object.prototype.toString.call(data)
  • 手写call
    思路:
    1.在函数的原型对象上添加一个call_方法以便所有的函数都能使用
    2.根据call的规则设置上下文对象,也就是this的指向。
    3.通过设置context的属性,将函数的this指向隐式绑定到context上
    4.通过隐式绑定执行函数并传递参数。
    5.删除临时属性,返回函数执行结果
Function.prototype.call_ = function(context) {
  // 正确判断上下文对象并赋予作用域参数 如果没有赋予全局作用域
  if(context === null || context === undefined) {
    context = window   //指定为 null 或 undefined 的 this 值指向全局对象
  } else {
    context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
  }
 context.fn = this  // 为这个函数作用域添加一个fn方法,并把这个函数赋给这个方法
 let args = [...arguments].slice(1)  // 获取传给函数的参数
 let result = context.fn(...args)  // 执行方法
 Reflect.deleteProperty(context, 'fn') // 删除这个方法
 return result  // 返回结果
}
  • 手写apply
    给函数传递参数的方式不太一样,其他部分都一样
Function.prototype.apply_ = function(context) {
  // 正确判断上下文对象并赋予作用域参数 如果没有赋予全局作用域
  if(context === null || context === undefined) {
    context = window   //指定为 null 或 undefined 的 this 值指向全局对象
  } else {
    context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
  }
  context.fn = this  // 为这个函数作用域添加一个fn方法,并把这个函数赋给这个方法
  let result
  if (arguments[1]) {
      result = context.fn(...arguments[1])
  } else {
      result = context.fn()
  }
  Reflect.deleteProperty(context, 'fn') // 删除这个方法
  return result  // 返回结果
}

到这里应该就能理解call和apply的底层是什么样子的了,如果还不理解,还有一种方法就是你可以直接理解成:把当前的这个函数直接放在一个对象里,通过这个对象调用这个方法,那么这个函数的作用域就是这个对象,里面的this值全都指向这个对象。

  • 手写bind
    今天的难点来了,先来讲讲bind方法的原理,bind()依然是改变函数的this指向。但是它不会像call和apply一样立即执行这个函数,而是返回一个新的函数给外部。bind方法返回的函数不仅可以作为普通函数调用,还可以当作构造函数被实例化。
    首先获取bind的this并保存到一个变量中(因为在内部函数的作用域中无法访问的外部函数的this)当返回的函数被执行时,绑定 bind 的原方法将被调用,并将原方法内部作用域对象替换为绑定 bind 时传入的第一个参数,bind 的实现离不开 call 或 apply
Function.prototype.bind_ = function(context, ...rest) {
  const self = this;  // 由于内部函数的作用域无法访问外部函数作用域中的this,因此需要把this用一个变量保存
  // 创建一个新的函数变量,用来改变函数执行作用域
  return function fn(...args) {
    // this是否是fn的实例 也就是返回的fn是否通过new调用, 通过new调用返回的fn函数就绑定到self上,否则就绑定到传入的context上
    return this instanceof fn ? new self(...rest, ...args) : self.apply(context, rest.concat(args)) 
  }
}
  1. 区别
    call apply 的效果是一样的, 区别就在于
    传参:
  • call 第一个参数是要执行的作用域, 之后的参数便是传递给函数的参数
  • apply 第一个参数是要执行的作用域, 第二个是要传递给函数的参数整合的一个数组
    执行:
  • call和apply 改变了函数的this指向后马上执行该函数
  • bind 是返回改变了this指向的函数,不执行该函数
    返回值:
  • call和apply 返回fun的执行结果
  • bind 返回fun的拷贝,并指定了fun的this指向,保存了fun的参数