面试官:call、apply和bind的原理?

560 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言

这里需要些作用域和闭包this指向的知识。

正文

首先,我们需要知道他们的区别?

首先他们的第一个参数都是要绑定的this。我记忆方式是: call是打电话,所以我们需要一句一句的说,对应参数就是一个一个的传。apply是应用我想到了app,当我们填写个人信息就是把全部的信息一次性的填写上去,对应就是第二参数是一个数组。bind是绑定,跟一个人绑定起来,形成了一个新个体,第二个参数嘛,之前记住只有一个是数组其他的都是一个个传就可以了。这个是我的记忆方式,一般是不适用大家,你们大家看到这几个单词想到什么就使用这个场景进行记忆

call

从区别我们知道call是一个个传参的。

核心代码:

Function.prototype.myCall = function (ctx, ...arg) {
  let context 
  if(ctx==null || ctx==undefined) {
    context = window
  }else {
    context = Object(ctx)
  }
  let symbol = Symbol('特殊的标记')
  context[symbol] = this
  let result = context[symbol](...arg)
  delete context[symbol]
  return result
}

例子加核心代码:

Function.prototype.myCall = function (ctx, ...arg) {
  let context 
  if(ctx==null || ctx==undefined) {
    context = window
  }else {
    context = Object(ctx)
  }
  let symbol = Symbol('特殊的标记')
  context[symbol] = this
  let result = context[symbol](...arg)
  delete context[symbol]
  return result
}

function sbThis() {
  console.log(this.name);
  return this.name
}

var name = '我是window'
const obj = {
  name: '我是Obj'
}

sbThis.myCall(obj) // 我是Obj
sbThis.myCall(null) // 我是window

这里的疑问:

  1. 为什么需要对nullundefined做判断?

答: 传入null和undefined是为了不绑定this的指向,当使用bind,可以实现柯里化的效果。

  1. 为什么this就是指sbThis

隐式绑定,看段代码:

function sbThis() {
  console.log(this.name);
  return this.name
}

var name = '我是window'
const obj = {
  name: '我是Obj',
  fn: sbThis
}
obj.fn() //我是Obj

样子是类似的、就是通过obj调用的fn,所以fn中的this就是obj。前面的代码是sbThis通过调用myCall,所以在myCall中的this就是指向sbThis。

  1. 为什么使用 Symbol

答:产生出一个特殊的值,用来传入obj绑定一个特殊的属性

  1. 为什么要把this的绑定到 context

答:就是前面说的隐式绑定,跟前面的隐式绑定的例子一样产生一个属性,这个属性的this就是context,这里使用symbol就是以免与obj的属性产生冲突。所以我们在下面一行就是使用这个属性拿到返回值,然后就删除这个属性。

apply

apply和call是相似的就是第二个参数是一个数组:

Function.prototype.myApply = function (ctx) {
  let context 
  if(ctx==null || ctx==undefined) {
    context = window
  }else {
    context = Object(ctx)
  }
  let symbol = Symbol('特殊的标记')
  context[symbol] = this
  let result
  let arg = arguments[1] // 注意点
  if(arg) {  // 判断有没有传第二个参数
    if(!Array.isArray(arg)) {  // 判断是不是数组
      throw new TypeError('myApply 的第二个参数需要是数组类型')
    }else {
      let arr = Array.from(arg)
      result = context[symbol](...arg)
    }
  }else {
    result =  context[symbol]()
  }
  delete context[symbol]
  return result
}

与call对比。多来一个对第二参数数组的判断

注意点:这里的第二参数是根据arguments中获取的,因为在使用myApply的时候可能是不会传第二个参数的。

bind

bind 和 call的区别就是返回一个函数,核心代码:

Function.prototype.myBind = function (ctx,...arg) {
  const thisFn = this; // 存储源函数以及上方的params(函数参数)
  let resultFn = function (...arg2) {
    return thisFn.call(ctx,...arg,...arg2)
  }
  return resultFn
}

这里的注意点: 为什么需要把this赋值给thisFn?

答: 如果我们使用了this.call()的话,由于闭包我们把resultFn的函数给返回了出去,这里有一个赋值的操作,然后我们运行的话,this指向是看使用位置(别把this和作用域搞混了)。详情查看参考,例如:

Function.prototype.myBind = function (ctx,...arg) {
  const thisFn = this; // 存储源函数以及上方的params(函数参数)
  let resultFn = function (...arg2) {
    console.log(this);  //window
    return this.call(ctx,...arg,...arg2)
  }
  return resultFn
}

function sbThis(name) {
  console.log(name);
  console.log(this.name);
  return this.name
}

var name = '我是window'
const obj = {
  name: '我是Obj',
  fn: sbThis
}

let bindResult = sbThis.myBind(obj)
bindResult('obj')

这里就是我们执行bindResult,在全局的执行,这里的this就指向了window,所以我们需要存储一个thisFn用来指向sbThis函数。

记忆bind:

闭包加call的结合。

结论

先认识call、apply、bind的区别。
以区别的角度出发,填写对应的参数和返回值。
整体的全部原理都是以this的隐式绑定出发。

call:

  1. 本身this(被使用函数)绑定到要使用的对象的一个属性。(隐式绑定)
  2. 执行这个属性。
  3. 删除这个属性。
  4. 返回被使用函数的返回值。

apply:

  1. 以call的书写出发。
  2. 使用arguments来拿第二个参数
  3. 针对第二属性做判断。
  4. 是数组转成数组传入。
  5. 不是数组报错。
  6. 是就执行并传传参
  7. 没有第二参数直接执行属性。

bind:

  1. 返回一个函数用来控制call什么时候被使用(给出一个按钮一样)。
  2. 注意点返回出去的函数不能直接使用this,要使用bind绑定的那个this。

参考

js基础-面试官想知道你有多理解call,apply,bind?

秒懂JS的this指向

闭包