读书笔记——this绑定方式

336 阅读3分钟

调用位置——函数在代码中被调用的位置,注意:不是声明的位置。只有寻找到了调用位置,才能明确 this 到底绑定的是什么。

绑定规则

默认绑定

最常见的函数调用类型。this 指向 window 或者 undefined(开启 strict 模式)。 举个:chestnut:

function foo () {
    console.log(this.a);
}

var a = 1;
foo();  // 1

严格模式下举:chestnut:

"use strict";
function foo () {
    console.log(this.a);
}

var a = 1;
foo();  // TypeError: Cannot read property 'a' of undefined

可以看到,第一个例子中,this 指向的是 window,所以结果是1;第二个例子开启严格模式,this 指向的是 undefined,因为无法获取 a,结果报错。

隐式绑定

直接思考:

function foo () {
    console.log(this.a);
}

var obj = {
    a: 2,
    foo: foo
};

obj.foo();  // 2

当 foo 被调用时,是在 obj 对象的引用下,此时函数也就有了上下文对象,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象,this.a 和 obj.a 是一样的。再:chestnut:

function foo () {
    console.log(this.a);
}

var obj2 = {
    a: 3,
    foo: foo
};

var obj1 = {
    a: 2,
    obj2: obj2
};

obj1.obj2.foo();  // 3

隐式丢失,隐式绑定的对象丢失,就会产生隐式丢失现象。举:chestnut:

function foo () {
    console.log(this.a);
}

var obj = {
    a: 3,
    foo: foo
};

var bar = obj.foo;
bar();  // undefined

虽然 barobj.foo 的一个引用,但实际上,它引用的是 foo 函数本身。所以执行时 this 绑定的是 window 对象(非严格模式),导致结果为 undefined

回调函数丢失 this 也是非常常见的。举:chestnut:

function foo () {
    console.log(this.a);
}

var obj = {
    a: 3,
    foo: foo
};

var a = 'window a';
setTimeout(obj.foo, 10);  // 'window a'
显示绑定

通过 callapply 明目张胆地去绑定。举:chestnut:

function foo () {
    console.log(this.a);
}

var obj = {
    a: 1
};

var a = 2;
foo.call(obj);  // 1

显示绑定还是无法解决上面的隐式丢失问题。只要稍加化化妆便可——硬绑定

function foo () {
    console.log(this.a);
}

var obj = {
    a: 1
};

var bar = function () {
    foo.call(obj);
}

bar();  // 1
setTimeout(bar, 10);  // 1

bar.call(window);  // 1

从上面例子可以看到,这妆容效果简直无敌。任你倾盆大雨,也无法为我卸妆。想要这样的效果?不需要998,也不需要98,只需要 bind 函数。ES5 提供了内置的 Function.prototype.bind。举:chestnut:

function foo (something) {
    console.log(this.a, something);
    return this.a + something;
}

var obj = {
    a: 2
};

var bar = foo.bind(obj);
console.log(bar(3));  // 5
new 绑定

new 都不陌生,执行 new Fn() 时自动执行下面的操作:

  • 创建一个全新的对象。
  • 新对象会被执行 Prototype 连接。
  • 新对象会绑定到函数调用的 this。
  • 如果函数没有返回其他对象,new 表达式自动返回这个新对象。
function foo (a) {
    this.a = a;
}

var bar = new foo(2);
console.log(bar.a);  // 2

上面:chestnut:就是 new 绑定。

优先级

结论:new 绑定 > 显示绑定 > 隐式绑定 > 默认绑定

接下来进入 PK 环节:

显示绑定 VS 隐式绑定

function foo () {
    console.log(this.a);
}

var obj1 = {
    a: 1,
    foo: foo
};

var obj2 = {
    a: 2,
    foo: foo
};

// 隐式绑定
obj1.foo();  // 1
obj2.foo();  // 2

// 显示绑定
obj1.foo.call(obj2);  // 2
obj2.foo.call(obj1);  // 1

new 绑定 VS 显示绑定

function foo (something) {
    this.a = something;
}

var obj1 = {};

var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a);  // 2

var baz = new bar(3);
console.log(obj1.a);  // 2
console.log(baz.a);  // 3

例外

function foo () {
    console.log(this.a);
}

var a = 2;
foo.call(null);  // 2

从:chestnut:中可以看到,如果你把 null、undefined 作为 this 的绑定对象传入 call 、apply 或 bind 时,这些值会被忽略,应用默认绑定。

可以看到,上面例子 this 指向 window,非常不利于代码维护和安全。常见的做法是通过一个空对象 Object.create(null),举个:chestnut:

function foo (a, b) {
    console.log(a + b);
}

var obj = Object.create(null);
foo.apply(obj, [1, 2]);  // 3

var bar = foo.bind(obj, 2);
bar(3);  // 5

箭头函数

function foo () {
    return (a) => {

        // this继承foo()
        console.log(this.a);
    }
}

var obj1 = {
    a: 2
};

var obj2 = {
    a: 3
};

var bar = foo.call(obj1);
bar.call(obj2);  // 2

foo() 内部创建的箭头函数会捕获调用时 foo() 的 this。由于 foo() 的 this 绑定到 obj1,bar(引用箭头函数)的 this 也会绑定到 obj1,无法被修改。

更多好文 star 我的博客!持续更新!