• 上代码:

    function foo(a) {
        this.a = a;
    }
    var a = 3;
    var bar = new foo(2);
    console.log(bar.a); // 2
    

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

    3.优先级。

    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
    

    可以看到,显式绑定优先级更高,也就是说在判断时应当先考虑是否可以应用显式绑定。

    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
    

    可以看到 new 绑定比隐式绑定优先级高。

    new 和 call/apply 无法一起使用,因此无法通过 new foo.call(obj1) 来直接进行测试。但是我们可以使用硬绑定来测试它俩的优先级。

    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
    

    可以看到,new 修改了硬绑定(到 obj1 的)调用 bar(..) 中的 this。因为使用了new 绑定,我们得到了一个名字为 baz 的新对象,并且 baz.a 的值是 3。

    总结

    现在我们可以根据优先级来判断函数在某个调用位置应用的是哪条规则。可以按照下面的顺序来进行判断:

    1.函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。

    var bar = new foo()
    

    2.函数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是指定的对象

    var bar = foo.call(obj2)
    

    3.函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上下文对象。

    var bar = obj1.foo()
    

    4.如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到全局对象。

    var bar = foo()
    

    4.箭头函数

    之前介绍的四条规则已经可以包含所有正常的函数。但是 ES6 中介绍了一种无法使用这些规则的特殊函数类型:箭头函数。箭头函数并不是使用 function 关键字定义的,而是使用被称为“胖箭头”的操作符 => 定义的。箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定 this。

    function foo() {
         // 返回一个箭头函数
         return (a) => {
         //this 继承自 foo()
         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 也不行!)

    箭头函数最常用于回调函数中,例如事件处理器或者定时器:

    function foo() {
         setTimeout(() => {
         // 这里的 this 在此法上继承自 foo()
         console.log( this.a );
     },100);
    }
    var obj = {
         a:2
    };
    foo.call( obj ); // 2
    

    箭头函数可以像 bind(..) 一样确保函数的 this 被绑定到指定对象,此外,其重要性还体现在它用更常见的词法作用域取代了传统的 this 机制。实际上,在 ES6 之前我们就已经在使用一种几乎和箭头函数完全一样的模式。

    function foo() {
         var self = this; // lexical capture of this 
         setTimeout( function(){
         console.log( self.a );
         }, 100 );
    }
    var obj = {
         a: 2
    };
    foo.call( obj ); // 2
    

    虽然 self = this 和箭头函数看起来都可以取代 bind(..),但是从本质上来说,它们想替代的是 this 机制。

    参考资料

    你好!我是 JHCan333,公众号:爱生活的前端狗的作者。公众号专注前端工程师方向,包括但不限于技术提高、职业规划、生活品质、个人理财等方面,会持续发布优质文章,从各个方面提升前端开发的幸福感。关注公众号,我们一起向前走!

    02-12 18:35