JavaScript中的this

来源:互联网 发布:jmeter调用java代码 编辑:程序博客网 时间:2024/06/02 21:29

学习JavaScript的时候,this曾让我困惑不已,因为不清楚this所指向的是什么,在阅读一些代码的时候总是迷迷糊糊,不知所云。弄懂this后,看JavaScript代码的时候比原来更清晰,对语言本身也有了更深的理解。在JavaScript中,this非常重要但又特别容易弄错,所以我在此总结一下this的点点滴滴,希望能给那些对this还不明白的朋友们一点帮助吧。


    • 一this的起源
    • 二对this的误解
    • 三this真正的指向
      • 隐式绑定
      • 显式绑定
      • new绑定
      • 默认绑定
    • 四this绑定优先级
    • 五值得注意的几个点
      • 小心隐性绑定
      • 箭头函数


一、this的起源

了解this是怎么来的,对更好地理解this还是很重要的。

当一个函数被调用的时候,也就是执行流进入一个函数的时候,会自动创建一个被称为执行上下文的记录(其实这个时候也就产生了作用域链),这个记录里包含了各种必要信息,比如函数的调用方式,函数的参数(也就是arguments)等,this就包含在这个信息里。

二、对this的误解

我刚接触this的时候,我简单地觉得this就是指向函数所在的对象。比如:

var myObj = {    message: "Hi, Ontides!",    say: function say(){        console.log(this.message);    }};myObj.say();    //Hi, Ontides!

这里确实如所预想的那样,打印出了“Hi, Ontides”,但是,函数并不一定在一个对象中,如果是一个全局函数,那么它里面的this指向就不能简单地理解为“指向函数所在的对象”那么了。

实际上,JavaScript中this的指向和this所在函数定义的位置没有任何关系(ES5及以前,因为ES6中的箭头函数不是这样,稍后会说)。我们之所以会认为this和函数所声明的地方有关,是因为在JavaScript中的作用域遵循词法作用域,因此,我们很容易使用词法作用域的分析方式去分析this的具体指向。实际上,this的处理方式和动态作用域的处理方式相同。this的指向取决于函数调用的位置,而不是函数定义的位置。

三、this真正的指向

上面提到,函数调用的位置决定了this绑定的对象。在JavaScript中,对应函数四种调用方式,this的绑定一共有四种方式,分别为隐式绑定,显式绑定、new绑定和默认绑定。

1. 隐式绑定

当函数的调用位置具有上下文对象,或者说函数是在一个对象下调用的时候,this会被隐式绑定到这个上下文对象上。如:

function foo(){    console.log(this.a);}var obj = {    a: 2,    foo: foo};//这里this指向了objobj.foo()   //2

2. 显式绑定

当一个函数使用call或者apply的方式调用的时候,this会被显式地绑定到call或者apply所指定的对象。如:

var a = 3;function foo(){    console.log(this.a);}var obj = {    a: 2};foo.call(obj);  //2

这里因为函数foo再调用的时候使用.call()的方式调用,所以this指向了obj而不是默认的全局对象。

3. new绑定

当对函数进行构造调用的时候(即使用new操作符进行调用),this的指向会发生改变。为了更好阐述new绑定,这里先阐述一下对函数构造调用的的过程。

当对一个函数尽兴构造调用的时候,会经历以下步骤:

  1. 创建一个新的对象
  2. 将构造函数的作用域赋给新的对象
  3. 执行构造函数中的代码
  4. 返回新对象

在步骤2中this被绑定到了这个新对象。

4. 默认绑定

当前几种绑定都不适用的情况下JavaScript引擎会对this执行默认绑定。这里的函数调用方式是独立函数调用,如:

var a = 1;function foo(){    console.log(this.name);}foo();  //1

在默认绑定的情况下this被绑定到了全局对象,因为全局变量a是全局对象的一个属性,因此这里输出了a中的内容。

这里需要注意的是,再严格模式下,默认绑定将不会绑定到全局对象上,而会绑定到undefined,如:

"use strict";var a = 2;function foo(){    console.log(this.a);}foo(); //TypeError: undefined is not an object

这里抛出了类型错误的提示,因为this被绑定到undefined,而undefined不是一个对象。

四、this绑定优先级

this绑定一共四条规则,那么在判断this绑定的时候应该怎样运用规则呢?通过this绑定的优先级来判断。

优先级从高到低分别为 new绑定 > 显式绑定 > 隐式绑定 > 默认绑定。

五、值得注意的几个点

1. 小心隐性绑定

有些情况一些函数看起来像是隐性绑定,而实际上是应用了默认绑定。如:

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

这里虽然把obj.foo赋给了bar,但是实际上只是把对foo的引用赋给了bar,因此这里bar的调用只是普通的独立函数调用。

还有一种更为常见而容易出错的情况发生在传入回调函数时:

var a = 3;function foo(){    console.log(this.a);}var obj = {    a: 2,    foo: foo};function doFoo(fn){    fn();}doFoo(obj.foo); //3

同样,如果理解函数名是对函数的引用的话,这个结果就容易理解了呃。传递到doFoo的参数是函数foo,函数的调用方式是独立调用,因此这里的this并不适用隐形绑定规则,而是采用默认绑定的规则。

2. 箭头函数

ES6中的箭头函数不适用与上面四个规则。

关于箭头有两点需要注意:

  1. 箭头函数中的this由函数所在的外部作用域来决定的

    这里箭头函数中this遵循词法作用域的规则。如:

    var a = 2;var obj = {    a: 1,    foo: function foo(){        setTimeout(()=>{            console.log(this.a);        },30);    }}obj.foo();  //1

    这里定时器中的this并没有指向全局对象,是绑定到obj,可以看出是继承了外层函数中的this的绑定对象。

  2. 箭头函数的this绑定无法修改

    箭头函数使用自己独特的this绑定原则,不能通过.call()等方式改变this指向。

0 0