全面理解 JavaScript 中的 this

本文介绍了 JavaScript 中的 this 关键字在各种情况下的含义,虽然这只是 JavaScript 中一个很小的概念,但借此我们可以深入了解 JavaScript 中函数的执行环境,而这是理解闭包等其他概念的基础。掌握了这些概念,才能充分发挥 JavaScript 的特点,才会发现 JavaScript 语言特性的强大。

全局上下文

无论是否在严格模式下,在全局执行上下文中(在任何函数体外部)this 都指向全局对象。当然具体的全局对象和宿主环境有关。

在浏览器中, window 对象同时也是全局对象:

JavaScript 代码:
console.log(this === window); // true

NodeJS 中,则是 global 对象:

JavaScript 代码:
console.log(this); // global

函数上下文
由于其运行期绑定的特性,JavaScript 中的 this 含义要丰富得多,它可以是全局对象、当前对象或者任意对象,这完全取决于函数的调用方式。JavaScript 中函数的调用有以下几种方式:作为函数调用,作为对象方法调用,作为构造函数调用,和使用 apply 或 call 调用。下面我们将按照调用方式的不同,分别讨论 this 的含义。

作为函数直接调用
作为函数直接调用时,要注意 2 种情况:
非严格模式
在非严格模式下执行函数调用,此时 this 默认指向全局对象。
JavaScript 代码:
function f1(){
return this;
}
//在浏览器中:
f1() === window; //在浏览器中,全局对象是window

//在Node中:
f1() === global;
严格模式 ‘use strict’;

在严格模式下,this 将保持他进入执行上下文时的值,所以下面的 this 并不会指向全局对象,而是默认为 undefined 。

JavaScript 代码:
‘use strict’; // 这里是严格模式
function test() {
return this;
};

test() === undefined; // true

作为对象的方法调用

在 JavaScript 中,函数也是对象,因此函数可以作为一个对象的属性,此时该函数被称为该对象的方法,在使用这种调用方式时,内部的 this 指向该对象。

JavaScript 代码:
var Obj = {
prop: 37,
getProp: function() {
return this.prop;
}
};

console.log(Obj.getProp()); // 37
上面的例子中,当 Obj.getProp() 被调用时,方法内的 this 将指向 Obj 对象。值得注意的是,这种行为根本不受函数定义方式或定义位置的影响。在前面的例子中,我们在定义对象 Obj 的同时,将成员 getProp 定义了一个匿名函数。但是,我们也可以首先定义函数,然后再将其附加到 Obj.getProp 。
所以,下面的代码和上面的例子是等价的:

JavaScript 代码:
var Obj = {
prop: 37
};

function independent() {
return this.prop;
}

Obj.getProp = independent;

console.log(Obj.getProp()); // logs 37
JavaScript 非常灵活,现在我们把对象的方法赋值给一个变量,然后直接调用这个函数变量又会发生什么呢?

JavaScript 代码:
var Obj = {
prop: 37,
getProp: function() {
return this.prop;
}
};

var test = Obj.getProp
console.log(test()); // undefined
可以看到,这时候 this 指向全局对象,这个例子 test 只是引用了 Obj.getProp 函数,也就是说这个函数并不作为 Obj 对象的方法调用,所以,它是被当作一个普通函数来直接调用。因此,this 指向全局对象。

(在这里推荐下我自己的web前端学习交流群:675498134,不管你是小白还是大神,我都欢迎你们过来学习交流,不定期分享干货,包括我自己整理的最新的前端资料和教程送给大家,每天还有前端大牛直播讲解项目知识。一起学习一起交流,共同进步。)

作为构造函数调用

JavaScript 支持面向对象式编程,与主流的面向对象式编程语言不同,JavaScript 并没有类(class)的概念,而是使用基于原型(prototype)的继承方式。作为又一项约定通用的准则,构造函数以大写字母开头,提醒调用者使用正确的方式调用。

当一个函数用作构造函数时(使用 new 关键字),它的 this 被绑定到正在构造的新对象,也就是我们常说的实例化出来的对象。

JavaScript 代码:
function Person(name) {
this.name = name;
}

var p = new Person(‘小鱼不乖’);
console.log(p.name); // “小鱼不乖”

箭头函数中的 this

在箭头函数中,this 与封闭词法上下文的 this 保持一致,也就是说由上下文确定。

