[译]浅显易懂的 this 取值规则

— By BingqiChen

翻译自文章The Simple Rules to ‘this’ in Javascript

确定什么是 this 并非难事。总的来说,通过查找函数被调用时的位置(和方法)就可以决定。遵循以下规则,按优先级排列。

规则

  1. 通过 new 关键字调用构造函数,函数内的 this 是一个全新的对象。

    function ConstructorExample() {
        console.log(this);
        this.value = 10;
        console.log(this);
    }
    new ConstructorExample();
    // -> {}
    // -> { value: 10 }
    
  2. 通过 applycallbind 调用一个函数,函数内的 this 就是传入的参数。

    function fn() {
        console.log(this);
    }
    var obj = {
        value: 5
    };
    var boundFn = fn.bind(obj);
    boundFn();     // -> { value: 5 }
    fn.call(obj);  // -> { value: 5 }
    fn.apply(obj); // -> { value: 5 }
    
  3. 如果一个函数作为对象的方法调用,即使用 . 符号调用该函数, this 是调用该函数的对象。换句话说,当 . 处于被调用函数的左边,则 this 就是左边的对象。

    var obj = {
        value: 5,
        printThis: function() {
            console.log(this);
        }
    };
    obj.printThis(); // -> { value: 5, printThis: ƒ }
    
  4. 如果函数作为普通函数调用,意味着调用方式不符合以上任意一种, this 就是全局对象。在浏览器中就是 window

    function fn() {
        console.log(this);
    }
    // If called in browser:
    fn(); // -> Window {stop: ƒ, open: ƒ, alert: ƒ, ...}
    

    *这个规则可以类比于规则3——不同之处在于这个函数自动挂载到了 window 对象上,所以可以这么理解,当我们调用 fn() 时其实调用的事 window.fn() ,所以 this 就是 window

    console.log(fn === window.fn); // -> true
    
  5. 如果符合上述多个规则,则越前面的规则会决定 this 的值。

  6. 如果函数是一个 ES2015 箭头函数,会忽略上述所有规则, this 设置为它被创建时的上下文。为了找到 this 的值,需要找到函数被创建时的环境中 this 的值。

    const obj = {
        value: 'abc',
        createArrowFn: function() {
            return () => console.log(this);
        }
    };
    const arrowFn = obj.createArrowFn();
    arrowFn(); // -> { value: 'abc', createArrowFn: ƒ }
    

    我们返回去看规则3,当我们调用 obj.createArrowFn() 时, createArrowFn 中的 this 就是 obj 对象,我们用 . 符号调用。如果我们在全局中创建一个箭头函数, this 就是 window

应用规则

下面在几个例子中应用一下我们的规则。试一下通过两种不同的方式调用函数时 this 的值。

找到应用的规则

var obj = {
    value: 'hi',
    printThis: function() {
        console.log(this);
    }
};
var print = obj.printThis;
obj.printThis(); // -> {value: "hi", printThis: ƒ}
print(); // -> Window {stop: ƒ, open: ƒ, alert: ƒ, ...}

obj.printThis() 很显然应用的是规则3——使用 . 符号。 print() 应用了规则4,在调用 print() 时,我们没有使用 newbind/call/apply. 符号,所以这里的 this 是全局对象 window

多重规则应用

如上文提到,当应用多个规则时,优先应用前面的规则。

var obj1 = {
    value: 'hi',
    print: function() {
        console.log(this);
    },
};
var obj2 = { value: 17 };

如果规则2和3同时应用,规则2优先。

obj1.print.call(obj2); // -> { value: 17 }

如果规则1和3同时应用,规则1优先。

new obj1.print(); // -> {}

关于工具库

一些 JavaScript 库有时候会在函数中主动绑定它认为最有用的内容到 this 上。比如在 JQuery中,在触发事件时 DOM 元素被绑定到了 this 上。在使用工具库时发现取值不符合上述规则时,请查看库文档。很可能使用了 bind 语法。