手写实现call、apply、bind

132 阅读4分钟

「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。

一、call

**语法**
function.call(thisArg, arg1, arg2, ...)    

参数说明

thisArg:可选的。在 function 函数运行时使用的 this 值。
    请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,
    则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。
arg1, arg2, ...   参数列表。

返回值:使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined

手写实现

Function.prototype.myCall = function(context) {
  // 这里的this是调用myCall的那个方法,context是我们想要this绑定到的对象
  // 我们知道函this 永远指向最后调用它的那个对象
  // 所以我们想了个办法把this赋值给context上的一个方法,此时方法去执行时,this就指向了context
  // 在非严格模式下,context为 null 或 undefined 时替换为指向全局对象
  var context = context || window
  context.fn = this
  // 拿到参数
  var args = [...arguments].slice(1)
  let result = context.fn(...args)
  delete context.fn
  return result
}

二、apply

 **语法**
func.apply(thisArg , [ argsArray])

参数说明

thisArg: 必选的。在 func 函数运行时使用的this值。
    请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,
    则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
    
argsArray: 可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 `func` 函数。
    如果该参数的值为 null 或  undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。

返回值:调用有指定this值和参数的函数的结果。

手写实现

Function.prototype.myApply = function(context) {
  var context = context || window
  context.fn = this
  let result
  // apply和call  差不多一样的,只是参数变成了一个数组
  result = arguments[1] ? context.fn(...arguments[1]) : context.fn()
  delete context.fn
  return result
}

三、bind

 **语法**
function.bind(thisArg[, arg1[, arg2[, ...]]])

参数说明

thisArg: 调用绑定函数时作为 this 参数传递给目标函数的值。 如果使用new运算符构 造绑定函数,则忽略该值。
         当使用 bind 在 setTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object。
         如果 bind 函数的参数列表为空,或者thisArg是null或undefined,执行作用域的 this 将被视为新函数的 thisArg。
 
arg1, arg2, ...  当目标函数被调用时,被预置入绑定函数的参数列表中的参数。

返回值:返回一个原函数的拷贝,并拥有指定的 this 值和初始参数。

手写实现

我们实现了我们的第一版

Function.prototype.myBind = function(context) {
  const fn = this
  // 拿到参数
  var args = [...arguments].slice(1)
  // bind需要返回一个新的方法
  const result = function () {
    // 由于返回的函数里面也可能会有参数,我们拿到他
    resultArgs = [...arguments]
    // 我们使用apply的方法去实现
    return fn.apply(context, args.concat(resultArgs));
  }

  return result
}

我们看一个原生bind的调用结果

const yJiang = {
    name: "yJiang",
    say: function (age, hobby) {
      console.log(`我叫${this.name} ${age}岁  爱好${hobby}`)
    }
}

const xiaoY = {
    name: "xiaoY"
}

const aa = yJiang.say.bind(xiaoY, 24)
const bb = new aa('唱歌')

image.png

我们发现this.nameundefined。我们知道当返回的函数aa作为构造函数去调用的时候,this指向的是实例对象。

于是,我们知道在返回的新函数里面,我们需要加一个this的判断

Function.prototype.myBind = function(context) {
  const fn = this
  // 拿到参数
  var args = [...arguments].slice(1)
  // bind需要返回一个新的方法
  const result = function () {
    // 由于返回的函数里面也可能会有参数,我们拿到他
    resultArgs = [...arguments]
    // 我们使用apply的方法去实现
    // 在这里我们需要做一个this的判断
    return fn.apply((this instanceof result ? this : context), args.concat(resultArgs));
  }

  return result
}

call、apply、bind的区别

对于三者,我们知道

  • 他们都可以改变this的指向
  • 第一个参数都是要指向的对象
  • 他们都可以添加参数。

区别的话

  • call、apply主要区别在于参数,call是一个参数列表而aplly是一个参数数组
  • bind和他们的区别在于,它返回的是一个新函数,然后再去执行调用函数,并且可以继续传递参数。