前言
this
是JavaScript中最令人困惑的关键字之一,他会自动在每个function作用域中生成,但是this
实际上是指向什么对很多资深的JS开发人员来说也是困惑的,对于this这个机制而言并没有这么先进,但是开发者常常会将它複杂化或令人困惑的方式引用他,如果没有对于this的充分了解那么this这个关键字将会变得跟魔术一样神奇。
Why 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 ); // KYLEidentify.call( you ); // READERspeak.call( me ); // Hello, I'm KYLEspeak.call( you ); // Hello, I'm READER
上面的程式中允许identify(...)
与speak(...)
重複使用me
与you
object,而不是每个object都需要自己的function。
如果不使用this
,你也可以将object显性的传递给function。
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( you ); // READERspeak( me ); // Hello, I'm KYLE
以上面的例子来说,可以看到虽然this
不是必要的,但是他可以更优雅的传递object,从而使API个简洁并易于重新使用;而随着模式更加複杂,将上下文使用显性参数传递的方式会比隐式使用this的传递更加混乱。
Confusions
开发者对于this
如果是使用字面上的意思去解释他的时候往往都会造成混乱,最常见的有两种假设,但他们都是错的。
itself
第一种假设是this
代表着函数本身,当你使用递归(在函式内部呼叫function
)或一个在首次调用可以解除绑定的事件处理
时会在自身函式中呼叫function。
对于刚接触JS的开发者,他们认为由于function是object(所有function在JS中都是object)所以可以在函数调用之间储存状态(function属性中的值),虽然这是可行的但是他的功能有限。
function foo(num) {console.log( "foo: " + num );this.count++; // keep track of how many times `foo` is called}foo.count = 0;for (var i=0; i<10; i++) {if (i > 5) {foo( i );}}// foo: 6// foo: 7// foo: 8// foo: 9// how many times was `foo` called?console.log( foo.count ); // 0 -- WTF?
上面的程式中,即使我们对于foo(...)调用了四次但是foo.count
依然是0,虽然我们确实有在foo的属性中加入了count,但是因为this
并不是只向该function的object,就算属性名称相同但是所指向的object不同所以依然是0。
function foo(num) {console.log( "foo: " + num );// keep track of how many times `foo` is called// Note: `this` IS actually `foo` now, based on// how `foo` is called (see below)this.count++;}foo.count = 0;var i;for (i=0; i<10; i++) {if (i > 5) {// using `call(..)`, we ensure the `this`// points at the function object (`foo`) itselffoo.call( foo, i );}}// foo: 6// foo: 7// foo: 8// foo: 9console.log( foo.count ); // 4
把原本的程式更改为上面就可以顺利的访问到foo中的count了,你可能会感到困惑,但是我们会在后面详细地做解释。
Its Scope
还有一个主要的误解,this
是function的作用域,从某种意义上来说他是对的,但另一种意义上来说他是完全错误的。
对于scope来说他确实有点像object,在他的範围中具有每个可用的标示符的属性,但是JS引擎无法访问到scope object。
function foo() {var a = 2;this.bar();}function bar() {console.log( this.a );}foo(); //undefined
在上面的程式中使用this.bar()
来调用bar(...)虽然在这里行得通(之后的章节会解释)但是其实是错误的,对于呼叫bar(...)
最自然的使用方法就是直接引用他的标示符,但是写上面这个程式的开发者希望将bar(...)
与foo(...)
的lexcial scope连结再一起以便bar(...)可以访问到a,这样的连结是不可能的
,不能够通过使用this来连结两个lexcial scope。
What's this?
对于this
来说,他不是取决于开发者时间所绑定而是运行时绑定,他是基于上下文取决于函数调用的条件
,这个绑定与函式声明的位置无关而是与函式调用的方式有关。
当呼叫一个function时会创建一个启动纪录(执行上下文)
,这个纪录包含有关从何处调用function的信息、呼叫此function的方式、传递的参数等等;this会在function执行的期间使用纪录的属性之一。
参考文献:
You Don't Know JavaScript