如果判定 this 又混乱了,就从头再读一遍……

this call apply bind

this

调用点

this 是完全根据调用点(函数是如何被调用的)而绑定的。

function baz() {
    // 调用栈是: `baz`
    // 我们的调用点是 global scope(全局作用域)

    console.log( "baz" );
    bar(); // <-- `bar` 的调用点
}

function bar() {
    // 调用栈是: `baz` -> `bar`
    // 我们的调用点位于 `baz`

    console.log( "bar" );
    foo(); // <-- `foo` 的 call-site
}

function foo() {
    // 调用栈是: `baz` -> `bar` -> `foo`
    // 我们的调用点位于 `bar`

    console.log( "foo" );
}

baz(); // <-- `baz` 的调用点

可以通过 debugger 来获取调用链

this的四种规则

1. 默认绑定

对此方法调用的 this 实施了 默认绑定,所以使 this 指向了全局对象。

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

var a = 2;

foo(); // 2

严格模式下,

function foo() {
    "use strict";

    console.log( this.a );
}

var a = 2;

foo(); // TypeError: `this` is `undefined`

2. 隐含绑定

调用点是否有一个环境对象?

调用点 使用 obj 环境来 引用 函数,所以你 可以说 obj 对象在函数被调用的时间点上“拥有”或“包含”这个 函数引用

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

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

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

3. 隐含丢失

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

bar 似乎是 obj.foo 的引用,但实际上它只是另一个 foo 本身的引用而已。

另外,起作用的调用点是 bar(),因此this指向全局对象。

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

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

var bar = obj.foo; // 函数引用!

var a = "oops, global"; // `a` 也是一个全局对象的属性

bar(); // "oops, global"
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"

3. 明确绑定

函数拥有 call(..)apply(..) 方法。

它们接收的第一个参数都是一个用于 this 的对象,之后使用这个指定的 this 来调用函数。

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

var obj = {
    a: 2
};

foo.call( obj ); // 2

如果你传递一个简单基本类型值(stringboolean,或 number 类型)作为 this 绑定,那么这个基本类型值会被包装在它的对象类型中(分别是 new String(..)new Boolean(..),或 new Number(..))。这通常称为“封箱(boxing)”。

4. 硬绑定

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

实现bind:

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

各种绑定的优先级

明确绑定 的优先级高于 隐含绑定:

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

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

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

obj1.foo(); // 2
obj2.foo(); // 3

obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2

new绑定 优先级高于 隐含绑定

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

var obj1 = {
    foo: foo
};

var obj2 = {};

obj1.foo( 2 );
console.log( obj1.a ); // 2

obj1.foo.call( obj2, 3 );
console.log( obj2.a ); // 3

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

按照优先级顺序 判定this

  1. 函数是通过 new 被调用的吗(new 绑定)?如果是,this 就是新构建的对象。

    var bar = new foo()
    
  2. 函数是通过 callapply 被调用(明确绑定),甚至是隐藏在 bind 硬绑定 之中吗?如果是,this 就是那个被明确指定的对象。

    var bar = foo.call( obj2 )
    
  3. 函数是通过环境对象(也称为拥有者或容器对象)被调用的吗(隐含绑定)?如果是,this 就是那个环境对象。

    var bar = obj1.foo()
    
  4. 否则,使用默认的 this默认绑定)。如果在 strict mode 下,就是 undefined,否则是 global 对象。

    var bar = foo()
    
    

例外

  1. 传递 nullundefined 作为 callapplybindthis 绑定参数,那么这些值会被忽略掉,取而代之的是 默认绑定 规则将适用于这个调用。

    利用

posted @ 2021-02-17 15:23  团叽  阅读(44)  评论(0编辑  收藏  举报