JavaScript中的this

学习JavaScript的时候,this曾让我困惑不已,因为不清楚this所指向的是什么,在阅读一些代码的时候总是迷迷糊糊,不知所云。弄懂this后,看JavaScript代码的时候比原来更清晰,对语言本身也有了更深的理解。在JavaScript中,this非常重要但又特别容易弄错,所以我在此总结一下this的点点滴滴,希望能给那些对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指向了obj
obj.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();  //2
bar();      //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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值