在JavaScript中,this 的概念比较复杂。除了在面向对象编程中,this 还是随处可用的。这篇文章介绍了this 的工作原理,它会造成什么样的问题以及this 的相关例子。 要根据this 所在的位置来理解它,情况大概可以分为3种:

  1. 在函数中:this 通常是一个隐含的参数。
  2. 在函数外(顶级作用域中):在浏览器中this 指的是全局对象;在Node.js中指的是模块(module)的导出(exports)。
  3. 传递到eval()中的字符串:如果eval()是被直接调用的,this 指的是当前对象;如果eval()是被间接调用的,this 就是指全局对象。

对这几个分类,我们做了相应的测试:

  1. 在函数中的this

     函数基本可以代表JS中所有可被调用的结构,所以这是也最常见的使用this 的场景,而函数又能被子分为下列三种角色:

    • 实函数
    • 构造器
    • 方法

    1.1  在实函数中的this

    在实函数中,this 的值是取决于它所处的上下文的模式

    • Sloppy模式:this 指的是全局对象(在浏览器中就是window)。

      1
      2
      3
      4
      function sloppyFunc() {
          console.log(this === window); // true
      }
      sloppyFunc();
    • Strict模式:this 的值是undefined。
      1
      2
      3
      4
      5
      function strictFunc() {
          'use strict';
          console.log(this === undefined); // true
      }
      strictFunc();

      this 是函数的隐含参数,所以它的值总是相同的。不过你是可以通过使用call()或者apply()的方法显示地定义好this的值的。

      1
      2
      3
      4
      5
      6
      7
      function func(arg1, arg2) {
          console.log(this); // 1
          console.log(arg1); // 2
          console.log(arg2); // 3
      }
      func.call(1, 2, 3); // (this, arg1, arg2)
      func.apply(1, [2, 3]); // (this, arrayWithArgs)

      1.2  构造器中的this

      你可以通过new 将一个函数当做一个构造器来使用。new 操作创建了一个新的对象,并将这个对象通过this 传入构造器中。

      1
      2
      3
      4
      5
      6
      var savedThis;
      function Constr() {
          savedThis = this;
      }
      var inst = new Constr();
      console.log(savedThis === inst); // true

      JS中new 操作的实现原理大概如下面的代码所示(更准确的实现请看这里,这个实现也比较复杂一些):

      1
      2
      3
      4
      5
      function newOperator(Constr, arrayWithArgs) {
          var thisValue = Object.create(Constr.prototype);
          Constr.apply(thisValue, arrayWithArgs);
          return thisValue;
      }

      1.3  方法中的this

      在方法中this 的用法更倾向于传统的面向对象语言:this 指向的接收方,也就是包含有这个方法的对象。

      1
      2
      3
      4
      5
      6
      var obj = {
          method: function () {
              console.log(this === obj); // true
          }
      }
      obj.method();
  2.  作用域中的this
    在浏览器中,作用域就是全局作用域,this 指的就是这个全局对象(就像window):
    1
    2
    3
    <script>
        console.log(this === window); // true
    </script>

    在Node.js中,你通常都是在module中执行函数的。因此,顶级作用域是个很特别的模块作用域(module scope):

    1
    2
    3
    4
    5
    6
    7
    // `global` (not `window`) refers to global object:
    console.log(Math === global.Math); // true
     
    // `this` doesn’t refer to the global object:
    console.log(this !== global); // true
    // `this` refers to a module’s exports:
    console.log(this === module.exports); // true
  3. eval()中的this
    eval()可以被直接(通过调用这个函数名’eval’)或者间接(通过别的方式调用,比如call())地调用。要了解更多细节,请看这里
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // Real functions
    function sloppyFunc() {
        console.log(eval('this') === window); // true
    }
    sloppyFunc();
     
    function strictFunc() {
        'use strict';
        console.log(eval('this') === undefined); // true
    }
    strictFunc();
     
    // Constructors
    var savedThis;
    function Constr() {
        savedThis = eval('this');
    }
    var inst = new Constr();
    console.log(savedThis === inst); // true
     
    // Methods
    var obj = {
        method: function () {
            console.log(eval('this') === obj); // true
        }
    }
    obj.method();
  4. this有关的陷阱
    你要小心下面将介绍的3个和this 有关的陷阱。要注意,在下面的例子中,使用Strict模式(strict mode)都能提高代码的安全性。由于在实函数中,this 的值是undefined,当出现问题的时候,你会得到警告。
    4.1  忘记使用new
    如果你不是使用new来调用构造器,那其实你就是在使用一个实函数。因此this就不会是你预期的值。在Sloppy模式中,this 指向的就是window 而你将会创建全局变量:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function Point(x, y) {
        this.x = x;
        this.y = y;
    }
    var p = Point(7, 5); // we forgot new!
    console.log(p === undefined); // true
     
    // Global variables have been created:
    console.log(x); // 7
    console.log(y); // 5

    不过如果使用的是strict模式,那你还是会得到警告(this===undefined):

    1
    2
    3
    4
    5
    6
    7
    function Point(x, y) {
        'use strict';
        this.x = x;
        this.y = y;
    }
    var p = Point(7, 5);
    // TypeError: Cannot set property 'x' of undefined

    4.2 不恰当地使用方法
    如果你直接取得一个方法的值(不是调用它),你就是把这个方法当做函数在用。当你要将一个方法当做一个参数传入一个函数或者一个调用方法中,你很可能会这么做。setTimeout()和注册事件句柄(event handlers)就是这种情况。我将会使用callIt()方法来模拟这个场景:

    1
    2
    3
    4
    /** Similar to setTimeout() and setImmediate() */
    function callIt(func) {
        func();
    }

    如果你是在Sloppy模式下将一个方法当做函数来调用,*this*指向的就是全局对象,所以之后创建的都会是全局的变量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var counter = {
        count: 0,
        // Sloppy-mode method
        inc: function () {
            this.count++;
        }
    }
    callIt(counter.inc);
     
    // Didn’t work:
    console.log(counter.count); // 0
     
    // Instead, a global variable has been created
    // (NaN is result of applying ++ to undefined):
    console.log(count);  // NaN

    如果你是在Strict模式下这么做的话,this是undefined的,你还是得不到想要的结果,不过至少你会得到一句警告:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var counter = {
        count: 0,
        // Strict-mode method
        inc: function () {
            'use strict';
            this.count++;
        }
    }
    callIt(counter.inc);
     
    // TypeError: Cannot read property 'count' of undefined
    console.log(counter.count);

    要想得到预期的结果,可以使用bind()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var counter = {
        count: 0,
        inc: function () {
            this.count++;
        }
    }
    callIt(counter.inc.bind(counter));
    // It worked!
    console.log(counter.count); // 1

    bind()又创建了一个总是能将this的值设置为counter 的函数。

    4.3 隐藏this
    当你在方法中使用函数的时候,常常会忽略了函数是有自己的this 的。这个this 又有别于方法,因此你不能把这两个this 混在一起使用。具体的请看下面这段代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var obj = {
        name: 'Jane',
        friends: [ 'Tarzan', 'Cheeta' ],
        loop: function () {
            'use strict';
            this.friends.forEach(
                function (friend) {
                    console.log(this.name+' knows '+friend);
                }
            );
        }
    };
    obj.loop();
    // TypeError: Cannot read property 'name' of undefined

    上面的例子里函数中的this.name 不能使用,因为函数的this 的值是undefined,这和方法loop()中的this 不一样。下面提供了三种思路来解决这个问题:

      • that=this,将this 赋值到一个变量上,这样就把this 显性地表现出来了(除了thatself 也是个很常见的用于存放this的变量名),之后就使用那个变量:

        1
        2
        3
        4
        5
        6
        7
        loop: function () {
            'use strict';
            var that = this;
            this.friends.forEach(function (friend) {
                console.log(that.name+' knows '+friend);
            });
        }
      • bind()。使用bind()来创建一个函数,这个函数的this 总是存有你想要传递的值(下面这个例子中,方法的this):
        1
        2
        3
        4
        5
        6
        loop: function () {
            'use strict';
            this.friends.forEach(function (friend) {
                console.log(this.name+' knows '+friend);
            }.bind(this));
        }
      • 用forEach的第二个参数。forEach的第二个参数会被传入回调函数中,作为回调函数的this 来使用。
        1
        2
        3
        4
        5
        6
        loop: function () {
            'use strict';
            this.friends.forEach(function (friend) {
                console.log(this.name+' knows '+friend);
            }, this);
        }
  5. 最佳实践

