JavaScript中的this

本章借鉴《你不知道的javascript(上卷)》

1、关于this

this 关键字是 JavaScript 中最复杂的机制之一。它是一个很特别的关键字,被自动定义在

所有函数的作用域中。但是即使是非常有经验的 JavaScript 开发者也很难说清它到底指向

什么。

任何足够先进的技术都和魔法无异。 ——Arthur C. Clarke

实际上,JavaScript 中 this 的机制并没有那么先进,但是开发者往往会把理解过程复杂化,

毫无疑问,在缺乏清晰认识的情况下,this 对你来说完全就是一种魔法。

1.1、为什么要用this
  • 使用this和显示传入代码:

    • 使用this

      function identify() {
      	return this.name.toUpperCase(); 
      }
      function speak() {
      	var greeting = "Hello, I'm " + identify.call( this );
        console.log( greeting ); 
      }
      var me = { name: "Kyle" };
      var you = { name: "Reader" };
      
      
      speak.call( me );  // Hello, I'm KYLE
      speak.call( you ); // Hello, I'm READER
      
      
    • 显式传入

      function identify(context) {
      	return context.name.toUpperCase(); 
      }
      function speak(context) {
      	var greeting = "Hello, I'm " + identify( context ); 
        console.log( greeting ); 
      }
      var me = { name: "Kyle" };
      var you = { name: "Reader" };
      
      
      
      speak( me );  // Hello, I'm KYLE
      speak( you ); // Hello, I'm READER
      
    • 阅读两端代码可以看出

      • 使用this,api设计会更简洁并且易于复用
      • 阅读性的话,觉得显示传入并没有太大优势
      • 当我们代码更加复杂的时候,显式传递上下文对象会让代码变得越来越混乱,使用 this 则不会这样。(想想我们的vue2代码如果使用显示传入会变成什么样!!!)
1.2、误解
  • 1)、指向自身

    • 人们很容易把 this 理解成指向函数自身,这个推断从英语的语法角度来说是说得通的。

    • 那么为什么需要从函数内部引用函数自身呢?常见的原因是递归(从函数内部调用这个函

      数)或者可以写一个在第一次被调用后自己解除绑定的事件处理器。

    • 举个例子,this并不指向函数本身

      function foo() { 
        console.log(this)
      }
      
      foo() // Window
      
    • 修正this指向本身

      function foo() { 
        console.log(this)
      }
      
      foo.call(foo) 
      // ƒ foo() { 
      //   	 console.log(this)
      //	 } 
      
    • 这里只是举例,稍后会详解this绑定规则,到时候再看这里会更清晰

  • 2)、它的作用域

    • 第二种常见的误解是,this 指向函数的作用域。这个问题有点复杂,因为在某种情况下它

      是正确的,但是在其他情况下它却是错误的。

    • 阅读下面代码辅助理解下

      // this 
      var a = 2;
      function foo() {
      	this.bar(); 
      }
      function bar() {
        console.log( this.a ); 
      }
      
      foo(); // 2
      
      // 词法作用域
      var a = 2;
      function foo() {
      	bar(); 
      }
      function bar() { 
        console.log( a ); 
      }
      
      foo(); // 2
      
      • 上述两段代码,结果相同。也不解读,看完this绑定后可以回来缕一缕。
      // this 
      var a = 6
      function foo() {
        var a = 2;
        function baz(){
          console.log( this.a ); 
        }
        baz()
      }
      foo(); // 6
      
      // 词法作用域
      var a = 6
      function foo() {
        var a = 2;
        function baz(){
          console.log( a ); 
        }
        baz()
      }
      foo(); // 2
      
      • 上面两段代码,结果不同。还是不解读,看完this绑定后可以回来缕一缕。
1.3、总结
  • this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
  • this 既不指向函数自身也不指向函数的词法作用域,你也许被这样的解释误导过,但其实它们都是错误的。
  • this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。

2、this全面解析

2.1、调用位置
function baz() { 
  // 当前调用栈是:baz 
  // 因此,当前调用位置是全局作用域 
  console.log( "baz" ); 
  bar(); // <-- bar 的调用位置 
}