JavaScript 代码:
var obj = {
x: 10,
foo: function() {
var fn = () => {
return () => {
return () => {
console.log(this); //{x: 10, foo: ƒ} 即 obj
console.log(this.x); //10
}
}
}
fn()()();
}
}
obj.foo();
obj.foo 是一个匿名函数,无论如何, 这个函数中的 this 指向它被创建时的上下文(在上面的例子中,就是 obj 对象)。这同样适用于在其他函数中创建的箭头函数:这些箭头函数的this 被设置为外层执行上下文。

JavaScript 代码:
// 创建一个含有bar方法的obj对象,bar返回一个函数,这个函数返回它自己的this,
// 这个返回的函数是以箭头函数创建的,所以它的this被永久绑定到了它外层函数的this。
// bar的值可以在调用中设置,它反过来又设置返回函数的值。
var obj = {
bar: function() {
var x = (() => this);
return x;
}
};

// 作为obj对象的一个方法来调用bar,把它的this绑定到obj。
// x所指向的匿名函数赋值给fn。
var fn = obj.bar();

// 直接调用fn而不设置this,通常(即不使用箭头函数的情况)默认为全局对象,若在严格模式则为undefined
console.log(fn() === obj); // true

// 但是注意,如果你只是引用obj的方法,而没有调用它(this是在函数调用过程中设置的)
var fn2 = obj.bar;
// 那么调用箭头函数后,this指向window,因为它从 bar 继承了this。
console.log(fn2()() == window); // true

在上面的例子中,一个赋值给了 obj.bar 的函数(称为匿名函数 A),返回了另一个箭头函数(称为匿名函数 B)。因此,函数B的this被永久设置为 obj.bar(函数A)被调用时的 this 。当返回的函数(函数B)被调用时,它this始终是最初设置的。在上面的代码示例中,函数B的 this 被设置为函数A的 this ,即 obj,所以它仍然设置为 obj,即使以通常将 this 设置为 undefined 或全局对象(或者如前面示例中全局执行上下文中的任何其他方法)进行调用。

原型链中的 this

相同的概念在定义在原型链中的方法也是一致的。如果该方法存在于一个对象的原型链上,那么 this 指向的是调用这个方法的对象,就好像该方法本来就存在于这个对象上。

JavaScript 代码:
var o = {
f : function(){
return this.a + this.b;
}
};
var p = Object.create(o);
p.a = 1;
p.b = 4;

console.log(p.f()); // 5
在这个例子中,对象 p 没有属于它自己的f属性,它的f属性继承自它的原型。但是这对于最终在 o 中找到 f 属性的查找过程来说没有关系;查找过程首先从 p.f 的引用开始,所以函数中的 this 指向 p 。也就是说,因为f是作为p的方法调用的,所以它的this 指向了 p 。这是 JavaScript 的原型继承中的一个有趣的特性。

使用 apply 或 call 调用

JavaScript 中函数也是对象,对象则有方法,apply 和 call 就是函数对象的方法。这两个方法异常强大,他们允许切换函数执行的上下文环境(context),即 this 绑定的对象。很多 JavaScript 中的技巧以及类库都用到了该方法。让我们看一个具体的例子:

JavaScript 代码:
function Point(x, y){
this.x = x;
this.y = y;
this.moveTo = function(x, y){
this.x = x;
this.y = y;
}
}

var p1 = new Point(0, 0);
p1.moveTo(1, 1);
console.log(p1.x,p1.y); //1 1

var p2 = {x: 0, y: 0};
p1.moveTo.apply(p2, [10, 10]);
console.log(p2.x,p2.y); //10 10
在上面的例子中,我们使用构造函数生成了一个对象 p1,该对象同时具有 moveTo 方法;使用对象字面量创建了另一个对象 p2,我们看到使用 apply 可以将 p1 的方法 apply 到 p2 上,这时候 this 也被绑定到对象 p2 上。另一个方法 call 也具备同样功能,不同的是最后的参数不是作为一个数组统一传入,而是分开传入的:

JavaScript 代码:
function Point(x, y){
this.x = x;
this.y = y;
this.moveTo = function(x, y){
this.x = x;
this.y = y;
}
}

var p1 = new Point(0, 0);
p1.moveTo(1, 1);
console.log(p1.x,p1.y); //1 1

var p2 = {x: 0, y: 0};
p1.moveTo.call(p2, 10, 10); // 只是参数不同
console.log(p2.x,p2.y); //10 10

很多人当谈到 JavaScript 中的 this 的时候会感到头疼,因为在 JavaScript 中,this 是动态绑定,或称为运行期绑定的,这就导致 JavaScript 中的 this 关键字有能力具备多重含义,带来灵活性的同时,也为初学者带来不少困惑。希望本篇能为你解惑!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值