你不知道的JavaScript-this绑定

this是在运行时绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。 当一个函数被调用时,会创建一个活动记录(有时也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)。函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。

绑定规则

默认绑定

独立函数调用,无法应用其它规则时的默认规则。

如果使用严格模式,则不能将全局对象用于默认绑定,因此this会绑定到undefined。

隐式绑定

调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。如下所示,调用位置会使用obj上下文引用函数,因此可以说函数被调用时obj对象“拥有”或者“包含”它。

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

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

obj.foo(); // 2
复制代码

这种情况同样适用于foo存在于obj的原型链上,如下所示:

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

var fo = {
    foo: foo
};

// 创建一个对象obj,使之原型指向fo
var obj = Object.create(fo);
obj.a = 2;

obj.foo(); // 2
复制代码

对象属性引用链中只有上一层或者最后一层在调用位置中起作用,如下

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

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

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

obj1.obj2.foo(); // 42
复制代码

隐式丢失

一个最常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定

思考下面的代码:

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

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

var bar = obj.foo;

var a = "oops";

bar(); // oops
复制代码

虽然bar是obj.foo的一个引用,但实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

一种更出乎意料的情况发生在传入回调函数时:

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

function doFoo(fn) {
    fn();
}

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

var a = "oops";

doFoo(obj.foo); // oops
复制代码

参数传递其实是一种隐式传递,因此我们传入函数时也会被隐式赋值,所以结果和上个例子一样。

显示绑定

隐式绑定时,必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this间接绑定到这个对象上。如果我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数,该怎么办呢?答案就是callapply!

这两个方法第一个参数是一个对象,是给this准备的,接着在调用函数时将其绑定到this。因为你可以直接指定this的绑定对象,因此称之为显示绑定

调用callapply时,如果你传入了一个原始值(字符串、布尔、数字)来当做this的绑定对象,这个原始值会转化为它的对象形式。这通常被称为“装箱”。

可以重复使用的辅助函数

function foo(something) {
    return this.a + somthing;
}

// 简单的辅助绑定函数
function bind(fn, obj) {
    return function() {
        return fn.apply(obj, arguments);
    };
}

var obj = {a: 2};

var bar = bind(foo, obj);

bar(3); // 5
复制代码

由于这是一种非常常用的模式,因此在Es5中提供了内置的方法Function.prototype.bind

第三方库的许多函数,以及JavaScript语言中许多新的内置函数,都提供了一个可选的参数,通常被称为“上下文”,其作用和bind一样,确保你的回调函数可以使用指定的this。

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

var obj = {
    a: 2
};

[1,2,3].forEach(foo, obj);
复制代码

这些函数实际上就是通过call或者apply实现了显示绑定,这样你可以少些一些代码。

new绑定

使用new来调用函数,会执行下面的操作:

  1. 创建一个全新的对象。
  2. 这个新对象会被执行[[Prototype]]连接。
  3. 这个新对象会被绑定到函数调用的this。
  4. 如果函数没有返回其它对象。那么new表达式中的函数调用会自动返回这个对象。

思考下面代码:

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

var bar = new foo(2);
console.log(bar.a); // 2
复制代码

使用new来调用foo时,会创建一个新对象,并绑定到foo()调用中的this上。

优先级

  1. new绑定
  2. 显示绑定
  3. 隐式绑定
  4. 默认绑定

箭头函数

箭头函数不使用this的四种标准规则,而是根据外层作用域来决定this。

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

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

var bar = foo.call(obj1);
bar.call(obj2); // 2, 不是3
复制代码

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

转载于:https://juejin.im/post/5a7ab5c45188257a74509e15

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值