一、为什么要用 this
使用了this
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)
identify.call(you)
speak.call(me) // Hello, I'm KYLE
speak.call(you) // Hello, I'm READER
这段代码可以在不同的上下文对象(
me
和you
)中重复使用函数identify()
和speak()
, 不用针对每个对象编写不同版本的函数。
不使用 this
如果不使用this
,那就需要给 identify()
和 speak()
显式传入一个上下文对象。
function identify(context) {
return context.name.toUpperCase()
}
function speak(context) {
var greeting = "Hello, I'm " + identify(context)
console.log(greeting)
}
var me = {
name:"Kyle"
}
var you = {
name:"Reader"
}
identify(me)
identify(you)
speak(me) //Hello, I'm KYLE
speak(you) //Hello, I'm READER
对比总结:this
提供了一种更优雅的方式来隐式“传递”一个对象引用,因此可以将 API 设计得更加简洁并且易于复用。随着使用的模式越来越复杂,显式传递上下文对象会让代码变得越来越混乱,使用 this
则不会这样。
二、对 this
的误解
1. 指向自身:容易把 this
理解成指向函数本身。
function foo(num) {
console.log("foo:" + num)
//记录 foo 被调用的次数
this.count++
}
//执行这句时,的确像函数对象 foo 添加了一个属性 count 。
//但是函数内部代码 this.count 中的 this 并不是指向那个函数对象,所以虽然属性名相同,根对象却并不相同。
foo.count = 0;
var i;
for (i = 0; i < 10; i++) {
if (i > 5) {
foo(i);
}
}
console.log(foo.count)
-----------------------------------------------------------------------------------
> foo:6
> foo:7
> foo:8
> foo:9
> 0
如果要从函数对象内容引用它自身,那只使用 this
是不够的。一般来说需要通过一个指向函数对象的词法标识符(变量)来引用它。
// 这是个具名函数,在它的内部可以用函数名字来引用自身
function foo(num) {
foo.count = 4 // foo 指向它自身
}
setTimeout(function(){
// 匿名(没有名字的)函数无法指向自身
},10)
所以上述“使用 this
的例子”可以使用函数标识符替代 this
来引用函数对象:
function foo(num) {
console.log("foo:" + num)
//是使用 foo 标识符替代 this 来引用函数
foo.count++
}
foo.count = 0;
var i;
for (i = 0; i < 10; i++) {
if (i > 5) {
foo(i);
}
}
console.log(foo.count)
----------------------------------------------------------
> foo:6
> foo:7
> foo:8
> foo:9
> 4
但是这种方法回避了 this
的问题,并且完全依赖于变量 foo
的词法作用域。
接下来是强制使用 this
指向函数对象的方法:
function foo(num) {
console.log("foo:" + num)
//注意:在当前的调用方式下,this 确实指向 foo
this.count++
}
foo.count = 0;
var i;
for (i = 0; i < 10; i++) {
if (i > 5) {
// 使用 call(...) 可以确保 this 指向函数对象 foo 本身
foo.call(foo,i);
}
}
console.log(foo.count)
----------------------------------------------------------
> foo:6
> foo:7
> foo:8
> foo:9
> 4
2.它的作用域:this
指向函数的作用域。
需要明确的是,
this
在任何情况下都不指向函数的词法作用域。在JavaScript
内部,作用域确实和对象类似,可见的标识符都是它的属性。但是作用域“对象”无法通过JavaScript
代码访问,它存在于JavaScript
引擎内部。
使用 this
来隐式引用函数的词法作用域,错误栗子:
function foo(){
var a = 2;
this.bar()
}
function bar(){
console.log(this.a)
}
foo() // ReferenceError: a is not defined
解释:
- 这段代码视图通过
this.bar()
来引用bar()
函数。这是决不能成功的。调用bar()
最自然的方法是省略前面的this
, 直接使用词法引用标识符。 - 编写这段代码的开发者视图使用
this
联通foo()
和bar()
的词法作用域,从而让bar()
可以访问foo()
作用域里的变量a
。 这是不可能实现的,不能使用this
来引用一个词法作用域内部的东西。
每当想要把 this
和词法作用域的查找混合使用时,一定要提醒自己,这是无法实现的。
三、this
到底是什么
this
是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this
的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this
就是记录的其中一个属性,会在函数执行的过程中用到。
四、总结
this
既不指向函数自身也不指向函数的词法作用域。this
实际上是在函数被调用时发生的绑定, 它指向什么完全取决于函数在哪里被调用。