美文网首页
JavaScript中的this陷阱

JavaScript中的this陷阱

作者: _孙大善 | 来源:发表于2017-04-21 19:57 被阅读0次

    JavaScript来自一门健全的语言,所以你可能觉得JavaScript中的this和其他面向对象的语言如java的this一样,是指存储在实例属性中的值。事实并非如此,在JavaScript中,最好把this当成哈利波特中的博格特的背包,有着深不可测的魔力。

    JavaScript中很多时候会用到this,下面详细介绍每一种情况。在这里我想首先介绍一下宿主环境这个概念。一门语言在运行的时候,需要一个环境,叫做宿主环境。对于JavaScript,宿主环境最常见的是web浏览器,浏览器提供了一个JavaScript运行的环境,这个环境里面,需要提供一些接口,好让JavaScript引擎能够和宿主环境对接。JavaScript引擎才是真正执行JavaScript代码的地方,常见的引擎有V8(目前最快JavaScript引擎、Google生产)、JavaScript core。JavaScript引擎主要做了下面几件事情:

    • 一套与宿主环境相联系的规则;
    • JavaScript引擎内核(基本语法规范、逻辑、命令和算法);
    • 一组内置对象和API;
    • 其他约定。

    但是环境不是唯一的,也就是JavaScript不仅仅能够在浏览器里面跑,也能在其他提供了宿主环境的程序里面跑,最常见的就是nodejs。同样作为一个宿主环境,nodejs也有自己的JavaScript引擎–V8。根据官方的定义:
    Node.js is a platform built on Chrome’s JavaScript runtime for easily building fast, scalable network applications

    global this
    • 在浏览器里,在全局范围内,this等价于window对象。
    <script type="text/javascript">
            console.log(this === window); //true
    </script>
    
    • 在浏览器里,在全局范围内,用var声明一个变量和给this或者window添加属性是等价的。
    <script type="text/javascript">
            var foo = "bar";
            console.log(this.foo); //logs "bar"
            console.log(window.foo); //logs "bar"
    </script>
    
    • 如果你在声明一个变量的时候没有使用var或者let(ECMAScript 6),你就是在给全局的this添加或者改变属性值。
    <script type="text/javascript">
            foo = "bar";
            function testThis() {
                  foo = "foo";
            }
            console.log(this.foo); //logs "bar"
            testThis();
            console.log(this.foo); //logs "foo"
    </script>
    
    • 在node环境里,如果使用REPL(Read-Eval-Print Loop,简称REPL:读取-求值-输出,是一个简单的,交互式的编程环境)来执行程序,this并不是最高级的命名空间,最高级的是global.
    > this
    { ArrayBuffer: [Function: ArrayBuffer],
      Int8Array: { [Function: Int8Array] BYTES_PER_ELEMENT: 1 },
      Uint8Array: { [Function: Uint8Array] BYTES_PER_ELEMENT: 1 },
      ...
    > global === this
    true
    
    • 在node环境里,如果执行一个js脚本,在全局范围内,this以一个空对象开始作为最高级的命名空间,这个时候,它和global不是等价的。
    test.js脚本内容:
    console.log(this);
    console.log(this === global);
    REPL运行脚本:
    $ node test.js
    {}
    false
    
    • 在node环境里,在全局范围内,如果你用REPL执行一个脚本文件,用var声明一个变量并不会和在浏览器里面一样将这个变量添加给this。
    test.js:
    var foo = "bar";
    console.log(this.foo);
    $ node test.js
    undefined
    
    • 但是如果你不是用REPL执行脚本文件,而是直接执行代码,结果和在浏览器里面是一样的(神坑)。
    > var foo = "bar";
    > this.foo
    bar
    > global.foo
    bar
    
    • 在node环境里,用REPL运行脚本文件的时候,如果在声明变量的时候没有使用var或者let,这个变量会自动添加到global对象,但是不会自动添加给this对象。如果是直接执行代码,则会同时添加给global和this
    test.js
    foo = "bar";
    console.log(this.foo);
    console.log(global.foo);
    $ node test.js
    undefined
    bar
    

    上面的八种情况可能大家已经绕晕了,总结起来就是:在浏览器里面this是老大,它等价于window对象,如果你声明一些全局变量(不管在任何地方),这些变量都会作为this的属性。在node里面,有两种执行JavaScript代码的方式,一种是直接执行写好的JavaScript文件,另外一种是直接在里面执行一行行代码。对于直接运行一行行JavaScript代码的方式,global才是老大,this和它是等价的。在这种情况下,和浏览器比较相似,也就是声明一些全局变量会自动添加给老大global,顺带也会添加给this。但是在node里面直接脚本文件就不一样了,你声明的全局变量不会自动添加到this,但是会添加到global对象。所以相同点是,在全局范围内,全局变量终究是属于老大的。

    function this

    无论是在浏览器环境还是node环境, 除了在DOM事件处理程序里或者给出了thisArg(接下来会讲到)外,如果不是用new调用,在函数里面使用this都是指代全局范围的this。

    <script type="text/javascript">
          foo = "bar"; 
          function testThis() {
               this.foo = "foo";
          }
          console.log(this.foo); //logs "bar"
          testThis();
          console.log(this.foo); //logs "foo"
    </script>
    
    test.js
     
    foo = "bar";
    function testThis () {
        this.foo = "foo";
    }
    console.log(global.foo);
    testThis();
    console.log(global.foo);
    $ node test.js
    bar
    foo
    
    • 除非你使用严格模式,这时候this就会变成undefined。
    <script type="text/javascript">
          foo = "bar";
          function testThis() {
              "use strict";
              this.foo = "foo";
          }
          console.log(this.foo); //logs "bar"
          testThis();  //Uncaught TypeError: Cannot set property 'foo' of undefined 
    </script>
    

    我更喜欢把新的值称作一个实例。

    函数里面的this其实相对比较好理解,如果我们在一个函数里面使用this,需要注意的就是我们调用函数的方式,如果是正常的方式调用函数,this指代全局的this,如果我们加一个new,这个函数就变成了一个构造函数,我们就创建了一个实例,this指代这个实例,这个和其他面向对象的语言很像。另外,写JavaScript很常做的一件事就是绑定事件处理程序,也就是诸如button.addEventListener(‘click’, fn, false)之类的,如果在fn里面需要使用this,this指代事件处理程序对应的对象,也就是button。

    时间有限,未完待续。。。

    相关文章

      网友评论

          本文标题:JavaScript中的this陷阱

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