JavaScript this - 初步介绍

程序人生

1.1 为什么要用this

如果对于有经验的 JavaScript 开发者来说 this 都是一种非常复杂的机制,那它到底有用在 哪里呢?真的值得我们付出这么大的代价学习吗?的确,在介绍怎么做之前我们需要先明白为什么。
下面我们来解释一下为什么要使用 this:
<script type="text/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 );
speak.call( me );
</script>
看不懂这段代码?不用担心!我们很快就会讲解。现在请暂时抛开这些问题,专注于为 什么。
这段代码可以在不同的上下文对象(me 和 you)中重复使用函数 identify() 和 speak(), 不用针对每个对象编写不同版本的函数。
如果不使用 this,那就需要给 identify() 和 speak() 显式传入一个上下文对象。
function identify(context){
    return context.name.toUpperCase();
}
function speak(context){
    var greeting = "Hello, I'm " + identify( context ); 
    console.log(greeting); 
}
identify( you );
// READER
speak( me );
//hello, i'm KYLE
然而,this 提供了一种更优雅的方式来隐式“传递”一个对象引用,因此可以将 API 设计 得更加简洁并且易于复用。
随着你的使用模式越来越复杂,显式传递上下文对象会让代码变得越来越混乱,使用 this 则不会这样。
当我们介绍对象和原型时,你就会明白函数可以自动引用合适的上下文对象 有多重要。

1.2 对 this 的误解

我们之后会解释 this 到底是如何工作的,但是首先需要消除一些关于 this 的错误认识。
有两种常见的对于 this 的解释,但是它们都是错误的。
1、人们很容易把 this 理解成指向函数自身,这个推断从英语的语法角度来说是说得通的。不过现在我们先来分析一下这个模式,让大家看到 this 并不像我们所想的那样指向函数本身。
我们想要记录一下函数 foo 被调用的次数,思考一下下面的代码:
<script type="text/javascript">
function foo(num) {
    console.log("foo: " + num );
    this.count++;
} 
foo.count = 0;
var i; 
for (i=0; i<10; i++) {
    if (i > 5){foo( i );}
} 
console.log(foo.count);
//0
</script>
console.log 语句产生了 4 条输出,证明 foo(..) 确实被调用了 4 次,但是 foo.count 仍然 是 0。显然从字面意思来理解 this 是错误的。
执行 foo.count = 0 时,的确向函数对象 foo 添加了一个属性 count。但是函数内部代码 this.count 中的 this 并不是指向那个函数对象,所以虽然属性名相同,根对象却并不相 同,困惑随之产生。

负责的开发者一定会问“如果我增加的 count 属性和预期的不一样,那我增加的是哪个 count ?”实际上,如果他深入探索的话,就会发现这段代码在无意中创建了一个全局变量 count。注意构造函数中的this才代表对象本身。

解决办法是强制 this 指向 foo 函数对象:
<script type="text/javascript">
function foo(num) {
    console.log("foo: " + num );
    this.count++;
} 
foo.count = 0;
var i; 
for (i=0; i<10; i++) {
    if (i > 5){
        foo.call(foo, i );
    }
} 
console.log(foo.count);
//4
</script>
第2种常见的误解是,this 指向函数的作用域。
这个问题有点复杂,因为在某种情况下它是正确的,但有时却是错误的。
思考一下下面的代码,它试图(但是没有成功)跨越边界:
<script type="text/javascript">
function foo() {
    var a = 2;
    this.bar();
}
function bar() {
    console.log( this.a + 'hi..');
}
foo();
//ReferenceError: a is not defined
</script>
这段代码中的错误不止一个。虽然这段代码看起来好像是我们故意写出来的例子,但是实 际上它出自一个公共社区中互助论坛的精华代码。这段代码非常完美(同时也令人伤感) 地展示了 this 多么容易误导人。
首先,这段代码试图通过 this.bar() 来引用 bar() 函数。这是绝对不可能成功的,我们之后会解释原因。调用 bar() 最自然的方法是省略前面的 this,直接使用词法引用标识符。
此外,编写这段代码的开发者还试图使用 this 联通 foo() 和 bar() 的词法作用域,从而让 bar() 可以访问 foo() 作用域里的变量 a。这是不可能实现的,你不能使用 this 来引用一 个词法作用域内部的东西。
每当你想要把 this 和词法作用域的查找混合使用时,一定要提醒自己,这是无法实现的。

1.3 this到底是什么

排除了一些错误理解之后,我们来看看 this 到底是一种什么样的机制。
之前我们说过 this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。
this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包 含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的其中一个属性,会在函数执行的过程中用到。
在下一节我们会学习如何寻找函数的调用位置,从而判断函数在执行过程中会如何绑定 this。

1.4 小结

对于那些没有投入时间学习 this 机制的 JavaScript 开发者来说,this 的绑定一直是一件非常令人困惑的事。this 是非常重要的,但是猜测、尝试并出错和盲目地从 Stack Overflow 上复制和粘贴答案并不能让你真正理解 this 的机制。
学习 this 的第一步是明白 this 既不指向函数自身也不指向函数的词法作用域,你也许被这样的解释误导过,但其实它们都是错误的。
this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。
Tags: JavaScript this
转自:http://www.hcoder.net/books/read/info/1216.html
  • 还没有评论,沙发等你来抢
*         

正在加载验证码......