理论上,我认为实函数并没有属于自己的this,而上述的解决方案也是按照这个思想的。ECMAScript 6是用箭头函数(arrow function)来实现这个效果的,箭头函数就是没有自己的this 的函数。在这样的函数中你可以随便使用this,也不用担心有没有隐式的存在。

1
2
3
4
5
6
7
8
loop: function () {
    'use strict';
    // The parameter of forEach() is an arrow function
    this.friends.forEach(friend => {
        // `this` is loop’s `this`
        console.log(this.name+' knows '+friend);
    });
}

我不喜欢有些API把this 当做实函数的一个附加参数:

1
2
3
4
5
6
7
beforeEach(function () {  
    this.addMatchers({  
        toBeInRange: function (start, end) {  
            ...
        }  
    });  
});

把一个隐性参数写成显性地样子传入,代码会显得更好理解,而且这样和箭头函数的要求也很一致:

1
2
3
4
5
6
7
beforeEach(api => {
    api.addMatchers({
        toBeInRange(start, end) {
            ...
        }
    });
});

javascript中this的解析的更多相关文章

  1. 第112天:javascript中函数预解析和执行阶段

    关于javascript中的函数:  1.预解析:把所有的函数定义提前,所有的变量声明提前,变量的赋值不提前  2.执行 :从上到下执行,但有例外(setTimeout,setInterval,aja ...

  2. javascript中的闭包解析

    学习javaScript已经有一段时间了,在这段时间里,已经感受到了JavaScript的种种魅力,这是一门神奇的语言,同时也是一门正在逐步完善的语言,相信在大家的逐步修改中,这门语言会逐步的完善下去 ...

  3. javascript中apply()方法解析-简单易懂!

    今天看到了js的call与apply的异同,想着整理一下知识点,发现了一篇好文章,分享过来给大家,写的非常好! 参考: http://www.cnblogs.com/delin/archive/201 ...

  4. 浅谈JavaScript中的Ajax

    引言 作为一名WEB开发者,我想Ajax技术是一定需要掌握的.你也许平时没有使用JavaScript真正的写过Ajax.但是你一定使用过JQuery里面的相关函数来进行异步调用.今天我们就来介绍下原生 ...

  5. javascript中的Error对象

    在javascript中一旦代码解析或运行时发生错误,javascript引擎就会自动产生并抛出一个Error对象的实例,然后整个程序就中断在发生错误的地方. Error对象的实例有三个基本的属性: ...

  6. 前端开发:Javascript中的数组,常用方法解析

    前端开发:Javascript中的数组,常用方法解析 前言 Array是Javascript构成的一个重要的部分,它可以用来存储字符串.对象.函数.Number,它是非常强大的.因此深入了解Array ...

  7. 深入解析Javascript中this关键字的使用

    深入解析Javascript中面向对象编程中的this关键字 在Javascript中this关键字代表函数运行时,自动生成的一个内部对象,只能在函数内部使用.比如: function TestFun ...

  8. 解析JavaScript中apply和call以及bind

    函数调用方法 在谈论JavaScript中apply.call和bind这三兄弟之前,我想先说下,函数的调用方式有哪些: 作为函数 作为方法 作为构造函数 通过它们的call()和apply()方法间 ...

  9. JavaScript中依赖注入详细解析

    计算机编程的世界其实就是一个将简单的部分不断抽象,并将这些抽象组织起来的过程.JavaScript也不例外,在我们使用JavaScript编写应用时,我们是不是都会使用到别人编写的代码,例如一些著名的 ...

随机推荐

  1. html5桌面通知,notification的使用,右下角出现通知框

    1先判断浏览器是否支持:window.Notification 2判断浏览器是否开启提示的权限:Notification.permission === 'granted'(如果不允许则设置为允许:No ...

  2. ASP.NET MVC 定义JsonpResult实现跨域请求

    1:原理 在js中,XMLHttpRequest是不能请求不同域的数据,但是script标签却可以,所以可以用script标签实现跨域请求.具体是定义一个函数,例如jsonp1234,请求不同域的ur ...

  3. BZOJ 3570 动物园

    Description 近日,园长发现动物园中好吃懒做的动物越来越多了.例如企鹅,只会卖萌向游客要吃的.为了整治动物园的不良风气,让动物们凭自己的真才实学向游客要吃的,园长决定开设算法班,让动物们学习 ...

  4. Borg Maze poj 3026

    Description The Borg is an immensely powerful race of enhanced humanoids from the delta quadrant of ...

  5. VS2010安装Visual Assist

    Visual Assist X是一款非常好的Microsoft Visual Studio 2005和Visual Studio .NET插件,支持C/C++,C#,ASP,Visual Basic, ...

  6. AlgorithmsI Programming Assignment 1: Percolation

    3种版本的答案,第一种使用virtual top and bottom site, 但有backwash的问题,解决这个问题有两种方法: 1. 使用2个WQUUF, 但会增加memory. One f ...

  7. 图论(差分约束系统):POJ 1275 Cashier Employment

    Cashier Employment Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 7651   Accepted: 288 ...

  8. delphi NativeXml的中文支持 乱码

    一般XML的编码格式设置成UTF8比较通用,下面演示使用UTF8编码方式存储和处理包含中文的XML字符串(文件).1.设置启用内置的widestring支持 NativeXml内部使用ANSI str ...

  9. Java---类反射(2)---类反射加强

    经过前面的一篇博客,Java-类反射(1),相信大家对类反射有了一定的了解了. 下面来进行对类反射的加强,了解一下怎么通过类反射去new一个对象, 怎么通过类反射去访问其他类的方法. 怎么通过类反射去 ...

  10. SRM 405(1-250pt, 1-500pt)

    DIV1 250pt 题意:以linux系统中文件系统的路径表示方法为背景,告诉你某文件的绝对路径和当前位置,求相对路径.具体看样例. 解法:模拟题,不多说.每次碰到STL的题自己的代码都会显得很sb ...