call、apply、bind

203 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

call、apply、bind

是什么?

这三个函数的存在意义是什么?

  • 改变函数执行时的上下文,再具体一点就是改变函数执行时的this指向
  • call/apply改变了函数的this上下文后马上执行该函数
  • bind则是返回改变了上下文后的函数,不执行该函数

语法

/**
 * thisArg(可选): foo的this指向thisArg对象
 * param1,param2(可选): 传给 foo的参数
 */
 
foo.call(thisArg, param1, param2, ...)  //  返回foo函数执行的结果
foo.apply(thisArg, [param1, param2, ...])   //  返回foo函数执行的结果
foo.bind(thisArg, param1, param2, ...)  //  返回foo函数的拷贝,并拥有指定的this值和初始参数

需要注意的是,是否严格模式对this的影响,以call为例:

  1. 严格模式下,this一定指向thisArg中的值
"use strict"
function foo() {
    console.log(this, '[][][]')
}
var obj = { a: 1 };
foo.call() // undefined
foo.call(undefined) // undefined
foo.call(null) // null
foo.call(obj)   // {a: 1}
  1. 非严格模式下,this指向thisArg对象,但是如果thisArg是null、undefined,fun中的this指向window对象
function foo() {
    console.log(this, '[][][]')
}
var obj = { a: 1 };
foo.call() // window
foo.call(undefined) // window
foo.call(null) // window
foo.call(obj)   // {a: 1}

从哪来?

这三个函数从哪来?

  • 继承自Function.prototype 正因为这三个方法在Function的实例原型上,所以调用call/apply/bind的必须是个函数
  1. call与apply的唯一区别
  • 传参不同;call传给foo函数的参数是第2~n的参数,apply的第2个参数是一个数组,里面的每一项值是foo函数的参数
  1. call/apply与bind的区别
  • call/apply改变了函数的this上下文后马上执行该函数
  • bind则是返回改变了上下文后的拷贝函数,不执行该函数

到哪去?

自己实现这3个方法

  1. 实现call,代码如下 容易理解版本,未考虑过多情况
Function.prototype.myCall = function (thisArg, ...args) {
    // 获取需要被执行的函数
    var fn = this;

    // 传入的为 null 和 undefined,就自动指向全局对象(浏览器中为window),否则转为对象
    // 对thisArg转成对象类型(防止传入的是非对象类型)
    if (thisArg === null || thisArg === undefined) {
        thisArg = window
    } else {
        thisArg = Object(thisArg)
    }

    // 改变函数的this指向
    thisArg.fn = fn
    // 调用需要执行的函数
    var result = thisArg.fn(...args)
    delete thisArg.fn

    // 返回函数执行结果
    return result
}

考虑边界稍微多点的情况

Function.prototype.myCall = function (thisArg, ...args) {
    // 传入的为 null 和 undefined,就自动指向全局对象(浏览器中为window),否则转为对象
    // 对thisArg转成对象类型(防止传入的是非对象类型)
    if (thisArg === null || thisArg === undefined) {
        thisArg = window
    } else {
        thisArg = Object(thisArg)
    }

    // 设置唯一的key,避免和传递过来的key一样
    var temporaryKey = Symbol('fn')

    // 改变函数的this指向,获取需要被执行的函数
    thisArg[temporaryKey] = this
    // 调用需要执行的函数
    var result = thisArg[temporaryKey](...args)
    delete thisArg[temporaryKey]

    // 返回函数执行结果
    return result
}
  1. 实现apply,代码如下:
Function.prototype.myApply = function (thisArg, args) {
    // 传入的为 null 和 undefined,就自动指向全局对象(浏览器中为window),否则转为对象
    // 对thisArg转成对象类型(防止传入的是非对象类型)
    if (thisArg === null || thisArg === undefined) {
        thisArg = window
    } else {
        thisArg = Object(thisArg)
    }
    // 设置唯一的key,避免和传递过来的key一样
    var temporaryKey = Symbol('fn')
    // 改变函数的this指向,获取需要被执行的函数
    thisArg[temporaryKey] = this
    // 调用需要执行的函数
    // 是否传参了
    args = args ? args : [];
    var result = thisArg[temporaryKey](...args)
    delete thisArg[temporaryKey]

    // 返回函数执行结果
    return result
}
  1. 实现bind,代码如下(未仔细考虑边界情况)
Function.prototype.myBind = function (thisArg, ...args) {
    // 传入的为 null 和 undefined,就自动指向全局对象(浏览器中为window),否则转为对象
    // 对thisArg转成对象类型(防止传入的是非对象类型)
    thisArg = [null, undefined].includes(thisArg) ? window : Object(thisArg);

    function proxyFn(...proxyArgs) {
        // 将函数放到thisArg中进行调用
        thisArg.fn = fn
        var finalArgs = [...args, ...proxyArgs]
        var result = thisArg.fn(...finalArgs)
        delete thisArg.fn
        // 返回结果
        return result
    }
    // 返回函数果
    return proxyFn
}