原文:http://dmitrysoshnikov.com/ecmascript/javascript-the-core/javascript
这篇文章是「深刻ECMA-262-3」系列的一个概览和摘要。每一个部分都包含了对应章节的连接,因此你能够阅读它们以便对其有更深的理解。java
面向读者:经验丰富的程序员,专家。程序员
咱们以思考对象的概念作为开始,这是ECMAScript的基础。express
ECMAScript作为一个高度抽象的面向对象语言,是经过对象来交互的。即便ECMAScript里边也有基本类型,可是,当须要的时候,它们也会被转换成对象。api
一个对象就是一个属性集合,并拥有一个独立的prototype(原型)对象。这个prototype能够是一个对象或者null。
让咱们看一个关于对象的基本例子。一个对象的prototype是之内部的[[Prototype]]属性来引用的。可是,在示意图里边咱们将会使用__<internal-property>__
下划线标记来替代两个括号,对于prototype对象来讲是:__proto__
。数组
对于如下代码:闭包
var foo = { x: 10, y: 20 };
咱们拥有一个这样的结构,两个明显的自身属性和一个隐含的__proto__
属性,这个属性是对foo
原型对象的引用:ecmascript
这些prototype有什么用?让咱们以原型链(prototype chain)的概念来回答这个问题。ide
原型对象也是简单的对象而且能够拥有它们本身的原型。若是一个原型对象的原型是一个非null的引用,那么以此类推,这就叫做原型链。函数
原型链是一个用来实现继承和共享属性的有限对象链。
考虑这么一个状况,咱们拥有两个对象,它们之间只有一小部分不一样,其余部分都相同。显然,对于一个设计良好的系统,咱们将会重用类似的功能/代码,而不是在每一个单独的对象中重复它。在基于类的系统中,这个代码重用风格叫做类继承-你把类似的功能放入类 A
中,而后类 B
和类 C
继承类 A
,而且拥有它们本身的一些小的额外变更。
ECMAScript中没有类的概念。可是,代码重用的风格并无太多不一样(尽管从某些方面来讲比基于类(class-based)的方式要更加灵活)而且经过原型链来实现。这种继承方式叫做委托继承(delegation based inheritance)(或者,更贴近ECMAScript一些,叫做原型继承(prototype based inheritance))。
跟例子中的类A
,B
,C
类似,在ECMAScript中你建立对象:a
,b
,c
。因而,对象a
中存储对象b
和c
中通用的部分。而后b
和c
只存储它们自身的额外属性或者方法。
var a = { x: 10, calculate: function (z) { return this.x + this.y + z } }; var b = { y: 20, __proto__: a }; var c = { y: 30, __proto__: a }; // call the inherited method b.calculate(30); // 60 c.calculate(40); // 80
足够简单,是否是?咱们看到b
和c
访问到了在对象a
中定义的calculate
方法。这是经过原型链实现的。
规则很简单:若是一个属性或者一个方法在对象自身中没法找到(也就是对象自身没有一个那样的属性),而后它会尝试在原型链中寻找这个属性/方法。若是这个属性在原型中没有查找到,那么将会查找这个原型的原型,以此类推,遍历整个原型链(固然这在类继承中也是同样的,当解析一个继承的方法的时候-咱们遍历class链( class chain))。第一个被查找到的同名属性/方法会被使用。所以,一个被查找到的属性叫做继承属性。若是在遍历了整个原型链以后仍是没有查找到这个属性的话,返回undefined
值。
注意,继承方法中所使用的this
的值被设置为原始对象,而并非在其中查找到这个方法的(原型)对象。也就是,在上面的例子中this.y
取的是b
和c
中的值,而不是a
中的值。可是,this.x
是取的是a
中的值,而且又一次经过原型链机制完成。
若是没有明确为一个对象指定原型,那么它将会使用__proto__
的默认值-Object.prototype
。Object.prototype
对象自身也有一个__proto__
属性,这是原型链的终点而且值为null
。
下一张图展现了对象a
,b
,c
之间的继承层级:
注意:
ES5标准化了一个实现原型继承的可选方法,即便用Object.create
函数:
var b = Object.create(a, {y: {value: 20}}); var c = Object.create(a, {y: {value: 30}});
你能够在对应的章节获取到更多关于ES5新API的信息。
ES6标准化了 __proto__
属性,而且能够在对象初始化的时候使用它。
一般状况下须要对象拥有相同或者类似的状态结构(也就是相同的属性集合),赋以不一样的状态值。在这个状况下咱们可能须要使用构造函数(constructor function),其以指定的模式来创造对象。
除了以指定模式建立对象以外,构造函数也作了另外一个有用的事情-它自动地为新建立的对象设置一个原型对象。这个原型对象存储在ConstructorFunction.prototype
属性中。
换句话说,咱们可使用构造函数来重写上一个拥有对象b
和对象c
的例子。所以,对象a
(一个原型对象)的角色由Foo.prototype
来扮演:
// a constructor function function Foo(y) { // which may create objects // by specified pattern: they have after // creation own "y" property this.y = y; } // also "Foo.prototype" stores reference // to the prototype of newly created objects, // so we may use it to define shared/inherited // properties or methods, so the same as in // previous example we have: // inherited property "x" Foo.prototype.x = 10; // and inherited method "calculate" Foo.prototype.calculate = function (z) { return this.x + this.y + z; }; // now create our "b" and "c" // objects using "pattern" Foo var b = new Foo(20); var c = new Foo(30); // call the inherited method b.calculate(30); // 60 c.calculate(40); // 80 // let's show that we reference // properties we expect console.log( b.__proto__ === Foo.prototype, // true c.__proto__ === Foo.prototype, // true // also "Foo.prototype" automatically creates // a special property "constructor", which is a // reference to the constructor function itself; // instances "b" and "c" may found it via // delegation and use to check their constructor b.constructor === Foo, // true c.constructor === Foo, // true Foo.prototype.constructor === Foo // true b.calculate === b.__proto__.calculate, // true b.__proto__.calculate === Foo.prototype.calculate // true );
这个代码能够表示为以下关系:
这张图又一次说明了每一个对象都有一个原型。构造函数Foo
也有本身的__proto__
,值为Function.prototype
,Function.prototype
也经过其__proto__
属性关联到Object.prototype
。所以,重申一下,Foo.prototype
就是Foo
的一个明确的属性,指向对象b
和对象c
的原型。
正式来讲,若是思考一下分类的概念(而且咱们已经对Foo
进行了分类),那么构造函数和原型对象合在一块儿能够叫做「类」。实际上,举个例子,Python的第一级(first-class)动态类(dynamic classes)显然是以一样的属性/方法
处理方案来实现的。从这个角度来讲,Python中的类就是ECMAScript使用的委托继承的一个语法糖。
注意: 在ES6中「类」的概念被标准化了,而且实际上以一种构建在构造函数上面的语法糖来实现,就像上面描述的同样。从这个角度来看原型链成为了类继承的一种具体实现方式:
// ES6 class Foo { constructor(name) { this._name = name; } getName() { return this._name; } } class Bar extends Foo { getName() { return super.getName() + ' Doe'; } } var bar = new Bar('John'); console.log(bar.getName()); // John Doe
有关这个主题的完整、详细的解释能够在ES3系列的第七章找到。分为两个部分:7.1 面向对象.基本理论,在那里你将会找到对各类面向对象范例、风格的描述以及它们和ECMAScript之间的对比,而后在7.2 面向对象.ECMAScript实现,是对ECMAScript中面向对象的介绍。
如今,在咱们知道了对象的基础以后,让咱们看看运行时程序的执行(runtime program execution)在ECMAScript中是如何实现的。这叫做执行上下文栈(execution context stack),其中的每一个元素也能够抽象成为一个对象。是的,ECMAScript几乎在任何地方都和对象的概念打交道;)
这里有三种类型的ECMAScript代码:全局代码、函数代码和eval代码。每一个代码是在其执行上下文(execution context)中被求值的。这里只有一个全局上下文,可能有多个函数执行上下文以及eval执行上下文。对一个函数的每次调用,会进入到函数执行上下文中,并对函数代码类型进行求值。每次对eval
函数进行调用,会进入eval执行上下文并对其代码进行求值。
注意,一个函数可能会建立无数的上下文,由于对函数的每次调用(即便这个函数递归的调用本身)都会生成一个具备新状态的上下文:
function foo(bar) {} // call the same function, // generate three different // contexts in each call, with // different context state (e.g. value // of the "bar" argument) foo(10); foo(20); foo(30);
一个执行上下文可能会触发另外一个上下文,好比,一个函数调用另外一个函数(或者在全局上下文中调用一个全局函数),等等。从逻辑上来讲,这是以栈的形式实现的,它叫做执行上下文栈。
一个触发其余上下文的上下文叫做caller。被触发的上下文叫做callee。callee在同一时间多是一些其余callee的caller(好比,一个在全局上下文中被调用的函数,以后调用了一些内部函数)。
当一个caller触发(调用)了一个callee,这个caller会暂缓自身的执行,而后把控制权传递给callee。这个callee被push到栈中,并成为一个运行中(活动的)执行上下文。在callee的上下文结束后,它会把控制权返回给caller,而后caller的上下文继续执行(它可能触发其余上下文)直到它结束,以此类推。callee可能简单的返回或者因为异常而退出。一个抛出的可是没有被捕获的异常可能退出(从栈中pop)一个或者多个上下文。
换句话说,全部ECMAScript_程序的运行时能够用执行上下文(EC)栈来表示,栈顶是当前活跃_(active)上下文:
当程序开始的时候它会进入全局执行上下文,此上下文位于栈底而且是栈中的第一个元素。而后全局代码进行一些初始化,建立须要的对象和函数。在全局上下文的执行过程当中,它的代码可能触发其余(已经建立完成的)函数,这些函数将会进入它们本身的执行上下文,向栈中push新的元素,以此类推。当初始化完成以后,运行时系统(runtime system)就会等待一些事件(好比,用户鼠标点击),这些事件将会触发一些函数,从而进入新的执行上下文中。
在下个图中,拥有一些函数上下文EC1
和全局上下文Global EC
,当EC1
进入和退出全局上下文的时候下面的栈将会发生变化:
这就是ECMAScript的运行时系统如何真正地管理代码执行的。
更多有关ECMAScript中执行上下文的信息能够在对应的第一章 执行上下文中获取。
像咱们所说的,栈中的每一个执行上下文均可以用一个对象来表示。让咱们来看看它的结构以及一个上下文到底须要什么状态(什么属性)来执行它的代码。
一个执行上下文能够抽象的表示为一个简单的对象。每个执行上下文拥有一些属性(能够叫做上下文状态)用来跟踪和它相关的代码的执行过程。在下图中展现了一个上下文的结构:
除了这三个必需的属性(一个变量对象(variable objec),一个this
值以及一个做用域链(scope chain))以外,执行上下文能够拥有任何附加的状态,这取决于实现。
让咱们详细看看上下文中的这些重要的属性。
变量对象是与执行上下文相关的数据做用域。它是一个与上下文相关的特殊对象,其中存储了在上下文中定义的变量和函数声明。
注意,函数表达式(与函数声明相对)不包含在变量对象之中。
变量对象是一个抽象概念。对于不一样的上下文类型,在物理上,是使用不一样的对象。好比,在全局上下文中变量对象就是全局对象自己(这就是为何咱们能够经过全局对象的属性名来关联全局变量)。
让咱们在全局执行上下文中考虑下面这个例子:
var foo = 10; function bar() {} // function declaration, FD (function baz() {}); // function expression, FE console.log( this.foo == foo, // true window.bar == bar // true ); console.log(baz); // ReferenceError, "baz" is not defined
以后,全局上下文的变量对象(variable objec,简称VO)将会拥有以下属性:
再看一遍,函数baz
是一个函数表达式,没有被包含在变量对象之中。这就是为何当咱们想要在函数自身以外访问它的时候会出现ReferenceError
。
注意,与其余语言(好比C/C++)相比,在ECMAScript中只有函数能够建立一个新的做用域。在函数做用域中所定义的变量和内部函数在函数外边是不能直接访问到的,并且并不会污染全局变量对象。
使用eval
咱们也会进入一个新的(eval类型)执行上下文。不管如何,eval
使用全局的变量对象或者使用caller(好比eval
被调用时所在的函数)的变量对象。
那么函数和它的变量对象是怎么样的?在函数上下文中,变量对象是以活动对象(activation object)来表示的。
当一个函数被caller所触发(被调用),一个特殊的对象,叫做活动对象(activation object)将会被建立。这个对象中包含形参和那个特殊的arguments
对象(是对形参的一个映射,可是值是经过索引来获取)。活动对象以后会作为函数上下文的变量对象来使用。
换句话说,函数的变量对象也是一个一样简单的变量对象,可是除了变量和函数声明以外,它还存储了形参和arguments
对象,并叫做活动对象。
考虑以下例子:
function foo(x, y) { var z = 30; function bar() {} // FD (function baz() {}); // FE } foo(10, 20);
咱们看下函数foo
的上下文中的活动对象(activation object,简称AO):
而且_函数表达式_baz
仍是没有被包含在变量/活动对象中。
关于这个主题全部细节方面(像变量和函数声明的提高问题(hoisting))的完整描述能够在同名的章节第二章 变量对象中找到。
注意,在ES5中变量对象和活动对象被并入了词法环境模型(lexical environments model),详细的描述能够在对应的章节找到。
而后咱们向下一个部分前进。众所周知,在ECMAScript中咱们可使用内部函数,而后在这些内部函数咱们能够引用父函数的变量或者全局上下文中的变量。当咱们把变量对象命名为上下文的做用域对象,与上面讨论的原型链类似,这里有一个叫做做用域链的东西。
做用域链是一个对象列表,上下文代码中出现的标识符在这个列表中进行查找。
这个规则仍是与原型链一样简单以及类似:若是一个变量在函数自身的做用域(在自身的变量/活动对象)中没有找到,那么将会查找它父函数(外层函数)的变量对象,以此类推。
就上下文而言,标识符指的是:变量名称,函数声明,形参,等等。当一个函数在其代码中引用一个不是局部变量(或者局部函数或者一个形参)的标识符,那么这个标识符就叫做自由变量。搜索这些自由变量(free variables)正好就要用到做用域链。
在一般状况下,做用域链是一个包含全部_父(函数)变量对象__加上_(在做用域链头部的)函数_自身变量/活动对象_的一个列表。可是,这个做用域链也能够包含任何其余对象,好比,在上下文执行过程当中动态加入到做用域链中的对象-像_with对象_或者特殊的_catch从句_(catch-clauses)对象。
当解析(查找)一个标识符的时候,会从做用域链中的活动对象开始查找,而后(若是这个标识符在函数自身的活动对象中没有被查找到)向做用域链的上一层查找-重复这个过程,就和原型链同样。
var x = 10; (function foo() { var y = 20; (function bar() { var z = 30; // "x" and "y" are "free variables" // and are found in the next (after // bar's activation object) object // of the bar's scope chain console.log(x + y + z); })(); })();
咱们能够假设经过隐式的__parent__
属性来和做用域链对象进行关联,这个属性指向做用域链中的下一个对象。这个方案可能在真实的Rhino代码中通过了测试,而且这个技术很明确得被用于ES5的词法环境中(在那里被叫做outer
链接)。做用域链的另外一个表现方式能够是一个简单的数组。利用__parent__
概念,咱们能够用下面的图来表现上面的例子(而且父变量对象存储在函数的[[Scope]]
属性中):
在代码执行过程当中,做用域链能够经过使用with
语句和catch
从句对象来加强。而且因为这些对象是简单的对象,它们能够拥有原型(和原型链)。这个事实致使做用域链查找变为两个维度:(1)首先是做用域链链接,而后(2)在每一个做用域链链接上-深刻做用域链链接的原型链(若是此链接拥有原型)。
对于这个例子:
Object.prototype.x = 10; var w = 20; var y = 30; // in SpiderMonkey global object // i.e. variable object of the global // context inherits from "Object.prototype", // so we may refer "not defined global // variable x", which is found in // the prototype chain console.log(x); // 10 (function foo() { // "foo" local variables var w = 40; var x = 100; // "x" is found in the // "Object.prototype", because // {z: 50} inherits from it with ({z: 50}) { console.log(w, x, y , z); // 40, 10, 30, 50 } // after "with" object is removed // from the scope chain, "x" is // again found in the AO of "foo" context; // variable "w" is also local console.log(x, w); // 100, 40 // and that's how we may refer // shadowed global "w" variable in // the browser host environment console.log(window.w); // 20 })();
咱们能够给出以下的结构(确切的说,在咱们查找__parent__
链接以前,首先查找__proto__
链):
注意,不是在全部的实现中全局对象都是继承自Object.prototype
。上图中描述的行为(从全局上下文中引用「未定义」的变量x
)能够在诸如SpiderMonkey引擎中进行测试。
因为全部父变量对象都存在,因此在内部函数中获取父函数中的数据没有什么特别-咱们就是遍历做用域链去解析(搜寻)须要的变量。就像咱们上边说起的,在一个上下文结束以后,它全部的状态和它自身都会被销毁。在同一时间父函数可能会返回一个内部函数。并且,这个返回的函数以后可能在另外一个上下文中被调用。若是自由变量的上下文已经「消失」了,那么这样的调用将会发生什么?一般来讲,有一个概念能够帮助咱们解决这个问题,叫做(词法)闭包,其在ECMAScript中就是和做用域链的概念紧密相关的。
在ECMAScript中,函数是第一级(first-class)对象。这个术语意味着函数能够作为参数传递给其余函数(在那种状况下,这些参数叫做「函数类型参数」(funargs,是"functional arguments"的简称))。接收「函数类型参数」的函数叫做高阶函数或者,贴近数学一些,叫做高阶操做符。一样函数也能够从其余函数中返回。返回其余函数的函数叫做以函数为值(function valued)的函数(或者叫做拥有函数类值的函数(functions with functional value))。
这有两个在概念上与「函数类型参数(funargs)」和「函数类型值(functional values)」相关的问题。而且这两个子问题在"Funarg problem"(或者叫做"functional argument"问题)中很广泛。为了解决整个"funarg problem",闭包(closure)的概念被创造了出来。咱们详细的描述一下这两个子问题(咱们将会看到这两个问题在ECMAScript中都是使用图中所提到的函数的[[Scope]]
属性来解决的)。
「funarg问题」的第一个子问题是「向上funarg问题」(upward funarg problem)。它会在当一个函数从另外一个函数向上返回(到外层)而且使用上面所提到的自由变量的时候出现。为了在即便父函数上下文结束的状况下也能访问其中的变量,内部函数在被建立的时候会在它的[[Scope]]
属性中保存父函数的做用域链。因此当函数被调用的时候,它上下文的做用域链会被格式化成活动对象与[[Scope]]
属性的和(实际上就是咱们刚刚在上图中所看到的):
Scope chain = Activation object + [[Scope]]
再次注意这个关键点-确切的说在建立时刻-函数会保存父函数的做用域链,由于确切的说这个保存下来的做用域链将会在将来的函数调用时用来查找变量。
function foo() { var x = 10; return function bar() { console.log(x); }; } // "foo" returns also a function // and this returned function uses // free variable "x" var returnedFunction = foo(); // global variable "x" var x = 20; // execution of the returned function returnedFunction(); // 10, but not 20
这个类型的做用域叫做静态(或者词法)做用域。咱们看到变量x
在返回的bar
函数的[[Scope]]
属性中被找到。一般来讲,也存在动态做用域,那么上面例子中的变量x
将会被解析成20
,而不是10
。可是,动态做用域在ECMAScript中没有被使用。
「funarg问题」的第二个部分是「向下funarg问题」。这种状况下可能会存在一个父上下文,可是在解析标识符的时候可能会模糊不清。问题是:标识符该使用哪一个做用域的值-以静态的方式存储在函数建立时刻的仍是在执行过程当中以动态方式生成的(好比caller的做用域)?为了不这种模棱两可的状况并造成闭包,静态做用域被采用:
// global "x" var x = 10; // global function function foo() { console.log(x); } (function (funArg) { // local "x" var x = 20; // there is no ambiguity, // because we use global "x", // which was statically saved in // [[Scope]] of the "foo" function, // but not the "x" of the caller's scope, // which activates the "funArg" funArg(); // 10, but not 20 })(foo); // pass "down" foo as a "funarg"
咱们能够判定静态做用域是一门语言拥有闭包的必需条件。可是,一些语言可能会同时提供动态和静态做用域,容许程序员作选择-什么应该包含(closure)在内和什么不该包含在内。因为在ECMAScript中只使用了静态做用域(好比咱们对于funarg问题
的两个子问题都有解决方案),因此结论是:ECMAScript彻底支持闭包,技术上是经过函数的[[Scope]]
属性实现的。如今咱们能够给闭包下一个准确的定义:
闭包是一个代码块(在ECMAScript是一个函数)和以静态方式/词法方式进行存储的全部父做用域的一个集合体。因此,经过这些存储的做用域,函数能够很容易的找到自由变量。
注意,因为每一个(标准的)函数都在建立的时候保存了[[Scope]]
,因此理论上来说,ECMAScript中的全部函数都是闭包。
另外一个须要注意的重要事情是,多个函数可能拥有相同的父做用域(这是很常见的状况,好比当咱们拥有两个内部/全局函数的时候)。在这种状况下,[[Scope]]
属性中存储的变量是在拥有相同父做用域链的全部函数之间共享的。一个闭包对变量进行的修改会体现在另外一个闭包对这些变量的读取上:
function baz() { var x = 1; return { foo: function foo() { return ++x; }, bar: function bar() { return --x; } }; } var closures = baz(); console.log( closures.foo(), // 2 closures.bar() // 1 );
以上代码能够经过下图进行说明:
确切来讲这个特性在循环中建立多个函数的时候会令人很是困惑。在建立的函数中使用循环计数器的时候,一些程序员常常会获得非预期的结果,全部函数中的计数器都是一样的值。如今是到了该揭开谜底的时候了-由于全部这些函数拥有同一个[[Scope]]
,这个属性中的循环计数器的值是最后一次所赋的值。
var data = []; for (var k = 0; k < 3; k++) { data[k] = function () { alert(k); }; } data[0](); // 3, but not 0 data[1](); // 3, but not 1 data[2](); // 3, but not 2
这里有几种技术能够解决这个问题。其中一种是在做用域链中提供一个额外的对象-好比,使用额外函数:
var data = []; for (var k = 0; k < 3; k++) { data[k] = (function (x) { return function () { alert(x); }; })(k); // pass "k" value } // now it is correct data[0](); // 0 data[1](); // 1 data[2](); // 2
对闭包理论和它们的实际应用感兴趣的同窗能够在第六章 闭包中找到额外的信息。若是想获取更多关于做用域链的信息,能够看一下同名的第四章 做用域链。
而后咱们移动到下个部分,考虑一下执行上下文的最后一个属性。这就是关于this
值的概念。
this是一个与执行上下文相关的特殊对象。所以,它能够叫做上下文对象(也就是用来指明执行上下文是在哪一个上下文中被触发的对象)。
任何对象均可以作为上下文中的this
的值。我想再一次澄清,在一些对ECMAScript执行上下文和部分this
的描述中的所产生误解。this
常常被错误的描述成是变量对象的一个属性。这类错误存在于好比像这本书中(即便如此,这本书的相关章节仍是十分不错的)。再重复一次:
this是执行上下文的一个属性,而不是变量对象的一个属性
这个特性很是重要,由于与变量相反,this
从不会参与到标识符解析过程。换句话说,在代码中当访问this
的时候,它的值是直接从执行上下文中获取的,并不须要任何做用域链查找。this
的值只在进入上下文的时候进行一次肯定。
顺便说一下,与ECMAScript相反,好比,Python的方法都会拥有一个被看成简单变量的self
参数,这个变量的值在各个方法中是相同的的而且在执行过程当中能够被更改为其余值。在ECMAScript中,给this
赋一个新值是不可能的,由于,再重复一遍,它不是一个变量而且不存在于变量对象中。
在全局上下文中,this
就等于全局对象自己(这意味着,这里的this
等于变量对象):
var x = 10; console.log( x, // 10 this.x, // 10 window.x // 10 );
在函数上下文的状况下,对函数的每次调用,其中的this
值多是不一样的。这个this
值是经过函数调用表达式(也就是函数被调用的方式)的形式由caller所提供的。举个例子,下面的函数foo
是一个callee,在全局上下文中被调用,此上下文为caller。让咱们经过例子看一下,对于一个代码相同的函数,this
值是如何在不一样的调用中(函数触发的不一样方式),由caller给出不一样的结果的:
// the code of the "foo" function // never changes, but the "this" value // differs in every activation function foo() { alert(this); } // caller activates "foo" (callee) and // provides "this" for the callee foo(); // global object foo.prototype.constructor(); // foo.prototype var bar = { baz: foo }; bar.baz(); // bar (bar.baz)(); // also bar (bar.baz = bar.baz)(); // but here is global object (bar.baz, bar.baz)(); // also global object (false || bar.baz)(); // also global object var otherFoo = bar.baz; otherFoo(); // again global object
为了深刻理解this
为何(而且更本质一些-如何)在每一个函数调用中可能会发生变化,你能够阅读第三章 This。在那里,上面所提到的状况都会有详细的讨论。
经过本文咱们完成了对概要的综述。尽管,它看起来并不像是「概要」;)。对全部这些主题进行彻底的解释须要一本完整的书。咱们只是没有涉及到两个大的主题:函数(和不一样函数之间的区别,好比,函数声明和函数表达式)和ECMAScript中所使用的求值策略(evaluation strategy )。这两个主题是能够ES3系列的在对应章节找到:第五章 函数和第八章 求值策略。
若是你有留言,问题或者补充,我将会很乐意地在评论中讨论它们。
祝学习ECMAScript好运!
做者:Dmitry A. Soshnikov 发布于:2010-09-02