手写call、apply、bind

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的参数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值