1. 为什么要用 this
?
对于有经验的JavaScript开发者来说,this都是一种非常复杂的机制,那它到底有用在哪里呢?真的值得我们付出这么大代价学习吗?的确,在介绍怎么做之前我们需要明白为什么。下面我们来解释一下为什么要使用this。
示例:
javascript
function identify(){
return this.name.toUpperCase();
}
function speak(){
var greeting = "Hello, I'm " + identify.call(this);
console.log(greeting);
}
var me = {name:"Kyle"};
var you = {name:"Reader"};
identify.call(me);//KYLE
identify.call(you);//READER
speak.call(me);//Hello, I'm KYLE
speak.call(you);//Hello, I'm READER
这段代码一开始看不懂,没关系,下一章就讲解。我们先专注于为什么。
这段代码可以在不同的上下文对象( me 和 you )中重复使用函数identify( )和speak( ),不用针对每个对象编写不同的版本。
如果不使用this
,那就需要给identify( )的和speak( )显式传入一个上下文对象。
javascript
function identify(context){
return context.name.toUpperCase();
}
function speak(context){
var greeting = "Hello, I'm " + identify(context);
console.log(greeting);
}
identify(me);//KYLE
speak(you);//Hello, I'm READER
this
让代码更简洁,更符合面向对象的设计逻辑。
随着你的使用模式越来越复杂,显示纯利上下文对象会让代码越来越混乱,使用this就不会这样。当了解对象和原型时,你就会明白函数可以自动引用适合的上下文对象有多重要。
2. 对 this
的常见误解
2.1 误解一:this
指向函数自身
我们可以试着分析一下,看看this是不是指向函数本身。
javascript
function foo(num){
console.log("foo:"+num);
//记录foo被调用的次数
this.count++;
}
foo.count=0;
foo(1);//foo:1
foo(2);//foo:2
foo(3);//foo:3
console.log(foo.count);//0
最后输出竟然是0
吗?!
按照 this 指向函数自身来说,this.count 就是 foo.count,那么foo被调用时foo.count应该会加 1。上面console.log语句的确产生了3条输出,证明 foo 确实是被调用了3次,但是 foo.count 仍然是0,那就反向证明了 this.count 和 foo.count 就不是一个东西。
那你一定会问,如果我增加的count属性和预期的不一样,那我增加的是哪个count?实际上,如果深入探究的话,就会发现这段代码在无意中创建了一个全局变量count
,它的值为NaN。
有一个简易的解决方法就是使用foo标识符替代this来引用函数对象:
javascript
function foo(num){
console.log("foo:"+num);
//记录foo被调用的次数
foo.count++;
}
foo.count=0;
foo(1);//foo:1
foo(2);//foo:2
foo(3);//foo:3
console.log(foo.count);//3
确实,这个方法解决了问题,但是它回避了this的含义和工作原理
,而是返回了词法作用域这个舒适区。
另一种方法是强制
this指向foo函数对象:
javascript
function foo(num){
console.log("foo:"+num);
//记录foo被调用的次数
this.count++;
}
foo.count=0;
for(var i=1;i<4;i++){
//使用call(...)可以确保this指向函数对象foo本身
foo.call(foo,i);
}
console.log(foo.count);//3
这次我们就接受了this,这是this的显示绑定
,下一篇咱会继续探索。
2.2 误解二:this
指向函数的作用域
需要明确的是,this在任何情况下都不指向函数的词法作用域。在JavaScript内部,作用域确实和对象类似,可见的标识符都是它的属性。但是作用域"对象"无法通过JavaScript代码访问,它存在于JavaScript引擎内部。
思考下面的代码:
javascript
function foo(){
var a=2;
this.bar();
}
function bar(){
console.log(this.a);
}
foo();//ReferenceError: a is not defined
这里试图使用this.bar()来调用bar()函数,但它竟然成功了,虽然也变相地说明了this不指向函数作用域,但是调用bar()应该直接省略前面的this,直接使用词法引用标识符。这里能对完全是凑巧函数调用时应用了this的默认绑定
,因此this指向全局对象。
此外,编写这段代码还试图使用this联通foo()和bar()的词法作用义,从而让bar()可以访问foo()作用域里的变量a。这是不可能实现的,使用this是不可能在词法作用中查到什么。
每当想要你把this和词法作用域的查找混合时,一定要提醒自己这是不对的。
3. this
到底是什么?
简而言之 :
this
是一个在函数运行时动态绑定的上下文对象,它的指向完全由 函数调用方式 决定,而非函数定义的位置。具体规则包括:
- 默认绑定 :独立调用时指向全局对象(严格模式下为
undefined
)。 - 隐式绑定:通过对象方法调用时指向该对象。
- 显式绑定 :通过
call
/apply
/bind
强制指定。 new
绑定:构造函数中指向新创建的对象。
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。 (具体细节将在后续的全面解析中展开。)
结语
理解 this
的关键是 抛弃直觉,关注调用方式 。它不是魔法,而是一套基于调用规则的上下文传递机制。在后续文章中,我们将深入探讨 this
的四大绑定规则,并解锁 箭头函数
、严格模式
等进阶场景下的行为差异。