bind、call、apply 的区别与实现

理解三种改变 this 指向的方法,并手写实现 bind

问题

bind、call、apply 都能改变函数的 this 指向,它们有什么区别?如何手写实现 bind?

解答

为什么需要改变 this 指向

看一个常见的场景:

var name = "lucy";
var obj = {
    name: "martin",
    say: function () {
        console.log(this.name);
    }
};

obj.say(); // martin,this 指向 obj
setTimeout(obj.say, 0); // lucy,this 指向 window

定时器中的回调函数在全局上下文执行,this 指向了 window。如果想让 this 指向 obj,就需要改变 this 指向:

setTimeout(obj.say.bind(obj), 0); // martin,this 指向 obj

注意:如果使用 const name = "lucy",setTimeout 会输出 undefined,因为 const/let 声明的变量不会挂载到 window 上,只有 var 会。

apply

接收两个参数:this 指向和参数数组,立即执行函数。

function fn(...args) {
    console.log(this, args);
}
let obj = {
    myname: "张三"
}

fn.apply(obj, [1, 2]); // this 指向 obj,参数必须是数组
fn(1, 2); // this 指向 window

当第一个参数为 null 或 undefined 时,this 指向 window(浏览器环境):

fn.apply(null, [1, 2]); // this 指向 window
fn.apply(undefined, [1, 2]); // this 指向 window

call

接收参数列表,立即执行函数。

function fn(...args) {
    console.log(this, args);
}
let obj = {
    myname: "张三"
}

fn.call(obj, 1, 2); // this 指向 obj,参数是列表形式
fn(1, 2); // this 指向 window

同样,第一个参数为 null 或 undefined 时,this 指向 window。

bind

接收参数列表,返回一个新函数,不立即执行。参数可以分多次传入。

function fn(...args) {
    console.log(this, args);
}
let obj = {
    myname: "张三"
}

const bindFn = fn.bind(obj); // 返回新函数,不立即执行
bindFn(1, 2); // this 指向 obj
fn(1, 2); // this 指向 window

手写实现 bind

bind 的实现需要考虑三点:修改 this 指向、动态传递参数、兼容 new 关键字。

支持两种传参方式:

// 方式一:只在 bind 中传参
fn.bind(obj, 1, 2)()

// 方式二:bind 和返回函数中都传参
fn.bind(obj, 1)(2)

完整实现:

Function.prototype.myBind = function (context) {
    // 判断调用对象是否为函数
    if (typeof this !== "function") {
        throw new TypeError("Error");
    }

    // 获取参数
    const args = [...arguments].slice(1),
          fn = this;

    return function Fn() {
        // 根据调用方式,传入不同绑定值
        return fn.apply(
            this instanceof Fn ? new fn(...arguments) : context, 
            args.concat(...arguments)
        ); 
    }
}

关键点

  • apply 和 call 立即执行,bind 返回新函数
  • apply 接收参数数组,call 和 bind 接收参数列表
  • bind 可以分多次传参,参数会合并
  • 第一个参数为 null/undefined 时,this 指向 window(浏览器环境)
  • 实现 bind 需要兼容 new 关键字,判断返回函数是否被 new 调用