this
关键字是JavaScript
中最复杂的机制之一。以至于我使用多年也很难说清它到底指向什么,我们有必要好好学习一下。this
也是我们学习和使用JavaScript
中的一座大山,我们必须翻过这座山。
为什么要使用this
先看一段代码如下:
var foo = {
name:'tom',
age:'24',
say:function(){
console.log('name: ' + this.name + ', age: ' + this.age);
}
}
foo.say(); //name: tom, age: 24
var bar = {
name:'cat',
age:'25'
}
foo.say.call(bar); //name: cat, age: 25
复制代码
这段代码在不同的上下文对象(foo
和bar
)中重复使用了函数say()
,不用写不同版本的函数。如果不使用this
那么就需要给函数say()
显示传入一个上下文对象。
say:function(context){
console.log('name: ' + context.name + ', age: ' + context.age);
}
复制代码
this
隐式的传递了一个对象的引用,因为我们可以将通用模块和API方法设计的更加易用和简洁。
什么是this
this指向函数自身
在开始工作的时候很容易把this
理解成指向函数自身,不过我们现在来分析下,this
是不是指向函数自身。思考下面一段代码:
function foo(){
this.name = 'tom';
}
foo.name = 'cat';
foo();
console.log(foo.name); //'cat'
复制代码
最终打印的是cat
,显然this
不是指向函数自身。执行foo.name=cat
,是向对象foo
添加一个name
属性,但是函数内部代码this.name
中的this
并不是指向那个函数对象。
遇到这样的问题,我们会采用其他的方式来达到目的,比如创建另一个带有name
属性的对象。
function foo(){
obj.name = 'tom';
}
var obj = {
name:'cat'
}
foo();
console.log(obj.name); //'tom'
复制代码
虽然这种方式确实解决了问题,其使用的是另一种我们比较熟悉的技术词法作用域
,并没有使用this
解决。
另一种方法是强制this
指向foo
函数对象。
function foo(){
this.name = 'tom';
}
foo.name = 'cat';
foo.call(foo);
console.log(foo.name);//'tom'
复制代码
这次我们没有回避this
,而且使用call()
来帮助我们使用this
。
this指向它的作用域
this
的另一个误区就是,指向函数的作用域。这个问题有点复杂,因为在某种情况下它是正确的,但是在其他情况下它却是错误的。需要说明的是,this
在任何情况下都不指向函数的词法作用域。
在
javaScript
中,作用域确实和对象类似,可见的标识符都是它的属性。但是作用域“对象”无法通过javaScript
代码访问,它存在于javaScript
引擎内部。(详细的可以参考之前的文章)
思考下面一段代码:
function foo() {
var a = 1;
this.bar();
}
function bar() {
console.log(this.a);
}
foo(); //undefined
复制代码
这段代码看起来像是故意为之,但实则反映出对this
的不了解。调用bar()
最常用的方法是省略前面的this
,直接使用词法引用标识符。这段代码还试图通过this
在foo()
函数中联动bar()
函数访问变量a
。这也是不可能的,不能使用this
来引用一个词法作用域内部的变量及函数。
this到底是什么
this
是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this
的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
当一个函数被调用时,会创建一个执行环境(也叫执行上下文)。执行环境中的活动对象会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this
就是记录的其中一个属性,会在函数执行的过程中用到。(详细的可以参考之前的文章)
总结
this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。