JavaScript的this指向以及规则

JavaScript的this指向规则

函数中this的指向是根据不同的绑定规则进行指向的,本章内容将从默认绑定,隐含绑定,明确绑定,new绑定四种方法来描述,如果你想快速了解this指向,并非系统了解可滑到底部看总结。

默认绑定(Default Binding)

在这里 ,全局定义了a,var a = 2,在没有任何修饰或者说函数内执行,那么this就指向全局,打印出了a=2,如果使用严格模式
,则为undefined

var a = 2;
function foo() {
    
    
    console.log( this.a );
}
foo(); // 2
// 严格模式
function foo1() {
    
    
    "use strict";
    console.log( this.a );
}
foo1(); // TypeError: `this` is `undefined`

一个微妙但是重要的细节是:即便所有的 this 绑定规则都是完全基于调用点的,但如果 foo() 的 内容 没有在 strict mode
下执行,对于 默认绑定 来说全局对象是 唯一 合法的;foo() 的调用点的 strict mode 状态与此无关。

function foo() {
    
    
    console.log( this.a );
}
var a = 2;
(function(){
    
    
    "use strict";
    foo(); // 2
})();

注意: 在你的代码中故意混用 strict mode 和非 strict mode 通常是让人皱眉头的。你的程序整体可能应当不是 Strict 就是 非 Strict。然而,有时你可能会引用与你的 Strict 模式不同的第三方包,所以对这些微妙的兼容性细节要多加小心

隐含绑定(Implicit Binding)

考虑函数的调用点是否有一个环境对象(context object),也称为拥有者(owning)或容器(containing)对象,虽然这些名词可能有些误导人。

function foo() {
    
    
    console.log( this.a );
}
var obj = {
    
    
    a: 2,
    foo: foo
};
obj.foo(); // 2

隐含丢失(Implicitly Lost)

this 绑定最常让人沮丧的事情之一,就是当一个 隐含绑定 丢失了它的绑定,这通常意味着它会退回到 默认绑定, 根据 strict mode 的状态,其结果不是全局对象就是 undefined。

function foo() {
    
    
    console.log( this.a );
}
var obj = {
    
    
    a: 2,
    foo: foo
};
var bar = obj.foo; // 函数引用!
// 相当于 bar = foo()  ,
var a = "oops, global"; // `a` 也是一个全局对象的属性
bar(); // "oops, global"

上面代码 的调用点或者说是执行环境是全局,所以this指向了全局的a
在看代码

function foo() {
    
    
    console.log( this.a );
}
function doFoo(fn) {
    
    
    // `fn` 只不过 `foo` 的另一个引用
    fn(); // <-- 调用点!
}
var obj = {
    
    
    a: 2,
    foo: foo
};
var a = "oops, global"; // `a` 也是一个全局对象的属性
doFoo( obj.foo ); // "oops, global"

其实这里 doFoo的调用点或者是执行环境,是全局,传参是obj的foo属性,属性对应的是foo()方法,foo方法执行console.log(this.a),this指向的是调用点,那就是全局中的a.
setTimeout调用是一样的

function foo() {
    
    
    console.log( this.a );
}
var obj = {
    
    
    a: 2,
    foo: foo
};
var a = "oops, global"; // `a` 也是一个全局对象的属性
setTimeout( obj.foo, 100 ); // "oops, global"

明确绑定(Explicit Binding)

如果你想强制一个函数调用使用某个特定对象作为 this 绑定,而不在这个对象上放置一个函数引用属性呢?,使用call(…) 和
apply(…) 方法。

function foo() {
    
    
    console.log( this.a );
}
var obj = {
    
    
    a: 2
};
foo.call( obj ); // 2

通过 foo.call(…) 使用 明确绑定 来调用 foo,允许我们强制函数的 this 指向 obj。

这里补充一下call和apply的参数

当我们通过 call 和 apply 来this的指向时,不传任何参数,则默认为将this指向修改为 windows
有参数时,this 指向第一个参数:
当需要传递参数时,call可以直接写多个参数,apply需要用数组方式传递:

console.log(person.sayHobby.call(person1, 'swimming', 'hiking')); // I'm Coy, I like swimming and hiking.
console.log(person.sayHobby.apply(person1, ['swimming', 'hiking'])); // I'm Coy, I like swimming and hiking.

硬绑定(Hard Binding)