function bar() {
  // 当前调用栈是 baz -> bar 
  // 因此,当前调用位置在 baz 中 
  console.log( "bar" ); 
  foo(); // <-- foo 的调用位置 
}

function foo() { 
  // 当前调用栈是 baz -> bar -> foo 
  // 因此,当前调用位置在 bar 中 
  console.log( "foo" ); 
}

baz(); // <-- baz 的调用位置

你可以把调用栈想象成一个函数调用链,就像我们在前面代码段的注释中所

写的一样。但是这种方法非常麻烦并且容易出错。另一个查看调用栈的方法

是使用浏览器的调试工具。绝大多数现代桌面浏览器都内置了开发者工具,

其中包含 JavaScript 调试器。就本例来说,你可以在工具中给 foo() 函数的

第一行代码设置一个断点,或者直接在第一行代码之前插入一条 debugger;

语句。运行代码时,调试器会在那个位置暂停,同时会展示当前位置的函数

调用列表,这就是你的调用栈。因此,如果你想要分析 this 的绑定,使用开

发者工具得到调用栈,然后找到栈中第二个元素,这就是真正的调用位置。

2.2、绑定规则
  • 默认绑定、隐式绑定、显示绑定、new绑定

  • 1)、默认绑定

    • 函数调用时this会绑定全局对象。
    • 但严格模式下,this会绑定到undefined
  • 2)、隐式绑定

    • 当函数作为某个对象的属性调用时,隐式绑定规则会把函数中的this绑定到调用当前函数的对象上。

    • 只有直接调用函数的对象,才会影响this。

      function foo() { 
        console.log( this.a ); 
      }
      
      var obj2 = { 
        a: 42, 
        foo: foo 
      };
      
      var obj1 = { 
        a: 2, 
        obj2: obj2 
      };
      
      obj1.obj2.foo(); // 42
      
  • 2.1)、隐式丢失

    • 一个最常见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默 认绑定,从而把 this 绑定到全局对象或者 undefined 上,取决于是否是严格模式。

      • 赋值给其他变量
      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)、显式绑定

    • 借助callapplybind 将函数调用时的this绑定到指定对象上。

      function foo(){
          console.log(this.a)
      }
      var obj = {
          a:'obj'
      }
      // call
      foo.call(obj); // obj
      // apply
      foo.apply(obj); // obj
      // bind
      foo.bind(obj)() // obj
      
  • 4)、new绑定

    • 使用new操作符调用函数时,会创建一个新对象,并把这个对象绑定到被调用函数的this。

      function foo(a) {
      	this.a = a; 
      }
      
      var bar = new foo(2); 
      console.log( bar.a ); // 2
      
2.3、优先级
  • 默认绑定 < 隐式绑定 < 显式绑定 < new绑定

  • 默认绑定最低。😄

  • 隐式绑定 < 显式绑定

    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
    };
    
    obj1.foo( 2 ); 
    console.log( obj1.a ); // 2 
    
    
    var bar = new obj1.foo( 4 ); 
    console.log( obj1.a ); // 2 
    console.log( bar.a ); // 4
    
  • 显式绑定 < 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
    
2.4、箭头函数
  • 箭头函数并不是使用 function 关键字定义的,而是使用被称为“胖箭头”的操作符 => 定 义的。箭头函数不使this 的四种标准规则,而是根据外层(函数或者全局)作用域来决 定 this。

  • 箭头函数的绑定无法被修改。(new 也不 行!)

2.5、总结

如果要判断一个运行中函数的 this 绑定,就需要找到这个函数的直接调用位置。找到之后

就可以顺序应用下面这四条规则来判断 this 的绑定对象。

1、由 new 调用?绑定到新创建的对象。

2、由 call 或者 apply(或者 bind)调用?绑定到指定的对象。

3、由上下文对象调用?绑定到那个上下文对象。

4、默认:在严格模式下绑定到 undefined,否则绑定到全局对象。

ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定

this,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这

其实和 ES6 之前代码中的 self = this 机制一样。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值