原文地址:ECMA-262核心-美拓Blog
本文是对学习“ECMA-262-3 in detail”系列的一个总结和回顾,本文每个部分都有对ES3系列相应章节的链接,如果你有兴趣可以深入阅读一下。
我们从ECMAScript基础之一object的概念入手。
Object
通过 objects ECMAScript成为了一个高度抽象、基于对象的语言,对于原始类型,但是当需要的时候它们也可以转化为object。
object 是一个属性的集合
和一个prototype
对象,prototype可以是一个object或者null
在下面的描述中我们将看一下object的基本外形(figure),object的 prototype 可以使用内部的 [[Prototype]] 属性引用到,然而在图中我们用__<internal-property>__ 符号(notation)来表示内部属性,原型对象可以这样表示: __proto__
(这是一个非标准的,但在某些引擎中如:SpiderMonkey 这是一个真实的实现).
看例子:
1 var foo = { 2 x: 10, 3 y: 20 4 };
foo拥有两个显式的属性和一个隐式的可以引用到 ‘foo’的 prototype 的__proto__ 属性。
![Figure 1. A basic object with a prototype. Figure 1. A basic object with a prototype.](http://blog.meituo.net/wp-content/uploads/ecmascript_core/basic-object.png)
这些prototype 有什么用? 让我们那看看 原型链
的概念来回答这个问题。
原型链
Prototype对象也是简单的对象,也拥有自己的属性。如果一个prototype 拥有一个非null 的 prototype ,这称为原型链.
原型链是一个用来实现继承和分享属性的有限对象链
设想这种情况,我们有两个对象,他们仅仅有一些小的不同其他大部分是相同的,显然,对于一个良好设计的系统,我们希望重用这些相似的函数或者代码,而不是重复的在每个对象重新定义或者书写一次。在基于类(class-based)的系统中,这种代码重用
形式称为基于类的继承—将相似的功能行放到 类A 中,然后让 类B 和 类C 继承 A ,同时自己额外拥有小部分的改变。
ECMAScript 没用类(class)的概念,但是 代码重用的设计并没有很大的不同(虽然,在某些方面它比类继承更加的灵活),它是通过 原型链实现的。这种继承方法称为基于委托的继承(elegation based inheritance)(或者,更接近ECMAScript,基于原型的继承)。
和例子中的类’A',’B'和’C'相似,在ECMAScript中创建 对象 ‘a’,'b’和’c’ ,这样 对象 ‘ a ‘ 保存 ‘ b ‘ 和’c’ 相同的部分, ‘ b ‘ 和’c’ 仅保存他们另外的属性和方法。
1 var a = { 2 x: 10, 3 calculate: function (z) { 4 return this.x + this.y + z 5 } 6 }; 7 8 var b = { 9 y: 20, 10 __proto__: a 11 }; 12 13 var c = { 14 y: 30, 15 __proto__: a 16 }; 17 18 // call the inherited method 19 b.calculate(30); // 60 20 c.calculate(40); // 80
很简单吧? 我们看到对象 ‘b ‘ 和 ‘ c ‘ 能够使用在对象’a’ 中定义的’ calculate ‘方法,这是通过原型链实现的,规则很简单,如果一个属性或者方法在对象本身找不到(i.e. 对象没有这样自己的(own)属性)然后就会试图从原型链中寻找这个属性或方法,如果这个属性在prototype中没有找到,然后就会寻找 prototype的 prototype,就这样,直到整个原型链(其实和基于类的继承完全相同,当要获取一个继承的方法 — 这里我们寻找的是类链(class chain)),第一个找到同名 属性/方法会被使用,这样 这个找到的属性被称为 继承的属性。如果在整个原型链中没有找到这个属性,那么就会返回undefined 。
注意,在使用继承方法时,在找到的方法里指this 的是原始的(original)的对象,而不是 那个prototype对象。例如:在上面的例子里 ” this.y ” 是从 ‘ b ‘和 ‘ c ‘获得的,而不是从 ‘a’ 。 而 ‘ this.x ‘就是从’a’ 获得的,也是通过原型链机制。
如果一个对象没有显式的声明prototype,那么默认的 ‘ __proto__ ‘ 是 Object.prototype,Object “Object.prototype” 自己也有一个’ __proto__‘ ,这是链的终点,值为null。
下一个图显示了对象a,b,c的继承层次机构.
![Figure 2. A prototype chain. Figure 2. A prototype chain.](http://blog.meituo.net/wp-content/uploads/ecmascript_core/prototype-chain.png)
经常我们需要对象拥有 相同的或者相似的结构,(i.e.相同的属性) 但不同的属性值。这样的话我们或许需要一个通过特定的模式(specified patter)生成对象的constructor 。
Constructor
除了以特定的模式创建对象,constructor 函数做了另一件有用的事——为新 创建的对象自动设置一个prototype对象。这个prototype 对象存储在ConstructorFunction.prototype属性中。
E.g,我们可以重写前面的例子,使用constructor 来创建’b',’c’. 这样,a对象相当于 ’ Foo.prototype‘ 。
1 // a constructor function 2 function Foo(y) { 3 // which may create objects 4 // by specified pattern: they have after 5 // creation own "y" property 6 this.y = y; 7 } 8 9 // also "Foo.prototype" stores reference 10 // to the prototype of newly created objects, 11 // so we may use it to define shared/inherited 12 // properties or methods, so the same as in 13 // previous example we have: 14 15 // inherited property "x" 16 Foo.prototype.x = 10; 17 18 // and inherited method "calculate" 19 Foo.prototype.calculate = function (z) { 20 this.x + this.y + z; 21 }; 22 23 // now create our "b" and "c" 24 // objects using "pattern" Foo 25 var b = new Foo(20); 26 var c = new Foo(30); 27 28 // call the inherited method 29 b.calculate(30); // 60 30 c.calculate(40); // 80 31 32 // let's show that we reference 33 // properties we expect 34 35 console.log( 36 37 b.__proto__ === Foo.prototype, // true 38 c.__proto__ === Foo.prototype, // true 39 40 // also "Foo.prototype" automatically creates 41 // a special property "constructor", which is a 42 // reference to the constructor function itself; 43 // instances "b" and "c" may found it via 44 // delegation and use to check their constructor 45 46 b.constructor === Foo, // true 47 c.constructor === Foo, // true 48 Foo.prototype.constructor === Foo // true 49 50 b.calculate === b.__proto__.calculate, // true 51 b.__proto__.calculate === Foo.prototype.calculate // true 52 53 );
This code may be presented as the following relationship:
这些代码可以用以下关系呈现
![Figure 3. A constructor and objects relationship. Figure 3. A constructor and objects relationship.](http://blog.meituo.net/wp-content/uploads/ecmascript_core/constructor-proto-chain.png)
这个图再次显示了每个对象都有一个prototype,构造函数 ’Foo‘ 也有它自己的__proto__ ,其值为 ‘Function.prototype ‘ ,通过它的__proto__ 属性最终引用到 Object.prototype ,这样,’Foo.prototype’ 仅仅是 ’Foo‘的 一个显式属性。
正式的, 如果考虑到“classification”的概念 (我们刚才 定义(classified)了这个新的独立的对象— Foo),constructor 函数和原型对象的组合可以称为“class”。 实际上,e.g. Python 的 “first-class”动态类有完全相同的 属性/方法 的实现。 从这个观点看, Python类是ECMAScript中使用的基于委托继承的语法糖(syntactic sugar)。
关于这个主题可以在ES 3 系列的第七章中找到,其中有两部分: Chapter 7.1. OOP. The general theory, 那里您将发现各种各样的OOP范例的描述和形式及他们的与ECMAScript 的比较,Chapter 7.2. OOP. ECMAScript implementation,全身心的投入到ECMAScript的OOP 吧
现在我们了解了基本的对象,让我们看看在ECMAScript 中 runtime program execution 是如何实行的,这称为 执行上下文栈(execution context stack) ,每个抽象的元素都可以用对象表示,是的,ECMAScript 几乎处处都是表现为一个对象。
执行作用域栈
有三种ECMAScript 代码:全局代码, 函数代码 和 eval代码,每一种代码都他的执行上下文中执行,全局上下文只有一个,但可以有多个函数实例或eval执行上下文。每次调用函数就进入到了函数的执行上下文,并执行函数代码的类型。每次调用eval 就进入了eval的执行上下文并执行其代码。
注意:一个函数可以创建无限个上下文,因为每次调用(即使是递归)函数产生一个拥有新的上下文状态的上下文。
1 function foo(bar) {} 2 3 // call the same function, 4 // generate three different 5 // contexts in each call, with 6 // different context state (e.g. value 7 // of the "bar" argument) 8 9 foo(10); 10 foo(20); 11 foo(30);
执行上下文可以激活另一个上下文,e.g. 函数调用另一个函数(或者全局上下文调用全局函数)等等,逻辑上,这将作为栈来执行,称为执行上下文栈。
激活另一个上下文的上下文称为 caller,那个被激活的上下文称为callee.callee 同时可以是caller(e.g. 一个被全局上下文调用的函数调用某些内部函数)
当一个caller激活(调用)一个callee,caller 挂起它的执行并将控制权交给callee,callee被推入栈中成为一个正在运行(激活)的执行上下文,当callee的上下文结束,它将控制权返还给caller,caller 的上下文将继续执行(当然有可能激活另一个上下文)直到结束,等等。callee 或者返回或者因异常退出,一个不能被接住的异常可能会跳出(从栈中弹出)一个或多个执行上下文。
I.e 所有的ECMAScript 程序执行(program runtime) 都以执行上下文(EC)栈呈现。栈的顶部是激活的上下文。
![Figure 4. An execution context stack. Figure 4. An execution context stack.](http://blog.meituo.net/wp-content/uploads/ecmascript_core/ec-stack.png)
当程序开始执行,就进入了一个全局执行上下文,它是栈的底部也是栈的第一个元素。然后全局代码提供了一些初始化,创建必需的对象和函数,在全局上下文的执行期间,它可以激活其他一些(已经创建的)函数并进入这个函数的执行上下文将新的元素推入栈中,等等。当初始化完成,执行系统就等待事件触发(e.g 用户点击),这些事件将激活某些函数并进入一个新的执行上下文。
在下一个图中,某些函数上下文 作为‘EC1’ ,全局上下文作为 ‘Global EC’,当从全局上下文进入或者退出’EC 1′,栈有以下的更改过程 :
![Figure 5. An execution context stack changes. Figure 5. An execution context stack changes.](http://blog.meituo.net/wp-content/uploads/ecmascript_core/ec-stack-changes.png)
这是真实的存在的,ECMAScript运行系统就是这样管理代码的执行。
关于ECMAScript执行上下文更多的信息可以在这里找到 Chapter 1. Execution context.
正如我们所说,栈中的每一个执行上下文都表现为一个对象,让我们看看它的结构和上下文执行它的代码所需要的状态(state)
五、执行上下文(Execution Context)
执行上下文可以抽象为一个简单的对象。每个上下文包含一系列属性(我们称之为 上下文状态(context’s state) ) 用以跟踪相关代码的执行过程。下图展示了上下文的结构:
![Figure 6. An execution context structure. Figure 6. An execution context structure.](http://blog.meituo.net/wp-content/uploads/ecmascript_core/execution-context.png)
除了这3个所需要的属性(变量对象(variable object),this指针(this value),作用域链(scope chain) ),执行上下文根据具体实现还可以具有任意额外属性。
接着,让我们仔细说明上下文的重要属性。
六、变量对象(Variable Object)
变量对象(variable object) 是与执行上下文相关的 数据作用域(scope of data) 。它是与上下文关联的特殊对象,用于存储被定义在上下文中的 变量(variables) 和 函数声明(function declarations) 。
请注意,变量对象不包含 函数表达式(function expressions) (与 函数声明(function declarations) 比较)。
变量对象是一个抽象的概念。在不同的上下文中,它以不同的对象[译注:意思应为对象集合]来表示。举例来说,全局上下文中,变量对象是 全局对象本身(global object itself) (这就是我们能够通过属性名称引用全局对象的全局变量)。
让我们看看下面的例子:
1 var foo = 10; 2 3 function bar() {} // 函数声明 4 (function baz() {}); // 函数表达式 5 6 console.log( 7 this.foo == foo, // true 8 window.bar == bar // true 9 ); 10 11 console.log(baz); // 引用错误,baz没有被定义
全局上下文中的变量对象(VO)会有如下属性:
![Figure 7. The global variable object. Figure 7. The global variable object.](http://blog.meituo.net/wp-content/uploads/ecmascript_core/variable-object.png)
如上所示,函数“baz”如果作为函数表达式则不被不被包含于变量对象。这就是在函数外部尝试访问产生 引应用错误(ReferenceError) 的原因。
请注意,ECMAScript和其他语言相比(比如C/C++),仅有函数能够创建新的作用域。在函数内部定义的变量与内部函数,在外部非直接可见并且不污染全局对象。
使用 eval 的时候,我们同样会使用一个新的(eval创建)执行上下文。eval会使用全局变量对象或调用者的变量对象(eval的调用来源)。
那函数以及自身的变量对象又是怎样的呢?在一个函数上下文中,变量对象被表示为 活动对象(activation object)。
七、活动对象(Activation object)
当函数被调用者激活,一个特殊的对象——活动对象(activation object) 将被创建。它包含 形式参数(formal parameters) 与特殊 参数(arguments) 对象(具有索引属性的形参键值表(map)[译注:表现为数组])。活动对象在函数上下文中作为变量对象使用。
即:函数的变量对象保持不变,但除去存储变量与函数声明之外,还包含形参以及特殊对象 arguments 。
考虑下面的例子
1 function foo(x, y) { 2 var z = 30; 3 function bar() {} // FD 4 (function baz() {}); // FE 5 } 6 7 foo(10, 20);
“foo”函数上下文的下一个活动对象(AO)如下图所示:
Figure 8. An activation object.
图 8. 活动对象
由上可见函数表达式“baz”并未包含在变量/活动对象中。
对于这个话题的完整描述与所有巧妙示例(比如变量与函数声明的“挂起(hoisting)”)[译注:hoisting的语义需要推敲~~]可以在第2章. 可变对象(Chapter 2. Variable object)找到。
让我们来看下一小节的内容。众所周知,在ECMAScript中我们可以使用内部函数,而在内部函数中,我们或许会引用父函数或全局上下文中的变量。如果我们将函数的 变量对象 命名为上下文的 作用域对象(scope object) ,那么和之前讨论的原型链类似,也有一个所谓的 作用域链(scope chain) 。
八、作用域链(Scope Chains)
作用域链是一个 对象列表(list of objects) ,用以检索上下文代码中出现的 标识符(identifiers) 。
作用域链规则简单并且与原型链相似:如果一个变量不是在自身的作用域内(自身 变量/活动 对象),那么就会在它的外部作用域中查找。
考虑到上下文,标识符为:变量名,函数声明,形式参数等等。 在函数代码中,如果标识符不是一个本地变量/函数/形参,那么就被称之为 自由变量(free variable) 。作用域链正是用于查找这些自由变量。
一般情况下,作用域链是 父变量对象(parent variable objects) 列表外加(在作用域链头部)函数的 自身变量/活动对象(own variable/activation object) 。然而作用域链也会包含其他对象,举例来说某些对象会在上下文的执行过程中,动态载入作用域链——如 with-objects 或 catch-clauses 创建的特殊对象。[译注:with-objects指的是with语句,产生的临时作用域对象;catch-clauses指的是catch从句,如catch(e),这会产生异常对象,导致作用域变更]
当查找标识符的时候,会从作用域链的活动对象部分开始查找,然后(如果标识符没有在活动对象中找到)查找作用域链的顶部,循环往复,就像作用域链那样。
1 var x = 10; 2 3 (function foo() { 4 var y = 20; 5 (function bar() { 6 var z = 30; 7 // "x"和"y"是自由变量 8 9 // 会在作用域链的下一个对象中找到(函数”bar”的互动对象之后) 10 console.log(x + y + z); 11 })(); 12 })();
我们可以通过隐含的__parent__ 属性来设定作用域链的范围,__parent__ 指向链表的下一个对象。这个尝试可以在真实的Rhino代码(real Rhino code)中测试[译注:Rhino为Mozilla的开源项目,基于Java实现的ECMAScript引擎],并且是 ES5词法环境(ES5 lexical environments) 中实际使用的技术(被命名为outer的链接)。作用域链还可以通过数组展现。通过使用 __parent__ 的概念,我们可以用下图展现上面的例子(父变量对象存储在函数的[[Scope]]属性内):
![Figure 9. A scope chain. Figure 9. A scope chain.](http://blog.meituo.net/wp-content/uploads/ecmascript_core/scope-chain.png)
图 9. 作用域链
代码执行时,作用域链可能会被 with语句(with-statement) 和 catch从句(catch-clause) 对象扩大(增长)。此外,由于这些对象是简单对象,他们拥有原型(和原型链)。实际上作用域链表查找分为 两个维度(two-dimensional) :(1)首先考虑自身的作用域链表指针,(2)对于每一个作用域链表指针,需要深入到指针自身的原型链当中去(如果作用域链表的节点拥有原型)。
举例:
1 Object.prototype.x = 10; 2 3 var w = 20; 4 var y = 30; 5 6 // in SpiderMonkey global object 7 // i.e. variable object of the global 8 // context inherits from "Object.prototype", 9 // so we may refer "not defined global 10 // variable x", which is found in 11 // the prototype chain 12 13 console.log(x); // 10 14 15 (function foo() { 16 17 // "foo" local variables 18 var x = 100; 19 var w = 40; 20 21 // "x" is found in the 22 // "Object.prototype", because 23 // {z: 50} inherits from it 24 25 with ({z: 50}) { 26 console.log(w, x, y , z); // 40, 10, 30, 50 27 } 28 29 // after "with" object is removed 30 // from the scope chain, "x" is 31 // again found in the AO of "foo" context; 32 // variable "w" is also local 33 console.log(x, w); // 100, 40 34 35 // and that's how we may refer 36 // shadowed global "w" variable in 37 // the browser host environment 38 console.log(window.w); // 20 39 40 })();
结构如下(这表明当我们进入 __parent__ 指针之前, 第一个 __proto__ 链会先背考虑):
![Figure 10. A \" title=](http://blog.meituo.net/wp-content/uploads/ecmascript_core/scope-chain-with.png)
请注意,全局对象并非在所有实现中都会继承自“Object.prototype”。上图描述的情景(从全局上下文中引用了未定义的变量”x”)可以在SpiderMonkey测试。
只要所有外部函数的变量对象都存在,那么从内部函数引用外部数据则没有特别之处——我们只要遍历作用域链表,查找所需变量。然而,如上文所提及,当一个上下文终止之后,其状态与自身将会被 销毁(destroyed),同时内部函数将会从外部函数中返回。此外,这个返回的函数之后可能会在其他的上下文中被激活,那么如果一个之前被终止的含有一些自由变量的上下文又被激活将会怎样?通常来说,解决这个问题的概念在ECMAScript中与作用域链直接相关,被称为 (词法)闭包((lexical) closure)。
九、闭包(Closures)
在ECMAScript中,函数是“第一类”对象。这个名词意味着函数可以作为参数被传递给其他函数使用 (在这种情况下,函数被称为“funargs”——“functional arguments”的缩写[译注:这里不知翻译为泛函参数是否恰当])。接收“funargs”的函数被称之为 高阶函数(higher-order functions) ,或者更接近数学概念的话,被称为 运算符(operators) 。其他函数的运行时也会返回函数,这些返回的函数被称为 function valued 函数 (有functional value 的函数)。
“funargs”与“functional values”有两个概念上的问题,这两个子问题被称为“Funarg problem” (“泛函参数问题”)。要准确解决泛函参数问题,需要引入 闭包(closures) 到的概念。让我们仔细描述这两个问题(我们可以见到,在ECMAScript中使用了函数的[[Scope]]属性来解决这个问题)。
“funarg problem”的一个子问题是“upward funarg problem”[译注:或许可以翻译为:向上查找的函数参数问题]。当一个函数从其他函数返回到外部的时候,这个问题将会出现。要能够在外部上下文结束时,进入外部上下文的变量,内部函数 在创建的时候(at creation moment) 需要将之存储进[[Scope]]属性的父元素的作用域中。然后当函数被激活时,上下文的作用域链表现为活动对象与[[Scope]]属性的组合(事实上,可以在上图见到):
作用域链 = 活动对象 + [[Scope]]
请注意,最主要的事情是——函数在被创建时保存外部作用域,是因为这个 被保存的作用域链(saved scope chain) 将会在未来的函数调用中用于变量查找。
1 function foo() { 2 var x = 10; 3 return function bar() { 4 console.log(x); 5 }; 6 } 7 8 // "foo" returns also a function 9 // and this returned function uses 10 // free variable "x" 11 12 var returnedFunction = foo(); 13 14 // global variable "x" 15 var x = 20; 16 17 // execution of the returned function 18 returnedFunction(); // 10, but not 20
这种类型的作用域被称为 静态作用域(static (or lexical) scope) 。可见变量“x”可以在函数”bar”返回的被保存的[[Scope]]中。通常来说,当上面代码中,变量”x”被设为20而不为10的时候还有一个 动态作用域(dynamic scope) 。 但是ECMAScript并未使用动态作用域。
“funarg problem”的第二个子问题是一个“downward funarg problem” [译注:或许可以翻译为:向内查找的函数参数问题]。在这个例子中,外部上下文可能存在,但是对于解析标识符会产生二义性。问题是:哪一个作用域中的标识符的值会被使用的呢——是在函数创建时还是在函数执行时产生的作用域呢(比如调用者的作用域)?为了避免二义性与确立闭包,静态作用域会被使用:
我们可以做出总结: 静态作用域是在一种语言中使用闭包的 必备需求(obligatory requirement) 。然而某些语言会同时提供静态作用域与动态作用域,以供程序员选择使用闭包。因ECMAScript中只有静态作用域被使用对于“funarg problem”的两个子问题,我们拥有解决方案),结论如下:ECMAScript在技术上使用函数的[[Scope]]属性,从而对闭包有完整的支持。现在我们可以给闭包一个准确的定义:
闭包由 代码块(ECMAScript中这是一个函数) 与 被静态/词法存储的所有外部作用域 组合而成。因此,函数可以通过被存储的作用域简单引用自由变量。
请注意,因为每一个(一般)函数在创建时保存了[[Scope]],理论上,ECMAScript中所有函数都是闭包。
另一件重要的事情是,一些函数可能会有同样的外部作用域(当有两个内部/全局函数式,这是很常见的情况)。在这个例子中[[Scope]]属性是由所有拥有同样外部作用域的函数共享的。在某个闭包中产生的变量变化将会在其他闭包中读取变量时 反映(reflected) 出来:
1 function baz() { 2 var x = 1; 3 return { 4 foo: function foo() { return ++x; }, 5 bar: function bar() { return --x; } 6 }; 7 } 8 9 var closures = baz(); 10 11 console.log( 12 closures.foo(), // 2 13 closures.bar() // 1 14 );
代码如下图所示:
![Figure 11. A shared [[Scope]]. Figure 11. A shared [[Scope]].](http://blog.meituo.net/wp-content/uploads/ecmascript_core/shared-scope.png)
在某个循环中创建多个函数时,上图会引发一个困惑。如果在创建的函数中使用循环变量(如”k”),那么所有的函数都使用同样的循环变量,导致一些程序员经常会得不到预期值。现在清楚为什么会产生如此问题了——因为所有函数共享同一个[[Scope]],其中循环变量为最后一次复赋值。
1 var data = []; 2 3 for (var k = 0; k < 3; k++) { 4 data[k] = function () { 5 alert(k); 6 }; 7 } 8 9 data[0](); // 3, but not 0 10 data[1](); // 3, but not 1 11 data[2](); // 3, but not 2
有一些用以解决这类问题的技术。其中一种技巧是在作用域链中提供一个额外的对象,比如使用其他函数:
1 var data = []; 2 3 for (var k = 0; k < 3; k++) { 4 data[k] = (function (x) { 5 return function () { 6 alert(x); 7 }; 8 })(k); // pass "k" value 9 } 10 11 // now it is correct 12 data[0](); // 0 13 data[1](); // 1 14 data[2](); // 2
闭包理论的深入研究与具体实践可以在 第6章.闭包(Chapter 6. Closures)中找到。如果想得到关于作用域链的更多信息,可以参照 第4章作用域链(Chapter 4. Scope chain)。
下一章节将会讨论一个执行上下文的最后一个属性——this指针的概念。
十、This指针(This Value)
this 指针是一个与执行上下文相关的特殊对象。因此,它也会被命名为 上下文对象(context object)(激活执行上下文的上下文)。
任何对象都可以作为上下文的this值。我想再次澄清对与ECMAScript中,与执行上下文相关的一些描述——特别是this的误解。通常,this 被错误地,描述为变量对象的属性。最近比如在这本书中就发现了(尽管书中提及this的那一章还不错)。 请牢记:
this 是执行上下文的属性,但不是变量对象的属性。
这个特征是很重要的。因为同变量比较,this 从不参与变量解析的过程。即当在代码中访问this,它的值是从执行上下文中直接得到而不需要任何作用域链的查找。this 的值在进入上下文时即被确立。
和ECMAScript相比,Python有自己的 self 参数作为单独的变量用于解析同样问题。ECMAScript中,this 不能被重新赋值,因为this不是一个变量,并且不属于变量对象。
在全局上下文中,this 就是全局对象本身(这意味着,this等于全局对象):
1 var x = 10; 2 3 console.log( 4 x, // 10 5 this.x, // 10 6 window.x // 10 7 );
在函数上下文中,每一次单独的函数调用,this 的值都不相同。这里this的值是由call函数表达式提供的调用者本身(函数被激活的一个方法)。举例来说,下面的函数“foo”被全局上下文调用,是一个被调用者。然我们看看这个例子,同样的函数代码对于不同的调用(使用不同的函数激活方式),调用者提供的this值是怎样不同的:
1 // the code of the "foo" function 2 // never changes, but the "this" value 3 // differs in every activation 4 5 function foo() { 6 alert(this); 7 } 8 9 // caller activates "foo" (callee) and 10 // provides "this" for the callee 11 12 foo(); // global object 13 foo.prototype.constructor(); // foo.prototype 14 15 var bar = { 16 baz: foo 17 }; 18 19 bar.baz(); // bar 20 21 (bar.baz)(); // also bar 22 (bar.baz = bar.baz)(); // but here is global object 23 (bar.baz, bar.baz)(); // also global object 24 (false || bar.baz)(); // also global object 25 26 var otherFoo = bar.baz; 27 otherFoo(); // again global object
如果要深入思考每一次函数调用中,this值的变化(更重要的是怎样变化),你可以阅读 第3章. This(Chapter 3. This) 。上文所提及的情况都会在第三章内详细讨论。
十一、总结(Conclusion)
在此我们完成了一个简短的概述。尽管看来不是那么简短,但是这些话题若要完整表述完毕,则需要一整本书。.我们没有提及两个重要话题:泛函(functions) (以及不同类型的函数之间的不同,比如函数声明与函数表达式)与ECMAScript的 求值策略(evaluation strategy) 。这两个话题可以分别查阅 第5章. 泛函(Chapter 5. Functions) 与 第8章. 求值策略(Chapter 8. Evaluation strategy)。
如果你有任何评论,问题或者补充,我很欢迎在文章评论中讨论。
祝大家学习ECMAScript顺利。