JavaScript 函数中的外部变量——理解 this

作者: 字母数字或汉字 | 来源:发表于2016-12-22 09:44 被阅读115次

    js 中的 this 指向确实是个坑,网上有人轰轰烈烈地讨论它,讨论 js 闭包,其实并没有那么玄学,让我们一点点剥开它的面纱。

    很多内容来自 邱桐城《JavaScript 中的 this》 的启发,基于他的文章,我写下了我的总结。

    在全局作用域下

    在浏览器环境下:

    console.log(this);
    // Window { .. }
    this === window;
    // true
    

    全局作用域下,this 指向 Window 对象,这很好理解,仍然是传统 js 的结果。

    在 node 环境下:

    console.log(this);
    // global
    this === global;
    // true
    

    全局作用域下,this 指向 global 对象。

    严格模式,在 node 环境下:

    'use strict';
    console.log(this);
    // {}
    

    遵循严格模式的规范,this 不再指向全局对象。

    函数对象作用域下

    function foo() {
        console.log(this);
    }
    foo();
    // global / Window
    

    严格模式,在 node 环境下:

    'use strict';
    function foo() {
        console.log(this);
    }
    foo();
    // undefined
    

    经过我的测试,虽然满足了规范要求,但在 node 7.2.0 下仍然出现了如上所示的不一致结果。

    对象方法作用域下

    let obj = {
        foo: function() {
            console.log(this);
        }
    };
    obj.foo();
    // { foo: [Function] }
    // obj 的值实际上是个匿名类的对象,foo 的值实际上是个匿名函数
    

    作为对象方法时,this 指向该对象。

    function func() {
        console.log(this);
    }
    let obj = {
        foo: func
    };
    obj.foo();
    // { foo: [Function func] }
    
    let foo1 = obj.foo;
    foo1();
    // global
    

    注意到:在函数体内使用的、在函数体外定义(声明)的变量,是 传引用 的。

    你可能对这个例子印象深刻:

    var foos = [];
    for (var i = 0; i < 3; ++i) {
        foos.unshift(function () {
            console.log(i);
        });
    }
    console.log(i);
    // 3
    for (var j in foos) {
        ++i;
        console.log(i);
        // 4 5 6
        foos[j]();
        // 4 5 6
    }
    

    i 变量在函数内是外部变量的引用,所以当函数外的 i 值变化了,函数内的 i 值也一同变化。

    避免这样的外部变量引用也很简单,使用 constlet 这样的新关键字是最简单的一种,它们比传统的 var 有着更严谨的定义域,使该变量无法被运行时上下文访问到而保证其值不被替换:

    const foos = [];
    for (let i = 0; i < 3; ++i) {
        foos.unshift(function () {
            console.log(i);
        });
    }
    for (let i in foos) {
        foos[i]();
    }
    // 0 1 2
    

    并不是新的关键字解决了该问题,而是新的关键字拥有更严谨的作用域。如果该变量在运行时上下文中仍能访问,那问题依旧:

    const foos = [];
    let i;
    for (i = 0; i < 3; ++i) {
        foos.unshift(function () {
            console.log(i);
        });
    }
    for (let j in foos) {
        foos[j]();
    }
    // 3 3 3
    

    看过上面的例子,你应该明白,函数内的 this 也是这样一个外部变量的引用。在传统使用 function 关键字的语法中,确实如此;但在 ES6 引入的新式 => 语法中,事情不一样了,你可以看做在函数内插入了这么一行伪代码:

    // 这不是真正的 js 代码
    const this = outer.this;
    

    试看一例:

    'use strict';
    
    const obj1 = {
        foo: function() {
            console.log(this);
            return () => {
                console.log(this);
            };
        }
    };
    const foo1 = obj1.foo();
    // { foo: [Function: foo] }
    foo1();
    // { foo: [Function: foo] }
    
    const obj2 = {
        foo: function() {
            console.log(this);
            return function () {
                console.log(this);
            };
        }
    };
    const foo2 = obj2.foo();
    // { foo: [Function: foo] }
    foo2();
    // undefined
    

    => 语法定义的函数对象,其 (const) this 指向定义时的上下文的 this,而不像 function 关键字定义的函数对象,其 (var) this 会跟随外部 this 的变化而变化。

    在构造函数对象作用域下(使用 new 关键字)

    'use strict';
    
    function A() {
        console.log(this);
    }
    
    var a = new A();
    // A {}
    console.log(a);
    // A {}
    
    var b = A();
    // undefined
    console.log(b);
    // undefined
    

    构造函数中 this 指向其构造出来的对象,但是一定不要忘记使用 new 关键字。

    call / apply / bind

    js 中的函数对象,其 prototype 中定义了如下三个函数:

    func.call(thisArg[, arg1[, arg2[, ...]]]);
    

    执行函数 func,使用第一个参数作为 this,其他参数作为 func 的实参,一一对应。

    func.apply(thisArg[, [arg1, arg2, ...]]);
    

    执行函数 func,使用第一个参数作为 this,第二个参数为数组,数组中的每个元素作为 func 的实参,一一对应。

    var foo = func.bind(thisArg[, arg1[, arg2[, ...]]]);
    

    绑定 func 的 this 和所有参数,返回一个新的函数,但不执行它。

    bind 的 this 对 new 关键字无效,但其他实参有效:

    function A(name) {
        console.log(this.name);
        this.name = name;
        console.log(this.name);
    }
    var obj = {
        name: "obj"
    };
    var B = A.bind(obj, "B");
    var b = new B('b');
    // undefined B
    console.log(obj.name);
    // obj
    

    要注意,=> 语法下的 this 不受影响,该语法下 this 视为 const 变量,不接受修改。

    相关文章

      网友评论

      本文标题:JavaScript 函数中的外部变量——理解 this

      本文链接:https://www.haomeiwen.com/subject/zdmcvttx.html