这是一篇译文。当时看到原文的时候觉得内容非常易懂,是不可多得的好文。所以一开始就扔进草稿箱准备翻译,不过一晃好久过去了,最新发布的博文ID是1190,而这篇的ID是715,可见其在草稿箱里待了有多久。而这期间网络上也已经有了这篇文章的翻译稿,不过传播并不广泛,我搜索了下并没有多少索引和转发量。总体来说,已有的翻译稿总体来说也挺好,但是但从第二节开始,翻译变的有些随意,并且出现了很多偏差,有大有小,虽不影响阅读却多少妨碍理解。比如将“传递函数作为值”译成“传递函数的值”等等,这很容易引起混淆。另外还遗漏了一小部分内容的翻译,不知道是否是原作者后期更新内容所致。
个人挺喜欢这篇文章,所以网上虽已有一版,但还是希望用自己的翻译语言来描述一遍。尽管可能显得多余,但我对自己的翻译水平还是有些自信,特别是斟词酌句的时候特别爱花时间。如果要说特点就是,尽可能保持原来作者的语句不改动。不到万不得已不分句,所以语言上难免定语比较多,读起来可能有些拗口。不过好处就是很少出现意思的偏差,同时尽可能不修改也是对原作者的尊重。
原文:Fully Understanding the this
Keyword by Cody Lindley
this
的概念性概述
每当一个函数被创建,一个叫做this
的关键字(在幕后)也随之被创建出来,它链接着包含函数进行操作的对象。换句话说,this
在其所在函数的作用域里可用,是包含那个函数作为属性和方法的对象的引用。
让我们来看看这个对象:
var cody = {
living:true,
age:23,
gender:'male',
getGender:function(){return cody.gender;}
};
console.log(cody.getGender()); // logs 'male'
注意里面的getGender
函数,我们通过点符号(例如cody.gender
)访问cody
对象自身的gender
属性。这段代码可以被重写,用this
来访问cody
对象,因为this
指向cody
对象。
var cody = {
living:true,
age:23,
gender:'male',
getGender:function(){return this.gender;}
};
console.log(cody.getGender()); // logs 'male'
在this.gender
里用到的this
简单地指向函数正在操作的cody
对象。
关于this
的主题会让人感到困惑,但其实未必如此。只要记住,通常,this
被用在函数里并指向包含这个函数的对象,而不是这个函数本身(例外情况包括使用new
关键字或者call()
和apply()
)。
注意事项
- 关键字
this
的行为看起来就像其他变量一样,除了你不能改变它。 - 不同于
arguments
和任何发送给函数的参数,在调用(活动)的对象中,this
是一个关键字(而不是属性)。
如何确定this
的值?
传递给所有函数的this
,其值基于函数被调用的运行时的上下文。请注意下这里,因为这是“那些你需要去记下来的怪异(不同寻常)”之一。
下面的代码里,myObject
对象被赋予有一个sayFoo
属性,其指向sayFoo
函数。当sayFoo
函数在全局作用域被调用时,this
指向window
对象。当它作为myObject
对象的方法被调用时,this
指向myObject
。
myObject
还有一个foo
属性,这里被一同用到。
var foo = 'foo';
var myObject = {foo: 'I am myObject.foo'};
var sayFoo = function() {
console.log(this['foo']);
};
// give myObject a sayFoo property and have it point to sayFoo function
myObject.sayFoo = sayFoo;
myObject.sayFoo(); // logs 'I am myObject.foo' 12
sayFoo(); // logs 'foo'
显然,this
的值基于函数被调用的上下文。myObject.sayFoo
和sayFoo
都指向相同的函数。然而,根据sayFoo()
从哪里(即:上下文)被调用,this
的值不同。
下面是显式使用全局对象(即window
)的相同的代码,或许有用吧。(译注:原文使用 head object 一词,是原作者所著书《JavaScript Enlightenment》里的用语,并不详见于其他地方,其意与全局变量 global object 完全相同。下面的翻译将所有的 head object 译为全局对象)
window.foo = 'foo';
window.myObject = {foo: 'I am myObject.foo'};
window.sayFoo = function() { ! console.log(this.foo); };
window.myObject.sayFoo = window.sayFoo;
window.myObject.sayFoo();
window.sayFoo();
确保你在传递函数时,或者有多个引用指向一个函数时,你明白this
的值会随着你调用函数的上下文的不同而改变。
注意事项
- 除了
this
以外的所有变量以及参数都遵循词法作用域(Lexical Scoping)
嵌套函数里的this
指向全局对象
你也许想知道,当this
用在被另一个函数包含的函数里会发生什么。糟糕的是在ECMA 3里,this
迷失了方向并指向了全局对象(浏览器的window
对象),而不是包含函数定义的对象。
下面的代码中,在func2
和func3
中的this
迷失了方向,不是指向myObject
,而是指向了全局对象。
var myObject = {
func1:function() {
console.log(this); //logs myObject
varfunc2=function() {
console.log(this); //logs window, and will do so from this point on
varfunc3=function() {
console.log(this); //logs window, as it’s the head object
}();
}();
}
};
myObject.func1();
好消息是这将会在ECMAScript 5里被修正。现在,你必须意识到这个困境,特别是当你开始将函数作为值传递给其他函数时。
考虑下面的代码,当将一个匿名函数传递到foo.func1
时发生了什么。当一个匿名函数在foo.func1
的内部调用(函数嵌套在函数中),这个匿名函数中的this
的值将会指向全局对象。
var foo = {
func1:function(bar){
bar(); //logs window, not foo
console.log(this);//the this keyword here will be a reference to foo object
}
};
foo.func1(function(){console.log(this)});
现在你应该不会忘记了:当包含this
的函数嵌套在另一个函数中,或者在另一个函数的上下文中被调用时,this
的值将总是指向全局对象(再说一次,这将会在ECMAScript 5里被修正)。
解决嵌套函数的问题
为了this
的值不丢失,你可以简单地在父函数里使用作用域链来保存this
的引用。下面的代码演示了怎么做,用一个叫做that
的变量,并利用它的作用域,我们能更好保存上下文的轨迹。
var myObject = {
myProperty:'Icanseethelight',
myMethod:function() {
var that=this; //store a reference to this (i.e.myObject) in myMethod scope varhelperFunctionfunction(){//childfunction
var helperFunction function() { //childfunction
//logs 'I can see the light' via scope chain because that=this
console.log(that.myProperty); //logs 'I can see the light'
console.log(this); // logs window object, if we don't use "that"
}();
}
}
myObject.myMethod(); // invoke myMethod
控制this
的值
this
的值通常取决于函数被调用处的上下文(除了使用new
关键字的情况以外,这方面稍后讨论),但是你可以使用apply()
和call()
来定义调用函数时this
指向的对象,以此覆盖/控制this
的值。使用这些方法,就好像再说:“嘿,调用 X 函数,但是告诉它,用 Z 对象作为this的值”。这么做的话,JavaScript默认决定this
值的方式就被覆盖了。
下面,我们创建了一个对象和一个函数。然后我们通过call()
调用函数,以便让函数中this
的值使用myObject
作为其上下文。于是myFunction
函数里的语句就会去填充myObject
的属性而不是填充全局对象。我们就改变了(myFunction
中的)this
所指向的对象
var myObject = {};
var myFunction = function(param1, param2) {
//setviacall()'this'points to my Object when function is invoked
this.foo = param1;
this.bar = param2;
console.log(this); //logs Object{foo = 'foo', bar = 'bar'}
};
myFunction.call(myObject, 'foo', 'bar'); // invoke function, set this value to myObject
console.log(myObject) // logs Object {foo = 'foo', bar = 'bar'}
在上面的例子里,我们使用了call()
,但也可以使用apply()
。两者的不同之处在于如何将参数传递给函数。使用call()
,参数仅使用逗号分隔。使用apply()
,参数放在一个数组里传递。下面是相同的思路,但使用的是apply()
。
var myObject = {};
var myFunction = function(param1, param2) {
//set via apply(), this points to my Object when function is invoked
this.foo=param1;
this.bar=param2;
console.log(this); // logs Object{foo='foo', bar='bar'}
};
myFunction.apply(myObject, ['foo', 'bar']); // invoke function, set this value
console.log(myObject); // logs Object {foo = 'foo', bar = 'bar'}
这里你需要记住的是,你可以覆盖在一个函数作用域里JavaScript默认决定this
值的方式。
在自定义构造函数中使用this
关键字
当一个函数联合new
关键字一起被调用时,this
的值——就如同其在构造函数中的表示——指向对象实例本身。换种说法:在构造函数里,我们可以在对象被实际创建之前就通过this
指代(控制)对象。这种情况下,this
默认值的变化,与使用call()
或apply()
并没有什么不同。
下面,我们设置了一个Person
的构造函数,并用this
来指向将要被创建的对象。当Person
的实例被创建,this.name
将会指向新创建的对象,并用传递给构造函数的参数(name)的值,给新对象里的name属性赋值。
var Person = function(name) {
this.name = name || 'johndoe'; // this will refer to the instanc ecreated
}
var cody = new Person('Cody Lindley'); // create an instance, based on Person constructor
console.log(cody.name); // logs 'Cody Lindley'
当使用new
关键字调用构造函数时this
指向“即将被创建的对象”。如果我们没有使用new
关键字,那么this
的值将会是Person
被调用的上下文——这里就是全局对象。让我们检验下这个脚本。
var Person = function(name) {
this.name=name||'johndoe';
}
var cody = Person('Cody Lindley'); // notice we did not use 'new'
console.log(cody.name); // undefined, the value is actually set at window.name
console.log(window.name); // logs 'Cody Lindley'
prototype
方法中的this指向一个构造实例
当函数被添加为构造函数的prototype
属性时,这个函数里的this
指向调用这个函数的实例。假设我们有一个自定义的Person()
的构造函数,其需要人的全名(full name)作为参数。为了访问人的全名,我们将whatIsMyFullName
方法添加到Person.prototype
,这样,所有的的Person
实例都继承了这个方法。当使用this
时,这个方法就可以引用调用它的实例(以及这个实例的属性)。
这里我演示了两个Person
对象的创建(cody
和lisa
),以及继承来的包含this
关键字的whatIsMyFullName
方法如何访问实例。
var Person = function(x){
if(x){this.fullName = x};
};
Person.prototype.whatIsMyFullName = function() {
return this.fullName; // 'this' refers to the instance created from Person()
}
var cody = new Person('cody lindley');
var lisa = new Person('lisa lindley');
// call the inherited whatIsMyFullName method, which uses this to refer to the instance
console.log(cody.whatIsMyFullName(), lisa.whatIsMyFullName());
/* The prototype chain is still in effect, so if the instance does not have a
fullName property, it will look for it in the prototype chain.
Below, we add a fullName property to both the Person prototype and the Object
prototype. See notes. */
Object.prototype.fullName = 'John Doe';
var john = new Person(); // no argument is passed so fullName is not added to instance
console.log(john.whatIsMyFullName()); // logs 'John Doe'
这里需要记住的是,当this
关键字用在prototype对象的方法里时,其就指向实例。如果实例不包含那个属性,就开始原型查找(搜索查找原型链)。
注意事项
如果this
所指向的实例或对象不包含被引用到的属性,那么这里适用任何原型查找都要使用的相同法则,沿着原型链(prototype chain)查找属性。所以在我们的例子里,如果我们的实例中不包含fullName
属性,那么就会沿原型链查找fullName
,先查找Person.prototype.fullName
,然后是Object.prototype.fullName
。
译后语
相当花时间,不过,值得~
BGM @ John Adorney - Beckoning
评论加载中...
由Disqus提供评论支持,如果评论长时间未加载,请飞跃长城。