function foo() {
    
    
    console.log( this.a );
}
var obj = {
    
    
    a: 2
};
var bar = function() {
    
    
    foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// `bar` 将 `foo` 的 `this` 硬绑定到 `obj`
// 所以它不可以被覆盖
bar.call( window ); // 2

我们来看看这个变种是如何工作的。我们创建了一个函数 bar(),在它的内部手动调用 foo.call(obj),由此强制 this 绑定到obj 并调用 foo。无论你过后怎样调用函数 bar,它总是手动使用 obj 调用 foo。这种绑定即明确又坚定,所以我们称之为硬绑定(hard binding)

用 硬绑定 将一个函数包装起来的最典型的方法,是为所有传入的参数和传出的返回值创建一个通道:

function foo(something) {
    
    
    console.log( this.a, something );
    return this.a + something;
}
var obj = {
    
    
    a: 2
};
var bar = function() {
    
    
    return foo.apply( obj, arguments );
};
var b = bar( 3 ); // 2 3
console.log( b ); // 5
另一种表达这种模式的方法是创建一个可复用的帮助函数:

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

// 简单的 bind 帮助函数

function bind(fn, obj) {
    
    
    return function() {
    
    
        return fn.apply( obj, arguments );
    };
}
var obj = {
    
    
    a: 2
};
var bar = bind( foo, obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5

由于 硬绑定 是一个如此常用的模式,它已作为 ES5 的内建工具提供:Function.prototype.bind,像这样使用:

function foo(something) {
    
    
    console.log( this.a, something );
    return this.a + something;
}
var obj = {
    
    
    a: 2
};
var bar = foo.bind( obj );

var b = bar( 3 ); // 2 3
console.log( b ); // 5

bind(…) 返回一个硬编码的新函数,它使用你指定的 this 环境来调用原本的函数。
注意: 在 ES6 中,bind(…) 生成的硬绑定函数有一个名为 .name 的属性,它源自于原始的 目标函数(target function)。举例来说:bar = foo.bind(…) 应该会有一个 bar.name 属性,它的值为 “bound foo”,这个值应当会显示在调用栈轨迹的函数调用名称中。

API 调用的“环境”

许多库中的函数,和许多在 JavaScript 语言以及宿主环境中的内建函数,都提供一个可选参数,通常称为“环境(context)”,这种设计作为一种替代方案来确保你的回调函数使用特定的 this 而不必非得使用 bind(…)。

举例来说:

function foo(el) {
    
    
    console.log( el, this.id );
}

var obj = {
    
    
    id: "awesome"
};

// 使用 obj 作为 this 来调用 foo(..)
[1, 2, 3].forEach( foo, obj ); // 1 awesome 2 awesome 3 awesome
从内部来说,几乎可以确定这种类型的函数是通过 call(…) 或 apply(…) 来使用 明确绑定 以节省你的麻烦。

new 绑定(new Binding)

这里回顾一下new 的时候做了什么

  1. 一个全新的对象会凭空创建(就是被构建)
  2. 这个新构建的对象会被接入原形链([[Prototype]]-linked)
  3. 这个新构建的对象被设置为函数调用的 this 绑定
  4. 除非函数返回一个它自己的其他 对象,否则这个被 new 调用的函数将 自动返回这个新构建的对象。
    考虑这段代码:
function foo(a) {
    
    
    this.a = a;
}
var bar = new foo( 2 );
console.log( bar.a ); // 2

通过在前面使用 new 来调用 foo(…),我们构建了一个新的对象并把这个新对象作为 foo(…) 调用的 this。 new
是函数调用可以绑定 this 的最后一种方式,我们称之为 new 绑定(new binding)。

几种this指向绑定的优先顺序

很显然,默认绑定 在四种规则中优先权最低的,其次new 绑定 的优先级要高于隐含绑定,明确绑定也是高于隐含绑定,拿着两者哪个高一点呢?注意:new 和 call/apply 不能同时使用,硬绑定(明确绑定的一种)的优先级要比 new 绑定高,new可以覆盖应绑定.

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  引用的baz被覆盖了

判断 this

现在,我们可以按照优先顺序来总结一下从函数调用的调用点来判定 this 的规则了。按照这个顺序来问问题,然后在第一个规则适用的地方停下。

  1. 函数是通过 new 被调用的吗(new 绑定)?如果是,this 就是新构建的对象。 var bar = new foo()
  2. 函数是通过 call 或 apply 被调用(明确绑定),甚至是隐藏在 bind 硬绑定 之中吗?如果是,this 就是那个被明确指定的对象。 var bar = foo.call( obj2 )
  3. 函数是通过环境对象(也称为拥有者或容器对象)被调用的吗(隐含绑定)?如果是,this 就是那个环境对象。 var bar = obj1.foo()
  4. 否则,使用默认的 this(默认绑定)。如果在 strict mode 下,就是 undefined,否则是 global 对象。 var bar = foo()

以上,就是理解对于普通的函数调用来说的 this 绑定规则 所需的全部。是的……几乎是全部。

复习

为执行中的函数判定 this 绑定需要找到这个函数的直接调用点。找到之后,四种规则将会以这种优先顺序施用于调用点:

  1. 通过 new 调用?使用新构建的对象。
  2. 通过 call 或 apply(或 bind)调用?使用指定的对象。
  3. 通过持有调用的环境对象调用?使用那个环境对象。
  4. 默认:strict mode 下是 undefined,否则就是全局对象。

小心偶然或不经意的 默认绑定 规则调用。如果你想“安全”地忽略 this 绑定,一个像 ø = Object.create(null)
这样的“DMZ”对象是一个很好的占位值,以保护 global 对象不受意外的副作用影响。

与这四种绑定规则不同,ES6 的箭头方法使用词法作用域来决定 this 绑定,这意味着它们采用封闭他们的函数调用作为 this
绑定(无论它是什么)。它们实质上是 ES6 之前的 self = this 代码的语法替代品。

文章学习于https://www.javascriptc.com/books/you-dont-know-js/this-object-prototypes/ch2.html

猜你喜欢

转载自blog.csdn.net/weixin_44000173/article/details/125023887