JavaScript中的this详解

this是JavaScript中一个很特别的关键字,被自动定义在所有函数的作用域中。

两个误解

1.this指向自身

单从字面意义上来说,很容易联想到this指向函数本身。这种理解到底对不对呢?看下面的例子:

function foo(){
    console.log(this.a);
}

foo.a = 10;
foo();

这里定义了一个函数foo,又给这个函数添加了一个属性a并赋值为10,接着执行这个函数。把这段代码复制到浏览器控制台执行一下,如果this指向的是函数本身的话,打印出的结果应该是10。

But,很不幸,这里打印出来的结果是:undefined

所以,将this理解为指向函数本身是不准确的。

2.指向函数的作用域

话不多说,直接看代码:

function foo(){
    var a = 2;
    bar();
}

function bar(){
    console.log(this.a);
}

foo();

变量a定义在函数foo的作用域中,但是这里打印出来的,依旧是undefined

显然,将this理解为指向函数的作用域也是不准确的。

事实上,this在任何情况下都不指向函数的词法作用域。在JavaScript内部,作用域确实和对象类似,可见的标识符都是它的属性。但是“对象”无法通过JavaScript代码访问,它存在于JavaScript引擎内部。

this到底是什么

说了这么多,this到底是个什么东东呢?

当一个函数被调用时会创建一个活动记录(有时也称为活动上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的其中一个属性,会在函数执行的过程中用到。

this是在运行时绑定的,并不是在编写时绑定的,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。因此要理解this的绑定过程,首先要理解调用位置

调用位置

调用位置就是函数在代码中被调用的位置(而不是被声明的位置)。寻找调用位置最重要的就是要分析调用栈(就是为了到达当前执行位置所调用的所有函数)。我们关心的调用位置就在当前正在执行的函数的前一个调用中。

看下面的代码,一切尽在注释中:

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的调用位置

可以使用浏览器的调试工具很方便的查看一个函数的调用栈。

this的绑定规则

说了半天,重点终于来了:函数在执行过程中调用位置到底如何决定this的绑定对象?

下面就介绍this绑定对象的四条规则及其优先级顺序,根据这些规则,我们就可以判断出this绑定的对象了。

1.默认绑定

首先要介绍的是最常用的函数调用类型:独立函数调用。这条规则是this绑定对象的默认规则。既然是默认规则,那么它的优先级自然是最低的,只有当其他规则都无法使用时才使用这条规则。

function foo(){
    console.log(this.a);
}

var a = 2;

foo();//"2"

如上面的代码所示,foo函数是直接使用不带任何修饰的函数引用进行调用的,这种调用方式就叫做独立函数调用。这种调用方式只能使用默认绑定,无法应用其他规则。

而默认绑定规则,是指将this绑定到全局对象或是undefined上。具体绑定到哪一个,要看函数是否运行在严格模式下。只有函数运行在非严格模式下,this才绑定到全局对象上;函数运行在严格模式下时,this绑定到undefined上。

回到上面的代码,我们知道声明在全局作用域中的变量可以看做时全局对象的同名属性,因此var a = 2实际上可以看做在全局对象上声明了一个属性并赋值为2。又因为foo函数的调用属于独立函数调用,应该使用默认绑定规则,即this绑定到全局对象上。所以上面代码在控制台中打印出的结果为2。

若foo函数在严格模式下执行:

function foo(){
    'use strict'
    console.log(this.a);
}

var a = 2;

foo();//Cannot read property 'a' of undefined

此时this绑定为undefined,执行就会报错:Cannot read property ‘a’ of undefined。

2.隐式绑定

若函数调用的位置有上下文,或是被某个函数或是对象拥有或是包含,此时this的绑定使用隐式绑定规则。

function print(){
    console.log(this.a);
}

var obj = {
    a: "haha",
    foo: print
}

obj.foo();//"haha"

上面的代码中,函数print作为对象obj的方法被调用,调用位置会使用obj上下文来引用函数,因此可以说函数被调用时obj对象“拥有”或是“包含”它。

当函数引用有上下文时,隐式绑定规则会将函数调用中的this绑定到这个上下文对象。在上面的例子中就是调用foo()时this被绑定到obj上,所以最后打印出的结果为“haha”。

隐式丢失

一个最常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,此时它会使用默认绑定,将this绑定到全局对象或是undefined上,取决于是否使用严格模式。

看下面的代码:

function print(){
    console.log(this.a);
}

var obj = {
    a: "haha",
    foo: print
}

var bar = obj.foo;
var a = "hehe";
bar();//"hehe"

虽然bar是obj.foo的一个引用,但实际上,它引用的是print函数本身,因此此时的bar()其实是不带任何修饰的函数调用,应用默认绑定规则。

隐式丢失的另一种情况发生在将函数作为参数进行传递的情况中。看下面的代码:

function print(){
    console.log(this.a);
}

function doPrint(fn){
    fn();
}

var obj = {
    a: "haha",
    foo: print
}

var a = "hehe";
doPrint(obj.foo);//"hehe"

参数传递实际上是一种隐式赋值,因此我们传入参数时也会被隐式赋值,所以结果和上个例子一样。

3.显式绑定

在分析隐式绑定时,我们在一个对象内部包含一个指向函数的属性foo,并通过这个属性间接引用函数,从而将this隐式绑定到这个对象上。

那么如果我们不想在对象内部包含函数引用而想在某个对象上强制调用函数,应该怎么办呢?

答案是使用函数的call()/apply()方法。严格来说JavaScript的宿主环境中会提供一些非常特殊的函数,它们没有这两个方法,但是这样的函数非常罕见。 JavaScript提供的大多数函数以及你自己创建的所有函数都可以使用这两个方法。

这两个方法的第一个参数是一个对象,它们会把这个对象绑定到this,接着在调用函数时指定这个this。因为可以直接指定this的绑定对象,所以称之为显式绑定。

function foo(){
    console.log(this.a);
}

var obj = {
    a: 2
};

foo.call(obj);

如上面代码所示,通过foo.call(…)可以在调用foo时强制将它的this绑定到obj上。

4.new绑定

在传统的面向类的语言中,“构造函数”是类中一种特殊的方法,使用new初始化类时会调用类中的构造函数。JavaScript也有一个new操作符,然而,JavaScript中new的机制与面向类的语言完全不同。

在JavaScript中,“构造函数”只是一些使用new操作符时被调用的函数。它并不属于某个类,也不会实例化一个类。实际上,它们只是被new操作符调用的普通函数而已

也就是说,在JavaScript中并不存在所谓的“构造函数”,只存在对函数的“构造调用”。

使用new来调用函数,实际上会执行以下四个步骤:
1.创建(或者说构造)一个全新的对象;
2.这个新对象会被执行[[原型]]链接;
3.这个新对象会绑定到函数调用的this
4.如果函数没有返回其他对象,那么new表达式中的函数会自动返回这个新对象。

从上面的分析中可以看出:当使用new来调用一个函数时,会构造一个新对象并将其绑定到该函数调用中的this上。

以上就是this的四种绑定规则。

优先级

new绑定 > 显式绑定 > 隐式绑定 > 默认绑定

总的来说呢,this的绑定对象可以按以下规则来进行判定:

1.函数是否在new中调用(new绑定)?如果是,则this绑定新创建的对象;

2.函数是否通过call/apply绑定(显式绑定)?如果是,this绑定的是指定的对象;

3.函数是否在某个上下文对象中调用(隐式绑定)?如果是,this绑定的是那个上下文对象;

4.如果上述情况都不符合,则使用默认绑定,在严格模式下,this绑定到undefined,非严格模式下绑定到全局对象。

上面介绍的规则已经涵盖了this绑定的大多数情况。当然,规则总有例外,this的绑定规则也是如此。至于有哪些例外情况,以后会有文章详细介绍。今天就先到这里啦~~~

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值