bind、call、apply 的区别与实现

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

问题

bind、call、apply 有什么区别?如何实现一个 bind?

解答

作用

这三个方法都用于改变函数执行时的 this 指向。

看一个典型场景:

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

obj.say(); // martin
setTimeout(obj.say, 0); // lucy

定时器中的回调函数在全局上下文执行,this 指向 window。使用 bind 可以解决:

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

注意:如果使用 const name1 = "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,输出 {myname: "张三"} [1, 2]
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,输出 {myname: "张三"} [1, 2]
fn(1, 2); // this 指向 window

同样,第一个参数为 null 或 undefined 时指向 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

支持分次传参:

fn.bind(obj, 1)(2); // 等同于 fn.bind(obj, 1, 2)()

实现 bind

实现要点:修改 this 指向、支持分次传参、兼容 new 关键字。

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

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

    return function Fn() {
        // 如果作为构造函数调用,this 指向实例;否则指向 context
        return fn.apply(
            this instanceof Fn ? this : context, 
            args.concat(...arguments)
        ); 
    }
}

关键点

  • apply 和 call 立即执行,bind 返回新函数
  • apply 接收参数数组,call 和 bind 接收参数列表
  • bind 支持分次传参,参数会合并
  • 第一个参数为 null/undefined 时,this 指向 window(浏览器环境)
  • bind 返回的函数用 new 调用时,this 指向新实例而非绑定的对象