From 4eb4b072da696c287f03bc87af7e598dbaf3547b Mon Sep 17 00:00:00 2001 From: goddyzhao Date: Tue, 31 Jan 2012 11:05:49 +0800 Subject: [PATCH 001/258] append my information and test for the pull request --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 1bdab2b..9f5f446 100644 --- a/README.markdown +++ b/README.markdown @@ -5,7 +5,7 @@ **“JavaScript patterns”中译本** - 《JavaScript 模式》 - 作者:[Stoyan Stefanov](http://www.phpied.com/) -- 翻译:[拔赤](http://jayli.github.com/) +- 翻译:[拔赤](http://jayli.github.com/)、[goddyzhao](http://goddyzhao.me) 偷懒是程序员的优良品质,模式则是先人们总结的偷懒招式。Stoyan Stefanov 的这本书,从 JavaScript 的实际使用场景出发,提炼了不少可以让前端们偷懒的实用招式。模式的探索、创新,将永远是程序员自我提升的一条修炼之道。值得一读。 From 3afe2d1317512b73f43dd6a83c8e903150932058 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 15 Jun 2012 14:05:26 +0800 Subject: [PATCH 002/258] =?UTF-8?q?=E7=BF=BB=E8=AF=91=E5=AE=8C=E2=80=9C?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E5=87=BD=E6=95=B0=E2=80=9D=E4=B8=80?= =?UTF-8?q?=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index 50c2a81..7a6eba8 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -435,13 +435,14 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 // calling as a method spooky.boo(); // "Boo!" spooky.boo(); // "Boo!" - console.log(spooky.boo.property); + console.log(spooky.boo.property); // "properly" - // "properly" // using the self-defined function scareMe(); // Double boo! scareMe(); // Double boo! console.log(scareMe.property); // undefined +从结果来看,当自定义函数被赋值给一个新的变量的时候,这段使用自定义函数的代码的执行结果与我们可能期望的结果并不一样。每当prank()运行的时候,它都弹出“Boo!”。同时它也重写了scareMe()函数,但是prank()自己仍然能够使用之前的定义,包括属性property。在这个函数被作为spooky对象的boo()方法调用的时候,结果也一样。所有的这些调用,在第一次的时候就已经修改了全局的scareMe()的指向,所以当它最终被调用的时候,它的函数体已经被修改为弹出“Double boo”。它也就不能获取到新添加的属性“property”。 + From 3da9522ce82a28d0d532142a59a2c56f53b59b0f Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 15 Jun 2012 18:43:32 +0800 Subject: [PATCH 003/258] =?UTF-8?q?=E7=AC=AC4=E7=AB=A0=20=E7=AB=8B?= =?UTF-8?q?=E5=8D=B3=E6=89=A7=E8=A1=8C=E5=87=BD=E6=95=B0=20=E4=B8=80?= =?UTF-8?q?=E8=8A=82=E7=BF=BB=E8=AF=91=E5=AE=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/chapter4.markdown b/chapter4.markdown index 7a6eba8..bcb8459 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -445,4 +445,38 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 从结果来看,当自定义函数被赋值给一个新的变量的时候,这段使用自定义函数的代码的执行结果与我们可能期望的结果并不一样。每当prank()运行的时候,它都弹出“Boo!”。同时它也重写了scareMe()函数,但是prank()自己仍然能够使用之前的定义,包括属性property。在这个函数被作为spooky对象的boo()方法调用的时候,结果也一样。所有的这些调用,在第一次的时候就已经修改了全局的scareMe()的指向,所以当它最终被调用的时候,它的函数体已经被修改为弹出“Double boo”。它也就不能获取到新添加的属性“property”。 +## 立即执行函数 +立即执行函数是一种语法模式,它会使函数在定义后立即执行。看这个例子: + + (function () { + alert('watch out!'); + }()); + +这种模式其实本质上只是一个在创建后就被执行的函数表达式(具名或者匿名)。“立即执行函数”这种说法并没有在ECMAScript标准中被定义,但它作为一个名词,有助于我们的描述和讨论。 + +这种模式由以下几个部分组成: + +- 使用函数表达式定义一个函数。(不能使用函数声明。) +- 在最后加入一对括号,这会使函数立即被执行。 +- 把整个函数包裹到一对括号中(只在没有将函数赋值给变量时需要)。 + +下面这种语法也很常见(注意右括号的位置),但是JSLint倾向于第一种: + + (function () { + alert('watch out!'); + })(); + +这种模式很有用,它为我们提供一个作用域的沙箱,可以在执行一些初始化代码的时候使用。想象这样的场景:当页面加载的时候,你需要运行一些代码,比如绑定事件、创建对象等等。所有的这些代码都只需要运行一次,所以没有必要创建一个具名函数。但是这些代码需要一些临时变量,而这些变量在初始化完之后又不会再次用到。显然,把这些变量作为全局变量声明也是不合适的。正因为如此,我们才需要立即执行函数。它可以把你所有的代码包裹到一个作用域里面,而不会暴露任何变量到全局作用域中: + + (function () { + + var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + today = new Date(), + msg = 'Today is ' + days[today.getDay()] + ', ' + today.getDate(); + + alert(msg); + + }()); // "Today is Fri, 13" + +如果这段代码没有包裹到立即执行函数中,那么变量days,today,msg都会是全局变量,而这仅仅是由初始化代码遗留下来的垃圾。 \ No newline at end of file From 4f429817339d2022fc69d84f8993567b00271073 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 15 Jun 2012 19:35:50 +0800 Subject: [PATCH 004/258] =?UTF-8?q?=E7=AB=8B=E5=8D=B3=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E7=9A=84=E5=87=BD=E6=95=B0=20=E4=B8=80=E8=8A=82=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 101 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 96 insertions(+), 5 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index bcb8459..3d2519a 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -445,15 +445,15 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 从结果来看,当自定义函数被赋值给一个新的变量的时候,这段使用自定义函数的代码的执行结果与我们可能期望的结果并不一样。每当prank()运行的时候,它都弹出“Boo!”。同时它也重写了scareMe()函数,但是prank()自己仍然能够使用之前的定义,包括属性property。在这个函数被作为spooky对象的boo()方法调用的时候,结果也一样。所有的这些调用,在第一次的时候就已经修改了全局的scareMe()的指向,所以当它最终被调用的时候,它的函数体已经被修改为弹出“Double boo”。它也就不能获取到新添加的属性“property”。 -## 立即执行函数 +## 立即执行的函数 -立即执行函数是一种语法模式,它会使函数在定义后立即执行。看这个例子: +立即执行的函数是一种语法模式,它会使函数在定义后立即执行。看这个例子: (function () { alert('watch out!'); }()); -这种模式其实本质上只是一个在创建后就被执行的函数表达式(具名或者匿名)。“立即执行函数”这种说法并没有在ECMAScript标准中被定义,但它作为一个名词,有助于我们的描述和讨论。 +这种模式其实本质上只是一个在创建后就被执行的函数表达式(具名或者匿名)。“立即执行的函数”这种说法并没有在ECMAScript标准中被定义,但它作为一个名词,有助于我们的描述和讨论。 这种模式由以下几个部分组成: @@ -467,7 +467,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 alert('watch out!'); })(); -这种模式很有用,它为我们提供一个作用域的沙箱,可以在执行一些初始化代码的时候使用。想象这样的场景:当页面加载的时候,你需要运行一些代码,比如绑定事件、创建对象等等。所有的这些代码都只需要运行一次,所以没有必要创建一个具名函数。但是这些代码需要一些临时变量,而这些变量在初始化完之后又不会再次用到。显然,把这些变量作为全局变量声明也是不合适的。正因为如此,我们才需要立即执行函数。它可以把你所有的代码包裹到一个作用域里面,而不会暴露任何变量到全局作用域中: +这种模式很有用,它为我们提供一个作用域的沙箱,可以在执行一些初始化代码的时候使用。设想这样的场景:当页面加载的时候,你需要运行一些代码,比如绑定事件、创建对象等等。所有的这些代码都只需要运行一次,所以没有必要创建一个具名函数。但是这些代码需要一些临时变量,而这些变量在初始化完之后又不会再次用到。显然,把这些变量作为全局变量声明也是不合适的。正因为如此,我们才需要立即执行的函数。它可以把你所有的代码包裹到一个作用域里面,而不会暴露任何变量到全局作用域中: (function () { @@ -479,4 +479,95 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 }()); // "Today is Fri, 13" -如果这段代码没有包裹到立即执行函数中,那么变量days,today,msg都会是全局变量,而这仅仅是由初始化代码遗留下来的垃圾。 \ No newline at end of file +如果这段代码没有包裹到立即执行函数中,那么变量days,today,msg都会是全局变量,而这仅仅是由初始化代码遗留下来的垃圾。 + +### 立即执行的函数的参数 + +立即执行的函数也可以接受参数,看这个例子: + + // prints: + // I met Joe Black on Fri Aug 13 2010 23:26:59 GMT-0800 (PST) + + (function (who, when) { + + console.log("I met " + who + " on " + when); + + }("Joe Black", new Date())); + +通常的做法,会把全局对象当作一个参数传给立即执行的函数,以保证在函数内部也可以访问到全局对象,而不是使用window对象,这样可以使得代码在非浏览器环境中使用时更具可移植性。 + +值得注意的是,一般来说尽量不要给立即执行的函数传入太多的参数,否则的话,你在阅读代码的时候需要频繁地上下滚动代码。 + +### 立即执行的函数的返回值 + +和其它的函数一样,立即执行的函数也可以返回值,并且这些返回值也可以被赋值给变量: + + var result = (function () { + return 2 + 2; + }()); + +如果省略括号的话也可以达到同样的目的,因为如果需要将返回值赋值给变量,那么括号就不是必需的。省略第一对括号的代码是这样子: + + var result = function () { + return 2 + 2; + }(); + +这种写法更简洁,但是同时也容易造成误解。如果有人在阅读代码的时候忽略了最后的一对括号,那么他会以为result指向了一个函数。而事实上result是指向这个函数运行后的返回值,在这个例子中是4。 + +还有一种写法也可以得到同样的结果: + + var result = (function () { + return 2 + 2; + })(); + +前面的例子中,立即执行的函数返回的是一个基本类型的数值。但事实上,除了基本类型以外,一个立即执行的函数可以返回任意类型的值,包括返回一个函数都可以。你可以利用立即执行的函数的作用域来存储一些私有的数据,这些数据只能在返回的内层函数中被访问。 + +在下面的例子中,立即执行的函数的返回值是一个函数,这个函数会简单地返回res的值,并且它被赋值给了变量getResult。而res就是一个预先计算好的变量,它被存储在立即执行函数的闭包中: + + var getResult = (function () { + var res = 2 + 2; + return function () { + return res; + }; + }()); + +在定义一个对象的属性的时候也可以使用立即执行的函数。设想一下这样的场景:你需要定义一个对象的属性,这个属性在对象的生命周期中都不会改变,但是在定义之前,你需要做一点额外的工作来得到正确的值。这种情况下你就可以使用立即执行的函数来包裹那些额外的工作,然后将它的返回值作为对象属性的值。下面是一个例子: + +var o = { + message: (function () { + var who = "me", + what = "call"; + return what + " " + who; + }()), + getMsg: function () { + return this.message; + } +}; +// usage +o.getMsg(); // "call me" +o.message; // "call me" + + +在这个例子中,o.message是一个字符串,而不是一个函数,但是它需要一个函数在脚本载入后来得到这个属性值。 + +### 好处和用法 + +立即执行的函数应用很广泛。它可以帮助我们做一些不想留下全局变量的工作。所有定义的变量都只是立即执行的函数的本地变量,你完全不用担心临时变量会污染全局对象。 + +> 立即执行的函数还有一些名字,比如“自调用函数”或者“自执行函数”,因为这些函数会在被定义后立即执行自己。 + +这种模式也经常被用到书签代码中,因为书签代码会在任何一个页面运行,所以需要保持全局命名空间干净是一个非常残酷的需求。 + +这种模式也可以让你包裹一些独立的特性到一个封闭的模块中。设想你的页面是静态的,在没有JavaScript的时候工作正常,然后,本着渐进增强的精神,你给页面加入了一点增加代码。这时候,你就可以把你的代码(也可以叫“模块”或者“特性”)到一个立即执行的函数中并且保证页面在有没有它的时候都可以正常工作。然后你就可以加入更多的增强特性,或者对它们进行移除、进行独立测试或者允许用户禁用等等。 + +你可以使用下面的模板定义一段函数代码,我们叫它module1: + + // module1 defined in module1.js + (function () { + + // all the module 1 code ... + + }()); + +套用这个模板,你就可以编写其它的模块。然后在发布到线上的时候,你就可以决定在这个时间节点上哪些特性是可以使用的,然后使用发布脚本将它们打包上线。 + From 95ef617b14c0ee624c6ca90b87ebb93762d53cc5 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 15 Jun 2012 20:24:15 +0800 Subject: [PATCH 005/258] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20=E7=AC=AC4?= =?UTF-8?q?=E7=AB=A0=20=E7=AB=8B=E5=8D=B3=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E7=9A=84=E5=AF=B9=E8=B1=A1=20=E6=A0=87=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chapter4.markdown b/chapter4.markdown index 3d2519a..f943a41 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -571,3 +571,5 @@ o.message; // "call me" 套用这个模板,你就可以编写其它的模块。然后在发布到线上的时候,你就可以决定在这个时间节点上哪些特性是可以使用的,然后使用发布脚本将它们打包上线。 +## 立即初始化的对象 + From 064724b85981d4cdc4f3e405a9ffbd8c663a8bcc Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 17 Jun 2012 18:01:15 +0800 Subject: [PATCH 006/258] =?UTF-8?q?=E7=AC=AC4=E7=AB=A0=20=E7=AB=8B?= =?UTF-8?q?=E5=8D=B3=E5=88=9D=E5=A7=8B=E5=8C=96=E7=9A=84=E5=87=BD=E6=95=B0?= =?UTF-8?q?=20=E4=B8=80=E8=8A=82=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/chapter4.markdown b/chapter4.markdown index f943a41..6fe0046 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -573,3 +573,40 @@ o.message; // "call me" ## 立即初始化的对象 +还有另外一种可以避免污染全局作用域的方法,和前面描述的立即执行的函数相似,叫做“立即初始化的对象”模式。这种模式使用一个带有init()方法的对象来实现,这个方法在对象被创建后立即执行。初始化的工作由init()函数来完成。 + +下面是一个立即初始化的对象模式的例子: + + ({ + // here you can define setting values + // a.k.a. configuration constants + maxwidth: 600, + maxheight: 400, + + // you can also define utility methods + gimmeMax: function () { + return this.maxwidth + "x" + this.maxheight; + }, + + // initialize + init: function () { + console.log(this.gimmeMax()); + // more init tasks... + } + }).init(); + +在语法上,当你使用这种模式的时候就像在使用对象字面量创建一个普通对象一样。除此之外,还需要将对象字面量用括号括起来,这样能让JavaScript引擎知道这是一个对象字面量,而不是一个代码块(if或者for循环之类)。在括号后面,紧接着就执行了init()方法。 + +你也可以将对象字面量和init()调用一起写到括号里面。简单地说,下面两种语法都是有效的: + + ({...}).init(); + ({...}.init()); + +这种模式的好处和自动执行的函数模式是一样的:在做一些一次性的初始化工作的时候保护全局作用域不被污染。从语法上看,这种模式似乎比只包含一段代码在一个匿名函数中要复杂一些,但是如果你的初始化工作比较复杂(这种情况很常见),它会给整个初始化工作一个比较清晰的结构。比如,一些私有的辅助性函数可以被很轻易地看出来,因为它们是这个临时对象的属性,但是如果是在立即执行的函数模式中,它们很可能只是一些散落和函数。 + +这种模式的一个弊端是,JavaScript压缩工具可能不能像压缩一段包裹在函数中的代码一样有效地压缩这种模式的代码。这些私有的属性和方法不被会重命名为一些更短的名字,因为从压缩工具的角度来看,保证压缩的可靠性更重要。在写作本书的时候,Google出品的Closure Compiler的“advanced”模式是唯一会重命名立即初始化的对象的属性的压缩工具。一个压缩后的样例是这样: + + ({d:600,c:400,a:function(){return this.d+"x"+this.c},b:function(){console.log(this.a())}}).b(); + +> 这种模式主要用于一些一次性的工作,并且在init()方法执行完后就无法再次访问到这个对象。如果希望在这些工作完成后保持对对象的引用,只需要简单地在init()的末尾加上return this;即可。 + From 294eca40e89b334c3db67e1a1d8e088e60d8b07e Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 18 Jun 2012 12:49:21 +0800 Subject: [PATCH 007/258] =?UTF-8?q?=E7=AC=AC4=E7=AB=A0=20=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E6=97=B6=E9=97=B4=E7=A8=8B=E5=BA=8F=20=E4=B8=80?= =?UTF-8?q?=E8=8A=82=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 62 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/chapter4.markdown b/chapter4.markdown index 6fe0046..9c973ea 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -610,3 +610,65 @@ o.message; // "call me" > 这种模式主要用于一些一次性的工作,并且在init()方法执行完后就无法再次访问到这个对象。如果希望在这些工作完成后保持对对象的引用,只需要简单地在init()的末尾加上return this;即可。 + +## 启动时间程序 + +启动时间程序(也叫加载时间程序)是一种优化模式。当你知道某种条件在整个程序生命周期中都不会变化的时候,那么对这个条件的探测只做一次就很有意义。浏览器探测(或者特征检测)是一个典型的例子。 + +举例说明,当你探测到XMLHttpRequest被作为一个本地对象支持时,就知道浏览器不会在程序执行过程中改变这一情况,也不用突然又需要去处理ActiveX对象。当环境不发生变化的时候,你的代码就没有必要在需要在每次XHR对象时探测一遍(并且得到同样的结果)。 + +另外一些可以从启动时间程序中获益的场景是获得一个DOM元素的computed styles或者是绑定事件处理函数。大部分程序员在他们的客户端编程生涯中都编写过事件绑定和取消绑定相关的组件,像下面的例子: + + // BEFORE + var utils = { + addListener: function (el, type, fn) { + if (typeof window.addEventListener === 'function') { + el.addEventListener(type, fn, false); + } else if (typeof document.attachEvent === 'function') { // IE + el.attachEvent('on' + type, fn); + } else { // older browsers + el['on' + type] = fn; + } + }, + removeListener: function (el, type, fn) { + // pretty much the same... + } + }; + +这段代码的问题就是效率不高。每当你执行utils.addListener()或者utils.removeListener()时,同样的检查都会被重复执行。 + +如果使用启动时间程序,那么浏览器探测的工作只需要在初始化代码的时候执行一次。在初始化的时候,代码探测一次环境,然后重新定义这个函数在剩下来的程序生命周期中应该怎样工作。下面是一个例子,看看如何达到这个目的: + + // AFTER + + // the interface + var utils = { + addListener: null, + removeListener: null + }; + + // the implementation + if (typeof window.addEventListener === 'function') { + utils.addListener = function (el, type, fn) { + el.addEventListener(type, fn, false); + }; + utils.removeListener = function (el, type, fn) { + el.removeEventListener(type, fn, false); + }; + } else if (typeof document.attachEvent === 'function') { // IE + utils.addListener = function (el, type, fn) { + el.attachEvent('on' + type, fn); + }; + utils.removeListener = function (el, type, fn) { + el.detachEvent('on' + type, fn); + }; + } else { // older browsers + utils.addListener = function (el, type, fn) { + el['on' + type] = fn; + }; + utils.removeListener = function (el, type, fn) { + el['on' + type] = null; + }; + } + +说到这里,要特别提醒一下关于浏览器探测的事情。当你使用这个模式的时候,不要对浏览器特性过度假设。举个例子,如果你探测到浏览器不支持window.addEventListener,不要假设这个浏览器是IE,也不要认为它不支持原生的XMLHttpRequest,虽然这个结论在整个浏览器历史上的某个点是正确的。当然,也有一些情况是可以放心地做一些特性假设的,比如.addEventListener和.removeEventListerner,但是通常来讲,浏览器的特性在发生变化时都是独立的。最好的策略就是分别探测每个特性,然后使用启动时间程序,使这种探测只做一次。 From b9c3c33f4f3285c9b412cc3fb907860b44ef692b Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 18 Jun 2012 14:13:00 +0800 Subject: [PATCH 008/258] =?UTF-8?q?=E7=AC=AC4=E7=AB=A0=20=E5=A4=87?= =?UTF-8?q?=E5=BF=98=E5=BD=95=20=E4=B8=80=E8=8A=82=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E4=B8=80=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/chapter4.markdown b/chapter4.markdown index 9c973ea..5909efc 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -672,3 +672,46 @@ o.message; // "call me" } 说到这里,要特别提醒一下关于浏览器探测的事情。当你使用这个模式的时候,不要对浏览器特性过度假设。举个例子,如果你探测到浏览器不支持window.addEventListener,不要假设这个浏览器是IE,也不要认为它不支持原生的XMLHttpRequest,虽然这个结论在整个浏览器历史上的某个点是正确的。当然,也有一些情况是可以放心地做一些特性假设的,比如.addEventListener和.removeEventListerner,但是通常来讲,浏览器的特性在发生变化时都是独立的。最好的策略就是分别探测每个特性,然后使用启动时间程序,使这种探测只做一次。 + +## 函数属性——一种备忘录模式 + +函数也是对象,所以它们可以有属性。事实上,函数也确实本来就有一些属性。比如,对一个函数来说,不管是用什么语法创建的,它会自动拥有一个length属性来标识这个函数期待接受的参数个数: + + function func(a, b, c) {} + console.log(func.length); // 3 + +任何时候都可以给函数添加自定义属性。添加自定义属性的一个有用场景是缓存函数的执行结果(返回值),这样下次同样的函数被调用的时候就不需要再做一次那些可能很复杂的计算。缓存一个函数的运行结果也就是为大家所熟知的备忘录(memoization)。 + +在下面的例子中,myFunc函数创建了一个cache属性,可以通过myFunc.cache访问到。这个cache属性是一个对象(hash表),传给函数的参数会作为对象的key,函数执行结果会作为对象的值。函数的执行结果可以是会用到的任何的复杂数据结构: + + var myFunc = function (param) { + if (!myFunc.cache[param]) { + var result = {}; + // ... expensive operation ... + myFunc.cache[param] = result; + } + return myFunc.cache[param]; + }; + + // cache storage + myFunc.cache = {}; + +上面的代码假设函数只接受一个参数param,并且这个参数是基本类型(比如字符串)。如果你有更多更复杂的参数,则通常需要对它们进行序列化。(译注:序列化后来作为缓存对象的key。)比如,你需要将arguments对象序列化为JSON字符串,然后使用JSON字符串作为cache对象的key: + + var myFunc = function () { + + var cachekey = JSON.stringify(Array.prototype.slice.call(arguments)), + result; + + if (!myFunc.cache[cachekey]) { + result = {}; + // ... expensive operation ... + myFunc.cache[cachekey] = result; + } + return myFunc.cache[cachekey]; + }; + + // cache storage + myFunc.cache = {}; + + From b8f0f51ad43e507e5b16d2d4803001057ae2e4d0 Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 18 Jun 2012 19:21:05 +0800 Subject: [PATCH 009/258] =?UTF-8?q?=E7=AC=AC4=E7=AB=A0=20=E5=A4=87?= =?UTF-8?q?=E5=BF=98=E5=BD=95=20=E4=B8=80=E8=8A=82=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/chapter4.markdown b/chapter4.markdown index 5909efc..1bee367 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -714,4 +714,23 @@ o.message; // "call me" // cache storage myFunc.cache = {}; +需要注意的是,在序列化的过程中,对象的“标识”将会丢失。如果你有两个不同的对象,却碰巧有相同的属性,那么他们会共享同样的缓存内容。 + +前面代码中的函数名还可以使用arguments.callee来替代,这样就不用将函数名硬编码。不过尽管现阶段这个办法可行,但是仍然需要注意,arguments.callee在ECMAScript 5的严格模式中是不被允许的: + + var myFunc = function (param) { + + var f = arguments.callee, + result; + + if (!f.cache[param]) { + result = {}; + // ... expensive operation ... + f.cache[param] = result; + } + return f.cache[param]; + }; + + // cache storage + myFunc.cache = {}; From 23b44939d3b34fcce59e747d7cba030eb9333f94 Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 18 Jun 2012 21:02:42 +0800 Subject: [PATCH 010/258] =?UTF-8?q?=E7=AC=AC4=E7=AB=A0=20=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E7=9A=84=E9=85=8D=E7=BD=AE=20=E4=B8=80=E8=8A=82?= =?UTF-8?q?=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/chapter4.markdown b/chapter4.markdown index 1bee367..a1b8b5d 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -734,3 +734,50 @@ o.message; // "call me" // cache storage myFunc.cache = {}; + +## 对象的配置 + +对象的配置模式是一种提供更简洁的API的方法,尤其是当你正在写一个即将被其它程序调用的类库或者任何其它代码的时候。 + +软件在开发和维护过程中需要不断改变是一个不争的事实。这样的事情总是以一些有限的需求开始,但是随着开发的进行,越来越多的功能会不断被加进来。 + +设想一下你正在写一个名为addPerson()的函数,它接受一个姓和一个名,然后在列表中加入一个人: + + function addPerson(first, last) {...} + +然后你意识到,生日也必须要存储,此外,性别和地址也作为可选项存储。所以你修改了函数,添加了一些新的参数(还得非常小心地将可选参数放到最后): + + function addPerson(first, last, dob, gender, address) {...} + +这个时候,函数已经显得有点长了。然后,你又被告知需要添加一个用户名,并且不是可选的。现在这个函数的调用者需要将所有的可选参数传进来,并且得非常小心地保证不弄混参数的顺序: + + addPerson("Bruce", "Wayne", new Date(), null, null, "batman"); + +传一大串的参数真的很不方便。一个更好的办法就是将它们替换成一个参数,并且把这个参数弄成对象;我们叫它conf,是“configuration”(配置)的缩写: + + addPerson(conf); + +然后这个函数的使用者就可以这样: + + var conf = { + username: "batman", + first: "Bruce", + last: "Wayne" + }; + addPerson(conf); + +对象的配置模式的好处是: + +- 不需要记住参数的顺序 +- 可以很安全地跳过可选参数 +- 拥有更好的可读性和可维护性 +- 更容易添加和移除参数 + +对象的配置模式的坏处是: + +- 需要记住参数的名字 +- 参数名字不能被压缩 + +举些实例,这个模式对创建DOM元素的函数或者是给元素设定CSS样式函数会非常实用,因为元素和CSS样式可能会有很多但是大部分可选的属性。 + + From 8b97503df74d3abde7c04cf23bf13f26bb99f13d Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 18 Jun 2012 22:53:59 +0800 Subject: [PATCH 011/258] =?UTF-8?q?=E6=B7=BB=E5=8A=A0TooBug=E5=90=8D?= =?UTF-8?q?=E5=AD=97=20=E6=B7=BB=E5=8A=A0=E7=AC=AC4=E7=AB=A0=E9=93=BE?= =?UTF-8?q?=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.markdown | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.markdown b/README.markdown index 9f5f446..dea4860 100644 --- a/README.markdown +++ b/README.markdown @@ -5,7 +5,7 @@ **“JavaScript patterns”中译本** - 《JavaScript 模式》 - 作者:[Stoyan Stefanov](http://www.phpied.com/) -- 翻译:[拔赤](http://jayli.github.com/)、[goddyzhao](http://goddyzhao.me) +- 翻译:[拔赤](http://jayli.github.com/)、[goddyzhao](http://goddyzhao.me)、[TooBug](http://www.toobug.net) 偷懒是程序员的优良品质,模式则是先人们总结的偷懒招式。Stoyan Stefanov 的这本书,从 JavaScript 的实际使用场景出发,提炼了不少可以让前端们偷懒的实用招式。模式的探索、创新,将永远是程序员自我提升的一条修炼之道。值得一读。 @@ -96,14 +96,14 @@ - [库中的回调](chapter4.markdown#a) - [返回函数](chapter4.markdown#a) - [自定义函数](chapter4.markdown#a) -- 立即执行的函数 - - 立即执行的函数的参数 - - 立即执行的函数的返回值 - - 好处和用法 -- 立即初始化的对象 -- 启动时间程序 -- 函数属性——一种备忘录模式 -- 对象的配置 +- [立即执行的函数](chapter4.markdown#a) + - [立即执行的函数的参数](chapter4.markdown#a) + - [立即执行的函数的返回值](chapter4.markdown#a) + - [好处和用法](chapter4.markdown#a) +- [立即初始化的对象](chapter4.markdown#a) +- [启动时间程序](chapter4.markdown#a) +- [函数属性——一种备忘录模式](chapter4.markdown#a) +- [对象的配置](chapter4.markdown#a) - 柯里化 (Curry) - 函数应用 - 部分应用 From fc8cb949169f465b8057b7717d9077854f7ca233 Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 19 Jun 2012 19:22:34 +0800 Subject: [PATCH 012/258] =?UTF-8?q?=E7=AC=AC4=E7=AB=A0=20=E6=9F=AF?= =?UTF-8?q?=E9=87=8C=E5=8C=96=E4=B9=8B=E5=87=BD=E6=95=B0=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E5=B0=8F=E8=8A=82=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/chapter4.markdown b/chapter4.markdown index a1b8b5d..247915e 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -781,3 +781,48 @@ o.message; // "call me" 举些实例,这个模式对创建DOM元素的函数或者是给元素设定CSS样式函数会非常实用,因为元素和CSS样式可能会有很多但是大部分可选的属性。 +## 柯里化 (Curry) + +在本章剩下的部分,我们将讨论一下关于柯里化和部分应用的话题。但是在我们开始这个话题之前,先看一下到底什么是函数应用。 + +### 函数应用 + +在一些纯粹的函数式编程语言中,对函数的描述不是被调用(called或者invoked),而是被应用(applied)。在JavaScript中也有同样的东西——我们可以使用Function.prototype.apply()来应用一个函数,因为在JavaScript中,函数实际上是对象,并且他们拥有方法。 + +下面是一个函数应用的例子: + + // define a function + var sayHi = function (who) { + return "Hello" + (who ? ", " + who : "") + "!"; + }; + + // invoke a function + sayHi(); // "Hello" + sayHi('world'); // "Hello, world!" + + // apply a function + sayHi.apply(null, ["hello"]); // "Hello, hello!" + +从上面的例子中可以看出来,调用一个函数和应用一个函数有相同的结果。apply()接受两个参数:第一个是在函数内部绑定到this上的对象,第二个是一个参数数组,参数数组会在函数内部变成一个类似数组的arguments对象。如果第一个参数为null,那么this将指向全局对象,这正是当你调用一个函数(且这个函数不是某个对象的方法)时发生的事情。 + +当一个函数是一个对象的方法时,我们不再像前面的例子一样传入null。(译注:主要是为了保证方法中的this绑定到一个有效的对象而不是全局对象。)在下面的例子中,对象被作为第一个参数传给apply(): + + var alien = { + sayHi: function (who) { + return "Hello" + (who ? ", " + who : "") + "!"; + } + }; + + alien.sayHi('world'); // "Hello, world!" + sayHi.apply(alien, ["humans"]); // "Hello, humans!" + +在这个例子中,sayHi()中的this指向alien。而在上一个例子中,this是指向的全局对象。(译注:这个例子的代码有误,最后一行的sayHi并不能访问到alien的sayHi方法,需要使用alien.sayHi.apply(alien, ["humans"])才可正确运行。另外,在sayHi中也没有出现this。) + +正如上面两个例子所展现出来的一样,我们所谓的函数调用跟函数应用的一种语法糖没有什么太大的差别。 + +需要注意的是,除了apply()之外,Function.prototype对象还有一个call()方法,但是它仍然只是apply()的一种语法糖。(译注:这两个方法的区别在于,apply()只接受两个参数,第二个参数为需要传给函数的参数数组,而call()则接受任意多个参数,从第二个开始将参数依次传给函数。)不过有种情况下使用这个语法糖会更好:当你的函数只接受一个参数的时候,你可以省去为唯一的一个元素创建数组的工作: + + // the second is more efficient, saves an array + sayHi.apply(alien, ["humans"]); // "Hello, humans!" + sayHi.call(alien, "humans"); // "Hello, humans!" + From 1945ff898e5bde481d50caab4acf91d5e50c6e5e Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 19 Jun 2012 19:47:15 +0800 Subject: [PATCH 013/258] =?UTF-8?q?=E7=AC=AC4=E7=AB=A0=20=E6=9F=AF?= =?UTF-8?q?=E9=87=8C=E5=8C=96=E4=B9=8B=E9=83=A8=E5=88=86=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E4=B8=80=E5=B0=8F=E8=8A=82=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/chapter4.markdown b/chapter4.markdown index 247915e..45cf7d9 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -826,3 +826,54 @@ o.message; // "call me" sayHi.apply(alien, ["humans"]); // "Hello, humans!" sayHi.call(alien, "humans"); // "Hello, humans!" +### 部分应用 + +现在我们知道了,调用一个函数实际上就是给它应用一堆参数,那是否能够只传一部分参数而不传全部呢?这实际上跟我们手工处理数学函数非常类似。 + +假设已经有了一个add()函数,它的工作是把x和y两个数加到一起。下面的代码片段展示了当x为5、y为4时的计算步骤: + + // for illustration purposes + // not valid JavaScript + + // we have this function + function add(x, y) { + return x + y; + } + + // and we know the arguments + add(5, 4); + + // step 1 -- substitute one argument + function add(5, y) { + return 5 + y; + } + + // step 2 -- substitute the other argument + function add(5, 4) { + return 5 + 4; + } + +在这个代码片段中,step 1和step 2并不是有效的JavaScript代码,但是它展示了我们手工计算的过程。首先获得第一个参数的值,然后将未知的x和已知的值5替换到函数中。然后重复这个过程,直到替换掉所有的参数。 + +step 1是一个所谓的部分应用的例子:我们只应用了第一个参数。当你执行一个部分应用的时候并不能获得结果(或者是解决方案),取而代之的是另一个函数。 + +下面的代码片段展示了一个虚拟的partialApply()方法的用法: + + var add = function (x, y) { + return x + y; + }; + + // full application + add.apply(null, [5, 4]); // 9 + + // partial application + var newadd = add.partialApply(null, [5]); + // applying an argument to the new function + newadd.apply(null, [4]); // 9 + +正如你所看到的一样,部分应用给了我们另一个函数,这个函数可以在稍后调用的时候接受其它的参数。这实际上跟add(5)(4)是等价的,因为add(5)返回了一个函数,这个函数可以使用(4)来调用。我们又一次看到,熟悉的add(5, 4)也跟add(5)(4)的一种语法糖差不了多少。 + +现在,让我们回到地球:并不存在这样的一个partialApply()函数,并且函数的默认表现也不会像上面的例子中那样。但是你完全可以自己去写,因为JavaScript的动态特性完全可以做到这样。 + +让函数理解并且处理部分应用的过程,叫柯里化。 + From e1f55a97042d4cc2905a7e2e3471233161bbda7d Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 19 Jun 2012 21:58:24 +0800 Subject: [PATCH 014/258] =?UTF-8?q?=E7=AC=AC4=E7=AB=A0=20=E6=9F=AF?= =?UTF-8?q?=E9=87=8C=E5=8C=96=E4=B9=8B=E6=9F=AF=E9=87=8C=E5=8C=96=E4=B8=80?= =?UTF-8?q?=E5=B0=8F=E8=8A=82=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 91 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/chapter4.markdown b/chapter4.markdown index 45cf7d9..c11728f 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -875,5 +875,94 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 现在,让我们回到地球:并不存在这样的一个partialApply()函数,并且函数的默认表现也不会像上面的例子中那样。但是你完全可以自己去写,因为JavaScript的动态特性完全可以做到这样。 -让函数理解并且处理部分应用的过程,叫柯里化。 +让函数理解并且处理部分应用的过程,叫柯里化(Currying)。 + +### 柯里化(Currying) + +柯里化和辛辣的印度菜可没什么关系;它来自数学家Haskell Curry。(Haskell编程语言也是因他而得名。)柯里化是一个变换的函数的过程。柯里化的另外一个名字也叫schönfinkelisation,来自另一位数学家——Moses Schönfinkelisation——这种变换的最初发明者。 + +所以我们怎样对一个函数进行柯里化呢?其它的函数式编程语言也许已经原生提供了支持并且所有的函数已经默认柯里化了。在JavaScript中我们可以修改一下add()函数使它柯里化,然后支持部分应用。 + +来看一个例子: + + // a curried add() + // accepts partial list of arguments + function add(x, y) { + var oldx = x, oldy = y; + if (typeof oldy === "undefined") { // partial + return function (newy) { + return oldx + newy; + }; + } + // full application + return x + y; + } + + // test + typeof add(5); // "function" + add(3)(4); // 7 + + // create and store a new function + var add2000 = add(2000); + add2000(10); // 2010 + +在这段代码中,第一次调用add()时,在返回的内层函数那里创建了一个闭包。这个闭包将原来的x和y的值存储到了oldx和oldy中。当内层函数执行的时候,oldx会被使用。如果没有部分应用,即x和y都传了值,那么这个函数会简单地将他们相加。这个add()函数的实现跟实际情况比起来有些冗余,仅仅是为了更好地说明问题。下面的代码片段中展示了一个更简洁的版本,没有oldx和oldy,因为原始的x已经被存储到了闭包中,此外我们复用了y作为本地变量,而不用像之前那样新定义一个变量newy: + + // a curried add + // accepts partial list of arguments + function add(x, y) { + if (typeof y === "undefined") { // partial + return function (y) { + return x + y; + }; + } + // full application + return x + y; + } + +在这些例子中,add()函数自己处理了部分应用。有没有可能用一种更为通用的方式来做同样的事情呢?换句话说,我们能不能对任意一个函数进行处理,得到一个新函数,使它可以处理部分参数?下面的代码片段展示了一个通用函数的例子,我们叫它schonfinkelize(),正是用来做这个的。我们使用schonfinkelize()一部分原因是它比较难发音,另一部分原因是它听起来比较像动词(使用“curry”则不是那么明确),而我们刚好需要一个动词来表明这是一个函数转换的过程。 + +这是一个通用的柯里化函数: + + function schonfinkelize(fn) { + var slice = Array.prototype.slice, + stored_args = slice.call(arguments, 1); + return function () { + var new_args = slice.call(arguments), + args = stored_args.concat(new_args); + return fn.apply(null, args); + }; + } + +这个schonfinkelize可能显得比较复杂了,只是因为在JavaScript中arguments不是一个真的数组。从Array.prototype中借用slice()方法帮助我们将arguments转换成数组,以便能更好地对它进行操作。当schonfinkelize()第一次被调用的时候,它使用slice变量存储了对slice()方法的引用,同时也存储了调用时的参数(stored\_args),参数保留了除第一个以外的,因为第一个参数是要被柯里化的函数。schonfinkelize()返回了一个函数。当这个返回的函数被调用的时候,它可以(通过闭包)访问到已经存储的参数stored\_args和slice。新的函数只需要合并老的部分应用的参数(stored\_args)和新的参数(new\_args),然后将它们应用到原来的函数fn(也可以在装饰中访问到)即可。 + +现在有了通用的柯里化函数,就可以做一些测试了: + + // a normal function + function add(x, y) { + return x + y; + } + + // curry a function to get a new function + var newadd = schonfinkelize(add, 5); + newadd(4); // 9 + + // another option -- call the new function directly + schonfinkelize(add, 6)(7); // 13 + +用来做函数转换的schonfinkelize()并不局限于单个参数或者单步的柯里化。这里有些更多用法的例子: + + // a normal function + function add(a, b, c, d, e) { + return a + b + c + d + e; + } + + // works with any number of arguments + schonfinkelize(add, 1, 2, 3)(5, 5); // 16 + + // two-step currying + var addOne = schonfinkelize(add, 1); + addOne(10, 10, 10, 10); // 41 + var addSix = schonfinkelize(addOne, 2, 3); + addSix(5, 5); // 16 From befd9bc14ba828716c81db26a18b8fe6a2f28476 Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 19 Jun 2012 22:07:14 +0800 Subject: [PATCH 015/258] =?UTF-8?q?=E7=AC=AC4=E7=AB=A0=20=E6=9F=AF?= =?UTF-8?q?=E9=87=8C=E5=8C=96=E4=B8=80=E8=8A=82=E7=BF=BB=E8=AF=91=E5=AE=8C?= =?UTF-8?q?=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/chapter4.markdown b/chapter4.markdown index c11728f..45ce126 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -966,3 +966,8 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 var addSix = schonfinkelize(addOne, 2, 3); addSix(5, 5); // 16 +### 什么时候使用柯里化 + +当你发现自己在调用同样的函数并且传入的参数大部分都相同的时候,就是考虑柯里化的理想场景了。你可以通过传入一部分的参数动态地创建一个新的函数。这个新函数会存储那些重复的参数(所以你不需要再每次都传入),然后再在调用原始函数的时候将整个参数列表补全,正如原始函数期待的那样。 + + From 1d2746565fb1041505207937a66112d19b70fad4 Mon Sep 17 00:00:00 2001 From: TooBug Date: Wed, 20 Jun 2012 13:22:12 +0800 Subject: [PATCH 016/258] =?UTF-8?q?=E7=AC=AC4=E7=AB=A0=20=E5=87=BD?= =?UTF-8?q?=E6=95=B0=20=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 49 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/chapter4.markdown b/chapter4.markdown index 45ce126..6ff901b 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -467,7 +467,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 alert('watch out!'); })(); -这种模式很有用,它为我们提供一个作用域的沙箱,可以在执行一些初始化代码的时候使用。设想这样的场景:当页面加载的时候,你需要运行一些代码,比如绑定事件、创建对象等等。所有的这些代码都只需要运行一次,所以没有必要创建一个具名函数。但是这些代码需要一些临时变量,而这些变量在初始化完之后又不会再次用到。显然,把这些变量作为全局变量声明也是不合适的。正因为如此,我们才需要立即执行的函数。它可以把你所有的代码包裹到一个作用域里面,而不会暴露任何变量到全局作用域中: +这种模式很有用,它为我们提供一个作用域的沙箱,可以在执行一些初始化代码的时候使用。设想这样的场景:当页面加载的时候,你需要运行一些代码,比如绑定事件、创建对象等等。所有的这些代码都只需要运行一次,所以没有必要创建一个带有名字的函数。但是这些代码需要一些临时变量,而这些变量在初始化完之后又不会再次用到。显然,把这些变量作为全局变量声明也是不合适的。正因为如此,我们才需要立即执行的函数。它可以把你所有的代码包裹到一个作用域里面,而不会暴露任何变量到全局作用域中: (function () { @@ -971,3 +971,50 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 当你发现自己在调用同样的函数并且传入的参数大部分都相同的时候,就是考虑柯里化的理想场景了。你可以通过传入一部分的参数动态地创建一个新的函数。这个新函数会存储那些重复的参数(所以你不需要再每次都传入),然后再在调用原始函数的时候将整个参数列表补全,正如原始函数期待的那样。 +##小结 + +在JavaScript中,开发者对函数的理解和运用的要求是比较苛刻的。在本章中,主要讨论了有关函数的一些背景知识和术语。你知道了JavaScript函数中两个重要的特性,也就是: + +1. 函数是一等对象,他们可以被作为值传递,也可以拥有属性和方法。 +2. 函数拥有本地作用域,而大括号不产生块级作用域。另外需要注意的是,变量的声明会被提前到本地作用域顶部。 + +创建一个函数的语法有: + +1. 带有名字的函数表达式 +2. 函数表达式(和上一种一样,但是没有名字),也就是为大家熟知的“匿名函数” +3. 函数声明,与其它语言的函数语法相似 + +在介绍完背景和函数的语法后,介绍了一些有用的模式,按分类列出: + +1. API模式,它们帮助我们为函数给出更干净的接口,包括: + +- 回调模式 +传入一个函数作为参数 + +- 对象的配置 +帮助保持函数的参数数量可控 + +- 返回函数 +函数的返回值是另一个函数 + +- 柯里化 +新函数在已有函数的基础上再加上一部分参数构成 + +2. 初始化模式,这些模式帮助我们用一种干净的、结构化的方法来做一些初始化工作(在web页面和应用中非常常见),通过一些临时变量来保证不污染全局命名空间。这些模式包括: + +- 立即执行的函数 +当它们被定义后立即执行 + +- 立即初始化的对象 +初始化工作被放入一个匿名对象,这个对象提供一个可以立即被执行的方法 + +- 启动时间程序 +使分支代码只在初始化的时候执行一次,而不是在整个程序生命周期中反复执行 + +3. 性能模式,这些模式帮助提高代码的性能,包括: + +- 备忘录 +利用函数的属性,使已经计算过的值不用再次计算 + +- 自定义函数 +重写自身的函数体,使第二次及后续的调用做更少的工作 \ No newline at end of file From a525fba92d9d387e14fd62f09db1042b19258120 Mon Sep 17 00:00:00 2001 From: TooBug Date: Wed, 20 Jun 2012 18:50:30 +0800 Subject: [PATCH 017/258] =?UTF-8?q?=E7=AC=AC4=E7=AB=A0=20=E5=90=8E?= =?UTF-8?q?=E9=9D=A2=E5=87=A0=E8=8A=82=E9=80=9A=E8=AF=BB=E3=80=81=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E6=B6=A6=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 101 +++++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 50 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index 6ff901b..8d172ba 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -402,7 +402,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 当函数中包含一些初始化操作,并希望这些初始化只执行一次,那么这种模式是非常适合这个场景的。因为能避免的重复执行则尽量避免,函数的一部分可能再也不会执行到。在这个场景中,函数执行一次后就被重写为另外一个函数了。 -使用这种模式可以帮助提高应用的执行效率,因为重新定义的函数执行的更少。 +使用这种模式可以帮助提高应用的执行效率,因为重新定义的函数执行更少的代码。 >这种模式的另外一个名字是“函数的懒惰定义”,因为直到函数执行一次后才重新定义,可以说它是“某个时间点之后才存在”,简称“懒惰定义”。 @@ -442,7 +442,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 scareMe(); // Double boo! console.log(scareMe.property); // undefined -从结果来看,当自定义函数被赋值给一个新的变量的时候,这段使用自定义函数的代码的执行结果与我们可能期望的结果并不一样。每当prank()运行的时候,它都弹出“Boo!”。同时它也重写了scareMe()函数,但是prank()自己仍然能够使用之前的定义,包括属性property。在这个函数被作为spooky对象的boo()方法调用的时候,结果也一样。所有的这些调用,在第一次的时候就已经修改了全局的scareMe()的指向,所以当它最终被调用的时候,它的函数体已经被修改为弹出“Double boo”。它也就不能获取到新添加的属性“property”。 +从结果来看,当自定义函数被赋值给一个新的变量的时候,这段使用自定义函数的代码的执行结果与我们期望的结果可能并不一样。每当prank()运行的时候,它都弹出“Boo!”。同时它也重写了scareMe()函数,但是prank()自己仍然能够使用之前的定义,包括属性property。在这个函数被作为spooky对象的boo()方法调用的时候,结果也一样。所有的这些调用,在第一次的时候就已经修改了全局的scareMe()的指向,所以当它最终被调用的时候,它的函数体已经被修改为弹出“Double boo”。它也就不能获取到新添加的属性“property”。 ## 立即执行的函数 @@ -453,7 +453,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 alert('watch out!'); }()); -这种模式其实本质上只是一个在创建后就被执行的函数表达式(具名或者匿名)。“立即执行的函数”这种说法并没有在ECMAScript标准中被定义,但它作为一个名词,有助于我们的描述和讨论。 +这种模式本质上只是一个在创建后就被执行的函数表达式(具名或者匿名)。“立即执行的函数”这种说法并没有在ECMAScript标准中被定义,但它作为一个名词,有助于我们的描述和讨论。 这种模式由以下几个部分组成: @@ -467,7 +467,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 alert('watch out!'); })(); -这种模式很有用,它为我们提供一个作用域的沙箱,可以在执行一些初始化代码的时候使用。设想这样的场景:当页面加载的时候,你需要运行一些代码,比如绑定事件、创建对象等等。所有的这些代码都只需要运行一次,所以没有必要创建一个带有名字的函数。但是这些代码需要一些临时变量,而这些变量在初始化完之后又不会再次用到。显然,把这些变量作为全局变量声明也是不合适的。正因为如此,我们才需要立即执行的函数。它可以把你所有的代码包裹到一个作用域里面,而不会暴露任何变量到全局作用域中: +这种模式很有用,它为我们提供一个作用域的沙箱,可以在执行一些初始化代码的时候使用。设想这样的场景:当页面加载的时候,你需要运行一些代码,比如绑定事件、创建对象等等。所有的这些代码都只需要运行一次,所以没有必要创建一个带有名字的函数。但是这些代码需要一些临时变量,而这些变量在初始化完之后又不会再次用到。显然,把这些变量作为全局变量声明是不合适的。正因为如此,我们才需要立即执行的函数。它可以把你所有的代码包裹到一个作用域里面,而不会暴露任何变量到全局作用域中: (function () { @@ -479,7 +479,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 }()); // "Today is Fri, 13" -如果这段代码没有包裹到立即执行函数中,那么变量days,today,msg都会是全局变量,而这仅仅是由初始化代码遗留下来的垃圾。 +如果这段代码没有被包裹到立即执行函数中,那么变量days、today、msg都会是全局变量,而这些变量仅仅是由因为初始化而遗留下来的垃圾,没有任何用处。 ### 立即执行的函数的参数 @@ -496,7 +496,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 通常的做法,会把全局对象当作一个参数传给立即执行的函数,以保证在函数内部也可以访问到全局对象,而不是使用window对象,这样可以使得代码在非浏览器环境中使用时更具可移植性。 -值得注意的是,一般来说尽量不要给立即执行的函数传入太多的参数,否则的话,你在阅读代码的时候需要频繁地上下滚动代码。 +值得注意的是,一般情况下尽量不要给立即执行的函数传入太多的参数,否则会有一件麻烦的事情,就是你在阅读代码的时候需要频繁地上下滚动代码。 ### 立即执行的函数的返回值 @@ -506,7 +506,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 return 2 + 2; }()); -如果省略括号的话也可以达到同样的目的,因为如果需要将返回值赋值给变量,那么括号就不是必需的。省略第一对括号的代码是这样子: +如果省略括号的话也可以达到同样的目的,因为如果需要将返回值赋给变量,那么第一对括号就不是必需的。省略括号的代码是这样子: var result = function () { return 2 + 2; @@ -520,9 +520,9 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 return 2 + 2; })(); -前面的例子中,立即执行的函数返回的是一个基本类型的数值。但事实上,除了基本类型以外,一个立即执行的函数可以返回任意类型的值,包括返回一个函数都可以。你可以利用立即执行的函数的作用域来存储一些私有的数据,这些数据只能在返回的内层函数中被访问。 +前面的例子中,立即执行的函数返回的是一个基本类型的数值。但事实上,除了基本类型以外,一个立即执行的函数可以返回任意类型的值,甚至返回一个函数都可以。你可以利用立即执行的函数的作用域来存储一些私有的数据,这些数据只能在返回的内层函数中被访问。 -在下面的例子中,立即执行的函数的返回值是一个函数,这个函数会简单地返回res的值,并且它被赋值给了变量getResult。而res就是一个预先计算好的变量,它被存储在立即执行函数的闭包中: +在下面的例子中,立即执行的函数的返回值是一个函数,这个函数会简单地返回res的值,并且它被赋给了变量getResult。而res是一个预先计算好的变量,它被存储在立即执行函数的闭包中: var getResult = (function () { var res = 2 + 2; @@ -533,19 +533,20 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 在定义一个对象的属性的时候也可以使用立即执行的函数。设想一下这样的场景:你需要定义一个对象的属性,这个属性在对象的生命周期中都不会改变,但是在定义之前,你需要做一点额外的工作来得到正确的值。这种情况下你就可以使用立即执行的函数来包裹那些额外的工作,然后将它的返回值作为对象属性的值。下面是一个例子: -var o = { - message: (function () { - var who = "me", - what = "call"; - return what + " " + who; - }()), - getMsg: function () { - return this.message; - } -}; -// usage -o.getMsg(); // "call me" -o.message; // "call me" + var o = { + message: (function () { + var who = "me", + what = "call"; + return what + " " + who; + }()), + getMsg: function () { + return this.message; + } + }; + + // usage + o.getMsg(); // "call me" + o.message; // "call me" 在这个例子中,o.message是一个字符串,而不是一个函数,但是它需要一个函数在脚本载入后来得到这个属性值。 @@ -556,9 +557,9 @@ o.message; // "call me" > 立即执行的函数还有一些名字,比如“自调用函数”或者“自执行函数”,因为这些函数会在被定义后立即执行自己。 -这种模式也经常被用到书签代码中,因为书签代码会在任何一个页面运行,所以需要保持全局命名空间干净是一个非常残酷的需求。 +这种模式也经常被用到书签代码中,因为书签代码会在任何一个页面运行,所以需要非常苛刻地保持全局命名空间干净。 -这种模式也可以让你包裹一些独立的特性到一个封闭的模块中。设想你的页面是静态的,在没有JavaScript的时候工作正常,然后,本着渐进增强的精神,你给页面加入了一点增加代码。这时候,你就可以把你的代码(也可以叫“模块”或者“特性”)到一个立即执行的函数中并且保证页面在有没有它的时候都可以正常工作。然后你就可以加入更多的增强特性,或者对它们进行移除、进行独立测试或者允许用户禁用等等。 +这种模式也可以让你包裹一些独立的特性到一个封闭的模块中。设想你的页面是静态的,在没有JavaScript的时候工作正常,然后,本着渐进增强的精神,你给页面加入了一点增加代码。这时候,你就可以把你的代码(也可以叫“模块”或者“特性”)放到一个立即执行的函数中并且保证页面在有没有它的时候都可以正常工作。然后你就可以加入更多的增强特性,或者对它们进行移除、进行独立测试或者允许用户禁用等等。 你可以使用下面的模板定义一段函数代码,我们叫它module1: @@ -602,7 +603,7 @@ o.message; // "call me" ({...}).init(); ({...}.init()); -这种模式的好处和自动执行的函数模式是一样的:在做一些一次性的初始化工作的时候保护全局作用域不被污染。从语法上看,这种模式似乎比只包含一段代码在一个匿名函数中要复杂一些,但是如果你的初始化工作比较复杂(这种情况很常见),它会给整个初始化工作一个比较清晰的结构。比如,一些私有的辅助性函数可以被很轻易地看出来,因为它们是这个临时对象的属性,但是如果是在立即执行的函数模式中,它们很可能只是一些散落和函数。 +这种模式的好处和自动执行的函数模式是一样的:在做一些一次性的初始化工作的时候保护全局作用域不被污染。从语法上看,这种模式似乎比只包含一段代码在一个匿名函数中要复杂一些,但是如果你的初始化工作比较复杂(这种情况很常见),它会给整个初始化工作一个比较清晰的结构。比如,一些私有的辅助性函数可以被很轻易地看出来,因为它们是这个临时对象的属性,但是如果是在立即执行的函数模式中,它们很可能只是一些散落的函数。 这种模式的一个弊端是,JavaScript压缩工具可能不能像压缩一段包裹在函数中的代码一样有效地压缩这种模式的代码。这些私有的属性和方法不被会重命名为一些更短的名字,因为从压缩工具的角度来看,保证压缩的可靠性更重要。在写作本书的时候,Google出品的Closure Compiler的“advanced”模式是唯一会重命名立即初始化的对象的属性的压缩工具。一个压缩后的样例是这样: @@ -611,13 +612,13 @@ o.message; // "call me" > 这种模式主要用于一些一次性的工作,并且在init()方法执行完后就无法再次访问到这个对象。如果希望在这些工作完成后保持对对象的引用,只需要简单地在init()的末尾加上return this;即可。 -## 启动时间程序 +## 条件初始化 -启动时间程序(也叫加载时间程序)是一种优化模式。当你知道某种条件在整个程序生命周期中都不会变化的时候,那么对这个条件的探测只做一次就很有意义。浏览器探测(或者特征检测)是一个典型的例子。 +条件初始化(也叫条件加载)是一种优化模式。当你知道某种条件在整个程序生命周期中都不会变化的时候,那么对这个条件的探测只做一次就很有意义。浏览器探测(或者特征检测)是一个典型的例子。 -举例说明,当你探测到XMLHttpRequest被作为一个本地对象支持时,就知道浏览器不会在程序执行过程中改变这一情况,也不用突然又需要去处理ActiveX对象。当环境不发生变化的时候,你的代码就没有必要在需要在每次XHR对象时探测一遍(并且得到同样的结果)。 +举例说明,当你探测到XMLHttpRequest被作为一个本地对象支持时,就知道浏览器不会在程序执行过程中改变这一情况,也不会出现突然需要去处理ActiveX对象的情况。当环境不发生变化的时候,你的代码就没有必要在需要在每次XHR对象时探测一遍(并且得到同样的结果)。 -另外一些可以从启动时间程序中获益的场景是获得一个DOM元素的computed styles或者是绑定事件处理函数。大部分程序员在他们的客户端编程生涯中都编写过事件绑定和取消绑定相关的组件,像下面的例子: +另外一些可以从条件初始化中获益的场景是获得一个DOM元素的computed styles或者是绑定事件处理函数。大部分程序员在他们的客户端编程生涯中都编写过事件绑定和取消绑定相关的组件,像下面的例子: // BEFORE var utils = { @@ -637,7 +638,7 @@ o.message; // "call me" 这段代码的问题就是效率不高。每当你执行utils.addListener()或者utils.removeListener()时,同样的检查都会被重复执行。 -如果使用启动时间程序,那么浏览器探测的工作只需要在初始化代码的时候执行一次。在初始化的时候,代码探测一次环境,然后重新定义这个函数在剩下来的程序生命周期中应该怎样工作。下面是一个例子,看看如何达到这个目的: +如果使用条件初始化,那么浏览器探测的工作只需要在初始化代码的时候执行一次。在初始化的时候,代码探测一次环境,然后重新定义这个函数在剩下来的程序生命周期中应该怎样工作。下面是一个例子,看看如何达到这个目的: // AFTER @@ -671,18 +672,18 @@ o.message; // "call me" }; } -说到这里,要特别提醒一下关于浏览器探测的事情。当你使用这个模式的时候,不要对浏览器特性过度假设。举个例子,如果你探测到浏览器不支持window.addEventListener,不要假设这个浏览器是IE,也不要认为它不支持原生的XMLHttpRequest,虽然这个结论在整个浏览器历史上的某个点是正确的。当然,也有一些情况是可以放心地做一些特性假设的,比如.addEventListener和.removeEventListerner,但是通常来讲,浏览器的特性在发生变化时都是独立的。最好的策略就是分别探测每个特性,然后使用启动时间程序,使这种探测只做一次。 +说到这里,要特别提醒一下关于浏览器探测的事情。当你使用这个模式的时候,不要对浏览器特性过度假设。举个例子,如果你探测到浏览器不支持window.addEventListener,不要假设这个浏览器是IE,也不要认为它不支持原生的XMLHttpRequest,虽然这个结论在整个浏览器历史上的某个点是正确的。当然,也有一些情况是可以放心地做一些特性假设的,比如.addEventListener和.removeEventListerner,但是通常来讲,浏览器的特性在发生变化时都是独立的。最好的策略就是分别探测每个特性,然后使用条件初始化,使这种探测只做一次。 -## 函数属性——一种备忘录模式 +## 函数属性——Memoization模式 函数也是对象,所以它们可以有属性。事实上,函数也确实本来就有一些属性。比如,对一个函数来说,不管是用什么语法创建的,它会自动拥有一个length属性来标识这个函数期待接受的参数个数: function func(a, b, c) {} console.log(func.length); // 3 -任何时候都可以给函数添加自定义属性。添加自定义属性的一个有用场景是缓存函数的执行结果(返回值),这样下次同样的函数被调用的时候就不需要再做一次那些可能很复杂的计算。缓存一个函数的运行结果也就是为大家所熟知的备忘录(memoization)。 +任何时候都可以给函数添加自定义属性。添加自定义属性的一个有用场景是缓存函数的执行结果(返回值),这样下次同样的函数被调用的时候就不需要再做一次那些可能很复杂的计算。缓存一个函数的运行结果也就是为大家所熟知的Memoization。 -在下面的例子中,myFunc函数创建了一个cache属性,可以通过myFunc.cache访问到。这个cache属性是一个对象(hash表),传给函数的参数会作为对象的key,函数执行结果会作为对象的值。函数的执行结果可以是会用到的任何的复杂数据结构: +在下面的例子中,myFunc函数创建了一个cache属性,可以通过myFunc.cache访问到。这个cache属性是一个对象(hash表),传给函数的参数会作为对象的key,函数执行结果会作为对象的值。函数的执行结果可以是任何的复杂数据结构: var myFunc = function (param) { if (!myFunc.cache[param]) { @@ -696,7 +697,7 @@ o.message; // "call me" // cache storage myFunc.cache = {}; -上面的代码假设函数只接受一个参数param,并且这个参数是基本类型(比如字符串)。如果你有更多更复杂的参数,则通常需要对它们进行序列化。(译注:序列化后来作为缓存对象的key。)比如,你需要将arguments对象序列化为JSON字符串,然后使用JSON字符串作为cache对象的key: +上面的代码假设函数只接受一个参数param,并且这个参数是基本类型(比如字符串)。如果你有更多更复杂的参数,则通常需要对它们进行序列化。比如,你需要将arguments对象序列化为JSON字符串,然后使用JSON字符串作为cache对象的key: var myFunc = function () { @@ -735,9 +736,9 @@ o.message; // "call me" myFunc.cache = {}; -## 对象的配置 +## 配置对象 -对象的配置模式是一种提供更简洁的API的方法,尤其是当你正在写一个即将被其它程序调用的类库或者任何其它代码的时候。 +配置对象模式是一种提供更简洁的API的方法,尤其是当你正在写一个即将被其它程序调用的类库之类的代码的时候。 软件在开发和维护过程中需要不断改变是一个不争的事实。这样的事情总是以一些有限的需求开始,但是随着开发的进行,越来越多的功能会不断被加进来。 @@ -766,19 +767,19 @@ o.message; // "call me" }; addPerson(conf); -对象的配置模式的好处是: +配置对象模式的好处是: - 不需要记住参数的顺序 - 可以很安全地跳过可选参数 - 拥有更好的可读性和可维护性 - 更容易添加和移除参数 -对象的配置模式的坏处是: +配置对象模式的坏处是: - 需要记住参数的名字 - 参数名字不能被压缩 -举些实例,这个模式对创建DOM元素的函数或者是给元素设定CSS样式函数会非常实用,因为元素和CSS样式可能会有很多但是大部分可选的属性。 +举些实例,这个模式对创建DOM元素的函数或者是给元素设定CSS样式的函数会非常实用,因为元素和CSS样式可能会有很多但是大部分可选的属性。 ## 柯里化 (Curry) @@ -818,7 +819,7 @@ o.message; // "call me" 在这个例子中,sayHi()中的this指向alien。而在上一个例子中,this是指向的全局对象。(译注:这个例子的代码有误,最后一行的sayHi并不能访问到alien的sayHi方法,需要使用alien.sayHi.apply(alien, ["humans"])才可正确运行。另外,在sayHi中也没有出现this。) -正如上面两个例子所展现出来的一样,我们所谓的函数调用跟函数应用的一种语法糖没有什么太大的差别。 +正如上面两个例子所展现出来的一样,我们将所谓的函数调用当作函数应用的一种语法糖并没有什么太大的问题。 需要注意的是,除了apply()之外,Function.prototype对象还有一个call()方法,但是它仍然只是apply()的一种语法糖。(译注:这两个方法的区别在于,apply()只接受两个参数,第二个参数为需要传给函数的参数数组,而call()则接受任意多个参数,从第二个开始将参数依次传给函数。)不过有种情况下使用这个语法糖会更好:当你的函数只接受一个参数的时候,你可以省去为唯一的一个元素创建数组的工作: @@ -871,7 +872,7 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 // applying an argument to the new function newadd.apply(null, [4]); // 9 -正如你所看到的一样,部分应用给了我们另一个函数,这个函数可以在稍后调用的时候接受其它的参数。这实际上跟add(5)(4)是等价的,因为add(5)返回了一个函数,这个函数可以使用(4)来调用。我们又一次看到,熟悉的add(5, 4)也跟add(5)(4)的一种语法糖差不了多少。 +正如你所看到的一样,部分应用给了我们另一个函数,这个函数可以在稍后调用的时候接受其它的参数。这实际上跟add(5)(4)是等价的,因为add(5)返回了一个函数,这个函数可以使用(4)来调用。我们又一次看到,熟悉的add(5, 4)也差不多是add(5)(4)的一种语法糖。 现在,让我们回到地球:并不存在这样的一个partialApply()函数,并且函数的默认表现也不会像上面的例子中那样。但是你完全可以自己去写,因为JavaScript的动态特性完全可以做到这样。 @@ -879,7 +880,7 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 ### 柯里化(Currying) -柯里化和辛辣的印度菜可没什么关系;它来自数学家Haskell Curry。(Haskell编程语言也是因他而得名。)柯里化是一个变换的函数的过程。柯里化的另外一个名字也叫schönfinkelisation,来自另一位数学家——Moses Schönfinkelisation——这种变换的最初发明者。 +柯里化和辛辣的印度菜可没什么关系;它来自数学家Haskell Curry。(Haskell编程语言也是因他而得名。)柯里化是一个变换函数的过程。柯里化的另外一个名字也叫schönfinkelisation,来自另一位数学家——Moses Schönfinkelisation——这种变换的最初发明者。 所以我们怎样对一个函数进行柯里化呢?其它的函数式编程语言也许已经原生提供了支持并且所有的函数已经默认柯里化了。在JavaScript中我们可以修改一下add()函数使它柯里化,然后支持部分应用。 @@ -920,7 +921,7 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 return x + y; } -在这些例子中,add()函数自己处理了部分应用。有没有可能用一种更为通用的方式来做同样的事情呢?换句话说,我们能不能对任意一个函数进行处理,得到一个新函数,使它可以处理部分参数?下面的代码片段展示了一个通用函数的例子,我们叫它schonfinkelize(),正是用来做这个的。我们使用schonfinkelize()一部分原因是它比较难发音,另一部分原因是它听起来比较像动词(使用“curry”则不是那么明确),而我们刚好需要一个动词来表明这是一个函数转换的过程。 +在这些例子中,add()函数自己处理了部分应用。有没有可能用一种更为通用的方式来做同样的事情呢?换句话说,我们能不能对任意一个函数进行处理,得到一个新函数,使它可以处理部分参数?下面的代码片段展示了一个通用函数的例子,我们叫它schonfinkelize(),正是用来做这个的。我们使用schonfinkelize()这个名字,一部分原因是它比较难发音,另一部分原因是它听起来比较像动词(使用“curry”则不是那么明确),而我们刚好需要一个动词来表明这是一个函数转换的过程。 这是一个通用的柯里化函数: @@ -934,7 +935,7 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 }; } -这个schonfinkelize可能显得比较复杂了,只是因为在JavaScript中arguments不是一个真的数组。从Array.prototype中借用slice()方法帮助我们将arguments转换成数组,以便能更好地对它进行操作。当schonfinkelize()第一次被调用的时候,它使用slice变量存储了对slice()方法的引用,同时也存储了调用时的参数(stored\_args),参数保留了除第一个以外的,因为第一个参数是要被柯里化的函数。schonfinkelize()返回了一个函数。当这个返回的函数被调用的时候,它可以(通过闭包)访问到已经存储的参数stored\_args和slice。新的函数只需要合并老的部分应用的参数(stored\_args)和新的参数(new\_args),然后将它们应用到原来的函数fn(也可以在装饰中访问到)即可。 +这个schonfinkelize可能显得比较复杂了,只是因为在JavaScript中arguments不是一个真的数组。从Array.prototype中借用slice()方法帮助我们将arguments转换成数组,以便能更好地对它进行操作。当schonfinkelize()第一次被调用的时候,它使用slice变量存储了对slice()方法的引用,同时也存储了调用时的除去第一个之外的参数(stored\_args),因为第一个参数是要被柯里化的函数。schonfinkelize()返回了一个函数。当这个返回的函数被调用的时候,它可以(通过闭包)访问到已经存储的参数stored\_args和slice。新的函数只需要合并老的部分应用的参数(stored\_args)和新的参数(new\_args),然后将它们应用到原来的函数fn(也可以在闭包中访问到)即可。 现在有了通用的柯里化函数,就可以做一些测试了: @@ -973,7 +974,7 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 ##小结 -在JavaScript中,开发者对函数的理解和运用的要求是比较苛刻的。在本章中,主要讨论了有关函数的一些背景知识和术语。你知道了JavaScript函数中两个重要的特性,也就是: +在JavaScript中,开发者对函数的理解和运用的要求是比较苛刻的。在本章中,主要讨论了有关函数的一些背景知识和术语。介绍了JavaScript函数中两个重要的特性,也就是: 1. 函数是一等对象,他们可以被作为值传递,也可以拥有属性和方法。 2. 函数拥有本地作用域,而大括号不产生块级作用域。另外需要注意的是,变量的声明会被提前到本地作用域顶部。 @@ -991,7 +992,7 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 - 回调模式 传入一个函数作为参数 -- 对象的配置 +- 配置对象 帮助保持函数的参数数量可控 - 返回函数 @@ -1008,12 +1009,12 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 - 立即初始化的对象 初始化工作被放入一个匿名对象,这个对象提供一个可以立即被执行的方法 -- 启动时间程序 +- 条件初始化 使分支代码只在初始化的时候执行一次,而不是在整个程序生命周期中反复执行 -3. 性能模式,这些模式帮助提高代码的性能,包括: +3. 性能模式,这些模式帮助提高代码的执行速度,包括: -- 备忘录 +- Memoization 利用函数的属性,使已经计算过的值不用再次计算 - 自定义函数 From ce1c625c533965369b0261979e88dd92a93ee1c3 Mon Sep 17 00:00:00 2001 From: TooBug Date: Wed, 20 Jun 2012 19:02:08 +0800 Subject: [PATCH 018/258] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E6=AD=A3README=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.markdown | 198 +++++++++++++++++++++++----------------------- chapter4.markdown | 36 ++++++++- 2 files changed, 134 insertions(+), 100 deletions(-) diff --git a/README.markdown b/README.markdown index dea4860..2b98f3e 100644 --- a/README.markdown +++ b/README.markdown @@ -11,105 +11,105 @@ # 目录 -## [第一章 概述](chapter1.markdown) - -- [模式](chapter1.markdown) -- [JavaScript:概念](chapter1.markdown#a2) - - [面向对象](chapter1.markdown#a3) - - [无类](chapter1.markdown#a4) - - [原型](chapter1.markdown#a5) - - [运行环境](chapter1.markdown#a6) -- [ECMAScript 5](chapter1.markdown#a7) -- [JSLint](chapter1.markdown#a8) -- [控制台工具](chapter1.markdown#a9) - -## [第二章 高质量JavaScript基本要点](chapter2.markdown) - -- [编写可维护的代码](chapter2.markdown#a2) -- [减少全局对象](chapter2.markdown#a3) - - [全局对象带来的困扰](chapter2.markdown#a4) - - [忘记var时的副作用](chapter2.markdown#a5) - - [访问全局对象](chapter2.markdown#a6) - - [单 var 模式](chapter2.markdown#a7) - - [声明提前:分散的 var 带来的问题](chapter2.markdown#a8) -- [for 循环](chapter2.markdown#a9) -- [for-in 循环](chapter2.markdown#a10) -- [(不)扩充内置原型](chapter2.markdown#a11) -- [switch 模式](chapter2.markdown#a12) -- [避免隐式类型转换](chapter2.markdown#a13) - - [避免使用 eval()](chapter2.markdown#a14) -- [使用parseInt()进行数字转换](chapter2.markdown#a15) -- [编码风格](chapter2.markdown#a16) - - [缩进](chapter2.markdown#a17) - - [花括号](chapter2.markdown#a18) - - [左花括号的放置](chapter2.markdown#a19) - - [空格](chapter2.markdown#a20) -- [命名规范](chapter2.markdown#a21) - - [构造器命名中的大小写](chapter2.markdown#a22) - - [单词分隔](chapter2.markdown#a23) - - [其他命名风格](chapter2.markdown#a24) -- [书写注释](chapter2.markdown#a25) -- [书写API文档](chapter2.markdown#a26) - - [一个例子:YUIDoc](chapter2.markdown#a27) -- [编写易读的代码](chapter2.markdown#a28) -- [相互评审](chapter2.markdown#a29) -- [生产环境中的代码压缩(Minify)](chapter2.markdown#a30) -- [运行JSLint](chapter2.markdown#a31) -- [小结](chapter2.markdown#a32) - -## [第三章 直接量和构造函数](chapter3.markdown) - -- [对象直接量](chapter3.markdown#a2) - - [对象直接量语法](chapter3.markdown#a3) - - [通过构造函数创建对象](chapter3.markdown#a4) - - [获得对象的构造器](chapter3.markdown#a5) -- [自定义构造函数](chapter3.markdown#a6) - - [构造函数的返回值](chapter3.markdown#a7) -- [强制使用new的模式](chapter3.markdown#a8) - - [命名约定](chapter3.markdown#a9) - - [使用that](chapter3.markdown#a10) - - [调用自身的构造函数](chapter3.markdown#a11) -- [数组直接量](chapter3.markdown#a12) - - [数组直接量语法](chapter3.markdown#a13) - - [有意思的数组构造器](chapter3.markdown#a14) - - [检查是不是数组](chapter3.markdown#a15) -- [JSON](chapter3.markdown#a16) - - [使用JSON](chapter3.markdown#a17) -- [正则表达式直接量](chapter3.markdown#a18) - - [正则表达式直接量语法](chapter3.markdown#a19) -- [原始值的包装对象](chapter3.markdown#a20) -- [Error对象](chapter3.markdown#a21) -- [小结](chapter3.markdown#a22) - -## [第四章 函数](chapter4.markdown#a) - -- [背景知识](chapter4.markdown#a) - - [术语释义](chapter4.markdown#a) - - [声明 vs 表达式:命名与提前](chapter4.markdown#a) - - [函数的name属性](chapter4.markdown#a) - - [函数提前](chapter4.markdown#a) -- [回调模式](chapter4.markdown#a) - - [一个回调的例子](chapter4.markdown#a) - - [回调和作用域](chapter4.markdown#a) - - [异步事件监听](chapter4.markdown#a) - - [超时](chapter4.markdown#a) - - [库中的回调](chapter4.markdown#a) -- [返回函数](chapter4.markdown#a) -- [自定义函数](chapter4.markdown#a) -- [立即执行的函数](chapter4.markdown#a) - - [立即执行的函数的参数](chapter4.markdown#a) - - [立即执行的函数的返回值](chapter4.markdown#a) - - [好处和用法](chapter4.markdown#a) -- [立即初始化的对象](chapter4.markdown#a) -- [启动时间程序](chapter4.markdown#a) -- [函数属性——一种备忘录模式](chapter4.markdown#a) -- [对象的配置](chapter4.markdown#a) -- 柯里化 (Curry) - - 函数应用 - - 部分应用 - - 柯里化 - - 什么时候使用柯里化 -- 小节 +## [第一章 概述](blob/master/chapter1.markdown) + +- [模式](blob/master/chapter1.markdown) +- [JavaScript:概念](blob/master/chapter1.markdown#a2) + - [面向对象](blob/master/chapter1.markdown#a3) + - [无类](blob/master/chapter1.markdown#a4) + - [原型](blob/master/chapter1.markdown#a5) + - [运行环境](blob/master/chapter1.markdown#a6) +- [ECMAScript 5](blob/master/chapter1.markdown#a7) +- [JSLint](blob/master/chapter1.markdown#a8) +- [控制台工具](blob/master/chapter1.markdown#a9) + +## [第二章 高质量JavaScript基本要点](blob/master/chapter2.markdown) + +- [编写可维护的代码](blob/master/chapter2.markdown#a2) +- [减少全局对象](blob/master/chapter2.markdown#a3) + - [全局对象带来的困扰](blob/master/chapter2.markdown#a4) + - [忘记var时的副作用](blob/master/chapter2.markdown#a5) + - [访问全局对象](blob/master/chapter2.markdown#a6) + - [单 var 模式](blob/master/chapter2.markdown#a7) + - [声明提前:分散的 var 带来的问题](blob/master/chapter2.markdown#a8) +- [for 循环](blob/master/chapter2.markdown#a9) +- [for-in 循环](blob/master/chapter2.markdown#a10) +- [(不)扩充内置原型](blob/master/chapter2.markdown#a11) +- [switch 模式](blob/master/chapter2.markdown#a12) +- [避免隐式类型转换](blob/master/chapter2.markdown#a13) + - [避免使用 eval()](blob/master/chapter2.markdown#a14) +- [使用parseInt()进行数字转换](blob/master/chapter2.markdown#a15) +- [编码风格](blob/master/chapter2.markdown#a16) + - [缩进](blob/master/chapter2.markdown#a17) + - [花括号](blob/master/chapter2.markdown#a18) + - [左花括号的放置](blob/master/chapter2.markdown#a19) + - [空格](blob/master/chapter2.markdown#a20) +- [命名规范](blob/master/chapter2.markdown#a21) + - [构造器命名中的大小写](blob/master/chapter2.markdown#a22) + - [单词分隔](blob/master/chapter2.markdown#a23) + - [其他命名风格](blob/master/chapter2.markdown#a24) +- [书写注释](blob/master/chapter2.markdown#a25) +- [书写API文档](blob/master/chapter2.markdown#a26) + - [一个例子:YUIDoc](blob/master/chapter2.markdown#a27) +- [编写易读的代码](blob/master/chapter2.markdown#a28) +- [相互评审](blob/master/chapter2.markdown#a29) +- [生产环境中的代码压缩(Minify)](blob/master/chapter2.markdown#a30) +- [运行JSLint](blob/master/chapter2.markdown#a31) +- [小结](blob/master/chapter2.markdown#a32) + +## [第三章 直接量和构造函数](blob/master/chapter3.markdown) + +- [对象直接量](blob/master/chapter3.markdown#a2) + - [对象直接量语法](blob/master/chapter3.markdown#a3) + - [通过构造函数创建对象](blob/master/chapter3.markdown#a4) + - [获得对象的构造器](blob/master/chapter3.markdown#a5) +- [自定义构造函数](blob/master/chapter3.markdown#a6) + - [构造函数的返回值](blob/master/chapter3.markdown#a7) +- [强制使用new的模式](blob/master/chapter3.markdown#a8) + - [命名约定](blob/master/chapter3.markdown#a9) + - [使用that](blob/master/chapter3.markdown#a10) + - [调用自身的构造函数](blob/master/chapter3.markdown#a11) +- [数组直接量](blob/master/chapter3.markdown#a12) + - [数组直接量语法](blob/master/chapter3.markdown#a13) + - [有意思的数组构造器](blob/master/chapter3.markdown#a14) + - [检查是不是数组](blob/master/chapter3.markdown#a15) +- [JSON](blob/master/chapter3.markdown#a16) + - [使用JSON](blob/master/chapter3.markdown#a17) +- [正则表达式直接量](blob/master/chapter3.markdown#a18) + - [正则表达式直接量语法](blob/master/chapter3.markdown#a19) +- [原始值的包装对象](blob/master/chapter3.markdown#a20) +- [Error对象](blob/master/chapter3.markdown#a21) +- [小结](blob/master/chapter3.markdown#a22) + +## [第四章 函数](blob/master/chapter4.markdown) + +- [背景知识](blob/master/chapter4.markdown#a2) + - [术语释义](blob/master/chapter4.markdown#a3) + - [声明 vs 表达式:命名与提前](blob/master/chapter4.markdown#a4) + - [函数的name属性](blob/master/chapter4.markdown#a5) + - [函数提前](blob/master/chapter4.markdown#a6) +- [回调模式](blob/master/chapter4.markdown#a7) + - [一个回调的例子](blob/master/chapter4.markdown#a8) + - [回调和作用域](blob/master/chapter4.markdown#a9) + - [异步事件监听](blob/master/chapter4.markdown#a10) + - [超时](blob/master/chapter4.markdown#a11) + - [库中的回调](blob/master/chapter4.markdown#a12) +- [返回函数](blob/master/chapter4.markdown#a12) +- [自定义函数](blob/master/chapter4.markdown#a14) +- [立即执行的函数](blob/master/chapter4.markdown#a15) + - [立即执行的函数的参数](blob/master/chapter4.markdown#a16) + - [立即执行的函数的返回值](blob/master/chapter4.markdown#a17) + - [好处和用法](blob/master/chapter4.markdown#a18) +- [立即初始化的对象](blob/master/chapter4.markdown#a19) +- [条件初始化](blob/master/chapter4.markdown#a20) +- [函数属性——Memoization模式](blob/master/chapter4.markdown#a21) +- [配置对象](blob/master/chapter4.markdown#a22) +- [柯里化 (Curry)](blob/master/chapter4.markdown#a23) + - [函数应用](blob/master/chapter4.markdown#a24) + - [部分应用](blob/master/chapter4.markdown#a25) + - [柯里化](blob/master/chapter4.markdown#a26) + - [什么时候使用柯里化](blob/master/chapter4.markdown#a27) +- [小结](blob/master/chapter4.markdown#a28) ## 第五章 对象创建模式 diff --git a/chapter4.markdown b/chapter4.markdown index 8d172ba..8bb2ee5 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -1,3 +1,4 @@ + # 函数 熟练运用函数是JavaScript程序员的必备技能,因为在JavaScript中函数实在是太常用了。它能够完成的任务种类非常之多,而在其他语言中则需要很多特殊的语法支持才能达到这种能力。 @@ -6,6 +7,8 @@ 现在让我们来一起揭秘JavaScript函数,我们首先从一些背景知识开始说起。 + + ## 背景知识 JavaScript的函数具有两个主要特性,正是这两个特性让它们与众不同。第一个特性是,函数是一等对象(first-class object),第二个是函数提供作用域支持。 @@ -30,6 +33,7 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 函数的第二个重要特性是它能提供作用域支持。在JavaScript中没有块级作用域(译注:在JavaScript1.7中提供了块级作用域部分特性的支持,可以通过let来声明块级作用域内的“局部变量”),也就是说不能通过花括号来创建作用域,JavaScript中只有函数作用域(译注:这里作者的表述只针对函数而言,此外JavaScript还有全局作用域)。在函数内所有通过var声明的变量都是局部变量,在函数外部是不可见的。刚才所指花括号无法提供作用域支持的意思是说,如果在if条件句内、或在for或while循环体内用var定义了变量,这个变量并不是属于if语句或for(while)循环的局部变量,而是属于它所在的函数。如果不在任何函数内部,它会成为全局变量。在第二章里提到我们要减少对全局命名空间的污染,那么使用函数则是控制变量的作用域的不二之选。 + ### 术语释义 首先我们先简单讨论下创建函数相关的术语,因为精确无歧义的术语约定和我们所讨论的各种模式一样重要。 @@ -66,7 +70,7 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 >另外我们经常看到“函数直接量”。它用来表示函数表达式或带命名的函数表达式。由于这个术语是有歧义的,所以最好不要用它。 - + ### 声明 vs 表达式:命名与提前 那么,到底应该用哪个呢?函数声明还是函数表达式?在不能使用函数声明语法的场景下,只能使用函数表达式了。下面这个例子中,我们给函数传入了另一个函数对象作为参数,以及给对象定义方法: @@ -102,6 +106,7 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 return bar; } + ### 函数的name属性 选择函数定义模式的另一个考虑是只读属性name的可用性。尽管标准规范中并未规定,但很多运行环境都实现了name属性,在函数声明和带有名字的函数表达式中是有name的属性定义的。在匿名函数表达式中,则不一定有定义,这个是和实现相关的,在IE中是无定义的,在Firefox和Safari中是有定义的,但是值为空字符串。 @@ -120,6 +125,7 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 >我们可以将一个带名字的函数表达式赋值给变量,变量名和函数名不同,这在技术上是可行的。比如:`var foo = function bar(){};`。然而,这种用法的行为在浏览器中的兼容性不佳(特别是IE中),因此并不推荐大家使用这种模式。 + ### 函数提前 通过前面的讲解,你可能以为函数声明和带名字的函数表达式是完全等价的。事实上不是这样,主要区别在于“声明提前”的行为。 @@ -170,6 +176,8 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 - 函数是对象 - 函数提供局部变量作用域 + + ## 回调模式 函数是对象,也就意味着函数可以当作参数传入另外一个函数中。当你给函数writeCode()传入一个函数参数introduceBugs(),在某个时刻writeCode()执行了(或调用了)introduceBugs()。在这种情况下,我们说introduceBugs()是一个“回调函数”,简称“回调”: @@ -188,6 +196,7 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 注意introduceBugs()是如何作为参数传入writeCode()的,当作参数的函数不带括号。括号的意思是执行函数,而这里我们希望传入一个引用,让writeCode()在合适的时机执行它(调用它)。 + ### 一个回调的例子 我们从一个例子开始,首先介绍无回调的情况,然后在作修改。假设你有一个通用的函数,用来完成某种复杂的逻辑并返回一大段数据。假设我们用findNodes()来命名这个通用函数,这个函数用来对DOM树进行遍历,并返回我所感兴趣的页面节点: @@ -262,6 +271,7 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 node.style.display = "block"; }); + ### 回调和作用域 在上一个例子中,执行回调函数的写法是: @@ -329,6 +339,7 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 // ... }; + ### 异步事件监听 JavaScript中的回调模式已经是我们的家常便饭了,比如,如果你给网页中的元素绑定事件,则需要提供回调函数的引用,以便事件发生时能调用到它。这里有一个简单的例子,我们将console.log()作为回调函数绑定了document的点击事件: @@ -339,6 +350,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 “不要打电话给我,我会打给你”,这是好莱坞很有名的一句话,很多电影都有这句台词。电影中的主角不可能同时应答很多个电话呼叫。在JavaScript的异步事件模型中也是同样的道理。电影中是留下电话号码,JavaScript中是提供一个回调函数,当时机成熟时就触发回调。有时甚至提供了很多回调,有些回调压根是没用的,但由于这个事件可能永远不会发生,因此这些回调的逻辑也不会执行。比如,假设你从此不再用“鼠标点击”,那么你之前绑定的鼠标点击的回调函数则永远也不会执行。 + ### 超时 另外一个最常用的回调模式是在调用超时函数时,超时函数是浏览器window对象的方法,共有两个:setTimeout()和setInterval()。这两个方法的参数都是回调函数。 @@ -350,10 +362,13 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 再次需要注意,函数thePlotThickens是作为变量传入setTimeout的,它不带括号,如果带括号的话则立即执行了,这里只是用到这个函数的引用,以便在setTimeout的逻辑中调用到它。也可以传入字符串“thePlotThickens()”,但这是一种反模式,和eval()一样不推荐使用。 + ### 库中的回调 回调模式非常简单,但又很强大。可以随手拈来灵活运用,因此这种模式在库的设计中也非常得宠。库的代码要尽可能的保持通用和重用,而回调模式则可帮助库的作者完成这个目标。你不必预料和实现你所想到的所有情形,因为这会让库变的膨胀而臃肿,而且大多数用户并不需要这些多余的特性支持。相反,你将精力放在核心功能的实现上,提供回调的入口作为“钩子”,可以让库的方法变得可扩展、可定制。 + + ## 返回函数 函数是对象,因此当然可以作为返回值。也就是说,函数不一定非要返回一坨数据,函数可以返回另外一个定制好的函数,或者可以根据输入的不同按需创造另外一个函数。 @@ -386,6 +401,8 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 next(); // 2 next(); // 3 + + ## 自定义函数 我们动态定义函数,并将函数赋值给变量。如果将你定义的函数赋值给已经存在的函数变量的话,则新函数会覆盖旧函数。这样做的结果是,旧函数的引用就丢弃掉了,变量中所存储的引用值替换成了新的。这样看起来这个变量指代的函数逻辑就发生了变化,或者说函数进行了“重新定义”或“重写”。说起来有些拗口,实际上并不复杂,来看一个例子: @@ -445,6 +462,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 从结果来看,当自定义函数被赋值给一个新的变量的时候,这段使用自定义函数的代码的执行结果与我们期望的结果可能并不一样。每当prank()运行的时候,它都弹出“Boo!”。同时它也重写了scareMe()函数,但是prank()自己仍然能够使用之前的定义,包括属性property。在这个函数被作为spooky对象的boo()方法调用的时候,结果也一样。所有的这些调用,在第一次的时候就已经修改了全局的scareMe()的指向,所以当它最终被调用的时候,它的函数体已经被修改为弹出“Double boo”。它也就不能获取到新添加的属性“property”。 + ## 立即执行的函数 立即执行的函数是一种语法模式,它会使函数在定义后立即执行。看这个例子: @@ -481,6 +499,8 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 如果这段代码没有被包裹到立即执行函数中,那么变量days、today、msg都会是全局变量,而这些变量仅仅是由因为初始化而遗留下来的垃圾,没有任何用处。 + + ### 立即执行的函数的参数 立即执行的函数也可以接受参数,看这个例子: @@ -498,6 +518,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 值得注意的是,一般情况下尽量不要给立即执行的函数传入太多的参数,否则会有一件麻烦的事情,就是你在阅读代码的时候需要频繁地上下滚动代码。 + ### 立即执行的函数的返回值 和其它的函数一样,立即执行的函数也可以返回值,并且这些返回值也可以被赋值给变量: @@ -551,6 +572,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 在这个例子中,o.message是一个字符串,而不是一个函数,但是它需要一个函数在脚本载入后来得到这个属性值。 + ### 好处和用法 立即执行的函数应用很广泛。它可以帮助我们做一些不想留下全局变量的工作。所有定义的变量都只是立即执行的函数的本地变量,你完全不用担心临时变量会污染全局对象。 @@ -572,6 +594,8 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 套用这个模板,你就可以编写其它的模块。然后在发布到线上的时候,你就可以决定在这个时间节点上哪些特性是可以使用的,然后使用发布脚本将它们打包上线。 + + ## 立即初始化的对象 还有另外一种可以避免污染全局作用域的方法,和前面描述的立即执行的函数相似,叫做“立即初始化的对象”模式。这种模式使用一个带有init()方法的对象来实现,这个方法在对象被创建后立即执行。初始化的工作由init()函数来完成。 @@ -612,6 +636,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 > 这种模式主要用于一些一次性的工作,并且在init()方法执行完后就无法再次访问到这个对象。如果希望在这些工作完成后保持对对象的引用,只需要简单地在init()的末尾加上return this;即可。 + ## 条件初始化 条件初始化(也叫条件加载)是一种优化模式。当你知道某种条件在整个程序生命周期中都不会变化的时候,那么对这个条件的探测只做一次就很有意义。浏览器探测(或者特征检测)是一个典型的例子。 @@ -674,6 +699,8 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 说到这里,要特别提醒一下关于浏览器探测的事情。当你使用这个模式的时候,不要对浏览器特性过度假设。举个例子,如果你探测到浏览器不支持window.addEventListener,不要假设这个浏览器是IE,也不要认为它不支持原生的XMLHttpRequest,虽然这个结论在整个浏览器历史上的某个点是正确的。当然,也有一些情况是可以放心地做一些特性假设的,比如.addEventListener和.removeEventListerner,但是通常来讲,浏览器的特性在发生变化时都是独立的。最好的策略就是分别探测每个特性,然后使用条件初始化,使这种探测只做一次。 + + ## 函数属性——Memoization模式 函数也是对象,所以它们可以有属性。事实上,函数也确实本来就有一些属性。比如,对一个函数来说,不管是用什么语法创建的,它会自动拥有一个length属性来标识这个函数期待接受的参数个数: @@ -736,6 +763,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 myFunc.cache = {}; + ## 配置对象 配置对象模式是一种提供更简洁的API的方法,尤其是当你正在写一个即将被其它程序调用的类库之类的代码的时候。 @@ -782,10 +810,12 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 举些实例,这个模式对创建DOM元素的函数或者是给元素设定CSS样式的函数会非常实用,因为元素和CSS样式可能会有很多但是大部分可选的属性。 + ## 柯里化 (Curry) 在本章剩下的部分,我们将讨论一下关于柯里化和部分应用的话题。但是在我们开始这个话题之前,先看一下到底什么是函数应用。 + ### 函数应用 在一些纯粹的函数式编程语言中,对函数的描述不是被调用(called或者invoked),而是被应用(applied)。在JavaScript中也有同样的东西——我们可以使用Function.prototype.apply()来应用一个函数,因为在JavaScript中,函数实际上是对象,并且他们拥有方法。 @@ -827,6 +857,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 sayHi.apply(alien, ["humans"]); // "Hello, humans!" sayHi.call(alien, "humans"); // "Hello, humans!" + ### 部分应用 现在我们知道了,调用一个函数实际上就是给它应用一堆参数,那是否能够只传一部分参数而不传全部呢?这实际上跟我们手工处理数学函数非常类似。 @@ -878,6 +909,7 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 让函数理解并且处理部分应用的过程,叫柯里化(Currying)。 + ### 柯里化(Currying) 柯里化和辛辣的印度菜可没什么关系;它来自数学家Haskell Curry。(Haskell编程语言也是因他而得名。)柯里化是一个变换函数的过程。柯里化的另外一个名字也叫schönfinkelisation,来自另一位数学家——Moses Schönfinkelisation——这种变换的最初发明者。 @@ -967,11 +999,13 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 var addSix = schonfinkelize(addOne, 2, 3); addSix(5, 5); // 16 + ### 什么时候使用柯里化 当你发现自己在调用同样的函数并且传入的参数大部分都相同的时候,就是考虑柯里化的理想场景了。你可以通过传入一部分的参数动态地创建一个新的函数。这个新函数会存储那些重复的参数(所以你不需要再每次都传入),然后再在调用原始函数的时候将整个参数列表补全,正如原始函数期待的那样。 + ##小结 在JavaScript中,开发者对函数的理解和运用的要求是比较苛刻的。在本章中,主要讨论了有关函数的一些背景知识和术语。介绍了JavaScript函数中两个重要的特性,也就是: From 8e486cc74ec7fe714ec51fdd5bcb249c3276fed1 Mon Sep 17 00:00:00 2001 From: TooBug Date: Wed, 20 Jun 2012 19:03:39 +0800 Subject: [PATCH 019/258] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.markdown | 198 ++++++++++++++++++++++++------------------------ 1 file changed, 99 insertions(+), 99 deletions(-) diff --git a/README.markdown b/README.markdown index 2b98f3e..f91e753 100644 --- a/README.markdown +++ b/README.markdown @@ -11,105 +11,105 @@ # 目录 -## [第一章 概述](blob/master/chapter1.markdown) - -- [模式](blob/master/chapter1.markdown) -- [JavaScript:概念](blob/master/chapter1.markdown#a2) - - [面向对象](blob/master/chapter1.markdown#a3) - - [无类](blob/master/chapter1.markdown#a4) - - [原型](blob/master/chapter1.markdown#a5) - - [运行环境](blob/master/chapter1.markdown#a6) -- [ECMAScript 5](blob/master/chapter1.markdown#a7) -- [JSLint](blob/master/chapter1.markdown#a8) -- [控制台工具](blob/master/chapter1.markdown#a9) - -## [第二章 高质量JavaScript基本要点](blob/master/chapter2.markdown) - -- [编写可维护的代码](blob/master/chapter2.markdown#a2) -- [减少全局对象](blob/master/chapter2.markdown#a3) - - [全局对象带来的困扰](blob/master/chapter2.markdown#a4) - - [忘记var时的副作用](blob/master/chapter2.markdown#a5) - - [访问全局对象](blob/master/chapter2.markdown#a6) - - [单 var 模式](blob/master/chapter2.markdown#a7) - - [声明提前:分散的 var 带来的问题](blob/master/chapter2.markdown#a8) -- [for 循环](blob/master/chapter2.markdown#a9) -- [for-in 循环](blob/master/chapter2.markdown#a10) -- [(不)扩充内置原型](blob/master/chapter2.markdown#a11) -- [switch 模式](blob/master/chapter2.markdown#a12) -- [避免隐式类型转换](blob/master/chapter2.markdown#a13) - - [避免使用 eval()](blob/master/chapter2.markdown#a14) -- [使用parseInt()进行数字转换](blob/master/chapter2.markdown#a15) -- [编码风格](blob/master/chapter2.markdown#a16) - - [缩进](blob/master/chapter2.markdown#a17) - - [花括号](blob/master/chapter2.markdown#a18) - - [左花括号的放置](blob/master/chapter2.markdown#a19) - - [空格](blob/master/chapter2.markdown#a20) -- [命名规范](blob/master/chapter2.markdown#a21) - - [构造器命名中的大小写](blob/master/chapter2.markdown#a22) - - [单词分隔](blob/master/chapter2.markdown#a23) - - [其他命名风格](blob/master/chapter2.markdown#a24) -- [书写注释](blob/master/chapter2.markdown#a25) -- [书写API文档](blob/master/chapter2.markdown#a26) - - [一个例子:YUIDoc](blob/master/chapter2.markdown#a27) -- [编写易读的代码](blob/master/chapter2.markdown#a28) -- [相互评审](blob/master/chapter2.markdown#a29) -- [生产环境中的代码压缩(Minify)](blob/master/chapter2.markdown#a30) -- [运行JSLint](blob/master/chapter2.markdown#a31) -- [小结](blob/master/chapter2.markdown#a32) - -## [第三章 直接量和构造函数](blob/master/chapter3.markdown) - -- [对象直接量](blob/master/chapter3.markdown#a2) - - [对象直接量语法](blob/master/chapter3.markdown#a3) - - [通过构造函数创建对象](blob/master/chapter3.markdown#a4) - - [获得对象的构造器](blob/master/chapter3.markdown#a5) -- [自定义构造函数](blob/master/chapter3.markdown#a6) - - [构造函数的返回值](blob/master/chapter3.markdown#a7) -- [强制使用new的模式](blob/master/chapter3.markdown#a8) - - [命名约定](blob/master/chapter3.markdown#a9) - - [使用that](blob/master/chapter3.markdown#a10) - - [调用自身的构造函数](blob/master/chapter3.markdown#a11) -- [数组直接量](blob/master/chapter3.markdown#a12) - - [数组直接量语法](blob/master/chapter3.markdown#a13) - - [有意思的数组构造器](blob/master/chapter3.markdown#a14) - - [检查是不是数组](blob/master/chapter3.markdown#a15) -- [JSON](blob/master/chapter3.markdown#a16) - - [使用JSON](blob/master/chapter3.markdown#a17) -- [正则表达式直接量](blob/master/chapter3.markdown#a18) - - [正则表达式直接量语法](blob/master/chapter3.markdown#a19) -- [原始值的包装对象](blob/master/chapter3.markdown#a20) -- [Error对象](blob/master/chapter3.markdown#a21) -- [小结](blob/master/chapter3.markdown#a22) - -## [第四章 函数](blob/master/chapter4.markdown) - -- [背景知识](blob/master/chapter4.markdown#a2) - - [术语释义](blob/master/chapter4.markdown#a3) - - [声明 vs 表达式:命名与提前](blob/master/chapter4.markdown#a4) - - [函数的name属性](blob/master/chapter4.markdown#a5) - - [函数提前](blob/master/chapter4.markdown#a6) -- [回调模式](blob/master/chapter4.markdown#a7) - - [一个回调的例子](blob/master/chapter4.markdown#a8) - - [回调和作用域](blob/master/chapter4.markdown#a9) - - [异步事件监听](blob/master/chapter4.markdown#a10) - - [超时](blob/master/chapter4.markdown#a11) - - [库中的回调](blob/master/chapter4.markdown#a12) -- [返回函数](blob/master/chapter4.markdown#a12) -- [自定义函数](blob/master/chapter4.markdown#a14) -- [立即执行的函数](blob/master/chapter4.markdown#a15) - - [立即执行的函数的参数](blob/master/chapter4.markdown#a16) - - [立即执行的函数的返回值](blob/master/chapter4.markdown#a17) - - [好处和用法](blob/master/chapter4.markdown#a18) -- [立即初始化的对象](blob/master/chapter4.markdown#a19) -- [条件初始化](blob/master/chapter4.markdown#a20) -- [函数属性——Memoization模式](blob/master/chapter4.markdown#a21) -- [配置对象](blob/master/chapter4.markdown#a22) -- [柯里化 (Curry)](blob/master/chapter4.markdown#a23) - - [函数应用](blob/master/chapter4.markdown#a24) - - [部分应用](blob/master/chapter4.markdown#a25) - - [柯里化](blob/master/chapter4.markdown#a26) - - [什么时候使用柯里化](blob/master/chapter4.markdown#a27) -- [小结](blob/master/chapter4.markdown#a28) +## [第一章 概述](javascript.patterns/blob/master/chapter1.markdown) + +- [模式](javascript.patterns/blob/master/chapter1.markdown) +- [JavaScript:概念](javascript.patterns/blob/master/chapter1.markdown#a2) + - [面向对象](javascript.patterns/blob/master/chapter1.markdown#a3) + - [无类](javascript.patterns/blob/master/chapter1.markdown#a4) + - [原型](javascript.patterns/blob/master/chapter1.markdown#a5) + - [运行环境](javascript.patterns/blob/master/chapter1.markdown#a6) +- [ECMAScript 5](javascript.patterns/blob/master/chapter1.markdown#a7) +- [JSLint](javascript.patterns/blob/master/chapter1.markdown#a8) +- [控制台工具](javascript.patterns/blob/master/chapter1.markdown#a9) + +## [第二章 高质量JavaScript基本要点](javascript.patterns/blob/master/chapter2.markdown) + +- [编写可维护的代码](javascript.patterns/blob/master/chapter2.markdown#a2) +- [减少全局对象](javascript.patterns/blob/master/chapter2.markdown#a3) + - [全局对象带来的困扰](javascript.patterns/blob/master/chapter2.markdown#a4) + - [忘记var时的副作用](javascript.patterns/blob/master/chapter2.markdown#a5) + - [访问全局对象](javascript.patterns/blob/master/chapter2.markdown#a6) + - [单 var 模式](javascript.patterns/blob/master/chapter2.markdown#a7) + - [声明提前:分散的 var 带来的问题](javascript.patterns/blob/master/chapter2.markdown#a8) +- [for 循环](javascript.patterns/blob/master/chapter2.markdown#a9) +- [for-in 循环](javascript.patterns/blob/master/chapter2.markdown#a10) +- [(不)扩充内置原型](javascript.patterns/blob/master/chapter2.markdown#a11) +- [switch 模式](javascript.patterns/blob/master/chapter2.markdown#a12) +- [避免隐式类型转换](javascript.patterns/blob/master/chapter2.markdown#a13) + - [避免使用 eval()](javascript.patterns/blob/master/chapter2.markdown#a14) +- [使用parseInt()进行数字转换](javascript.patterns/blob/master/chapter2.markdown#a15) +- [编码风格](javascript.patterns/blob/master/chapter2.markdown#a16) + - [缩进](javascript.patterns/blob/master/chapter2.markdown#a17) + - [花括号](javascript.patterns/blob/master/chapter2.markdown#a18) + - [左花括号的放置](javascript.patterns/blob/master/chapter2.markdown#a19) + - [空格](javascript.patterns/blob/master/chapter2.markdown#a20) +- [命名规范](javascript.patterns/blob/master/chapter2.markdown#a21) + - [构造器命名中的大小写](javascript.patterns/blob/master/chapter2.markdown#a22) + - [单词分隔](javascript.patterns/blob/master/chapter2.markdown#a23) + - [其他命名风格](javascript.patterns/blob/master/chapter2.markdown#a24) +- [书写注释](javascript.patterns/blob/master/chapter2.markdown#a25) +- [书写API文档](javascript.patterns/blob/master/chapter2.markdown#a26) + - [一个例子:YUIDoc](javascript.patterns/blob/master/chapter2.markdown#a27) +- [编写易读的代码](javascript.patterns/blob/master/chapter2.markdown#a28) +- [相互评审](javascript.patterns/blob/master/chapter2.markdown#a29) +- [生产环境中的代码压缩(Minify)](javascript.patterns/blob/master/chapter2.markdown#a30) +- [运行JSLint](javascript.patterns/blob/master/chapter2.markdown#a31) +- [小结](javascript.patterns/blob/master/chapter2.markdown#a32) + +## [第三章 直接量和构造函数](javascript.patterns/blob/master/chapter3.markdown) + +- [对象直接量](javascript.patterns/blob/master/chapter3.markdown#a2) + - [对象直接量语法](javascript.patterns/blob/master/chapter3.markdown#a3) + - [通过构造函数创建对象](javascript.patterns/blob/master/chapter3.markdown#a4) + - [获得对象的构造器](javascript.patterns/blob/master/chapter3.markdown#a5) +- [自定义构造函数](javascript.patterns/blob/master/chapter3.markdown#a6) + - [构造函数的返回值](javascript.patterns/blob/master/chapter3.markdown#a7) +- [强制使用new的模式](javascript.patterns/blob/master/chapter3.markdown#a8) + - [命名约定](javascript.patterns/blob/master/chapter3.markdown#a9) + - [使用that](javascript.patterns/blob/master/chapter3.markdown#a10) + - [调用自身的构造函数](javascript.patterns/blob/master/chapter3.markdown#a11) +- [数组直接量](javascript.patterns/blob/master/chapter3.markdown#a12) + - [数组直接量语法](javascript.patterns/blob/master/chapter3.markdown#a13) + - [有意思的数组构造器](javascript.patterns/blob/master/chapter3.markdown#a14) + - [检查是不是数组](javascript.patterns/blob/master/chapter3.markdown#a15) +- [JSON](javascript.patterns/blob/master/chapter3.markdown#a16) + - [使用JSON](javascript.patterns/blob/master/chapter3.markdown#a17) +- [正则表达式直接量](javascript.patterns/blob/master/chapter3.markdown#a18) + - [正则表达式直接量语法](javascript.patterns/blob/master/chapter3.markdown#a19) +- [原始值的包装对象](javascript.patterns/blob/master/chapter3.markdown#a20) +- [Error对象](javascript.patterns/blob/master/chapter3.markdown#a21) +- [小结](javascript.patterns/blob/master/chapter3.markdown#a22) + +## [第四章 函数](javascript.patterns/blob/master/chapter4.markdown) + +- [背景知识](javascript.patterns/blob/master/chapter4.markdown#a2) + - [术语释义](javascript.patterns/blob/master/chapter4.markdown#a3) + - [声明 vs 表达式:命名与提前](javascript.patterns/blob/master/chapter4.markdown#a4) + - [函数的name属性](javascript.patterns/blob/master/chapter4.markdown#a5) + - [函数提前](javascript.patterns/blob/master/chapter4.markdown#a6) +- [回调模式](javascript.patterns/blob/master/chapter4.markdown#a7) + - [一个回调的例子](javascript.patterns/blob/master/chapter4.markdown#a8) + - [回调和作用域](javascript.patterns/blob/master/chapter4.markdown#a9) + - [异步事件监听](javascript.patterns/blob/master/chapter4.markdown#a10) + - [超时](javascript.patterns/blob/master/chapter4.markdown#a11) + - [库中的回调](javascript.patterns/blob/master/chapter4.markdown#a12) +- [返回函数](javascript.patterns/blob/master/chapter4.markdown#a12) +- [自定义函数](javascript.patterns/blob/master/chapter4.markdown#a14) +- [立即执行的函数](javascript.patterns/blob/master/chapter4.markdown#a15) + - [立即执行的函数的参数](javascript.patterns/blob/master/chapter4.markdown#a16) + - [立即执行的函数的返回值](javascript.patterns/blob/master/chapter4.markdown#a17) + - [好处和用法](javascript.patterns/blob/master/chapter4.markdown#a18) +- [立即初始化的对象](javascript.patterns/blob/master/chapter4.markdown#a19) +- [条件初始化](javascript.patterns/blob/master/chapter4.markdown#a20) +- [函数属性——Memoization模式](javascript.patterns/blob/master/chapter4.markdown#a21) +- [配置对象](javascript.patterns/blob/master/chapter4.markdown#a22) +- [柯里化 (Curry)](javascript.patterns/blob/master/chapter4.markdown#a23) + - [函数应用](javascript.patterns/blob/master/chapter4.markdown#a24) + - [部分应用](javascript.patterns/blob/master/chapter4.markdown#a25) + - [柯里化](javascript.patterns/blob/master/chapter4.markdown#a26) + - [什么时候使用柯里化](javascript.patterns/blob/master/chapter4.markdown#a27) +- [小结](javascript.patterns/blob/master/chapter4.markdown#a28) ## 第五章 对象创建模式 From 530467a271ea40af1d11a0f2c2212d88f96acb03 Mon Sep 17 00:00:00 2001 From: TooBug Date: Wed, 20 Jun 2012 19:07:47 +0800 Subject: [PATCH 020/258] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=B0=8F=E7=BB=93?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E5=88=97=E8=A1=A8=E8=A1=A8=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index 8bb2ee5..9984640 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -1023,33 +1023,33 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 1. API模式,它们帮助我们为函数给出更干净的接口,包括: -- 回调模式 -传入一个函数作为参数 + - 回调模式 + - 传入一个函数作为参数 -- 配置对象 -帮助保持函数的参数数量可控 + - 配置对象 + - 帮助保持函数的参数数量可控 -- 返回函数 -函数的返回值是另一个函数 + - 返回函数 + - 函数的返回值是另一个函数 -- 柯里化 -新函数在已有函数的基础上再加上一部分参数构成 + - 柯里化 + - 新函数在已有函数的基础上再加上一部分参数构成 2. 初始化模式,这些模式帮助我们用一种干净的、结构化的方法来做一些初始化工作(在web页面和应用中非常常见),通过一些临时变量来保证不污染全局命名空间。这些模式包括: -- 立即执行的函数 -当它们被定义后立即执行 + - 立即执行的函数 + - 当它们被定义后立即执行 -- 立即初始化的对象 -初始化工作被放入一个匿名对象,这个对象提供一个可以立即被执行的方法 + - 立即初始化的对象 + - 初始化工作被放入一个匿名对象,这个对象提供一个可以立即被执行的方法 -- 条件初始化 -使分支代码只在初始化的时候执行一次,而不是在整个程序生命周期中反复执行 + - 条件初始化 + - 使分支代码只在初始化的时候执行一次,而不是在整个程序生命周期中反复执行 3. 性能模式,这些模式帮助提高代码的执行速度,包括: -- Memoization -利用函数的属性,使已经计算过的值不用再次计算 + - Memoization + - 利用函数的属性,使已经计算过的值不用再次计算 -- 自定义函数 -重写自身的函数体,使第二次及后续的调用做更少的工作 \ No newline at end of file + - 自定义函数 + - 重写自身的函数体,使第二次及后续的调用做更少的工作 \ No newline at end of file From b3d83bba2aa9240c66e188266ee6568021b00f31 Mon Sep 17 00:00:00 2001 From: TooBug Date: Wed, 20 Jun 2012 19:07:47 +0800 Subject: [PATCH 021/258] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=B0=8F=E7=BB=93?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E5=88=97=E8=A1=A8=E8=A1=A8=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index 8bb2ee5..21457d9 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -1023,33 +1023,33 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 1. API模式,它们帮助我们为函数给出更干净的接口,包括: -- 回调模式 -传入一个函数作为参数 + - 回调模式 + - 传入一个函数作为参数 -- 配置对象 -帮助保持函数的参数数量可控 + - 配置对象 + - 帮助保持函数的参数数量可控 -- 返回函数 -函数的返回值是另一个函数 + - 返回函数 + - 函数的返回值是另一个函数 -- 柯里化 -新函数在已有函数的基础上再加上一部分参数构成 + - 柯里化 + - 新函数在已有函数的基础上再加上一部分参数构成 2. 初始化模式,这些模式帮助我们用一种干净的、结构化的方法来做一些初始化工作(在web页面和应用中非常常见),通过一些临时变量来保证不污染全局命名空间。这些模式包括: -- 立即执行的函数 -当它们被定义后立即执行 + - 立即执行的函数 + - 当它们被定义后立即执行 -- 立即初始化的对象 -初始化工作被放入一个匿名对象,这个对象提供一个可以立即被执行的方法 + - 立即初始化的对象 + - 初始化工作被放入一个匿名对象,这个对象提供一个可以立即被执行的方法 -- 条件初始化 -使分支代码只在初始化的时候执行一次,而不是在整个程序生命周期中反复执行 + - 条件初始化 + - 使分支代码只在初始化的时候执行一次,而不是在整个程序生命周期中反复执行 3. 性能模式,这些模式帮助提高代码的执行速度,包括: -- Memoization -利用函数的属性,使已经计算过的值不用再次计算 + - Memoization + - 利用函数的属性,使已经计算过的值不用再次计算 -- 自定义函数 -重写自身的函数体,使第二次及后续的调用做更少的工作 \ No newline at end of file + - 自定义函数 + - 重写自身的函数体,使第二次及后续的调用做更少的工作 \ No newline at end of file From 5395354cca44f93fbd3595d3382ecfb2989c6a62 Mon Sep 17 00:00:00 2001 From: TooBug Date: Wed, 20 Jun 2012 19:18:52 +0800 Subject: [PATCH 022/258] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=B0=8F=E7=BB=93?= =?UTF-8?q?=E4=B8=AD=E5=88=97=E8=A1=A8=E7=9A=84=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index 21457d9..ffde6b6 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -1023,33 +1023,33 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 1. API模式,它们帮助我们为函数给出更干净的接口,包括: - - 回调模式 - - 传入一个函数作为参数 + - 回调模式 + - 传入一个函数作为参数 - - 配置对象 - - 帮助保持函数的参数数量可控 + - 配置对象 + - 帮助保持函数的参数数量可控 - - 返回函数 - - 函数的返回值是另一个函数 + - 返回函数 + - 函数的返回值是另一个函数 - - 柯里化 - - 新函数在已有函数的基础上再加上一部分参数构成 + - 柯里化 + - 新函数在已有函数的基础上再加上一部分参数构成 2. 初始化模式,这些模式帮助我们用一种干净的、结构化的方法来做一些初始化工作(在web页面和应用中非常常见),通过一些临时变量来保证不污染全局命名空间。这些模式包括: - - 立即执行的函数 - - 当它们被定义后立即执行 + - 立即执行的函数 + - 当它们被定义后立即执行 - - 立即初始化的对象 - - 初始化工作被放入一个匿名对象,这个对象提供一个可以立即被执行的方法 + - 立即初始化的对象 + - 初始化工作被放入一个匿名对象,这个对象提供一个可以立即被执行的方法 - - 条件初始化 - - 使分支代码只在初始化的时候执行一次,而不是在整个程序生命周期中反复执行 + - 条件初始化 + - 使分支代码只在初始化的时候执行一次,而不是在整个程序生命周期中反复执行 3. 性能模式,这些模式帮助提高代码的执行速度,包括: - - Memoization - - 利用函数的属性,使已经计算过的值不用再次计算 + - Memoization + - 利用函数的属性,使已经计算过的值不用再次计算 - - 自定义函数 - - 重写自身的函数体,使第二次及后续的调用做更少的工作 \ No newline at end of file + - 自定义函数 + - 重写自身的函数体,使第二次及后续的调用做更少的工作 \ No newline at end of file From c78593d58b8f5b5982894a8a6ea14828091ce087 Mon Sep 17 00:00:00 2001 From: TooBug Date: Wed, 20 Jun 2012 19:18:52 +0800 Subject: [PATCH 023/258] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=B0=8F=E7=BB=93?= =?UTF-8?q?=E4=B8=AD=E5=88=97=E8=A1=A8=E7=9A=84=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 47 ++++++++++++++++++----------------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index 21457d9..bf48a2f 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -1022,34 +1022,23 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 在介绍完背景和函数的语法后,介绍了一些有用的模式,按分类列出: 1. API模式,它们帮助我们为函数给出更干净的接口,包括: - - - 回调模式 - - 传入一个函数作为参数 - - - 配置对象 - - 帮助保持函数的参数数量可控 - - - 返回函数 - - 函数的返回值是另一个函数 - - - 柯里化 - - 新函数在已有函数的基础上再加上一部分参数构成 - + - 回调模式 + - 传入一个函数作为参数 + - 配置对象 + - 帮助保持函数的参数数量可控 + - 返回函数 + - 函数的返回值是另一个函数 + - 柯里化 + - 新函数在已有函数的基础上再加上一部分参数构成 2. 初始化模式,这些模式帮助我们用一种干净的、结构化的方法来做一些初始化工作(在web页面和应用中非常常见),通过一些临时变量来保证不污染全局命名空间。这些模式包括: - - - 立即执行的函数 - - 当它们被定义后立即执行 - - - 立即初始化的对象 - - 初始化工作被放入一个匿名对象,这个对象提供一个可以立即被执行的方法 - - - 条件初始化 - - 使分支代码只在初始化的时候执行一次,而不是在整个程序生命周期中反复执行 - + - 立即执行的函数 + - 当它们被定义后立即执行 + - 立即初始化的对象 + - 初始化工作被放入一个匿名对象,这个对象提供一个可以立即被执行的方法 + - 条件初始化 + - 使分支代码只在初始化的时候执行一次,而不是在整个程序生命周期中反复执行 3. 性能模式,这些模式帮助提高代码的执行速度,包括: - - - Memoization - - 利用函数的属性,使已经计算过的值不用再次计算 - - - 自定义函数 - - 重写自身的函数体,使第二次及后续的调用做更少的工作 \ No newline at end of file + - Memoization + - 利用函数的属性,使已经计算过的值不用再次计算 + - 自定义函数 + - 重写自身的函数体,使第二次及后续的调用做更少的工作 \ No newline at end of file From b49c4b5199fc866b272b26008a350132f234e80b Mon Sep 17 00:00:00 2001 From: TooBug Date: Wed, 20 Jun 2012 19:24:42 +0800 Subject: [PATCH 024/258] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=B0=8F=E7=BB=93?= =?UTF-8?q?=E4=B8=AD=E5=88=97=E8=A1=A8=E7=9A=84=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 82 ++++++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index bf48a2f..17f4929 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -602,37 +602,37 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 下面是一个立即初始化的对象模式的例子: - ({ - // here you can define setting values - // a.k.a. configuration constants - maxwidth: 600, - maxheight: 400, - - // you can also define utility methods - gimmeMax: function () { - return this.maxwidth + "x" + this.maxheight; - }, - - // initialize - init: function () { - console.log(this.gimmeMax()); - // more init tasks... - } - }).init(); - + ({ + // here you can define setting values + // a.k.a. configuration constants + maxwidth: 600, + maxheight: 400, + + // you can also define utility methods + gimmeMax: function () { + return this.maxwidth + "x" + this.maxheight; + }, + + // initialize + init: function () { + console.log(this.gimmeMax()); + // more init tasks... + } + }).init(); + 在语法上,当你使用这种模式的时候就像在使用对象字面量创建一个普通对象一样。除此之外,还需要将对象字面量用括号括起来,这样能让JavaScript引擎知道这是一个对象字面量,而不是一个代码块(if或者for循环之类)。在括号后面,紧接着就执行了init()方法。 你也可以将对象字面量和init()调用一起写到括号里面。简单地说,下面两种语法都是有效的: - ({...}).init(); - ({...}.init()); + ({...}).init(); + ({...}.init()); 这种模式的好处和自动执行的函数模式是一样的:在做一些一次性的初始化工作的时候保护全局作用域不被污染。从语法上看,这种模式似乎比只包含一段代码在一个匿名函数中要复杂一些,但是如果你的初始化工作比较复杂(这种情况很常见),它会给整个初始化工作一个比较清晰的结构。比如,一些私有的辅助性函数可以被很轻易地看出来,因为它们是这个临时对象的属性,但是如果是在立即执行的函数模式中,它们很可能只是一些散落的函数。 这种模式的一个弊端是,JavaScript压缩工具可能不能像压缩一段包裹在函数中的代码一样有效地压缩这种模式的代码。这些私有的属性和方法不被会重命名为一些更短的名字,因为从压缩工具的角度来看,保证压缩的可靠性更重要。在写作本书的时候,Google出品的Closure Compiler的“advanced”模式是唯一会重命名立即初始化的对象的属性的压缩工具。一个压缩后的样例是这样: - ({d:600,c:400,a:function(){return this.d+"x"+this.c},b:function(){console.log(this.a())}}).b(); - + ({d:600,c:400,a:function(){return this.d+"x"+this.c},b:function(){console.log(this.a())}}).b(); + > 这种模式主要用于一些一次性的工作,并且在init()方法执行完后就无法再次访问到这个对象。如果希望在这些工作完成后保持对对象的引用,只需要简单地在init()的末尾加上return this;即可。 @@ -1022,23 +1022,25 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 在介绍完背景和函数的语法后,介绍了一些有用的模式,按分类列出: 1. API模式,它们帮助我们为函数给出更干净的接口,包括: - - 回调模式 - - 传入一个函数作为参数 - - 配置对象 - - 帮助保持函数的参数数量可控 - - 返回函数 - - 函数的返回值是另一个函数 - - 柯里化 - - 新函数在已有函数的基础上再加上一部分参数构成 + - 回调模式 + + 传入一个函数作为参数 + + - 配置对象 + - 帮助保持函数的参数数量可控 + - 返回函数 + - 函数的返回值是另一个函数 + - 柯里化 + - 新函数在已有函数的基础上再加上一部分参数构成 2. 初始化模式,这些模式帮助我们用一种干净的、结构化的方法来做一些初始化工作(在web页面和应用中非常常见),通过一些临时变量来保证不污染全局命名空间。这些模式包括: - - 立即执行的函数 - - 当它们被定义后立即执行 - - 立即初始化的对象 - - 初始化工作被放入一个匿名对象,这个对象提供一个可以立即被执行的方法 - - 条件初始化 - - 使分支代码只在初始化的时候执行一次,而不是在整个程序生命周期中反复执行 + - 立即执行的函数 + - 当它们被定义后立即执行 + - 立即初始化的对象 + - 初始化工作被放入一个匿名对象,这个对象提供一个可以立即被执行的方法 + - 条件初始化 + - 使分支代码只在初始化的时候执行一次,而不是在整个程序生命周期中反复执行 3. 性能模式,这些模式帮助提高代码的执行速度,包括: - - Memoization - - 利用函数的属性,使已经计算过的值不用再次计算 - - 自定义函数 - - 重写自身的函数体,使第二次及后续的调用做更少的工作 \ No newline at end of file + - Memoization + - 利用函数的属性,使已经计算过的值不用再次计算 + - 自定义函数 + - 重写自身的函数体,使第二次及后续的调用做更少的工作 \ No newline at end of file From 69b477888a5a134d0116bd7ebbaf75f4802a8484 Mon Sep 17 00:00:00 2001 From: TooBug Date: Wed, 20 Jun 2012 19:26:03 +0800 Subject: [PATCH 025/258] Update master --- chapter4.markdown | 1 - 1 file changed, 1 deletion(-) diff --git a/chapter4.markdown b/chapter4.markdown index 17f4929..c09a335 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -1025,7 +1025,6 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 - 回调模式 传入一个函数作为参数 - - 配置对象 - 帮助保持函数的参数数量可控 - 返回函数 From d6403eaa23bee11f58ea3be621991fae6f7a11a7 Mon Sep 17 00:00:00 2001 From: TooBug Date: Wed, 20 Jun 2012 19:28:58 +0800 Subject: [PATCH 026/258] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=B0=8F=E7=BB=93?= =?UTF-8?q?=E9=83=A8=E5=88=86=E5=88=97=E8=A1=A8=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4-MarkdownPadPreview.html | 1376 ------------------------------ chapter4.markdown | 24 +- 2 files changed, 16 insertions(+), 1384 deletions(-) delete mode 100644 chapter4-MarkdownPadPreview.html diff --git a/chapter4-MarkdownPadPreview.html b/chapter4-MarkdownPadPreview.html deleted file mode 100644 index fc00f09..0000000 --- a/chapter4-MarkdownPadPreview.html +++ /dev/null @@ -1,1376 +0,0 @@ - - - -chapter4.markdown - - - - -

函数

- -

熟练运用函数是JavaScript程序员的必备技能,因为在JavaScript中函数实在是太常用了。它能够完成的任务种类非常之多,而在其他语言中则需要很多特殊的语法支持才能达到这种能力。

- -

在本章将会介绍在JavaScript中定义函数的多种方式,包括函数表达式和函数声明、以及局部作用域和变量声明提前的工作原理。然后会介绍一些有用的模式,帮助你设计API(为你的函数提供更好的接口)、搭建代码架构(使用尽可能少的全局对象)、并优化性能(避免不必要的操作)。

- -

现在让我们来一起揭秘JavaScript函数,我们首先从一些背景知识开始说起。

- -

背景知识

- -

JavaScript的函数具有两个主要特性,正是这两个特性让它们与众不同。第一个特性是,函数是一等对象(first-class object),第二个是函数提供作用域支持。

- -

函数是对象,那么:

- -
    -
  • 可以在程序执行时动态创建函数
  • -
  • 可以将函数赋值给变量,可以将函数的引用拷贝至另一个变量,可以扩充函数,除了某些特殊场景外均可被删除。
  • -
  • 可以将函数作为参数传入另一个函数,也可以被当作返回值返回。
  • -
  • 函数可以包含自己的属性和方法
  • -
- -

对于一个函数A来说,首先它是对象,拥有属性和方法,其中某个属性碰巧是另一个函数B,B可以接受函数作为参数,假设这个函数参数为C,当执行B的时候,返回另一个函数D。乍一看这里有一大堆相互关联的函数。当你开始习惯函数的许多用法时,你会惊叹原来函数是如此强大、灵活并富有表现力。通常说来,一说到JavaScript的函数,我们首先认为它是对象,它具有一个可以“执行”的特性,也就是说我们可以“调用”这个函数。

- -

我们通过new Function()构造器来生成一个函数,这时可以明显看出函数是对象:

- -
// antipattern
-// for demo purposes only
-var add = new Function('a, b', 'return a + b');
-add(1, 2); // returns 3
-
- -

在这段代码中,毫无疑问add()是一个对象,毕竟它是由构造函数创建的。这里并不推荐使用Function()构造器创建函数(和eval()一样糟糕),因为程序逻辑代码是以字符串的形式传入构造器的。这样的代码可读性差,写起来也很费劲,你不得不对逻辑代码中的引号做转义处理,并需要特别关注为了让代码保持一定的可读性而保留的空格和缩进。

- -

函数的第二个重要特性是它能提供作用域支持。在JavaScript中没有块级作用域(译注:在JavaScript1.7中提供了块级作用域部分特性的支持,可以通过let来声明块级作用域内的“局部变量”),也就是说不能通过花括号来创建作用域,JavaScript中只有函数作用域(译注:这里作者的表述只针对函数而言,此外JavaScript还有全局作用域)。在函数内所有通过var声明的变量都是局部变量,在函数外部是不可见的。刚才所指花括号无法提供作用域支持的意思是说,如果在if条件句内、或在for或while循环体内用var定义了变量,这个变量并不是属于if语句或for(while)循环的局部变量,而是属于它所在的函数。如果不在任何函数内部,它会成为全局变量。在第二章里提到我们要减少对全局命名空间的污染,那么使用函数则是控制变量的作用域的不二之选。

- -

术语释义

- -

首先我们先简单讨论下创建函数相关的术语,因为精确无歧义的术语约定和我们所讨论的各种模式一样重要。

- -

看下这个代码片段:

- -
// named function expression
-var add = function add(a, b) {
-    return a + b;
-};
-
- -

这段代码描述了一个函数,这种描述称为“带有命名的函数表达式”。

- -

如果函数表达式将名字省略掉(比如下面的示例代码),这时它是“无名字的函数表达式”,通常我们称之为“匿名函数”,比如:

- -
// function expression, a.k.a. anonymous function
-var add = function (a, b) {
-    return a + b;
-};
-
- -

因此“函数表达式”是一个更广义的概念,“带有命名的函数表达式”是函数表达式的一种特殊形式,仅仅当需要给函数定义一个可选的名字时使用。

- -

当省略第二个add,它就成了无名字的函数表达式,这不会对函数定义和调用语法造成任何影响。带名字和不带名字唯一的区别是函数对象的name属性是否是一个空字符串。name属性属于语言的扩展(未在ECMA标准中定义),但很多环境都实现了。如果不省略第二个add,那么属性add.name则是"add",name属性在用Firebug的调试过程中非常有用,还能让函数递归调用自身,其他情况可以省略它。

- -

最后来看一下“函数声明”,函数声明的语法和其他语言中的语法非常类似:

- -
function foo() {
-    // function body goes here
-}
-
- -

从语法角度讲,带有命名的函数表达式和函数声明非常像,特别是当不需要将函数表达式赋值给一个变量的时候(在本章后面所讲到的回调模式中有类似的例子)。多数情况下,函数声明和带命名的函数表达式在外观上没有多少不同,只是它们在函数执行时对上下文的影响有所区别,下一小节会讲到。

- -

两种语法的一个区别是末尾的分号。函数声明末尾不需要分号,而函数表达式末尾是需要分号的。推荐你始终不要丢掉函数表达式末尾的分号,即便JavaScript可以进行分号补全,也不要冒险这样做。

- -
-

另外我们经常看到“函数直接量”。它用来表示函数表达式或带命名的函数表达式。由于这个术语是有歧义的,所以最好不要用它。

-
- -

声明 vs 表达式:命名与提前

- -

那么,到底应该用哪个呢?函数声明还是函数表达式?在不能使用函数声明语法的场景下,只能使用函数表达式了。下面这个例子中,我们给函数传入了另一个函数对象作为参数,以及给对象定义方法:

- -
// this is a function expression,
-// pased as an argument to the function `callMe`
-callMe(function () {
-    // I am an unnamed function expression
-    // also known as an anonymous function
-});
-
-// this is a named function expression
-callMe(function me() {
-    // I am a named function expression
-    // and my name is "me"
-});
-
-// another function expression
-var myobject = {
-    say: function () {
-        // I am a function expression
-    }
-};
-
- -

函数声明只能出现在“程序代码”中,也就是说在别的函数体内或在全局。这个定义不能赋值给变量或属性,同样不能作为函数调用的参数。下面这个例子是函数声明的合法用法,这里所有的函数foo(),bar()和local()都使用函数声明来定义:

- -
// global scope
-function foo() {}
-
-function local() {
-    // local scope
-    function bar() {}
-    return bar;
-}
-
- -

函数的name属性

- -

选择函数定义模式的另一个考虑是只读属性name的可用性。尽管标准规范中并未规定,但很多运行环境都实现了name属性,在函数声明和带有名字的函数表达式中是有name的属性定义的。在匿名函数表达式中,则不一定有定义,这个是和实现相关的,在IE中是无定义的,在Firefox和Safari中是有定义的,但是值为空字符串。

- -
function foo() {} // declaration
-var bar = function () {}; // expression
-var baz = function baz() {}; // named expression
-
-foo.name; // "foo"
-bar.name; // ""
-baz.name; // "baz"
-
- -

在Firebug或其他工具中调试程序时name属性非常有用,它可以用来显示当前正在执行的函数。同样可以通过name属性来递归的调用函数自身。如果你对这些场景不感兴趣,那么请尽可能的使用匿名函数表达式,这样会更简单、且冗余代码更少。

- -

和函数声明相比而言,函数表达式的语法更能说明函数是一种对象,而不是某种特别的语言写法。

- -
-

我们可以将一个带名字的函数表达式赋值给变量,变量名和函数名不同,这在技术上是可行的。比如:var foo = function bar(){};。然而,这种用法的行为在浏览器中的兼容性不佳(特别是IE中),因此并不推荐大家使用这种模式。

-
- -

函数提前

- -

通过前面的讲解,你可能以为函数声明和带名字的函数表达式是完全等价的。事实上不是这样,主要区别在于“声明提前”的行为。

- -
-

术语“提前”并未在ECMAScript中定义,但是并没有其他更好的方法来描述这种行为了。

-
- -

我们知道,不管在函数内何处声明变量,变量都会自动提前至函数体的顶部。对于函数来说亦是如此,因为他们也是一种对象,赋值给了变量。需要注意的是,函数声明定义的函数不仅能让声明提前,还能让定义提前,看一下这段示例代码:

- -
// antipattern
-// for illustration only
-
-// global functions
-function foo() {
-    alert('global foo');
-}
-function bar() {
-    alert('global bar');
-}
-
-function hoistMe() {
-
-    console.log(typeof foo); // "function"
-    console.log(typeof bar); // "undefined"
-
-    foo(); // "local foo"
-    bar(); // TypeError: bar is not a function
-
-    // function declaration:
-    // variable 'foo' and its implementation both get hoisted
-
-    function foo() {
-        alert('local foo');
-    }
-
-    // function expression:
-    // only variable 'bar' gets hoisted
-    // not the implementation
-    var bar = function () {
-        alert('local bar');
-    };
-}
-hoistMe();
-
- -

在这段代码中,和普通的变量一样,hoistMe()函数中的foo和bar被“搬运”到了顶部,覆盖了全局的foo和bar。不同之处在于,局部的foo()定义提前至顶部并能正常工作,尽管定义它的位置并不靠前。bar()的定义并未提前,只是声明提前了。因此当程序执行到bar()定义的位置之前,它的值都是undefined,并不是函数(防止当前上下文查找到作用域链上的全局的bar(),也就“覆盖”了全局的bar())。

- -

到目前为止我们介绍了必要的背景知识和函数定义相关的术语,下面开始介绍一些JavaScript所提供的函数相关的好的模式,我们从回调模式开始。同样,再次强调JavaScript函数的两个特殊特性,掌握这两点至关重要:

- -
    -
  • 函数是对象
  • -
  • 函数提供局部变量作用域
  • -
- -

回调模式

- -

函数是对象,也就意味着函数可以当作参数传入另外一个函数中。当你给函数writeCode()传入一个函数参数introduceBugs(),在某个时刻writeCode()执行了(或调用了)introduceBugs()。在这种情况下,我们说introduceBugs()是一个“回调函数”,简称“回调”:

- -
function writeCode(callback) {
-    // do something...
-    callback();
-    // ...
-}
-
-function introduceBugs() {
-    // ... make bugs
-}
-
-writeCode(introduceBugs);
-
- -

注意introduceBugs()是如何作为参数传入writeCode()的,当作参数的函数不带括号。括号的意思是执行函数,而这里我们希望传入一个引用,让writeCode()在合适的时机执行它(调用它)。

- -

一个回调的例子

- -

我们从一个例子开始,首先介绍无回调的情况,然后在作修改。假设你有一个通用的函数,用来完成某种复杂的逻辑并返回一大段数据。假设我们用findNodes()来命名这个通用函数,这个函数用来对DOM树进行遍历,并返回我所感兴趣的页面节点:

- -
var findNodes = function () {
-    var i = 100000, // big, heavy loop
-        nodes = [], // stores the result
-        found; // the next node found
-    while (i) {
-        i -= 1;
-        // complex logic here...
-        nodes.push(found);
-    }
-    return nodes;
-};
-
- -

保持这个函数的功能的通用性并一贯返回DOM节点组成的数组,并不会发生对节点的实际操作,这是一个不错的注意。可以将操作节点的逻辑放入另外一个函数中,比如放入一个hide()函数中,这个函数用来隐藏页面中的节点元素:

- -
var hide = function (nodes) {
-    var i = 0, max = nodes.length;
-    for (; i < max; i += 1) {
-        nodes[i].style.display = "none";
-    }
-};
-
-// executing the functions
-hide(findNodes());
-
- -

这个实现的效率并不高,因为它将findNodes()所返回的节点数组重新遍历了一遍。最好在findNodes()中选择元素的时候就直接应用hide()操作,这样就能避免第二次的遍历,从而提高效率。但如果将hide()的逻辑写死在findNodes()的函数体内,findNodes()就变得不再通用了(译注:如果我将hide()的逻辑替换成其他逻辑怎么办呢?),因为修改逻辑和遍历逻辑耦合在一起了。如果使用回调模式,则可以将隐藏节点的逻辑写入回调函数,将其传入findNodes()中适时执行:

- -
// refactored findNodes() to accept a callback
-var findNodes = function (callback) {
-    var i = 100000,
-        nodes = [],
-        found;
-
-    // check if callback is callable
-    if (typeof callback !== "function") {
-        callback = false;
-    }
-    while (i) {
-        i -= 1;
-
-        // complex logic here...
-
-        // now callback:
-        if (callback) {
-            callback(found);
-        }
-
-        nodes.push(found);
-    }
-    return nodes;
-};
-
- -

这里的实现比较直接,findNodes()多作了一个额外工作,就是检查回调函数是否存在,如果存在的话就执行它。回调函数是可选的,因此修改后的findNodes()也是和之前一样使用,是可以兼容旧代码和旧API的。

- -

这时hide()的实现就非常简单了,因为它不用对元素列表做任何遍历了:

- -
// a callback function
-var hide = function (node) {
-    node.style.display = "none";
-};
-
-// find the nodes and hide them as you go
-findNodes(hide);
-
- -

正如代码中所示,回调函数可以是事先定义好的,也可以是一个匿名函数,你也可以将其称作main函数,比如这段代码,我们利用同样的通用函数findNodes()来完成显示元素的操作:

- -
// passing an anonymous callback
-findNodes(function (node) {
-    node.style.display = "block";
-});
-
- -

回调和作用域

- -

在上一个例子中,执行回调函数的写法是:

- -
callback(parameters);
-
- -

尽管这种写法可以适用大多数的情况,而且足够简单,但还有一些场景,回调函数不是匿名函数或者全局函数,而是对象的方法。如果回调函数中使用this指向它所属的对象,则回调逻辑往往并不像我们希望的那样执行。

- -

假设回调函数是paint(),它是myapp的一个方法:

- -
var myapp = {};
-myapp.color = "green";
-myapp.paint = function (node) {
-    node.style.color = this.color;
-};
-
- -

函数findNodes()大致如下:

- -
var findNodes = function (callback) {
-    // ...
-    if (typeof callback === "function") {
-        callback(found);
-    }
-    // ...
-};
-
- -

当你调用findNodes(myapp.paint),运行结果和我们期望的不一致,因为this.color未定义。因为findNodes()是全局函数,this指向的是全局对象。如果findNodes()是dom对象的方法(类似dom.findNodes()),那么回调函数内的this则指向dom,而不是myapp。

- -

解决办法是,除了传入回调函数,还需将回调函数所属的对象当作参数传进去:

- -
findNodes(myapp.paint, myapp);
-
- -

同样需要修改findNodes()的逻辑,增加对传入的对象的绑定:

- -
var findNodes = function (callback, callback_obj) {
-    //...
-    if (typeof callback === "function") {
-        callback.call(callback_obj, found);
-    }
-    // ...
-};
-
- -

在后续的章节会对call()和apply()有更详细的讲述。

- -

其实还有一种替代写法,就是将函数当作字符串传入findNodes(),这样就不必再写一次对象了,换句话说:

- -
findNodes(myapp.paint, myapp);
-
- -

可以写成:

- -
findNodes("paint", myapp);
-
- -

在findNodes()中的逻辑则需要修改为:

- -
var findNodes = function (callback, callback_obj) {
-
-    if (typeof callback === "string") {
-        callback = callback_obj[callback];
-    }
-
-    //...
-    if (typeof callback === "function") {
-        callback.call(callback_obj, found);
-    }
-    // ...
-};
-
- -

异步事件监听

- -

JavaScript中的回调模式已经是我们的家常便饭了,比如,如果你给网页中的元素绑定事件,则需要提供回调函数的引用,以便事件发生时能调用到它。这里有一个简单的例子,我们将console.log()作为回调函数绑定了document的点击事件:

- -
document.addEventListener("click", console.log, false);
-
- -

客户端浏览器中的大多数编程都是事件驱动的,当网页下载完成,则触发load事件,当用户和页面产生交互时也会触发多种事件,比如click、keypress、mouseover、mousemove等等。正是由于回调模式的灵活性,JavaScript天生适于事件驱动编程。回调模式能够让程序“异步”执行,换句话说,就是让程序不按顺序执行。

- -

“不要打电话给我,我会打给你”,这是好莱坞很有名的一句话,很多电影都有这句台词。电影中的主角不可能同时应答很多个电话呼叫。在JavaScript的异步事件模型中也是同样的道理。电影中是留下电话号码,JavaScript中是提供一个回调函数,当时机成熟时就触发回调。有时甚至提供了很多回调,有些回调压根是没用的,但由于这个事件可能永远不会发生,因此这些回调的逻辑也不会执行。比如,假设你从此不再用“鼠标点击”,那么你之前绑定的鼠标点击的回调函数则永远也不会执行。

- -

超时

- -

另外一个最常用的回调模式是在调用超时函数时,超时函数是浏览器window对象的方法,共有两个:setTimeout()和setInterval()。这两个方法的参数都是回调函数。

- -
var thePlotThickens = function () {
-    console.log('500ms later...');
-};
-setTimeout(thePlotThickens, 500);
-
- -

再次需要注意,函数thePlotThickens是作为变量传入setTimeout的,它不带括号,如果带括号的话则立即执行了,这里只是用到这个函数的引用,以便在setTimeout的逻辑中调用到它。也可以传入字符串“thePlotThickens()”,但这是一种反模式,和eval()一样不推荐使用。

- -

库中的回调

- -

回调模式非常简单,但又很强大。可以随手拈来灵活运用,因此这种模式在库的设计中也非常得宠。库的代码要尽可能的保持通用和重用,而回调模式则可帮助库的作者完成这个目标。你不必预料和实现你所想到的所有情形,因为这会让库变的膨胀而臃肿,而且大多数用户并不需要这些多余的特性支持。相反,你将精力放在核心功能的实现上,提供回调的入口作为“钩子”,可以让库的方法变得可扩展、可定制。

- -

返回函数

- -

函数是对象,因此当然可以作为返回值。也就是说,函数不一定非要返回一坨数据,函数可以返回另外一个定制好的函数,或者可以根据输入的不同按需创造另外一个函数。

- -

这里有一个简单的例子:一个函数完成了某种功能,可能是一次性初始化,然后都基于这个返回值进行操作,这个返回值恰巧是另一个函数:

- -
var setup = function () {
-    alert(1);
-    return function () {
-        alert(2);
-    };
-};
-
-// using the setup function
-var my = setup(); // alerts 1
-my(); // alerts 2
-
- -

因为setup()把返回的函数作了包装,它创建了一个闭包,我们可以用这个闭包来存储一些私有数据,这些私有数据可以通过返回的函数进行操作,但在函数外部不能直接读取到这些私有数据。比如这个例子中提供了一个计数器,每次调用这个函数计数器都会加一:

- -
var setup = function () {
-    var count = 0;
-    return function () {
-        return (count += 1);
-    };
-};
-
-// usage
-var next = setup();
-next(); // returns 1
-next(); // 2
-next(); // 3
-
- -

自定义函数

- -

我们动态定义函数,并将函数赋值给变量。如果将你定义的函数赋值给已经存在的函数变量的话,则新函数会覆盖旧函数。这样做的结果是,旧函数的引用就丢弃掉了,变量中所存储的引用值替换成了新的。这样看起来这个变量指代的函数逻辑就发生了变化,或者说函数进行了“重新定义”或“重写”。说起来有些拗口,实际上并不复杂,来看一个例子:

- -
var scareMe = function () {
-    alert("Boo!");
-    scareMe = function () {
-        alert("Double boo!");
-    };
-};
-// using the self-defining function
-scareMe(); // Boo!
-scareMe(); // Double boo!
-
- -

当函数中包含一些初始化操作,并希望这些初始化只执行一次,那么这种模式是非常适合这个场景的。因为能避免的重复执行则尽量避免,函数的一部分可能再也不会执行到。在这个场景中,函数执行一次后就被重写为另外一个函数了。

- -

使用这种模式可以帮助提高应用的执行效率,因为重新定义的函数执行更少的代码。

- -
-

这种模式的另外一个名字是“函数的懒惰定义”,因为直到函数执行一次后才重新定义,可以说它是“某个时间点之后才存在”,简称“懒惰定义”。

-
- -

这种模式有一种明显的缺陷,就是之前给原函数添加的功能在重定义之后都丢失了。如果将这个函数定义为不同的名字,函数赋值给了很多不同的变量,或作为对象的方法使用,那么新定义的函数有可能不会执行,原始的函数会照旧执行(译注:由于函数的赋值是引用的赋值,函数赋值给多个变量只是将引用赋值给了多个变量,当某一个变量定义了新的函数,也只是变量的引用值发生变化,原函数本身依旧存在,当程序中存在某个变量的引用还是旧函数的话,旧函数还是会依旧执行)。

- -

让我们来看一个例子,scareMe()函数在这里作为一等对象来使用:

- -
    -
  1. 给他增加了一个属性
  2. -
  3. 函数对象赋值给一个新变量
  4. -
  5. 函数依旧可以作为方法来调用
  6. -
- -

看一下这段代码:

- -
// 1. adding a new property
-scareMe.property = "properly";
-
-// 2. assigning to a different name
-var prank = scareMe;
-
-// 3. using as a method
-var spooky = {
-    boo: scareMe
-};
-
-// calling with a new name
-prank(); // "Boo!"
-prank(); // "Boo!"
-console.log(prank.property); // "properly"
-
-// calling as a method
-spooky.boo(); // "Boo!"
-spooky.boo(); // "Boo!"
-console.log(spooky.boo.property);   // "properly"
-
-// using the self-defined function
-scareMe(); // Double boo!
-scareMe(); // Double boo!
-console.log(scareMe.property); // undefined
-
- -

从结果来看,当自定义函数被赋值给一个新的变量的时候,这段使用自定义函数的代码的执行结果与我们期望的结果可能并不一样。每当prank()运行的时候,它都弹出“Boo!”。同时它也重写了scareMe()函数,但是prank()自己仍然能够使用之前的定义,包括属性property。在这个函数被作为spooky对象的boo()方法调用的时候,结果也一样。所有的这些调用,在第一次的时候就已经修改了全局的scareMe()的指向,所以当它最终被调用的时候,它的函数体已经被修改为弹出“Double boo”。它也就不能获取到新添加的属性“property”。

- -

立即执行的函数

- -

立即执行的函数是一种语法模式,它会使函数在定义后立即执行。看这个例子:

- -
(function () {
-    alert('watch out!');
-}());
-
- -

这种模式本质上只是一个在创建后就被执行的函数表达式(具名或者匿名)。“立即执行的函数”这种说法并没有在ECMAScript标准中被定义,但它作为一个名词,有助于我们的描述和讨论。

- -

这种模式由以下几个部分组成:

- -
    -
  • 使用函数表达式定义一个函数。(不能使用函数声明。)
  • -
  • 在最后加入一对括号,这会使函数立即被执行。
  • -
  • 把整个函数包裹到一对括号中(只在没有将函数赋值给变量时需要)。
  • -
- -

下面这种语法也很常见(注意右括号的位置),但是JSLint倾向于第一种:

- -
(function () {
-    alert('watch out!');
-})();
-
- -

这种模式很有用,它为我们提供一个作用域的沙箱,可以在执行一些初始化代码的时候使用。设想这样的场景:当页面加载的时候,你需要运行一些代码,比如绑定事件、创建对象等等。所有的这些代码都只需要运行一次,所以没有必要创建一个带有名字的函数。但是这些代码需要一些临时变量,而这些变量在初始化完之后又不会再次用到。显然,把这些变量作为全局变量声明是不合适的。正因为如此,我们才需要立即执行的函数。它可以把你所有的代码包裹到一个作用域里面,而不会暴露任何变量到全局作用域中:

- -
(function () {
-
-    var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
-        today = new Date(),
-        msg = 'Today is ' + days[today.getDay()] + ', ' + today.getDate();
-
-    alert(msg);
-
-}()); // "Today is Fri, 13"
-
- -

如果这段代码没有被包裹到立即执行函数中,那么变量days、today、msg都会是全局变量,而这些变量仅仅是由因为初始化而遗留下来的垃圾,没有任何用处。

- -

立即执行的函数的参数

- -

立即执行的函数也可以接受参数,看这个例子:

- -
// prints:
-// I met Joe Black on Fri Aug 13 2010 23:26:59 GMT-0800 (PST)
-
-(function (who, when) {
-
-    console.log("I met " + who + " on " + when);
-
-}("Joe Black", new Date()));
-
- -

通常的做法,会把全局对象当作一个参数传给立即执行的函数,以保证在函数内部也可以访问到全局对象,而不是使用window对象,这样可以使得代码在非浏览器环境中使用时更具可移植性。

- -

值得注意的是,一般情况下尽量不要给立即执行的函数传入太多的参数,否则会有一件麻烦的事情,就是你在阅读代码的时候需要频繁地上下滚动代码。

- -

立即执行的函数的返回值

- -

和其它的函数一样,立即执行的函数也可以返回值,并且这些返回值也可以被赋值给变量:

- -
var result = (function () {
-    return 2 + 2;
-}());
-
- -

如果省略括号的话也可以达到同样的目的,因为如果需要将返回值赋给变量,那么第一对括号就不是必需的。省略括号的代码是这样子:

- -
var result = function () {
-    return 2 + 2;
-}();
-
- -

这种写法更简洁,但是同时也容易造成误解。如果有人在阅读代码的时候忽略了最后的一对括号,那么他会以为result指向了一个函数。而事实上result是指向这个函数运行后的返回值,在这个例子中是4。

- -

还有一种写法也可以得到同样的结果:

- -
var result = (function () {
-    return 2 + 2;
-})();
-
- -

前面的例子中,立即执行的函数返回的是一个基本类型的数值。但事实上,除了基本类型以外,一个立即执行的函数可以返回任意类型的值,甚至返回一个函数都可以。你可以利用立即执行的函数的作用域来存储一些私有的数据,这些数据只能在返回的内层函数中被访问。

- -

在下面的例子中,立即执行的函数的返回值是一个函数,这个函数会简单地返回res的值,并且它被赋给了变量getResult。而res是一个预先计算好的变量,它被存储在立即执行函数的闭包中:

- -
var getResult = (function () {
-    var res = 2 + 2;
-    return function () {
-        return res;
-    };
-}());
-
- -

在定义一个对象的属性的时候也可以使用立即执行的函数。设想一下这样的场景:你需要定义一个对象的属性,这个属性在对象的生命周期中都不会改变,但是在定义之前,你需要做一点额外的工作来得到正确的值。这种情况下你就可以使用立即执行的函数来包裹那些额外的工作,然后将它的返回值作为对象属性的值。下面是一个例子:

- -
var o = {
-    message: (function () {
-        var who = "me",
-            what = "call";
-        return what + " " + who;
-    }()),
-    getMsg: function () {
-        return this.message;
-    }
-};
-
-// usage
-o.getMsg(); // "call me"
-o.message; // "call me"
-
- -

在这个例子中,o.message是一个字符串,而不是一个函数,但是它需要一个函数在脚本载入后来得到这个属性值。

- -

好处和用法

- -

立即执行的函数应用很广泛。它可以帮助我们做一些不想留下全局变量的工作。所有定义的变量都只是立即执行的函数的本地变量,你完全不用担心临时变量会污染全局对象。

- -
-

立即执行的函数还有一些名字,比如“自调用函数”或者“自执行函数”,因为这些函数会在被定义后立即执行自己。

-
- -

这种模式也经常被用到书签代码中,因为书签代码会在任何一个页面运行,所以需要非常苛刻地保持全局命名空间干净。

- -

这种模式也可以让你包裹一些独立的特性到一个封闭的模块中。设想你的页面是静态的,在没有JavaScript的时候工作正常,然后,本着渐进增强的精神,你给页面加入了一点增加代码。这时候,你就可以把你的代码(也可以叫“模块”或者“特性”)放到一个立即执行的函数中并且保证页面在有没有它的时候都可以正常工作。然后你就可以加入更多的增强特性,或者对它们进行移除、进行独立测试或者允许用户禁用等等。

- -

你可以使用下面的模板定义一段函数代码,我们叫它module1:

- -
// module1 defined in module1.js
-(function () {
-
-    // all the module 1 code ...
-
-}());
-
- -

套用这个模板,你就可以编写其它的模块。然后在发布到线上的时候,你就可以决定在这个时间节点上哪些特性是可以使用的,然后使用发布脚本将它们打包上线。

- -

立即初始化的对象

- -

还有另外一种可以避免污染全局作用域的方法,和前面描述的立即执行的函数相似,叫做“立即初始化的对象”模式。这种模式使用一个带有init()方法的对象来实现,这个方法在对象被创建后立即执行。初始化的工作由init()函数来完成。

- -

下面是一个立即初始化的对象模式的例子:

- -
({
-    // here you can define setting values
-    // a.k.a. configuration constants
-    maxwidth: 600,
-    maxheight: 400,
-
-    // you can also define utility methods
-    gimmeMax: function () {
-        return this.maxwidth + "x" + this.maxheight;
-    },
-
-    // initialize
-    init: function () {
-        console.log(this.gimmeMax());
-        // more init tasks...
-    }
-}).init();
-
- -

在语法上,当你使用这种模式的时候就像在使用对象字面量创建一个普通对象一样。除此之外,还需要将对象字面量用括号括起来,这样能让JavaScript引擎知道这是一个对象字面量,而不是一个代码块(if或者for循环之类)。在括号后面,紧接着就执行了init()方法。

- -

你也可以将对象字面量和init()调用一起写到括号里面。简单地说,下面两种语法都是有效的:

- -
({...}).init();
-({...}.init());
-
- -

这种模式的好处和自动执行的函数模式是一样的:在做一些一次性的初始化工作的时候保护全局作用域不被污染。从语法上看,这种模式似乎比只包含一段代码在一个匿名函数中要复杂一些,但是如果你的初始化工作比较复杂(这种情况很常见),它会给整个初始化工作一个比较清晰的结构。比如,一些私有的辅助性函数可以被很轻易地看出来,因为它们是这个临时对象的属性,但是如果是在立即执行的函数模式中,它们很可能只是一些散落的函数。

- -

这种模式的一个弊端是,JavaScript压缩工具可能不能像压缩一段包裹在函数中的代码一样有效地压缩这种模式的代码。这些私有的属性和方法不被会重命名为一些更短的名字,因为从压缩工具的角度来看,保证压缩的可靠性更重要。在写作本书的时候,Google出品的Closure Compiler的“advanced”模式是唯一会重命名立即初始化的对象的属性的压缩工具。一个压缩后的样例是这样:

- -
({d:600,c:400,a:function(){return this.d+"x"+this.c},b:function(){console.log(this.a())}}).b();
-
- -
-

这种模式主要用于一些一次性的工作,并且在init()方法执行完后就无法再次访问到这个对象。如果希望在这些工作完成后保持对对象的引用,只需要简单地在init()的末尾加上return this;即可。

-
- -

条件初始化

- -

条件初始化(也叫条件加载)是一种优化模式。当你知道某种条件在整个程序生命周期中都不会变化的时候,那么对这个条件的探测只做一次就很有意义。浏览器探测(或者特征检测)是一个典型的例子。

- -

举例说明,当你探测到XMLHttpRequest被作为一个本地对象支持时,就知道浏览器不会在程序执行过程中改变这一情况,也不用突然又需要去处理ActiveX对象。当环境不发生变化的时候,你的代码就没有必要在需要在每次XHR对象时探测一遍(并且得到同样的结果)。

- -

另外一些可以从条件初始化中获益的场景是获得一个DOM元素的computed styles或者是绑定事件处理函数。大部分程序员在他们的客户端编程生涯中都编写过事件绑定和取消绑定相关的组件,像下面的例子:

- -
// BEFORE
-var utils = {
-    addListener: function (el, type, fn) {
-        if (typeof window.addEventListener === 'function') {
-            el.addEventListener(type, fn, false);
-        } else if (typeof document.attachEvent === 'function') { // IE
-            el.attachEvent('on' + type, fn);
-        } else { // older browsers
-            el['on' + type] = fn;
-        }
-    },
-    removeListener: function (el, type, fn) {
-        // pretty much the same...
-    }
-};
-
- -

这段代码的问题就是效率不高。每当你执行utils.addListener()或者utils.removeListener()时,同样的检查都会被重复执行。

- -

如果使用条件初始化,那么浏览器探测的工作只需要在初始化代码的时候执行一次。在初始化的时候,代码探测一次环境,然后重新定义这个函数在剩下来的程序生命周期中应该怎样工作。下面是一个例子,看看如何达到这个目的:

- -
// AFTER
-
-// the interface
-var utils = {
-    addListener: null,
-    removeListener: null
-};
-
-// the implementation
-if (typeof window.addEventListener === 'function') {
-    utils.addListener = function (el, type, fn) {
-        el.addEventListener(type, fn, false);
-    };
-    utils.removeListener = function (el, type, fn) {
-        el.removeEventListener(type, fn, false);
-    };
-} else if (typeof document.attachEvent === 'function') { // IE
-    utils.addListener = function (el, type, fn) {
-        el.attachEvent('on' + type, fn);
-    };
-    utils.removeListener = function (el, type, fn) {
-        el.detachEvent('on' + type, fn);
-    };
-} else { // older browsers
-    utils.addListener = function (el, type, fn) {
-        el['on' + type] = fn;
-    };
-    utils.removeListener = function (el, type, fn) {
-        el['on' + type] = null;
-    };
-}
-
- -

说到这里,要特别提醒一下关于浏览器探测的事情。当你使用这个模式的时候,不要对浏览器特性过度假设。举个例子,如果你探测到浏览器不支持window.addEventListener,不要假设这个浏览器是IE,也不要认为它不支持原生的XMLHttpRequest,虽然这个结论在整个浏览器历史上的某个点是正确的。当然,也有一些情况是可以放心地做一些特性假设的,比如.addEventListener和.removeEventListerner,但是通常来讲,浏览器的特性在发生变化时都是独立的。最好的策略就是分别探测每个特性,然后使用条件初始化,使这种探测只做一次。

- -

函数属性——Memoization模式

- -

函数也是对象,所以它们可以有属性。事实上,函数也确实本来就有一些属性。比如,对一个函数来说,不管是用什么语法创建的,它会自动拥有一个length属性来标识这个函数期待接受的参数个数:

- -
function func(a, b, c) {}
-console.log(func.length); // 3
-
- -

任何时候都可以给函数添加自定义属性。添加自定义属性的一个有用场景是缓存函数的执行结果(返回值),这样下次同样的函数被调用的时候就不需要再做一次那些可能很复杂的计算。缓存一个函数的运行结果也就是为大家所熟知的Memoization。

- -

在下面的例子中,myFunc函数创建了一个cache属性,可以通过myFunc.cache访问到。这个cache属性是一个对象(hash表),传给函数的参数会作为对象的key,函数执行结果会作为对象的值。函数的执行结果可以是会用到的任何的复杂数据结构:

- -
var myFunc = function (param) {
-    if (!myFunc.cache[param]) {
-        var result = {};
-        // ... expensive operation ...
-        myFunc.cache[param] = result;
-    }
-    return myFunc.cache[param];
-};
-
-// cache storage
-myFunc.cache = {};
-
- -

上面的代码假设函数只接受一个参数param,并且这个参数是基本类型(比如字符串)。如果你有更多更复杂的参数,则通常需要对它们进行序列化。(译注:序列化后来作为缓存对象的key。)比如,你需要将arguments对象序列化为JSON字符串,然后使用JSON字符串作为cache对象的key:

- -
var myFunc = function () {
-
-    var cachekey = JSON.stringify(Array.prototype.slice.call(arguments)),
-        result;
-
-    if (!myFunc.cache[cachekey]) {
-        result = {};
-        // ... expensive operation ...
-        myFunc.cache[cachekey] = result;
-    }
-    return myFunc.cache[cachekey];
-};
-
-// cache storage
-myFunc.cache = {};
-
- -

需要注意的是,在序列化的过程中,对象的“标识”将会丢失。如果你有两个不同的对象,却碰巧有相同的属性,那么他们会共享同样的缓存内容。

- -

前面代码中的函数名还可以使用arguments.callee来替代,这样就不用将函数名硬编码。不过尽管现阶段这个办法可行,但是仍然需要注意,arguments.callee在ECMAScript 5的严格模式中是不被允许的:

- -
var myFunc = function (param) {
-
-    var f = arguments.callee,
-        result;
-
-    if (!f.cache[param]) {
-        result = {};
-        // ... expensive operation ...
-        f.cache[param] = result;
-    }
-    return f.cache[param];
-};
-
-// cache storage
-myFunc.cache = {};
-
- -

配置对象

- -

配置对象模式是一种提供更简洁的API的方法,尤其是当你正在写一个即将被其它程序调用的类库或者任何其它代码的时候。

- -

软件在开发和维护过程中需要不断改变是一个不争的事实。这样的事情总是以一些有限的需求开始,但是随着开发的进行,越来越多的功能会不断被加进来。

- -

设想一下你正在写一个名为addPerson()的函数,它接受一个姓和一个名,然后在列表中加入一个人:

- -
function addPerson(first, last) {...}
-
- -

然后你意识到,生日也必须要存储,此外,性别和地址也作为可选项存储。所以你修改了函数,添加了一些新的参数(还得非常小心地将可选参数放到最后):

- -
function addPerson(first, last, dob, gender, address) {...}
-
- -

这个时候,函数已经显得有点长了。然后,你又被告知需要添加一个用户名,并且不是可选的。现在这个函数的调用者需要将所有的可选参数传进来,并且得非常小心地保证不弄混参数的顺序:

- -
addPerson("Bruce", "Wayne", new Date(), null, null, "batman");
-
- -

传一大串的参数真的很不方便。一个更好的办法就是将它们替换成一个参数,并且把这个参数弄成对象;我们叫它conf,是“configuration”(配置)的缩写:

- -
addPerson(conf);
-
- -

然后这个函数的使用者就可以这样:

- -
var conf = {
-    username: "batman",
-    first: "Bruce",
-    last: "Wayne"
-};
-addPerson(conf);
-
- -

配置对象模式的好处是:

- -
    -
  • 不需要记住参数的顺序
  • -
  • 可以很安全地跳过可选参数
  • -
  • 拥有更好的可读性和可维护性
  • -
  • 更容易添加和移除参数
  • -
- -

配置对象模式的坏处是:

- -
    -
  • 需要记住参数的名字
  • -
  • 参数名字不能被压缩
  • -
- -

举些实例,这个模式对创建DOM元素的函数或者是给元素设定CSS样式函数会非常实用,因为元素和CSS样式可能会有很多但是大部分可选的属性。

- -

柯里化 (Curry)

- -

在本章剩下的部分,我们将讨论一下关于柯里化和部分应用的话题。但是在我们开始这个话题之前,先看一下到底什么是函数应用。

- -

函数应用

- -

在一些纯粹的函数式编程语言中,对函数的描述不是被调用(called或者invoked),而是被应用(applied)。在JavaScript中也有同样的东西——我们可以使用Function.prototype.apply()来应用一个函数,因为在JavaScript中,函数实际上是对象,并且他们拥有方法。

- -

下面是一个函数应用的例子:

- -
// define a function
-var sayHi = function (who) {
-    return "Hello" + (who ? ", " + who : "") + "!";
-};
-
-// invoke a function
-sayHi(); // "Hello"
-sayHi('world'); // "Hello, world!"
-
-// apply a function
-sayHi.apply(null, ["hello"]); // "Hello, hello!"
-
- -

从上面的例子中可以看出来,调用一个函数和应用一个函数有相同的结果。apply()接受两个参数:第一个是在函数内部绑定到this上的对象,第二个是一个参数数组,参数数组会在函数内部变成一个类似数组的arguments对象。如果第一个参数为null,那么this将指向全局对象,这正是当你调用一个函数(且这个函数不是某个对象的方法)时发生的事情。

- -

当一个函数是一个对象的方法时,我们不再像前面的例子一样传入null。(译注:主要是为了保证方法中的this绑定到一个有效的对象而不是全局对象。)在下面的例子中,对象被作为第一个参数传给apply():

- -
var alien = {
-    sayHi: function (who) {
-        return "Hello" + (who ? ", " + who : "") + "!";
-    }
-};
-
-alien.sayHi('world'); // "Hello, world!"
-sayHi.apply(alien, ["humans"]); // "Hello, humans!"
-
- -

在这个例子中,sayHi()中的this指向alien。而在上一个例子中,this是指向的全局对象。(译注:这个例子的代码有误,最后一行的sayHi并不能访问到alien的sayHi方法,需要使用alien.sayHi.apply(alien, ["humans"])才可正确运行。另外,在sayHi中也没有出现this。)

- -

正如上面两个例子所展现出来的一样,我们所谓的函数调用跟函数应用的一种语法糖没有什么太大的差别。

- -

需要注意的是,除了apply()之外,Function.prototype对象还有一个call()方法,但是它仍然只是apply()的一种语法糖。(译注:这两个方法的区别在于,apply()只接受两个参数,第二个参数为需要传给函数的参数数组,而call()则接受任意多个参数,从第二个开始将参数依次传给函数。)不过有种情况下使用这个语法糖会更好:当你的函数只接受一个参数的时候,你可以省去为唯一的一个元素创建数组的工作:

- -
// the second is more efficient, saves an array
-sayHi.apply(alien, ["humans"]); // "Hello, humans!"
-sayHi.call(alien, "humans"); // "Hello, humans!"
-
- -

部分应用

- -

现在我们知道了,调用一个函数实际上就是给它应用一堆参数,那是否能够只传一部分参数而不传全部呢?这实际上跟我们手工处理数学函数非常类似。

- -

假设已经有了一个add()函数,它的工作是把x和y两个数加到一起。下面的代码片段展示了当x为5、y为4时的计算步骤:

- -
// for illustration purposes
-// not valid JavaScript
-
-// we have this function
-function add(x, y) {
-    return x + y;
-}
-
-// and we know the arguments
-add(5, 4);
-
-// step 1 -- substitute one argument
-function add(5, y) {
-    return 5 + y;
-}
-
-// step 2 -- substitute the other argument
-function add(5, 4) {
-    return 5 + 4;
-}
-
- -

在这个代码片段中,step 1和step 2并不是有效的JavaScript代码,但是它展示了我们手工计算的过程。首先获得第一个参数的值,然后将未知的x和已知的值5替换到函数中。然后重复这个过程,直到替换掉所有的参数。

- -

step 1是一个所谓的部分应用的例子:我们只应用了第一个参数。当你执行一个部分应用的时候并不能获得结果(或者是解决方案),取而代之的是另一个函数。

- -

下面的代码片段展示了一个虚拟的partialApply()方法的用法:

- -
var add = function (x, y) {
-    return x + y;
-};
-
-// full application
-add.apply(null, [5, 4]); // 9
-
-// partial application
-var newadd = add.partialApply(null, [5]);
-// applying an argument to the new function
-newadd.apply(null, [4]); // 9
-
- -

正如你所看到的一样,部分应用给了我们另一个函数,这个函数可以在稍后调用的时候接受其它的参数。这实际上跟add(5)(4)是等价的,因为add(5)返回了一个函数,这个函数可以使用(4)来调用。我们又一次看到,熟悉的add(5, 4)也跟add(5)(4)的一种语法糖差不了多少。

- -

现在,让我们回到地球:并不存在这样的一个partialApply()函数,并且函数的默认表现也不会像上面的例子中那样。但是你完全可以自己去写,因为JavaScript的动态特性完全可以做到这样。

- -

让函数理解并且处理部分应用的过程,叫柯里化(Currying)。

- -

柯里化(Currying)

- -

柯里化和辛辣的印度菜可没什么关系;它来自数学家Haskell Curry。(Haskell编程语言也是因他而得名。)柯里化是一个变换的函数的过程。柯里化的另外一个名字也叫schönfinkelisation,来自另一位数学家——Moses Schönfinkelisation——这种变换的最初发明者。

- -

所以我们怎样对一个函数进行柯里化呢?其它的函数式编程语言也许已经原生提供了支持并且所有的函数已经默认柯里化了。在JavaScript中我们可以修改一下add()函数使它柯里化,然后支持部分应用。

- -

来看一个例子:

- -
// a curried add()
-// accepts partial list of arguments
-function add(x, y) {
-    var oldx = x, oldy = y;
-    if (typeof oldy === "undefined") { // partial
-        return function (newy) {
-            return oldx + newy;
-        };
-    }
-    // full application
-    return x + y;
-}
-
-// test
-typeof add(5); // "function"
-add(3)(4); // 7
-
-// create and store a new function
-var add2000 = add(2000);
-add2000(10); // 2010
-
- -

在这段代码中,第一次调用add()时,在返回的内层函数那里创建了一个闭包。这个闭包将原来的x和y的值存储到了oldx和oldy中。当内层函数执行的时候,oldx会被使用。如果没有部分应用,即x和y都传了值,那么这个函数会简单地将他们相加。这个add()函数的实现跟实际情况比起来有些冗余,仅仅是为了更好地说明问题。下面的代码片段中展示了一个更简洁的版本,没有oldx和oldy,因为原始的x已经被存储到了闭包中,此外我们复用了y作为本地变量,而不用像之前那样新定义一个变量newy:

- -
// a curried add
-// accepts partial list of arguments
-function add(x, y) {
-    if (typeof y === "undefined") { // partial
-        return function (y) {
-            return x + y;
-        };
-    }
-    // full application
-    return x + y;
-}
-
- -

在这些例子中,add()函数自己处理了部分应用。有没有可能用一种更为通用的方式来做同样的事情呢?换句话说,我们能不能对任意一个函数进行处理,得到一个新函数,使它可以处理部分参数?下面的代码片段展示了一个通用函数的例子,我们叫它schonfinkelize(),正是用来做这个的。我们使用schonfinkelize()一部分原因是它比较难发音,另一部分原因是它听起来比较像动词(使用“curry”则不是那么明确),而我们刚好需要一个动词来表明这是一个函数转换的过程。

- -

这是一个通用的柯里化函数:

- -
function schonfinkelize(fn) {
-    var slice = Array.prototype.slice,
-    stored_args = slice.call(arguments, 1);
-    return function () {
-        var new_args = slice.call(arguments),
-        args = stored_args.concat(new_args);
-        return fn.apply(null, args);
-    };
-}
-
- -

这个schonfinkelize可能显得比较复杂了,只是因为在JavaScript中arguments不是一个真的数组。从Array.prototype中借用slice()方法帮助我们将arguments转换成数组,以便能更好地对它进行操作。当schonfinkelize()第一次被调用的时候,它使用slice变量存储了对slice()方法的引用,同时也存储了调用时的参数(stored_args),参数保留了除第一个以外的,因为第一个参数是要被柯里化的函数。schonfinkelize()返回了一个函数。当这个返回的函数被调用的时候,它可以(通过闭包)访问到已经存储的参数stored_args和slice。新的函数只需要合并老的部分应用的参数(stored_args)和新的参数(new_args),然后将它们应用到原来的函数fn(也可以在装饰中访问到)即可。

- -

现在有了通用的柯里化函数,就可以做一些测试了:

- -
// a normal function
-function add(x, y) {
-    return x + y;
-}
-
-// curry a function to get a new function
-var newadd = schonfinkelize(add, 5);
-newadd(4); // 9
-
-// another option -- call the new function directly
-schonfinkelize(add, 6)(7); // 13
-
- -

用来做函数转换的schonfinkelize()并不局限于单个参数或者单步的柯里化。这里有些更多用法的例子:

- -
// a normal function
-function add(a, b, c, d, e) {
-    return a + b + c + d + e;
-}
-
-// works with any number of arguments
-schonfinkelize(add, 1, 2, 3)(5, 5); // 16
-
-// two-step currying
-var addOne = schonfinkelize(add, 1);
-addOne(10, 10, 10, 10); // 41
-var addSix = schonfinkelize(addOne, 2, 3);
-addSix(5, 5); // 16
-
- -

什么时候使用柯里化

- -

当你发现自己在调用同样的函数并且传入的参数大部分都相同的时候,就是考虑柯里化的理想场景了。你可以通过传入一部分的参数动态地创建一个新的函数。这个新函数会存储那些重复的参数(所以你不需要再每次都传入),然后再在调用原始函数的时候将整个参数列表补全,正如原始函数期待的那样。

- -

小结

- -

在JavaScript中,开发者对函数的理解和运用的要求是比较苛刻的。在本章中,主要讨论了有关函数的一些背景知识和术语。你知道了JavaScript函数中两个重要的特性,也就是:

- -
    -
  1. 函数是一等对象,他们可以被作为值传递,也可以拥有属性和方法。
  2. -
  3. 函数拥有本地作用域,而大括号不产生块级作用域。另外需要注意的是,变量的声明会被提前到本地作用域顶部。
  4. -
- -

创建一个函数的语法有:

- -
    -
  1. 带有名字的函数表达式
  2. -
  3. 函数表达式(和上一种一样,但是没有名字),也就是为大家熟知的“匿名函数”
  4. -
  5. 函数声明,与其它语言的函数语法相似
  6. -
- -

在介绍完背景和函数的语法后,介绍了一些有用的模式,按分类列出:

- -
    -
  1. API模式,它们帮助我们为函数给出更干净的接口,包括:

    - -
      -
    • 回调模式 -传入一个函数作为参数

    • -
    • 配置对象 -帮助保持函数的参数数量可控

    • -
    • 返回函数 -函数的返回值是另一个函数

    • -
    • 柯里化 -新函数在已有函数的基础上再加上一部分参数构成

    • -
  2. -
  3. 初始化模式,这些模式帮助我们用一种干净的、结构化的方法来做一些初始化工作(在web页面和应用中非常常见),通过一些临时变量来保证不污染全局命名空间。这些模式包括:

    - -
      -
    • 立即执行的函数 -当它们被定义后立即执行

    • -
    • 立即初始化的对象 -初始化工作被放入一个匿名对象,这个对象提供一个可以立即被执行的方法

    • -
    • 条件初始化 -使分支代码只在初始化的时候执行一次,而不是在整个程序生命周期中反复执行

    • -
  4. -
  5. 性能模式,这些模式帮助提高代码的性能,包括:

    - -
      -
    • Memoization -利用函数的属性,使已经计算过的值不用再次计算

    • -
    • 自定义函数 -重写自身的函数体,使第二次及后续的调用做更少的工作

    • -
  6. -
- - - - \ No newline at end of file diff --git a/chapter4.markdown b/chapter4.markdown index c09a335..92c18dd 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -1026,20 +1026,28 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 传入一个函数作为参数 - 配置对象 - - 帮助保持函数的参数数量可控 + + 帮助保持函数的参数数量可控 - 返回函数 - - 函数的返回值是另一个函数 + + 函数的返回值是另一个函数 - 柯里化 - - 新函数在已有函数的基础上再加上一部分参数构成 + + 新函数在已有函数的基础上再加上一部分参数构成 2. 初始化模式,这些模式帮助我们用一种干净的、结构化的方法来做一些初始化工作(在web页面和应用中非常常见),通过一些临时变量来保证不污染全局命名空间。这些模式包括: - 立即执行的函数 - - 当它们被定义后立即执行 + + 当它们被定义后立即执行 - 立即初始化的对象 - - 初始化工作被放入一个匿名对象,这个对象提供一个可以立即被执行的方法 + + 初始化工作被放入一个匿名对象,这个对象提供一个可以立即被执行的方法 - 条件初始化 - - 使分支代码只在初始化的时候执行一次,而不是在整个程序生命周期中反复执行 + + 使分支代码只在初始化的时候执行一次,而不是在整个程序生命周期中反复执行 3. 性能模式,这些模式帮助提高代码的执行速度,包括: - Memoization - - 利用函数的属性,使已经计算过的值不用再次计算 + + 利用函数的属性,使已经计算过的值不用再次计算 - 自定义函数 - - 重写自身的函数体,使第二次及后续的调用做更少的工作 \ No newline at end of file + + 重写自身的函数体,使第二次及后续的调用做更少的工作 \ No newline at end of file From 95c1f6f367bf3bf8c243b63cd5da516ada745681 Mon Sep 17 00:00:00 2001 From: TooBug Date: Sat, 23 Jun 2012 20:49:09 +0800 Subject: [PATCH 027/258] =?UTF-8?q?=E7=AC=AC=E5=85=AD=E7=AB=A0=20=E7=B1=BB?= =?UTF-8?q?=E5=BC=8F=E7=BB=A7=E6=89=BF=20vs=20=E7=8E=B0=E4=BB=A3=E7=BB=A7?= =?UTF-8?q?=E6=89=BF=E6=A8=A1=E5=BC=8F=20=E4=B8=80=E8=8A=82=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 chapter6.markdown diff --git a/chapter6.markdown b/chapter6.markdown new file mode 100644 index 0000000..865f46f --- /dev/null +++ b/chapter6.markdown @@ -0,0 +1,32 @@ + +# 代码复用模式 + +代码复用是一个既重要又有趣的话题,因为努力在自己或者别人写的代码上写尽量少且可以复用的代码是件很自然的事情,尤其当这些代码是经过测试的、可维护的、可扩展的、有文档的时候。 + +当我们说到代码复用的时候,想到的第一件事就是继承,本章会有很大篇幅讲述这个话题。你将看到好多种方法来实现“类式(classical)”和一些其它方式的继承。但是,最最重要的事情,是你需要记住终极目标——代码复用。继承是达到这个目标的一种方法,但是不是唯一的。在本章,你将看到怎样基于其它对象来构建新对象,怎样使用混元,以及怎样在不使用继承的情况下只复用你需要的功能。 + +在做代码复用的工作的时候,谨记Gang of Four 在书中给出的关于对象创建的建议:“优先使用对象创建而不是类继承”。(译注:《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)是一本设计模式的经典书籍,该书作者为Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides,被称为“Gang of Four”,简称“GoF”。) + + +## 类式继承 vs 现代继承模式 + +在讨论JavaScript的继承这个话题的时候,经常会听到“类式继承”的概念,那我们先看一下什么是类式(classical)继承。classical一词并不是来自某些古老的、固定的或者是被广泛接受的解决方案,而仅仅是来自单词“class”。(译注:classical也有“经典”的意思。) + +很多编程语言都有原生的类的概念,作为对象的蓝本。在这些语言中,每个对象都是一个指定类的实例(instance),并且(以Java为例)一个对象不能在不存在对应的类的情况下存在。在JavaScript中,因为没有类,所以类的实例的概念没什么意义。JavaScript的对象仅仅是简单的键值对,这些键值对都可以动态创建或者是改变。 + +但是JavaScript拥有构造函数(constructor functions),并且有语法和使用类非常相似的new运算符。 + +在Java中你可能会这样写: + + Person adam = new Person(); + +在JavaScript中你可以这样: + + var adam = new Person(); + +除了Java是强类型语言需要给adam添加类型Person外,其它的语法看起来是一样的。JavaScript的创建函数调用看起来感觉Person是一个类,但事实上,Person仅仅是一个函数。语法上的相似使得非常多的开发者陷入对JavaScript类的思考,并且给出了很多模拟类的继承方案。这样的实现方式,我们叫它“类式继承”。顺便也提一下,所谓“现代”继承模式是指那些不需要你去想类这个概念的模式。 + +当需要给项目选择一个继承模式时,有不少的备选方案。你应该尽量选择那些现代继承模式,除非团队已经觉得“无类不欢”。 + +本章先讨论类式继承,然后再关注现代继承模式。 + From 3df9191b936aa4c316ae019c2663a803a561e382 Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 24 Jun 2012 11:27:51 +0800 Subject: [PATCH 028/258] =?UTF-8?q?=E7=AC=AC=E5=85=AD=E7=AB=A0=20=E7=B1=BB?= =?UTF-8?q?=E5=BC=8F=E7=BB=A7=E6=89=BF=E6=9C=9F=E6=9C=9B=E7=9A=84=E7=BB=93?= =?UTF-8?q?=E6=9E=9C=20=20=E4=B8=80=E8=8A=82=E7=BF=BB=E8=AF=91=E5=AE=8C?= =?UTF-8?q?=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/chapter6.markdown b/chapter6.markdown index 865f46f..33ce8f8 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -30,3 +30,28 @@ 本章先讨论类式继承,然后再关注现代继承模式。 +## 类式继承的期望结果 + +实现类式继承的目标是基于构造函数Child()来创建一个对象,然后从另一个构造函数Parent()获得属性。 + +> 尽管我们是在讨论类式继承,但还是尽量避免使用“类”这个词。“构造函数”或者“constructor”虽然更长,但是更准确,不会让人迷惑。通常情况下,应该努力避免在跟团队沟通的时候使用“类”这个词,因为在JavaScript中,很可能每个人都会有不同的理解。 + +下面是定义两个构造函数Parent()和Child()的例子: + + // the parent constructor + function Parent(name) { + this.name = name || 'Adam'; + } + + // adding functionality to the prototype + Parent.prototype.say = function () { + return this.name; + }; + + // empty child constructor + function Child(name) {} + + // inheritance magic happens here + inherit(Child, Parent); + +上面的代码定义了两个构造函数Parent()和Child(),say()方法被添加到了Parent()构建函数的原型(prototype)中,inherit()函数完成了继承的工作。inherit()函数并不是原生提供的,需要自己实现。让我们来看一看比较大众的实现它的几种方法。 \ No newline at end of file From e23948ea461f32aa3e5b7e7c35d133df5a13f819 Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 24 Jun 2012 11:34:57 +0800 Subject: [PATCH 029/258] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=AC=AC=E5=85=AD?= =?UTF-8?q?=E7=AB=A0=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.markdown b/README.markdown index f91e753..8a6b8b0 100644 --- a/README.markdown +++ b/README.markdown @@ -140,10 +140,10 @@ - method() 方法 - 小节 -## 第六章 代码重用模式 +## [第六章 代码重用模式](javascript.patterns/blob/master/chapter6.markdown#a) -- 类式继承 vs 现代继承模式 -- 类式继承的期望结果 +- [类式继承 vs 现代继承模式](javascript.patterns/blob/master/chapter6.markdown#a) +- [类式继承的期望结果](javascript.patterns/blob/master/chapter6.markdown#a) - 经典模式 1 ——默认模式 - 使用原型链 - 模式 1 的缺陷 From f556b52f94044cfdaf66d03bcfbf48519038d0ca Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 3 Jul 2012 13:37:18 +0800 Subject: [PATCH 030/258] =?UTF-8?q?=E7=B1=BB=E5=BC=8F=E7=BB=A7=E6=89=BF1?= =?UTF-8?q?=E2=80=94=E2=80=94=E9=BB=98=E8=AE=A4=E6=A8=A1=E5=BC=8F=20?= =?UTF-8?q?=E4=B8=80=E8=8A=82=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 66 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/chapter6.markdown b/chapter6.markdown index 33ce8f8..ac86869 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -54,4 +54,68 @@ // inheritance magic happens here inherit(Child, Parent); -上面的代码定义了两个构造函数Parent()和Child(),say()方法被添加到了Parent()构建函数的原型(prototype)中,inherit()函数完成了继承的工作。inherit()函数并不是原生提供的,需要自己实现。让我们来看一看比较大众的实现它的几种方法。 \ No newline at end of file +上面的代码定义了两个构造函数Parent()和Child(),say()方法被添加到了Parent()构建函数的原型(prototype)中,inherit()函数完成了继承的工作。inherit()函数并不是原生提供的,需要自己实现。让我们来看一看比较大众的实现它的几种方法。 + +## 类式继承1——默认模式 + +最常用的一种模式是使用Parent()构造函数来创建一个对象,然后把这个对象设为Child()的原型。这是可复用的inherit()函数的第一种实现方法: + + function inherit(C, P) { + C.prototype = new P(); + } + +需要强调的是原型(prototype属性)应该指向一个对象,而不是函数,所以它需要指向由父构造函数创建的实例(对象),而不是构造函数自己。换句话说,请注意new运算符,有了它这种模式才可以正常工作。 + +之后在应用中使用new Child()创建对象的时候,它将通过原型拥有Parent()实例的功能,像下面的例子一样: + + var kid = new Child(); + kid.say(); // "Adam" + +### 跟踪原型链 + +在这种模式中,子对象既继承了(父对象)“自己的属性”(添加给this的实例属性,比如name),也继承了原型中的属性和方法(比如say())。 + +我们来看一下在这种继承模式中原型链是怎么工作的。为了讨论方便,我们假设对象是内在中的一块空间,它包含数据和指向其它空间的引用。当使用new Parent()创建一个对象时,这样的一块空间就被分配了(图6-1中的2号)。它保存着name属性的数据。如果你尝试访问say()方法(比如通过(new Parent).say()),2号空间中并没有这个方法。但是在通过隐藏的链接__proto__指向Parent()构建函数的原型prototype属性时,就可以访问到包含say()方法的1号空间(Parent.prototype)了。所有的这一块都是在幕后发生的,不需要任何额外的操作,但是知道它是怎样工作的以及你正在访问或者修正的数据在哪是很重要的。注意,__proto__在这里只是为了解释原型链,这个属性在语言本身中是不可用的,尽管有一些环境提供了(比如Firefox)。 + +![图6-1 Parent()构造函数的原型链](./Figure/chapter6/6-1.jpg) + +图6-1 Parent()构造函数的原型链 + +现在我们来看一下在使用inherit()函数之后再使用var kid = new Child()创建一个新对象时会发生什么。见图6-2。 + +![图6-2 继承后的原型链](./Figure/chapter6/6-2.jpg) + +图6-2 继承后的原型链 + +Child()构造函数是空的,也没有属性添加到Child.prototype上,这样,使用new Child()创建出来的对象都是空的,除了有隐藏的链接__proto__。在这个例子中,__proto__指向在inherit()函数中创建的new Parent()对象。 + +现在使用kid.say()时会发生什么?3号对象没有这个方法,所以通过原型链找到2号。2号对象也没有这个方法,所以也通过原型链找到1号,刚好有这个方法。接下来say()方法引用了this.name,这个变量也需要解析。于是沿原型链查找的过程又走了一遍。在这个例子中,this指向3号对象,它没有name属性。然后2号对象被访问,并且有name属性,值为“Adam”。 + +最后,我们多看一点东西,假如我们有如下的代码: + + var kid = new Child(); + kid.name = "Patrick"; + kid.say(); // "Patrick" + +图6-3展现了这个例子的原型链: + +![图6-3 继承并且给子对象添加属性后的原型链](./Figure/chapter6/6-3.jpg) + +图6-3 继承并且给子对象添加属性后的原型链 + +设定kid.name并没有改变2号对象的name属性,但是它直接在3号对象上添加了自己的name属性。当kid.say()执行时,say方法在3号对象中找,然后是2号,最后到1号,像前面说的一样。但是这一次在找this.name(和kid.name一样)时很快,因为这个属性在3号对象中就被找到了。 + +如果通过delete kid.name的方式移除新添加的属性,那么2号对象的name属性将暴露出来并且在查找的时候被找到。 + +### 这种模式的缺点 + +这种模式的一个缺点是既继承了(父对象)“自己的属性”,也继承了原型中的属性。大部分情况下你可能并不需要“自己的属性”,因为它们更可能是为实例对象添加的,并不用于复用。 + +> 一个在构造函数上常用的规则是,用于复用的成员(译注:属性和方法)应该被添加到原型上。 + +在使用这个inherit()函数时另外一个不便是它不能够让你传参数给子构造函数,这些参数有可能是想再传给父构造函数的。考虑下面的例子: + + var s = new Child('Seth'); + s.say(); // "Adam" + +这并不是我们期望的结果。事实上传递参数给父构造函数是可能的,但这样需要在每次需要一个子对象时再做一次继承,很不方便,因为需要不断地创建父对象。 \ No newline at end of file From 0f060dd7b7b6e7beb1294562e6e3427d15ac3cd5 Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 3 Jul 2012 13:37:39 +0800 Subject: [PATCH 031/258] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=AC=AC6=E7=AB=A0?= =?UTF-8?q?=E6=8F=92=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Figure/chapter6/6-1.jpg | Bin 0 -> 16984 bytes Figure/chapter6/6-2.jpg | Bin 0 -> 24232 bytes Figure/chapter6/6-3.jpg | Bin 0 -> 26729 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Figure/chapter6/6-1.jpg create mode 100644 Figure/chapter6/6-2.jpg create mode 100644 Figure/chapter6/6-3.jpg diff --git a/Figure/chapter6/6-1.jpg b/Figure/chapter6/6-1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..805968027345c232dc27ceeef52a4a05571db217 GIT binary patch literal 16984 zcmeIZcU)6j_a}Uirc{xpv?#p@h|`hb zYpDYi6coUH@E<^)0ImX*M~?jb0Y6mW5A{)MYAPyfI$BzqqYQKm4D@vL^o&d=jx#c` zFwxT==Qz&7%Er#l&cMuhl7sE!2{v}NpD&@H1fQXzK1NM_jE#|=k?kLU$c+HYQDBAw zMM=RA9ATlLWT7Cp0uTV8pa!M=De!L}iX)(mG_*&L(b0o9R2&D6P*74Hp`!dLHF&ol z_&q?yLVec#MvN^W-TmAz=|wF>zTr zc?HFbN>{I`scUFzUDr3bV`y~up0TBs^+Ow5J9}3*cMngmr`|!&gF`}JgoVeye3g)x z^!iP5W>$7iZeD)D`_i)Vipr|$n%d@;*0%PJ&tEzR28X^4kBp9uqcL;y3yVwNmsfC` zTiZLkd-(l>pL$UMl)tL=FU|g>7YnG@5h^N5D%ziVQ5^9EFG?0F>N7GlC$8wxTDY+C zpAR_7rW%(~(sWGV!fh=3gD3rT9D=fFA>2>Ze$njjDHix&((GS~{dc{_0R~D6aPTNu z02pwnpBXL+{Lfs-Q->L(^DAV45$Vr|cWoXXRxp>uAzz#sa8%FX{z6?*8YDT@#jkW> z-$krB1ahGOdW5;ms0&s;As;}vsV#4v!TuwReLk5;l*(2 z_i*WL%n=Y~qMoCLBhu@F{<8_KysQr1;WzG_DGp*ro+|e#zI|5w%p>JuE`#gr^dtj; zZ~S{k#HflKIbw>A2^$CO~2FpJ|uJ#MLL+awcw{M*f z>MNBHRZ00yxneJNUi9-j>dcq?k>P;xKl|f<()yX zE*V&n0dc&p$zi3e?l^N32NM}6L$;=~dp&e`T|v)X=Oaib9Aerbd%NSDwY59!{6}RC zRflzFhzy+8!3xp)YUY3f8Ak^0>+Z`+heCFiVWb2dlY!j#*SnXB^ajFh2gyLlHcsJ? zyQ&RFWbYsY9TI6+=XFbwXPib{%xfJun66SohJ2y_+)X2&lmeH<>ua0wT8+Gq!SF!s zBOOkXz>73`Kj%V()my&v@+P6FV#im;vpGsTpC>RB3Iw*Cj*Oli(X#WTKKx)LKd9m2 zEFKzd`}(|6TxBKaX?I2R;Db--@WLn#f_H|qBKqrm4ah)~DT4SU=FbhE zYQt3u`OrHx9#9Pu_ekr^w$ltd3~K}-13X!OZa`WF8J5%xIhEs`B^OW?n)B5aS_WNo z4eOCBOa}5DO#-QTyo~=?T;;2VB?D?T4fR!JoUB6S!k9ZTJqgu!M??~pUp=v(f*8g; z{{P7M>qB}wwtLJ)T_rIDC~5EjwoW^{f!91M+qKT>I6r)T)ZT)_Yd);<D3jre6Sj+%3;j~%wNR9%H4Z}^1I+GIzGi)LoO`-g$?Nb33| zm-~ki1^o_ZM>^Sf9SLg9A%?^UUI3saT)%hwFaye-d)Gl>06wW>ldZ$RdBJ3?OT6ql zy}G4%UiWPpPbrO?4!QUk$|*0(*4Q>1hx{+XDUTy;d<%9^NFeU`YVfGJq4tOvw7s_ zVSfcLebtCI9Zy$}tQO%h_v4kf8(J>5nw&$`e_5?k+z5{_z%QY7I5t`h_Y^n_rGlt9 z6*3+@ykFAImT{YN38pkJrMaZc=zU!8`^D7`V`#uve(#LsWOk_%#B; zuNnJ_Vo}ZT5bx;}bgDR`ih#9riYRyP+ZPjy@mIHGR)=Oi02lZrQe*n2Eu0CqI)FG7 zPyTfF$XmDkA8WqTPmO4e%hKqWjyHtW{R}H+ui>oq_t3q^NFz8cjE?-4QAI75qAL7r zms<5VJ|m@oec${Cv60Wz?@}+FT6~8UPAT#g#k1#{n0e(+ps6b}FpR1zEjLgfwI}Md zFa8it9MKItzu?=ZdNSYWwAKCl$$Qdx!xBXEA{ht{+TGhieT04VZ(3li^Y35UPuUuQ zNy%&^ci@rOq&v9Y@OoZM46|1%R0hM$$Xy<2lBx)w=wk2Wzh1-=f1Z2K{!WX_=hGql zpQMZmReavq#)x8L+QQL^YZ8rpS_?zP$T z&b+~gmlrE!5#i<+u}N!Wzz>(yu2jy6g)l_jo@uVE^}fQT`8kRbI%YFH8#VW%AoiKc z<(q9WYl8*f5sdYoXh(^TBr*3nUc)*4p$eZvrAuLVO^r3Emdm{J&ss{Idd%t3_m09$ zA;h2Vb2_!N3Kzc1j0`+4yPnp#EaAc!KU(kMui9D=Y|~Xd&~z{#t(YN3`IxAC5$}rA z23IfaN1~WT{?uxNw{)SzZmbP_CuYhp|*JT59X?;9G5(Gu85#w+RYE$=%(9PE$PLH`zod2p=jjc)n#U0ln7>qc>lU zbC_Y}#|eksk2+pSYK$IVS@vMpRYAxFv7<}F_%QWYO~qkwnRYCP(8{I*WWZUoZ~0(z zHIuNG<@#}GoD6i0)deJOZlwh&uB+u(Y^g4#dcACh9hX8q>bkq;f5v#!RCo4J6*FMi zC-BO|7-2i{V6@kLmY_|y{py3XuNO1Q122UIw>7sH3O30fJIQ3?{3y+3ynY0u z&@AcDia72DnN(N;q??W@h%O|<#QZwz+?QO zxb#q{1Gc5boFT1we6>v}YAT_1JQ(#hPJ_3w)0*z6;5+rlHQeFPG^s{p^1MbcVuKA* z_}d7Md#Erd6V}`q|9ttV|A>QJM)lo=XNs3+3p1=68}%a>a}KlIiY)c;b=VUfsv|UE zg{k@m$5&pCOHTLHnl40KyB=~jBPqXqE&6qFCiAn}=r^;vY|F`r$UMHYxI0bN!$iG& zJ6x~O@Pp%PcduNF;t(=W)87pKp(SKLL8@h|F}Onpif(E&LRjnFEwM+5Hn`$4>YhOS zy&WrTPvYCy55X2E)WlNGHZ9QI&EMzzN|~CHtRvkHVI@JlBG9ROQBaLsuOLla#q0o5 zc0&1_38V0)iJj9K0yH2(>*S`f{MLDId5!DqM0)J?xodbySPP5|A4)or%3hw4(I#jp zVJ+yvKY95$I^ETgYGd`~VEAV&9Gpi;yYZ#xQuP*d%f6Ntvpddf+Glcd-kf-SgI>8S z4AL9-z~O`%IivGZh;mrZR%g5&Dm<19GUv{ZxO+BQ!xUw^I()R{FqKK$nja3Mfonf@|KUS+`ol`XZx=Q0tL-hEiu;**t z40`_i0&wX?LeIx5fwTBDyKQ# z=pmr^)!~~zKfbFSL0wJ;LTl41kmL9ujhAp8iPoen9g|z9Yo~bY8)==-s>y_zysU=V zPvbTkQ(!Da1#DHYFRVZzcpjVcEqF2b>AYWoWOg~lgA?laeA^}x1RjfA{4Cc+>U%Q! z9a5$n?xq`NE?7QXA!*o-8UFTRZTPFhYUSdzgb6~XOH*)j_gS|WRob!49GdsK^RjPuG3eGrw0;o-swER>Guob@*cy0OV2os7Hr+cn z>^%rK*=74C;434#p$~_ooH5DhFQl_t0t|a}myABzqJU&FVEkML``ILWFP zKh8wl>o;mtN=&uO>X5zBCHHA9HrNZ}7v8SnxS}m)YV@sQ9>L_#hTm$G$g(@{tHae1 zESsPyxt7q>-{7W~YAfYzGsCaaiDo|gC)dUF7am}Djs9n<%p~PJ5$LYgf;DlbaEkx!tM)aB>8uH@bd8w-QQj`(C+%!k3AHF*43f4iH7{+cF>0=%Ad z5hS-^Wp=|@GVl;e@-cP{(}=esoqs)1*KlDvYn@l&u%q{2^}zRQVq&=X*ZUwmrA);K zkO6+DDl%ZMkN8f55DlMuHe|BuAWWFSt#1dJtOsBKC(EvYsAh(DKC*{(5`sj!F$#TJ*mpPaZcIK9Fp zRKC@z*8PsdQ?;j&@?{_0_jf5TOM1+5*j>{>k9dtokpVT6U1Kn^gm3+pSIOWIr0}Y> z3J`&?FM;U@G8=_{LpqM=7sG0hSSt}krx^T0#B<2@bzc^6#a;(r#9chCwWsKpqtelX z!VW?>$-wtQ|1LOQ&m8eHRmxS7CIkJ3hlwa`WNHLd;iKl1isEl}qnBI@zxkCjC=;Op!9_#-VLE{ve2?6hT-Mn~_%A5M-qsJ8D zE)ZHO9*CZXAR)9oZ*+f<#AOL)sfX;&V-941PNJ7|QG+Pk0V1aZ@tM*Q5}Q#v@kv8G zXg?eL6*6!wuL**`IR<8hwXi=KDq3rg*-p1)3p!J-+vZT1;uGdC;@>&Y!n1jrsA4C7~x|g$iVg;lr@qliiF`8{Dl5$5=6@d;%QKC{n;3z$RjXwp5l4= zgKr?+a&hJ_&qMw)5(nC(Q|V~h!xQOL2=pZzGSL40KPYigWK#M6w~81Do#b^o-i={_~rU{3xDJZ>5(y6&>X|*6y-F*XEJ$mG7)f6o)Rk+Hj zMoE7@uZNJ2lgkez<;SR_R+G{uSVTAHA5ci4+}<+8+UvgP*HCC711ueq4H=%?b|Vz; z1`u!{4OAM=>IeZE(lkd3pA-hy$;H4_w8H}he$-uI~x1Ekw z=6zOo)WK#78MrLB30pfx284sLGo()yJuwyEV!`yLxdcj^z1jRhk(UvJ)6IdArn{R;Gi%ZyDMpB#=R0zJjQrGphNC|=n^ zL#M>)>olXn#A==oLNm_gEp&2zS&r*@IaL8qMWaWLgp z>eq?gXG-cF6<(-j=CL`wq**cm#y)C%$F-o#6}XTpA9p)D>Vc^b99v`K*^eNCNzt04 z)*rRisu|e35Eg}Iu5BvEhy8`Tat8bmt&)zpg8dEKK4vOs9`Q0~zl@H zv`*U$8~3?fJ4ic5gyF=b9Obmt_8;gNk5kGjTvwt^Jh>2si)s@P5!6Rnr37O)f+{vb z_MWR1dx;p=mnL(d=PQz*N=Zrqr1`k()hhW@O$817*P)ICZQ^)NA~?{zJ*yGBJH{po|x2xA30AQEU<*;N+qF4 z5B+joN7wi5TRooK%ys9&WiQXYYde#EM-QH!ezYF&6(G3cd(p5E=sk=sCs9s?P8!n% zVX=>g3sn#|%Ejv*mzFznd!rZD-Z4Ku41nsrcR-XK*c$f`Yl{nJr)+Lvv#SPAn7fz1 z{cuC;2fqvwQMZ~59NYva+>yU|F_~a)>0`HA>!LFY4pUrTW;09&EL8di#Jxe(^-i}z z7*%6J)41r|Ids=19`Tw!*yA&v>pi8IM3_8W>&HngyMbWeU%1+#u#PQ4rzWoW1E+iI z>1!RnR|TZ1J0qhnXXEYMYC~Ik873vaO>JuDnrK!TtyW_0KhST`dl~v@Aw*Yj$S*4o zZn1#qTglH1FMBPT84fJ@pGJI6DIxmX&0)p6vI=Vso~Wl#zkw z9Jao0>kiDhrtnTz~x*_LuI8G(P&>Hv5~hWUcD0K@5^ye;7-k zk^i#jZ-Qq1S-68CAKL#_g6k135=}%%+BU7QU#;r*P&sHN{)df!3mk{TNE1%ELk{`A z9k`%p_Gzz@)_tuB=SV%9MBy?H)s+oBJ1MKWn0Ncbftrx$0^zB;QJ*_bTciLiyFEeO` zb@3&U-F#=F!ZwE)!`LUsjM*%|5vwP8gF}r8*enoi7S@y^V(NZO&aHvMUIX=mtB+lbg z`G~7*A$K4JQ_1U=BNS9D?Gm5C32}=wkiPBC2>Jv;hYVmqoEvxRq*!N1&5s#yLNk`-& zwS_e$D=zE3wE-x;Q~Sx#@5hDgd|>?Yb5&VX;hz>x1=If4q}wY`zL#9doxDE&N@hRa zm^vyf!3h6GrkZJ954(bsg8*JmE0Q@PS`N(SgvJXuDQbzh9E z=in(@JssYI&|a<~)kX9hG)Q5LuD#>yW~0!qrAHr=OSlTpOl1KGmCY&(4{Jxb-0SlU zHW^?Jl3eMRx=yprq)up_{3@=}Kj=IO?>!(DyR+%3-RlPb0|Z1Zf()Ewy`*lMQ4qxs z*(l=3z|pC6A`adegI5O`$)#}89i$pyg=kg)b3&-mW)_L14z`a$B{=k7J_W%>fw^rj zJ24+j_MLi3UdRRG?OQY&L;(-PRvSVbhUNyd^)!in!%zPQy?XaR`BP1&IxX5LK{b-j zosDACh{!HYx)FmB836;rU_z1i`cq~i`-PuPkl&kjJkemC#ZslWJ8CIhOdx9hTD+>N z#5w90rxbbdi}61Bj=m@*Tg(=ZddjHOqB5IJfl30F_Js=X_uUyP6-BR|S(6x!yS|!3 z!UGleXlw>ry}R#Ambu5caz2!Zn!t_$mIL+Lvp!@X5>{T-rYv%&pD2L2>lPs?KcoE- z=XSf(@xx-}jNtD3tfR)R;^l>*G_t2ovJZYG@TB#4l0Yi-!s>eowC?dqJYm)*5(+~X zLGD$JGzfTwHgVK)Bo(|mf7L}fH+%jwp=sK(qWX#U39Xyni=QHP)p6M%xRwq>uoPw5 zO78S%=mea3te|Q!@Tfa3?Yu{qY+=0a>1Y|9n?IV})@qO$YcMw7r_R_G^vDaMnY)9~ zPGy9J({0-y@B+6We}=9x8x-K4sTp6g$o zWgaSs#yM_I-F0G=P0>D$j+W@#j(O%!I}8IUAHK0Eyt7WaDj(lqg+vsGiGhQ5;j)ud zQ%&M|`MPHi>VSorv0IF0e|R)kojy?+=tbDaz0uQ_p?hZ) z{}@2V5%p#r(!=28sHpmjKAB!H6A;MJ--x-^Uw@A(zJ>Zjp>&G!hwJho=f%`xeY+Og zDJYBm_L|DBj=9!yLY9a}Swwtr` z%MrZN==}`a>Az!=xcjZ6OvO4*QIxd#roQu4zz(Abmwa9OklPZHSGBRI7euYYAWP^G z&Fz8Mq)3wjLW;45IA`t;i7}LQZ%7&=)~4C{*b0fOPqsBYLLT}E!4y?@Q8@|=W| z2mI641)j9|zU+x(V6scRgpYbp)WQc~=JHycnFGp?&8USh_ebs>L_SjK`|cXR0AH!) z{mK(~WVWmcWJTp(lg@c1p~*n_yNc*FRd*ya-1v;A^+JbIp;uaix`!iGbu`qkJoUg)W@0Y-8-1(Fsj0=CBL38HuNTI0o^n^wLk;A{>9 zoj{^ayI=ZwPOS>KZ70xk@*-Xr%{H|eaV3BZ9FaSKSb$FSWgGdm7cb&Tj7Ngx!^k@_ z&==(&Hj3yd^^<5;d0qx)khF3*rk(N`3a{{W=h=ZYMiq=2swSc*di291-;Nu+oz;^@ zw5KiE(wU`AZet;#PU9rTJ4=J>^9}^t`urc->UrJQ8pn%Ln;xHIlF`PoO3cEJmrmXt zi_4p2aVf}mxC_!nGqR0=43>oqZZt8WPn{|7Pm>qhv8gJPDN!=2<6j6B`xyWrplo27 z!OnX#McM*!At&=Up8B8eF{7ME;>F>_H`1-JVvu#olQ7;H--O`Bor!NC|7ya6^eN|| zab6Ti!(kA+?jxZuqt>FKH`wCqNDbTmfj!uz<~=Tsyv(k5cAIuese?1-W3qG)s;i$; zAtjLNmZurCHhOHl_GUW*{6)?0P2mfh$DL|CALc4bIc3loT>YXmN0lKMpFMkE)P%sk zi#g=_y(>3Fb0BQnJ`DtaEny%e9WhcS^)8V#M9QkhiKy;Fic-8aTJro>%buY>_dt;p z6vmKva4puDpZjRGS#j*FZ$Ky&Q-x3SG(%rKi;cU(J39_j*SUm-v_yQ zx}#vi_iryHih{eO)C=YiM8)*ORG2NnA2HAO1);D&vYFz89Hx`Fy>9OyWf4@i$?k)u zIWsBF55v2}Rkp=lHlLA!b9&FX-#$#SASe?r4AdJoh?HTaf*mmIH4aS$Z4V+I3qR1- zLYeT~_7X*<4=QxIL6qGJW@SI2H8mM9{Jm>k*d7A52@%;tNpGp_;x?K#sEB%L zod|Zk<;@YPvsLsC5&2gVPtQEP(|%IyM@`cYaZ(&O=;IW9U@096%UzyR4RP>0YdoI) zp!1@~e&O+3@}Z(RB|`zp!~?J^D#Z71P>|>gWd(1~BC)Z68_5iCC=!n@>1#U79NtR| z8Y`*+`-Z8>BI%d8c#I=GxEzbJy`#F5lET<7(}!huyb*}OJZ$`;>~hzf5)Ye225-&i zpmiDSYN^(ns#>G2hdI?lhVA&@L+1rc1WkZVk#rq$FIl59e4%Ub&^>Q{$ zA)N|p_c$V<1z*37Cj;G@kDqT?&1_K&MdRB<^d_8pi6I3N8~cbiKfvH{b7Nn~92{fh z6*7>)b;$aW48&L+7%kZlS?fqzM$gH>Y9HdeI_laQY;H^?VS%sd;ZJ7R5~H$#HzEm6 z9H!P0l;$84iUcNKkc5ee#4R8YP{$q!lLDd{g`M75Tb~&l-Y2zpjFv4Ri9T~N5`@=t zPGU1G6kwDLQ~MM(rsUUn-2M^AFnp@2OGc35)b|eMV_E1?Fv>C&RF#wq+^nKulnFBK z0E4K-=~zo%wxxGq5RKV)-BL0Z&HS+RPwJPy->aept|z&OFLA`H2g|5~ClyF@w+c_gg z2?p(V%1%9$tfL%kfO)m(^}BaPI6^R~dm%fDvZ#tC^b~6geUa%U8pMpLc#CWx&Aw3p zpM1?UBD65oc!{&jvt+}~sI7tjFiS;X)%@s3oO;6haobzQM1=}m(R#hS%U3C>=f!-~ zTNGkE0h)p*Iyvc{crrS(`nuzTXLYpiVXa9Y$E#jAL$&kl5)X1h!bM){hRoem@Z{&V zZQ6?TH;v#s>{z1i3cjeE2`P=+SzyOND2NtwI#=6B0(}*`IGUPwR~($5=Hf$J`5>e( z)pn;+A6CXuxnw0wa2N73@dU+7Osovn*1S!U_^1b$#$Vm7_=JN*xS5?Hs$m1&Q4N88 zZMpc)MV~xo{^B!{W1>%){O_E7_m&}YvEg0LDX~We@8)VkF(_`lPh(=Dr@6rJ2e(TO zUD+j-i6r;1ht-d-U(1O(t6D-idSJNV>qB^mxKGr?2RO{~TAB@(-x;Vs^L}->H-B2g z30ZJMrN^SYj`@vdwbRAr!6385BwTMRf?bw5s8;v69Jd*_5iTuMs;pp@)i~m%1!KL- z*)Fz6s^wA8CW=SFbn^wX23srr{=HK-_Lz_2S2b|GyCb`5Et3e#71w3XlsT(0SB@DO zb&bU zAmKL{rm#*qhh(-xvCGy$Sf;%)SFcv~JD2;Jw5sqasr3`?pabt_2fqum(oI@Ou%u*o zd^a+<*E-)vexhX8jD0gL6vvHmikl<&DC@VgI4G&7%zY*bL2a}HIfqX8Fjth-=)KyE zEIDf-6I)bYa}i>Ar`@#uNsOqT8vC_iHoeQ#uHe?#`4oH^sYwBGAheoO?0|75zC>^! zMuj)PYU(Fui=zH5+W$Pw_rX+@3JeeNchIndZO8!&vi}k@epffgOQSCl+&2I$`k%Qc zpEC<%F&0jz`LMOT$08Zr-C*D;lx49{H}7cp72F3vdoc^W{?e|261!fwjJ1=dNq1(w zYofi_@#va8wA?qfvwECc387?GS(rhCl?){8PdYBle~`4)^i2g%e)I4;E-6y6U< zU?*Tlz}TN_i`c`V@X0WAr9X%W`fq?M`orWp9vp8XuY`=@IYCPJ!c{VW0#RjaKe#o#3@c3ve+aIL2oR-&W$pR3 z5vIF0d@0b*>|Redgm4;B*|-76ndj(Yj|pYcMq9aMa3Kp`;9rKclql}BGI3z_B*HGo>$6`QK@Y^nL5GI7hleD8{QTfjv8eB^zD zQvXieil@riAvxs974P;kjE|!dJz?yA7$EPd?$xbV%+3~Avd)|2a<;o0+=0;YhzQm- zGT&s7ff*3+Vr>7zh!7gkgz4TN3b}Y@3;7C{{M6g?&}o^7wgqTK zR$rQD{0)m5_d*}aPZH+&gBOU4!5%2eAeaPs=-;=bL4-F31?#iEY zy^$0|%Ol@k6wO5ZqNf9G2N|feX;Rj0OcN-pz{h0iy1V2~wy|AbPx1&04A|Te3{O(r z6I1mZ1+m&}>dfZ#dJz4a2ay3)xdRc4|3QM-t~}5K21}lfR)=dt{t$fhu+Dk2hZnae zUZm)R=>8-SKgviT=v6#|K>je4BP`DshF$pJZB;A#*sc;Ml>$w(N(`{P#s82be8W+s zhAo{{G7*G>OJPOh!<&b~gHDzXCNvW+=VZ=}u{5<*KF*_!7scB>s)R5@FE}MuB_3l+ zapW4Gsa$<=IZXY_O&T+o5JjFKy`g0g_HpkhC4=~8@psJfPk#T7Z{mG_!#BlCB=#iG zDBtzLXn{2YQ7Nmb!Y{ns%YWzPEI?iklUn0%1A|oJ~lZydez~)8lQ!2&@o=T+QgYB#Mv{z z1$NpuLnwRh6zS*2;FhJAjkVAB_)a#|eZ=ua?cz_E$*qsLMe?MU7So-7wGb2(9?`~? z-;-AGHzYQdL#_k64OGL#r%1fvo+?%+HGF0iF4wI;WiQ%s^u*M~tHS#CAMV$;eQk;5 z$1Zg^dMvfY;o+?_%+B=^NP8u-_JT~4FZoR_e3@q^7B5@fIGPKWIiDv^Y2(ty873b$ zgJ>?rA`a=iAtZWms{Wj&!Pa15|A1{9M&txzXX{9?F9RNl6KI98H3*E_ryU!z9>n0- zIKJ8pucyjt_N3OxJfGT%dHsb@KVP$v{Jq|Ya9jJ`7mTA}OJi+r#Vnj|MkT!nAJ3g1 z+ah?4J~3o6bxTkXJ?gk%D(aX$a6774KN4v<1Ulh8FBmCU860V9aL948f0E20Bhc%| zyJ%Z8>u@!u=)5zpA_Ez=|K2M(u6Ve@u^;Lo z;LJ?}9WD@bJaCQsqZV=l{c5phZ%2p{(zGDalLy*(wiZGXuIUYfFcO7mTTpCYAi?E# z=XJ!F6Gn8Ov-5K_(K>po{SgeU7#m6|xcP$a{U1S&zZ;?nY7?fyfmh?YX5^ropbgwS zxx{|Jowp)iI$^R_6HM&8W^G05uQWFTqUS4J0@^A=Hp=faR}7=d9J@t)*luX5SWYrM zm1Vry7tFuNe~MVaf29lI=0mt$y)QJ{jDK|*{}gk3W?TqX=Hz-9>*B5PYHX8-=I@k`WA|6gSl146mqU;}l-C?t{lXig^v(C#j9`uKYS!Fc?2kbICVTA9t@idr(F6zs}$gl1@raDF54rqHbmW^YeC;^XYhTn}4UBc2<5eQGaZz$=b^B+AH^YRlKce-RrVp zHVQ9)I*KaWf~Y;^Bwkn;Ib=t?n8C|~!6|NelL*u%1b~yK5XX4#eYK7upa|HIln>ro z5t7&I)*f#bFR|j1Oh#mx$Meu_Bcb)@NfJXa^r>O}H8CIdj5`A)V>pSuL_O!76DkZ_7)}Ul4DO%er5}(ALo@W4^3rBnJI44871a zt`Mcd>uZbKOPXD-Krwoi-K!Wvj}%qV+s60!cQLRE_SJ=H=^wKwxqA-Q>=4p`cgDdq zu&`D{FqcuVBhn4dwxaa%u2)ILNz2O8(jhC)*SZwW7js_dh*Gc>TKHQd3q^B2hpxjP zB!*?Ty!$@PWnDSvZ0*cj?h}1;=vEf&Sz*N@xLsVr1;gmHrKkIXbaB9NQ3um?b0{Nv z8nUZ*Loi@RxD16(mqL9nB&vgF^2#B^m^+@MDNobt)SmDGct7a?m6=6+S*yP&!6+#P zuPHT`{&}MA(ERftca`hgCHw6*_wD|(VBPP*zgD}yU<7Sb@EqU1FbUk+_fcnt6z0v> zVv;obHYT+fo!xN~XQn42rLOO<*xE$K@utha| zg2b|$rgPy@DWpsmzopsbT1UBIGr^A2AhM!<5+A7Fc#lUK2TFbtjV=+MwgJ=?a`GaNVw2|v>>YF$UWwlmtxJ@P2x3qc%qf`XYprdUrVm7&-I^2I0C7ro`a3-vL2swSnms|MBate=?%r!8_rks8lAYo(e#dVF1}dL(?m z)YbPq6*%XSBGGTRi}6xT0ev2Mu3E`#yhZZcpi9dw}3 z;T)Vi5(rgwLX^tMaK3%Cu(?2U@wS0@D~~S@X$e1$FWVIz+9p)#z%Z~@k4Qdf?}-~7 z`$o&qxGrOd`0_HHX{7&@n%0D>Y7V9|@bj z(K3=KkO=>^n8{Q6OEFW=bJDc1WIqAi-{J$FJQYQO7!{kD9D}!<0S|Vn;lXtvnC;sE zSS&iako0RQRpTrQtfU&4?J?aMC3b=^SQ$KS%LWHGujuJ${IUN-5XC%y()F{J3f577 z?OKWuD!%4#jcDNmkFJJ;Bsx(vH;Z&~4?G&G=YIxVHr3?(lR@b(f&}8PHQFxepEX)e zzm^0K*m=|+R08v&Kgf;JL7;!v>|Y{BG@v`{AaCHhh*ZS#|0p>;7lnV`qGmjU{V!DxkFvidyBu5Uy@xK`a@w>>G8`V#WI~I-pXp6r!78i{ywmcz@hh`| z65HVPoA|}9;9JHJy+XwNx*sZcM)y;8?eYBS^ySJIG|IKqQX^GGyL)Or^M2VuzAs|6 m5?7|a;+T8IaqqZv-<`g5VhkVw_>ayn{41*b_8g2n_TK=Oixpu2 literal 0 HcmV?d00001 diff --git a/Figure/chapter6/6-2.jpg b/Figure/chapter6/6-2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3e1c70d2c35cf5b26eaff0b604064905b7b74768 GIT binary patch literal 24232 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 Dq4pDT literal 0 HcmV?d00001 diff --git a/Figure/chapter6/6-3.jpg b/Figure/chapter6/6-3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0bda99d3a90c9c3e778fb630f44c5fdc4e47d4ff GIT binary patch literal 26729 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 XX|s~W{GVul6Q=kUTbzBwzV7-L%E=|M literal 0 HcmV?d00001 From 6bf9b83f5a570cc257e333e7d5906d0809745a44 Mon Sep 17 00:00:00 2001 From: TooBug Date: Wed, 4 Jul 2012 13:31:47 +0800 Subject: [PATCH 032/258] =?UTF-8?q?=E7=B1=BB=E5=BC=8F=E7=BB=A7=E6=89=BF2?= =?UTF-8?q?=E2=80=94=E2=80=94=E5=80=9F=E7=94=A8=E6=9E=84=E9=80=A0=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E7=AC=AC=E4=B8=80=E5=B0=8F=E8=8A=82=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 56 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/chapter6.markdown b/chapter6.markdown index ac86869..09df7d8 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -38,20 +38,20 @@ 下面是定义两个构造函数Parent()和Child()的例子: - // the parent constructor + //parent构造函数 function Parent(name) { this.name = name || 'Adam'; } - // adding functionality to the prototype + //给原型增加方法 Parent.prototype.say = function () { return this.name; }; - // empty child constructor + //空的child构造函数 function Child(name) {} - // inheritance magic happens here + //继承 inherit(Child, Parent); 上面的代码定义了两个构造函数Parent()和Child(),say()方法被添加到了Parent()构建函数的原型(prototype)中,inherit()函数完成了继承的工作。inherit()函数并不是原生提供的,需要自己实现。让我们来看一看比较大众的实现它的几种方法。 @@ -118,4 +118,50 @@ Child()构造函数是空的,也没有属性添加到Child.prototype上,这 var s = new Child('Seth'); s.say(); // "Adam" -这并不是我们期望的结果。事实上传递参数给父构造函数是可能的,但这样需要在每次需要一个子对象时再做一次继承,很不方便,因为需要不断地创建父对象。 \ No newline at end of file +这并不是我们期望的结果。事实上传递参数给父构造函数是可能的,但这样需要在每次需要一个子对象时再做一次继承,很不方便,因为需要不断地创建父对象。 + + +## 类式继承2——借用构造函数 + +下面这种模式解决了从子对象传递参数到父对象的问题。它借用了父对象的构造函数,将子对象绑定到this,同时传入参数: + + function Child(a, c, b, d) { + Parent.apply(this, arguments); + } + +使用这种模式时,只能继承在父对象的构造函数中添加到this的属性,不能继承原型上的成员。 + +使用借用构造函数的模式,子对象通过复制的方式继承父对象的成员,而不是像类式继承1中那样获得引用。下面的例子展示了这两者的不同: + + //父构造函数 + function Article() { + this.tags = ['js', 'css']; + } + var article = new Article(); + + //BlogPost通过类式继承1(默认模式)从article继承 + function BlogPost() {} + BlogPost.prototype = article; + var blog = new BlogPost(); + //注意你不需要使用`new Article()`,因为已经有一个实例了 + + //StaticPage通过借用构造函数的方式从Article继承 + function StaticPage() { + Article.call(this); + } + var page = new StaticPage(); + + alert(article.hasOwnProperty('tags')); // true + alert(blog.hasOwnProperty('tags')); // false + alert(page.hasOwnProperty('tags')); // true + +在上面的代码片段中,Article()被两种方式分别继承。默认模式使blog可以通过原型链访问到tags属性,所以它自己并没有tags属性,hasOwnProperty()返回false。page对象有自己的tags属性,因为它是使用借用构造函数的方式继承,复制(而不是引用)了tags属性。 + +注意在修改继承后的tags属性时的不同: + + blog.tags.push('html'); + page.tags.push('php'); + alert(article.tags.join(', ')); // "js, css, html" + +在这个例子中,blog对象修改了tags属性,同时,它也修改了父对象,因为实际上blog.tags和article.tags是引向同一个数组。而对pages.tags的修改并不影响父对象article,因为pages.tags在继承的时候是一份独立的拷贝。 + From 00805e5f0adef8e1bd482f2bdd0735ea44235b8c Mon Sep 17 00:00:00 2001 From: TooBug Date: Thu, 5 Jul 2012 13:03:51 +0800 Subject: [PATCH 033/258] =?UTF-8?q?=E7=B1=BB=E5=BC=8F=E7=BB=A7=E6=89=BF2?= =?UTF-8?q?=E2=80=94=E2=80=94=E5=80=9F=E7=94=A8=E6=9E=84=E9=80=A0=E5=87=BD?= =?UTF-8?q?=E6=95=B0=20=E6=8F=92=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Figure/chapter6/6-4.jpg | Bin 0 -> 36936 bytes Figure/chapter6/6-5.jpg | Bin 0 -> 4550 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Figure/chapter6/6-4.jpg create mode 100644 Figure/chapter6/6-5.jpg diff --git a/Figure/chapter6/6-4.jpg b/Figure/chapter6/6-4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b42531b8cffab9dc8578e73b08ad1311bd1d10f6 GIT binary patch literal 36936 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 literal 0 HcmV?d00001 diff --git a/Figure/chapter6/6-5.jpg b/Figure/chapter6/6-5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0325547e0d9e51ea09faf180bf788f334aaa6306 GIT binary patch literal 4550 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 literal 0 HcmV?d00001 From ac36271a79411f1d432dfbb4b70b2c13c6284e8d Mon Sep 17 00:00:00 2001 From: TooBug Date: Thu, 5 Jul 2012 13:06:53 +0800 Subject: [PATCH 034/258] =?UTF-8?q?=E7=B1=BB=E5=BC=8F=E7=BB=A7=E6=89=BF2?= =?UTF-8?q?=E2=80=94=E2=80=94=E5=80=9F=E7=94=A8=E6=9E=84=E9=80=A0=E5=87=BD?= =?UTF-8?q?=E6=95=B0=20=E4=B8=80=E8=8A=82=E7=BF=BB=E8=AF=91=E5=AE=8C?= =?UTF-8?q?=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 69 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/chapter6.markdown b/chapter6.markdown index 09df7d8..2753b17 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -165,3 +165,72 @@ Child()构造函数是空的,也没有属性添加到Child.prototype上,这 在这个例子中,blog对象修改了tags属性,同时,它也修改了父对象,因为实际上blog.tags和article.tags是引向同一个数组。而对pages.tags的修改并不影响父对象article,因为pages.tags在继承的时候是一份独立的拷贝。 + +### 原型链 + +我们来看一下当我们使用熟悉的Parent()和Child()构造函数和这种继承模式时原型链是什么样的。为了使用这种继承模式,Child()有明显变化: + + //父构造函数 + function Parent(name) { + this.name = name || 'Adam'; + } + + //在原型上添加方法 + Parent.prototype.say = function () { + return this.name; + }; + + //子构造函数 + function Child(name) { + Parent.apply(this, arguments); + } + + var kid = new Child("Patrick"); + kid.name; // "Patrick" + typeof kid.say; // "undefined" + +如果看一下图6-4,就能发现new Child对象和Parent之间不再有链接。这是因为Child.prototype根本就没有被使用,它指向一个空对象。使用这种模式,kid拥有了自己的name属性,但是并没有继承say()方法,如果尝试调用它的话会出错。这种继承方式只是一种一次性地将父对象的属性复制为子对象的属性,并没有__proto__链接。 + +![图6-4 使用借用构造函数模式时没有被关联的原型链](./Figure/chapter6/6-4.jpg) + +图6-4 使用借用构造函数模式时没有被关联的原型链 + + +### 利用借用构造函数模式实现多继承 + +使用借用构造函数模式,可以通过借用多个构造函数的方式来实现多继承: + + function Cat() { + this.legs = 4; + this.say = function () { + return "meaowww"; + } + } + + function Bird() { + this.wings = 2; + this.fly = true; + } + + function CatWings() { + Cat.apply(this); + Bird.apply(this); + } + + var jane = new CatWings(); + console.dir(jane); + +结果如图6-5,任何重复的属性都会以最后的一个值为准。 + +![图6-5 在Firebug中查看CatWings对象](./Figure/chapter6/6-5.jpg) + +图6-5 在Firebug中查看CatWings对象 + + +### 借用构造函数的利与弊 + +这种模式的一个明显的弊端就是无法继承原型。如前面所说,原型往往是添加可复用的方法和属性的地方,这样就不用在每个实例中再创建一遍。 + +这种模式的一个好处是获得了父对象自己成员的拷贝,不存在子对象意外改写父对象属性的风险。 + +那么,在上一个例子中,怎样使一个子对象也能够继承原型属性呢?怎样能使kid可以访问到say()方法呢?下一种继承模式解决了这个问题。 From 0765f5e048dabed17a871b74542ab842f2cc379e Mon Sep 17 00:00:00 2001 From: TooBug Date: Thu, 5 Jul 2012 13:24:59 +0800 Subject: [PATCH 035/258] =?UTF-8?q?=E7=B1=BB=E5=BC=8F=E7=BB=A7=E6=89=BF3?= =?UTF-8?q?=E2=80=94=E2=80=94=E5=80=9F=E7=94=A8=E5=B9=B6=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E5=8E=9F=E5=9E=8B=20=E6=8F=92=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Figure/chapter6/6-6.jpg | Bin 0 -> 30972 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Figure/chapter6/6-6.jpg diff --git a/Figure/chapter6/6-6.jpg b/Figure/chapter6/6-6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..78926190d4ee8efedc04412eaac6eb391227c809 GIT binary patch literal 30972 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 Date: Thu, 5 Jul 2012 13:25:11 +0800 Subject: [PATCH 036/258] =?UTF-8?q?=E7=B1=BB=E5=BC=8F=E7=BB=A7=E6=89=BF3?= =?UTF-8?q?=E2=80=94=E2=80=94=E5=80=9F=E7=94=A8=E5=B9=B6=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E5=8E=9F=E5=9E=8B=20=E4=B8=80=E8=8A=82=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/chapter6.markdown b/chapter6.markdown index 2753b17..13b2867 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -234,3 +234,49 @@ Child()构造函数是空的,也没有属性添加到Child.prototype上,这 这种模式的一个好处是获得了父对象自己成员的拷贝,不存在子对象意外改写父对象属性的风险。 那么,在上一个例子中,怎样使一个子对象也能够继承原型属性呢?怎样能使kid可以访问到say()方法呢?下一种继承模式解决了这个问题。 + + +## 类式继承3——借用并设置原型 + +综合以上两种模式,首先借用父对象的构造函数,然后将子对象的原型设置为父对象的一个新实例: + + function Child(a, c, b, d) { + Parent.apply(this, arguments); + } + Child.prototype = new Parent(); + +这样做的好处是子对象获得了父对象自己的成员,也获得了父对象中可复用的(在原型中实现的)方法。子对象也可以传递任何参数给父构造函数。这种行为可能是最接近Java的,子对象继承了父对象的所有东西,同时可以安全地修改自己的属性而不用担心修改到父对象。 + +一个弊端是父构造函数被调用了两次,所以不是很高效。最后,(父对象)自己的属性(比如这个例子中的name)也被继承了两次。 + +我们来看一下代码并做一些测试: + + //父构造函数 + function Parent(name) { + this.name = name || 'Adam'; + } + + //在原型上添加方法 + Parent.prototype.say = function () { + return this.name; + }; + + //子构造函数 + function Child(name) { + Parent.apply(this, arguments); + } + Child.prototype = new Parent(); + + var kid = new Child("Patrick"); + kid.name; // "Patrick" + kid.say(); // "Patrick" + delete kid.name; + kid.say(); // "Adam" + +跟前一种模式不一样,现在say()方法被正确地继承了。可以看到name也被继承了两次,在删除掉自己的拷贝后,在原型链上的另一个就被暴露出来了。 + +图6-6展示了这些对象之间的关系。这些关系有点像图6-3中展示的,但是获得这种关系的方法是不一样的。 + +![图6-6 除了继承“自己的属性”外,原型链也被保留了](./Figure/chapter6/6-6.jpg) + +图6-6 除了继承“自己的属性”外,原型链也被保留了 From 1d95c0eae5385ea4cd940a716d4aabebf5b10218 Mon Sep 17 00:00:00 2001 From: TooBug Date: Thu, 5 Jul 2012 13:29:25 +0800 Subject: [PATCH 037/258] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E6=A0=87=E9=A2=98=E5=92=8C=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.markdown | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.markdown b/README.markdown index 8a6b8b0..84d5942 100644 --- a/README.markdown +++ b/README.markdown @@ -144,14 +144,14 @@ - [类式继承 vs 现代继承模式](javascript.patterns/blob/master/chapter6.markdown#a) - [类式继承的期望结果](javascript.patterns/blob/master/chapter6.markdown#a) -- 经典模式 1 ——默认模式 - - 使用原型链 - - 模式 1 的缺陷 -- 经典模式 2 ——借用构造器 - - 原型连 - - 通过借用构造函数实现多重继承 - - 借用构造器模式的利弊 -- 经典模式 3 ——借用并设置原型 +- [类式继承 1 ——默认模式](javascript.patterns/blob/master/chapter6.markdown#a) + - [跟踪原型链](javascript.patterns/blob/master/chapter6.markdown#a) + - [这种模式的缺点](javascript.patterns/blob/master/chapter6.markdown#a) +- [类式继承 2 ——借用构造函数](javascript.patterns/blob/master/chapter6.markdown#a) + - [原型链](javascript.patterns/blob/master/chapter6.markdown#a) + - [通过借用构造函数实现多继承](javascript.patterns/blob/master/chapter6.markdown#a) + - [借用构造器模式的利与弊](javascript.patterns/blob/master/chapter6.markdown#a) +- [类式继承 3 ——借用并设置原型](javascript.patterns/blob/master/chapter6.markdown#a) - 经典模式 4 ——共享原型 - 经典模式 5 —— 临时构造器 - 存储父类 From eb5e18bb4422bbf2fd15f10f7ce1073ea6ea8163 Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 10 Jul 2012 13:03:05 +0800 Subject: [PATCH 038/258] =?UTF-8?q?=E7=B1=BB=E5=BC=8F=E7=BB=A7=E6=89=BF4?= =?UTF-8?q?=E2=80=94=E2=80=94=E5=85=B1=E4=BA=AB=E5=8E=9F=E5=9E=8B=20?= =?UTF-8?q?=E6=8F=92=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Figure/chapter6/6-7.jpg | Bin 0 -> 25223 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Figure/chapter6/6-7.jpg diff --git a/Figure/chapter6/6-7.jpg b/Figure/chapter6/6-7.jpg new file mode 100644 index 0000000000000000000000000000000000000000..461949ddb5709834b5ae30a56e8c8f5e59830bba GIT binary patch literal 25223 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 literal 0 HcmV?d00001 From 781989e178c5b2fd2233056579f5c0f49e437d89 Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 10 Jul 2012 13:03:38 +0800 Subject: [PATCH 039/258] =?UTF-8?q?=E7=B1=BB=E5=BC=8F=E7=BB=A7=E6=89=BF4?= =?UTF-8?q?=E2=80=94=E2=80=94=E5=85=B1=E4=BA=AB=E5=8E=9F=E5=9E=8B=20?= =?UTF-8?q?=E4=B8=80=E8=8A=82=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/chapter6.markdown b/chapter6.markdown index 13b2867..69de815 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -280,3 +280,23 @@ Child()构造函数是空的,也没有属性添加到Child.prototype上,这 ![图6-6 除了继承“自己的属性”外,原型链也被保留了](./Figure/chapter6/6-6.jpg) 图6-6 除了继承“自己的属性”外,原型链也被保留了 + + +## 类式继承4——共享原型 + +不像前一种类式继承模式需要调用两次父构造函数,下面这种模式根本不会涉及到调用父构造函数的问题。 + +一般的经验是将可复用的成员放入原型中而不是this。从继承的角度来看,则是任何应该被继承的成员都应该放入原型中。这样你只需要设定子对象的原型和父对象的原型一样即可: + + function inherit(C, P) { + C.prototype = P.prototype; + } + +这种模式的原型链很短并且查找很快,因为所有的对象实际上共享着同一个原型。但是这样也有弊端,那就是如果子对象或者在继承关系中的某个地方的任何一个子对象修改这个原型,将影响所有的继承关系中的父对象。(译注:这里应该是指会影响到所有从这个原型中继承的对象。) + +如图6-7,子对象和父对象共享同一个原型,都可以访问say()方法。但是,子对象不继承name属性。 + +![图6-7 (父子对象)共享原型时的关系](./Figure/chapter6/6-7.jpg) + +图6-7 (父子对象)共享原型时的关系 + From 31367c538a123c5afb074e2e6c5a03878d07c549 Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 10 Jul 2012 13:20:00 +0800 Subject: [PATCH 040/258] =?UTF-8?q?=E7=B1=BB=E5=BC=8F=E7=BB=A7=E6=89=BF5?= =?UTF-8?q?=E2=80=94=E2=80=94=E4=B8=B4=E6=97=B6=E6=9E=84=E9=80=A0=E5=87=BD?= =?UTF-8?q?=E6=95=B0=20=E6=8F=92=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Figure/chapter6/6-8.jpg | Bin 0 -> 33152 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Figure/chapter6/6-8.jpg diff --git a/Figure/chapter6/6-8.jpg b/Figure/chapter6/6-8.jpg new file mode 100644 index 0000000000000000000000000000000000000000..848cf57f309b7a28eeb4b6c3c62d59e29eab741a GIT binary patch literal 33152 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 Date: Tue, 10 Jul 2012 13:20:26 +0800 Subject: [PATCH 041/258] =?UTF-8?q?=E7=B1=BB=E5=BC=8F=E7=BB=A7=E6=89=BF5?= =?UTF-8?q?=E2=80=94=E2=80=94=E4=B8=B4=E6=97=B6=E6=9E=84=E9=80=A0=E5=87=BD?= =?UTF-8?q?=E6=95=B0=20=E7=AC=AC=E4=B8=80=E5=B0=8F=E8=8A=82=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/chapter6.markdown b/chapter6.markdown index 69de815..f776e6c 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -300,3 +300,29 @@ Child()构造函数是空的,也没有属性添加到Child.prototype上,这 图6-7 (父子对象)共享原型时的关系 + +## 类式继承5——临时构造函数 + +下一种模式通过打断父对象和子对象原型的直接链接解决了共享原型时的问题,同时还从原型链中获得其它的好处。 + +下面是这种模式的一种实现方式,F()函数是一个空函数,它充当了子对象和父对象的代理。F()的prototype属性指向父对象的原型。子对象的原型是一这个空函数的一个实例: + + function inherit(C, P) { + var F = function () {}; + F.prototype = P.prototype; + C.prototype = new F(); + } + +这种模式有一种和默认模式(类式继承1)明显不一样的行为,因为在这里子对象只继承原型中的属性(图6-8)。 + +![图6-8 使用临时(代理)构造函数F()实现类式继承](./Figure/chapter6/6-8.jpg) + +图6-8 使用临时(代理)构造函数F()实现类式继承 + +这种模式通常情况下都是一种很棒的选择,因为原型本来就是存放复用成员的地方。在这种模式中,父构造函数添加到this中的任何成员都不会被继承。 + +我们来创建一个子对象并且检查一下它的行为: + + var kid = new Child(); + +如果你访问kid.name将得到undefined。在这个例子中,name是父对象自己的属性,而在继承的过程中我们并没有调用new Parent(),所以这个属性并没有被创建。当访问kid.say()时,它在3号对象中不可用,所以在原型链中查找,4号对象也没有,但是1号对象有,它在内在中的位置会被所有从Parent()创建的构造函数和子对象所共享。 From e1f5f5936ced5667cd388460afd314effa7e4399 Mon Sep 17 00:00:00 2001 From: TooBug Date: Wed, 18 Jul 2012 13:17:57 +0800 Subject: [PATCH 042/258] =?UTF-8?q?=E7=B1=BB=E5=BC=8F=E7=BB=A7=E6=89=BF5?= =?UTF-8?q?=E2=80=94=E2=80=94=E4=B8=B4=E6=97=B6=E6=9E=84=E9=80=A0=E5=87=BD?= =?UTF-8?q?=E6=95=B0=20=E4=B8=80=E8=8A=82=E7=BF=BB=E8=AF=91=E5=AE=8C?= =?UTF-8?q?=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 59 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/chapter6.markdown b/chapter6.markdown index f776e6c..07472e9 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -326,3 +326,62 @@ Child()构造函数是空的,也没有属性添加到Child.prototype上,这 var kid = new Child(); 如果你访问kid.name将得到undefined。在这个例子中,name是父对象自己的属性,而在继承的过程中我们并没有调用new Parent(),所以这个属性并没有被创建。当访问kid.say()时,它在3号对象中不可用,所以在原型链中查找,4号对象也没有,但是1号对象有,它在内在中的位置会被所有从Parent()创建的构造函数和子对象所共享。 + + +### 存储父类(Superclass) + +在上一种模式的基础上,还可以添加一个指向原始父对象的引用。这很像其它语言中访问超类(superclass)的情况,有时候很方便。 + +我们将这个属性命名为“uber”,因为“super”是一个保留字,而“superclass”则可能误导别人认为JavaScript拥有类。下面是这种类式继承模式的一个改进版实现: + + function inherit(C, P) { + var F = function () {}; + F.prototype = P.prototype; + C.prototype = new F(); + C.uber = P.prototype; + } + + +### 重置构造函数引用 + +这个近乎完美的模式上还需要做的最后一件事情就是重置构造函数(constructor)的指向,以便未来在某个时刻能被正确地使用。 + +如果不重置构造函数的指向,那所有的子对象都会认为Parent()是它们的构造函数,而这个结果完全没有用。使用前面的inherit()的实现,你可以观察到这种行为: + + // parent, child, inheritance + function Parent() {} + function Child() {} + inherit(Child, Parent); + + // testing the waters + var kid = new Child(); + kid.constructor.name; // "Parent" + kid.constructor === Parent; // true + +constructor属性很少用,但是在运行时检查对象很方便。你可以重新将它指向期望的构造函数而不影响功能,因为这个属性更多是“信息性”的。(译注:即它更多的时候是在提供信息而不是参与到函数功能中。) + +最终,这种类式继承的Holy Grail版本看起来是这样的: + + function inherit(C, P) { + var F = function () {}; + F.prototype = P.prototype; + C.prototype = new F(); + C.uber = P.prototype; + C.prototype.constructor = C; + } + +类似这样的函数也存在于YUI库(也许还有其它库)中,它将类式继承的方法带给了没有类的语言。如果你决定使用类式继承,那么这是最好的方法。 + +> “代理函数”或者“代理构造函数”也是指这种模式,因为临时构造函数是被用作获取父构造函数原型的代理。 + +一种常见的对Holy Grail模式的优化是避免每次需要继承的时候都创建一个临时(代理)构造函数。事实上创建一次就足够了,以后只需要修改它的原型即可。你可以用一个立即执行的函数来将代理函数存储到闭包中: + + var inherit = (function () { + var F = function () {}; + return function (C, P) { + F.prototype = P.prototype; + C.prototype = new F(); + C.uber = P.prototype; + C.prototype.constructor = C; + } + }()); \ No newline at end of file From b50f58e7274a0bab09b7e7dfec0867a3372d7850 Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 24 Jul 2012 13:28:12 +0800 Subject: [PATCH 043/258] =?UTF-8?q?Klass=E4=B8=80=E8=8A=82=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E4=B8=80=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/chapter6.markdown b/chapter6.markdown index 07472e9..042fbd7 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -384,4 +384,47 @@ constructor属性很少用,但是在运行时检查对象很方便。你可以 C.uber = P.prototype; C.prototype.constructor = C; } - }()); \ No newline at end of file + }()); + + +## Klass + +有很多JavaScript类库模拟了类,创造了新的语法糖。具体的实现方式可能会不一样,但是基本上都有一些共性,包括: + +- 有一个约定好名字的方法,如initialize、_init或者其它相似的名字,会被自动调用,来充当类的构造函数。 +- 类可以从其它类继承 +- 在子类中可以访问到父类(superclass) + +> 我们在这里做一下变化,在本章的这部分自由地使用“class”单词,因为主题就是模拟类。 + +为避免讨论太多细节,我们来看一下JavaScript中一种模拟类的实现。首先,这种解决方案从客户的角度来看将如何被使用? + + var Man = klass(null, { + __construct: function (what) { + console.log("Man's constructor"); + this.name = what; + }, + getName: function () { + return this.name; + } + }); + +这种语法糖的形式是一个名为klass()的函数。在一些实现方式中,它可能是Klass()构造函数或者是增强的Object.prototype,但是在这个例子中,我们让它只是一个简单的函数。 + +这个函数接受两个参数:一个被继承的类和通过对象字面量提供的新类的实现。受PHP的影响,我们约定类的构造函数必须是一个名为\_\_construct的方法。在前面的代码片段中,建立了一个名为Man的新类,并且它不继承任何类(意味着继承自Object)。Man类有一个在\_\_construct建立的自己的属性name和一个方法getName()。这个类是一个构造函数,所以下面的代码将正常工作(并且看起来像类实例化的过程): + + var first = new Man('Adam'); // logs "Man's constructor" + first.getName(); // "Adam" + +现在我们来扩展这个类,创建一个SuperMan类: + + var SuperMan = klass(Man, { + __construct: function (what) { + console.log("SuperMan's constructor"); + }, + getName: function () { + var name = SuperMan.uber.getName.call(this); + return "I am " + name; + } + }); + From f866b8c133cc91e8ec53032977d70900969f1310 Mon Sep 17 00:00:00 2001 From: TooBug Date: Wed, 1 Aug 2012 13:17:31 +0800 Subject: [PATCH 044/258] =?UTF-8?q?Klass=E4=B8=80=E8=8A=82=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 57 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/chapter6.markdown b/chapter6.markdown index 042fbd7..bcd0644 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -428,3 +428,60 @@ constructor属性很少用,但是在运行时检查对象很方便。你可以 } }); +这里,klass()的第一个参数是将被继承的Man类。值得注意的是,在getName()中,父类的getName()方法首先通过SuperMan类的uber静态属性被调用。我们来测试一下: + + var clark = new SuperMan('Clark Kent'); + clark.getName(); // "I am Clark Kent" + +第一行在console中记录了“Man's constructor”,然后是“Superman's constructor”。在一些语言中,父类的构造函数在子类构造函数被调用的时候会自动执行,这个特性也可以模拟。 + +用instanceof运算符测试返回希望的结果: + + clark instanceof Man; // true + clark instanceof SuperMan; // true + +最后,我们来看一下klass()函数是怎样实现的: + + var klass = function (Parent, props) { + + var Child, F, i; + + // 1. + // new constructor + Child = function () { + if (Child.uber && Child.uber.hasOwnProperty("__construct")) { + Child.uber.__construct.apply(this, arguments); + } + if (Child.prototype.hasOwnProperty("__construct")) { + Child.prototype.__construct.apply(this, arguments); + } + }; + + // 2. + // inherit + Parent = Parent || Object; + F = function () {}; + F.prototype = Parent.prototype; + Child.prototype = new F(); + Child.uber = Parent.prototype; + Child.prototype.constructor = Child; + + // 3. + // add implementation methods + for (i in props) { + if (props.hasOwnProperty(i)) { + Child.prototype[i] = props[i]; + } + } + + // return the "class" + return Child; + }; + +这个klass()实现有三个明显的部分: + +1. 创建Child()构造函数,这也是最后返回的将被作为类使用的函数。在这个函数里面,如果__construct方法存在的话将被调用。同样是在父类的__construct(如果存在)被调用前使用静态的uber属性。也可能存在uber没有定义的情况——比如从Object继承,因为它是在Man类中被定义的。 +2. 第二部分主要完成继承。只是简单地使用前面章节讨论过的Holy Grail类式继承模式。只有一个东西是新的:如果Parent没有传值的话,设定Parent为Object。 +3. 最后一部分是类真正定义的地方,循环需要实现的方法(如例子中的__constructt和getName),并将它们添加到Child的原型中。 + +什么时候使用这种模式?其实,最好是能避免则避免,因为它带来了在这门语言中不存在的完整的类的概念,会让人疑惑。使用它需要学习新的语法和新的规则。也就是说,如果你或者你的团队对类感到习惯并且同时对原型感到不习惯,这种模式可能是一个可以探索的方向。这种模式允许你完全忘掉原型,好处就是你可以将语法变种得像其它你所喜欢的语言一样。 From 2346813ad48098ea4e1f9e71b618d27cbf122470 Mon Sep 17 00:00:00 2001 From: TooBug Date: Wed, 1 Aug 2012 13:19:09 +0800 Subject: [PATCH 045/258] =?UTF-8?q?Klass=E4=B8=80=E8=8A=82=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapter6.markdown b/chapter6.markdown index bcd0644..bc4073a 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -480,8 +480,8 @@ constructor属性很少用,但是在运行时检查对象很方便。你可以 这个klass()实现有三个明显的部分: -1. 创建Child()构造函数,这也是最后返回的将被作为类使用的函数。在这个函数里面,如果__construct方法存在的话将被调用。同样是在父类的__construct(如果存在)被调用前使用静态的uber属性。也可能存在uber没有定义的情况——比如从Object继承,因为它是在Man类中被定义的。 +1. 创建Child()构造函数,这也是最后返回的将被作为类使用的函数。在这个函数里面,如果__construct方法存在的话将被调用。同样是在父类的\_\_construct(如果存在)被调用前使用静态的uber属性。也可能存在uber没有定义的情况——比如从Object继承,因为它是在Man类中被定义的。 2. 第二部分主要完成继承。只是简单地使用前面章节讨论过的Holy Grail类式继承模式。只有一个东西是新的:如果Parent没有传值的话,设定Parent为Object。 -3. 最后一部分是类真正定义的地方,循环需要实现的方法(如例子中的__constructt和getName),并将它们添加到Child的原型中。 +3. 最后一部分是类真正定义的地方,循环需要实现的方法(如例子中的\_\_constructt和getName),并将它们添加到Child的原型中。 什么时候使用这种模式?其实,最好是能避免则避免,因为它带来了在这门语言中不存在的完整的类的概念,会让人疑惑。使用它需要学习新的语法和新的规则。也就是说,如果你或者你的团队对类感到习惯并且同时对原型感到不习惯,这种模式可能是一个可以探索的方向。这种模式允许你完全忘掉原型,好处就是你可以将语法变种得像其它你所喜欢的语言一样。 From ac510e23bed1f9b099bf200d0a3470d40963ac81 Mon Sep 17 00:00:00 2001 From: TooBug Date: Wed, 1 Aug 2012 13:20:09 +0800 Subject: [PATCH 046/258] =?UTF-8?q?Klass=E4=B8=80=E8=8A=82=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapter6.markdown b/chapter6.markdown index bc4073a..2ecbf37 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -480,7 +480,7 @@ constructor属性很少用,但是在运行时检查对象很方便。你可以 这个klass()实现有三个明显的部分: -1. 创建Child()构造函数,这也是最后返回的将被作为类使用的函数。在这个函数里面,如果__construct方法存在的话将被调用。同样是在父类的\_\_construct(如果存在)被调用前使用静态的uber属性。也可能存在uber没有定义的情况——比如从Object继承,因为它是在Man类中被定义的。 +1. 创建Child()构造函数,这也是最后返回的将被作为类使用的函数。在这个函数里面,如果\_\_construct方法存在的话将被调用。同样是在父类的\_\_construct(如果存在)被调用前使用静态的uber属性。也可能存在uber没有定义的情况——比如从Object继承,因为它是在Man类中被定义的。 2. 第二部分主要完成继承。只是简单地使用前面章节讨论过的Holy Grail类式继承模式。只有一个东西是新的:如果Parent没有传值的话,设定Parent为Object。 3. 最后一部分是类真正定义的地方,循环需要实现的方法(如例子中的\_\_constructt和getName),并将它们添加到Child的原型中。 From 9929c9cebce6bdd31ca01a125bfaf2d7d5f6816f Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 6 Aug 2012 13:24:39 +0800 Subject: [PATCH 047/258] =?UTF-8?q?=E5=8E=9F=E5=9E=8B=E7=BB=A7=E6=89=BF?= =?UTF-8?q?=E4=B8=80=E8=8A=82=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 90 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/chapter6.markdown b/chapter6.markdown index 2ecbf37..af28415 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -485,3 +485,93 @@ constructor属性很少用,但是在运行时检查对象很方便。你可以 3. 最后一部分是类真正定义的地方,循环需要实现的方法(如例子中的\_\_constructt和getName),并将它们添加到Child的原型中。 什么时候使用这种模式?其实,最好是能避免则避免,因为它带来了在这门语言中不存在的完整的类的概念,会让人疑惑。使用它需要学习新的语法和新的规则。也就是说,如果你或者你的团队对类感到习惯并且同时对原型感到不习惯,这种模式可能是一个可以探索的方向。这种模式允许你完全忘掉原型,好处就是你可以将语法变种得像其它你所喜欢的语言一样。 + + +## 原型继承 + +现在,让我们从一个叫作“原型继承”的模式来讨论没有类的现代继承模式。在这种模式中,没有任何类进来,在这里,一个对象继承自另外一个对象。你可以这样理解它:你有一个想复用的对象,然后你想创建第二个对象,并且获得第一个对象的功能。下面是这种模式的用法: + + //需要继承的对象 + var parent = { + name: "Papa" + }; + + //新对象 + var child = object(parent); + + //测试 + alert(child.name); // "Papa" + +在这个代码片段中,有一个已经存在的使用对象字面量创建的对象叫parent,我们想创建一个和parent有相同的属性和方法的对象叫child。child对象使用object()函数创建。这个函数在JavaScript中并不存在(不要与构造函数Object()混淆),所以我们来看看怎样定义它。 + +与Holy Grail类式继承相似,可以使用一个空的临时构造函数F(),然后设定F()的原型为parent对象。最后,返回一个临时构造函数的新实例。 + + function object(o) { + function F() {} + F.prototype = o; + return new F(); + } + +图6-9展示了使用原型继承时的原型链。在这里child总是以一个空对象开始,它没有自己的属性但通过原型链(\_\_proto\_\_)拥有父对象的所有功能。 + +//TODO:图6-9 + +### 讨论 + +在原型继承模式中,parent不需要使用对象字面量来创建。(尽管这是一种更觉的方式。)可以使用构造函数来创建parent。注意,如果你这样做,那么自己的属性和原型上的属性都将被继承: + + // parent constructor + function Person() { + // an "own" property + this.name = "Adam"; + } + // a property added to the prototype + Person.prototype.getName = function () { + return this.name; + }; + + // create a new person + var papa = new Person(); + // inherit + var kid = object(papa); + + // test that both the own property + // and the prototype property were inherited + kid.getName(); // "Adam" + +在这种模式的另一个变种中,你可以选择只继承已存在的构造函数的原型对象。记住,对象继承自对象,不管父对象是怎么创建的。这是前面例子的一个修改版本: + + // parent constructor + function Person() { + // an "own" property + this.name = "Adam"; + } + // a property added to the prototype + Person.prototype.getName = function () { + + }; + + // inherit + var kid = object(Person.prototype); + + typeof kid.getName; // "function", because it was in the prototype + typeof kid.name; // "undefined", because only the prototype was inherited + +###例外的ECMAScript 5 + +在ECMAScript 5中,原型继承已经正式成为语言的一部分。这种模式使用Object.create方法来实现。换句话说,你不再需要自己去写类似object()的函数,它是语言原生的了: + + var child = Object.create(parent); + +Object.create()接收一个额外的参数——一个对象。这个额外对象中的属性将被作为自己的属性添加到返回的子对象中。这让我们可以很方便地将继承和创建子对象在一个方法调用中实现。例如: + + var child = Object.create(parent, { + age: { value: 2 } // ECMA5 descriptor + }); + child.hasOwnProperty("age"); // true + +你可能也会发现原型继承模式已经在一些JavaScript类库中实现了,比如,在YUI3中,它是Y.Object()方法: + + YUI().use('*', function (Y) { + var child = Y.Object(parent); + }); \ No newline at end of file From d04c03aef65abe70adc4f16b6b01b467e8b25a94 Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 13 Aug 2012 23:49:13 +0800 Subject: [PATCH 048/258] =?UTF-8?q?=E9=80=9A=E8=BF=87=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E7=BB=A7=E6=89=BF=20=E4=B8=80=E8=8A=82?= =?UTF-8?q?=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 78 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/chapter6.markdown b/chapter6.markdown index af28415..561152a 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -574,4 +574,80 @@ Object.create()接收一个额外的参数——一个对象。这个额外对 YUI().use('*', function (Y) { var child = Y.Object(parent); - }); \ No newline at end of file + }); + + +## 通过复制属性继承 + +让我们来看一下另外一种继承模式——通过复制属性继承。在这种模式中,一个对象通过简单地复制另一个对象来获得功能。下面是一个简单的实现这种功能的extend()函数: + + function extend(parent, child) { + var i; + child = child || {}; + for (i in parent) { + if (parent.hasOwnProperty(i)) { + child[i] = parent[i]; + } + } + return child; + } + +这是一个简单的实现,仅仅是遍历了父对象的成员然后复制它们。在这个实现中,child是可选参数,如果它没有被传入一个已有的对象,那么一个全新的对象将被创建并被返回: + + var dad = {name: "Adam"}; + var kid = extend(dad); + kid.name; // "Adam" + +上面给出的实现叫作对象的“浅拷贝”(shallow copy)。另一方面,“深拷贝”是指检查准备复制的属性本身是否是对象或者数组,如果是,也遍历它们的属性并复制。如果使用浅拷贝的话(因为在JavaScript中对象是按引用传递),如果你改变子对象的一个属性,而这个属性恰好是一个对象,那么你也会改变父对象。实际上这对方法来说可能很好(因为函数也是对象,也是按引用传递),但是当遇到其它的对象和数组的时候可能会有些意外情况。考虑这种情况: + + var dad = { + counts: [1, 2, 3], + reads: {paper: true} + }; + var kid = extend(dad); + kid.counts.push(4); + dad.counts.toString(); // "1,2,3,4" + dad.reads === kid.reads; // true + +现在让我们来修改一下extend()函数以便做深拷贝。所有你需要做的事情只是检查一个属性的类型是否是对象,如果是,则递归遍历它的属性。另外一个需要做的检查是这个对象是真的对象还是数组。我们可以使用第3章讨论过的数组检查方式。最终深拷贝版的extend()是这样的: + + function extendDeep(parent, child) { + var i, + toStr = Object.prototype.toString, + astr = "[object Array]"; + + child = child || {}; + + for (i in parent) { + if (parent.hasOwnProperty(i)) { + if (typeof parent[i] === "object") { + child[i] = (toStr.call(parent[i]) === astr) ? [] : {}; + extendDeep(parent[i], child[i]); + } else { + child[i] = parent[i]; + } + } + } + return child; + } + +现在测试时这个新的实现给了我们对象的真实拷贝,所以子对象不会修改父对象: + + var dad = { + counts: [1, 2, 3], + reads: {paper: true} + }; + var kid = extendDeep(dad); + + kid.counts.push(4); + kid.counts.toString(); // "1,2,3,4" + dad.counts.toString(); // "1,2,3" + + dad.reads === kid.reads; // false + kid.reads.paper = false; + kid.reads.web = true; + dad.reads.paper; // true + +通过复制属性继承的模式很简单且应用很广泛。例如Firebug(JavaScript写的Firefox扩展)有一个方法叫extend()做浅拷贝,jQuery的extend()方法做深拷贝。YUI3提供了一个叫作Y.clone()的方法,它创建一个深拷贝并且通过绑定到子对象的方式复制函数。(本章后面将有更多关于绑定的内容。) + +它并不高深,根本并没有原型牵涉到这种模式中,它只是跟对象和它们属性有关的模式。 \ No newline at end of file From 37936472e45bf16020cc5c291399ee30630a4ba1 Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 30 Oct 2012 05:49:51 -0400 Subject: [PATCH 049/258] =?UTF-8?q?=E7=AC=AC=E5=85=AD=E7=AB=A0=20=E6=B7=B7?= =?UTF-8?q?=E5=85=83=E4=B8=80=E8=8A=82=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 72 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 18 deletions(-) diff --git a/chapter6.markdown b/chapter6.markdown index 561152a..4b5c6ec 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -38,22 +38,22 @@ 下面是定义两个构造函数Parent()和Child()的例子: - //parent构造函数 - function Parent(name) { - this.name = name || 'Adam'; - } - - //给原型增加方法 - Parent.prototype.say = function () { - return this.name; - }; - - //空的child构造函数 - function Child(name) {} - - //继承 - inherit(Child, Parent); - + //parent构造函数 + function Parent(name) { + this.name = name || 'Adam'; + } + + //给原型增加方法 + Parent.prototype.say = function () { + return this.name; + }; + + //空的child构造函数 + function Child(name) {} + + //继承 + inherit(Child, Parent); + 上面的代码定义了两个构造函数Parent()和Child(),say()方法被添加到了Parent()构建函数的原型(prototype)中,inherit()函数完成了继承的工作。inherit()函数并不是原生提供的,需要自己实现。让我们来看一看比较大众的实现它的几种方法。 ## 类式继承1——默认模式 @@ -589,7 +589,7 @@ Object.create()接收一个额外的参数——一个对象。这个额外对 child[i] = parent[i]; } } - return child; + return child; } 这是一个简单的实现,仅仅是遍历了父对象的成员然后复制它们。在这个实现中,child是可选参数,如果它没有被传入一个已有的对象,那么一个全新的对象将被创建并被返回: @@ -650,4 +650,40 @@ Object.create()接收一个额外的参数——一个对象。这个额外对 通过复制属性继承的模式很简单且应用很广泛。例如Firebug(JavaScript写的Firefox扩展)有一个方法叫extend()做浅拷贝,jQuery的extend()方法做深拷贝。YUI3提供了一个叫作Y.clone()的方法,它创建一个深拷贝并且通过绑定到子对象的方式复制函数。(本章后面将有更多关于绑定的内容。) -它并不高深,根本并没有原型牵涉到这种模式中,它只是跟对象和它们属性有关的模式。 \ No newline at end of file +这种模式并不高深,因为根本没有原型牵涉进来,而只跟对象和它们的属性有关。 + +## 混元(Mix-ins) + +既然谈到了通过拷贝属性来继随,就让我们顺便多说一点,来讨论一下“混元”模式。除了前面说的从一个对象复制,你还可以从任意多数量的对象中复制属性,然后将它们混在一起组成一个新对象。 + +实现很简单,只需要遍历传入的每个参数然后复制它们的每个属性: + + function mix() { + var arg, prop, child = {}; + for (arg = 0; arg < arguments.length; arg += 1) { + for (prop in arguments[arg]) { + if (arguments[arg].hasOwnProperty(prop)) { + child[prop] = arguments[arg][prop]; + } + } + } + return child; + } + +现在我们有了一个通用的混元函数,我们可以传递任意数量的对象进去,返回的结果将是一个包含所有传入对象属性的新对象。下面是用法示例: + + var cake = mix( + {eggs: 2, large: true}, + {butter: 1, salted: true}, + {flour: "3 cups"}, + {sugar: "sure!"} + ); + +图6-10展示了在Firebug的控制台中用console.dir(cake)展示出来的混元后cake对象的属性。 + +![图6-10 在Firebug中查看cake对象](./Figure/chapter6/6-10.jpg) + +图6-10 在Firebug中查看cake对象 + +> 如果你习惯了某些将混元作为原生部分的语言,那么你可能期望修改一个或多个父对象时也影响子对象。但在这个实现中这是不会发生的事情。这里我们只是简单地遍历、复制自己的属性,并没有与父对象的链接。 + From 8aadabf2176b4b23b2edd7cc9802582e83361840 Mon Sep 17 00:00:00 2001 From: TooBug Date: Wed, 31 Oct 2012 01:34:39 -0400 Subject: [PATCH 050/258] =?UTF-8?q?=E7=AC=AC=E5=85=AD=E7=AB=A0=E5=85=A8?= =?UTF-8?q?=E9=83=A8=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 128 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/chapter6.markdown b/chapter6.markdown index 4b5c6ec..19c983d 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -686,4 +686,132 @@ Object.create()接收一个额外的参数——一个对象。这个额外对 图6-10 在Firebug中查看cake对象 > 如果你习惯了某些将混元作为原生部分的语言,那么你可能期望修改一个或多个父对象时也影响子对象。但在这个实现中这是不会发生的事情。这里我们只是简单地遍历、复制自己的属性,并没有与父对象的链接。 + +## 借用方法 + +有时候会有这样的情况:你希望使用某个已存在的对象的一两个方法,你希望能复用它们,但是又真的不希望和那个对象产生继承关系,因为你只希望使用你需要的那一两个方法,而不继承那些你永远用不到的方法。受益于函数方法call()和apply(),通过借用方法模式,这是可行的。在本书中,你其实已经见过这种模式了,甚至在本章extendDeep()的实现中也有用到。 + +如你所熟知的一样,在JavaScript中函数也是对象,它们有一些有趣的方法,比如call()和apply()。这两个方法的唯一区别是后者接受一个参数数组以传入正在调用的方法,而前者只接受一个一个的参数。你可以使用这两个方法来从已有的对象中借用方法: + + //call() example + notmyobj.doStuff.call(myobj, param1, p2, p3); + // apply() example + notmyobj.doStuff.apply(myobj, [param1, p2, p3]); + +在这个例子中有一个对象myobj,而且notmyobj有一个用得着的方法叫doStuff()。你可以简单地临时借用doStuff()方法,而不用处理继承然后得到一堆myobj中你永远不会用的方法。 + +你传一个对象和任意的参数,这个被借用的方法会将this绑定到你自己的对象上。简单地说,你的对象会临时假装成另一个对象以使用它的方法。这就像实际上获得了继承但又免除了“继承税”(指你不需要的属性和方法)。 + +### 例:从数组借用 + +这种模式的一种常见用法是从数组借用方法。 + +数组有很多很有用但是一些“类数组”对象(如arguments)不具备的方法。所以arguments可以借用数组的方法,比如slice()。这是一个例子: + + function f() { + var args = [].slice.call(arguments, 1, 3); + return args; + } + + // example + f(1, 2, 3, 4, 5, 6); // returns [2,3] + +在这个例子中,有一个空数组被创建了,因为要借用它的方法。同样的事情也可以使用一种看起来代码更长的方法来做,那就是直接从数组的原型中借用方法,使用Array.prototype.slice.call(...)。这种方法代码更长一些,但是不用创建一个空数组。 + +### 借用并绑定 + +当借用方法的时候,不管是通过call()/apply()还是通过简单的赋值,方法中的this指向的对象都是基于调用的表达式来决定的。但是有时候最好的使用方式是将this的值锁定或者提前绑定到一个指定的对象上。 + +我们来看一个例子。这是一个对象one,它有一个say()方法: + + var one = { + name: "object", + say: function (greet) { + return greet + ", " + this.name; + } + }; + + // test + one.say('hi'); // "hi, object" + +现在另一个对象two没有say()方法,但是它可以从one借用: + + var two = { + name: "another object" + }; + + one.say.apply(two, ['hello']); // "hello, another object" + +在这个例子中,say()方法中的this指向了two,this.name是“another object”。但是如果在某些场景下你将th函数赋值给了全局变量或者是将这个函数作为回调,会发生什么?在客户端编程中有非常多的事件和回调,所以这种情况经常发生: + + // assigning to a variable + // `this` will point to the global object + var say = one.say; + say('hoho'); // "hoho, undefined" + // passing as a callback + var yetanother = { + name: "Yet another object", + method: function (callback) { + return callback('Hola'); + } + }; + yetanother.method(one.say); // "Holla, undefined" + +在这两种情况中say()中的this都指向了全局对象,所以代码并不像我们想象的那样正常工作。要修复(换言之,绑定)一个方法的对象,我们可以用一个简单的函数,像这样: + + function bind(o, m) { + return function () { + return m.apply(o, [].slice.call(arguments)); + }; + } + +这个bind()函数接受一个对象o和一个方法m,然后把它们绑定在一起,再返回另一个函数。返回的函数通过闭包可以访问到o和m。也就是说,即使在bind()返回之后,内层的函数仍然可以访问到o和m,而o和m会始终指向原始的对象和方法。让我们用bind()来创建一个新函数: + + var twosay = bind(two, one.say); + twosay('yo'); // "yo, another object" + +正如你看到的,尽管twosay()是作为一个全局函数被创建的,但this并没有指向全局对象,而是指向了通过bind()传入的对象two。不论无何调用twosay(),this将始终指向two。 + +绑定是奢侈的,你需要付出的代价是一个额外的闭包。 + +### Function.prototype.bind() + +ECMAScript5在Function.prototype中添加了一个方法叫bind(),使用时和apply和call()一样简单。所以你可以这样写: + + var newFunc = obj.someFunc.bind(myobj, 1, 2, 3); + +这意味着将someFunc()主myobj绑定了并且传入了someFunc()的前三个参数。这也是一个在第4章讨论过的部分应用的例子。 + +让我们来看一下当你的程序跑在低于ES5的环境中时如何实现Function.prototype.bind(): + + if (typeof Function.prototype.bind === "undefined") { + Function.prototype.bind = function (thisArg) { + var fn = this, + slice = Array.prototype.slice, + args = slice.call(arguments, 1); + + return function () { + return fn.apply(thisArg, args.concat(slice.call(arguments))); + }; + }; + } + +这个实现可能看起来有点熟悉,它使用了部分应用,将传入bind()的参数串起来(除了第一个参数),然后在被调用时传给bind()返回的新函数。这是用法示例: + + var twosay2 = one.say.bind(two); + twosay2('Bonjour'); // "Bonjour, another object" + +在这个例子中,除了绑定的对象外,我们没有传任何参数给bind()。下一个例子中,我们来传一个用于部分应用的参数: + + var twosay3 = one.say.bind(two, 'Enchanté'); + twosay3(); // "Enchanté, another object" + +##小结 + +在JavaScript中,继承有很多种方案可以选择。学习和理解不同的模式是有好处的,因为这可以增强你对这门语言的掌握能力。在本章中你看到了很多类式继承和现代继承的方案。 + +但是,也许在开发过程中继承并不是你经常面对的一个问题。这一部分是因为这个问题已经被使用某种方式或者某个你使用的类库解决了,另一部分是因为你不需要在JavaScript中建立很长很复杂的继承链。在静态强类型语言中,继承可能是唯一可以利用代码的方法,但在JavaScript中你可能有更多更简单更优化的方法,包括借用方法、绑定、拷贝属性、混元等。 + +记住,代码复用才是目标,继承只是达成这个目标的一种手段。 + From ef837e2697d32e56e2db7ca4a74317d847ece823 Mon Sep 17 00:00:00 2001 From: TooBug Date: Thu, 1 Nov 2012 01:41:21 -0400 Subject: [PATCH 051/258] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=AC=AC=E5=85=AD?= =?UTF-8?q?=E7=AB=A0=E9=85=8D=E5=9B=BE;=E6=B7=BB=E5=8A=A0README=E4=B8=AD?= =?UTF-8?q?=E7=AC=AC=E5=85=AD=E7=AB=A0=E9=93=BE=E6=8E=A5;=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E5=B0=91=E9=87=8F=E6=8E=AA=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Figure/chapter6/6-10.jpg | Bin 0 -> 7650 bytes Figure/chapter6/6-9.jpg | Bin 0 -> 13766 bytes README.markdown | 54 +++++++++++++++++++-------------------- chapter6.markdown | 48 ++++++++++++++++++++++------------ 4 files changed, 58 insertions(+), 44 deletions(-) create mode 100644 Figure/chapter6/6-10.jpg create mode 100644 Figure/chapter6/6-9.jpg diff --git a/Figure/chapter6/6-10.jpg b/Figure/chapter6/6-10.jpg new file mode 100644 index 0000000000000000000000000000000000000000..61720cf72b4483ee38901bb341c11d78910c78ee GIT binary patch literal 7650 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 literal 0 HcmV?d00001 diff --git a/README.markdown b/README.markdown index 84d5942..ad89f86 100644 --- a/README.markdown +++ b/README.markdown @@ -140,33 +140,33 @@ - method() 方法 - 小节 -## [第六章 代码重用模式](javascript.patterns/blob/master/chapter6.markdown#a) - -- [类式继承 vs 现代继承模式](javascript.patterns/blob/master/chapter6.markdown#a) -- [类式继承的期望结果](javascript.patterns/blob/master/chapter6.markdown#a) -- [类式继承 1 ——默认模式](javascript.patterns/blob/master/chapter6.markdown#a) - - [跟踪原型链](javascript.patterns/blob/master/chapter6.markdown#a) - - [这种模式的缺点](javascript.patterns/blob/master/chapter6.markdown#a) -- [类式继承 2 ——借用构造函数](javascript.patterns/blob/master/chapter6.markdown#a) - - [原型链](javascript.patterns/blob/master/chapter6.markdown#a) - - [通过借用构造函数实现多继承](javascript.patterns/blob/master/chapter6.markdown#a) - - [借用构造器模式的利与弊](javascript.patterns/blob/master/chapter6.markdown#a) -- [类式继承 3 ——借用并设置原型](javascript.patterns/blob/master/chapter6.markdown#a) -- 经典模式 4 ——共享原型 -- 经典模式 5 —— 临时构造器 - - 存储父类 - - 重置构造器引用 -- Klass -- 原型继承 - - 讨论 - - 除了ECMAScript5之外 -- 通过拷贝属性继承 -- 混元 -- 借用方法 - - 例子:从数组借用 - - 借用和绑定 - - Function.prototype.bind() -- 小节 +## [第六章 代码复用模式](javascript.patterns/blob/master/chapter6.markdown#a1) + +- [类式继承 vs 现代继承模式](javascript.patterns/blob/master/chapter6.markdown#a2) +- [类式继承的期望结果](javascript.patterns/blob/master/chapter6.markdown#a3) +- [类式继承 1 ——默认模式](javascript.patterns/blob/master/chapter6.markdown#a4) + - [跟踪原型链](javascript.patterns/blob/master/chapter6.markdown#a5) + - [这种模式的缺点](javascript.patterns/blob/master/chapter6.markdown#a6) +- [类式继承 2 ——借用构造函数](javascript.patterns/blob/master/chapter6.markdown#a7) + - [原型链](javascript.patterns/blob/master/chapter6.markdown#a8) + - [利用借用构造函数模式实现多继承](javascript.patterns/blob/master/chapter6.markdown#a9) + - [借用构造函数的利与弊](javascript.patterns/blob/master/chapter6.markdown#a10) +- [类式继承 3 ——借用并设置原型](javascript.patterns/blob/master/chapter6.markdown#a11) +- [经典模式 4 ——共享原型](javascript.patterns/blob/master/chapter6.markdown#a12) +- [经典模式 5 —— 临时构造函数](javascript.patterns/blob/master/chapter6.markdown#a13) + - [存储父类](javascript.patterns/blob/master/chapter6.markdown#a14) + - [重置构造函数引用](javascript.patterns/blob/master/chapter6.markdown#a15) +- [Klass](javascript.patterns/blob/master/chapter6.markdown#a16) +- [原型继承](javascript.patterns/blob/master/chapter6.markdown#a17) + - [讨论](javascript.patterns/blob/master/chapter6.markdown#a18) + - [例外的ECMAScript 5](javascript.patterns/blob/master/chapter6.markdown#a19) +- [通过复制属性继承](javascript.patterns/blob/master/chapter6.markdown#a20) +- [混元(Mix-ins)](javascript.patterns/blob/master/chapter6.markdown#a21) +- [借用方法](javascript.patterns/blob/master/chapter6.markdown#a22) + - [例:从数组借用](javascript.patterns/blob/master/chapter6.markdown#a23) + - [借用并绑定](javascript.patterns/blob/master/chapter6.markdown#a24) + - [Function.prototype.bind()](javascript.patterns/blob/master/chapter6.markdown#a25) +- [小结](javascript.patterns/blob/master/chapter6.markdown#a26) ## 第七章 设计模式 diff --git a/chapter6.markdown b/chapter6.markdown index 19c983d..395653d 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -7,7 +7,7 @@ 在做代码复用的工作的时候,谨记Gang of Four 在书中给出的关于对象创建的建议:“优先使用对象创建而不是类继承”。(译注:《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)是一本设计模式的经典书籍,该书作者为Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides,被称为“Gang of Four”,简称“GoF”。) - + ## 类式继承 vs 现代继承模式 在讨论JavaScript的继承这个话题的时候,经常会听到“类式继承”的概念,那我们先看一下什么是类式(classical)继承。classical一词并不是来自某些古老的、固定的或者是被广泛接受的解决方案,而仅仅是来自单词“class”。(译注:classical也有“经典”的意思。) @@ -30,6 +30,7 @@ 本章先讨论类式继承,然后再关注现代继承模式。 + ## 类式继承的期望结果 实现类式继承的目标是基于构造函数Child()来创建一个对象,然后从另一个构造函数Parent()获得属性。 @@ -56,6 +57,7 @@ 上面的代码定义了两个构造函数Parent()和Child(),say()方法被添加到了Parent()构建函数的原型(prototype)中,inherit()函数完成了继承的工作。inherit()函数并不是原生提供的,需要自己实现。让我们来看一看比较大众的实现它的几种方法。 + ## 类式继承1——默认模式 最常用的一种模式是使用Parent()构造函数来创建一个对象,然后把这个对象设为Child()的原型。这是可复用的inherit()函数的第一种实现方法: @@ -71,6 +73,7 @@ var kid = new Child(); kid.say(); // "Adam" + ### 跟踪原型链 在这种模式中,子对象既继承了(父对象)“自己的属性”(添加给this的实例属性,比如name),也继承了原型中的属性和方法(比如say())。 @@ -107,6 +110,7 @@ Child()构造函数是空的,也没有属性添加到Child.prototype上,这 如果通过delete kid.name的方式移除新添加的属性,那么2号对象的name属性将暴露出来并且在查找的时候被找到。 + ### 这种模式的缺点 这种模式的一个缺点是既继承了(父对象)“自己的属性”,也继承了原型中的属性。大部分情况下你可能并不需要“自己的属性”,因为它们更可能是为实例对象添加的,并不用于复用。 @@ -120,7 +124,7 @@ Child()构造函数是空的,也没有属性添加到Child.prototype上,这 这并不是我们期望的结果。事实上传递参数给父构造函数是可能的,但这样需要在每次需要一个子对象时再做一次继承,很不方便,因为需要不断地创建父对象。 - + ## 类式继承2——借用构造函数 下面这种模式解决了从子对象传递参数到父对象的问题。它借用了父对象的构造函数,将子对象绑定到this,同时传入参数: @@ -165,7 +169,7 @@ Child()构造函数是空的,也没有属性添加到Child.prototype上,这 在这个例子中,blog对象修改了tags属性,同时,它也修改了父对象,因为实际上blog.tags和article.tags是引向同一个数组。而对pages.tags的修改并不影响父对象article,因为pages.tags在继承的时候是一份独立的拷贝。 - + ### 原型链 我们来看一下当我们使用熟悉的Parent()和Child()构造函数和这种继承模式时原型链是什么样的。为了使用这种继承模式,Child()有明显变化: @@ -195,7 +199,7 @@ Child()构造函数是空的,也没有属性添加到Child.prototype上,这 图6-4 使用借用构造函数模式时没有被关联的原型链 - + ### 利用借用构造函数模式实现多继承 使用借用构造函数模式,可以通过借用多个构造函数的方式来实现多继承: @@ -226,7 +230,7 @@ Child()构造函数是空的,也没有属性添加到Child.prototype上,这 图6-5 在Firebug中查看CatWings对象 - + ### 借用构造函数的利与弊 这种模式的一个明显的弊端就是无法继承原型。如前面所说,原型往往是添加可复用的方法和属性的地方,这样就不用在每个实例中再创建一遍。 @@ -235,7 +239,7 @@ Child()构造函数是空的,也没有属性添加到Child.prototype上,这 那么,在上一个例子中,怎样使一个子对象也能够继承原型属性呢?怎样能使kid可以访问到say()方法呢?下一种继承模式解决了这个问题。 - + ## 类式继承3——借用并设置原型 综合以上两种模式,首先借用父对象的构造函数,然后将子对象的原型设置为父对象的一个新实例: @@ -281,7 +285,7 @@ Child()构造函数是空的,也没有属性添加到Child.prototype上,这 图6-6 除了继承“自己的属性”外,原型链也被保留了 - + ## 类式继承4——共享原型 不像前一种类式继承模式需要调用两次父构造函数,下面这种模式根本不会涉及到调用父构造函数的问题。 @@ -300,7 +304,7 @@ Child()构造函数是空的,也没有属性添加到Child.prototype上,这 图6-7 (父子对象)共享原型时的关系 - + ## 类式继承5——临时构造函数 下一种模式通过打断父对象和子对象原型的直接链接解决了共享原型时的问题,同时还从原型链中获得其它的好处。 @@ -327,7 +331,7 @@ Child()构造函数是空的,也没有属性添加到Child.prototype上,这 如果你访问kid.name将得到undefined。在这个例子中,name是父对象自己的属性,而在继承的过程中我们并没有调用new Parent(),所以这个属性并没有被创建。当访问kid.say()时,它在3号对象中不可用,所以在原型链中查找,4号对象也没有,但是1号对象有,它在内在中的位置会被所有从Parent()创建的构造函数和子对象所共享。 - + ### 存储父类(Superclass) 在上一种模式的基础上,还可以添加一个指向原始父对象的引用。这很像其它语言中访问超类(superclass)的情况,有时候很方便。 @@ -341,7 +345,7 @@ Child()构造函数是空的,也没有属性添加到Child.prototype上,这 C.uber = P.prototype; } - + ### 重置构造函数引用 这个近乎完美的模式上还需要做的最后一件事情就是重置构造函数(constructor)的指向,以便未来在某个时刻能被正确地使用。 @@ -386,7 +390,7 @@ constructor属性很少用,但是在运行时检查对象很方便。你可以 } }()); - + ## Klass 有很多JavaScript类库模拟了类,创造了新的语法糖。具体的实现方式可能会不一样,但是基本上都有一些共性,包括: @@ -486,7 +490,7 @@ constructor属性很少用,但是在运行时检查对象很方便。你可以 什么时候使用这种模式?其实,最好是能避免则避免,因为它带来了在这门语言中不存在的完整的类的概念,会让人疑惑。使用它需要学习新的语法和新的规则。也就是说,如果你或者你的团队对类感到习惯并且同时对原型感到不习惯,这种模式可能是一个可以探索的方向。这种模式允许你完全忘掉原型,好处就是你可以将语法变种得像其它你所喜欢的语言一样。 - + ## 原型继承 现在,让我们从一个叫作“原型继承”的模式来讨论没有类的现代继承模式。在这种模式中,没有任何类进来,在这里,一个对象继承自另外一个对象。你可以这样理解它:你有一个想复用的对象,然后你想创建第二个对象,并且获得第一个对象的功能。下面是这种模式的用法: @@ -514,8 +518,11 @@ constructor属性很少用,但是在运行时检查对象很方便。你可以 图6-9展示了使用原型继承时的原型链。在这里child总是以一个空对象开始,它没有自己的属性但通过原型链(\_\_proto\_\_)拥有父对象的所有功能。 -//TODO:图6-9 +![图6-9 原型继承模式](./Figure/chapter6/6-9.jpg) +图6-9 原型继承模式 + + ### 讨论 在原型继承模式中,parent不需要使用对象字面量来创建。(尽管这是一种更觉的方式。)可以使用构造函数来创建parent。注意,如果你这样做,那么自己的属性和原型上的属性都将被继承: @@ -556,7 +563,8 @@ constructor属性很少用,但是在运行时检查对象很方便。你可以 typeof kid.getName; // "function", because it was in the prototype typeof kid.name; // "undefined", because only the prototype was inherited - + + ###例外的ECMAScript 5 在ECMAScript 5中,原型继承已经正式成为语言的一部分。这种模式使用Object.create方法来实现。换句话说,你不再需要自己去写类似object()的函数,它是语言原生的了: @@ -576,7 +584,7 @@ Object.create()接收一个额外的参数——一个对象。这个额外对 var child = Y.Object(parent); }); - + ## 通过复制属性继承 让我们来看一下另外一种继承模式——通过复制属性继承。在这种模式中,一个对象通过简单地复制另一个对象来获得功能。下面是一个简单的实现这种功能的extend()函数: @@ -652,9 +660,10 @@ Object.create()接收一个额外的参数——一个对象。这个额外对 这种模式并不高深,因为根本没有原型牵涉进来,而只跟对象和它们的属性有关。 + ## 混元(Mix-ins) -既然谈到了通过拷贝属性来继随,就让我们顺便多说一点,来讨论一下“混元”模式。除了前面说的从一个对象复制,你还可以从任意多数量的对象中复制属性,然后将它们混在一起组成一个新对象。 +既然谈到了通过复制属性来继承,就让我们顺便多说一点,来讨论一下“混元”模式。除了前面说的从一个对象复制,你还可以从任意多数量的对象中复制属性,然后将它们混在一起组成一个新对象。 实现很简单,只需要遍历传入的每个参数然后复制它们的每个属性: @@ -687,6 +696,7 @@ Object.create()接收一个额外的参数——一个对象。这个额外对 > 如果你习惯了某些将混元作为原生部分的语言,那么你可能期望修改一个或多个父对象时也影响子对象。但在这个实现中这是不会发生的事情。这里我们只是简单地遍历、复制自己的属性,并没有与父对象的链接。 + ## 借用方法 有时候会有这样的情况:你希望使用某个已存在的对象的一两个方法,你希望能复用它们,但是又真的不希望和那个对象产生继承关系,因为你只希望使用你需要的那一两个方法,而不继承那些你永远用不到的方法。受益于函数方法call()和apply(),通过借用方法模式,这是可行的。在本书中,你其实已经见过这种模式了,甚至在本章extendDeep()的实现中也有用到。 @@ -702,6 +712,7 @@ Object.create()接收一个额外的参数——一个对象。这个额外对 你传一个对象和任意的参数,这个被借用的方法会将this绑定到你自己的对象上。简单地说,你的对象会临时假装成另一个对象以使用它的方法。这就像实际上获得了继承但又免除了“继承税”(指你不需要的属性和方法)。 + ### 例:从数组借用 这种模式的一种常见用法是从数组借用方法。 @@ -718,6 +729,7 @@ Object.create()接收一个额外的参数——一个对象。这个额外对 在这个例子中,有一个空数组被创建了,因为要借用它的方法。同样的事情也可以使用一种看起来代码更长的方法来做,那就是直接从数组的原型中借用方法,使用Array.prototype.slice.call(...)。这种方法代码更长一些,但是不用创建一个空数组。 + ### 借用并绑定 当借用方法的时候,不管是通过call()/apply()还是通过简单的赋值,方法中的this指向的对象都是基于调用的表达式来决定的。但是有时候最好的使用方式是将this的值锁定或者提前绑定到一个指定的对象上。 @@ -775,6 +787,7 @@ Object.create()接收一个额外的参数——一个对象。这个额外对 绑定是奢侈的,你需要付出的代价是一个额外的闭包。 + ### Function.prototype.bind() ECMAScript5在Function.prototype中添加了一个方法叫bind(),使用时和apply和call()一样简单。所以你可以这样写: @@ -807,11 +820,12 @@ ECMAScript5在Function.prototype中添加了一个方法叫bind(),使用时和 var twosay3 = one.say.bind(two, 'Enchanté'); twosay3(); // "Enchanté, another object" + ##小结 在JavaScript中,继承有很多种方案可以选择。学习和理解不同的模式是有好处的,因为这可以增强你对这门语言的掌握能力。在本章中你看到了很多类式继承和现代继承的方案。 -但是,也许在开发过程中继承并不是你经常面对的一个问题。这一部分是因为这个问题已经被使用某种方式或者某个你使用的类库解决了,另一部分是因为你不需要在JavaScript中建立很长很复杂的继承链。在静态强类型语言中,继承可能是唯一可以利用代码的方法,但在JavaScript中你可能有更多更简单更优化的方法,包括借用方法、绑定、拷贝属性、混元等。 +但是,也许在开发过程中继承并不是你经常面对的一个问题。这一部分是因为这个问题已经被使用某种方式或者某个你使用的类库解决了,另一部分是因为你不需要在JavaScript中建立很长很复杂的继承链。在静态强类型语言中,继承可能是唯一可以利用代码的方法,但在JavaScript中你可能有更多更简单更优化的方法,包括借用方法、绑定、复制属性、混元等。 记住,代码复用才是目标,继承只是达成这个目标的一种手段。 From 0323ee198bc2fdc8dc82dfcc4444fc018ef84f40 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 2 Nov 2012 02:03:19 -0400 Subject: [PATCH 052/258] =?UTF-8?q?=E7=AC=AC=E4=B8=83=E7=AB=A0=20=E5=8D=95?= =?UTF-8?q?=E4=BE=8B=E7=AC=AC=E4=B8=80=E5=B0=8F=E8=8A=82=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 chapter7.markdown diff --git a/chapter7.markdown b/chapter7.markdown new file mode 100644 index 0000000..251daca --- /dev/null +++ b/chapter7.markdown @@ -0,0 +1,33 @@ + +# 设计模式 + +在GoF(Gang of Four)的书中提出的设计模式为面向对象的软件设计中遇到的一些普遍问题提供了解决方案。它们已经诞生很久了,而且被证实在很多情况下是很有效的。这正是你需要熟悉它的原因,也是我们要讨论它的原因。 + +尽管这些设计模式跟语言和具体的实现方式无关,但它们多年来被关注到的方面仍然主要是在强类型静态语言比如C++和Java中的应用。 + +JavaScript作为一种基于原型的弱类型动态语言,使得有些时候实现某些模式时相当简单,甚至不费吹灰之力。 + +让我们从第一个例子——单例模式——来看一下在JavaScript中和静态的基于类的语言有什么不同。 + +## 单例 + +单例模式的核心思想是让指定的类只存在唯一一个实例。这意味着当你第二次使用相同的类去创建对象的时候,你得到的应该和第一次创建的是同一个对象。 + +这如何应用到JavaScript中呢?在JavaScript中没有类,只有对象。当你创建一个对象时,事实上根本没有另一个对象和它一样,这个对象其实已经是一个单例。使用对象字面量创建一个简单的对象也是一种单例的例子: + + var obj = { + myprop: 'my value' + }; + +在JavaScript中,对象永远不会相等,除非它们是同一个对象,所以既然你创建一个看起来完全一样的对象,它也不会和前面的对象相等: + + var obj2 = { + myprop: 'my value' + }; + obj === obj2; // false + obj == obj2; // false + +所以你可以说当你每次使用对象字面量创建一个对象的时候就是在创建一个单例,并没有特别的语法迁涉进来。 + +> 需要注意的是,有的时候当人们在JavaScript中提出“单例”的时候,它们可能是在指第5章讨论过的“模块模式”。 + From e1739e6d4a6731ee533055956ed72ec25ff73e7e Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 5 Nov 2012 00:18:30 -0500 Subject: [PATCH 053/258] =?UTF-8?q?=E7=AC=AC=E4=B8=83=E7=AB=A0=20=E5=8D=95?= =?UTF-8?q?=E4=BE=8B=E6=A8=A1=E5=BC=8F=E4=B8=80=E8=8A=82=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 178 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) diff --git a/chapter7.markdown b/chapter7.markdown index 251daca..dc4d613 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -31,3 +31,181 @@ JavaScript作为一种基于原型的弱类型动态语言,使得有些时候 > 需要注意的是,有的时候当人们在JavaScript中提出“单例”的时候,它们可能是在指第5章讨论过的“模块模式”。 +### 使用new + +JavaScript没有类,所以一字一句地说单例的定义并没有什么意义。但是JavaScript有使用new、通过构造函数来创建对象的语法,有时候你可能需要这种语法下的一个单例实现。这也就是说当你使用new、通过同一个构造函数来创建多个对象的时候,你应该只是得到同一个对象的不同引用。 + +> 温馨提示:从一个实用模式的角度来说,下面的讨论并不是那么有用,只是更多地在实践模拟一些语言中关于这个模式的一些问题的解决方案。这些语言主要是(静态强类型的)基于类的语言,在这些语言中,函数并不是“一等公民”。 + +下面的代码片段展示了期望的结果(假设你忽略了多元宇宙的设想,接受了只有一个宇宙的观点): + + var uni = new Universe(); + var uni2 = new Universe(); + uni === uni2; // true + +在这个例子中,uni只在构造函数第一次被调用时创建。第二次(以及后续更多次)调用时,同一个uni对象被返回。这就是为什么uni === uni2的原因——因为它们实际上是同一个对象的两个引用。那么怎么在JavaScript达到这个效果呢? + +当对象实例this被创建时,你需要在Universe构造函数中缓存它,以便在第二次调用的时候返回。有几种选择可以达到这种效果: + +- 你可以使用一个全局变量来存储实例。不推荐使用这种方法,因为通常我们认为使用全局变量是不好的。而且,任何人都可以改写全局变量的值,甚至可能是无意中改写。所以我们不再讨论这种方案。 +- 你也可以将对象实例缓存在构造函数的属性中。在JavaScript中,函数也是对象,所以它们也可以有属性。你可以写一些类似Universe.instance的属性来缓存对象。这是一种漂亮干净的解决方案,不足之处是instance属性仍然是可以被公开访问的,别人写的代码可能修改它,这样就会失去这个实例。 +- 你可以将实例包裹在闭包中。这可以保持实例是私有的,不会在构造函数之外被修改,代价是一个额外的闭包。 + +让我们来看一下第二种和第三种方案的实现示例。 + +### 将实例放到静态属性中 + +下面是一个将唯一的实例放入Universe构造函数的一个静态属性中的例子: + + function Universe() { + + // do we have an existing instance? + if (typeof Universe.instance === "object") { + return Universe.instance; + } + + // proceed as normal + this.start_time = 0; + this.bang = "Big"; + + // cache + Universe.instance = this; + + // implicit return: + // return this; + } + + // testing + var uni = new Universe(); + var uni2 = new Universe(); + uni === uni2; // true + +如你所见,这是一种直接有效的解决方案,唯一的缺陷是instance是可被公开访问的。一般来说它被其它代码误删改的可能是很小的(起码比全局变量instance要小得多),但是仍然是有可能的。 + +### 将实例放入闭包中 + +另一种实现基于类的单例模式的方法是使用一个闭包来保护这个唯一的实例。你可以通过第5章讨论过的“私有静态成员模式”来实现。唯一的秘密就是重写构造函数: + + function Universe() { + + // the cached instance + var instance = this; + + // proceed as normal + this.start_time = 0; + this.bang = "Big"; + + // rewrite the constructor + Universe = function () { + return instance; + }; + } + + // testing + var uni = new Universe(); + var uni2 = new Universe(); + uni === uni2; // true + +第一次调用时,原始的构造函数被调用并且正常返回this。在后续的调用中,被重写的构造函数被调用。被重写怕这个构造函数可以通过闭包访问私有的instance变量并且将它返回。 + +这个实现实际上也是第4章讨论的自定义函数的又一个例子。如我们讨论过的一样,这种模式的缺点是被重写的函数(在这个例子中就是构造函数Universe())将丢失那些在初始定义和重新定义之间添加的属性。在这个例子中,任何添加到Universe()的原型上的属性将不会被链接到使用原来的实现创建的实例上。(注:这里的“原来的实现”是指实例是由未被重写的构造函数创建的,而Universe()则是被重写的构造函数。) + +下面我们通过一些测试来展示这个问题: + + // adding to the prototype + Universe.prototype.nothing = true; + + var uni = new Universe(); + + // again adding to the prototype + // after the initial object is created + Universe.prototype.everything = true; + + var uni2 = new Universe(); + + Testing: + // only the original prototype was + // linked to the objects + uni.nothing; // true + uni2.nothing; // true + uni.everything; // undefined + uni2.everything; // undefined + + // that sounds right: + uni.constructor.name; // "Universe" + + // but that's odd: + uni.constructor === Universe; // false + +uni.constructor不再和Universe()相同的原因是uni.constructor仍然是指向原来的构造函数,而不是被重新定义的那个。 + +如果一定被要求让prototype和constructor的指向像我们期望的那样,可以通过一些调整来做到: + + function Universe() { + + // the cached instance + var instance; + + // rewrite the constructor + Universe = function Universe() { + return instance; + }; + + // carry over the prototype properties + Universe.prototype = this; + + // the instance + instance = new Universe(); + + // reset the constructor pointer + instance.constructor = Universe; + + // all the functionality + instance.start_time = 0; + instance.bang = "Big"; + + return instance; + } + +现在所有的测试结果都可以像我们期望的那样了: + + // update prototype and create instance + Universe.prototype.nothing = true; // true + var uni = new Universe(); + Universe.prototype.everything = true; // true + var uni2 = new Universe(); + + // it's the same single instance + uni === uni2; // true + + // all prototype properties work + // no matter when they were defined + uni.nothing && uni.everything && uni2.nothing && uni2.everything; // true + // the normal properties work + uni.bang; // "Big" + // the constructor points correctly + uni.constructor === Universe; // true + +另一种可选的解决方案是将构造函数和实例包在一个立即执行的函数中。当构造函数第一次被调用的时候,它返回一个对象并且将私有的instance指向它。在后续调用时,构造函数只是简单地返回这个私有变量。在这种新的实现下,前面所有的测试代码也会和期望的一样: + + var Universe; + + (function () { + + var instance; + + Universe = function Universe() { + + if (instance) { + return instance; + } + + instance = this; + + // all the functionality + this.start_time = 0; + this.bang = "Big"; + + }; + + }()); \ No newline at end of file From 1755545c1c32ddae54914c067da969dc7fb2d202 Mon Sep 17 00:00:00 2001 From: TooBug Date: Thu, 15 Nov 2012 00:29:52 -0500 Subject: [PATCH 054/258] =?UTF-8?q?=E7=AC=AC7=E7=AB=A0=20=E5=B7=A5?= =?UTF-8?q?=E5=8E=82=E6=A8=A1=E5=BC=8F=E4=B8=80=E8=8A=82=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 83 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/chapter7.markdown b/chapter7.markdown index dc4d613..185f1cd 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -208,4 +208,85 @@ uni.constructor不再和Universe()相同的原因是uni.constructor仍然是指 }; - }()); \ No newline at end of file + }()); + +## 工厂模式 + +使用工厂模式的目的就是创建对象。它通常被在类或者类的静态方法中实现,目的是: + +- 执行在建立相似的对象时进行的一些重复操作 +- 让工厂的使用者在编译阶段创建对象时不必知道它的特定类型(类) + +第二点在静态的基于类的语言中更重要,因为在(编译阶段)提前不知道类的情况下,创建类的实例是不普通的行为。但在JavaScript中,这部分的实现却是相当容易的事情。 + +使用工厂方法(或类)创建的对象被设计为从同一个父对象继承;它们是特定的实现一些特殊功能的子类。有些时候这个共同的父对象就是包含工厂方法的同一个类。 + +我们来看一个示例实现,我们有: + +- 一个共同的父构造函数CarMaker。 +- CarMaker的一个静态方法叫factory(),用来创建car对象。 +- 特定的从CarMaker继承而来的构造函数CarMaker.Compact,CarMaker.SUV,CarMaker.Convertible。它们都被定义为父构造函数的静态属性以便保持全局空间干净,同时在需要的时候我们也知道在哪里找到它们。 + +我们来看一下已经完成的实现会怎么被使用: + + var corolla = CarMaker.factory('Compact'); + var solstice = CarMaker.factory('Convertible'); + var cherokee = CarMaker.factory('SUV'); + corolla.drive(); // "Vroom, I have 4 doors" + solstice.drive(); // "Vroom, I have 2 doors" + cherokee.drive(); // "Vroom, I have 17 doors" + +这一段: + + var corolla = CarMaker.factory('Compact'); + +可能是工厂模式中最知名的。你有一个方法可以在运行时接受一个表示类型的字符串,然后它创建并返回了一个和请求的类型一样的对象。这里没有使用new的构造函数,也没有看到任何对象字面量,仅仅只有一个函数根据一个字符串指定的类型创建了对象。 + +这里是一个工厂模式的示例实现,它能让上面的代码片段工作: + + // parent constructor + function CarMaker() {} + + // a method of the parent + CarMaker.prototype.drive = function () { + return "Vroom, I have " + this.doors + " doors"; + }; + + // the static factory method + CarMaker.factory = function (type) { + var constr = type, + newcar; + + // error if the constructor doesn't exist + if (typeof CarMaker[constr] !== "function") { + throw { + name: "Error", + message: constr + " doesn't exist" + }; + } + + // at this point the constructor is known to exist + // let's have it inherit the parent but only once + if (typeof CarMaker[constr].prototype.drive !== "function") { + CarMaker[constr].prototype = new CarMaker(); + } + // create a new instance + newcar = new CarMaker[constr](); + // optionally call some methods and then return... + return newcar; + }; + + // define specific car makers + CarMaker.Compact = function () { + this.doors = 4; + }; + CarMaker.Convertible = function () { + this.doors = 2; + }; + CarMaker.SUV = function () { + this.doors = 24; + }; + +工厂模式的实现中没有什么是特别困难的。你需要做的仅仅是寻找请求类型的对象的构造函数。在这个例子中,使用了一个简单的名字转换以便映射对象类型和创建对象的构造函数。继承的部分只是一个公共的重复代码片段的示例,它可以被放到工厂方法中而不是被每个构造函数的类型重复。(译注:指通过原型继承的代码可以在factory方法以外执行,而不是放到factory中每调用一次都要执行一次。) + + From ba6d9f9d9e7ab846ecb041738d91d473f377bc70 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 16 Nov 2012 00:02:35 -0500 Subject: [PATCH 055/258] =?UTF-8?q?=E7=AC=AC7=E7=AB=A0=20=E5=86=85?= =?UTF-8?q?=E7=BD=AE=E5=AF=B9=E8=B1=A1=E5=B7=A5=E5=8E=82=E4=B8=80=E5=B0=8F?= =?UTF-8?q?=E8=8A=82=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/chapter7.markdown b/chapter7.markdown index 185f1cd..f3eb3ef 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -289,4 +289,22 @@ uni.constructor不再和Universe()相同的原因是uni.constructor仍然是指 工厂模式的实现中没有什么是特别困难的。你需要做的仅仅是寻找请求类型的对象的构造函数。在这个例子中,使用了一个简单的名字转换以便映射对象类型和创建对象的构造函数。继承的部分只是一个公共的重复代码片段的示例,它可以被放到工厂方法中而不是被每个构造函数的类型重复。(译注:指通过原型继承的代码可以在factory方法以外执行,而不是放到factory中每调用一次都要执行一次。) +### 内置对象工厂 + +作为一个“野生的工厂”的例子,我们来看一下内置的全局构造函数Object()。它的行为很像工厂,因为它根据不同的输入创建不同的对象。如果传入一个数字,它会使用Number()构造函数创建一个对象。在传入字符串和布尔值的时候也会发生同样的事情。任何其它的值(包括空值)将会创建一个正常的对象。 + +下面是这种行为的例子和测试,注意Object调用时可以不用加new: + + var o = new Object(), + n = new Object(1), + s = Object('1'), + b = Object(true); + + // test + o.constructor === Object; // true + n.constructor === Number; // true + s.constructor === String; // true + b.constructor === Boolean; // true + +Object()也是一个工厂这一事实可能没有太多实际用处,仅仅是觉得值得作为一个例子提一下,告诉我们工厂模式是随处可见的。 From d0b88d4c2447b52a5ee5b2a171458ed26a245c2d Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 19 Nov 2012 00:30:35 -0500 Subject: [PATCH 056/258] =?UTF-8?q?=E7=AC=AC=E4=B8=83=E7=AB=A0=20=E8=A3=85?= =?UTF-8?q?=E9=A5=B0=E5=99=A8=E6=A8=A1=E5=BC=8F=E7=BF=BB=E8=AF=91=E4=B8=80?= =?UTF-8?q?=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/chapter7.markdown b/chapter7.markdown index f3eb3ef..4b35d0a 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -308,3 +308,49 @@ uni.constructor不再和Universe()相同的原因是uni.constructor仍然是指 Object()也是一个工厂这一事实可能没有太多实际用处,仅仅是觉得值得作为一个例子提一下,告诉我们工厂模式是随处可见的。 +## 迭代器 + +在迭代器模式中,你有一些含有有序聚合数据的对象。这些数据可能在内部用一种复杂的结构存储着,但是你希望提供一种简单的方法来访问这种结构中的每个元素。数据的使用者不需要知道你是怎样组织你的数据的,他们只需要操作一个个独立的元素。 + +在迭代器模式中,你的对象需要提供一个next()方法。按顺序调用next()方法必须返回序列中的下一个元素,但是“下一个”在你的特定的数据结构中指什么是由你自己来决定的。 + +假设你的对象叫agg,你可以通过简单地在循环中调用next()来访问每个数据元素,像这样: + + var element; + while (element = agg.next()) { + // do something with the element ... + console.log(element); + } + +在迭代器模式中,聚合对象通常也会提供一个方便的方法hasNext(),这样对象的使用者就可以知道他们已经获取到你数据的最后一个元素。当使用另一种方法——hasNext()——来按顺序访问所有元素时,是像这样的: + + while (agg.hasNext()) { + // do something with the next element... + console.log(agg.next()); + } + +## 装饰器 + +在装饰器模式中,一些额外的功能可以在运行时被动态地添加到一个对象中。在静态的基于类的语言中,处理这个问题可能是个挑战,但是在JavaScript中,对象本来就是可变的,所以给一个对象添加额外的功能本身并不是什么问题。 + +装饰器模式的一个很方便的特性是可以对我们需要的特性进行定制和配置。刚开始时,我们有一个拥有基本功能的对象,然后可以从可用的装饰器中去挑选一些需要用到的去增加这个对象,甚至如果顺序很重要的话,还可以指定增强的顺序。 + +### 用法 + +我们来看一下这个模式的示例用法。假设你正在做一个卖东西的web应用,每个新交易是一个新的sale对象。这个对象“知道”交易的价格并且可以通过调用sale.getPrice()方法返回。根据环境的不同,你可以开始用一些额外的功能来装饰这个对象。假设一个场景是这笔交易是发生在加拿大的一个省Québec,在这种情况下,购买者需要付联邦税和Québec省税。根据装饰器模式的用法,你需要指明使用联邦税装饰器和Québec省税装饰器来装饰这个对象。然后你还可以给这个对象装饰一些价格格式的功能。这个场景的使用方式可能是像这样: + + var sale = new Sale(100); // the price is 100 dollars + sale = sale.decorate('fedtax'); // add federal tax + sale = sale.decorate('quebec'); // add provincial tax + sale = sale.decorate('money'); // format like money + sale.getPrice(); // "$112.88" + +在另一种场景下,购买者在一个不需要交省税的省,并且你想用加拿大元的格式来显示价格,你可以这样做: + + var sale = new Sale(100); // the price is 100 dollars + sale = sale.decorate('fedtax'); // add federal tax + sale = sale.decorate('cdn'); // format using CDN + sale.getPrice(); // "CDN$ 105.00" + +如你所见,这是一种在运行时很灵活的方法来添加功能和调整对象。我们来看一下如何来实现这种模式。 + From 8c4645434a6180ed444291bb7ca92cf5b6a3f20a Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 20 Nov 2012 00:40:37 -0500 Subject: [PATCH 057/258] =?UTF-8?q?=E7=AC=AC=E4=B8=83=E7=AB=A0=20=E8=A3=85?= =?UTF-8?q?=E9=A5=B0=E5=99=A8=E6=A8=A1=E5=BC=8F=E7=BF=BB=E8=AF=91=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E4=B8=80=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Figure/chapter7/7-1.jpg | Bin 0 -> 26827 bytes chapter7.markdown | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 Figure/chapter7/7-1.jpg diff --git a/Figure/chapter7/7-1.jpg b/Figure/chapter7/7-1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c4085b5c18ab05f1682f3f4d4396759de9ea3207 GIT binary patch literal 26827 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 b>!v?rT3zJ_ Date: Tue, 20 Nov 2012 23:55:50 -0500 Subject: [PATCH 058/258] =?UTF-8?q?=E7=AC=AC7=E7=AB=A0=20=E8=A3=85?= =?UTF-8?q?=E9=A5=B0=E5=99=A8=E6=A8=A1=E5=BC=8F=E4=B8=80=E8=8A=82=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E5=AE=8C=E6=AF=95=EF=BC=88=E8=AF=91=E5=BE=97=E5=A5=BD?= =?UTF-8?q?=E7=94=9F=E6=B6=A9=EF=BC=8C=E5=90=8E=E9=9D=A2=E8=A6=81=E5=A5=BD?= =?UTF-8?q?=E5=A5=BD=E6=B6=A6=E8=89=B2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 104 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/chapter7.markdown b/chapter7.markdown index 40c7386..6d0b109 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -386,4 +386,108 @@ Object()也是一个工厂这一事实可能没有太多实际用处,仅仅是 } }; +使用类似的方法我们可以实现任意多个需要的其它装饰器。他们的实现方式像插件一样来扩展核心的Sale()的功能。他们甚至可以被放到额外的文件中,被第三方的开发者来开发和共享: + Sale.decorators.quebec = { + getPrice: function () { + var price = this.uber.getPrice(); + price += price * 7.5 / 100; + return price; + } + }; + + Sale.decorators.money = { + getPrice: function () { + return "$" + this.uber.getPrice().toFixed(2); + } + }; + + Sale.decorators.cdn = { + getPrice: function () { + return "CDN$ " + this.uber.getPrice().toFixed(2); + } + }; + +最后我们来看decorate()这个神奇的方法,它把所有上面说的片段都串起来了。记得它是这样被调用的: + + sale = sale.decorate('fedtax'); + +字符串'fedtax'对应在Sale.decorators.fedtax中实现的对象。被装饰过的最新的对象newobj将从现在有的对象(也就是this对象,它要么是原始的对象,要么是经过最后一个装饰器装饰过的对象)中继承。实现这一部分需要用到前面章节中提到的临时构造函数模式。我们也设置一个uber属性给newobj以便子对象可以访问到父对象。然后我们从装饰器中复制所有额外的属性到被装饰的对象newobj中。最后,在我们的例子中,newobj被返回并且成为被更新过的sale对象。 + + Sale.prototype.decorate = function (decorator) { + var F = function () {}, + overrides = this.constructor.decorators[decorator], + i, newobj; + F.prototype = this; + newobj = new F(); + newobj.uber = F.prototype; + for (i in overrides) { + if (overrides.hasOwnProperty(i)) { + newobj[i] = overrides[i]; + } + } + return newobj; + }; + +### 使用列表实现 + +我们来看另一个明显不同的实现方法,受益于JavaScript的动态特性,它完全不需要使用继承。同时,我们也可以简单地将前一个方面的结果作为参数传给下一个方法,而不需要每一个方法都去调用前一个方法。 + +这样的实现方法还允许很容易地反装饰(undecorating)或者撤销一个装饰,这仅仅需要从一个装饰器列表中移除一个条目。 + +用法示例也会明显简单一些,因为我们不需要将decorate()的返回值赋值给对象。在这个实现中,decorate()不对对象做任何事情,它只是简单地将装饰器加入到一个列表中: + + var sale = new Sale(100); // the price is 100 dollars + sale.decorate('fedtax'); // add federal tax + sale.decorate('quebec'); // add provincial tax + sale.decorate('money'); // format like money + sale.getPrice(); // "$112.88" + +Sale()构造函数现在有了一个作为自己属性的装饰器列表: + + function Sale(price) { + this.price = (price > 0) || 100; + this.decorators_list = []; + } + +可用的装饰器仍然被实现为Sale.decorators的属性。注意getPrice()方法现在更简单了,因为它们不需要调用父对象的getPrice()来获取结果,结果已经作为参数传递给它们了: + + Sale.decorators = {}; + + Sale.decorators.fedtax = { + getPrice: function (price) { + return price + price * 5 / 100; + } + }; + + Sale.decorators.quebec = { + getPrice: function (price) { + return price + price * 7.5 / 100; + } + }; + + Sale.decorators.money = { + getPrice: function (price) { + return "$" + price.toFixed(2); + } + }; + +最有趣的部分发生在父对象的decorate()和getPrice()方法上。在前一种实现方式中,decorate()还是多少有些复杂,而getPrice()十分简单。在这种实现方式中事情反过来了:decorate()只需要往列表中添加条目而getPrice()做了所有的工作。这些工作包括遍历现在添加的装饰器的列表,然后调用它们的getPrice()方法,并将结果传递给前一个: + + Sale.prototype.decorate = function (decorator) { + this.decorators_list.push(decorator); + }; + + Sale.prototype.getPrice = function () { + var price = this.price, + i, + max = this.decorators_list.length, + name; + for (i = 0; i < max; i += 1) { + name = this.decorators_list[i]; + price = Sale.decorators[name].getPrice(price); + } + return price; + }; + +装饰器模式的第二种实现方式更简单一些,并且没有引入继承。装饰的方法也会简单。所有的工作都由“同意”被装饰的方法来做。在这个示例实现中,getPrice()是唯一被允许装饰的方法。如果你想有更多可以被装饰的方法,那遍历装饰器列表的工作就需要由每个方法重复去做。但是,这可以很容易地被抽象到一个辅助方法中,给它传一个方法然后使这个方法“可被装饰”。如果这样实现的话,decorators_list属性就应该是一个对象,它的属性名字是方法名,值是装饰器对象的数组。 \ No newline at end of file From 7398b22542096a1477df1cef5e5cafd56c4183a1 Mon Sep 17 00:00:00 2001 From: TooBug Date: Thu, 22 Nov 2012 00:29:46 -0500 Subject: [PATCH 059/258] =?UTF-8?q?=E7=AC=AC7=E7=AB=A0=20=E7=AD=96?= =?UTF-8?q?=E7=95=A5=E6=A8=A1=E5=BC=8F=E5=AE=8C=E6=88=90=E4=B8=80=E9=83=A8?= =?UTF-8?q?=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/chapter7.markdown b/chapter7.markdown index 6d0b109..e2cb8d6 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -490,4 +490,13 @@ Sale()构造函数现在有了一个作为自己属性的装饰器列表: return price; }; -装饰器模式的第二种实现方式更简单一些,并且没有引入继承。装饰的方法也会简单。所有的工作都由“同意”被装饰的方法来做。在这个示例实现中,getPrice()是唯一被允许装饰的方法。如果你想有更多可以被装饰的方法,那遍历装饰器列表的工作就需要由每个方法重复去做。但是,这可以很容易地被抽象到一个辅助方法中,给它传一个方法然后使这个方法“可被装饰”。如果这样实现的话,decorators_list属性就应该是一个对象,它的属性名字是方法名,值是装饰器对象的数组。 \ No newline at end of file +装饰器模式的第二种实现方式更简单一些,并且没有引入继承。装饰的方法也会简单。所有的工作都由“同意”被装饰的方法来做。在这个示例实现中,getPrice()是唯一被允许装饰的方法。如果你想有更多可以被装饰的方法,那遍历装饰器列表的工作就需要由每个方法重复去做。但是,这可以很容易地被抽象到一个辅助方法中,给它传一个方法然后使这个方法“可被装饰”。如果这样实现的话,decorators_list属性就应该是一个对象,它的属性名字是方法名,值是装饰器对象的数组。 + +## 策略模式 + +策略模式允许在运行的时候选择算法。你的代码的使用者可以在处理特定任务的时候根据即将要做的事情的上下文来从一些可用的算法中选择一个。 + +使用策略模式的一个例子是解决表单验证的问题。 + +你可以创建一个validator对象,有一个validate()方法。这个方法被调用时不用区分具体的表单类型,它总是会返回同样的结果——一个没有通过验证的列表和错误信息。但是根据具体的需要验证的表单和数据,你代码的使用者可以选择进行不同类别的检查。你的validator选择最佳的策略来处理这个任务,然后将具体的数据检查工作交给合适的算法去做。 + From 0be72906ead1faa4efb04f02abdd38288c92d0fc Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 23 Dec 2012 06:56:53 -0500 Subject: [PATCH 060/258] =?UTF-8?q?=E7=AC=AC=E4=B8=83=E7=AB=A0=20=E7=AD=96?= =?UTF-8?q?=E7=95=A5=E6=A8=A1=E5=BC=8F=E4=B8=80=E8=8A=82=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 120 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 2 deletions(-) diff --git a/chapter7.markdown b/chapter7.markdown index e2cb8d6..867d5f1 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -496,7 +496,123 @@ Sale()构造函数现在有了一个作为自己属性的装饰器列表: 策略模式允许在运行的时候选择算法。你的代码的使用者可以在处理特定任务的时候根据即将要做的事情的上下文来从一些可用的算法中选择一个。 -使用策略模式的一个例子是解决表单验证的问题。 +使用策略模式的一个例子是解决表单验证的问题。你可以创建一个validator对象,有一个validate()方法。这个方法被调用时不用区分具体的表单类型,它总是会返回同样的结果——一个没有通过验证的列表和错误信息。 -你可以创建一个validator对象,有一个validate()方法。这个方法被调用时不用区分具体的表单类型,它总是会返回同样的结果——一个没有通过验证的列表和错误信息。但是根据具体的需要验证的表单和数据,你代码的使用者可以选择进行不同类别的检查。你的validator选择最佳的策略来处理这个任务,然后将具体的数据检查工作交给合适的算法去做。 +但是根据具体的需要验证的表单和数据,你代码的使用者可以选择进行不同类别的检查。你的validator选择最佳的策略来处理这个任务,然后将具体的数据检查工作交给合适的算法去做。 +### 数据验证示例 + +假设你有一个下面这样的数据,它可能来自页面上的一个表单,你希望验证它是不是有效的数据: + + var data = { + first_name: "Super", + last_name: "Man", + age: "unknown", + username: "o_O" + }; + +对这个例子中的validator,它需要知道哪个是最佳策略,因此你需要先配置它,给它设定好规则以确定哪些是有效的数据。 + +假设你不需要姓,名字可以接受任何内容,但要求年龄是一个数字,并且用户名只允许包含字母和数字。配置可能是这样的: + + validator.config = { + first_name: 'isNonEmpty', + age: 'isNumber', + username: 'isAlphaNum' + }; + +现在validator对象已经有了用来处理数据的配置,你可以调用validate()方法,然后将任何验证错误打印到控制台上: + + validator.validate(data); + if (validator.hasErrors()) { + console.log(validator.messages.join("\n")); + } + +它可能会打印出这样的信息: + + Invalid value for *age*, the value can only be a valid number, e.g. 1, 3.14 or 2010 + Invalid value for *username*, the value can only contain characters and numbers, no special symbols + +现在我们来看一下这个validator是如何实现的。所有可用的用来检查的逻辑都是拥有一个validate()方法的对象,它们还有一行辅助信息用来显示错误信息: + + // checks for non-empty values + validator.types.isNonEmpty = { + validate: function (value) { + return value !== ""; + }, + instructions: "the value cannot be empty" + }; + + // checks if a value is a number + validator.types.isNumber = { + validate: function (value) { + return !isNaN(value); + }, + instructions: "the value can only be a valid number, e.g. 1, 3.14 or 2010" + }; + + // checks if the value contains only letters and numbers + validator.types.isAlphaNum = { + validate: function (value) { + return !/[^a-z0-9]/i.test(value); + }, + instructions: "the value can only contain characters and numbers, no special symbols" + }; + +最后,validator对象的核心是这样的: + + var validator = { + + // all available checks + types: {}, + + // error messages in the current + // validation session + messages: [], + + // current validation config + // name: validation type + config: {}, + + // the interface method + // `data` is key => value pairs + validate: function (data) { + + var i, msg, type, checker, result_ok; + + // reset all messages + this.messages = []; + for (i in data) { + + if (data.hasOwnProperty(i)) { + + type = this.config[i]; + checker = this.types[type]; + + if (!type) { + continue; // no need to validate + } + if (!checker) { // uh-oh + throw { + name: "ValidationError", + message: "No handler to validate type " + type + }; + } + + result_ok = checker.validate(data[i]); + if (!result_ok) { + msg = "Invalid value for *" + i + "*, " + checker.instructions; + this.messages.push(msg); + } + } + } + return this.hasErrors(); + }, + + // helper + hasErrors: function () { + return this.messages.length !== 0; + } + }; + +如你所见,validator对象是通用的,在所有的需要验证的场景下都可以保持这个样子。改进它的办法就是增加更多类型的检查。如果你将它用在很多页面上,每快你就会有一个非常好的验证类型的集合。然后在每个新的使用场景下你需要做的仅仅是配置validator然后调用validate()方法。 \ No newline at end of file From 666b37281b15f02fd7985d7a72cdf20b97d47091 Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 23 Dec 2012 07:20:28 -0500 Subject: [PATCH 061/258] =?UTF-8?q?=E7=AC=AC=E4=B8=83=E7=AB=A0=20=E5=A4=96?= =?UTF-8?q?=E8=A7=82=E6=A8=A1=E5=BC=8F=E4=B8=80=E8=8A=82=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 51 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/chapter7.markdown b/chapter7.markdown index 867d5f1..bc90122 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -615,4 +615,53 @@ Sale()构造函数现在有了一个作为自己属性的装饰器列表: } }; -如你所见,validator对象是通用的,在所有的需要验证的场景下都可以保持这个样子。改进它的办法就是增加更多类型的检查。如果你将它用在很多页面上,每快你就会有一个非常好的验证类型的集合。然后在每个新的使用场景下你需要做的仅仅是配置validator然后调用validate()方法。 \ No newline at end of file +如你所见,validator对象是通用的,在所有的需要验证的场景下都可以保持这个样子。改进它的办法就是增加更多类型的检查。如果你将它用在很多页面上,每快你就会有一个非常好的验证类型的集合。然后在每个新的使用场景下你需要做的仅仅是配置validator然后调用validate()方法。 + +## 外观模式 + +外观模式是一种很简单的模式,它只是为对象提供了更多的可供选择的接口。使方法保持短小而不是处理太多的工作是一种很好的实践。在这种实践的指导下,你会有一大堆的方法,而不是一个有着非常多参数的uber方法。有些时候,两个或者更多的方法会经常被一起调用。在这种情况下,创建另一个将这些重复调用包裹起来的方法就变得意义了。 + +例如,在处理浏览器事件的时候,有以下的事件: + +- stopPropagation() + 阻止事件冒泡到父节点 +- preventDefault() + 阻止浏览器执行默认动作(如打开链接或者提交表单) + +这是两个有不同目的的相互独立的方法,他们也应该被保持独立,但与此同时,他们也经常被一起调用。所以为了不在应用中到处重复调用这两个方法,你可以创建一个外观方法来调用它们: + + var myevent = { + // ... + stop: function (e) { + e.preventDefault(); + e.stopPropagation(); + } + // ... + }; + +外观模式也适用于一些浏览器脚本的场景,即将浏览器的差异隐藏在一个外观方法下面。继续前面的例子,你可以添加一些处理IE中事件API的代码: + + var myevent = { + // ... + stop: function (e) { + // others + if (typeof e.preventDefault === "function") { + e.preventDefault(); + } + if (typeof e.stopPropagation === "function") { + e.stopPropagation(); + } + // IE + if (typeof e.returnValue === "boolean") { + e.returnValue = false; + } + if (typeof e.cancelBubble === "boolean") { + e.cancelBubble = true; + } + } + // ... + }; + +外观模式在做一些重新设计和重构工作时也很有用。当你想用一个不同的实现来替换某个对象的时候,你可能需要工作相当长一段时间(一个复杂的对象),与此同时,一些使用这个新对象的代码也在被同步编写。你可以先想好新对象的API,然后使用新的API创建一个外观方法在旧的对象前面。使用这种方式,当你完全替换到旧的对象的时候,你只需要修改少量客户代码,因为新的客户代码已经是在使用新的API了。 + + From ed2b68a5745b4fd419a2dd583b126ca2bcff463c Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 23 Dec 2012 07:45:05 -0500 Subject: [PATCH 062/258] =?UTF-8?q?=E7=AC=AC=E4=B8=83=E7=AB=A0=20=E4=BB=A3?= =?UTF-8?q?=E7=90=86=E6=A8=A1=E5=BC=8F=E7=BF=BB=E8=AF=91=E5=AE=8C=E4=B8=80?= =?UTF-8?q?=E5=B0=8F=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/chapter7.markdown b/chapter7.markdown index bc90122..92c0ec7 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -664,4 +664,18 @@ Sale()构造函数现在有了一个作为自己属性的装饰器列表: 外观模式在做一些重新设计和重构工作时也很有用。当你想用一个不同的实现来替换某个对象的时候,你可能需要工作相当长一段时间(一个复杂的对象),与此同时,一些使用这个新对象的代码也在被同步编写。你可以先想好新对象的API,然后使用新的API创建一个外观方法在旧的对象前面。使用这种方式,当你完全替换到旧的对象的时候,你只需要修改少量客户代码,因为新的客户代码已经是在使用新的API了。 +## 代理模式 + +在代理设计模式中,一个对象充当了另一个对象的接口的角色。它和外观模式不一样,外观模式带来的方便仅限于将几个方法调用联合起来。而代理对象位于某个对象和它的客户之间,可以保护对对象的访问。 + +这个模式看起来开销有点大,但在出于性能考虑时非常有用。代理对象可以作为对象(也叫“真正的主体”)的保护者,让真正的主体对象做尽量少的工作。 + +一种示例用法是我们称之为“懒初始化”(延迟初始化)的东西。假设初始化真正的主体是开销很大的,并且正好客户代码将它初始化后并不真正使用它。在这种情况下,代理对象可以作为真正的主体的接口起到帮助作用。代理对象接收到初始化请求,但在真正的主体真正被使用之前都不会将它传递过去。 + +图7-2展示了这个场景,当客户代码发出初始化请求时,代理对象回复一切就绪,但并没有将请求传递过去,只有在客户代码真正需要真正的主体做些工作的时候才将两个请求一起传递过去。 + +![图7-2 通过代理对象时客户代码与真正的主体的关系](./figure/chapter7/7-2.jpg) + +图7-2 通过代理对象时客户代码与真正的主体的关系 + From f645e45f9c4e51f438880ccfd370cc2c3157890d Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 24 Dec 2012 00:36:21 -0500 Subject: [PATCH 063/258] =?UTF-8?q?=E7=AC=AC=E4=B8=83=E7=AB=A0=20=E4=BB=A3?= =?UTF-8?q?=E7=90=86=E6=A8=A1=E5=BC=8F=E4=B8=80=E8=8A=82=20=E6=97=A0?= =?UTF-8?q?=E4=BB=A3=E7=90=86=E7=9A=84=E9=83=A8=E5=88=86=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 147 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/chapter7.markdown b/chapter7.markdown index 92c0ec7..7364c5b 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -678,4 +678,151 @@ Sale()构造函数现在有了一个作为自己属性的装饰器列表: 图7-2 通过代理对象时客户代码与真正的主体的关系 +### 一个例子 +在真正的主体做某件工作开销很大时,代理模式很有用处。在web应用中,开销最大的操作之一就是网络请求,此时尽可能地合并HTTP请求是有意义的。我们来看一个这种场景下应用代理模式的实例。 + +#### 一个视频列表(expando) + +我们假设有一个用来播放选中视频的应用。你可以在这里看到真实的例子(http://www.jspatterns.com/book/7/proxy.html)[http://www.jspatterns.com/book/7/proxy.html]。 + +页面上有一个视频标题的列表,当用户点击视频标题的时候,标题下方的区域会展开并显示视频的更多信息,同时也使得视频可被播放。视频的详细信息和用来播放的URL并不是页面的一部分,它们需要通过网络请求来获取。服务端可以接受多个视频ID,这样我们就可以在合适的时候通过一次请求多个视频信息来减少HTTP请求以加快应用的速度。 + +我们的应用允许一次展开好几个(或全部)视频,所以这是一个合并网络请求的绝好机会。 + +![图7-3 真实的视频列表](./figure/chapter7/7-3.jpg) + +图7-3 真实的视频列表 + +#### 没有代理对象的情况 + +这个应用中最主要的角色是两个对象: + +- videos + 负责对信息区域展开/收起(videos.getInfo()方法)和播放视频的响应(videos.getPlayer()方法) +- http + 负责通过http.makeRequest()方法与服务端通讯 + +当没有代理对象的时候,videos.getInfo()会为每个视频调用一次http.makeRequest()方法。当我们添加代理对象proxy后,它将位于vidoes和http中间,接手对makeRequest()的调用,并在可能的时候合并请求。 + +我们首先看一下没有代理对象的代码,然后添加代理对象来提升应用的响应速度。 + +#### HTML + +HTML代码仅仅是一个链接列表: + +

Toggle Checked

+
    +
  1. Gravedigger
  2. +
  3. Save Me
  4. +
  5. Crush
  6. +
  7. Don't Drink The Water
  8. +
  9. Funny the Way It Is
  10. +
  11. What Would You Say
  12. +
+ +#### 事件处理 + +现在我们来看一下事件处理的逻辑。首先我们定义一个方便的快捷函数$: + + var $ = function (id) { + return document.getElementById(id); + }; + +使用事件代理(第8章有更多关于这个模式的内容),我们将所有id="vids"的条目上的点击事件统一放到一个函数中处理: + + $('vids').onclick = function (e) { + var src, id; + + e = e || window.event; + src = e.target || e.srcElement; + + if (src.nodeName !== "A") { + return; + } + + if (typeof e.preventDefault === "function") { + e.preventDefault(); + } + e.returnValue = false; + + id = src.href.split('--')[1]; + + if (src.className === "play") { + src.parentNode.innerHTML = videos.getPlayer(id); + return; + } + + src.parentNode.id = "v" + id; + videos.getInfo(id); + }; + +#### videos对象 + +videos对象有三个方法: + +- getPlayer() + 返回播放视频需要的HTML代码(跟我们讨论的无关) +- updateList() + 网络请求的回调函数,接受从服务器返回的数据,然后生成用于视频详细信息的HTML代码。这一部分也没有什么太有趣的事情。 +- getInfo() + 这个方法切换视频信息的可视状态,同时也调用http对象的方法,并传递updaetList()作为回调函数。 + +下面是这个对象的代码片段: + + var videos = { + + getPlayer: function (id) {...}, + updateList: function (data) {...}, + + getInfo: function (id) { + + var info = $('info' + id); + + if (!info) { + http.makeRequest([id], "videos.updateList"); + return; + } + + if (info.style.display === "none") { + info.style.display = ''; + } else { + info.style.display = 'none'; + } + + } + }; + +#### http对象 + +http对象只有一个方法,它向Yahoo!的YQL服务发起一个JSONP请求: + + var http = { + makeRequest: function (ids, callback) { + var url = 'http://query.yahooapis.com/v1/public/yql?q=', + sql = 'select * from music.video.id where ids IN ("%ID%")', + format = "format=json", + handler = "callback=" + callback, + script = document.createElement('script'); + + sql = sql.replace('%ID%', ids.join('","')); + sql = encodeURIComponent(sql); + + url += sql + '&' + format + '&' + handler; + script.src = url; + + document.body.appendChild(script); + } + }; + +> YQL(Yahoo! Query Language)是一种web service,它提供了使用类似SQL的语法来调用很多其它web service的能力,使得使用者不需要学习每个service的API。 + +当所有的六个视频都被选中后,将会向服务端发起六个独立的像这样的YQL请求: + +select * from music.video.id where ids IN ("2158073") \ No newline at end of file From de86fcf0f3ca28b6d29022df36c938b5afb942fc Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 24 Dec 2012 10:09:47 -0500 Subject: [PATCH 064/258] =?UTF-8?q?=E7=AC=AC=E4=B8=83=E7=AB=A0=20=E4=BB=A3?= =?UTF-8?q?=E7=90=86=E6=A8=A1=E5=BC=8F=E7=AC=AC=E4=B8=80=E4=B8=AA=E4=BE=8B?= =?UTF-8?q?=E5=AD=90=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 76 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/chapter7.markdown b/chapter7.markdown index 7364c5b..9afcf7b 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -825,4 +825,78 @@ http对象只有一个方法,它向Yahoo!的YQL服务发起一个JSONP请求 当所有的六个视频都被选中后,将会向服务端发起六个独立的像这样的YQL请求: -select * from music.video.id where ids IN ("2158073") \ No newline at end of file +select * from music.video.id where ids IN ("2158073") + +#### 代理对象 + +前面的代码工作得很正常,但我们可以让它工作得更好。proxy对象就在这样的场景中出现,并接管了http和videos对象之间的通讯。它将使用一个简单的逻辑来尝试合并请求:50ms的延迟。videos对象并不直接调用后台接口,而是调用proxy对象的方法。proxy对象在转发这个请求前将会等待一段时间,如果在等待的50ms内有另一个来自videos的调用,则它们将被合并为同一个请求。50ms的延迟对用户来说几乎是无感知的,但是却可以用来合并请求以提升点击“toggle”时的体验,一次展开多个视频。它也可以显著降低服务器的负载,因为web服务器只需要处理更少量的请求。 + +合并后查询两个视频信息的YQL大概是这样: + + select * from music.video.id where ids IN ("2158073", "123456") + +在修改后的代码中,唯一的变化是videos.getInfo()现在调用的是proxy.makeRequest()而不是http.makeRequest(),像这样: + + proxy.makeRequest(id, videos.updateList, videos); + +proxy对象创建了一个队列来收集50ms之内接受到的视频ID,然后将这个队列传递给http对象,并提供回调函数,因为videos.updateList()只能处理一次接收到的数据。(译注:指每次传入的回调函数只能处理当次接收到的数据。) + +下面是proxy对象的代码: + + var proxy = { + ids: [], + delay: 50, + timeout: null, + callback: null, + context: null, + makeRequest: function (id, callback, context) { + // add to the queue + this.ids.push(id); + + this.callback = callback; + this.context = context; + + // set up timeout + if (!this.timeout) { + this.timeout = setTimeout(function () { + proxy.flush(); + }, this.delay); + } + }, + flush: function () { + + http.makeRequest(this.ids, "proxy.handler"); + + // clear timeout and queue + this.timeout = null; + this.ids = []; + + }, + handler: function (data) { + var i, max; + + // single video + if (parseInt(data.query.count, 10) === 1) { + proxy.callback.call(proxy.context, data.query.results.Video); + return; + } + + // multiple videos + for (i = 0, max = data.query.results.Video.length; i < max; i += 1) { + proxy.callback.call(proxy.context, data.query.results.Video[i]); + } + } + }; + +了解代理模式后就在只简单地改动一下原来的代码的情况下,将多个web service请求合并为一个。 + +图7-4和7-5展示了使用代理模式将与服务器三次数据交互(不用代理模式时)变为一次交互的过程。 + +![图7-4 与服务器三次数据交互](./figure/chapter7/7-4.jpg) + +图7-4 与服务器三次数据交互 + +![图7-5 通过一个代理对象合并请求,减少与服务器数据交互](./figure/chapter7/7-5.jpg) + +图7-5 通过一个代理对象合并请求,减少与服务器数据交互 + From 515353325c979e06be9d26db8354ea867cb269b9 Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 24 Dec 2012 23:54:29 -0500 Subject: [PATCH 065/258] =?UTF-8?q?=E7=AC=AC=E4=B8=83=E7=AB=A0=20=E4=BB=A3?= =?UTF-8?q?=E7=90=86=E6=A8=A1=E5=BC=8F=E4=B8=80=E8=8A=82=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/chapter7.markdown b/chapter7.markdown index 9afcf7b..17810bb 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -900,3 +900,12 @@ proxy对象创建了一个队列来收集50ms之内接受到的视频ID,然后 图7-5 通过一个代理对象合并请求,减少与服务器数据交互 + +#### 使用代理对象做缓存 + +在这个例子中,客户对象(videos)已经可以做到不对同一个对象重复发出请求。但现实情况中并不总是这样。这个代理对象还可以通过缓存之前的请求结果到cache属性中来进一步保护真正的主体http对象(图7-6)。然后当videos对象需要对同一个ID的视频请求第二次时,proxy对象可以直接从缓存中取出,从而避免一次网络交互。 + +![图7-6 代理缓存](./figure/chapter7/7-6.jpg) + +图7-6 代理缓存 + From 556f5a901317e15005ff29d6af381869196d09e6 Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 25 Dec 2012 00:25:04 -0500 Subject: [PATCH 066/258] =?UTF-8?q?=E7=AC=AC=E4=B8=83=E7=AB=A0=20=E4=B8=AD?= =?UTF-8?q?=E4=BB=8B=E8=80=85=E6=A8=A1=E5=BC=8F=E4=B8=80=E8=8A=82=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 119 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/chapter7.markdown b/chapter7.markdown index 17810bb..2ba2bf1 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -909,3 +909,122 @@ proxy对象创建了一个队列来收集50ms之内接受到的视频ID,然后 图7-6 代理缓存 + +## 中介者模式 + +一个应用不论大小,都是由一些彼此独立的对象组成的。所有的对象都需要一个通讯的方式来保持可维护性,即你可以安全地修改应用的一部分而不破坏其它部分。随着应用的开发和维护,会有越来越多的对象。然后,在重构代码的时候,对象可能会被移除或者被重新安排。当对象知道其它对象的太多信息并且直接通讯(直接调用彼此的方法或者修改属性)时,会导致我们不愿意看到的紧耦合。当对象耦合很紧时,要修改一个对象而不影响其它的对象是很困难的。此时甚至连一个最简单的修改都变得不那么容易,甚至连一个修改需要用多长时间都难以评估。 + +中介者模式就是一个缓解此问题的办法,它通过解耦来提升代码的可维护性(见图7-7)。在这个模式中,各个彼此合作的对象并不直接通讯,而是通过一个mediator(中介者)对象通讯。当一个对象改变了状态后,它就通知中介者,然后中介者再将这个改变告知给其它应该知道这个变化的对象。 + +![图7-7 中介者模式中的对象关系](./figure/chapter7/7-7.jpg) + +图7-7 中介者模式中的对象关系 + +### 中介者示例 + +我们来看一个使用中介者模式的实例。这个应用是一个游戏,它的玩法是比较两位游戏者在半分钟内按下按键的次数,次数多的获胜。玩家1需要按的是1,玩家2需要按的是0(这样他们的手指不会搅在一起)。当前分数会显示在一个计分板上。 + +对象列表如下: + +- Player 1 +- Player 2 +- Scoreboard +- Mediator + +中介者Mediator知道所有的对象。它与输入设备(键盘)打交道,处理keypress事件,决定现在是哪位玩家玩的,然后通知这个玩家(见图7-8)。玩家负责玩(即给自己的分数加一分),然后通知中介者他这一轮已经玩完。中介者再告知计分板最新的分数,计分板更新显示。 + +除了中介者之外,其它的对象都不知道有别的对象存在。这样就使得更新这个游戏变得很简单,比如要添加一位玩家或者是添加另外一个显示剩余时间的地方。 + +你可以在这里看到这个游戏的在线演示[http://jspatterns.com/book/7/mediator.html](http://jspatterns.com/book/7/mediator.html)。 + +![图7-8 游戏涉及的对象](./figure/chapter7/7-8.jpg) + +图7-8 游戏涉及的对象 + +玩家对象是通过Player()构造函数来创建的,有自己的points和name属性。原型上的play()方法负责给自己加一分然后通知中介者: + + function Player(name) { + this.points = 0; + this.name = name; + } + Player.prototype.play = function () { + this.points += 1; + mediator.played(); + }; + +scoreboard对象(计分板)有一个update()方法,它会在每次玩家玩完后被中介者调用。计分析根本不知道玩家的任何信息,也不保存分数,它只负责显示中介者给过来的分数: + + var scoreboard = { + + // HTML element to be updated + element: document.getElementById('results'), + + // update the score display + update: function (score) { + + var i, msg = ''; + for (i in score) { + + if (score.hasOwnProperty(i)) { + msg += '

' + i + '<\/strong>: '; + msg += score[i]; + msg += '<\/p>'; + } + } + this.element.innerHTML = msg; + } + }; + +现在我们来看一下mediator对象(中介者)。在游戏初始化的时候,在setup()方法中创建游戏者,然后放后players属性以便后续使用。played()方法会被游戏者在每轮玩完后调用,它更新score哈希然表然后将它传给scoreboard用于显示。最后一个方法是keypress(),负责处理键盘事件,决定是哪位玩家玩的,并且通知它: + + var mediator = { + + // all the players + players: {}, + + // initialization + setup: function () { + var players = this.players; + players.home = new Player('Home'); + players.guest = new Player('Guest'); + + }, + + // someone plays, update the score + played: function () { + var players = this.players, + score = { + Home: players.home.points, + Guest: players.guest.points + }; + + scoreboard.update(score); + }, + + // handle user interactions + keypress: function (e) { + e = e || window.event; // IE + if (e.which === 49) { // key "1" + mediator.players.home.play(); + return; + } + if (e.which === 48) { // key "0" + mediator.players.guest.play(); + return; + } + } + }; + +最后一件事是初始化和结束游戏: + + // go! + mediator.setup(); + window.onkeypress = mediator.keypress; + + // game over in 30 seconds + setTimeout(function () { + window.onkeypress = null; + alert('Game over!'); + }, 30000); + + From e7d53a938c7cf95ebeb2e9ecfdcc11555c7b6dd1 Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 13 Jan 2013 09:23:25 +0800 Subject: [PATCH 067/258] =?UTF-8?q?=E7=AC=AC=E4=B8=83=E7=AB=A0=20=E8=A7=82?= =?UTF-8?q?=E5=AF=9F=E8=80=85=E6=A8=A1=E5=BC=8F=E5=8F=8A=E7=A4=BA=E4=BE=8B?= =?UTF-8?q?1=E4=B8=80=E8=8A=82=E9=87=8D=E6=96=B0=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 146 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/chapter7.markdown b/chapter7.markdown index 2ba2bf1..15fa37d 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -1028,3 +1028,149 @@ scoreboard对象(计分板)有一个update()方法,它会在每次玩家 }, 30000); +## 观察者模式 + +观察者模式被广泛地应用于JavaScript客户端编程中。所有的浏览器事件(mouseover,keypress等)都是使用观察者模式的例子。这种模式的另一个名字叫“自定义事件”,意思是这些事件是被编写出来的,和浏览器触发的事件相对。它还有另外一个名字叫“订阅者/发布者”模式。 + +使用这个模式的最主要目的就是促进代码触解耦。在观察者模式中,一个对象订阅另一个对象的指定活动并得到通知,而不是调用另一个对象的方法。订阅者也被叫作观察者,被观察的对象叫作发布者或者被观察者(译注:subject,不知道如何翻译,第一次的时候译为“主体”,第二次译时觉得不妥,还是直接叫被观察者好了)。当一个特定的事件发生的时候,发布者会通知(调用)所有的订阅者,同时还可能以事件对象的形式传递一些消息。 + +### 例1:杂志订阅 + +为了理解观察者模式的实现方式,我们来看一个具体的例子。我们假设有一个发布者paper,它发行一份日报和一份月刊。无论是日报还是月刊发行,有一个名叫joe的订阅者都会收到通知。 + +paper对象有一个subscribers属性,它是一个数组,用来保存所有的订阅者。订阅的过程就仅仅是将订阅者放到这个数组中而已。当一个事件发生时,paper遍历这个订阅者列表,然后通知它们。通知的意思也就是调用订阅者对象的一个方法。因此,在订阅过程中,订阅者需要提供一个方法给paper对象的subscribe()。 + +paper对象也可以提供unsubscribe()方法,它可以将订阅者从数组中移除。paper对象的最后一个重要的方法是publish(),它负责调用订阅者的方法。总结一下,一个发布者对象需要有这些成员: + +- subscribers + 一个数组 +- subscribe() + 将订阅者加入数组 +- unsubscribe() + 从数组中移除订阅者 +- publish() + 遍历订阅者并调用它们订阅时提供的方法 + +所有三个方法都需要一个type参数,因为一个发布者可能触发好几种事件(比如同时发布杂志和报纸),而订阅者可以选择性地订阅其中的一种或几种。 + +因为这些成员对任何对象来说都是通用的,因此将它们作为独立对象的一部分提取出来是有意义的。然后,我们可以(通过混元模式)将它们复制到任何一个对象中,将这些对象转换为订阅者。 + +下面是这些发布者通用功能的一个示例实现,它定义了上面列出来的所有成员,还有一个辅助的visitSubscribers()方法: + + var publisher = { + subscribers: { + any: [] // event type: subscribers + }, + subscribe: function (fn, type) { + type = type || 'any'; + if (typeof this.subscribers[type] === "undefined") { + this.subscribers[type] = []; + } + this.subscribers[type].push(fn); + }, + unsubscribe: function (fn, type) { + this.visitSubscribers('unsubscribe', fn, type); + }, + publish: function (publication, type) { + this.visitSubscribers('publish', publication, type); + }, + visitSubscribers: function (action, arg, type) { + var pubtype = type || 'any', + subscribers = this.subscribers[pubtype], + i, + max = subscribers.length; + + for (i = 0; i < max; i += 1) { + if (action === 'publish') { + subscribers[i](arg); + } else { + if (subscribers[i] === arg) { + subscribers.splice(i, 1); + } + } + } + } + }; + +下面这个函数接受一个对象作为参数,并通过复制通用的发布者的方法将这个对象墨迹成发布者: + + function makePublisher(o) { + var i; + for (i in publisher) { + if (publisher.hasOwnProperty(i) && typeof publisher[i] === "function") { + o[i] = publisher[i]; + } + } + o.subscribers = {any: []}; + } + +现在我们来实现paper对象,它能做的事情就是发布日报和月刊: + + var paper = { + daily: function () { + this.publish("big news today"); + }, + monthly: function () { + this.publish("interesting analysis", "monthly"); + } + }; + +将paper对象变成发布者: + + makePublisher(paper); + +现在我们有了一个发布者,让我们再来看一下订阅者对象joe,它有两个方法: + + var joe = {
 + drinkCoffee: function (paper) { + console.log('Just read ' + paper); + }, + sundayPreNap: function (monthly) { + console.log('About to fall asleep reading this ' + monthly); + } + }; + +现在让joe来订阅paper: + + paper.subscribe(joe.drinkCoffee); + paper.subscribe(joe.sundayPreNap, 'monthly'); + +如你所见,joe提供了一个当默认的any事件发生时被调用的方法,还提供了另一个当monthly事件发生时被调用的方法。现在让我们来触发一些事件: + + paper.daily(); + paper.daily(); + paper.daily(); + paper.monthly(); + +这些发布行为都会调用joe的对应方法,控制台中输出的结果是: + + Just read big news today + Just read big news today + Just read big news today + About to fall asleep reading this interesting analysis + +这里值得称道的地方就是paper对象并没有硬编码写上joe,而joe也同样没有硬编码写上paper。这里也没有知道所有事情的中介者对象。所有涉及到的对象都是松耦合的,而且在不修改代码的前提下,我们可以给paper添加更多的订阅者,同时joe也可以在任何时候取消订阅。 + +让我们更进一步,将joe也变成一个发布者。(毕竟,在博客和微博上,任何人都可以是发布者。)这样,joe变成发布者之后就可以在Twitter上更新状态: + + makePublisher(joe); + joe.tweet = function (msg) { + this.publish(msg); + }; + +现在假设paper的公关部门准备通过Twitter收集读者反馈,于是它订阅了joe,提供了一个方法readTweets(): + + paper.readTweets = function (tweet) { + alert('Call big meeting! Someone ' + tweet); + }; + joe.subscribe(paper.readTweets); + +这样每当joe发出消息时,paper就会弹出警告窗口: + + joe.tweet("hated the paper today"); + +结果是一个警告窗口:“Call big meeting! Someone hated the paper today”。 + +你可以在看到完整的源代码,并且在控制台中运行这个实例。 + + From 9f131a2edead9b854afe2123f2eadc3c70860200 Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 13 Jan 2013 10:25:12 +0800 Subject: [PATCH 068/258] =?UTF-8?q?=E7=AC=AC=E4=B8=83=E7=AB=A0=20=E8=A7=82?= =?UTF-8?q?=E5=AF=9F=E8=80=85=E6=A8=A1=E5=BC=8F=E4=B8=80=E8=8A=82=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 136 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/chapter7.markdown b/chapter7.markdown index 15fa37d..48b59c2 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -1173,4 +1173,140 @@ paper对象也可以提供unsubscribe()方法,它可以将订阅者从数组 你可以在看到完整的源代码,并且在控制台中运行这个实例。 +### 例2:按键游戏 + +我们来看另一个例子。我们将实现一个和中介者模式的示例一样的按钮游戏,但这次使用观察者模式。为了让它看起来更高档,我们允许接受无限个玩家,而不限于2个。我们仍然保留用来产生玩家的Player()构造函数,也保留scoreboard对象。只有mediator会变成game对象。 + +在中介者模式中,mediator对象知道所有涉及到的对象,并且调用它们的方法。而观察者模式中的game对象不是这样,它会让对象来订阅它们感兴趣的事件。比如,scoreboard会订阅game对象的scorechange事件。 + +首先我们重新看一下通用的publisher对象,并且将它的接口做一点小修改以更贴近浏览器的情况: + +- 将publish(),subscribe(),unsubscribe()分别改为fire(),on(),remove() +- 事件的type每次都会被用到,所以把它变成三个方法的第一个参数 +- 可以给订阅者的方法额外加一个context参数,以便回调方法可以用this指向它自己所属的对象 + +新的publisher对象是这样: + + var publisher = { + subscribers: { + any: [] + }, + on: function (type, fn, context) { + type = type || 'any'; + fn = typeof fn === "function" ? fn : context[fn]; + + if (typeof this.subscribers[type] === "undefined") { + this.subscribers[type] = []; + } + this.subscribers[type].push({fn: fn, context: context || this}); + }, + remove: function (type, fn, context) { + this.visitSubscribers('unsubscribe', type, fn, context); + }, + fire: function (type, publication) { + this.visitSubscribers('publish', type, publication); + }, + visitSubscribers: function (action, type, arg, context) { + var pubtype = type || 'any', + subscribers = this.subscribers[pubtype], + i, + max = subscribers ? subscribers.length : 0; + + for (i = 0; i < max; i += 1) { + if (action === 'publish') { + subscribers[i].fn.call(subscribers[i].context, arg); + } else { + if (subscribers[i].fn === arg && subscribers[i].context === context) { + subscribers.splice(i, 1); + } + } + } + } + }; + + +新的Player()构造函数是这样: + + function Player(name, key) { + this.points = 0; + this.name = name; + this.key = key; + this.fire('newplayer', this); + } + + Player.prototype.play = function () { + this.points += 1; + this.fire('play', this); + }; + +变动的部分是这个构造函数接受key,代表这个玩家在键盘上用来按之后得分的按键。(这些键预先被硬编码过。)每次创建一个新玩家的时候,一个newplayer事件也会被触发。类似的,每次有一个玩家玩的时候,会触发play事件。 + +scoreboard对象和原来一样,它只是简单地将当前分数显示出来。 + +game对象会关注所有的玩家,这样它就可以给出分数并且触发scorechange事件。它也会订阅浏览吕中所有的keypress事件,这样它就会知道按钮对应的玩家: + +var game = { + + keys: {}, + + addPlayer: function (player) { + var key = player.key.toString().charCodeAt(0); + this.keys[key] = player; + }, + + handleKeypress: function (e) { + e = e || window.event; // IE + if (game.keys[e.which]) { + game.keys[e.which].play(); + } + }, + + handlePlay: function (player) { + var i, + players = this.keys, + score = {}; + + for (i in players) { + if (players.hasOwnProperty(i)) { + score[players[i].name] = players[i].points; + } + } + this.fire('scorechange', score); + } +}; + +用于将任意对象转变为订阅者的makePublisher()还是和之前一样。game对象会变成发布者(这样它才可以触发scorechange事件),Player.prototype也会变成发布者,以使得每个玩家对象可以触发play和newplayer事件: + + makePublisher(Player.prototype); + makePublisher(game); + +game对象订阅play和newplayer事件(以及浏览器的keypress事件),scoreboard订阅scorechange事件: + + Player.prototype.on("newplayer", "addPlayer", game); + Player.prototype.on("play", "handlePlay", game); + game.on("scorechange", scoreboard.update, scoreboard); + window.onkeypress = game.handleKeypress; + +如你所见,on()方法允许订阅者通过函数(scoreboard.update)或者是字符串("addPlayer")来指定回调函数。当有提供context(如game)时,才能通过字符串来指定回调函数。 + +初始化的最后一点工作就是动态地创建玩家对象(以及它们对象的按键),用户想要多少个就可以创建多少个: + +var playername, key; +while (1) { + playername = prompt("Add player (name)"); + if (!playername) { + break; + } + while (1) { + key = prompt("Key for " + playername + "?"); + if (key) { + break; + } + } + new Player(playername, key); +} + +这就是游戏的全部。你可以在看到完整的源代码并且试玩一下。 + +值得注意的是,在中介者模式中,mediator对象必须知道所有的对象,然后在适当的时机去调用对应的方法。而这个例子中,game对象会显得笨一些(译注:指知道的信息少一些),游戏依赖于对象去观察特写的事件然后触发相应的动作:如scoreboard观察scorechange事件。这使得对象之间的耦合更松了(对象间知道彼此的信息越少越好),而代价则是弄清事件和订阅者之间的对应关系会更困难一些。在这个例子中,所有的订阅行为都发生在代码中的同一个地方,而随着应用规模的境长,on()可能会被在各个地方调用(如在每个对象的初始化代码中)。这使得调试更困难一些,因为没有一个集中的地方来看这些代码并理解正在发生什么事情。在观察者模式中,你将不再能看到那种从开头一直跟到结尾的顺序执行方式。 From c4ac6d9d1599aad66dec3a71ec0ed94ab886d796 Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 13 Jan 2013 10:50:09 +0800 Subject: [PATCH 069/258] =?UTF-8?q?=E7=AC=AC=E4=B8=83=E7=AB=A0=20=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/chapter7.markdown b/chapter7.markdown index 48b59c2..0da3c36 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -624,8 +624,10 @@ Sale()构造函数现在有了一个作为自己属性的装饰器列表: 例如,在处理浏览器事件的时候,有以下的事件: - stopPropagation() + 阻止事件冒泡到父节点 - preventDefault() + 阻止浏览器执行默认动作(如打开链接或者提交表单) 这是两个有不同目的的相互独立的方法,他们也应该被保持独立,但与此同时,他们也经常被一起调用。所以为了不在应用中到处重复调用这两个方法,你可以创建一个外观方法来调用它们: @@ -699,8 +701,10 @@ Sale()构造函数现在有了一个作为自己属性的装饰器列表: 这个应用中最主要的角色是两个对象: - videos + 负责对信息区域展开/收起(videos.getInfo()方法)和播放视频的响应(videos.getPlayer()方法) - http + 负责通过http.makeRequest()方法与服务端通讯 当没有代理对象的时候,videos.getInfo()会为每个视频调用一次http.makeRequest()方法。当我们添加代理对象proxy后,它将位于vidoes和http中间,接手对makeRequest()的调用,并在可能的时候合并请求。 @@ -768,10 +772,13 @@ HTML代码仅仅是一个链接列表: videos对象有三个方法: - getPlayer() + 返回播放视频需要的HTML代码(跟我们讨论的无关) - updateList() + 网络请求的回调函数,接受从服务器返回的数据,然后生成用于视频详细信息的HTML代码。这一部分也没有什么太有趣的事情。 - getInfo() + 这个方法切换视频信息的可视状态,同时也调用http对象的方法,并传递updaetList()作为回调函数。 下面是这个对象的代码片段: @@ -1043,12 +1050,16 @@ paper对象有一个subscribers属性,它是一个数组,用来保存所有 paper对象也可以提供unsubscribe()方法,它可以将订阅者从数组中移除。paper对象的最后一个重要的方法是publish(),它负责调用订阅者的方法。总结一下,一个发布者对象需要有这些成员: - subscribers + 一个数组 - subscribe() + 将订阅者加入数组 - unsubscribe() + 从数组中移除订阅者 - publish() + 遍历订阅者并调用它们订阅时提供的方法 所有三个方法都需要一个type参数,因为一个发布者可能触发好几种事件(比如同时发布杂志和报纸),而订阅者可以选择性地订阅其中的一种或几种。 @@ -1310,3 +1321,34 @@ while (1) { 值得注意的是,在中介者模式中,mediator对象必须知道所有的对象,然后在适当的时机去调用对应的方法。而这个例子中,game对象会显得笨一些(译注:指知道的信息少一些),游戏依赖于对象去观察特写的事件然后触发相应的动作:如scoreboard观察scorechange事件。这使得对象之间的耦合更松了(对象间知道彼此的信息越少越好),而代价则是弄清事件和订阅者之间的对应关系会更困难一些。在这个例子中,所有的订阅行为都发生在代码中的同一个地方,而随着应用规模的境长,on()可能会被在各个地方调用(如在每个对象的初始化代码中)。这使得调试更困难一些,因为没有一个集中的地方来看这些代码并理解正在发生什么事情。在观察者模式中,你将不再能看到那种从开头一直跟到结尾的顺序执行方式。 +## 小结 + +在这章中你学习到了若干种流行的设计模式,并且也知道了如何在JavaScript中实现它们。我们讨论过的设计模式有: + +- 单例模式 + + 只创建类的唯一一个实例。我们看了好几种可以不通过构造函数和类Java语法达成单例的方法。从另一方面来说,JavaScript中所有的对象都是单例。有时候开发者说的单例是指通过模块化模式创建的对象。 +- 工厂模式 + + 一种在运行时通过指定字符串来创建指定类型对象的方法。 +- 遍历模式 + + 通过提供API来实现复杂的自定义数据结构中的遍历和导航。 +- 装饰模式 + + 在运行时通过从预先定义好的装饰器对象来给被装饰对象动态添加功能。 +- 策略模式 + + 保持接口一致的情况下选择最好的策略来完成特写类型的任务。 +- 外观模式 + + 通过包装通用的(或者设计得很差的)方法来提供一个更方便的API。 +- 代理模式 + + 包装一个对象以控制对它的访问,通过合并操作或者是只在真正需要时执行来尽量避免开销太大的操作。 +- 中介者模式 + + 通过让对象不彼此沟通,只通过一个中介者对象沟通的方法来促进解耦。 +- 观察者模式 + + 通过创建“可被观察的对象”使它在某个事件发生时通知订阅者的方式来解耦。(也叫“订阅者/发布者”或者“自定义事件”。) \ No newline at end of file From e459673be2915ae268a0d166fba537d1afcf0670 Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 13 Jan 2013 11:21:34 +0800 Subject: [PATCH 070/258] =?UTF-8?q?=E6=B7=BB=E5=8A=A0README=E7=9A=84?= =?UTF-8?q?=E9=93=BE=E6=8E=A5=EF=BC=8C=E4=BF=AE=E6=94=B9=E5=B0=91=E9=87=8F?= =?UTF-8?q?=E6=A0=87=E9=A2=98wording?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.markdown | 49 +++++++++++++++++++++++------------------------ chapter7.markdown | 26 +++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 27 deletions(-) diff --git a/README.markdown b/README.markdown index ad89f86..55c0d07 100644 --- a/README.markdown +++ b/README.markdown @@ -168,31 +168,30 @@ - [Function.prototype.bind()](javascript.patterns/blob/master/chapter6.markdown#a25) - [小结](javascript.patterns/blob/master/chapter6.markdown#a26) -## 第七章 设计模式 - -- 单体 - - 使用 new - - 静态属性中的实例 - - 闭包中的实例 -- 工厂 - - 内置对象工厂 -- 迭代器 -- 装饰者 - - 用法 - - 实现 - - 使用列表实现 -- 策略 - - 数据校验的例子 -- 外观 -- 代理 - - 一个例子 - - 作为缓存的代理 -- 中介者 - - 中介者例子 -- 观察者 - - 例子 1:杂志订阅 - - 例子 2:按键游戏 -- 小节 +## [第七章 设计模式](javascript.patterns/blob/master/chapter7.markdown#a1) + +- [单例](javascript.patterns/blob/master/chapter7.markdown#a2) + - [使用new](javascript.patterns/blob/master/chapter7.markdown#a3) + - [将实例放到静态属性中](javascript.patterns/blob/master/chapter7.markdown#a4) + - [将实例放到闭包中](javascript.patterns/blob/master/chapter7.markdown#a5) +- [工厂模式](javascript.patterns/blob/master/chapter7.markdown#a6) + - [内置对象工厂](javascript.patterns/blob/master/chapter7.markdown#a7) +- [迭代器](javascript.patterns/blob/master/chapter7.markdown#a8) +- [装饰器](javascript.patterns/blob/master/chapter7.markdown#a9) + - [用法](javascript.patterns/blob/master/chapter7.markdown#a10) + - [实现](javascript.patterns/blob/master/chapter7.markdown#a11) + - [使用列表实现](javascript.patterns/blob/master/chapter7.markdown#a12) +- [策略模式](javascript.patterns/blob/master/chapter7.markdown#a13) + - [数据验证示例](javascript.patterns/blob/master/chapter7.markdown#a14) +- [外观模式](javascript.patterns/blob/master/chapter7.markdown#a15) +- [代理模式](javascript.patterns/blob/master/chapter7.markdown#a16) + - [一个例子](javascript.patterns/blob/master/chapter7.markdown#a17) +- [中介者模式](javascript.patterns/blob/master/chapter7.markdown#a18) + - [中介者示例](javascript.patterns/blob/master/chapter7.markdown#a19) +- [观察者模式](javascript.patterns/blob/master/chapter7.markdown#a20) + - [例1:杂志订阅](javascript.patterns/blob/master/chapter7.markdown#a21) + - [例2:按键游戏](javascript.patterns/blob/master/chapter7.markdown#a22) +- [小结](javascript.patterns/blob/master/chapter7.markdown#a23) ## 第八章 DOM和浏览器模式 diff --git a/chapter7.markdown b/chapter7.markdown index 0da3c36..c8016d6 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -9,6 +9,7 @@ JavaScript作为一种基于原型的弱类型动态语言,使得有些时候 让我们从第一个例子——单例模式——来看一下在JavaScript中和静态的基于类的语言有什么不同。 + ## 单例 单例模式的核心思想是让指定的类只存在唯一一个实例。这意味着当你第二次使用相同的类去创建对象的时候,你得到的应该和第一次创建的是同一个对象。 @@ -31,6 +32,7 @@ JavaScript作为一种基于原型的弱类型动态语言,使得有些时候 > 需要注意的是,有的时候当人们在JavaScript中提出“单例”的时候,它们可能是在指第5章讨论过的“模块模式”。 + ### 使用new JavaScript没有类,所以一字一句地说单例的定义并没有什么意义。但是JavaScript有使用new、通过构造函数来创建对象的语法,有时候你可能需要这种语法下的一个单例实现。这也就是说当你使用new、通过同一个构造函数来创建多个对象的时候,你应该只是得到同一个对象的不同引用。 @@ -53,6 +55,7 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 让我们来看一下第二种和第三种方案的实现示例。 + ### 将实例放到静态属性中 下面是一个将唯一的实例放入Universe构造函数的一个静态属性中的例子: @@ -82,7 +85,8 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 如你所见,这是一种直接有效的解决方案,唯一的缺陷是instance是可被公开访问的。一般来说它被其它代码误删改的可能是很小的(起码比全局变量instance要小得多),但是仍然是有可能的。 -### 将实例放入闭包中 + +### 将实例放到闭包中 另一种实现基于类的单例模式的方法是使用一个闭包来保护这个唯一的实例。你可以通过第5章讨论过的“私有静态成员模式”来实现。唯一的秘密就是重写构造函数: @@ -210,6 +214,7 @@ uni.constructor不再和Universe()相同的原因是uni.constructor仍然是指 }()); + ## 工厂模式 使用工厂模式的目的就是创建对象。它通常被在类或者类的静态方法中实现,目的是: @@ -289,6 +294,7 @@ uni.constructor不再和Universe()相同的原因是uni.constructor仍然是指 工厂模式的实现中没有什么是特别困难的。你需要做的仅仅是寻找请求类型的对象的构造函数。在这个例子中,使用了一个简单的名字转换以便映射对象类型和创建对象的构造函数。继承的部分只是一个公共的重复代码片段的示例,它可以被放到工厂方法中而不是被每个构造函数的类型重复。(译注:指通过原型继承的代码可以在factory方法以外执行,而不是放到factory中每调用一次都要执行一次。) + ### 内置对象工厂 作为一个“野生的工厂”的例子,我们来看一下内置的全局构造函数Object()。它的行为很像工厂,因为它根据不同的输入创建不同的对象。如果传入一个数字,它会使用Number()构造函数创建一个对象。在传入字符串和布尔值的时候也会发生同样的事情。任何其它的值(包括空值)将会创建一个正常的对象。 @@ -308,6 +314,8 @@ uni.constructor不再和Universe()相同的原因是uni.constructor仍然是指 Object()也是一个工厂这一事实可能没有太多实际用处,仅仅是觉得值得作为一个例子提一下,告诉我们工厂模式是随处可见的。 + + ## 迭代器 在迭代器模式中,你有一些含有有序聚合数据的对象。这些数据可能在内部用一种复杂的结构存储着,但是你希望提供一种简单的方法来访问这种结构中的每个元素。数据的使用者不需要知道你是怎样组织你的数据的,他们只需要操作一个个独立的元素。 @@ -329,12 +337,14 @@ Object()也是一个工厂这一事实可能没有太多实际用处,仅仅是 console.log(agg.next()); } + ## 装饰器 在装饰器模式中,一些额外的功能可以在运行时被动态地添加到一个对象中。在静态的基于类的语言中,处理这个问题可能是个挑战,但是在JavaScript中,对象本来就是可变的,所以给一个对象添加额外的功能本身并不是什么问题。 装饰器模式的一个很方便的特性是可以对我们需要的特性进行定制和配置。刚开始时,我们有一个拥有基本功能的对象,然后可以从可用的装饰器中去挑选一些需要用到的去增加这个对象,甚至如果顺序很重要的话,还可以指定增强的顺序。 + ### 用法 我们来看一下这个模式的示例用法。假设你正在做一个卖东西的web应用,每个新交易是一个新的sale对象。这个对象“知道”交易的价格并且可以通过调用sale.getPrice()方法返回。根据环境的不同,你可以开始用一些额外的功能来装饰这个对象。假设一个场景是这笔交易是发生在加拿大的一个省Québec,在这种情况下,购买者需要付联邦税和Québec省税。根据装饰器模式的用法,你需要指明使用联邦税装饰器和Québec省税装饰器来装饰这个对象。然后你还可以给这个对象装饰一些价格格式的功能。这个场景的使用方式可能是像这样: @@ -354,6 +364,7 @@ Object()也是一个工厂这一事实可能没有太多实际用处,仅仅是 如你所见,这是一种在运行时很灵活的方法来添加功能和调整对象。我们来看一下如何来实现这种模式。 + ### 实现 一种实现装饰器模式的方法是让每个装饰器成为一个拥有应该被重写的方法的对象。每个装饰器实际上是继承自已经被前一个装饰器增强过的对象。装饰器的每个方法都会调用父对象(继承自的对象)的同名方法并取得值,然后做一些额外的处理。 @@ -429,6 +440,7 @@ Object()也是一个工厂这一事实可能没有太多实际用处,仅仅是 return newobj; }; + ### 使用列表实现 我们来看另一个明显不同的实现方法,受益于JavaScript的动态特性,它完全不需要使用继承。同时,我们也可以简单地将前一个方面的结果作为参数传给下一个方法,而不需要每一个方法都去调用前一个方法。 @@ -492,6 +504,7 @@ Sale()构造函数现在有了一个作为自己属性的装饰器列表: 装饰器模式的第二种实现方式更简单一些,并且没有引入继承。装饰的方法也会简单。所有的工作都由“同意”被装饰的方法来做。在这个示例实现中,getPrice()是唯一被允许装饰的方法。如果你想有更多可以被装饰的方法,那遍历装饰器列表的工作就需要由每个方法重复去做。但是,这可以很容易地被抽象到一个辅助方法中,给它传一个方法然后使这个方法“可被装饰”。如果这样实现的话,decorators_list属性就应该是一个对象,它的属性名字是方法名,值是装饰器对象的数组。 + ## 策略模式 策略模式允许在运行的时候选择算法。你的代码的使用者可以在处理特定任务的时候根据即将要做的事情的上下文来从一些可用的算法中选择一个。 @@ -500,6 +513,7 @@ Sale()构造函数现在有了一个作为自己属性的装饰器列表: 但是根据具体的需要验证的表单和数据,你代码的使用者可以选择进行不同类别的检查。你的validator选择最佳的策略来处理这个任务,然后将具体的数据检查工作交给合适的算法去做。 + ### 数据验证示例 假设你有一个下面这样的数据,它可能来自页面上的一个表单,你希望验证它是不是有效的数据: @@ -617,6 +631,7 @@ Sale()构造函数现在有了一个作为自己属性的装饰器列表: 如你所见,validator对象是通用的,在所有的需要验证的场景下都可以保持这个样子。改进它的办法就是增加更多类型的检查。如果你将它用在很多页面上,每快你就会有一个非常好的验证类型的集合。然后在每个新的使用场景下你需要做的仅仅是配置validator然后调用validate()方法。 + ## 外观模式 外观模式是一种很简单的模式,它只是为对象提供了更多的可供选择的接口。使方法保持短小而不是处理太多的工作是一种很好的实践。在这种实践的指导下,你会有一大堆的方法,而不是一个有着非常多参数的uber方法。有些时候,两个或者更多的方法会经常被一起调用。在这种情况下,创建另一个将这些重复调用包裹起来的方法就变得意义了。 @@ -666,6 +681,7 @@ Sale()构造函数现在有了一个作为自己属性的装饰器列表: 外观模式在做一些重新设计和重构工作时也很有用。当你想用一个不同的实现来替换某个对象的时候,你可能需要工作相当长一段时间(一个复杂的对象),与此同时,一些使用这个新对象的代码也在被同步编写。你可以先想好新对象的API,然后使用新的API创建一个外观方法在旧的对象前面。使用这种方式,当你完全替换到旧的对象的时候,你只需要修改少量客户代码,因为新的客户代码已经是在使用新的API了。 + ## 代理模式 在代理设计模式中,一个对象充当了另一个对象的接口的角色。它和外观模式不一样,外观模式带来的方便仅限于将几个方法调用联合起来。而代理对象位于某个对象和它的客户之间,可以保护对对象的访问。 @@ -680,6 +696,7 @@ Sale()构造函数现在有了一个作为自己属性的装饰器列表: 图7-2 通过代理对象时客户代码与真正的主体的关系 + ### 一个例子 在真正的主体做某件工作开销很大时,代理模式很有用处。在web应用中,开销最大的操作之一就是网络请求,此时尽可能地合并HTTP请求是有意义的。我们来看一个这种场景下应用代理模式的实例。 @@ -916,7 +933,7 @@ proxy对象创建了一个队列来收集50ms之内接受到的视频ID,然后 图7-6 代理缓存 - + ## 中介者模式 一个应用不论大小,都是由一些彼此独立的对象组成的。所有的对象都需要一个通讯的方式来保持可维护性,即你可以安全地修改应用的一部分而不破坏其它部分。随着应用的开发和维护,会有越来越多的对象。然后,在重构代码的时候,对象可能会被移除或者被重新安排。当对象知道其它对象的太多信息并且直接通讯(直接调用彼此的方法或者修改属性)时,会导致我们不愿意看到的紧耦合。当对象耦合很紧时,要修改一个对象而不影响其它的对象是很困难的。此时甚至连一个最简单的修改都变得不那么容易,甚至连一个修改需要用多长时间都难以评估。 @@ -927,6 +944,7 @@ proxy对象创建了一个队列来收集50ms之内接受到的视频ID,然后 图7-7 中介者模式中的对象关系 + ### 中介者示例 我们来看一个使用中介者模式的实例。这个应用是一个游戏,它的玩法是比较两位游戏者在半分钟内按下按键的次数,次数多的获胜。玩家1需要按的是1,玩家2需要按的是0(这样他们的手指不会搅在一起)。当前分数会显示在一个计分板上。 @@ -1035,12 +1053,14 @@ scoreboard对象(计分板)有一个update()方法,它会在每次玩家 }, 30000); + ## 观察者模式 观察者模式被广泛地应用于JavaScript客户端编程中。所有的浏览器事件(mouseover,keypress等)都是使用观察者模式的例子。这种模式的另一个名字叫“自定义事件”,意思是这些事件是被编写出来的,和浏览器触发的事件相对。它还有另外一个名字叫“订阅者/发布者”模式。 使用这个模式的最主要目的就是促进代码触解耦。在观察者模式中,一个对象订阅另一个对象的指定活动并得到通知,而不是调用另一个对象的方法。订阅者也被叫作观察者,被观察的对象叫作发布者或者被观察者(译注:subject,不知道如何翻译,第一次的时候译为“主体”,第二次译时觉得不妥,还是直接叫被观察者好了)。当一个特定的事件发生的时候,发布者会通知(调用)所有的订阅者,同时还可能以事件对象的形式传递一些消息。 + ### 例1:杂志订阅 为了理解观察者模式的实现方式,我们来看一个具体的例子。我们假设有一个发布者paper,它发行一份日报和一份月刊。无论是日报还是月刊发行,有一个名叫joe的订阅者都会收到通知。 @@ -1184,6 +1204,7 @@ paper对象也可以提供unsubscribe()方法,它可以将订阅者从数组 你可以在看到完整的源代码,并且在控制台中运行这个实例。 + ### 例2:按键游戏 我们来看另一个例子。我们将实现一个和中介者模式的示例一样的按钮游戏,但这次使用观察者模式。为了让它看起来更高档,我们允许接受无限个玩家,而不限于2个。我们仍然保留用来产生玩家的Player()构造函数,也保留scoreboard对象。只有mediator会变成game对象。 @@ -1321,6 +1342,7 @@ while (1) { 值得注意的是,在中介者模式中,mediator对象必须知道所有的对象,然后在适当的时机去调用对应的方法。而这个例子中,game对象会显得笨一些(译注:指知道的信息少一些),游戏依赖于对象去观察特写的事件然后触发相应的动作:如scoreboard观察scorechange事件。这使得对象之间的耦合更松了(对象间知道彼此的信息越少越好),而代价则是弄清事件和订阅者之间的对应关系会更困难一些。在这个例子中,所有的订阅行为都发生在代码中的同一个地方,而随着应用规模的境长,on()可能会被在各个地方调用(如在每个对象的初始化代码中)。这使得调试更困难一些,因为没有一个集中的地方来看这些代码并理解正在发生什么事情。在观察者模式中,你将不再能看到那种从开头一直跟到结尾的顺序执行方式。 + ## 小结 在这章中你学习到了若干种流行的设计模式,并且也知道了如何在JavaScript中实现它们。我们讨论过的设计模式有: From d35c8d75f602ab85ab35bd76783fa04df12d15be Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 13 Jan 2013 11:24:19 +0800 Subject: [PATCH 071/258] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E9=83=A8=E5=88=86?= =?UTF-8?q?=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapter7.markdown b/chapter7.markdown index c8016d6..654191d 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -703,7 +703,7 @@ Sale()构造函数现在有了一个作为自己属性的装饰器列表: #### 一个视频列表(expando) -我们假设有一个用来播放选中视频的应用。你可以在这里看到真实的例子(http://www.jspatterns.com/book/7/proxy.html)[http://www.jspatterns.com/book/7/proxy.html]。 +我们假设有一个用来播放选中视频的应用。你可以在这里看到真实的例子。 页面上有一个视频标题的列表,当用户点击视频标题的时候,标题下方的区域会展开并显示视频的更多信息,同时也使得视频可被播放。视频的详细信息和用来播放的URL并不是页面的一部分,它们需要通过网络请求来获取。服务端可以接受多个视频ID,这样我们就可以在合适的时候通过一次请求多个视频信息来减少HTTP请求以加快应用的速度。 @@ -960,7 +960,7 @@ proxy对象创建了一个队列来收集50ms之内接受到的视频ID,然后 除了中介者之外,其它的对象都不知道有别的对象存在。这样就使得更新这个游戏变得很简单,比如要添加一位玩家或者是添加另外一个显示剩余时间的地方。 -你可以在这里看到这个游戏的在线演示[http://jspatterns.com/book/7/mediator.html](http://jspatterns.com/book/7/mediator.html)。 +你可以在这里看到这个游戏的在线演示。 ![图7-8 游戏涉及的对象](./figure/chapter7/7-8.jpg) From 234d22aa19db25a0ad943d46a22b9427a0276af0 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 18 Jan 2013 17:22:24 +0800 Subject: [PATCH 072/258] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E9=94=99=E5=88=AB?= =?UTF-8?q?=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapter7.markdown b/chapter7.markdown index 654191d..eae7d4d 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -20,7 +20,7 @@ JavaScript作为一种基于原型的弱类型动态语言,使得有些时候 myprop: 'my value' }; -在JavaScript中,对象永远不会相等,除非它们是同一个对象,所以既然你创建一个看起来完全一样的对象,它也不会和前面的对象相等: +在JavaScript中,对象永远不会相等,除非它们是同一个对象,所以即使你创建一个看起来完全一样的对象,它也不会和前面的对象相等: var obj2 = { myprop: 'my value' From 30a3a733ae9f9908c9a925fbc067c03b43dfd14a Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 18 Jan 2013 17:22:44 +0800 Subject: [PATCH 073/258] =?UTF-8?q?DOM=E7=BC=96=E7=A8=8B=20=E4=B8=80?= =?UTF-8?q?=E8=8A=82=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter8.markdown | 160 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 chapter8.markdown diff --git a/chapter8.markdown b/chapter8.markdown new file mode 100644 index 0000000..4403598 --- /dev/null +++ b/chapter8.markdown @@ -0,0 +1,160 @@ +# DOM和浏览器中的模式 + +在本书的前面几章中,我们主要关注了JavaScript核心(ECMAScript),并没有涉及太多关于在浏览器中使用JavaScript的内容。在本章,我们将探索一些在浏览器环境中的模式,因为这是最常见的JavaScript程序环境。浏览器脚本编程也是大部分不喜欢JavaScript的人对这门语言的认知。这当然是可以理解,因为在浏览器中有非常多不一致的宿主对象和DOM实现。很明显,任何能够减轻客户端脚本编程的痛楚的最佳初中都是大有益处的。 + +在本章中,你会看到一些零散的模式,包括DOM编程、事件处理、远程脚本、页面脚本的加载策略以及将JavaScript部署到生产环境的步骤。 + +但首先,让我们来简要讨论一下如何做客户端脚本编程。 + +## 分离 + +在web应用开发中主要关注的有三种东西: + +- 内容 + + 即HTML文档 +- 表现 + + 指定文档样式的CSS +- 行为 + + JavaScript,用来处理用户交互和页面的动态变化 + +尽可能地将这三者分离可以加强应用在各种用户代理(译注:user agent,即为用户读取页面并呈现的软件,一般指浏览器)的可到达性(译注:delivery,指可被用户代理接受并理解的程度),比如图形浏览器、纯文本浏览器、用于残障人士的辅助技术、移动设备等等。分离常常是和渐进增强的思想一起实现的,我们从一个给最简单的用户代理的最基础的体验(纯HTML)开始,当用户代理的兼容性提升时再添加更多的可以为体验加分的东西。如果浏览器支持CSS,那么用户会看到文档更好的呈现。如果浏览器支持JavaScript,那文档会更像一个应用,提供更多的特性来增强用户体验。 + +在实践中,分离意味者: + +- 在关掉CSS的情况下测试页面,看页面是否仍然可用,内容是否可以呈现和阅读 +- 在关掉JavaScript的情况下测试页面,确保页面仍然可以完成它的主要功能,所有的链接都可以正常工作(没有href="#"的链接),表单仍然可以正常填写和提交 +- 不要使用内联的事件处理(如onclick)或者是内联的style属性,因为它们不属于内容层 +- 使用语义化的HTML元素,比如头部和列表等 + +JavaScript(行为)层的地位不应该很显赫,也就是说它不应该成为页面正常工作必须的东西,不应该使得用户在使用不支持的浏览器操作时存在障碍。它只应该被用来增强页面。 + +通常比较优雅的用来处理浏览器差异的方法是特性检测。它的思想是你不应该使用浏览器类型检测来决定代码的逻辑,而是应该检测在当前环境中你需要使用的某个方法或者是属性是否存在。浏览器检测一般认为是一种“反模式”(译注:anitpattern,指不好的模式)。虽然有的情况下不可避免要使用,但它应该是最后考虑的选择,并且应该只在特性检测没有办法给出明确答案(或者造成明显性能问题)的时候使用: + + // antipattern + if (navigator.userAgent.indexOf('MSIE') !== −1) { + document.attachEvent('onclick', console.log); + } + + // better + if (document.attachEvent) { + document.attachEvent('onclick', console.log); + } + + // or even more specific + if (typeof document.attachEvent !== "undefined") { + document.attachEvent('onclick', console.log); + } + +分离也有助于开发、维护,减少升级一个现有应用的难度,因为当出现问题的时候,你知道去看哪一块。当出现一个JavaScript错误的时候,你不需要去看HTML或者是CSS就能修复它。 + +## DOM编程 + +操作页面的DOM树是在客户端JavaScript编程中最普遍的动作。这也是导致开发者头疼的最主要原因(这也导致了JavaScript名声不好),因为DOM方法在不同的浏览器中实现得有很多差异。这也是为什么使用一个抽象了浏览器差异的JavaScript库能显著提高开发速度的原因。 + +我们来看一些在访问和修改DOM树时推荐的模式,主要考虑点是性能方面。 + +### DOM访问 + +DOM操作性能不好,这是影响JavaScript性能的最主要原因。性能不好是因为浏览器的DOM实现通常是和JavaScript引擎分离的。从浏览器的角度来讲,这样做是很有意义的,因为有可能一个JavaScript应用根本不需要DOM,而除了JavaScript之外的其它语言(如IE的VBScript)也可以用来操作页面中的DOM。 + +一个原则就是DOM访问的次数应该被减少到最低,这意味者: + +- 避免在环境中访问DOM +- 将DOM引用赋给本地变量,然后操作本地变量 +- 当可能的时候使用selectors API +- 遍历HTML collections时缓存length(见第2章) + +看下面例子中的第二个(better)循环,尽管它看起来更长一些,但却要快上几十上百倍(取决于具体浏览器): + + // antipattern + for (var i = 0; i < 100; i += 1) { + document.getElementById("result").innerHTML += i + ", "; + } + + // better - update a local variable var i, content = ""; + for (i = 0; i < 100; i += 1) { + content += i + ","; + } + document.getElementById("result").innerHTML += content; + +在下一个代码片段中,第二个例子(使用了本地变量style)更好,尽管它需要多写一行代码,还需要多定义一个变量: + + // antipattern + var padding = document.getElementById("result").style.padding, + margin = document.getElementById("result").style.margin; + + // better + var style = document.getElementById("result").style, + padding = style.padding, + margin = style.margin; + +使用selectors API是指使用这个方法: + + document.querySelector("ul .selected"); + document.querySelectorAll("#widget .class"); + +这两个方法接受一个CSS选择器字符串,返回匹配这个选择器的DOM列表(译注:querySelector只返回第一个匹配的DOM)。selectors API在现代浏览器(以及IE8+)可用,它总是会比你使用其它DOM方法来做同样的选择要快。主流的JavaScript库的最近版本都已经使用了这个API,所以你有理由去检查你的项目,确保使用的是最新版本。 + +给你经常访问的元素加上一个id属性也是有好处的,因为document.getElementById(myid)是找到一个DOM元素最容易也是最快的方法。 + +### DOM操作 + +除了访问DOM元素之外,你可能经常需要改变它们、删除其中的一些或者是添加新的元素。更新DOM会导致浏览器重绘(repaint)屏幕,也经常导致重排(reflow)(重新计算元素的位置),这些操作代价是很高的。 + +再说一次,通用的原则仍然是尽量少地更新DOM,这意味着我们可以将变化集中到一起,然后在“活动的”(live)文档树之外去执行这些变化。 + +当你需要添加一棵相对较大的子树的时候,你应该在完成这棵树的构建之后再放到文档树中。为了达到这个目的,你可以使用文档碎片(document fragment)来包含你的节点。 + +不要这样添加节点: + + // antipattern + // appending nodes as they are created + + var p, t; + + p = document.createElement('p'); + t = document.createTextNode('first paragraph'); + p.appendChild(t); + document.body.appendChild(p); + + p = document.createElement('p'); + t = document.createTextNode('second paragraph'); + p.appendChild(t); + document.body.appendChild(p); + +一个更好的版本是创建一个文档碎片,然后“离线地”(译注:即不在文档树中)更新它,当它准备好之后再将它加入文档树中。当你将文档碎片添加到DOM树中时,碎片的内容将会被添加进去,而不是碎片本身。这个特性非常好用。所以当有好几个没有被包裹在同一个父元素的节点时,文档碎片是一个很好的包裹方式。 + +下面是使用文档碎片的例子: + + var p, t, frag; + + frag = document.createDocumentFragment(); + + p = document.createElement('p'); + t = document.createTextNode('first paragraph'); + p.appendChild(t); + frag.appendChild(p); + + p = document.createElement('p'); + t = document.createTextNode('second paragraph'); + p.appendChild(t); + frag.appendChild(p); + + document.body.appendChild(frag); + +这个例子和前面例子中每段更新一次相比,文档树只被更新了一下,只导致一次重排/重绘。 + +当你添加新的节点到文档中时,文档碎片很有用。当你需要更新已有的节点时,你也可以将这些变化集中。你可以将你要修改的子树的父节点克隆一份,然后对克隆的这份做修改,完成之后再去替换原来的元素。 + + var oldnode = document.getElementById('result'), + clone = oldnode.cloneNode(true); + + // work with the clone... + + // when you're done: + oldnode.parentNode.replaceChild(clone, oldnode); + + \ No newline at end of file From 2f845ca224835c198caed6bcd9c2a2dddecbd05e Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 20 Jan 2013 00:20:55 +0800 Subject: [PATCH 074/258] =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E4=B9=8B=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E5=A4=84=E7=90=86=E5=B0=8F=E8=8A=82=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter8.markdown | 83 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/chapter8.markdown b/chapter8.markdown index 4403598..c7e2813 100644 --- a/chapter8.markdown +++ b/chapter8.markdown @@ -157,4 +157,85 @@ DOM操作性能不好,这是影响JavaScript性能的最主要原因。性能 // when you're done: oldnode.parentNode.replaceChild(clone, oldnode); - \ No newline at end of file +## 事件 + +在浏览器脚本编程中,另一块充满兼容性问题并且带来很多不愉快的区域就是浏览器事件,比如click,mouseover等等。同样的,一个JavaScript库可以解决支持IE(9以下)和W3C标准实现的双倍工作量。 + +我们来看一下一些主要的点,因为你在做一些简单的页面或者快速开发的时候可能不会使用已有的库,当然,也有可能你正在写你自己的库。 + +### 事件处理 + +麻烦是从给元素绑定事件开始的。假设你有一个按钮,点击它的时候增加计数器的值。你可以添加一个内联的onclick属性,这在所有的浏览器中都能正常工作,但是会违反分离和渐进增强的思想。所以你应该尽力在JavaScript中来做绑定,而不是在标签中。 + +假设你有下面的标签: + + + +你可以将一个函数赋给节点的onclick属性,但你只能这样做一次: + + // suboptimal solution + var b = document.getElementById('clickme'), + count = 0; + b.onclick = function () { + count += 1; + b.innerHTML = "Click me: " + count; + }; + +如果你希望在按钮点击的时候执行好几个函数,那么在维持松耦合的情况下就不能用这种方法来做绑定。从技术上讲,你可以检测onclick是否已经包含一个函数,如果已经包含,就将它加到你自己的函数中,然后替换onclick的值为你的新函数。但是一个更干净的解决方案是使用addEventListener()方法。这个方法在IE8及以下版本中不存在,在这些浏览器需要使用attachEvent()。 + +当我们回头看条件初始化模式(第4章)时,会发现一个示例实现是一个很好的解决跨浏览器事件监听的套件。现在我们不讨论细节,只看一下如何给我们的按钮绑定事件: + + var b = document.getElementById('clickme'); + if (document.addEventListener) { // W3C + b.addEventListener('click', myHandler, false); + } else if (document.attachEvent) { // IE + b.attachEvent('onclick', myHandler); + } else { // last resort + b.onclick = myHandler; + } + +现在当按钮被点击时,myHandler会被执行。让我们来让这个函数实现增加按钮文字“Click me: 0”中的数字的功能。为了更有趣一点,我们假设有好几个按钮,一个myHandler()函数来处理所有的按钮点击。如果我们可以从每次点击的事件对象中获取节点和节点对应的计数器值,那为每个按钮保持一个引用和计数器就显得不高效了。 + +我们先看一下解决方案,稍后再来做些评论: + + function myHandler(e) { + + var src, parts; + + // get event and source element + e = e || window.event; + src = e.target || e.srcElement; + + // actual work: update label + parts = src.innerHTML.split(": "); + parts[1] = parseInt(parts[1], 10) + 1; + src.innerHTML = parts[0] + ": " + parts[1]; + + // no bubble + if (typeof e.stopPropagation === "function") { + e.stopPropagation(); + } + if (typeof e.cancelBubble !== "undefined") { + e.cancelBubble = true; + } + + // prevent default action + if (typeof e.preventDefault === "function") { + e.preventDefault(); + } + if (typeof e.returnValue !== "undefined") { + e.returnValue = false; + } + + } + +一个在线的例子可以在找到。 + +在这个事件处理函数中,有四个部分: + +- 首先,我们需要访问事件对象,它包含事件的一些信息以及触发这个事件的页面元素。事件对象会被传到事件处理回调函数中,但是使用onclick属性时需要使用全局属性window.event来获取。 +- 第二部分是真正用于更新文字的部分 +- 接下来是阻止事件冒泡。在这个例子中它不是必须的,但通常情况下,如果你不阻止的话,事件会一直冒泡到文档根元素甚至window对象。同样的,我们也需要用两种方法来阻止冒泡:W3C标准方式(stopPropagation())和IE的方式(使用cancelBubble) +- 最后,如果需要的话,阻止默认行为。有一些事件(点击链接、提交表单)有默认的行为,但你可以使用preventDefault()(IE是通过设置returnValue的值为false的方式)来阻止这些默认行为。 + +如你所见,这里涉及到了很多重复性的工作,所以使用第7章讨论过的外观模式创建自己的事件处理套件是很有意义的。 \ No newline at end of file From 30d17cd04426734f92976e304764adef74f68751 Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 20 Jan 2013 11:28:23 +0800 Subject: [PATCH 075/258] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=B8=A4=E5=A4=84wor?= =?UTF-8?q?ding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index 55c0d07..ba3bec0 100644 --- a/README.markdown +++ b/README.markdown @@ -152,8 +152,8 @@ - [利用借用构造函数模式实现多继承](javascript.patterns/blob/master/chapter6.markdown#a9) - [借用构造函数的利与弊](javascript.patterns/blob/master/chapter6.markdown#a10) - [类式继承 3 ——借用并设置原型](javascript.patterns/blob/master/chapter6.markdown#a11) -- [经典模式 4 ——共享原型](javascript.patterns/blob/master/chapter6.markdown#a12) -- [经典模式 5 —— 临时构造函数](javascript.patterns/blob/master/chapter6.markdown#a13) +- [类式继承 4 ——共享原型](javascript.patterns/blob/master/chapter6.markdown#a12) +- [类式继承 5 —— 临时构造函数](javascript.patterns/blob/master/chapter6.markdown#a13) - [存储父类](javascript.patterns/blob/master/chapter6.markdown#a14) - [重置构造函数引用](javascript.patterns/blob/master/chapter6.markdown#a15) - [Klass](javascript.patterns/blob/master/chapter6.markdown#a16) From 3511f16273a2e688ec1931ea1b47060fdcb2acfc Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 20 Jan 2013 12:00:55 +0800 Subject: [PATCH 076/258] =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E4=B8=80=E8=8A=82?= =?UTF-8?q?=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter8.markdown | 56 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/chapter8.markdown b/chapter8.markdown index c7e2813..07458d0 100644 --- a/chapter8.markdown +++ b/chapter8.markdown @@ -238,4 +238,58 @@ DOM操作性能不好,这是影响JavaScript性能的最主要原因。性能 - 接下来是阻止事件冒泡。在这个例子中它不是必须的,但通常情况下,如果你不阻止的话,事件会一直冒泡到文档根元素甚至window对象。同样的,我们也需要用两种方法来阻止冒泡:W3C标准方式(stopPropagation())和IE的方式(使用cancelBubble) - 最后,如果需要的话,阻止默认行为。有一些事件(点击链接、提交表单)有默认的行为,但你可以使用preventDefault()(IE是通过设置returnValue的值为false的方式)来阻止这些默认行为。 -如你所见,这里涉及到了很多重复性的工作,所以使用第7章讨论过的外观模式创建自己的事件处理套件是很有意义的。 \ No newline at end of file +如你所见,这里涉及到了很多重复性的工作,所以使用第7章讨论过的外观模式创建自己的事件处理套件是很有意义的。 + +### 事件委托 + +事件委托是通过事件冒泡来实现的,它可以减少分散到各个节点上的事件处理函数的数量。如果有10个按钮在一个div元素中,你可以给div绑定一个事件处理函数,而不是给每个按钮都绑定一个。 + +我们来的睦一个实例,三个按钮放在一个div元素中(图8-1)。你可以在看到这个事件委托的实例。 + +![图8-1 事件委托示例:三个在点击时增加计数器值的按钮](./figure/chapter8/8-1.jpg) + +图8-1 事件委托示例:三个在点击时增加计数器值的按钮 + +结构是这样的: + +

+ + + +
+ +你可以给包裹按钮的div绑定一个事件处理函数,而不是给每个按钮绑定一个。然后你可以使用和前面的示例中一样的myHandler()函数,但需要修改一个小地方:你需要将你不感兴趣的点击排除掉。在这个例子中,你只关注按钮上的点击,而在同一个div中产生的其它的点击应该被忽略掉。 + +myHandler()的改变就是检查事件来源的nodeName是不是“button”: + + // ... + // get event and source element + e = e || window.event; + src = e.target || e.srcElement; + + if (src.nodeName.toLowerCase() !== "button") { + return; + } + // ... + +事件委托的坏处是筛选容器中感兴趣的事件使得代码看起来更多了,但好处是性能的提升和更干净的代码,这个好处明显大于坏处,因此这是一种强烈推荐的模式。 + +主流的JavaScript库通过提供方便的API的方式使得使用事件委托变得很容易。比如YUI3中有Y.delegate()方法,它允许你指定一个用来匹配包裹容器的CSS选择器和一个用于匹配你感兴趣的节点的CSS选择器。这很方便,因为如果事件发生在你不关心的元素上时,你的事件处理回调函数不会被调用。在这种情况下,绑定一个事件处理函数很简单: + + Y.delegate('click', myHandler, "#click-wrap", "button"); + +感谢YUI抽象了浏览器的差异,已经处理好了事件的来源,使得回调函数更简单了: + + function myHandler(e) { + + var src = e.currentTarget, + parts; + + parts = src.get('innerHTML').split(": "); + parts[1] = parseInt(parts[1], 10) + 1; + src.set('innerHTML', parts[0] + ": " + parts[1]); + + e.halt(); + } + +你可以在看到实例。 From 31abed62774b15223849fd947f79a884a0599499 Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 20 Jan 2013 22:54:42 +0800 Subject: [PATCH 077/258] =?UTF-8?q?=E4=B8=80=E5=A4=84=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/chapter7.markdown b/chapter7.markdown index eae7d4d..ae5b4a3 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -1323,20 +1323,20 @@ game对象订阅play和newplayer事件(以及浏览器的keypress事件),s 初始化的最后一点工作就是动态地创建玩家对象(以及它们对象的按键),用户想要多少个就可以创建多少个: -var playername, key; -while (1) { - playername = prompt("Add player (name)"); - if (!playername) { - break; - } + var playername, key; while (1) { - key = prompt("Key for " + playername + "?"); - if (key) { + playername = prompt("Add player (name)"); + if (!playername) { break; } + while (1) { + key = prompt("Key for " + playername + "?"); + if (key) { + break; + } + } + new Player(playername, key); } - new Player(playername, key); -} 这就是游戏的全部。你可以在看到完整的源代码并且试玩一下。 From ca989d5075b542c5ea9065136c5cf708f68cd0f2 Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 21 Jan 2013 13:38:16 +0800 Subject: [PATCH 078/258] =?UTF-8?q?=E9=95=BF=E6=97=B6=E9=97=B4=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E7=9A=84=E8=84=9A=E6=9C=AC=20=E4=B8=80=E8=8A=82?= =?UTF-8?q?=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter8.markdown | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/chapter8.markdown b/chapter8.markdown index 07458d0..3d17217 100644 --- a/chapter8.markdown +++ b/chapter8.markdown @@ -293,3 +293,51 @@ myHandler()的改变就是检查事件来源的nodeName是不是“button”: } 你可以在看到实例。 + +## 长时间运行的脚本 + +你可能注意到过,有时候浏览器会提示脚本运行时间过长,询问用户是否要停止执行。这种情况你当然不希望发生在自己的应用中,不管它有多复杂。 + +同时,如果脚本运行时间太长的话,浏览器的UI将变得没有响应,用户不能点击任何东西。这是一种很差的用户体验,应该尽量避免。 + +在JavaScript中没有线程,但你可以在浏览器中使用setTimeout来模拟,或者在现代浏览器中使用web workers。 + +### setTimeout() + +它的思想是将一大堆工作分解成为一小段一小段,然后每隔1毫秒运行一段。使用1毫秒的延迟会导致整个任务完成得更慢,但是用户界面会保持可响应状态,用户会觉得浏览器没有失控,觉得更舒服。 + +> 1毫秒(甚至0毫秒)的延迟执行命令在实际运行的时候会延迟更多,这取决于浏览器和操作系统。设定0毫秒的延迟并不意味着马上执行,而是指“尽快执行”。比如,在IE中,最短的延迟是15毫秒。 + +### Web Workers + +现代浏览器为长时间运行的脚本提供了另一种解决方案:web workers。web workers在浏览器内部提供了后台线程支持,你可以将计算量很大的部分放到一个单独的文件中,比如my_web_worker.js,然后从主程序(页面)中这样调用它: + + var ww = new Worker('my_web_worker.js'); + ww.onmessage = function (event) { + document.body.innerHTML += + "

message from the background thread: " + event.data + "

"; + }; + +下面展示了一个做1亿次简单的数学运算的web worker: + + var end = 1e8, tmp = 1; + + postMessage('hello there'); + + while (end) { + end -= 1; + tmp += end; + if (end === 5e7) { // 5e7 is the half of 1e8 + postMessage('halfway there, `tmp` is now ' + tmp); + } + } + + postMessage('all done'); + +web worker使用postMessage()来和调用它的程序通讯,调用者通过onmessage事件来接受更新。onmessage事件处理函数接受一个事件对象作为参数,这个对象含有一个由web worker传过来data属性。类似的,调用者(在这个例子中)也可以使用ww.postMessage()来给web worker传递数据,web worker可以通过一个onmessage事件处理函数来接受这些数据。 + +上面的例子会在浏览器中打印出: + + message from the background thread: hello there + message from the background thread: halfway there, `tmp` is now 3749999975000001 message from the background thread: all done + From 74f1517be7fc343a900dc39908cb7cbc62dd9bd9 Mon Sep 17 00:00:00 2001 From: TooBug Date: Wed, 23 Jan 2013 13:02:03 +0800 Subject: [PATCH 079/258] =?UTF-8?q?=E8=BF=9C=E7=A8=8B=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E7=BC=96=E7=A8=8B=20=E4=B9=8B=20XHR=20=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter8.markdown | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/chapter8.markdown b/chapter8.markdown index 3d17217..e82a661 100644 --- a/chapter8.markdown +++ b/chapter8.markdown @@ -341,3 +341,70 @@ web worker使用postMessage()来和调用它的程序通讯,调用者通过onm message from the background thread: hello there message from the background thread: halfway there, `tmp` is now 3749999975000001 message from the background thread: all done +## 远程脚本编程 + +现代web应用经常会使用远程脚本编程和服务器通讯,而不刷新当前页面。这使得web应用更灵活,更像桌面程序。我们来看一下几种用JavaScript和服务器通讯的方法。 + +### XMLHttpRequest + +现在,XMLHttpRequest是一个特别的对象(构造函数),绝大多数浏览器都可以用,它使得我们可以从JavaScript来发送HTTP请求。发送一个请求有以下三步: + +1. 初始化一个XMLHttpRequest对象(简称XHR) +2. 提供一个回调函数,供请求对象状态改变时调用 +3. 发送请求 + +第一步很简单: + + var xhr = new XMLHttpRequest(); + +但是在IE7之前的版本中,XHR的功能是使用ActiveX对象实现的,所以需要做一下兼容处理。 + +第二步是给readystatechange事件提供一个回调函数: + + xhr.onreadystatechange = handleResponse; + +最后一步是使用open()和send()两个方法触发请求。open()方法用于初始化HTTP请求的方法(如GET,POST)和URL。send()方法用于传递POST的数据,如果是GET方法,则是一个空字符串。open()方法的最后一个参数用于指定这个请求是不是异步的。异步是指浏览器在等待响应的时候不会阻塞,这明显是更好的用户体验,因此除非必须要同步,否则异步参数应该使用true: + + xhr.open("GET", "page.html", true); + xhr.send(); + +下面是一个完整的示例,它获取新页面的内容,然后将当前页面的内容替换掉(可以在看到示例): + + var i, xhr, activeXids = [ + 'MSXML2.XMLHTTP.3.0', + 'MSXML2.XMLHTTP', + 'Microsoft.XMLHTTP' + ]; + + if (typeof XMLHttpRequest === "function") { // native XHR + xhr = new XMLHttpRequest(); + } else { // IE before 7 + for (i = 0; i < activeXids.length; i += 1) { + try { + xhr = new ActiveXObject(activeXids[i]); + break; + } catch (e) {} + } + } + + xhr.onreadystatechange = function () { + if (xhr.readyState !== 4) { + return false; + } + if (xhr.status !== 200) { + alert("Error, status code: " + xhr.status); + return false; + } + document.body.innerHTML += "
" + xhr.responseText + "<\/pre>"; };
+
+	xhr.open("GET", "page.html", true);
+	xhr.send("");
+
+代码中的一些说明:
+
+- 因为IE6及以下版本中,创建XHR对象有一点复杂,所以我们通过一个数组列出ActiveX的名字,然后遍历这个数组,使用try-catch块来尝试创建对象。
+- 回调函数会检查xhr对象的readyState属性。这个属性有0到4一共5个值,4代表“complete”(完成)。如果状态还没有完成,我们就继续等待下一次readystatechange事件。
+- 回调函数也会检查xhr对象的status属性。这个属性和HTTP状态码对应,比如200(OK)或者是404(Not found)。我们只对状态码200感兴趣,而将其它所有的都报为错误(为了简化示例,否则需要检查其它不代表出错的状态码)。
+- 上面的代码会在每次创建XHR对象时检查一遍支持情况。你可以使用前面提到过的模式(如条件初始化)来重写上面的代码,使得只需要做一次检查。
+
+

From d02e4cb4def154c7fb0e4401fdde44176ab63005 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Wed, 23 Jan 2013 13:26:08 +0800
Subject: [PATCH 080/258] =?UTF-8?q?=E8=BF=9C=E7=A8=8B=E8=84=9A=E6=9C=AC?=
 =?UTF-8?q?=E7=BC=96=E7=A8=8B=20=E4=B9=8B=20JSONP=20=E9=99=A4=E4=BE=8B?=
 =?UTF-8?q?=E5=AD=90=E5=A4=96=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter8.markdown | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/chapter8.markdown b/chapter8.markdown
index e82a661..b12f4a7 100644
--- a/chapter8.markdown
+++ b/chapter8.markdown
@@ -407,4 +407,35 @@ web worker使用postMessage()来和调用它的程序通讯,调用者通过onm
 - 回调函数也会检查xhr对象的status属性。这个属性和HTTP状态码对应,比如200(OK)或者是404(Not found)。我们只对状态码200感兴趣,而将其它所有的都报为错误(为了简化示例,否则需要检查其它不代表出错的状态码)。
 - 上面的代码会在每次创建XHR对象时检查一遍支持情况。你可以使用前面提到过的模式(如条件初始化)来重写上面的代码,使得只需要做一次检查。
 
+### JSONP
+
+JSONP(JSON with padding)是另一种发起远程请求的方式。与XHR不同,它不受浏览器同源策略的限制,所以考虑到加载第三方站点的安全影响的问题,使用它时应该很谨慎。
+
+一个XHR请求的返回可以是任何类型的文档:
+
+- XML文档(过去很常用)
+- HTML片段(很常用)
+- JSON数据(轻量、方便)
+- 简单的文本文件及其它
+
+使用JSONP的话,数据经常是被包裹在一个函数中的JSON,函数名称在请求的时候提供。
+
+JSONP的请求URL通常是像这样:
+
+	http://example.org/getdata.php?callback=myHandler
+
+getdata.php可以是任何类型的页面或者脚本。callback参数指定用来处理响应的JavaScript函数。
+
+这个URL会被放到一个动态生成的\元素中,像这样:
+
+	var script = document.createElement("script");
+	script.src = url;
+	document.body.appendChild(script);
+
+服务器返回一些作为参数传递给回调函数的JSON数据。最终的结果实际上是页面中多了一个新的脚本,这个脚本的内容就是一个函数调用,如:
+
+	myHandler({"hello": "world"});
+
+(译注:原文这里说得不是太明白。JSONP的返回内容如上面的代码片段,它的工作原理是在页面中动态插入一个脚本,这个脚本的内容是函数调用+JSON数据,其中要调用的函数是在页面中已经定义好的,数据以参数的形式存在。一般情况下数据由服务端动态生成,而函数由页面生成,为了使返回的脚本能调用到正确的函数,在请求的时候一般会带上callback参数以便后台动态返回处理函数的名字。)
+
 

From e025f9a764ef728a9573f62f5d1dfadd0427933a Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Wed, 23 Jan 2013 20:16:22 +0800
Subject: [PATCH 081/258] =?UTF-8?q?JSONP=E4=B8=80=E8=8A=82=E7=BF=BB?=
 =?UTF-8?q?=E8=AF=91=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter8.markdown | 102 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 102 insertions(+)

diff --git a/chapter8.markdown b/chapter8.markdown
index b12f4a7..1b4d36c 100644
--- a/chapter8.markdown
+++ b/chapter8.markdown
@@ -438,4 +438,106 @@ getdata.php可以是任何类型的页面或者脚本。callback参数指定用
 
 (译注:原文这里说得不是太明白。JSONP的返回内容如上面的代码片段,它的工作原理是在页面中动态插入一个脚本,这个脚本的内容是函数调用+JSON数据,其中要调用的函数是在页面中已经定义好的,数据以参数的形式存在。一般情况下数据由服务端动态生成,而函数由页面生成,为了使返回的脚本能调用到正确的函数,在请求的时候一般会带上callback参数以便后台动态返回处理函数的名字。)
 
+#### JSONP示例:井字棋
+
+我们来看一个使用JSONP的井字棋游戏示例,玩家就是客户端(浏览器)和服务器。它们两者都会产生1到9之间的随机数,我们使用JSONP去取服务器产生的数字(图8-2)。
+
+你可以在玩这个游戏。
+
+![图8-2 使用JSONP的井字棋游戏](./figure/chapter8/8-2.jpg)
+
+图8-2 使用JSONP的井字棋游戏
+
+界面上有两个按钮:一个用于开始新游戏,一个用于取服务器下的棋(客户端下的棋会在一定数量的延时之后自动进行):
+
+	
+	
+
+界面上包含9个单元格,每个都有对应的id,比如:
+
+	 
+	 
+	 
+	...
+
+整个游戏是在一个全局对象ttt中实现:
+
+var ttt = {

+	// cells played so far
+	played: [],
+
+	// shorthand

+	get: function (id) {
+		return document.getElementById(id);
+	},
+
+	// handle clicks
+	setup: function () {
+		this.get('new').onclick = this.newGame;
+		this.get('server').onclick = this.remoteRequest;
+	},
+
+	// clean the board
+	newGame: function () {
+		var tds = document.getElementsByTagName("td"),
+			max = tds.length,

+			i;
+		for (i = 0; i < max; i += 1) {
+			tds[i].innerHTML = " ";
+		}
+		ttt.played = [];
+	},
+
+	// make a request
+	remoteRequest: function () {
+		var script = document.createElement("script");
+		script.src = "server.php?callback=ttt.serverPlay&played=" + ttt.played.join(',');
+		document.body.appendChild(script);
+	},
+
+	// callback, server's turn to play
+	serverPlay: function (data) {
+		if (data.error) {
+			alert(data.error);
+			return;
+		}
+
+		data = parseInt(data, 10);
+		this.played.push(data);
+
+		this.get('cell-' + data).innerHTML = 'X<\/span>';
+
+		setTimeout(function () {
+			ttt.clientPlay();
+		}, 300); // as if thinking hard
+	},
+
+	// client's turn to play
+	clientPlay: function () {
+		var data = 5;
+
+		if (this.played.length === 9) {
+			alert("Game over");
+			return;
+		}
+
+		// keep coming up with random numbers 1-9

+		// until one not taken cell is found

+		while (this.get('cell-' + data).innerHTML !== " ") {
+			data = Math.ceil(Math.random() * 9); }
+			this.get('cell-' + data).innerHTML = 'O';
+			this.played.push(data);
+		} 
+	};
+
+ttt对象维护着一个已经填过的单元格的列表ttt.played,并且将它发送给服务器,这样服务器就可以返回一个没有玩过的数字。如果有错误发生,服务器会像这样响应:
+
+	ttt.serverPlay({"error": "Error description here"});
+
+如你所见,JSONP中的回调函数必须是公开的并且全局可访问的函数,它并不一定要是全局函数,也可以是一个全局对象的方法。如果没有错误发生,服务器将会返回一个函数调用,像这样:
+
+	ttt.serverPlay(3);
+
+这里的3是指3号单元格是服务器要下棋的位置。在这种情况下,数据非常简单,甚至都不需要使用JSON格式,只需要一个简单的值就可以了。
+
 

From e9a55622e373a173735b64f64f32e820a7b93672 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Wed, 23 Jan 2013 20:28:37 +0800
Subject: [PATCH 082/258] =?UTF-8?q?=E8=BF=9C=E7=A8=8B=E8=84=9A=E6=9C=AC?=
 =?UTF-8?q?=E7=BC=96=E7=A8=8B=20=E4=B8=80=E8=8A=82=E7=BF=BB=E8=AF=91?=
 =?UTF-8?q?=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter8.markdown | 128 +++++++++++++++++++++++++---------------------
 1 file changed, 70 insertions(+), 58 deletions(-)

diff --git a/chapter8.markdown b/chapter8.markdown
index 1b4d36c..39ff97a 100644
--- a/chapter8.markdown
+++ b/chapter8.markdown
@@ -462,69 +462,70 @@ getdata.php可以是任何类型的页面或者脚本。callback参数指定用
 
 整个游戏是在一个全局对象ttt中实现:
 
-var ttt = {

-	// cells played so far
-	played: [],
-
-	// shorthand

-	get: function (id) {
-		return document.getElementById(id);
-	},
-
-	// handle clicks
-	setup: function () {
-		this.get('new').onclick = this.newGame;
-		this.get('server').onclick = this.remoteRequest;
-	},
-
-	// clean the board
-	newGame: function () {
-		var tds = document.getElementsByTagName("td"),
-			max = tds.length,

-			i;
-		for (i = 0; i < max; i += 1) {
-			tds[i].innerHTML = " ";
-		}
-		ttt.played = [];
-	},
-
-	// make a request
-	remoteRequest: function () {
-		var script = document.createElement("script");
-		script.src = "server.php?callback=ttt.serverPlay&played=" + ttt.played.join(',');
-		document.body.appendChild(script);
-	},
-
-	// callback, server's turn to play
-	serverPlay: function (data) {
-		if (data.error) {
-			alert(data.error);
-			return;
-		}
-
-		data = parseInt(data, 10);
-		this.played.push(data);
+	var ttt = {

+		// cells played so far
+		played: [],
+
+		// shorthand

+		get: function (id) {
+			return document.getElementById(id);
+		},
+
+		// handle clicks
+		setup: function () {
+			this.get('new').onclick = this.newGame;
+			this.get('server').onclick = this.remoteRequest;
+		},
+
+		// clean the board
+		newGame: function () {
+			var tds = document.getElementsByTagName("td"),
+				max = tds.length,

+				i;
+			for (i = 0; i < max; i += 1) {
+				tds[i].innerHTML = " ";
+			}
+			ttt.played = [];
+		},
+
+		// make a request
+		remoteRequest: function () {
+			var script = document.createElement("script");
+			script.src = "server.php?callback=ttt.serverPlay&played=" + ttt.played.join(',');
+			document.body.appendChild(script);
+		},
+
+		// callback, server's turn to play
+		serverPlay: function (data) {
+			if (data.error) {
+				alert(data.error);
+				return;
+			}
+
+			data = parseInt(data, 10);
+			this.played.push(data);
 
-		this.get('cell-' + data).innerHTML = 'X<\/span>';
+			this.get('cell-' + data).innerHTML = 'X<\/span>';
 
-		setTimeout(function () {
-			ttt.clientPlay();
-		}, 300); // as if thinking hard
-	},
+			setTimeout(function () {
+				ttt.clientPlay();
+			}, 300); // as if thinking hard
+		},
 
-	// client's turn to play
-	clientPlay: function () {
-		var data = 5;
+		// client's turn to play
+		clientPlay: function () {
+			var data = 5;
 
-		if (this.played.length === 9) {
-			alert("Game over");
-			return;
-		}
+			if (this.played.length === 9) {
+				alert("Game over");
+				return;
+			}
 
-		// keep coming up with random numbers 1-9

-		// until one not taken cell is found

-		while (this.get('cell-' + data).innerHTML !== " ") {
-			data = Math.ceil(Math.random() * 9); }
+			// keep coming up with random numbers 1-9

+			// until one not taken cell is found

+			while (this.get('cell-' + data).innerHTML !== " ") {
+				data = Math.ceil(Math.random() * 9);
+			}
 			this.get('cell-' + data).innerHTML = 'O';
 			this.played.push(data);
 		} 
@@ -540,4 +541,15 @@ ttt对象维护着一个已经填过的单元格的列表ttt.played,并且将
 
 这里的3是指3号单元格是服务器要下棋的位置。在这种情况下,数据非常简单,甚至都不需要使用JSON格式,只需要一个简单的值就可以了。
 
+### 框架(frame)和图片信标(image beacon)
+
+另外一种做远程脚本编程的方式是使用框架。你可以使用JavaScript来创建框架并改变它的src URL。新的URL可以包含数据和函数调用来更新调用者,也就是框架之外的父页面。
+
+远程脚本编程中最最简单的情况是你只需要传递一点数据给服务器,而并不需要服务器的响应内容。在这种情况下,你可以创建一个新的图片,然后将它的src指向服务器的脚本:
+
+	new Image().src = "http://example.org/some/page.php";
+
+这种模式叫作图片信标,当你想发送一些数据给服务器记录时很有用,比如做访问统计。因为信标的响应对你来说完全是没有用的,所以通常的做法(不推荐)是让服务器返回一个1x1的GIF图片。更好的做法是让服务器返回一个“204 No Content”HTTP响应。这意味着返回给客户端的响应只有响应头(header)而没有响应体(body)。
+
+
 

From 5f1cc882efe54d441ef93097008aead204d22f07 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Thu, 24 Jan 2013 13:41:00 +0800
Subject: [PATCH 083/258] =?UTF-8?q?=E9=83=A8=E7=BD=B2JavaScript=E4=B8=80?=
 =?UTF-8?q?=E8=8A=82=20=E4=B9=8B=20=E5=90=88=E5=B9=B6=E8=84=9A=E6=9C=AC=20?=
 =?UTF-8?q?=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter8.markdown | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/chapter8.markdown b/chapter8.markdown
index 39ff97a..c800a9a 100644
--- a/chapter8.markdown
+++ b/chapter8.markdown
@@ -551,5 +551,27 @@ ttt对象维护着一个已经填过的单元格的列表ttt.played,并且将
 
 这种模式叫作图片信标,当你想发送一些数据给服务器记录时很有用,比如做访问统计。因为信标的响应对你来说完全是没有用的,所以通常的做法(不推荐)是让服务器返回一个1x1的GIF图片。更好的做法是让服务器返回一个“204 No Content”HTTP响应。这意味着返回给客户端的响应只有响应头(header)而没有响应体(body)。
 
+## 部署JavaScript
+
+在生产环境中使用JavaScript时,有不少性能方面的考虑。我们来讨论一下最重要的一些。如果需要了解所有的细节,可以参见O'Reilly出社的《高性能网站建设指南》和《高性能网站建设进阶指南》。
+
+### 合并脚本
+
+创建高性能网站的第一个原则就是尽量减少外部引用的组件(译注:这里指文件),因为HTTP请求的代价是比较大的。具体就JavaScript而言,可以通过合并外部脚本来显著提高页面加载速度。
+
+我们假设你的页面正在使用jQuery库,这是一个.js文件。然后你使用了一些jQuery插件,这些插件也是单独的文件。这样的话在你还一行代码都没有写的时候就已经有了四五个文件了。把这些文件合并起来是很有意义的,尤其是其中的一些体积很小(2-3kb)时,这种情况下,HTTP协议中的开销会比下载本身还大。合并脚本的意思就是简单地创建一个新的js文件,然后把每个文件的内容粘贴进去。
+
+当然,合并的操作应该放在代码部署到生产环境之前,而不是在开发环境中,因为这会使调试变得困难。
+
+合并脚本的不便之处是:
+
+- 在部署前多了一步操作,但这很容易使用命令行自动化工具来做,比如使用Linux/Unix的cat:
+
+	$ cat jquery.js jquery.quickselect.js jquery.limit.js > all.js
+- 失去一些缓存上的便利——当你对某个文件做了一点小修改之后,会使得整个合并后的代码缓存失效。所以比较好的方法是为大的项目设定一个发布计划,或者是将代码合并为两个文件:一个包含可能会经常变更的代码,另一个包含那些不会轻易变更的“核心”。
+- 你需要处理合并后文件的命名或者是版本问题,比如使用一个时间戳all_20100426.js或者是使用文件内容的hash值。
+
+这就是主要的不便之处,但它带来的好处却是远远大于这些麻烦的。
+
 
 

From 6c75fb7aea6ed14b62ad19e4925606edb2dfc583 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Thu, 24 Jan 2013 23:01:22 +0800
Subject: [PATCH 084/258] =?UTF-8?q?=E9=83=A8=E7=BD=B2JavaScript=20?=
 =?UTF-8?q?=E4=B9=8B=20=E5=8E=8B=E7=BC=A9=E4=BB=A3=E7=A0=81=20=E7=BF=BB?=
 =?UTF-8?q?=E8=AF=91=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter8.markdown | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/chapter8.markdown b/chapter8.markdown
index c800a9a..f81449a 100644
--- a/chapter8.markdown
+++ b/chapter8.markdown
@@ -567,11 +567,26 @@ ttt对象维护着一个已经填过的单元格的列表ttt.played,并且将
 
 - 在部署前多了一步操作,但这很容易使用命令行自动化工具来做,比如使用Linux/Unix的cat:
 
-	$ cat jquery.js jquery.quickselect.js jquery.limit.js > all.js
+		$ cat jquery.js jquery.quickselect.js jquery.limit.js > all.js
 - 失去一些缓存上的便利——当你对某个文件做了一点小修改之后,会使得整个合并后的代码缓存失效。所以比较好的方法是为大的项目设定一个发布计划,或者是将代码合并为两个文件:一个包含可能会经常变更的代码,另一个包含那些不会轻易变更的“核心”。
 - 你需要处理合并后文件的命名或者是版本问题,比如使用一个时间戳all_20100426.js或者是使用文件内容的hash值。
 
 这就是主要的不便之处,但它带来的好处却是远远大于这些麻烦的。
 
+### 压缩代码
+
+第二章中,我们讨论过代码压缩。部署之前进行代码压缩也是一个很重要的步骤。
+
+从用户的角度来想,完全没有必要下载代码中的注释,因为这些注释根本不影响代码运行。
+
+压缩代码带来的好处多少取决于代码中注释和空白的数量,也取决于你使用的压缩工具。平均来说,压缩可以减少50%左右的体积。
+
+服务端脚本压缩也是应该要做的事情。配置启用gzip压缩是一个一次性的工作,能带来立杆见影的速度提升。即使你正在使用共享的空间,供应商并没有提供那么多服务器配置的空间,大部分的供应商也会允许使用.htaccess配置文件。所以可以将这些加入到站点根目录的.htaccess文件中:
+
+	AddOutputFilterByType DEFLATE text/html text/css text/plain text/xml application/javascript application/json
+
+平均下来压缩会节省70%的文件体积。将代码压缩和服务端压缩合计起来,你可以期望你的用户只下载你写出来的未压缩文件体积的15%。
+
+
 
 

From 9747e293f63e37de896b23a6d0a1123507c5476b Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Fri, 25 Jan 2013 12:57:52 +0800
Subject: [PATCH 085/258] =?UTF-8?q?=E9=83=A8=E7=BD=B2JavaScript=20?=
 =?UTF-8?q?=E4=B8=80=E8=8A=82=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter8.markdown | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/chapter8.markdown b/chapter8.markdown
index f81449a..61e9cae 100644
--- a/chapter8.markdown
+++ b/chapter8.markdown
@@ -587,6 +587,28 @@ ttt对象维护着一个已经填过的单元格的列表ttt.played,并且将
 
 平均下来压缩会节省70%的文件体积。将代码压缩和服务端压缩合计起来,你可以期望你的用户只下载你写出来的未压缩文件体积的15%。
 
+### 缓存头
+
+与流行的观点相反,文件在浏览器缓存中的时间并没有那么久。你可以尽你自己的努力,通过使用Expires头来增加非首次访问时命中缓存的概率:
+
+这也是一个在.htaccess中做的一次性配置工作:
+
+	ExpiresActive On
+	ExpiresByType application/x-javascript "access plus 10 years"
+
+它的弊端是当你想更改这个文件时,你需要给它重命名,如果你已经处理好了合并的文件命名规则,那你就已经处理好这里的命名问题了。
+
+### 使用CDN
+
+CDN是指“文件分发网络”(Content Delivery Network)。这是一项收费(有时候还相当昂贵)的托管服务,它将你的文件分发到世界上各个不同的数据中心,但代码中的URL却都是一样的,这样可以使用户更快地访问。
+
+即使你没有CDN的预算,你仍然有一些可以免费使用的东西:
+
+- Google托管了很多流行的开源库,你可以免费使用,并从它的CDN中得到速度提升(译注:鉴于Google在国内的尴尬处境,不建议使用)
+- 微软托管了jQuery和自家的Ajax库
+- 雅虎在自己的CDN上托管了YUI库
+
+
 
 
 

From b87ce1417c141b1376c40c9c44620cf151fb33ff Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Fri, 25 Jan 2013 13:27:17 +0800
Subject: [PATCH 086/258] =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E7=AD=96=E7=95=A5?=
 =?UTF-8?q?=E4=B9=8B=20script=E5=85=83=E7=B4=A0=E7=9A=84=E4=BD=8D=E7=BD=AE?=
 =?UTF-8?q?=20=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter8.markdown | 69 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)

diff --git a/chapter8.markdown b/chapter8.markdown
index 61e9cae..1c57032 100644
--- a/chapter8.markdown
+++ b/chapter8.markdown
@@ -608,7 +608,76 @@ CDN是指“文件分发网络”(Content Delivery Network)。这是一项
 - 微软托管了jQuery和自家的Ajax库
 - 雅虎在自己的CDN上托管了YUI库
 
+## 加载策略
 
+怎样在页面上引入脚本,这第一眼看起来是一个简单的问题——使用\元素,然后要么写内联的JavaScript代码或者是在src属性中指定一个独立的文件:
 
+	// option 1
+	
+	// option 2
+	
 
+但是,当你的目标是要构建一个高性能的web应用的时候,有些模式和考虑点还是应该知道的。
+
+作为题外话,来看一些比较常见的开发者会用在
+		
+		
+		
+	
+	
+		...
+	
+	
+
+一个更好的选择是将所有的文件合并起来:
+
+	
+	
+	
+		My App
+		
+	
+	
+		...
+	
+	
+
+最好的选择是将合并后的脚本放到页面的尾部:
+
+	
+	
+	
+		My App
+	
+	
+		...
+		
+	
+	
 

From 2ccb06c3b6feddca1aa9a9444657c9fa3d289725 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sat, 26 Jan 2013 15:08:14 +0800
Subject: [PATCH 087/258] =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E7=AD=96=E7=95=A5=20?=
 =?UTF-8?q?=E4=B9=8B=20HTTP=E5=88=86=E5=9D=97=20=E7=BF=BB=E8=AF=91?=
 =?UTF-8?q?=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter8.markdown | 49 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 49 insertions(+)

diff --git a/chapter8.markdown b/chapter8.markdown
index 1c57032..2e5e90f 100644
--- a/chapter8.markdown
+++ b/chapter8.markdown
@@ -681,3 +681,52 @@ script元素会阻塞页面的下载。浏览器会同时下载好几个组件
 	
 	
 
+### HTTP分块
+
+HTTP协议支持“分块编码”。它允许将页面分成一块一块发送。所以如果你有一个很复杂的页面,你不需要将那些每个站都多多少少会有的(静态)头部信息也等到所有的服务端工作都完成后再开始发送。
+
+一个简单的策略是在组装页面其余部分的时候将页面\的内容作为第一块发送。也就是像这样子:
+
+	
+	
+	
+		My App
+	
+	
+	
+		...
+		 
+	
+	
+
+这种情况下可以做一个简单的发动,将JavaScript移回\,随着第一块一起发送。
+
+这样的话可以让服务器在拿到head区内容后就开始下载脚本文件,而此时页面的其它部分在服务端还尚未就绪:
+
+	
+	
+	
+		My App
+		 
+	
+	
+	
+		...
+	
+	
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

From 6476bf690dc9fbe794bbe0802929eeeabeeaf9cf Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sat, 26 Jan 2013 15:58:50 +0800
Subject: [PATCH 088/258] =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E7=AD=96=E7=95=A5=20?=
 =?UTF-8?q?=E4=B9=8B=20=E5=8A=A8=E6=80=81
+	
+	
+	
+
+这种方法很适合使用渐进增强思想的网站(关键业务不依赖JavaScript)。当HTML的第二块发送完毕的时候,浏览器已经有了一个加载、显示完毕并且可用的页面,就像禁用JavaScript时的情况。当JavaScript随着第三块到达时,它会进一步增强页面,为页面锦上添花。
+
+### 动态\元素实现非阻塞下载
+
+前面已经说到过,JavaScript会阻塞后面文件的下载,但有一些模式可以防止阻塞:
+
+- 使用XHR加载脚本,然后作为一个字符串使用eval()来执行。这种方法受同源策略的限制,而且引入了eval()这种“反模式”。
+- 使用defer和async属性,但有浏览器兼容性问题
+- 使用动态
+
+	// becomes:
+	
+
+最后一步是使用异步加载的脚本遍历这个数组,然后执行函数:
+
+	var i, scripts = mynamespace.inline_scripts, max = scripts.length;
+	for (i = 0; i < max; max += 1) {
+		scripts[i]();
+	}
+
+#### 插入\元素
+
+通常脚本是插入到文档的中的,但其实你可以插入任何元素中,包括body(像JSONP示例中那样)。
+
+在前面的例子中,我们使用documentElement来插到\中,因为documentElement就是\,它的第一个子元素是\:
+
+	document.documentElement.firstChild.appendChild(script);
+
+通常也会这样写:
+
+	document.getElementsByTagName("head")[0].appendChild(script);
+
+当你能控制结构的时候,这样做没有问题,但是如果你在写挂件(widget)或者是广告时,你并不知道托管它的是一个什么样的页面。甚至可能页面上连\和\都没有,尽管document.body在绝大多数没有\标签的时候也可以工作:
+
+	document.body.appendChild(script);
+
+可以肯定页面上一定存在的一个标签是你正在运行的脚本所处的位置——script标签。(对内联或者外部文件来说)如果没有script标签,那么代码就不会运行。可以利用这一事实,在页面的第一个script标签上使用insertBefore():
+
+	var first_script = document.getElementsByTagName('script')[0];
+	first_script.parentNode.insertBefore(script, first_script);
+
+frist_script是页面中一定存在的一个script标签,script是你创建的新的script元素。
+
+
+
+
+
+
+
+
 
 
 

From a5c26c1fabad8644ff2d819e3fcf2451b9dd5ec4 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sat, 26 Jan 2013 21:28:37 +0800
Subject: [PATCH 089/258] =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E7=AD=96=E7=95=A5=20?=
 =?UTF-8?q?=E4=B9=8B=20=E5=BB=B6=E8=BF=9F=E5=8A=A0=E8=BD=BD=20=E7=BF=BB?=
 =?UTF-8?q?=E8=AF=91=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter8.markdown | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/chapter8.markdown b/chapter8.markdown
index fb2047b..77bcac7 100644
--- a/chapter8.markdown
+++ b/chapter8.markdown
@@ -805,6 +805,33 @@ HTTP协议支持“分块编码”。它允许将页面分成一块一块发送
 
 frist_script是页面中一定存在的一个script标签,script是你创建的新的script元素。
 
+### 延迟加载
+
+所谓的延迟加载是指在页面的load事件之后再加载外部文件。通常,将一个大的合并后的文件分成两部分是有好处的:
+
+- 一部分是页面初始化和绑定UI元素的事件处理函数必须的
+- 第二部分是只在用户交互或者其它条件下才会用到的
+
+目标就是逐步加载页面,让用户尽快可以进行一些操作。剩余的部分可以在用户可以看到页面的时候再在后台加载。
+
+加载第二部分JavaScript的方法也是使用动态script元素,将它加在head或者body中:
+
+		.. The full body of the page ...
+
+		
+		
+		
+	
+	
+	
+
+对很多应用来说,延迟加载的部分大部分情况下会比核心部分要大,因为我们关注的“行为”(比如拖放、XHR、动画)只在用户初始化之后才会发生。
 
 
 

From ce8758009a86134c758e48773b2d4a775c0052db Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sat, 26 Jan 2013 22:02:13 +0800
Subject: [PATCH 090/258] =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E7=AD=96=E7=95=A5=20?=
 =?UTF-8?q?=E4=B9=8B=20=E6=8C=89=E9=9C=80=E5=8A=A0=E8=BD=BD=20=E7=BF=BB?=
 =?UTF-8?q?=E8=AF=91=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter8.markdown | 62 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 62 insertions(+)

diff --git a/chapter8.markdown b/chapter8.markdown
index 77bcac7..b8a47c7 100644
--- a/chapter8.markdown
+++ b/chapter8.markdown
@@ -833,6 +833,68 @@ frist_script是页面中一定存在的一个script标签,script是你创建
 
 对很多应用来说,延迟加载的部分大部分情况下会比核心部分要大,因为我们关注的“行为”(比如拖放、XHR、动画)只在用户初始化之后才会发生。
 
+### 按需加载
+
+前面的模式会在页面加载后无条件加载其它的JavaScript,并假设这些代码很可能会被用到。但我们是否可以做得更好,分部分加载,在真正需要使用的时候才加载那一部分?
+
+假设你页面的侧边栏上有一些tabs。点击tab会发出一个XHR请求获取内容,然后更新tab的内容,然后有一个更新的动画。如果这是页面上唯一需要XHR和动画库的地方,而用户又不点击tab的话会怎样?
+
+下面介绍按需加载模式。你可以创建一个require()函数或者方法,它接受一个需要被加载的脚本文件的文件名,还有一个在脚本被加载完毕后执行的回调函数。
+
+require()函数可以被这样使用:
+
+	require("extra.js", function () {
+		functionDefinedInExtraJS();
+	});
+
+我们来看一下如何实现这样一个函数。加载脚本很简单——你只需要按照动态\元素模式做就可以了。获知脚本已经加载需要一点点技巧,因为浏览器之间有差异:
+
+function require(file, callback) {
+
+	var script = document.getElementsByTagName('script')[0], newjs = document.createElement('script');
+
+	// IE
+	newjs.onreadystatechange = function () {
+		if (newjs.readyState === 'loaded' || newjs.readyState === 'complete') {
+			newjs.onreadystatechange = null;
+			callback();
+		}
+	};
+
+	// others
+	newjs.onload = function () {
+		callback();
+	};
+
+	newjs.src = file;
+	script.parentNode.insertBefore(newjs, script);
+}
+
+这个实现的几点说明:
+
+- 在IE中需要订阅readystatechange事件,然后判断状态是否为“loaded”或者“complete”。其它的浏览器会忽略这里。
+- 在Firefox,Safari和Opera中,通过onload属性订阅load事件。
+- 这个方法在Safari 2中无效。如果必须要处理这个浏览器,需要设一个定时器,周期性地去检查某个指定的变量(在脚本中定义的)是否有定义。当它变成已定义时,就意味着新的脚本已经被加载并执行。
+
+你可以通过建立一个人为延迟的脚本来测试这个实现(模拟网络延迟),比如ondemand.js.php,如:
+
+	
+	function extraFunction(logthis) {
+		console.log('loaded and executed');
+		console.log(logthis);
+	}
+
+现在测试require()函数:
+
+	require('ondemand.js.php', function () {
+		extraFunction('loaded from the parent page');
+		document.body.appendChild(document.createTextNode('done!'));
+	});
+
+这段代码会在console中打印两条,然后页面中会显示“done!”,你可以在看到示例。
 
 
 

From 39affbc717d7eb2ee5f0237cfe54857521cb8b6b Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sat, 26 Jan 2013 23:03:18 +0800
Subject: [PATCH 091/258] =?UTF-8?q?=E7=AC=AC8=E7=AB=A0=E7=BF=BB=E8=AF=91?=
 =?UTF-8?q?=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter8.markdown | 48 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 47 insertions(+), 1 deletion(-)

diff --git a/chapter8.markdown b/chapter8.markdown
index b8a47c7..b801b7f 100644
--- a/chapter8.markdown
+++ b/chapter8.markdown
@@ -896,23 +896,69 @@ function require(file, callback) {
 
 这段代码会在console中打印两条,然后页面中会显示“done!”,你可以在看到示例。
 
+### 预加载JavaScript
 
+在延迟加载模式和按需加载模式中,我们加载了当前页面需要用到的脚本。除此之外,我们也可以加载当前页面不需要但可能在接下来的页面中需要的脚本。这样的话,当用户进入第二个页面时,脚本已经被预加载过,整体体验会变得更快。
 
+预加载可以简单地通过动态脚本模式实现。但这也意味着脚本会被解析和执行。解析仅仅会在页面加载时间中增加预加载消耗的时间,但执行却可能导致JavaScript错误,因为预加载的脚本会假设自己运行在第二个页面上,比如找一个特写的DOM节点就可能出错。
 
+仅加载脚本而不解析和执行是可能的,这也同样适用于CSS和图像。
 
+在IE中,你可以使用熟悉的图片信标模式来发起请求:
 
+	new Image().src = "preloadme.js";
 
+在其它的浏览器中,你可以使用\替代script元素,然后将它的data属性指向脚本的URL:
 
+	var obj = document.createElement('object');
+	obj.data = "preloadme.js";
+	document.body.appendChild(obj);
 
+为了阻止object可见,你应该设置它的width和height属性为0。
 
+你可以创建一个通用的preload()函数或者方法,使用条件初始化模式(第4章)来处理浏览器差异:
 
+	var preload;
+	if (/*@cc_on!@*/false) { // IE sniffing with conditional comments
+		preload = function (file) {
+			new Image().src = file;
+		};
+	} else {
+		preload = function (file) {
+			var obj = document.createElement('object'),
+				body = document.body;
+
+			obj.width = 0;
+			obj.height = 0;
+			obj.data = file;
+			body.appendChild(obj);
+		};
+	}
 
+使用这个新函数:
 
+	preload('my_web_worker.js');
 
+这种模式的坏处在于存在用户代理(浏览器)嗅探,但这里无法避免,因为特性检测没有办法告知足够的浏览器行为信息。比如在这个模式中,理论上你可以测试typeof Image是否是“function”来代替嗅探。但这种方法其实没有作用,因为所有的浏览器都支持new Image();只是有一些浏览器会为图片单独做缓存,意味着作为图片缓存下来的组件(文件)在第二个页面中不会被作为脚本取出来,而是会重新下载。
 
+> 浏览器嗅探中使用条件注释很有意思,这明显比在navigator.userAgent中找字符串要安全得多,因为用户可以很容易地修改这些字符串。
+> 比如:
+> 	var isIE = /*@cc_on!@*/false;
+> 会在其它的浏览器中将isIE设为false(因为忽略了注释),但在IE中会是true,因为在条件注释中有取反运算符!。在IE中就像是这样:
+> 	var isIE = !false; // true
 
+预加载模式可以被用于各种组件(文件),而不仅仅是脚本。比如在登录页就很有用。当用户开始输入用户名时,你可以使用打字的时间开始预加载(非敏感的东西),因为用户很可能会到第二个也就是登录后的页面。
 
+## 小结
 
+在前一章中我们讨论了JavaScript核心的模式,它们与环境无关,这一章主要关注了只在客户端浏览器环境中应用的模式。
 
+我们看了:
 
-
+- 分离的思想(HTML:内容,CSS:表现,JavaScript:行为),只用于增强体验的JavaScript以及基于特性检测的浏览器探测。(尽管在本章的最后你看到了如何打破这个模式。)
+- DOM编程——加速DOM访问和操作的模式,主要通过将DOM操作集中在一起来实现,因为频繁和DOM打交道代码是很高的。
+- 事件,跨浏览器的事件处理,以及使用事件代码来减少事件处理函数的绑定数量以提高性能。
+- 两种处理长时间大计算量脚本的模式——使用setTimeout()将长时间操作拆分为小块执行和在现代浏览器中使用web workers。
+- 多种用于远程编程,进行服务器和客户端通讯的模式——XHR,JSONP,框架和图片信标。
+- 在生产环境中部署JavaScript的步骤——将脚本合并为更少的文件,压缩和gzip(总共节省85%),可能的话托管到CDN并发送Expires头来提升缓存效果。
+- 基于性能考虑引入页面脚本的模式,包括:放置\元素的位置,同时也可以从HTTP分块获益。为了减少页面初始化时加载大的脚本文件引起的初始化工作量,我们讨论了几种不同的模式,比如延迟加载、预加载和按需加载。

From ab555c802b506b21cd91c9e99a8c9483861a3091 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sat, 26 Jan 2013 23:07:55 +0800
Subject: [PATCH 092/258] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=B8=80=E4=B8=AA?=
 =?UTF-8?q?=E6=A0=87=E8=AE=B0=E5=86=99=E6=B3=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter8.markdown | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/chapter8.markdown b/chapter8.markdown
index b801b7f..c244f23 100644
--- a/chapter8.markdown
+++ b/chapter8.markdown
@@ -620,7 +620,7 @@ CDN是指“文件分发网络”(Content Delivery Network)。这是一项
 
 但是,当你的目标是要构建一个高性能的web应用的时候,有些模式和考虑点还是应该知道的。
 
-作为题外话,来看一些比较常见的开发者会用在
+	console.log("hello world");
+	
 	// option 2
 	
 
@@ -627,7 +631,7 @@ CDN是指“文件分发网络”(Content Delivery Network)。这是一项
 	还有一些不同大小写形式的“JavaScript”,有的时候还会带上一个版本号。language属性不应该被使用,因为默认的语言就是JavaScript。版本号也不像想象中工作得那么好,这应该是一个设计上的错误。
 - type="text/javascript"
 
-	这个属性是HTML4和XHTML1标准所要求的,但它不应该存在,因为浏览器会假设它就是JavaScript。HTML5不再要求这个属性。除非是要强制通过难,否则没有任何使用type的理由。
+	这个属性是HTML4和XHTML1标准所要求的,但它不应该存在,因为浏览器会假设它就是JavaScript。HTML5不再要求这个属性。除非是要强制通过验证,否则没有任何使用type的理由。
 - defer
 	
 	(或者是HTML5中更好的async)是一种指定浏览器在下载外部脚本时不阻塞页面其它部分的方法,但还没有被广泛支持。关于阻塞的更多内容会在后面提及。
@@ -784,7 +788,7 @@ HTTP协议支持“分块编码”。它允许将页面分成一块一块发送
 
 #### 插入\元素
 
-通常脚本是插入到文档的中的,但其实你可以插入任何元素中,包括body(像JSONP示例中那样)。
+通常脚本是插入到文档的\中的,但其实你可以插入任何元素中,包括\(像JSONP示例中那样)。
 
 在前面的例子中,我们使用documentElement来插到\中,因为documentElement就是\,它的第一个子元素是\:
 

From f62f1a2da61bcd18df057525f66e4cd6c3ea7143 Mon Sep 17 00:00:00 2001
From: "dds_feng@qq.com" 
Date: Sun, 17 Feb 2013 22:10:18 +0800
Subject: [PATCH 142/258] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=B8=A4=E5=A4=84?=
 =?UTF-8?q?=E9=94=99=E5=88=AB=E5=AD=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter6.markdown | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/chapter6.markdown b/chapter6.markdown
index ea2672a..e09321f 100644
--- a/chapter6.markdown
+++ b/chapter6.markdown
@@ -525,7 +525,7 @@ constructor属性很少用,但是在运行时检查对象很方便。你可以
 
 ### 讨论
 
-在原型继承模式中,parent不需要使用对象字面量来创建。(尽管这是一种更觉的方式。)可以使用构造函数来创建parent。注意,如果你这样做,那么自己的属性和原型上的属性都将被继承:
+在原型继承模式中,parent不需要使用对象字面量来创建。(尽管这是一种更常用的方式。)可以使用构造函数来创建parent。注意,如果你这样做,那么自己的属性和原型上的属性都将被继承:
 
 	// parent constructor
 	function Person() {
@@ -794,7 +794,7 @@ ECMAScript5在Function.prototype中添加了一个方法叫bind(),使用时和
 
 	var newFunc = obj.someFunc.bind(myobj, 1, 2, 3);
 	
-这意味着将someFunc()主myobj绑定了并且传入了someFunc()的前三个参数。这也是一个在第4章讨论过的部分应用的例子。
+这意味着将someFunc()和myobj绑定了,并且还传入了someFunc()的前三个参数。这也是一个在第4章讨论过的部分应用的例子。
 
 让我们来看一下当你的程序跑在低于ES5的环境中时如何实现Function.prototype.bind():
 

From 3e5502119b7377538bc4603b50e4df8799111660 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Tue, 19 Feb 2013 22:50:47 +0800
Subject: [PATCH 143/258] =?UTF-8?q?=E6=A0=A1=E5=AF=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter1.markdown | 62 ++++++++++++++++++++++++-----------------------
 1 file changed, 32 insertions(+), 30 deletions(-)

diff --git a/chapter1.markdown b/chapter1.markdown
index d9ff15a..a83cc44 100644
--- a/chapter1.markdown
+++ b/chapter1.markdown
@@ -1,84 +1,86 @@
 # 第一章 概述
 
-JavaScript是一门Web开发语言。起初只是用来操作网页中为数不多的元素(比如图片和表单域),但谁也没想到这门语言的成长是如此迅速。除了适用于客户端浏览器编程,如今JavaScript程序可以运行于越来越多的平台之上。你可以用它来进行服务器端开发(使用.Net或Node.js)、桌面应用程序开发(运行于桌面操作系统)、以及应用程序扩展(Firefox插件或者Photoshop扩展)、移动终端应用和纯命令行的批处理脚本。
+JavaScript是一门Web开发语言。起初人们只是用它来操作网页中为数不多的元素(比如图片和表单域),但是谁也没想到这门语言可以成长得如此迅速,如今,JavaScript除了适用于客户端浏览器编程外,还可以在越来越多的平台上运行。你可以用它来开发服务端程序(使用.Net或Node.js)、桌面应用程序(运行于桌面操作系统)、应用程序扩展(Firefox插件或者Photoshop扩展)、移动终端应用和纯命令行的批处理脚本。
 
-JavaScript同样是一门不寻常的语言。它没有类,许多场景中它使用函数作为一等对象。起初,许多开发者认为这门语言存在很多缺陷,但最近几年情况发生了微妙的变化。有意思的是,有一些老牌语言比如Java和PHP也已经开始添加诸如闭包和匿名函数等新特性,而闭包和匿名函数则是JavaScript程序员最愿意津津乐道的话题。
+JavaScript是一门有些独特的语言。它没有类,在很多场景中它都将函数作为“第一型”(first-class objects,中文也有译作“第一类”,以前叫“一等公民”)。起初,许多开发者认为这门语言存在很多缺陷,但最近几年情况发生了微妙的变化。有意思的是,有一些老牌语言比如Java和PHP也已经开始添加诸如闭包和匿名函数等新特性,而闭包和匿名函数则是JavaScript程序员最津津乐道的话题。
 
-JavaScript十分灵活,可以用你所熟悉的其他任何编程语言的编程风格来写JavaScript程序。但最好的方式还是拥抱它所带来的变化、学习它所特有的编程模式。
+尽管JavaScript十分灵活,可以用你所熟悉的其他任何编程语言的编程风格来写JavaScript程序,但最好的方式还是拥抱它的独到之处、学习它所特有的编程模式。
 
 ## 模式
 
-对 “模式”的广义解释是“反复发生的事件或对象的固定用法...可以用来作为重复使用的模板或模型”(http://en.wikipedia.org/wiki/Pattern)。
+对“模式”的广义解释是“反复发生的事件或对象的固定用法...可以用来作为重复使用的模板或模型”()。
 
 在软件开发领域,模式是指常见问题的通用解决方案。模式不是简单的代码复制和粘贴,而是一种最佳实践,一种高级抽象,是解决某一类问题的范本。
 
-识别这些模式非常重要,因为:
+学习这些模式非常重要,因为:
 
-- 这些模式提供了经过论证的最佳实践,它可以帮助我们更好的编码,避免重复制造车轮。
-- 这些模式提供了高一层的抽象,某个时间段内大脑只能处理一定复杂度的逻辑,因此当你处理更繁琐棘手的问题时,它会帮你理清头绪,你才不会被低级的琐事阻碍大脑思考,因为所有的细枝末节都可以被归类和切分成不同的块(模式)。
-- 这些模式为开发者和团队提供了沟通的渠道,团队开发者之间往往是异地协作,不会有经常面对面的沟通机会。简单的代码编写技巧和技术问题处理方式的约定(代码注释)使得开发者之间的交流更加通畅。例如,“函数立即执行”用大白话表述成“你写好一个函数后,在函数的结束花括号的后面添加一对括号,这样能在定义函数结束后马上执行这个函数”(我的天)。
+- 这些模式提供了经过论证的最佳实践,它可以帮助我们更好的编码,避免重复造轮子。
+- 这些模式提供了高一层的抽象。一个时间段内大脑只能处理一定复杂度的逻辑,因此当你处理更繁琐棘手的问题时,使用模式可以帮你理清头绪,不会被低级的琐事阻碍大脑思考,因为所有的细枝末节都可以被归类和切分成不同的块(模式)。
+- 这些模式为开发者和团队提供了沟通的渠道,团队开发者之间往往是异地协作,不会有经常面对面的沟通机会。简单的代码编写技巧和技术问题处理方式的约定(代码注释)可以使开发者之间的交流更加通畅。例如,说“即时函数”(immediate function)比说“你写好一个函数后,在函数的结束花括号的后面添加一对括号,这样能在定义函数结束后马上执行这个函数”要更容易表达和理解。
 
 本书将着重讨论下面这三种模式:
 
-- 设计模式(Design patterns)
-- 编码模式(Coding patterns)
-- 反模式(Antipatterns)
+- 设计模式(design patterns)
+- 编码模式(coding patterns)
+- 反模式(antipatterns)
 
-设计模式最初的定义是来自于“GoF”(四人组,94年版“设计模式”的四个作者)的一本书,这本书在1994年出版,书名全称是“设计模式:可复用面向对象软件基础”。书中列举了一些重要的设计模式,比如单体、工厂、装饰者、观察者等等。但适用于JavaScript的设计模式并不多,尽管设计模式是脱离某种语言而存在的,但通常会以某种语言做范例来讲解设计模式,这些语言多是强类型语言,比如C++和Java。有时直接将其应用于弱类型的动态语言比如JavaScript又显得捉襟见肘。通常这些设计模式都是基于语言的强类型特性以及类的继承。而JavaScript则需要某种轻型的替代方案。本书在第七章将讨论基于 JavaScript实现的一些设计模式。
+“设计模式”最初的定义是来自于1994年出版的《设计模式:可复用面向对象软件基础》,作者是“GoF”(四人组,即四位作者)。书中列举了一些重要的设计模式,比如单例、工厂、装饰者、观察者等等。尽管设计模式是脱离某种特定的语言而存在的,但通常仍会以某种语言作为范例来讲解设计模式,这些语言多是强类型语言,比如C++和Java。有时直接将其应用于弱类型的动态语言比如JavaScript会显得毫无意义,因此适用于JavaScript的设计模式并不多。一般而言,设计模式都是基于语言的强类型特性以及基于类的继承发展而来,而对JavaScript来说则需要某种更简单的替代方案。在本书第七章将讨论基于 JavaScript实现的一些设计模式。
 
-编码模式更有趣一些。它们是JavaScript特有的模式和最佳实践,它利用了这门语言独有的一些特性,比如对函数的灵活运用,JavaScript编码模式是本书所要讨论的重点内容。
+“编码模式”会更有趣一些,它们是JavaScript特有的模式和最佳实践,利用了这门语言独有的一些特性,比如对函数的灵活运用。JavaScript编码模式是本书所要讨论的重点内容。
 
-本书中你会偶尔读到一点关于“反模式”的内容,顾名思义,反模式具有某些负作用甚至破坏性,书中会顺便一提。反模式并不是bug或代码错误,它只是一种处理问题的对策,只是这种对策带来的麻烦远超过他们解决的问题。在示例代码中我们会对反模式做明显的标注。
+本书中你会偶尔读到一点关于“反模式”的内容,顾名思义,反模式具有某些负作用甚至破坏性,书中会在讲到有关的话题时提出来。反模式并不是bug或代码错误,它只是一种处理问题的对策,但是这种对策带来的麻烦远超过他们解决的问题。在示例代码中我们会对反模式做明显的标注。
 
 
 ## JavaScript:概念
 
-在正式的讨论之前,应当先理清楚JavaScript中的一些重要的概念,这些概念在后续章节中会经常碰到,我们先来快速过一下。
+在正式的讨论之前,应当先理清楚JavaScript中的一些重要概念,这些概念在后续章节中会经常碰到,我们先来快速过一下。
 
 
 ### 面向对象
 
-JavaScript 是一门面向对象的编程语言,对于那些仓促学习JavaScript并很快丢掉它的开发者来说,这的确有点让人感到意外。你所能接触到的任何JavaScript代码片段都可以作为对象。只有五类原始类型不是对象,它们是数字、字符串、布尔值、null和undefined,前三种类型都有与之对应的包装对象(下一章会讲到)。数字、字符串和布尔值可以轻易的转换为对象类型,可以通过手动转换,也可以利用JavaScript解析器进行自动转换。
+JavaScript是一门面向对象的编程语言,这一点对于那些对JavaScript了解不多的开发者来说的确有点意外。你所能接触到的任何JavaScript代码片段都可以作为对象。只有五类原始类型不是对象,它们是数字、字符串、布尔值、`null`和`undefined`,数字、字符串和布尔值类型都有与之对应的包装对象(下一章会讲到),可以轻易的转换为对象类型,可以通过手动转换,也可以利用JavaScript解析器进行自动转换。
 
 函数也是对象,也可以拥有属性和方法。
 
-在任何语言中,最简单的操作莫过于定义变量。那么,在JavaScript中定义变量的时候,其实也在和对象打交道。首先,变量自动变为一个被称作“活动对象”的内置对象的属性(如果是全局变量的话,就变为全局对象的属性)。第二,这个变量实际上也是“伪对象”,因为它有自己的属性(属性特性),用以表示变量是否可以被修改、删除或在for-in循环中枚举。这些特性并未在ECMAScript3中作规定,而ECMAScript5中提供了一组可以修改这些特性的方法。
+在任何语言中,最简单的操作莫过于定义变量。在JavaScript中定义变量的时候,其实也在和对象打交道。首先,变量自动成为一个内置对象的属性(这个内置对象被称作“活动对象”,如果是全局变量的话,就变为全局对象的属性)。其次,这个变量实际上也是“伪对象”,因为它有自己的属性(译注:原文使用了`attributes`,指内置的特性),用以表示变量是否可以被修改、删除或在`for-in`中枚举。这些特性并未在ECMAScript3中作规定,但ECMAScript5中提供了一组可以修改这些特性的方法。
 
-那么,到底什么是对象?对象能作这么多事情,那它们一定非常特别。实际上,对象是及其简单的。对象只是很多属性的集合,一个名值对的列表(在其他语言中可能被称作关联数组),这些属性也可以是函数(函数对象),这种函数我们称为“方法”。
+那么,到底什么是对象?对象能做这么多事情,那它们一定非常特别。实际上,对象是及其简单的。对象只是很多属性的集合,一个名值对的列表(在其他语言中可能被称作关联数组),这些属性也可以是函数(函数对象),这种函数我们称为“方法”。
 
-关于对象还需要了解,我们可以随时随地修改你创建的对象(当然,ECMAScript5中提供了可阻止这些修改的API)。得到一个对象后,你可以给他添加、删除或更新成员。如果你关心私有成员和访问控制,本书中我们也会讲到相关的编程模式。
+关于对象,我们还需要了解,我们可以随时随地修改已经创建的对象(ECMAScript5中提供了可阻止这些修改的API)。得到一个对象后,你可以给他添加、删除或更新成员。如果你关心私有成员和访问控制,我们也会在本书中讲到相关的模式。
 
 最后一个需要注意的是,对象有两大类:
 
-- 本地对象(Native):由ECMAScript标准规范定义的对象
+- 本地对象(Native):由ECMAScript标准定义的对象
 - 宿主对象(Host):由宿主环境创建的对象(比如浏览器环境)
 
-本地对象也可以被归类为内置对象(比如Array,Date)或自定义对象(var o = {})。
+本地对象也可以被归类为内置对象(比如`Array`、`Date`)或自定义对象(var o = {})。(译注:指本地对象包含内置对象和自定义对象。)
 
-宿主对象包含window和所有DOM对象。如果你想知道你是否在使用宿主对象,将你的代码迁移到一个非浏览器环境中运行一下,如果正常工作,那么你的代码只用到了本地对象。
+宿主对象包含`window`和所有DOM对象。如果你想知道你是否在使用宿主对象,将你的代码迁移到一个非浏览器环境中运行一下,如果正常工作,那么你的代码就只用到了本地对象。
 
 
-### 无类
+### 没有类
 
 在本书中的许多场合都会反复碰到这个概念。JavaScript中没有类,对于其他语言的编程老手来说这个观念非常新颖,需要反复的琢磨和重新学习才能理解JavaScript只能处理对象的观念。
 
-没有类,你的代码变得更小巧,因为你不必使用类去创建对象,看一下Java风格的对象创建:
+没有类,你的代码会变得更小巧,因为你不必使用类去创建对象,看一下Java风格的对象创建:
 
-	// Java object creation
+	// Java中创建对象
 	HelloOO hello_oo = new HelloOO();
 
-为了创建一个简单的对象,同样一件事情却重复做了三遍,这让这段代码看起来很“重”。而大多数情况下,我们只想让我们的对象保持简单。
+为了创建一个简单的对象,同样一件事情重复做了三遍,这让这段代码看起来很“重”。而大多数情况下,我们想让我们的对象保持简单。
 
-在JavaScript中,你需要一个对象,就随手创建一个空对象,然后开始给这个对象添加有趣的成员。你可以给它添加原始值、函数或其他对象作为这个对象属性。“空”对象并不是真正的空,对象中存在一些内置的属性,但并没有“自有属性”。在下一章里我们对此作详细讨论。
+在JavaScript中,你需要一个对象,就随手创建一个空对象,然后给这个对象添加你需要的成员。你可以给它添加原始值、函数或其他对象作为这个对象属性。“空”对象并不是真正的空,对象中存在一些内置的属性,但并没有“自有属性”。在下一章里我们对此作详细讨论。
 
-“GoF”的书中提到一条通用规则,“组合优于继承”,也就是说,如果你手头有创建这个对象所需的资源,更推荐直接将这些资源组装成你所需的对象,而不推荐先作分类再创建链式父子继承的方式来创建对象。在JavaScript中,这条规则非常容易遵守,因为JavaScript中没有类,而且对象组装无处不在。
+“GoF”的书中提到一条通用规则,“组合优于继承”,也就是说,如果你手头有创建这个对象所需的资源,更推荐直接将这些资源组装成你所需的对象,而不推荐通过先做分类再创建链式父子继承的方式来创建对象。在JavaScript中,这条规则非常容易遵守,因为JavaScript中没有类,而对象组装无处不在。
 
 
 ### 原型
 
-JavaScript中的确有继承,尽管这只是一种代码重用的方式(本书有专门的一章来讨论代码重用)。继承可以有多种方式,最常用的方式就是利用原型。原型(prototype)是一个普通的对象,你所创建的每一个函数会自动带有prototype属性,这个属性指向一个空对象,这个空对象包含一个constructor属性,它指向你新建的函数而不是内置的Object(),除此之外它和通过对象直接量或Object()构造函数创建的对象没什么两样。你可以给它添加新的成员,这些成员可以被其他的对象继承,并当作其他对象的自有属性来使用。
+尽管继承只是实现代码复用的其中一种方式,但在JavaScript中的确有继承(本书有专门的一章来讨论代码复用)。继承可以通过多种方式实现,最常用的就是利用原型。“原型”(prototype)是一个普通的对象,你所创建的每一个函数会自动带有`prototype`属性,这个属性指向一个空对象,这个空对象包含一个`constructor`属性,它指向你新建的函数而不是内置的`Object()`。除此之外,它和通过对象直接量或`Object()`构造函数创建的对象没什么两样。你可以给它添加新的成员,这些成员可以被其他对象继承,并当作其他对象的自有属性来使用。
 
-我们会详细讨论JavaScript中的继承,现在只要记住:原型是一个对象(不是类或者其他什么特别的东西),每个函数都有一个prototype属性。
+我们后面会详细讨论JavaScript中的继承,现在只要记住:原型是一个对象(不是类或者其他什么特别的东西),每个函数都有一个`prototype`属性。
+
+===========校对分割线=========
 
 
 ### 运行环境

From 183b96cb2bdd1adff8f05e835b2b8baccc72d985 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Tue, 19 Feb 2013 22:56:24 +0800
Subject: [PATCH 144/258] =?UTF-8?q?=E5=90=88=E5=B9=B6=E6=94=B9=E5=8A=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter5.markdown | 25 +++++++++++++++++++++----
 1 file changed, 21 insertions(+), 4 deletions(-)

diff --git a/chapter5.markdown b/chapter5.markdown
index 4c113d0..7bc94f6 100644
--- a/chapter5.markdown
+++ b/chapter5.markdown
@@ -127,7 +127,7 @@ JavaScript默认语法并不支持命名空间,但很容易可以实现此特
 
 ## 声明依赖
 
-JavaScript库往往是模块化而且有用到命名空间的,这使用你可以只使用你需要的模块。比如在YUI2中,全局变量YAHOO就是一个命名空间,各个模块作为全局变量的属性,比如YAHOO.util.Dom(DOM模块)、YAHOO.util.Event(事件模块)。
+JavaScript库往往是模块化而且有用到命名空间的,这使得你可以只使用你需要的模块。比如在YUI2中,全局变量YAHOO就是一个命名空间,各个模块作为全局变量的属性,比如YAHOO.util.Dom(DOM模块)、YAHOO.util.Event(事件模块)。
 
 将你的代码依赖在函数或者模块的顶部进行声明是一个好主意。声明就是创建一个本地变量,指向你需要用到的模块:
 
@@ -430,6 +430,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护
 	MYAPP.namespace('MYAPP.utilities.array');
 	MYAPP.utilities.array = (function () {
 	
+<<<<<<< HEAD
 		// dependencies
 		var uobj = MYAPP.utilities.object,
 			ulang = MYAPP.utilities.lang,
@@ -437,11 +438,23 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护
 		// private properties
 			array_string = "[object Array]",
 			ops = Object.prototype.toString;
+=======
+			// dependencies
+		var uobj = MYAPP.utilities.object,
+			ulang = MYAPP.utilities.lang,
+>>>>>>> 合并改动
 
-		// private methods
-		// ...
-		// end var
+			// private properties
+			array_string = "[object Array]",
+			ops = Object.prototype.toString;
+
+<<<<<<< HEAD
+=======
+			// private methods
+			// ...
+			// end var
 
+>>>>>>> 合并改动
 		// optionally one-time init procedures
 		// ...
 
@@ -455,7 +468,11 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护
 					}
 				}
 			},
+<<<<<<< HEAD
 	
+=======
+			
+>>>>>>> 合并改动
 			isArray: function (a) {
 				return ops.call(a) === array_string;
 			}

From aab23b054963a8dc5c1f48e1341a79b457e0bd70 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Wed, 20 Feb 2013 13:41:54 +0800
Subject: [PATCH 145/258] =?UTF-8?q?=E7=AC=AC=E4=B8=80=E7=AB=A0=E6=B6=A6?=
 =?UTF-8?q?=E8=89=B2=E5=AE=8C=E6=AF=95=20close=20#2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter1.markdown | 50 ++++++++++++++++++-----------------------------
 1 file changed, 19 insertions(+), 31 deletions(-)

diff --git a/chapter1.markdown b/chapter1.markdown
index a83cc44..a58aa61 100644
--- a/chapter1.markdown
+++ b/chapter1.markdown
@@ -30,12 +30,10 @@ JavaScript是一门有些独特的语言。它没有类,在很多场景中它
 
 本书中你会偶尔读到一点关于“反模式”的内容,顾名思义,反模式具有某些负作用甚至破坏性,书中会在讲到有关的话题时提出来。反模式并不是bug或代码错误,它只是一种处理问题的对策,但是这种对策带来的麻烦远超过他们解决的问题。在示例代码中我们会对反模式做明显的标注。
 
-
 ## JavaScript:概念
 
 在正式的讨论之前,应当先理清楚JavaScript中的一些重要概念,这些概念在后续章节中会经常碰到,我们先来快速过一下。
 
-
 ### 面向对象
 
 JavaScript是一门面向对象的编程语言,这一点对于那些对JavaScript了解不多的开发者来说的确有点意外。你所能接触到的任何JavaScript代码片段都可以作为对象。只有五类原始类型不是对象,它们是数字、字符串、布尔值、`null`和`undefined`,数字、字符串和布尔值类型都有与之对应的包装对象(下一章会讲到),可以轻易的转换为对象类型,可以通过手动转换,也可以利用JavaScript解析器进行自动转换。
@@ -57,7 +55,6 @@ JavaScript是一门面向对象的编程语言,这一点对于那些对JavaScr
 
 宿主对象包含`window`和所有DOM对象。如果你想知道你是否在使用宿主对象,将你的代码迁移到一个非浏览器环境中运行一下,如果正常工作,那么你的代码就只用到了本地对象。
 
-
 ### 没有类
 
 在本书中的许多场合都会反复碰到这个概念。JavaScript中没有类,对于其他语言的编程老手来说这个观念非常新颖,需要反复的琢磨和重新学习才能理解JavaScript只能处理对象的观念。
@@ -73,75 +70,68 @@ JavaScript是一门面向对象的编程语言,这一点对于那些对JavaScr
 
 “GoF”的书中提到一条通用规则,“组合优于继承”,也就是说,如果你手头有创建这个对象所需的资源,更推荐直接将这些资源组装成你所需的对象,而不推荐通过先做分类再创建链式父子继承的方式来创建对象。在JavaScript中,这条规则非常容易遵守,因为JavaScript中没有类,而对象组装无处不在。
 
-
 ### 原型
 
 尽管继承只是实现代码复用的其中一种方式,但在JavaScript中的确有继承(本书有专门的一章来讨论代码复用)。继承可以通过多种方式实现,最常用的就是利用原型。“原型”(prototype)是一个普通的对象,你所创建的每一个函数会自动带有`prototype`属性,这个属性指向一个空对象,这个空对象包含一个`constructor`属性,它指向你新建的函数而不是内置的`Object()`。除此之外,它和通过对象直接量或`Object()`构造函数创建的对象没什么两样。你可以给它添加新的成员,这些成员可以被其他对象继承,并当作其他对象的自有属性来使用。
 
 我们后面会详细讨论JavaScript中的继承,现在只要记住:原型是一个对象(不是类或者其他什么特别的东西),每个函数都有一个`prototype`属性。
 
-===========校对分割线=========
-
-
 ### 运行环境
 
-JavaScript程序需要一个运行环境。一个天然的运行环境就是浏览器,但这绝不是唯一的运行环境。本书所讨论的编程模式更多的和JavaScript语言核心(ECMAScript)相关,因此这些编程模式是环境无关的。有两个例外:
+JavaScript程序需要一个运行环境。最理所当然的运行环境就是浏览器,但这绝不是唯一的运行环境。本书所讨论的编程模式更多的和JavaScript语言核心(ECMAScript)相关,因此这些编程模式是环境无关的。除了有两个例外:
 
 - 第八章,这一章专门讲述浏览器相关的模式
-- 其他一些展示模式的实际应用的例子
+- 一些演示模式用法的实际程序
 
-运行环境会提供自己的宿主对象,这些宿主对象并未在ECMAScript标准中定义,它们的行为也是不可预知的。
+运行环境会提供自己的宿主对象,这些宿主对象并未在ECMAScript标准中定义,因此它们的行为也是不可预知的。
 
-
 ## ECMAScript 5
 
-JavaScript语言的核心部分(不包含DOM、BOM和外部宿主对象)是基于ECMAScript标准(简称为ES)来实现的。其中第三版是在1999年正式颁布的,目前大多数浏览器都实现了这个版本。第四版已经废弃了。第三版颁布后十年,2009年十二月,第五版才正式颁布。
+JavaScript语言的核心部分(不包含DOM、BOM和其它宿主对象)是基于ECMAScript标准(简称为ES)来实现的。其中第三版是在1999年正式颁布的,目前大多数浏览器都实现了这个版本。第四版已经废弃了。第三版颁布后十年,2009年12月,第五版才正式颁布。
 
-第五版增加了新的内置对象、方法和属性,但最重要的增加内容是所谓的严格模式(strict mode),这个模式移除了某些语言特性,让程序变得简单且健壮。比如,with语句的使用已经争论了很多年,如今,在ECMAScript5严格模式中使用with则会报错,而在非严格模式中则是ok的。我们通过一个指令来激活严格模式,这个指令在旧版本的语言实现中被忽略。也就是说,严格模式是向下兼容的,因为在不支持严格模式的旧浏览器中也不会报错。
+第五版增加了新的内置对象、方法和属性,但最重要的一项是所谓的“严格模式”(strict mode),这个模式移除了一些语言特性,让程序变得更简单更健壮。比如,对with语句的使用已经争论了很多年,现在在ECMAScript5严格模式中使用with则会报错,而在非严格模式中则是允许的。我们通过一个指令来激活严格模式,这个指令在旧版本的语言实现中被忽略。也就是说,严格模式是向下兼容的,因为在不支持严格模式的旧浏览器中也不会报错。
 
-对于每一个作用域(包括函数作用域、全局作用域或在eval()参数字符串的开始部分),你可以使用这种代码来激活严格模式:
+对于每一个作用域(包括函数作用域、全局作用域或在传给`eval()`的参数字符串的开始部分),你可以使用这种代码来激活严格模式:
 
 	function my() {
 		"use strict";
-		// rest of the function...
+		// 函数剩余的部分……
 	}
 
-这样就激活了严格模式,函数的执行则会被限制在语言的严格子集的范围内。对于旧浏览器来说,这句话只是一个没有赋值给任何变量的字符串,因此不会报错。
+这样就激活了严格模式,函数的执行会被限制在语言的严格子集的范围内。对于旧浏览器来说,这句话只是一个没有赋值给任何变量的字符串,因此不会报错。
 
 按照语言的发展计划,未来将会只保留“严格模式”。因此,现在的ES5只是一个过渡版本,它鼓励开发者使用严格模式,而非强制。
 
-本书不会讨论ES5新增特性相关的模式,因为在本书截稿时并没有任何浏览器实现了ES5,但本书的示例代码通过一些技巧鼓励开发者向新标准转变:
+本书不会讨论ES5新增特性相关的模式,因为在本书截稿时并没有任何浏览器实现了ES5(译注:截止译稿校对时,Chrome/Firefox/IE9+(部分)已实现ES5,具体兼容情况可参考),但本书的示例代码有以下特点,以鼓励开发者向新标准转变:
 
 - 确保所提供的示例代码在严格模式下不包错
 - 避免使用并明确指出弃用的构造函数相关的属性和方法,比如arguments.callee
-- 针对ES5中的内置模式比如Object.create(),在ES3中实现等价的模式
+- 针对ES5中的内置模式比如Object.create(),在ES3中做同样的实现
 
-
 ## JSLint
 
-JavaScript是一种解释型语言,它没有静态编译时的代码检查,所以很可能将带有简单类型错误的破碎的程序部署到线上,而且往往意识不到这些错误的存在。这时我们就需要JSLint的帮助。
+JavaScript是一种解释型语言,它没有静态编译时的代码检查,所以将一个仅仅因为类型错误而导致不正常的程序部署上线是完全可能的事情,而且开发者往往意识不到这些错误的存在,这时我们就需要JSLint的帮助。
 
-JSLint(http://jslint.com )是一个JavaScript代码质量检测工具,它的作者是 Douglas Crockford,JSLint会对代码作扫描,并针对潜在的问题报出警告。笔者强烈推荐你在执行代码前先通过JSlint作检查。作者给出了警告:这个工具可能“会让你不爽”,但仅仅是在开始使用它的时候不爽一下而已。你会很快从你的错误中吸取教训,并学习这些成为一名专业的JavaScript程序员应当必备的好习惯。让你的代码通过JSLint的检查,这会让你对自己的代码更加有自信,因为你不用再去担心代码中某个不起眼的地方丢失了逗号或者有某种难以察觉的语法错误。
+JSLint( )是一个JavaScript代码质量检测工具,它的作者是 Douglas Crockford。JSLint会对代码进行扫描,并针对可能存在的问题做出警告。笔者强烈推荐你在执行代码前先通过JSlint进行检查。作者的忠告:这个工具可能“会让你不爽”,但仅仅是在开始使用它的时候不爽一下而已,你会很快会从你的错误中吸取教训,并通过它们掌握一些专业的JavaScript程序员应有的好习惯。通过JSLint的检查会让你对自己的代码更有信心,因为你不用再担心代码中某个不起眼的地方丢失了一个逗号或者有某种难以察觉的语法错误。
 
-当开始下一章的学习时,你将发现JSLint会被多次提到。本书中除了讲解反模式的示例代码外(有清楚的注释说明)、所有示例代码均通过了JSLint的检查(使用JSLint的默认设置)。
+当开始下一章的学习时,你将发现JSLint被多次提到。本书中除了讲解反模式的示例代码外(有清楚的注释说明)、所有示例代码均通过了JSLint的检查(使用JSLint的默认设置)。
 
-
 ## 控制台工具
 
-console对象在本书中非常常见。这个对象并不是语言的一部分,而是运行环境的一部分,目前大多数浏览器也都实现了这个对象。比如在Firefox中,它是通过Firebug扩展引入进来的。Firebug控制台工具包含UI操作界面,可以让你快速输入并测试JavaScript代码片段,同样用它可以调试当前打开的页面(图1-1)。在这里强烈推荐使用它来辅助学习。在Webkit核心的浏览器(Safari和Chrome)也提供了类似的工具,可以监控页面情况,IE从版本8开始也提供了开发者工具。
+`console`对象在本书中非常常见。这个对象并不是语言的一部分,而是运行环境的一部分,目前大多数浏览器也都实现了这个对象。比如在Firefox中,它是通过Firebug扩展引入进来的。Firebug控制台工具包含UI操作界面,可以让你快速输入并测试JavaScript代码片段,也可以用它调试当前页面(图1-1)。笔者强烈推荐你使用它来辅助学习。Webkit核心的浏览器(Safari和Chrome)也提供了类似的工具,可以监控页面情况,IE8+也提供了开发者工具。
 
-本书中大多数代码都使用console对象来输出结果,而没有使用alert()或者刷新当前页面。因为用这种方法输出结果实在太简单了。
+本书中大多数代码都使用`console`对象来输出结果,而没有使用`alert()`或者刷新当前页面,因为用这种方法输出结果实在太方便了。
 
-图 1-1 使用Firebug控制台工具
+![console](./Figure/chapter1/1-1.jpg)
 
-![console](http://img01.taobaocdn.com/tps/i1/T1AGmgXgxvXXXXXXXX-629-383.png)
+图 1-1 使用Firebug控制台
 
-我们经常使用log()方法,它将传入的参数在控制台输出,有时会用到dir(),用以将传入的对象属性枚举出来,这里是一个例子:
+我们经常使用`log()`方法,它将传入的参数在控制台输出,有时也会用到`dir()`,它可以将传入对象的属性枚举出来。例如:
 
 	console.log("test", 1, {}, [1,2,3]);
 	console.dir({one: 1, two: {three: 3}});
 
-当你在控制台输入内容时,则不必使用console.log()。为了避免混乱,有些代码片段仍然使用console.log()作输出,并假设所有的代码片段都使用控制台来作检测:
+当你在控制台输入内容时,不必使用console.log()。为了避免混乱,有些代码片段仍然会使用console.log()进行输出,并假设所有的代码片段都使用控制台来运行:
 	
 	window.name === window['name']; // true
 
@@ -150,5 +140,3 @@ console对象在本书中非常常见。这个对象并不是语言的一部分
 	console.log(window.name === window['name']);
 
 这段代码在控制台中输出为true。
-
-

From c7b84556adebd74f649e2ecaa505dab35b04dbec Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Wed, 20 Feb 2013 23:54:12 +0800
Subject: [PATCH 146/258] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=A0=87=E9=A2=98?=
 =?UTF-8?q?=EF=BC=8C=E4=B8=80=E5=A4=84=E8=AF=91=E6=B3=A8=E5=BE=AE=E8=B0=83?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter1.markdown | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/chapter1.markdown b/chapter1.markdown
index a58aa61..71d8d20 100644
--- a/chapter1.markdown
+++ b/chapter1.markdown
@@ -1,4 +1,4 @@
-# 第一章 概述
+# 第一章 绪言
 
 JavaScript是一门Web开发语言。起初人们只是用它来操作网页中为数不多的元素(比如图片和表单域),但是谁也没想到这门语言可以成长得如此迅速,如今,JavaScript除了适用于客户端浏览器编程外,还可以在越来越多的平台上运行。你可以用它来开发服务端程序(使用.Net或Node.js)、桌面应用程序(运行于桌面操作系统)、应用程序扩展(Firefox插件或者Photoshop扩展)、移动终端应用和纯命令行的批处理脚本。
 
@@ -102,7 +102,7 @@ JavaScript语言的核心部分(不包含DOM、BOM和其它宿主对象)是
 
 按照语言的发展计划,未来将会只保留“严格模式”。因此,现在的ES5只是一个过渡版本,它鼓励开发者使用严格模式,而非强制。
 
-本书不会讨论ES5新增特性相关的模式,因为在本书截稿时并没有任何浏览器实现了ES5(译注:截止译稿校对时,Chrome/Firefox/IE9+(部分)已实现ES5,具体兼容情况可参考),但本书的示例代码有以下特点,以鼓励开发者向新标准转变:
+本书不会讨论ES5新增特性相关的模式,因为在本书截稿时并没有任何浏览器实现了ES5(译注:截止译稿校对时,Chrome/Firefox/IE9+已(部分)实现ES5,具体兼容情况可参考),但本书的示例代码有以下特点,以鼓励开发者向新标准转变:
 
 - 确保所提供的示例代码在严格模式下不包错
 - 避免使用并明确指出弃用的构造函数相关的属性和方法,比如arguments.callee

From 7707fc65eb75ef9de8ef3ea50f1684e147f28ce5 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Wed, 20 Feb 2013 23:54:55 +0800
Subject: [PATCH 147/258] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=A0=87=E9=A2=98?=
 =?UTF-8?q?=EF=BC=8C=E5=BA=8F=E8=A8=80=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter2.markdown | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)

diff --git a/chapter2.markdown b/chapter2.markdown
index 9479936..31b2d5b 100644
--- a/chapter2.markdown
+++ b/chapter2.markdown
@@ -1,38 +1,40 @@
 
-# 第二章 高质量JavaScript基本要点
+# 第二章 概要
 
-本章将对一些实质内容展开讨论,这些内容包括最佳实践、模式和编写高质量JavaScript代码的习惯,比如避免全局变量、使用单var声明、循环中的length预缓存、遵守编码约定等等。本章还包括一些非必要的编程习惯,但更多的关注点将放在总体的代码创建过程上,包括撰写API文档、组织相互评审以及使用JSLint。这些习惯和最佳实践可以帮助你写出更好的、更易读的和可维护的代码,当几个月后或数年后再重读你的代码时,你就会深有体会了。
+本章将概要介绍一些编写高质量JavaScript的最佳实践、模式和习惯,比如避免全局变量、使用单var声明、预缓存循环中的length、遵守编码约定等等。本章还包括一些编程习惯,这些习惯跟具体的代码关系不大,而是更多关注代码创建的总体过程,包括撰写API文档、code review以及使用JSLint。这些习惯和最佳实践可以帮助你写出更好更易读和可维护性更好的代码,当几个月或数年后你再重读你的代码时,就会深有体会了。
 
 
 ## 编写可维护的代码
 
-修复软件bug成本很高,而且随着时间的推移,它们造成的损失也越来越大,特别是在已经打包发布了的软件发现了bug的时候。当然最好是发现bug立刻解决掉,但前提是你对你的代码依然很熟悉,否则当你转身投入到另外一个项目的开发中后,根本不记得当初代码的模样了。过了一段时间后你再去阅读当初的代码你需要:
+修复软件bug成本很高,而且随着时间的推移,修复这些bug的成本会越来越高,尤其以出现在已经打包发布的软件中的bug为最甚。发现bug时立刻解决掉是最好的,但前提是你对你的代码依然很熟悉,否则当你转身投入到另外一个项目的开发中后,已经根本不记得当初的代码的模样了。当过了一段时间后你再去阅读当初的代码时你需要更多的时间:
 
-- 时间来重新学习并理解问题
-- 时间去理解问题相关的代码
+- 重新学习并理解面临的问题
+- 理解用于问题的代码
 
-对大型项目或者公司来说还有一个不得不考虑的问题,就是解决这个bug的人和制造这个bug的人往往不是同一个人。因此减少理解代码所需的时间成本就显得非常重要,不管是隔了很长时间重读自己的代码还是阅读团队内其他人的代码。这对于公司的利益底线和工程师的幸福指数同样重要,因为每个人都宁愿去开发新的项目而不愿花很多时间和精力去维护旧代码。
+在大项目或者大公司的软件开发中还有另一个问题,就是解决这个bug的人和制造这个bug的人往往不是同一个人(而发现bug的往往又是另外一个人)。因此不管是隔了很长时间重读自己的代码还是阅读团队内其他人的代码,减少理解代码所需的时间成本都是非常重要的。这对于公司的利益底线和工程师的幸福指数同样重要,因为每个人都宁愿去开发新的项目而不愿花很多时间和精力去维护旧代码。
 
-另外一个软件开发中的普遍现象是,在读代码上花的时间要远远超过写代码的时间。常常当你专注于某个问题的时候,你会坐下来用一下午的时间产出大量的代码。当时的场景下代码是可以正常运行的,但当应用趋于成熟,会有很多因素促使你重读代码、改进代码或对代码做微调。比如:
+软件开发中的另一个普遍现象是,在读代码上花的时间要远远超过写代码的时间。当你专注于某个问题的时候,你往往会坐下来用一下午的时间写出大量的代码。在当时的场景下,这些代码是可以正常运行的,但当应用趋于成熟,会有很多因素促使你重读代码、改进代码或对代码做微调。比如:
 
 - 发现了bug
 - 需要给应用添加新需求
 - 需要将应用迁移到新的平台中运行(比如当市场中出现了新的浏览器时)
 - 代码重构
-- 由于架构更改或者更换另一种语言导致代码重写
+- 由于架构更改或者更换语言导致代码重写
 
 这些不确定因素带来的后果是,少数人花几小时写的代码需要很多人花几个星期去阅读它。因此,创建可维护的代码对于一个成功的应用来说至关重要。
 
 可维护的代码意味着代码是:
 
 - 可读的
-- 一致的
+- 风格一致的
 - 可预测的
 - 看起来像是同一个人写的
 - 有文档的
 
 本章接下来的部分会对这几点深入讲解。
 
+================校对分割线================
+
 
 ## 减少全局对象
 

From b16fd8b55c15190059500e77ff1fffa86c5c5801 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Thu, 21 Feb 2013 13:22:03 +0800
Subject: [PATCH 148/258] =?UTF-8?q?=E6=A0=A1=E5=AF=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter2.markdown | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/chapter2.markdown b/chapter2.markdown
index 31b2d5b..d57dd48 100644
--- a/chapter2.markdown
+++ b/chapter2.markdown
@@ -33,36 +33,36 @@
 
 本章接下来的部分会对这几点深入讲解。
 
-================校对分割线================
 
 
-## 减少全局对象
+## 减少全局变量
 
-JavaScript 使用函数来管理作用域,在一个函数内定义的变量称作“局部变量”,局部变量在函数外部是不可见的。另一方面,“全局变量”是不在任何函数体内部声明的变量,或者是直接使用而未声明的变量。
+JavaScript使用函数来管理作用域,在一个函数内定义的变量称作“本地变量”,本地变量在函数外部是不能被访问的。与之相对,“全局变量”是不在任何函数体内部声明的变量,或者是直接使用而未声明的变量。
 
-每一个JavaScript运行环境都有一个“全局对象”,不在任何函数体内使用this就可以获得对这个全局对象的引用。你所创建的每一个全局变量都是这个全局对象的属性。为了方便起见,浏览器都会额外提供一个全局对象的属性window,(常常)用以指向全局对象本身。下面的示例代码中展示了如何在浏览器中创建或访问全局变量:
+每一个JavaScript运行环境都有一个“全局对象”,不在任何函数体内使用this就可以获得对这个全局对象的引用。你所创建的每一个全局变量都是这个全局对象的属性。为了方便起见,浏览器会额外提供一个全局对象的属性`window`,(一般)指向全局对象本身。下面的示例代码展示了如何在浏览器中创建或访问全局变量:
 
-	myglobal = "hello"; // antipattern
+	myglobal = "hello"; // 反模式
 	console.log(myglobal); // "hello"
 	console.log(window.myglobal); // "hello"
 	console.log(window["myglobal"]); // "hello"
 	console.log(this.myglobal); // "hello"
 
 
-### 全局对象带来的困扰
+### 全局变量的问题
 
-全局变量的问题是,它们在JavaScript代码执行期间或者整个web页面中始终是可见的。它们存在于同一个命名空间中,因此命名冲突的情况时有发生,毕竟在应用程序的不同模块中,经常会出于某种目的定义相同的全局变量。
+全局变量的问题是,它们在整个JavaScript应用或者是整个web页面中是始终被所有代码共享的。它们存在于同一个命名空间中,因此命名冲突的情况会时有发生,毕竟在应用程序的不同模块中,经常会出于某种目的定义相同的全局变量。
 
-同样,常常网页中所嵌入的代码并不是这个网页的开发者所写,比如:
+同样,在网页中嵌入不是页面开发者编写的代码是很常见的,比如:
 
 - 网页中使用了第三方的JavaScript库
 - 网页中使用了广告代码
 - 网页中使用了用以分析流量和点击率的第三方统计代码
-- 网页中使用了很多组件,挂件和按钮等等
+- 网页中使用了很多组件、挂件和按钮等等
 
-假设某一段第三方提供的脚本定义了一个全局变量result。随后你在自己写的某个函数中也定义了一个全局变量result。这时,第二个变量就会覆盖第一个,这时就会导致第三方脚本停止工作。
+假设某一段第三方提供的脚本定义了一个全局变量result。随后你在自己写的某个函数中也定义了一个全局变量result。这时,第二个变量就会覆盖第一个,会导致第三方脚本工作不正常。
 
-因此,为了让你的脚本和这个页面中的其他脚本和谐相处,要尽可能少的使用全局变量,这一点非常重要。本书随后的章节中会讲到一些减少全局变量的技巧和策略,比如使用命名空间或者立即执行的匿名函数等,但减少全局变量最有效的方法是坚持使用var来声明变量。
+因此,为了让你的脚本和这个页面中的其他脚本和谐相处,要尽量少使用全局变量,这一点非常重要。本书随后的章节中会讲到一些减少全局变量的技巧和策略,比如使用命名空间或者即时函数等,但减少全局变量最有效的方法还是坚持使用`var`来声明变量。
+================校对分割线================
 
 由于JavaScript的特点,我们经常有意无意的创建全局变量,毕竟在JavaScript中创建全局变量实在太简单了。首先,你可以不声明而直接使用变量,再者,JavaScirpt中具有“隐式全局对象”的概念,也就是说任何不通过var声明(译注:在JavaScript1.7及以后的版本中,可以通过let来声明块级作用域的变量)的变量都会成为全局对象的一个属性(可以把它们当作全局变量)。看一下下面这段代码:
 

From caebd0371661454ab59e6301a4e099fa8e7a1111 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Fri, 1 Mar 2013 20:22:34 +0800
Subject: [PATCH 149/258] =?UTF-8?q?=E5=87=8F=E5=B0=91=E5=85=A8=E5=B1=80?=
 =?UTF-8?q?=E5=8F=98=E9=87=8F=20=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter2.markdown | 84 +++++++++++++++++++++--------------------------
 1 file changed, 38 insertions(+), 46 deletions(-)

diff --git a/chapter2.markdown b/chapter2.markdown
index d57dd48..6cff288 100644
--- a/chapter2.markdown
+++ b/chapter2.markdown
@@ -1,9 +1,7 @@
-
 # 第二章 概要
 
 本章将概要介绍一些编写高质量JavaScript的最佳实践、模式和习惯,比如避免全局变量、使用单var声明、预缓存循环中的length、遵守编码约定等等。本章还包括一些编程习惯,这些习惯跟具体的代码关系不大,而是更多关注代码创建的总体过程,包括撰写API文档、code review以及使用JSLint。这些习惯和最佳实践可以帮助你写出更好更易读和可维护性更好的代码,当几个月或数年后你再重读你的代码时,就会深有体会了。
 
-
 ## 编写可维护的代码
 
 修复软件bug成本很高,而且随着时间的推移,修复这些bug的成本会越来越高,尤其以出现在已经打包发布的软件中的bug为最甚。发现bug时立刻解决掉是最好的,但前提是你对你的代码依然很熟悉,否则当你转身投入到另外一个项目的开发中后,已经根本不记得当初的代码的模样了。当过了一段时间后你再去阅读当初的代码时你需要更多的时间:
@@ -33,8 +31,6 @@
 
 本章接下来的部分会对这几点深入讲解。
 
-
-
 ## 减少全局变量
 
 JavaScript使用函数来管理作用域,在一个函数内定义的变量称作“本地变量”,本地变量在函数外部是不能被访问的。与之相对,“全局变量”是不在任何函数体内部声明的变量,或者是直接使用而未声明的变量。
@@ -47,7 +43,6 @@ JavaScript使用函数来管理作用域,在一个函数内定义的变量称
 	console.log(window["myglobal"]); // "hello"
 	console.log(this.myglobal); // "hello"
 
-
 ### 全局变量的问题
 
 全局变量的问题是,它们在整个JavaScript应用或者是整个web页面中是始终被所有代码共享的。它们存在于同一个命名空间中,因此命名冲突的情况会时有发生,毕竟在应用程序的不同模块中,经常会出于某种目的定义相同的全局变量。
@@ -62,70 +57,68 @@ JavaScript使用函数来管理作用域,在一个函数内定义的变量称
 假设某一段第三方提供的脚本定义了一个全局变量result。随后你在自己写的某个函数中也定义了一个全局变量result。这时,第二个变量就会覆盖第一个,会导致第三方脚本工作不正常。
 
 因此,为了让你的脚本和这个页面中的其他脚本和谐相处,要尽量少使用全局变量,这一点非常重要。本书随后的章节中会讲到一些减少全局变量的技巧和策略,比如使用命名空间或者即时函数等,但减少全局变量最有效的方法还是坚持使用`var`来声明变量。
-================校对分割线================
 
-由于JavaScript的特点,我们经常有意无意的创建全局变量,毕竟在JavaScript中创建全局变量实在太简单了。首先,你可以不声明而直接使用变量,再者,JavaScirpt中具有“隐式全局对象”的概念,也就是说任何不通过var声明(译注:在JavaScript1.7及以后的版本中,可以通过let来声明块级作用域的变量)的变量都会成为全局对象的一个属性(可以把它们当作全局变量)。看一下下面这段代码:
+在JavaScript中有意无意地创建全局变量是件很容易的是,因为它有两个特性:首先,你可以不声明而直接使用变量,其次,JavaScirpt中具有“隐式全局对象”的概念,也就是说任何不通过`var`声明的变量都会成为全局对象的一个属性(可以把它们当作全局变量)。(译注:在ES6中可以通过`let`来声明块级作用域变量。)看一下下面这段代码:
 
 	function sum(x, y) {
-		// antipattern: implied global
+		// 反模式:隐式全局变量
 		result = x + y;
 		return result;
 	}
 
-这段代码中,我们直接使用了result而没有事先声明它。这段代码是能够正常工作的,但在调用这个方法之后,会产生一个全局变量result,这会带来其他问题。
+这段代码中,我们直接使用了`result`而没有事先声明它。这段代码的确是可以正常工作,但被调用后会产生一个全局变量`result`,这可能会导致其他问题。
 
-解决办法是,总是使用var来声明变量,下面代码就是改进了的sum()函数:
+解决办法是,总是使用var来声明变量,下面代码就是改进了的`sum()`函数:
 
 	function sum(x, y) {
 		var result = x + y;
 		return result;
 	}
 
-这里我们要注意一种反模式,就是在var声明中通过链式赋值的方法创建全局变量。在下面这个代码片段中,a是局部变量,但b是全局变量,而作者的意图显然不是如此:
+另一种创建全局变量的反模式,就是在`var`声明中使用链式赋值的方法。在下面这个代码片段中,`a`是局部变量,但`b`是全局变量,而作者的意图显然不是这样:
 
-	// antipattern, do not use
+	// 反模式
 	function foo() {
 		var a = b = 0;
 		// ...
 	}
 
-为什么会这样?因为这里的计算顺序是从右至左的。首先计算表达式b=0,这里的b是未声明的,这个表达式的值是0,然后通过var创建了局部变量a,并赋值为0。换言之,可以等价的将代码写成这样:
+为什么会这样呢?因为这里的计算顺序是从右至左的:首先计算表达式`b=0`,这里的`b`是未声明的;这个表达式的结果是`0`,然后通过var创建了本地变量`a`,并赋值为`0`。换言之,可以将代码写成这样:
 
 	var a = (b = 0);
 
-如果变量b已经被声明,这种链式赋值的写法是ok的,不会意外的创建全局变量,比如:
+如果变量b已经被声明,这种链式赋值的写法是可以使用的,不会意外地创建全局变量,比如:
 
 	function foo() {
 		var a, b;
 		// ...
-		a = b = 0; // both local
+		a = b = 0; // 两个都是本地变量
 	}
 
-> 避免使用全局变量的另一个原因是出于可移植性考虑的,如果你希望将你的代码运行于不同的平台环境(宿主),使用全局变量则非常危险。很有可能你无意间创建的某个全局变量在当前的平台环境中是不存在的,你认为可以安全的使用,而在其他的环境中却是存在的。
+> 避免使用全局变量的另一个原因是出于可移植性考虑,如果你希望将你的代码运行于不同的平台环境(宿主),那么使用全局变量就非常危险。因为很有可能你无意间创建的某个全局变量在当前的平台环境中是不存在的,你以为可以安全地使用,而在另一个环境中却是本来就存在的。
 
-
 ### 忘记var时的副作用
 
-隐式的全局变量和显式定义的全局变量之间有着细微的差别,差别在于通过delete来删除它们的时候表现不一致。
+隐式创建的全局变量和显式定义的全局变量之间有着细微的差别,就是通过`delete`来删除它们的时候表现不一致。
 
-- 通过var创建的全局变量(在任何函数体之外创建的变量)不能被删除。
-- 没有用var创建的隐式全局变量(不考虑函数内的情况)可以被删除。
+- 通过`var`创建的全局变量(在任何函数体之外创建的变量)不能被删除。
+- 没有用`var`创建的隐式全局变量(不考虑函数内的情况)可以被删除。
 
-也就是说,隐式全局变量并不算是真正的变量,但他们是全局对象的属性成员。属性是可以通过delete运算符删除的,而变量不可以被删除:
+也就是说,隐式全局变量并不算是真正的变量,但它们却是全局对象的属性。属性是可以通过`delete`运算符删除的,而变量不可以被删除:
 
-	// define three globals
+	// 定义三个全局变量
 	var global_var = 1;
-	global_novar = 2; // antipattern
+	global_novar = 2; // 反模式
 	(function () {
-		global_fromfunc = 3; // antipattern
+		global_fromfunc = 3; // 反模式
 	}());
 
-	// attempt to delete
+	// 尝试删除
 	delete global_var; // false
 	delete global_novar; // true
 	delete global_fromfunc; // true
 
-	// test the deletion
+	// 测试删除结果
 	typeof global_var; // "number"
 	typeof global_novar; // "undefined"
 	typeof global_fromfunc; // "undefined"
@@ -135,23 +128,22 @@ JavaScript使用函数来管理作用域,在一个函数内定义的变量称
 
 ### 访问全局对象
 
-在浏览器中,我们可以随时随地通过window属性来访问全局对象(除非你定义了一个名叫window的局部变量)。但换一个运行环境这个方便的window可能就换成了别的名字(甚至根本就被禁止访问全局对象了)。如果不想通过这种写死window的方式来得到全局变量,有一个办法,你可以在任意层次嵌套的函数作用域内执行:
+在浏览器中,我们可以随时随地通过`window`属性来访问全局对象(除非你定义了一个名叫`window`的局部变量)。但换一个运行环境这个`window`可能就换成了别的名字(甚至根本就被禁止访问全局对象了)。如果不想通过这种写死`window`的方式来访问全局变量,那么你可以在任意函数作用域内执行:
 
 	var global = (function () {
 		return this;
 	}());
 
-这种方式总是可以得到全局对象,因为在被当作函数执行的函数体内(而不是被当作构造函数执行的函数体内),this总是指向全局对象。但这种情况在ECMAScript5的严格模式中行不通,因此在严格模式中你不得不寻求其他的替代方案。比如,如果你在开发一个库,你会将你的代码包装在一个立即执行的匿名函数中(在第四章会讲到),然后从全局作用域中给这个匿名函数传入一个指向this的参数。
+这种方式总是可以访问到全局对象,因为在被当作函数(而不是构造函数)执行的函数体内,`this`总是指向全局对象。但这种情况在ECMAScript5的严格模式中行不通,因此在严格模式中你不得不寻求其他的替代方案。比如,如果你在开发一个库,你会将你的代码包装在一个即时函数中(在第四章会讲到),然后从全局作用域给这个匿名函数传入一个指向`this`的参数。
 
-
-### 单 var 模式
+### 单var模式
 
-在函数的顶部使用一个单独的var语句是非常推荐的一种模式,它有如下一些好处:
+在函数的顶部使用唯一一个`var`语句是非常推荐的一种模式,它有如下一些好处:
 
-- 在同一个位置可以查找到函数所需的所有变量
-- 避免当在变量声明之前使用这个变量时产生的逻辑错误(参照下一小节“声明提前:分散的 var 带来的问题”)
+- 可以在同一个位置找到函数所需的所有变量
+- 避免在变量声明之前使用这个变量时产生的逻辑错误(参考下一小节“声明提前:分散的`var`带来的问题”)
 - 提醒你不要忘记声明变量,顺便减少潜在的全局变量
-- 代码量更少(输入更少且更易做代码优化)
+- 代码量更少(输入代码更少且更易做代码优化)
 
 单var模式看起来像这样:
 
@@ -162,26 +154,25 @@ JavaScript使用函数来管理作用域,在一个函数内定义的变量称
 			myobject = {},
 			i,
 			j;
-		// function body...
+		// 函数体…
 	}
 
-你可以使用一个var语句来声明多个变量,变量之间用逗号分隔。也可以在这个语句中加入变量的初始化,这是一个非常好的实践。这种方式可以避免逻辑错误(所有未初始化的变量都被声明了,且值为undefined)并增加了代码的可读性。过段时间后再看这段代码,你会体会到声明不同类型变量的惯用名称,比如,你一眼就可看出某个变量是对象还是整数。
+你可以使用一个`var`语句来声明多个变量,变量之间用逗号分隔,也可以在这个语句中加入变量初始化的部分。这是一种非常好的实践方式,可以避免逻辑错误(所有未初始化的变量都被声明了,且值为undefined),并增加了代码的可读性。过段时间后再看这段代码,你可以从初始化的值中大概知道这个变量的用法,比如你一眼就可看出某个变量是对象还是整数。
 
-你可以在声明变量时多做一些额外的工作,比如在这个例子中就写了sum=a+b这种代码。另一个例子就是当代码中用到对DOM元素时,你可以把对DOM的引用赋值给一些变量,这一步就可以放在一个单独的声明语句中,比如下面这段代码:
+你可以在声明变量时做一些额外的工作,比如在这个例子中就写了`sum=a+b`这种代码。另一个例子就是当代码中用到对DOM元素时,你可以把DOM引用赋值的操作也放在这个变量声明语句中,比如下面这段代码:
 
 	function updateElement() {
 		var el = document.getElementById("result"),
 			style = el.style;
-		// do something with el and style...
+		// 使用el和style…
 	}
 
-
 ### 声明提前:分散的 var 带来的问题 
 
-JavaScript 中是允许在函数的任意地方写任意多个var语句的,其实相当于在函数体顶部声明变量,这种现象被称为“变量提前”,当你在声明之前使用这个变量时,可能会造成逻辑错误。对于JavaScript来说,一旦在某个作用域(同一个函数内)里声明了一个变量,这个变量在整个作用域内都是存在的,包括在var声明语句之前。看一下这个例子:
+JavaScript允许在函数的任意地方写任意多个`var`语句,但它们的行为会像在函数体顶部声明变量一样,这种现象被称为“声明提前”,当你在声明语句之前使用这个变量时,可能会造成逻辑错误。对于JavaScript来说,一旦在某个作用域(同一个函数内)里声明了一个变量,那么这个变量在整个作用域内都是存在的,包括在`var`声明语句之前的位置。看一下这个例子:
 
-	// antipattern
-	myname = "global"; // global variable
+	// 反模式
+	myname = "global"; // 全局变量
 	function func() {
 		alert(myname); // "undefined"
 		var myname = "local";
@@ -189,21 +180,22 @@ JavaScript 中是允许在函数的任意地方写任意多个var语句的,其
 	}
 	func();
 
-这个例子中,你可能期望第一个alert()弹出“global”,第二个alert()弹出“local”。这种结果看起来是合乎常理的,因为在第一个alert执行时,myname还没有声明,这时就应该“寻找”全局变量中的myname。但实际情况并不是这样,第一个alert弹出“undefined”,因为myname已经在函数内有声明了(尽管声明语句在后面)。所有的变量声明都提前到了函数的顶部。因此,为了避免类似带有“歧义”的程序逻辑,最好在使用之前一起声明它们。
+这个例子中,你可能会期望第一个`alert()`弹出“global”,第二个`alert()`弹出“local”。这种结果看起来是合乎常理的,因为在第一个`alert()`执行时,`myname`还没有被声明,这时就应该“寻找”全局变量`myname`。但实际情况并不是这样,第一个`alert()`弹出“undefined”,因为`myname`已经在函数内被声明了(尽管声明语句在后面)。所有的变量声明都会被提前到函数的顶部,因此,为了避免类似带有“歧义”的程序逻辑,最好在使用之前一起声明它们。
 
 上一个代码片段等价于下面这个代码片段:
 
-	myname = "global"; // global variable
+	myname = "global"; // 全局变量
 	function func() {
-		var myname; // same as -> var myname = undefined;
+		var myname; // 等价于 -> var myname = undefined;
 		alert(myname); // "undefined"
 		myname = "local";
 		alert(myname); // "local"
 	}
 	func();
 
->这里有必要对“变量提前”作进一步补充,实际上从JavaScript引擎的工作机制上看,这个过程稍微有点复杂。代码处理经过了两个阶段,第一阶段是创建变量、函数和参数,这一步是预编译的过程,它会扫描整段代码的上下文。第二阶段是代码的运行,这一阶段将创建函数表达式和一些非法的标识符(未声明的变量)。从实用性角度来讲,我们更愿意将这两个阶段归成一个概念“变量提前”,尽管这个概念并没有在ECMAScript标准中定义,但我们常常用它来解释预编译的行为过程。
+> 这里有必要对“变量提前”做进一步补充,实际上从JavaScript引擎的工作机制上看,这个过程稍微有点复杂。代码处理经过了两个阶段,第一阶段是创建变量、函数和形参,也就是预编译的过程,它会扫描整段代码的上下文。第二阶段是在代码的运行时(runtime),这一阶段将创建函数表达式和一些非法的标识符(未声明的变量)。(译注:这两个阶段并没有包含代码的执行,是在执行前的处理过程。)从实用性角度来讲,我们更愿意将这两个阶段归成一个概念“变量提前”,尽管这个概念并没有在ECMAScript标准中定义,但我们常常用它来解释预编译的行为过程。
 
+================校对分割线================
 
 ## for 循环
 

From a4dfd283e980b8b75dce774061ee8d60e44e56d9 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Wed, 6 Mar 2013 13:28:45 +0800
Subject: [PATCH 150/258] =?UTF-8?q?for=E5=BE=AA=E7=8E=AF=20=E6=A0=A1?=
 =?UTF-8?q?=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter2.markdown | 73 +++++++++++++++++++++++------------------------
 1 file changed, 35 insertions(+), 38 deletions(-)

diff --git a/chapter2.markdown b/chapter2.markdown
index 6cff288..a504ec1 100644
--- a/chapter2.markdown
+++ b/chapter2.markdown
@@ -125,7 +125,6 @@ JavaScript使用函数来管理作用域,在一个函数内定义的变量称
 
 在ES5严格模式中,给未声明的变量赋值会报错(比如这段代码中提到的两个反模式)。
 
-
 ### 访问全局对象
 
 在浏览器中,我们可以随时随地通过`window`属性来访问全局对象(除非你定义了一个名叫`window`的局部变量)。但换一个运行环境这个`window`可能就换成了别的名字(甚至根本就被禁止访问全局对象了)。如果不想通过这种写死`window`的方式来访问全局变量,那么你可以在任意函数作用域内执行:
@@ -141,7 +140,7 @@ JavaScript使用函数来管理作用域,在一个函数内定义的变量称
 在函数的顶部使用唯一一个`var`语句是非常推荐的一种模式,它有如下一些好处:
 
 - 可以在同一个位置找到函数所需的所有变量
-- 避免在变量声明之前使用这个变量时产生的逻辑错误(参考下一小节“声明提前:分散的`var`带来的问题”)
+- 避免在变量声明之前使用这个变量时产生的逻辑错误(参考下一小节“声明提前:分散的var带来的问题”)
 - 提醒你不要忘记声明变量,顺便减少潜在的全局变量
 - 代码量更少(输入代码更少且更易做代码优化)
 
@@ -167,7 +166,7 @@ JavaScript使用函数来管理作用域,在一个函数内定义的变量称
 		// 使用el和style…
 	}
 
-### 声明提前:分散的 var 带来的问题 
+### 声明提前:分散的var带来的问题 
 
 JavaScript允许在函数的任意地方写任意多个`var`语句,但它们的行为会像在函数体顶部声明变量一样,这种现象被称为“声明提前”,当你在声明语句之前使用这个变量时,可能会造成逻辑错误。对于JavaScript来说,一旦在某个作用域(同一个函数内)里声明了一个变量,那么这个变量在整个作用域内都是存在的,包括在`var`声明语句之前的位置。看一下这个例子:
 
@@ -193,90 +192,88 @@ JavaScript允许在函数的任意地方写任意多个`var`语句,但它们
 	}
 	func();
 
-> 这里有必要对“变量提前”做进一步补充,实际上从JavaScript引擎的工作机制上看,这个过程稍微有点复杂。代码处理经过了两个阶段,第一阶段是创建变量、函数和形参,也就是预编译的过程,它会扫描整段代码的上下文。第二阶段是在代码的运行时(runtime),这一阶段将创建函数表达式和一些非法的标识符(未声明的变量)。(译注:这两个阶段并没有包含代码的执行,是在执行前的处理过程。)从实用性角度来讲,我们更愿意将这两个阶段归成一个概念“变量提前”,尽管这个概念并没有在ECMAScript标准中定义,但我们常常用它来解释预编译的行为过程。
+> 这里有必要对“变量提前”做进一步补充,实际上从JavaScript引擎的工作机制上看,这个过程稍微有点复杂。代码处理经过了两个阶段:第一阶段是创建变量、函数和形参,也就是预编译的过程,它会扫描整段代码的上下文;第二阶段是在代码的运行时(runtime),这一阶段将创建函数表达式和一些非法的标识符(未声明的变量)。(译注:这两个阶段并没有包含代码的执行,是在执行前的处理过程。)从实用性角度来讲,我们更愿意将这两个阶段归成一个概念“变量提前”,尽管这个概念并没有在ECMAScript标准中定义,但我们常常用它来解释预编译的行为过程。
 
-================校对分割线================
-
-## for 循环
+## for循环
 
-在for循环中,可以对数组或类似数组的对象(比如arguments和HTMLCollection对象)作遍历,最普通的for循环模式形如:
+在`for`循环中,可以对数组或类似数组的对象(比如`arguments`和`HTMLCollection`对象)进行遍历,通常`for`循环模式形如:
 
-	// sub-optimal loop
+	// 非最优的循环方式
 	for (var i = 0; i < myarray.length; i++) {
-		// do something with myarray[i]
+		// 访问myarray[i]…
 	}
 
-这种模式的问题是,每次遍历都会访问数组的length属性。这降低了代码运行效率,特别是当myarray并不是一个数组而是一个HTMLCollection对象的时候。
+这种模式的问题是,每次遍历都会访问数组的length属性,这会降低代码运行效率,特别是当`myarray`不是一个数组而是一个`HTMLCollection`对象的时候。
 
-HTMLCollection是由DOM方法返回的对象,比如:
+`HTMLCollection`是由DOM方法返回的对象集合,比如:
 
 - document.getElementsByName()
 - document.getElementsByClassName()
 - document.getElementsByTagName()
 
-还有很多其他的HTMLCollection,这些对象是在DOM标准之前就已经在用了,这些HTMLCollection主要包括:
+还有一些`HTMLCollection`是在DOM标准诞生之前就已经在用了并且现在仍然可用,包括:
 
-**document.images**
+- document.images
 
-页面中所有的IMG元素
+	页面中所有的IMG元素
 
-**document.links**
+- document.links
 
-页面中所有的A元素
+	页面中所有的A元素
 
-**document.forms**
+- document.forms
 
-页面中所有的表单
+	页面中所有的表单
 
-**document.forms[0].elements**
+- document.forms[0].elements
 
-页面中第一个表单的所有字段
+	页面中第一个表单的所有字段
 
-这些对象的问题在于,它们均是指向文档(HTML页面)中的活动对象。也就是说每次通过它们访问集合的length时,总是会去查询DOM,而DOM操作则是很耗资源的。
+这些对象的问题在于,它们都会实时查询文档(HTML页面)中的对象。也就是说每次通过它们访问集合的`length`属性时,总是都会去查询DOM,而DOM操则是很耗资源的。
 
-更好的办法是为for循环缓存住要遍历的数组的长度,比如下面这段代码:
+更好的办法是在`for`循环中缓存要遍历的数组的长度,比如下面这段代码:
 
 	for (var i = 0, max = myarray.length; i < max; i++) {
-		// do something with myarray[i]
+		// 访问myarray[i]…
 	}
 
-通过这种方法只需要访问DOM节点一次以获得length,在整个循环过程中就都可以使用它。
+通过这种方法只需要获取`length`一次,然后在整个循环过程中使用它。
 
-不管在什么浏览器中,在遍历HTMLCollection时缓存length都可以让程序执行的更快,可以提速两倍(Safari3)到一百九十倍(IE7)不等。更多细节可以参照Nicholas Zakas的《高性能JavaScript》,这本书也是由O'Reilly出版。
+不管在什么浏览器中,在遍历`HTMLCollection`时缓存`length`都可以让程序执行的更快,可以提速2倍(Safari3)到190倍(IE7)不等。更多细节可以参照Nicholas Zakas的《高性能JavaScript》,这本书也是由O'Reilly出版。
 
-需要注意的是,当你在循环过程中需要修改这个元素集合(比如增加DOM元素)时,你更希望更新length而不是更新常量。
+需要注意的是,当你在循环过程中需要修改这个元素集合(比如增加DOM元素)时,你可能需要更新`length`。
 
-遵照单var模式,你可以将var提到循环的外部,比如:
+按照“单var模式”,你可以将`var`提到循环的外部,比如:
 
 	function looper() {
 		var i = 0,
 			max,
 			myarray = [];
-		// ...
+		// …
 		for (i = 0, max = myarray.length; i < max; i++) {
-			// do something with myarray[i]
+			// 访问myarray[i]…
 		}
 	}
 
-这种模式带来的好处就是提高了代码的一致性,因为你越来越依赖这种单var模式。缺点就是在重构代码的时候不能直接复制粘贴一个循环体,比如,你正在将某个循环从一个函数拷贝至另外一个函数中,必须确保i和max也拷贝至新函数里,并且需要从旧函数中将这些没用的变量删除掉。
+当你越来越依赖“单var模式”时,带来的好处就是提高了代码的一致性。而缺点则是在重构代码的时候不能直接复制粘贴一个循环体,比如,你正在将某个循环从一个函数复制至另外一个函数中,那么必须确保`i`和`max`也复制到新函数里,并且需要从旧函数中将这些没用的变量删除掉。
 
 最后一个需要对循环做出调整的地方是将i++替换成为下面两者之一:
 
 	i = i + 1
 	i += 1
 
-JSLint提示你这样做,是因为++和--实际上降低了代码的可读性,如果你觉得无所谓,可以将JSLint的plusplus选项设为false(默认为true),本书所介绍的最后一个模式用到了: i += 1。
+JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读性,如果你觉得无所谓,可以将JSLint的`plusplus`选项设为`false`(默认为`true`)。稍后,在本书所介绍的最后一个模式中用到了:`i += 1`。
 
-关于这种for模式还有两种变化的形式,做了少量改进,原因有二:
+关于这种`for`模式还有两种变化的形式,做了少量改进,原因有二:
 
-- 减少一个变量(没有max)
+- 减少一个变量(没有`max`)
 - 减量循环至0,这种方式速度更快,因为和零比较要比和非零数字或数组长度比较要高效的多
 
 第一种变化形式是:
 
 	var i, myarray = [];
 	for (i = myarray.length; i--;) {
-		// do something with myarray[i]
+		// 访问myarray[i]…
 	}
 
 第二种变化形式用到了while循环:
@@ -284,12 +281,12 @@ JSLint提示你这样做,是因为++和--实际上降低了代码的可读性
 	var myarray = [],
 		i = myarray.length;
 	while (i--) {
-		// do something with myarray[i]
+		// 访问myarray[i]…
 	}
 
-这些小改进只体现在性能上,此外,JSLint不推荐使用i--。
+这些小改进只能体现在对性能要求比较苛刻的地方,此外,JSLint不推荐使用`i--`。
 
-
+================校对分割线================
 ## for-in 循环
 
 for-in 循环用于对非数组对象作遍历。通过for-in进行循环也被称作“枚举”。

From 9b1a964c308922cfb7ff4d829f54cea41f49b5b1 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Thu, 7 Mar 2013 21:56:41 +0800
Subject: [PATCH 151/258] =?UTF-8?q?for-in=E5=BE=AA=E7=8E=AF=20=E6=A0=A1?=
 =?UTF-8?q?=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter2.markdown | 45 +++++++++++++++++++++------------------------
 1 file changed, 21 insertions(+), 24 deletions(-)

diff --git a/chapter2.markdown b/chapter2.markdown
index a504ec1..fd386ac 100644
--- a/chapter2.markdown
+++ b/chapter2.markdown
@@ -286,86 +286,83 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读
 
 这些小改进只能体现在对性能要求比较苛刻的地方,此外,JSLint不推荐使用`i--`。
 
-================校对分割线================
-## for-in 循环
+## for-in循环
 
-for-in 循环用于对非数组对象作遍历。通过for-in进行循环也被称作“枚举”。
+`for-in`循环用于对非数组对象进行遍历。通过`for-in`进行循环也被称作“枚举”(enumeration)。
 
-从技术角度讲,for-in循环同样可以用于数组(JavaScript中数组即是对象),但不推荐这样做。当使用自定义函数扩充了数组对象时,这时更容易产生逻辑错误。另外,for-in循环中属性的遍历顺序是不固定的,所以最好数组使用普通的for循环,对象使用for-in循环。
+从技术上讲,`for-in`循环同样可以用于数组(JavaScript中数组也是对象),但不推荐这样做。当数组对象被扩充了自定义函数时,可能会产生逻辑错误。另外,`for-in`循环中属性的遍历顺序是不固定的,所以最好数组使用普通的`for`循环,对象使用`for-in`循环。
 
-可以使用对象的hasOwnProperty()方法将从原型链中继承来的属性过滤掉,这一点非常重要。看一下这段代码:
+可以使用对象的`hasOwnProperty()`方法过来从原型链中继承来的属性,这一点非常重要。看一下这段代码:
 
-	// the object
+	// 对象
 	var man = {
 		hands: 2,
 		legs: 2,
 		heads: 1
 	};
-	// somewhere else in the code
-	// a method was added to all objects
+	// 在代码的另一个地方给所有的对象添加了一个方法
 	if (typeof Object.prototype.clone === "undefined") {
 		Object.prototype.clone = function () {};
 	}
 
-在这段例子中,我们定义了一个名叫man的对象直接量。在代码中的某个地方(可以是man定义之前也可以是之后),给Object的原型中增加了一个方法clone()。原型链是实时的,这意味着所有的对象都可以访问到这个新方法。要想在枚举man的时候避免枚举出clone()方法,则需要调用hasOwnProperty()来对原型属性进行过滤。如果不做过滤,clone()也会被遍历到,而这不是我们所希望的:
+在这个例子中,我们使用对象字面量定义了一个名叫`man`的对象。在代码中的某个地方(可以是`man`定义之前也可以是之后),给`Object`的原型增加了一个方法`clone()`。原型链是实时的,这意味着所有的对象都可以访问到这个新方法。要想在枚举`man`的时候避免枚举出`clone()`方法,就需要调用`hasOwnProperty()`来过滤来自原型的属性。如果不做过滤,`clone()`也会被遍历到,这是我们不希望看到的:
 
-	// 1.
-	// for-in loop
+	// 1.for-in循环
 	for (var i in man) {
 		if (man.hasOwnProperty(i)) { // filter
 			console.log(i, ":", man[i]);
 		}
 	}
 	/*
-	result in the console
+	控制台中的结果
 	hands : 2
 	legs : 2
 	heads : 1
 	*/
 
-	// 2.
-	// antipattern:
-	// for-in loop without checking hasOwnProperty()
+	// 2.反模式:
+	// 不使用hasOwnProperty()过滤的for-in循环
 	for (var i in man) {
 		console.log(i, ":", man[i]);
 	}
 	/*
-	result in the console
+	控制台中的结果
 	hands : 2
 	legs : 2
 	heads : 1
 	clone: function()
 	*/
 
-另外一种的写法是通过Object.prototype直接调用hasOwnProperty()方法,像这样:
+另外一种调用`hasOwnProperty()`的方法是通过`Object.prototype`来调用,像这样:
 
 	for (var i in man) {
-		if (Object.prototype.hasOwnProperty.call(man, i)) { // filter
+		if (Object.prototype.hasOwnProperty.call(man, i)) { // 过滤
 			console.log(i, ":", man[i]);
 		}
 	}
 
-这种做法的好处是,当man对象中重新定义了hasOwnProperty方法时,可以避免调用时的命名冲突(译注:明确指定调用的是Object.prototype上的方法而不是实例对象中的方法),这种做法同样可以避免冗长的属性查找过程(译注:这种查找过程多是在原型链上进行查找),一直查找到Object中的方法,你可以定义一个变量来“缓存”住它(译注:这里所指的是缓存住Object.prototype.hasOwnProperty):
+这种做法的好处是,在`man`对象中重新定义了`hasOwnProperty`方法的情况下,可以避免调用时的命名冲突。为了避免查找属性时从`Object`对象一路找到原型的冗长过程,你可以定义一个变量来“缓存”住它:
 
 	var i,
 		hasOwn = Object.prototype.hasOwnProperty;
 	for (i in man) {
-		if (hasOwn.call(man, i)) { // filter
+		if (hasOwn.call(man, i)) { // 过滤
 			console.log(i, ":", man[i]);
 		}
 	}
 
->严格说来,省略hasOwnProperty()并不是一个错误。根据具体的任务以及你对代码的自信程度,你可以省略掉它以提高一些程序执行效率。但当你对当前要遍历的对象不确定的时候,添加hasOwnProperty()则更加保险些。
+> 严格说来,省略`hasOwnProperty()`并不是一个错误。根据具体的任务以及你对代码的自信程度,你可以省略掉它以提高一些程序执行效率。但当你对当前要遍历的对象不确定的时候,添加hasOwnProperty()则更加保险些。
 
-这里提到一种格式上的变化写法(这种写法无法通过JSLint检查),这种写法在for循环所在的行加入了if判断条件,他的好处是能让循环语句读起来更完整和通顺(“如果元素包含属性X,则拿X做点什么”):
+这里介绍一种格式上的变种(这种写法无法通过JSLint检查),这种写法在`for`循环所在的行加入了`if`判断条件,他的好处是能让循环语句读起来更完整和通顺(“如果元素包含属性X,则对X做点什么”):
 
-	// Warning: doesn't pass JSLint
+	// 警告:无法通过JSLint检查
 	var i,
 		hasOwn = Object.prototype.hasOwnProperty;
-	for (i in man) if (hasOwn.call(man, i)) { // filter
+	for (i in man) if (hasOwn.call(man, i)) { // 过滤
 		console.log(i, ":", man[i]);
 	}
 
+================校对分割线================
 
 ## (不)扩充内置原型
 

From 47d9265da2dfef0cff22536c672bb82ab49f01c5 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Thu, 7 Mar 2013 22:26:09 +0800
Subject: [PATCH 152/258] =?UTF-8?q?=EF=BC=88=E4=B8=8D=EF=BC=89=E6=89=A9?=
 =?UTF-8?q?=E5=85=85=E5=86=85=E7=BD=AE=E5=8E=9F=E5=9E=8B=20=E6=A0=A1?=
 =?UTF-8?q?=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter2.markdown | 19 +++++++++----------
 1 file changed, 9 insertions(+), 10 deletions(-)

diff --git a/chapter2.markdown b/chapter2.markdown
index fd386ac..4426cd5 100644
--- a/chapter2.markdown
+++ b/chapter2.markdown
@@ -362,30 +362,29 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读
 		console.log(i, ":", man[i]);
 	}
 
-================校对分割线================
-
 ## (不)扩充内置原型
 
-我们可以扩充构造函数的prototype属性,这是一种非常强大的特性,用来为构造函数增加功能,但有时这个功能强大到超过我们的掌控。
+我们可以扩充构造函数的`prototype`属性来为构造函数增加功能,这个特性非常强大,但有时会强大到超过我们的掌控。
 
-给内置构造函数比如Object()、Array()、和Function()扩充原型看起来非常诱人,但这种做法严重降低了代码的可维护性,因为它让你的代码变得难以预测。对于那些基于你的代码做开发的开发者来说,他们更希望使用原生的JavaScript方法来保持工作的连续性,而不是使用你所添加的方法(译注:因为原生的方法更可靠,而你写的方法可能会有bug)。
+给内置构造函数如`Object()`、`Array()`、`Function()`扩充原型看起来非常诱人,但这种做法会严重降低代码的可维护性,因为它会让你的代码变得难以预测。对于那些基于你的代码来做开发的开发者来说,他们更希望使用原生的JavaScript方法来保持代码的一致性,而不愿意使用你所添加的方法。
 
-另外,如果将属性添加至原型中,很可能导致在那些不使用hasOwnProperty()做检测的循环中将原型上的属性遍历出来,这会造成混乱。
+另外,如果将属性添加至原型中,很可能导致原型上的属性在那些不使用`hasOwnProperty()`做过滤的循环中被遍历出来,从而造成混乱。
 
-因此,不扩充内置对象的原型是最好的,你也可以自己定义一个规则,仅当下列条件满足时做例外考虑:
+因此,不扩充内置对象的原型是最好的,你也可以自己定义一个规则,仅当下列条件满足时才考虑扩充内置对象的原型:
 
-1. 未来的ECMAScript版本的JavaScirpt会将你实现的方法添加为内置方法。比如,你可以实现ECMAScript5定义的一些方法,一直等到浏览器升级至支持ES5。这样,你只是提前定义了这些有用的方法。
-2. 如果你发现你自定义的方法已经不存在,要么已经在代码其他地方实现了,要么是浏览器的JavaScript引擎已经内置实现了。
-3. 你所做的扩充附带充分的文档说明,且和团队其他成员做了沟通。
+1. 未来的ECMAScript版本或者JavaScirpt会将你将要实现的方法添加为内置方法。比如,你可以实现ECMAScript5定义的一些方法,直到浏览器升级至支持ES5。这样,你只是提前定义了这些方法。
+2. 当某个属性或者方法是你在其它地方实现过的,或者是某个JavaScript引擎或浏览器的一部分,而你检查时又发现它不存在时。
+3. 在有充分的文档说明,并且和团队其他成员做了沟通的时候。
 
 如果你遇到这三种情况之一,你可以给内置原型添加自定义方法,写法如下:
 
 	if (typeof Object.protoype.myMethod !== "function") {
 		Object.protoype.myMethod = function () {
-			// implementation...
+			// 实现…
 		};
 	}
 
+================校对分割线================
 
 ## switch 模式
 

From 4fbf0f6e67f5109ccbe71e916c534410ca620b60 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Fri, 8 Mar 2013 09:11:46 +0800
Subject: [PATCH 153/258] =?UTF-8?q?switch=E6=A8=A1=E5=BC=8F=20=E6=A0=A1?=
 =?UTF-8?q?=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter2.markdown | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/chapter2.markdown b/chapter2.markdown
index 4426cd5..8bd5509 100644
--- a/chapter2.markdown
+++ b/chapter2.markdown
@@ -384,11 +384,9 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读
 		};
 	}
 
-================校对分割线================
-
-## switch 模式
+## switch模式
 
-你可以通过下面这种模式的写法来增强switch语句的可读性和健壮性:
+你可以通过下面这种模式来增强`switch`语句的可读性和健壮性:
 
 	var inspect_me = 0,
 		result = '';
@@ -405,12 +403,13 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读
 
 这个简单的例子所遵循的风格约定如下:
 
-- 每个case和switch对齐(这里不考虑花括号相关的缩进规则)
-- 每个case中的代码整齐缩进
-- 每个case都以break作为结束
-- 避免连续执行多个case语句块(当省略break时会发生),如果你坚持认为连续执行多case语句块是最好的方法,请务必补充文档说明,对于其他人来说,这种情况看起来是错误的。
-- 以default结束整个switch,以确保即便是在找不到匹配项时也会有正常的结果,
+- 每个`case`和`switch`对齐(这里不考虑花括号相关的缩进规则)。
+- 每个`case`中的代码整齐缩进。
+- 每个`case`都以`break`作为结束。
+- 避免连续执行多个case语句块(省略break时),如果你坚持认为连续执行多个`case`语句块是最好的方法,请务必补充文档说明,对于其他人来说,会觉得这种情况是错误的写法。
+- 以`default`结束整个`switch`,以确保即便是在找不到匹配项时也有合理的结果。
 
+================校对分割线================
 
 ## 避免隐式类型转换
 

From 0ad8c18095ccfefb8b751e08bd81f44a3e1722fc Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Fri, 8 Mar 2013 09:39:53 +0800
Subject: [PATCH 154/258] =?UTF-8?q?=E9=81=BF=E5=85=8D=E9=9A=90=E5=BC=8F?=
 =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E8=BD=AC=E6=8D=A2=20=E6=A0=A1=E5=AF=B9?=
 =?UTF-8?q?=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter2.markdown | 49 ++++++++++++++++++++++-------------------------
 1 file changed, 23 insertions(+), 26 deletions(-)

diff --git a/chapter2.markdown b/chapter2.markdown
index 8bd5509..a25e0d1 100644
--- a/chapter2.markdown
+++ b/chapter2.markdown
@@ -409,87 +409,84 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读
 - 避免连续执行多个case语句块(省略break时),如果你坚持认为连续执行多个`case`语句块是最好的方法,请务必补充文档说明,对于其他人来说,会觉得这种情况是错误的写法。
 - 以`default`结束整个`switch`,以确保即便是在找不到匹配项时也有合理的结果。
 
-================校对分割线================
-
 ## 避免隐式类型转换
 
-在JavaScript的比较操作中会有一些隐式的数据类型转换。比如诸如false == 0或""==0之类的比较都返回true。
+在JavaScript对变量进行比较时会有一些隐式的数据类型转换。比如诸如`false == 0`或`"" == 0`之类的比较都返回`true`。
 
-为了避免隐式类型转换造对程序造成干扰,推荐使用===和!===运算符,它们较除了比较值还会比较类型。
+为了避免隐式类型转换对程序造成干扰,推荐使用`===`和`!==`运算符,它们除了比较值还会比较类型:
 
 	var zero = 0;
 	if (zero === false) {
-		// not executing because zero is 0, not false
+		// 不会执行,因为zero是0,不是false
 	}
-	// antipattern
+	// 反模式
 	if (zero == false) {
-		// this block is executed...
+		// 代码块会执行…
 	}
 
-另外一种观点认为当==够用的时候就不必多余的使用===。比如,当你知道typeof的返回值是一个字符串,就不必使用全等运算符。但JSLint却要求使用全等运算符,这当然会提高代码风格的一致性,并减少了阅读代码时的思考(“这里使用==是故意的还是无意的?”)。
+有一种观点认为当`==`够用的时候就不必使用`===`。比如,当你知道`typeof`的返回值是一个字符串,就不必使用全等运算符。但JSLint却要求使用全等运算符,这无疑会提高代码风格的一致性,并减少了阅读代码时的思考量(“这里使用`==`是故意的还是无意的?”)。
 
-
 ### 避免使用eval()
 
-当你想使用eval()的时候,不要忘了那句话“eval()是魔鬼”。这个函数的参数是一个字符串,它可以执行任意字符串。如果事先知道要执行的代码是有问题的(在运行之前),则没有理由使用eval()。如果需要在运行时动态生成执行代码,往往都会有更佳的方式达到同样的目的,而非一定要使用eval()。例如,访问动态属性时可以使用方括号:
+当你想使用`eval()`的时候,不要忘了那句话“`eval()` is evil”(`eval()`是魔鬼)。这个函数的参数是一个字符串,它会将传入的字符串作为JavaScript代码执行。如果用来解决问题的代码是事先知道的(在运行之前),则没有理由使用`eval()`。如果需要在运行时动态生成并执行代码,那一般都会有更好的方式达到同样的目的,而非一定要使用`eval()`。例如,访问动态属性时可以使用方括号:
 
-	// antipattern
+	// 反模式
 	var property = "name";
 	alert(eval("obj." + property));
-	// preferred
+	// 更好的方式
 	var property = "name";
 	alert(obj[property]);
 
-eval()同样有安全隐患,因为你需要运行一些容易被干扰的代码(比如运行一段来自于网络的代码)。在处理Ajax请求所返回的JSON数据时会常遇到这种情况,使用eval()是一种反模式。这种情况下最好使用浏览器的内置方法来解析JSON数据,以确保代码的安全性和数据的合法性。如果浏览器不支持JSON.parse(),你可以使用JSON.org所提供的库。
+`eval()`还有安全隐患,因为你有可能会运行一些被干扰过的代码(比如一段来自于网络的代码)。这是一种在处理Ajax请求所返回的JSON数据时比较常见的反模式。这种情况下最好使用浏览器的内置方法来解析JSON数据,以确保代码的安全性和数据的合法性。如果浏览器不支持`JSON.parse()`,你可以使用JSON.org所提供的库。
 
-记住,多数情况下,给setInterval()、setTimeout()和Function()构造函数传入字符串的情形和eval()类似,这种用法也是应当避免的,这一点非常重要,因为这些情形中JavaScript最终还是会执行传入的字符串参数:
+值得一提的是,多数情况下,给`setInterval()`、`setTimeout()`和`Function()`构造函数传入字符串的情形和`eval()`类似,这种用法也是应当避免的,因为这些情形中JavaScript最终还是会执行传入的字符串参数:
 
-	// antipatterns
+	// 反模式
 	setTimeout("myFunc()", 1000);
 	setTimeout("myFunc(1, 2, 3)", 1000);
-	// preferred
+	// 更好的方式
 	setTimeout(myFunc, 1000);
 	setTimeout(function () {
 		myFunc(1, 2, 3);
 	}, 1000);
 
-new Function()的用法和eval()非常类似,应当特别注意。这种构造函数的方式很强大,但往往被误用。如果你不得不使用eval(),你可以尝试用new Function()来代替。这有一个潜在的好处,在new Function()中运行的代码会在一个局部函数作用域内执行,因此源码中所有用var定义的变量不会自动变成全局变量。还有一种方法可以避免eval()中定义的变量转换为全局变量,即是将eval()包装在一个立即执行的匿名函数内(详细内容请参照第四章)。
+`new Function()`的用法和`eval()`非常类似,应当特别注意。这种构造函数的方式很强大,但经常会被误用。如果你不得不使用`eval()`,你可以尝试用`new Function()`来代替。这有一个潜在的好处,在`new Function()`中运行的代码会在一个局部函数作用域内执行,因此源码中所有用`var`定义的变量不会自动变成全局变量。还有一种方法可以避免`eval()`中定义的变量被转换为全局变量,即是将`eval()`包装在一个即时函数内(详细内容请参见第四章)。
 
-看一下这个例子,这里只有un成为了全局变量,污染了全局命名空间:
+看一下这个例子,这里只有`un`成为全局变量污染了全局命名空间:
 
 	console.log(typeof un);// "undefined"
 	console.log(typeof deux); // "undefined"
 	console.log(typeof trois); // "undefined"
 
 	var jsstring = "var un = 1; console.log(un);";
-	eval(jsstring); // logs "1"
+	eval(jsstring); // 打印出 "1"
 
 	jsstring = "var deux = 2; console.log(deux);";
-	new Function(jsstring)(); // logs "2"
+	new Function(jsstring)(); // 打印出 "2"
 
 	jsstring = "var trois = 3; console.log(trois);";
 	(function () {
 		eval(jsstring);
-	}()); // logs "3"
+	}()); // 打印出 "3"
 
 	console.log(typeof un); // "number"
 	console.log(typeof deux); // "undefined"
 	console.log(typeof trois); // "undefined"
 
-eval()和Function构造函数还有一个区别,就是eval()可以修改作用域链,而Function更像是一个沙箱。不管在什么地方执行Function,它只能看到全局作用域。因此它不会太严重的污染局部变量。在下面的示例代码中,eval()可以访问且修改其作用域之外的变量,而Function不能(注意,使用Function和new Function是完全一样的)。
+`eval()`和`Function()`构造函数还有一个区别,就是`eval()`可以修改作用域链,而`Function`更像是一个沙箱。不管在什么地方执行`Function()`,它都只能看到全局作用域。因此它不会太严重的污染局部变量。在下面的示例代码中,`eval()`可以访问并修改其作用域之外的变量,而`Function()`则不能(注意,使用`Function()`和`new Function()`是完全一样的)。
 
 	(function () {
 		var local = 1;
-		eval("local = 3; console.log(local)"); // logs 3
-		console.log(local); // logs 3
+		eval("local = 3; console.log(local)"); // 打印出 3
+		console.log(local); // 打印出 3
 	}());
 
 	(function () {
 		var local = 1;
-		Function("console.log(typeof local);")(); // logs undefined
+		Function("console.log(typeof local);")(); // 打印出 undefined
 	}());
 
-
+================校对分割线================
 ## 使用parseInt()进行数字转换
 
 可以使用parseInt()将字符串转换为数字。函数的第二个参数是转换基数(译注:“基数”指的是数字进制的方式),这个参数通常被省略。但当字符串以0为前缀时转换就会出错,例如,在表单中输入日期的一个字段。ECMAScript3中以0为前缀的字符串会被当作八进制数处理(基数为8)。但在ES5中不是这样。为了避免转换类型不一致而导致的意外结果,应当总是指定第二个参数:

From 3d308938c4e977a1957626a6e865124de54a24de Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Fri, 8 Mar 2013 09:44:24 +0800
Subject: [PATCH 155/258] =?UTF-8?q?=E4=BD=BF=E7=94=A8parseInt()=E8=BF=9B?=
 =?UTF-8?q?=E8=A1=8C=E6=95=B0=E5=AD=97=E8=BD=AC=E6=8D=A2=20=E6=A0=A1?=
 =?UTF-8?q?=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter2.markdown | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/chapter2.markdown b/chapter2.markdown
index a25e0d1..7d21152 100644
--- a/chapter2.markdown
+++ b/chapter2.markdown
@@ -486,25 +486,25 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读
 		Function("console.log(typeof local);")(); // 打印出 undefined
 	}());
 
-================校对分割线================
 ## 使用parseInt()进行数字转换
 
-可以使用parseInt()将字符串转换为数字。函数的第二个参数是转换基数(译注:“基数”指的是数字进制的方式),这个参数通常被省略。但当字符串以0为前缀时转换就会出错,例如,在表单中输入日期的一个字段。ECMAScript3中以0为前缀的字符串会被当作八进制数处理(基数为8)。但在ES5中不是这样。为了避免转换类型不一致而导致的意外结果,应当总是指定第二个参数:
+你可以使用`parseInt()`将字符串转换为数字。函数的第二个参数是进制参数,这个参数应该被指定,但却通常被省略。当字符串以0为前缀时转换就会出问题,例如,在表单中输入日期的一个字段。ECMAScript3中以0为前缀的字符串会被当作八进制数处理,这一点在ES5中已经有了改变。为了避免转换类型不一致而导致的意外结果,应当总是指定第二个参数:
 
 	var month = "06",
 		year = "09";
 	month = parseInt(month, 10);
 	year = parseInt(year, 10);
 
-在这个例子中,如果省略掉parseInt的第二个参数,比如parseInt(year),返回值是0,因为“09”被认为是八进制数(等价于parseInt(year,8)),而且09是非法的八进制数。
+在这个例子中,如果省略掉parseInt的第二个参数,比如`parseInt(year)`,返回的值是0,因为“09”被认为是八进制数(等价于`parseInt(year,8)`),但09是非法的八进制数。
 
 字符串转换为数字还有两种方法:
 
-	+"08" // result is 8
-	Number("08") // 8
+	+"08" // 结果为8
+	Number("08") // 结果为8
 
-这两种方法要比parseInt()更快一些,因为顾名思义parseInt()是一种“解析”而不是简单的“转换”。但当你期望将“08 hello”这类字符串转换为数字,则必须使用parseInt(),其他方法都会返回NaN。
+这两种方法要比`parseInt()`更快一些,因为顾名思义`parseInt()`是一种“解析”而不是简单的“转换”。但当你期望将“08 hello”这类字符串转换为数字,则必须使用`parseInt()`,其他方法都会返回NaN。
 
+================校对分割线================
 
 ## 编码风格
 

From 4292b213e4886926151527da90ea1acd9b4bf6e9 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Fri, 8 Mar 2013 13:04:03 +0800
Subject: [PATCH 156/258] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E8=A7=84=E8=8C=83=20?=
 =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter2.markdown | 83 +++++++++++++++++++++--------------------------
 1 file changed, 37 insertions(+), 46 deletions(-)

diff --git a/chapter2.markdown b/chapter2.markdown
index 7d21152..2203199 100644
--- a/chapter2.markdown
+++ b/chapter2.markdown
@@ -504,22 +504,19 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读
 
 这两种方法要比`parseInt()`更快一些,因为顾名思义`parseInt()`是一种“解析”而不是简单的“转换”。但当你期望将“08 hello”这类字符串转换为数字,则必须使用`parseInt()`,其他方法都会返回NaN。
 
-================校对分割线================
-
-## 编码风格
+## 代码规范
 
-确立并遵守编码规范非常重要,这会让你的代码风格一致、可预测、可读性更强。团队新成员通过学习编码规范可以很快进入开发状态、并写出团队其他成员易于理解的代码。
+确立并遵守代码规范非常重要,这会让你的代码风格一致、可预测,并且可读性更强。团队新成员通过学习代码规范可以很快进入开发状态,并写出让团队其他成员易于理解的代码。
 
-在开源社区和邮件组中关于编码风格的争论一直不断(比如关于代码缩进,用tab还是空格?)。因此,如果你打算在团队内推行某种编码规范时,要做好应对各种反对意见的心理准备,而且要吸取各种意见,这对确立并一贯遵守某种编码规范是非常重要,而不是斤斤计较的纠结于编码规范的细节。
+在开源社区和邮件组中关于编代风格的争论一直不断。(比如关于代码缩进,用tab还是空格?)因此,如果你打算在团队内推行某种编码规范时,要做好应对各种反对意见的心理准备,而且要吸取各种意见。确定并遵守代码规范非常重要,任何一种规范都可以,这甚至比代码规范中的具体约定是怎么样的还要重要。
 
-
 ### 缩进
 
-代码没有缩进几乎就不能读了,而不一致的缩进更加糟糕,因为它看上去像是遵循了规范,真正读起来却磕磕绊绊。因此规范的使用缩进非常重要。
+代码如果没有缩进就几乎不能读了,而不一致的缩进会使情况更加糟糕,因为它看上去像是遵守了规范,但真正读起来却没那么顺利。因此规范地使用缩进非常重要。
 
-有些开发者喜欢使用tab缩进,因为每个人都可以根据自己的喜好来调整tab缩进的空格数,有些人则喜欢使用空格缩进,通常是四个空格,这都无所谓,只要团队每个人都遵守同一个规范即可,本书中所有的示例代码都采用四个空格的缩进写法,这也是JSLint所推荐的。
+有些开发者喜欢使用tab缩进,因为每个人都可以根据自己的喜好来调整tab缩进的空格数,有些人则喜欢使用空格缩进,通常是四个空格。这都无所谓,只要团队每个人都遵守同一个规范即可,本书中所有的示例代码都采用四个空格的缩进写法,这也是JSLint所推荐的。(译注:电子版中看到的是用tab缩进,本译文也保留使用tab缩进。)
 
-那么到底什么应该缩进呢?规则很简单,花括号里的内容应当缩进,包括函数体、循环(do、while、for和for-in)体、if条件、switch语句和对象直接量里的属性。下面的代码展示了如何正确的使用缩进:
+那么到底什么时候应该缩进呢?规则很简单,花括号里的内容应当缩进,包括函数体、循环(`do`、`while`、`for`和`for-in`)体、`if`语句、`switch`语句和对象字面量里的属性。下面的代码展示了如何正确地使用缩进:
 
 	function outer(a, b) {
 		var c = 1,
@@ -541,47 +538,45 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读
 		return inner;
 	}
 
-
 ### 花括号
 
-应当总是使用花括号,即使是在可省略花括号的时候也应当如此。从技术角度讲,如果if或for中只有一个语句,花括号是可以省略的,但最好还是不要省略。这让你的代码更加工整一致而且易于更新。
+在特定的语句中应当总是使用花括号,即便是在可省略花括号的情况下也应当如此。从技术角度讲,如果`if`或`for`中只有一个语句,花括号是可以省略的,但最好还是不要省略,这会让你的代码更加工整一致而且易于修改。
 
-假设有这样一段代码,for循环中只有一条语句,你可以省略掉这里的花括号,而且不会有语法错误:
+假设有这样一段代码,`for`循环中只有一条语句,你可以省略掉这里的花括号,而且不会有语法错误:
 
-	// bad practice
+	// 不好的方式
 	for (var i = 0; i < 10; i += 1)
 		alert(i);
 
-但如果过了一段时间,你给这个循环添加了另一行代码?
+但如果过了一段时间,你给这个循环添加了另一行代码会怎样?
 
-	// bad practice
+	// 不好的方式
 	for (var i = 0; i < 10; i += 1)
 		alert(i);
 		alert(i + " is " + (i % 2 ? "odd" : "even"));
 
-第二个alert实际处于循环体之外,但这里的缩进会迷惑你。长远考虑最好还是写上花括号,即便是在只有一个语句的语句块中也应如此:
+第二个`alert`实际上在循环体之外,但这里的缩进会让你迷惑。从长远考虑最好还是写上花括号,即便是在只有一个语句的语句块中也应如此:
 
-	// better
+	// 更好的方式
 	for (var i = 0; i < 10; i += 1) {
 		alert(i);
 	}
 
 同理,if条件句也应当如此:
 
-	// bad
+	// 不好的方式
 	if (true)
 		alert(1);
 	else
 		alert(2);
 	
-	// better
+	// 更好的试
 	if (true) {
 		alert(1);
 	} else {
 		alert(2);
 	}
 
-
 ###  左花括号的位置
 
 开发人员对于左大括号的位置有着不同的偏好,在同一行呢还是在下一行?
@@ -598,9 +593,9 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读
 	}
 
 
-在这个例子中,看起来只是个人偏好问题。但有时候花括号位置的不同则会影响程序的执行。因为JavaScript会“自动插入分号”。JavaScript对行结束时的分号并无要求,它会自动将分号补全。因此,当函数return语句返回了一个对象直接量,而对象的左花括号和return不在同一行时,程序的执行就和预想的不同了:
+在这个例子中,这个问题只是个人偏好问题。但有时候花括号位置的不同会影响程序的执行,因为JavaScript会“自动插入分号”。JavaScript对行尾是否有分号并没有要求,它会自动将分号补全。因此,当函数的`return`语句返回了一个对象字面量,而对象的左花括号和`return`又不在同一行时,程序的执行就和预期的不同了:
 
-	// warning: unexpected return value
+	// 警告:返回值和预期的不同
 	function func() {
 		return
 		{
@@ -608,18 +603,18 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读
 		};
 	}
 
-可以看出程序作者的意图是返回一个包含了name属性的对象,但实际情况不是这样。因为return后会填补一个分号,函数的返回值就是undefined。这段代码等价于:
+可以看出程序作者的意图是返回一个包含了`name`属性的对象,但实际情况不是这样。因为return后会填补一个分号,函数的返回值就是undefined。这段代码等价于:
 
-	// warning: unexpected return value
+	// 警告:返回值和预期的不同
 	function func() {
 		return undefined;
-		// unreachable code follows...
+		// 下面的代码不会运行…
 		{
 			name: "Batman"
 		};
 	}
 
-结论,总是使用花括号,而且总是将左花括号与上一条语句放在同一行:
+总结一下好的写法,在特写的语句中总是使用花括号,并且总是将左花括号与上一条语句放在同一行:
 
 	function func() {
 		return {
@@ -627,28 +622,25 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读
 		};
 	}
 
->关于分号应当注意:和花括号一样,应当总是使用分号,尽管在JavaScript解析代码时会补全行末省略的分号。严格遵守这条规则,可以让代码更加严谨,同时可以避免前面例子中所出现的歧义。
+> 关于分号也值得注意:和花括号一样,应当总是使用分号,尽管在JavaScript解析代码时会补全行末省略的分号,但严格遵守这条规则,可以让代码更加严谨,同时可以避免前面例子中所出现的歧义。
 
-
 ### 空格
 
-空格的使用同样有助于改善代码的可读性和一致性。在写英文句子的时候,在逗号和句号后面会使用间隔。在JavaScript中,你可以按照同样的逻辑在表达式(相当于逗号)和语句结束(相对于完成了某个“想法”)后面添加间隔。
+空格的使用同样有助于改善代码的可读性和一致性。在写英文句子的时候,在逗号和句号后面会使用间隔,在JavaScript中,你可以按照同样的逻辑在表达式(相当于逗号)和语句结束(相对于完成了某个“想法”的表达)后面添加间隔。
 
 适合使用空格的地方包括:
 
-- for循环中的分号之后,比如 `for (var i = 0; i < 10; i += 1) {...}`
-- for循环中初始化多个变量,比如 `for (var i = 0, max = 10; i < max; i += 1) {...}`
-- 分隔数组项的逗号之后,`var a = [1, 2, 3];`
-- 对象属性后的逗号以及名值对之间的冒号之后,`var o = {a: 1, b: 2};`
-- 函数参数中,`myFunc(a, b, c)`
-- 函数声明的花括号之前,`function myFunc() {}`
-- 匿名函数表达式function之后,`var myFunc = function () {};`
+- for循环中的分号之后,比如`for (var i = 0; i < 10; i += 1) {...}`
+- for循环中初始化多个变量,比如`for (var i = 0, max = 10; i < max; i += 1) {...}`
+- 用于分隔数组元素的逗号之后,比如`var a = [1, 2, 3];`
+- 对象属性后的逗号以及名值对之间的冒号之后,比如`var o = {a: 1, b: 2};`
+- 函数参数中,比如`myFunc(a, b, c)`
+- 函数声明的花括号之前,比如`function myFunc() {}`
+- 匿名函数表达式`function`之后,比如`var myFunc = function () {};`
 
-另外,我们推荐在运算符和操作数之间添加空格。也就是说在+, -, *, =, <, >, <=, >=, ===, !==, &&, ||, +=符号前后都添加空格。
+另外,我们推荐在运算符和操作数之间也添加空格。也就是说在`+`、`-`、`*`、`=`、`<`、`>`、`<=`、`>=`、`===`、`!==`、`&&`、`||`、`+=`符号前后都添加空格。
 
-	// generous and consistent spacing
-	// makes the code easier to read
-	// allowing it to "breathe"
+	// 适当且一致的空格给代码留了“呼吸空间”,使代码更易读
 	var d = 0,
 		a = b + 1;
 	if (a && b && c) {
@@ -656,9 +648,7 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读
 		a += d;
 	}
 
-	// antipattern
-	// missing or inconsistent spaces
-	// make the code confusing
+	// 反模式,缺少或者不正确的空格使得代码不易读
 	var d= 0,
 		a =b+1;
 	if (a&& b&&c) {
@@ -668,11 +658,12 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读
 
 最后,还应当注意,最好在花括号旁边添加空格:
 
-- 在函数、if-else语句、循环、对象直接量的左花括号之前补充空格({)
-- 在右花括号和else和while之间补充空格
+- 在函数、`if-else`语句、循环、对象字面量的左花括号之前补充空格
+- 在右花括号和`else`或者`while`之间补充空格
 
->垂直空白的使用经常被我们忽略,你可以使用空行来将代码单元分隔开,就像文学作品中使用段落作分隔一样。
+> 垂直空白的使用经常被我们忽略,你可以使用空行来将代码单元分隔开,就像文学作品中使用段落进行分隔一样。
 
+================校对分割线================
 
 ## 命名规范
 

From 76f6c50d33e7f8b6f4bd206547a0f36c9c024cd7 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Fri, 8 Mar 2013 13:19:28 +0800
Subject: [PATCH 157/258] =?UTF-8?q?=E5=91=BD=E5=90=8D=E8=A7=84=E8=8C=83=20?=
 =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter2.markdown | 44 +++++++++++++++++++-------------------------
 1 file changed, 19 insertions(+), 25 deletions(-)

diff --git a/chapter2.markdown b/chapter2.markdown
index 2203199..4fe95ee 100644
--- a/chapter2.markdown
+++ b/chapter2.markdown
@@ -663,57 +663,50 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读
 
 > 垂直空白的使用经常被我们忽略,你可以使用空行来将代码单元分隔开,就像文学作品中使用段落进行分隔一样。
 
-================校对分割线================
-
 ## 命名规范
 
-另外一种可以提升你代码的可预测性和可维护性的方法是采用命名规范。也就是说变量和函数的命名都遵照同种习惯。
+另外一种可以提升你代码的可预测性和可维护性的方法是采用命名规范。也就是说变量和函数的命名都遵守同样的习惯。
 
-下面是一些建议的命名规范,你可以原样采用,也可以根据自己的喜好作调整。同样,遵循规范要比规范本身更加重要。
+下面是一些建议的命名规范,你可以原样采用,也可以根据自己的喜好作调整。同样,遵循规范要比规范本身是什么样更加重要。
 
-
-### 构造器命名中的大小写
+### 构造函数命名中的大小写
 
-JavaScript中没有类,但有构造函数,可以通过new来调用构造函数:
+JavaScript中没有类,但有构造函数,可以通过`new`来调用构造函数:
 
 	var adam = new Person();
 
-由于构造函数毕竟还是函数,不管我们将它用作构造器还是函数,当然希望只通过函数名就可分辨出它是构造器还是普通函数。
+由于构造函数毕竟还是函数,如果只通过函数名就可分辨出它是构造函数还是普通函数是非常有用的。
 
 首字母大写可以提示你这是一个构造函数,而首字母小写的函数一般只认为它是普通的函数,不应该通过new来调用它:
 
 	function MyConstructor() {...}
 	function myFunction() {...}
 
-下一章将介绍一些强制将函数用作构造器的编程模式,但遵守我们所提到的命名规范会更好的帮助程序员阅读源码。
+下一章将介绍一些强制将普通函数用作构造函数的编程模式,但遵守我们所提到的命名规范会更好的帮助程序员阅读源码。
 
-
 ### 单词分隔
 
-当你的变量名或函数名中含有多个单词时,单词之间的分隔也应当遵循统一的约定。最常见的做法是“驼峰式”命名,单词都是小写,每个单词的首字母是大写。
-
-对于构造函数,可以使用“大驼峰式”命名,比如MyConstructor(),对于函数和方法,可以采用“小驼峰式”命名,比如myFunction(),calculateArea()和getFirstName()。
+当你的变量名或函数名中含有多个单词时,单词之间的分隔也应当遵循统一的规范。最常见的是“驼峰式”(camel case)命名,单词都是小写,每个单词的首字母是大写。
 
-那么对于那些不是函数的变量应当如何命名呢?变量名通常采用小驼峰式命名,还有一个不错的做法是,变量所有字母都是小写,单词之间用下划线分隔,比如,first_name,favorite_bands和old_company_name,这种方法可以帮助你区分函数和其他标识符——原始数据类型或对象。
+对于构造函数,可以使用“大驼峰式”(upper camel case)命名,比如`MyConstructor()`,对于函数和方法,可以采用“小驼峰式”(lower camel case)命名,比如`myFunction()`、`calculateArea()`和`getFirstName()`。
 
-ECMAScript的属性和方法均使用Camel标记法,尽管多字的属性名称是罕见的(正则表达式对象的lastIndex和ignoreCase属性)。
+那么对于那些不是函数的变量应当如何命名呢?变量名通常采用小驼峰式命名,还有一个不错的做法是,变量所有字母都是小写,单词之间用下划线分隔,比如,`first_name`、`favorite_bands`和`old_company_name`,这种方法可以帮助你区分函数和其他标识符如原始类型数据或对象。
 
-在ECMAScript中的属性和方法均使用驼峰式命名,尽管包含多单词的属性名称(正则表达式对象中的lastIndex和ignoreCase)并不常见。
+ECMAScript的属性和方法均使用驼峰式命名,尽管包含多个单词的属性名称并不多见(正则表达式对象的`lastIndex`和`ignoreCase`属性)。
 
-
 ### 其他命名风格
 
 有时开发人员使用命名规范来弥补或代替语言特性的不足。
 
-比如,JavaScript中无法定义常量(尽管有一些内置常量比如Number.MAX_VALUE),所以开发者都采用了这种命名习惯,对于那些程序运行周期内不会更改的变量使用全大写字母来命名。比如:
+比如,JavaScript中无法定义常量(尽管有一些内置常量比如`Number.MAX_VALUE`),所以开发者都采用了一种命名规范,对于那些程序运行周期内不会更改的变量使用全大写字母来命名。比如:
 
-	// precious constants, please don't touch
+	// 常量,请勿修改
 	var PI = 3.14,
 		MAX_WIDTH = 800;
 
-除了使用大写字母的命名方式之外,还有另一种命名规约:全局变量都大写。这种命名方式和“减少全局变量”的约定相辅相成,并让全局变量很容易辨认。
+除了使用大写字母的命名方式之外,还有另一种命名规范:全局变量全大写。这种命名方式和“减少全局变量”的约定相辅相成,并让全局变量很容易辨认。
 
-除了常量和全局变量的命名惯例,这里讨论另外一种命名惯例,即私有变量的命名。尽管在JavaScript是可以实现真正的私有变量的,但开发人员更喜欢在私有成员或方法名之前加上下划线前缀,比如下面的例子:
+除了常量和全局变量的命名规范,这里讨论另外一种命名规范,即私有变量的命名。尽管在JavaScript是可以实现真正的私有变量的,但开发人员更喜欢在私有成员或方法名之前加上下划线前缀,比如下面的例子:
 
 	var person = {
 		getName: function () {
@@ -727,14 +720,15 @@ ECMAScript的属性和方法均使用Camel标记法,尽管多字的属性名
 		}
 	};
 
-在这个例子中,getName()的身份是一个公有方法,属于稳定的API,而_getFirst()和_getLast()则是私有方法。尽管这两个方法本质上和公有方法无异,但在方法名前加下划线前缀就是为了警告用户不要直接使用这两个私有方法,因为不能保证它们在下一个版本中还能正常工作。JSLint会对私有方法作检查,除非设置了JSLint的nomen选项为false。
+在这个例子中,`getName()`是一个公有方法,是确定的API的一部分,而`_getFirst()`和`_getLast()`则是私有方法。尽管这两个方法本质上和公有方法没有区别,但在方法名前加下划线前缀就是为了告知用户不要直接使用这两个私有方法,因为不能保证它们在下一个版本中还能正常工作。JSLint会对私有方法作检查,除非设置了JSLint的`nomen`选项为`false`。
 
-下面介绍一些_private风格写法的变种:
+下面介绍一些`_private`风格写法的变种:
 
-- 在名字尾部添加下划下以表明私有,比如`name_`和`getElements_()`
-- 使用一个下划线前缀表明受保护的属性_protected,用两个下划线前缀表明私有属性__private
+- 在名字尾部添加下划线以表明私有,比如`name_`和`getElements_()`
+- 使用一个下划线前缀表明受保护的属性`_protected`,用两个下划线前缀表明私有属性`__private`
 - 在Firefox中实现了一些非标准的内置属性,这些属性在开头和结束都有两个下划线,比如`__proto__`和`__parent__`
 
+================校对分割线================
 
 ## 书写注释
 

From a35d063339d88184355a182a47a1d6d8d38d0800 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Thu, 21 Mar 2013 23:41:19 +0800
Subject: [PATCH 158/258] =?UTF-8?q?=E4=B9=A6=E5=86=99=E6=B3=A8=E9=87=8A=20?=
 =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter2.markdown | 14 ++++++--------
 1 file changed, 6 insertions(+), 8 deletions(-)

diff --git a/chapter2.markdown b/chapter2.markdown
index 4fe95ee..f88db13 100644
--- a/chapter2.markdown
+++ b/chapter2.markdown
@@ -728,19 +728,17 @@ ECMAScript的属性和方法均使用驼峰式命名,尽管包含多个单词
 - 使用一个下划线前缀表明受保护的属性`_protected`,用两个下划线前缀表明私有属性`__private`
 - 在Firefox中实现了一些非标准的内置属性,这些属性在开头和结束都有两个下划线,比如`__proto__`和`__parent__`
 
-================校对分割线================
-
-## 书写注释
+## 写注释
 
-写代码就要写注释,即便你认为你的代码不会被别人读到。当你对一个问题非常熟悉时,你会很快找到问题代码,但当过了几个星期后再来读这段代码,则需要绞尽脑汁的回想代码的逻辑。
+在写代码时,即便你认为你的代码不会被别人读到,也应该写好注释。因为当你对一个问题非常熟悉时,你会非常明白这些代码的作用,但当过了几个星期后再来读这段代码时,则需要绞尽脑汁的回想这些代码在干什么。
 
-你不必对显而易见的代码作过多的注释:每个变量和每一行都作注释。但你需要对所有的函数、他们的参数和返回值补充注释,对于那些有趣的或怪异的算法和技术也应当配备注释。对于阅读你的代码的其他人来说,注释就是一种提示,只要阅读注释、函数名以及参数,就算不读代码也能大概理解程序的逻辑。比如,这里有五到六行代码完成了某个功能,如果提供了一行描述这段代码功能的注释,读程序的人就不必再去关注代码的细节实现了。代码注释的写法并没有硬性规定,有些代码片段(比如正则表达式)的确需要比代码本身还多的注释。
+你不必对那些浅显易懂的代码写过多的注释,比如每个变量、每一行都写注释。但你应该对所有的函数、它们的参数和返回值进行注释,除此之外,对于那些值得注意的或是比较怪异的算法和技术也应当写好注释。对于其他阅读你代码的人来说,注释就是一种提示,只要阅读注释、函数名和参数,就算不读其它部分的代码也能大概理解程序的逻辑。比如,这里有五六行代码完成了某个功能,如果有一行描述这段代码功能的注释,读程序的人就不必再去关注代码的实现细节了。代码注释的写法并没有硬性规定,但有些代码片段(比如正则表达式)需要比代码本身更多的注释。
 
->由于过时的注释会带来很多误导,这比不写注释还糟糕。因此保持注释时刻更新的习惯非常重要,尽管对很多人来说这很难做到。
+> 过时的注释会造成误导,这比不写注释还要糟糕。保持注释的状态为最新的习惯非常重要,尽管对很多人来说这很难做到。
 
-在下一小节我们会讲到,注释可以自动生成文档。
+在下一小节我们会讲到,利用注释可以自动生成文档。
 
-
+================校对分割线================
 ## 书写API文档
 
 很多人都觉得写文档是一件枯燥且吃力不讨好的事情,但实际情况不是这样。我们可以通过代码注释自动生成文档,这样就不用再去专门写文档了。很多人觉得这是一个不错的点子,因为根据某些关键字和格式化的文档自动生成可阅读的参考手册本身就是“某种编程”。

From bb92e228803b92dd74b4ed1a26350d7873e19021 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Thu, 21 Mar 2013 23:49:48 +0800
Subject: [PATCH 159/258] =?UTF-8?q?=E5=86=99API=E6=96=87=E6=A1=A3=20?=
 =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter2.markdown | 19 +++++++++----------
 1 file changed, 9 insertions(+), 10 deletions(-)

diff --git a/chapter2.markdown b/chapter2.markdown
index f88db13..72d2b8f 100644
--- a/chapter2.markdown
+++ b/chapter2.markdown
@@ -738,26 +738,25 @@ ECMAScript的属性和方法均使用驼峰式命名,尽管包含多个单词
 
 在下一小节我们会讲到,利用注释可以自动生成文档。
 
-================校对分割线================
-## 书写API文档
+## 写API文档
 
-很多人都觉得写文档是一件枯燥且吃力不讨好的事情,但实际情况不是这样。我们可以通过代码注释自动生成文档,这样就不用再去专门写文档了。很多人觉得这是一个不错的点子,因为根据某些关键字和格式化的文档自动生成可阅读的参考手册本身就是“某种编程”。
+很多人都觉得写文档是一件很枯燥而且吃力不讨好的事情,但实际情况并不是这样。我们可以通过代码注释自动生成文档,这样就不用再去专门写文档了。很多人觉得这是一个不错的点子,因为根据某些关键字和特定的格式自动生成可阅读的参考手册本身就是“某种编程”。
 
-传统的APIdoc诞生自Java世界,这个工具名叫“javadoc”,和Java SDK(软件开发工具包)一起提供。但这个创意迅速被其他语言借鉴。JavaScript领域有两个非常优秀的开源工具,它们是JSDoc Toolkit(http://code.google.com/p/jsdoc-toolkit/ )和YUIDoc(http://yuilibrary.com/projects/yuidoc )。
+最早利用注释生成API文档的工具诞生自Java业界,这个工具名叫“javadoc”,和Java SDK(软件开发工具包)一起提供,但这个创意迅速被其他语言借鉴。JavaScript领域有两个非常优秀的开源工具,它们是JSDoc Toolkit()和YUIDoc()。
 
-生成API文档的过程包括:
+生成API文档的过程:
 
-- 以特定的格式来组织书写源代码
+- 以特定的格式来写代码
 - 运行工具来对代码和注释进行解析
 - 发布工具运行的结果,通常是HTML页面
 
-你需要学习这种特殊的语法,包括十几种标签,写法类似于:
+这种语法包括十几种标签(tag),写法类似于:
 
 	/**
 	 * @tag value
 	 */
 
-比如这里有一个函数reverse(),可以对字符串进行反序操作。它的参数和返回值都是字符串。给它补充注释如下:
+比如这里有一个函数`reverse()`,可以对字符串进行反序操作。它的参数和返回值都是字符串。给它补充注释如下:
 
 	/**
 	* Reverse a string
@@ -770,9 +769,9 @@ ECMAScript的属性和方法均使用驼峰式命名,尽管包含多个单词
 		return output;
 	};
 
-可以看到,@param是用来说明输入参数的标签,@return是用来说明返回值的标签,文档生成工具最终会为将这种带注释的源代码解析成格式化好的HTML文档。
+如你所见,`@param`是用来说明输入参数的标签,`@return`是用来说明返回值的标签,文档生成工具最终会将这种带注释的源代码解析成HTML文档。
 
-
+================校对分割线================
 ### 一个例子:YUIDoc
 
 YUIDoc最初的目的是为YUI库(Yahoo! User Interface)生成文档,但也可以应用于任何项目,为了更充分的使用YUIDoc你需要学习它的注释规范,比如模块和类的写法(当然在JavaScript中是没有类的概念的)。

From b7d4dc338cb0eb963adf5c536d4793ba247b7293 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sat, 23 Mar 2013 12:06:02 +0800
Subject: [PATCH 160/258] =?UTF-8?q?=E7=A4=BA=E4=BE=8B=EF=BC=9AYUIDoc=20?=
 =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter2.markdown | 48 +++++++++++++++++++++++------------------------
 1 file changed, 24 insertions(+), 24 deletions(-)

diff --git a/chapter2.markdown b/chapter2.markdown
index 72d2b8f..42f93a8 100644
--- a/chapter2.markdown
+++ b/chapter2.markdown
@@ -771,24 +771,23 @@ ECMAScript的属性和方法均使用驼峰式命名,尽管包含多个单词
 
 如你所见,`@param`是用来说明输入参数的标签,`@return`是用来说明返回值的标签,文档生成工具最终会将这种带注释的源代码解析成HTML文档。
 
-================校对分割线================
-### 一个例子:YUIDoc
+### 示例:YUIDoc
 
-YUIDoc最初的目的是为YUI库(Yahoo! User Interface)生成文档,但也可以应用于任何项目,为了更充分的使用YUIDoc你需要学习它的注释规范,比如模块和类的写法(当然在JavaScript中是没有类的概念的)。
+YUIDoc的初衷是为YUI(Yahoo! User Interface)库生成文档,但其实它也可以应用于任何项目。为了更充分的使用YUIDoc,你需要学习它的注释规范,比如模块和类的写法。(尽管在JavaScript中其实是没有类的概念的)。
 
 让我们看一个用YUIDoc生成文档的完整例子。
 
-图2-1展示了最终生成的文档的模样,你可以根据项目需要随意定制HTML模板,让生成的文档更加友好和个性化。
+图2-1展示了最终生成的文档的样子,你可以根据项目需要定制HTML模板,让生成的文档更加友好和个性化。
 
-这里同样提供了在线的demo,请参照 http://jspatterns.com/book/2/。
+这里提供了在线的demo,请参照。
 
 这个例子中所有的应用作为一个模块(myapp)放在一个文件里(app.js),后续的章节会更详细的介绍模块,现在只需知道用可以用一个YUIDoc的标签来表示模块即可。
 
 图2-1 YUIDoc生成的文档
 
-![pic](http://img02.taobaocdn.com/tps/i2/T1fSCgXdBsXXXXXXXX-781-647.png)
+![YUIDoc生成的文档](./Figure/cahpter2/2-1.jpg)
 
-app.js的开始部分:
+`app.js`的开始部分:
 
 	/**
 	 * My JavaScript application
@@ -800,7 +799,7 @@ app.js的开始部分:
 
 	var MYAPP = {};
 
-紧接着定义了一个包含两个方法的对象math_stuff,这两个方法分别是sum()和multi():
+紧接着定义了一个包含两个方法的对象`math_stuff`,这两个方法分别是`sum()`和`multi()`:
 
 	/**
 	* A math utility
@@ -833,29 +832,29 @@ app.js的开始部分:
 		}
 	};
 
-这样就结束了第一个“类”的定义,注意粗体表示的标签。
+这样就完成了第一个“类”的定义,注意以下标签:
 
-@namespace
+- `@namespace`
 
-指向你的对象的全局引用
+	包含对象的全局引用
 
-@class
+- `@class`
 
-代表一个对象或构造函数的不恰当的称谓(JavaScript中没有类)
+	代表一个对象或构造函数(JavaScript中没有类)
 
-@method
+- `@method`
 
-定义对象的方法,并指定方法的名称
+	定义对象的方法,并指定方法的名称
 
-@param
+- `@param`
 
-列出函数需要的参数,参数的类型放在一对花括号内,跟随其后的是参数名和描述
+	列出函数需要的参数,参数的类型放在一对花括号内,后面跟参数名和描述
 
-@return
+- `@return`
 
-和@param类似,用以描述方法的返回值,可以不带名字
+	和@param类似,用以描述方法的返回值,可以不带名字
 
-我们用构造函数来实现第二个“类”,给这个类的原型添加一个方法,能够体会到YUIDoc采用了不同的方式来创建对象:
+我们来实现第二个“类”,使用一个构造函数,并给这个构造函数的原型添加一个方法,看看YUIDoc在面对不同的对象创建方式时是如何工作的:
 
 	/**
 	* Constructs Person objects
@@ -889,13 +888,14 @@ app.js的开始部分:
 		return this.first_name + ' ' + this.last_name;
 	};
 
-在图2-1中可以看到生成的文档中Person构造函数的生成结果,粗体的部分是:
+在图2-1中可以看到生成的文档中`Person`构造函数的生成结果,值得注意的部分是:
 
-- @constructor 暗示了这个“类”其实是一个构造函数
-- @prototype 和 @type 用来描述对象的属性
+- `@constructor` 说明这个“类”其实是一个构造函数
+- `@prototype` 和 `@type` 用来描述对象的属性
 
-YUIDoc工具是语言无关的,只解析注释块,而不是JavaScript代码。它的缺点是必须要在注释中指定属性、参数和方法的名字,比如,@property first_name。好处是一旦你熟练掌握YUIDoc,就可以用它对任何语言源码进行注释的文档化。
+YUIDoc工具是与语言无关的,只解析注释块,而不是JavaScript代码。它的缺点是必须要在注释中指定属性、参数和方法的名字,比如,`@property first_name`。好处是一旦你熟练掌握YUIDoc,就可以用它对任何语言源码生成文档。
 
+================校对分割线================
 
 ## 编写易读的代码
 

From 68f6c4b0929614f4131ab8e5056e9c9d68dfc0c9 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sat, 23 Mar 2013 12:38:01 +0800
Subject: [PATCH 161/258] =?UTF-8?q?=E7=BC=96=E5=86=99=E6=98=93=E8=AF=BB?=
 =?UTF-8?q?=E7=9A=84=E4=BB=A3=E7=A0=81=20=E6=A0=A1=E5=AF=B9=E5=AE=8C?=
 =?UTF-8?q?=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter2.markdown | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/chapter2.markdown b/chapter2.markdown
index 42f93a8..d035af9 100644
--- a/chapter2.markdown
+++ b/chapter2.markdown
@@ -895,22 +895,21 @@ YUIDoc的初衷是为YUI(Yahoo! User Interface)库生成文档,但其实
 
 YUIDoc工具是与语言无关的,只解析注释块,而不是JavaScript代码。它的缺点是必须要在注释中指定属性、参数和方法的名字,比如,`@property first_name`。好处是一旦你熟练掌握YUIDoc,就可以用它对任何语言源码生成文档。
 
-================校对分割线================
-
 ## 编写易读的代码
 
-这种将APIDoc格式的代码注释解析成API参考文档的做法看起来很偷懒,但还有另外一个目的,通过代码重审来提高代码质量。
+这种编写注释块来生成API文档的做法可不仅仅是为了偷懒,它还有另外一个作用,就是通过回头重看代码来提高代码质量。
 
-很多作者或编辑会告诉你“编辑非常重要”,甚至是写一本好书或好文章最最重要的步骤。将想法落实在纸上形成草稿只是第一步,草稿给读者提的信息往往重点不明晰、结构不合理、或不符合循序渐进的阅读习惯。
+随便一个作者或者编辑都会告诉你“编辑非常重要”,甚至是写一本好书或好文章最最重要的步骤。将想法落实在纸上形成草稿只是第一步,草稿确实可以给读者提供不少信息,但往往还不是重点最明晰、结构最合理、最符合阅读习惯的呈现形式。
 
-对于编程也是同样的道理,当你坐下来解决一个问题的时候,这时的解决方案只是一种“草案”,尽管能正常工作,但是不是最优的方法呢?是不是可读性好、易于理解、可维护佳或容易更新?当一段时间后再来review你的代码,一定会发现很多需要改进的地方,需要重新组织代码或删掉多余的内容等等。这实际上就是在“整理”你的代码了,可以很大程度提高你的代码质量。但事情往往不是这样,我们常常承受着高强度的工作压力,根本没有时间来整理代码,因此通过代码注释写文档其实是不错的机会。
+编程也是同样的道理,当你坐下来解决一个问题的时候,这时的解决方案只是一种“草案”,尽管能正常工作,但是不是最优的方法呢?是不是可读性好、易于理解、可维护和更新?假设当你过一段时间后再来回头看你的代码,一定会发现很多需要改进的地方,比如需要重新组织代码或删掉多余的内容等等。这实际上就是在“整理”你的代码了,可以很大程度上提高你的代码质量。但事实却不那么如愿,我们常常承受着高强度的工作,根本没有时间来整理代码,因此通过代码注释来写文档其实是个不错的机会。
 
-往往在写注释文档的时候,你会发现很多问题。你也会重新思考源代码中不合理之处,比如,某个方法中的第三个参数比第二个参数更常用,第二个参数多数情况下取值为true,因此就需要对这个方法接口进行适当的改造和包装。
+你往往会在写注释文档的时候发现很多问题,也会重新思考代码中的不合理之处,比如,某个方法中的第三个参数比第二个参数更常用,第二个参数多数情况下取值为`true`,因此就需要对这个方法进行适当的改造和包装。
 
-写出易读的代码(或API),是指别人能轻易读懂程序的思路。所以你需要采用更好的思路来解决手头的问题。
+写出易读的代码(或API),是指写代码时要有让别人能轻易读懂的意识。带着这个意识,你就需要不断思考采用更好的方法来解决手头的问题。
 
-尽管我们认为“草稿”不甚完美,但至少也算“抱佛脚”的权宜之计,一眼看上去是有点“草”,不过也无所谓,特别是当你处理的是一个关键项目时(会有人命悬与此)。其实你应当扔掉你所给出的第一个解决方案,虽然它是可以正常工作的,但毕竟是一个草率的方案,不是最佳方案。你给出的第二个方案会更加靠谱,因为这时你对问题的理解更加透彻。第二个方案不是简单的复制粘贴之前的代码,也不能投机取巧寻找某种捷径。
+说回“草稿”的问题,也算是“抱佛脚”的权宜之计,一眼看上去是有点“草”,不过至少是有用的,特别是当你处理的是一个关键项目时(比如人命关天时)。一个合适的思路是,你应当始终扔掉你所给出的第一个解决方案,虽然它是可以正常工作的,但毕竟是一个草稿,是一种仅用于验证解决问题可行性的方案。事实上,第二个方案往往会更好,因为这时你对问题的理解会更加透彻。在产生第二个方案的过程中,不要允许自己去复制粘贴之前的代码,这有助于阻止自己投机取巧利用之前的捷径,最后产生不完美的方案。
 
+================校对分割线================
 
 ## 相互评审
 

From 651cc6997089d4a0089b686057f57dd2fd031ab9 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sat, 23 Mar 2013 13:27:06 +0800
Subject: [PATCH 162/258] =?UTF-8?q?=E7=9B=B8=E4=BA=92=E8=AF=84=E5=AE=A1=20?=
 =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter2.markdown | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/chapter2.markdown b/chapter2.markdown
index d035af9..9c2f1a5 100644
--- a/chapter2.markdown
+++ b/chapter2.markdown
@@ -909,20 +909,19 @@ YUIDoc工具是与语言无关的,只解析注释块,而不是JavaScript代
 
 说回“草稿”的问题,也算是“抱佛脚”的权宜之计,一眼看上去是有点“草”,不过至少是有用的,特别是当你处理的是一个关键项目时(比如人命关天时)。一个合适的思路是,你应当始终扔掉你所给出的第一个解决方案,虽然它是可以正常工作的,但毕竟是一个草稿,是一种仅用于验证解决问题可行性的方案。事实上,第二个方案往往会更好,因为这时你对问题的理解会更加透彻。在产生第二个方案的过程中,不要允许自己去复制粘贴之前的代码,这有助于阻止自己投机取巧利用之前的捷径,最后产生不完美的方案。
 
-================校对分割线================
-
-## 相互评审
+## 同事评审(Peer Reviews)
 
-另外一种可以提高代码质量的方法是组织相互评审。同行的评审很正式也很规范,即便是求助于特定的工具,也不失是一种开发生产线上值得提倡的步骤。但你可能觉得没有时间去作代码互审,没关系,你可以让坐在你旁边的同事读一下你的代码,或者和她(译注:注意是“她”而不是“他”)一起过一遍你的代码。
+另外一种可以提高代码质量的方法是组织相互评审。同事评审可以用一些工具辅助,可以很正式很规范,也是一种开发流程中值得提倡的步骤。你可能觉得没有时间去作代码互相评审,没关系,你可以让坐在你旁边的同事读一下你的代码,或者和她(译注:注意是“她”而不是“他”)一起过一遍你的代码。
 
-同样,当你在写APIDoc或任何其他文档的时候,同行的评审能帮助你的产出物更加清晰,因为你写的文档是让别人读的,你必须确保别人能理解你所作的东西。
+同样,当你在写API文档或者其他文档的时候,同事评审能让你的产出物更加清晰,因为你写的文档是本来就是让别人读的,你得让别人通过文档知道你所做的东西。
 
-同行的评审是一种非常不错的习惯,不仅仅是因为它能让代码变得更好,更重要的,在评审的过程中,评审人和代码作者通过分享和讨论,两人都能取长补短、相互促进。
+同事评审是一种很好的实践,不仅仅是因为它能让代码变得更好,更重要的是,在评审的过程中,评审人和代码作者通过分享和讨论,两人都能取长补短、相互促进。
 
-如果你的团队只有你一个开发人员,找不出第二个人能给你作代码评审,这也没关系。你可以通过将你的代码片段开源,或把有意思的代码片段贴在博客中,会有人对你的代码感兴趣的。
+如果你的团队只有你一个开发人员,找不出第二个人能给你作代码评审,这也没关系。你可以通过将你的代码片段开源,或把有意思的代码片段贴在博客中,让全世界的人为你评审。
 
-另外一个非常好的习惯是使用版本管理工具(CVS,SVN或Git),一旦有人修改并提交了代码,都会发邮件通知组内成员。虽然大部分邮件都进入了垃圾箱,但总是会碰巧有人在工作间隙看到你所提交的代码,并对代码做出一些评价。
+另外一个很好的实践是使用版本管理工具(CVS、SVN或Git),一旦有人修改并提交了代码,就会发邮件通知组内成员。虽然大部分邮件都进入了垃圾箱,但总是会碰巧有人在工作间隙看到你所提交的代码,并对代码做出一些评价。
 
+================校对分割线================
 
 ## 生产环境中的代码压缩(Minify)
 

From 1472689fb3aaf873418e8ca28d79ee6813d84188 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sat, 23 Mar 2013 13:50:57 +0800
Subject: [PATCH 163/258] =?UTF-8?q?=E5=8F=91=E5=B8=83=E6=97=B6=E7=9A=84?=
 =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=8E=8B=E7=BC=A9=20=E6=A0=A1=E5=AF=B9?=
 =?UTF-8?q?=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter2.markdown | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/chapter2.markdown b/chapter2.markdown
index 9c2f1a5..767c5e1 100644
--- a/chapter2.markdown
+++ b/chapter2.markdown
@@ -921,25 +921,24 @@ YUIDoc工具是与语言无关的,只解析注释块,而不是JavaScript代
 
 另外一个很好的实践是使用版本管理工具(CVS、SVN或Git),一旦有人修改并提交了代码,就会发邮件通知组内成员。虽然大部分邮件都进入了垃圾箱,但总是会碰巧有人在工作间隙看到你所提交的代码,并对代码做出一些评价。
 
-================校对分割线================
-
-## 生产环境中的代码压缩(Minify)
+## 发布时的代码压缩(Minify)
 
-这里所说的代码压缩(Minify)是指去除JavaScript代码中的空格、注释以及其他不必要的部分,用以减少JavaScript文件的体积,降低网络带宽损耗。我们通常使用类似YUICompressor(Yahoo!)或Closure Compiler(Google)的压缩工具来为网页加载提速。对于生产环境(译注:“生产环境”指的是项目上线后的正式环境)中的脚本是需要作压缩的,压缩后的文件体积能减少至原来的一半以下。
+这里所说的代码压缩(Minify)是指去除JavaScript代码中的空格、注释以及其他不必要的部分,用以减少JavaScript文件的体积,降低网络带宽消耗。我们通常使用压缩工具来进行压缩,比如YUICompressor(Yahoo!)或Closure Compiler(Google),这可以减少页面加载时间。压缩用于发布的的脚本是很重要的,压缩后的文件体积能减少至原来的一半以下。
 
-下面这段代码是压缩后的样子(这段代码是YUI2库中的Event模块):
+下面这段代码是压缩后的样子(这段代码是YUI2库中的事件模块):
 
 	YAHOO.util.CustomEvent=function(D,C,B,A){this.type=D;this.scope=C||window;this.silent
 	=B;this.signature=A||YAHOO.util.CustomEvent.LIST;this.subscribers=[];if(!this.silent)
 	{}var E="_YUICEOnSubscribe";if(D!==E){this.subscribeEvent=new
 	YAHOO.util.CustomEvent(E,this,true);}...
 
-除了去除空格、空行和注释之外,压缩工具还能缩短命名的长度(前提是保证代码的安全),比如这段代码中的参数A、B、C、D。压缩工具只会重命名局部变量,因为更改全局变量会破坏代码的逻辑。这也是要尽量使用局部变量的原因。如果你使用的全局变量是对DOM节点的引用,而且程序中多次用到,最好将它赋值给一个局部变量,这样能提高查找速度,代码也会运行的更快,此外还能提高压缩比、加快下载速度(译注:在服务器开启Gzip的情况下,对下载速度的影响几乎可以忽略不计)。
+除了去除空格、空行和注释之外,压缩工具还能缩短命名的长度(在保证代码安全的前提下),比如这段代码中的参数`A`、`B`、`C`、`D`。压缩工具只会重命名局部变量,因为更改全局变量会破坏代码的逻辑,这也是要尽量使用局部变量的原因。如果你使用的全局变量是对DOM节点的引用,而且程序中多次用到,那么最好将它赋值给一个局部变量,这样能提高查找速度,代码也会运行的更快,此外还能提高压缩比、加快下载速度。
 
-补充说明一下,Goolge Closure Compiler还会对全局变量进行压缩(在“高级”模式中),这是很危险的,且对编程规范的要求非常苛刻。它的好处是压缩比非常高。
+补充说明一下,Goolge Closure Compiler还会为了更高的压缩比对全局变量进行压缩(在“高级”模式中),这是很危险的,且对编程规范的要求非常苛刻。
 
-对生产环境的脚本做压缩是相当重要的步骤,它能提升页面性能,你应当使用工具来完成压缩。千万不要试图手写“压缩好的”代码,你应当坚持使用语义化的变量命名,并保留足够的空格、缩进和注释。你写的代码是需要被人阅读的,所以应当将注意力放在代码可读性和可维护性上,代码压缩的工作交给工具去完成。
+对用于生产环境的脚本做压缩是非常重要的步骤,因为它能提升页面性能,但你应当将这个过程交给工具来完成。千万不要试图手写“压缩好的”代码,你应当在编写代码时坚持使用语义化的变量命名,并保留足够的空格、缩进和注释。你写的代码是需要被人阅读的,所以应当将注意力放在代码可读性和可维护性上,将代码压缩的工作交给工具去完成。
 
+================校对分割线================
 
 ## 运行 JSLint
 

From ead3ae6659d4d62fc25d16ff6f6c7c9bd451479f Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sat, 23 Mar 2013 14:09:36 +0800
Subject: [PATCH 164/258] =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E7=AB=A0=20=E6=A0=A1?=
 =?UTF-8?q?=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter2.markdown | 38 +++++++++++++++++---------------------
 1 file changed, 17 insertions(+), 21 deletions(-)

diff --git a/chapter2.markdown b/chapter2.markdown
index 767c5e1..ff1decb 100644
--- a/chapter2.markdown
+++ b/chapter2.markdown
@@ -1,6 +1,6 @@
 # 第二章 概要
 
-本章将概要介绍一些编写高质量JavaScript的最佳实践、模式和习惯,比如避免全局变量、使用单var声明、预缓存循环中的length、遵守编码约定等等。本章还包括一些编程习惯,这些习惯跟具体的代码关系不大,而是更多关注代码创建的总体过程,包括撰写API文档、code review以及使用JSLint。这些习惯和最佳实践可以帮助你写出更好更易读和可维护性更好的代码,当几个月或数年后你再重读你的代码时,就会深有体会了。
+本章将概要介绍一些编写高质量JavaScript的最佳实践、模式和习惯,比如避免全局变量、使用单`var`声明、预缓存循环中的`length`、遵守编码约定等等。本章还包括一些编程习惯,这些习惯跟具体的代码关系不大,而是更多关注代码创建的总体过程,包括撰写API文档、同事评审以及使用JSLint。这些习惯和最佳实践可以帮助你写出更好更易读和可维护性更好的代码,当几个月或数年后你再重读你的代码时,就会深有体会了。
 
 ## 编写可维护的代码
 
@@ -144,7 +144,7 @@ JavaScript使用函数来管理作用域,在一个函数内定义的变量称
 - 提醒你不要忘记声明变量,顺便减少潜在的全局变量
 - 代码量更少(输入代码更少且更易做代码优化)
 
-单var模式看起来像这样:
+单`var`模式看起来像这样:
 
 	function func() {
 		var a = 1,
@@ -243,7 +243,7 @@ JavaScript允许在函数的任意地方写任意多个`var`语句,但它们
 
 需要注意的是,当你在循环过程中需要修改这个元素集合(比如增加DOM元素)时,你可能需要更新`length`。
 
-按照“单var模式”,你可以将`var`提到循环的外部,比如:
+按照“单`var`模式”,你可以将`var`提到循环的外部,比如:
 
 	function looper() {
 		var i = 0,
@@ -255,7 +255,7 @@ JavaScript允许在函数的任意地方写任意多个`var`语句,但它们
 		}
 	}
 
-当你越来越依赖“单var模式”时,带来的好处就是提高了代码的一致性。而缺点则是在重构代码的时候不能直接复制粘贴一个循环体,比如,你正在将某个循环从一个函数复制至另外一个函数中,那么必须确保`i`和`max`也复制到新函数里,并且需要从旧函数中将这些没用的变量删除掉。
+当你越来越依赖“单`var`模式”时,带来的好处就是提高了代码的一致性。而缺点则是在重构代码的时候不能直接复制粘贴一个循环体,比如,你正在将某个循环从一个函数复制至另外一个函数中,那么必须确保`i`和`max`也复制到新函数里,并且需要从旧函数中将这些没用的变量删除掉。
 
 最后一个需要对循环做出调整的地方是将i++替换成为下面两者之一:
 
@@ -938,33 +938,29 @@ YUIDoc工具是与语言无关的,只解析注释块,而不是JavaScript代
 
 对用于生产环境的脚本做压缩是非常重要的步骤,因为它能提升页面性能,但你应当将这个过程交给工具来完成。千万不要试图手写“压缩好的”代码,你应当在编写代码时坚持使用语义化的变量命名,并保留足够的空格、缩进和注释。你写的代码是需要被人阅读的,所以应当将注意力放在代码可读性和可维护性上,将代码压缩的工作交给工具去完成。
 
-================校对分割线================
-
-## 运行 JSLint
+## 运行JSLint
 
-在上一章我们已经介绍了JSLint,这里我们介绍更多的使用场景。对你的代码进行JSLint检查是非常好的编程习惯,你应该相信这一点。
+在上一章我们已经介绍了JSLint,本章中也提到了数次。到现在你应该已经相信用JSLint检查你的代码是一种好的编程模式了。
 
-JSLint的检查点都有哪些呢?它会对本章讨论过的一些模式(单var模式、parseInt()的第二个参数、总是使用花括号)做检查。JSLint还包括其他方面的检查:
+JSLint的检查点都有哪些呢?它会对本章讨论过的一些模式(单`var`模式、`parseInt()`的第二个参数、总是使用花括号)做检查。JSLint还包括其他方面的检查:
 
-- 不可达代码
-- 在使用变量之前需要声明
+- 不可达代码(译注:指永远不可能运行的代码)
+- 变量在声明之前被使用
 - 不安全的UTF字符
-- 使用void、with、和eval
+- 使用`void`、`with`或者`eval`
 - 无法正确解析的正则表达式
 
-JSLint是基于JavaScript实现的(它是可以通过JSLint检查的),它提供了在线工具,也可以下载使用,可以运行于很多种平台的JavaScript解析器。你可以将源码下载后在本地运行,支持的环境包括WSH(Windows Scripting Host,Windows)、JSC(JavaScriptCore,MacOSX)或Rhino(Mozilla开发的JavaScript引擎)。
+JSLint是基于JavaScript实现的(它自己的代码是可以通过JSLint检查的),它提供了在线工具,也可以下载使用,可以运行于很多种平台的JavaScript解析器。你可以将源码下载后在本地运行,支持的环境包括WSH(Windows Scripting Host,Windows)、JSC(JavaScriptCore,MacOSX)或Rhino(Mozilla开发的JavaScript引擎)。
 
-可以将JSLint下载后和你的代码编辑器配置在一起,着是一个不错的注意,这样每次你保存代码的时候都会自动执行代码检查(比如配置快捷键)。
+将JSLint下载后和你的代码编辑器配置在一起是个很不错的主意,这样每次你保存代码的时候都会自动执行代码检查。(为它配置一个快捷键也很有用)。
 
-
 ## 小结
 
-本章我们讲解了编写可维护性代码的含义,本章的讨论非常重要,它不仅关系着软件项目的成功与否,还关系到参与项目的工程师的“精神健康”和“幸福指数”。随后我们讨论了一些最佳实践和模式,它们包括:
+本章我们讨论了编写可维护性代码的意义,它不仅关系着软件项目的成功与否,还关系到参与项目的工程师的“精神健康”和“幸福指数”。随后我们讨论了一些最佳实践和模式,它们包括:
 
 - 减少全局对象,最好每个应用只有一个全局对象
-- 函数都使用单var模式来定义,这样可以将所有的变量放在同一个地方声明,同时可以避免“声明提前”给程序逻辑带来的影响。
-- for循环、for-in循环、switch语句、“禁止使用eval()”、不要扩充内置原型
-- 遵守统一的编码规范(在任何必要的时候保持空格、缩进、花括号和分号)和命名约定(构造函数、普通函数和变量)。
-
-本章还讨论了其他一些和代码本身无关的实践,这些实践和编码过程紧密相关,包括书写注释、生成API文档,组织代码评审、不要试图去手动了“压缩”(minify)代码而牺牲代码可读性、坚持使用JSLint来对代码做检查。
+- 函数都使用单`var`模式来定义,这样可以将所有的变量放在同一个地方声明,同时可以避免“声明提前”给程序逻辑带来的影响
+- `for`循环、`for-in`循环、`switch`语句、“避免使用`eval()`”、不要扩充内置原型
+- 遵守统一的编码规范(在任何必要的时候保持空格、缩进、花括号和分号)和命名规范(构造函数、普通函数和变量)。
 
+本章还讨论了其他一些和代码本身无关的实践,这些实践和编码过程紧密相关,包括写注释、写API文档、组织同事评审、不要试图去手动“压缩”(minify)代码而牺牲代码可读性、坚持使用JSLint来对代码进行检查。
\ No newline at end of file

From d4b2e20d3da7790a80f9cb196ea53cc77b3ab18a Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sat, 23 Mar 2013 14:09:36 +0800
Subject: [PATCH 165/258] =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E7=AB=A0=20=E6=A0=A1?=
 =?UTF-8?q?=E5=AF=B9=E5=AE=8C=E6=AF=95=20close=20#3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter2.markdown | 38 +++++++++++++++++---------------------
 1 file changed, 17 insertions(+), 21 deletions(-)

diff --git a/chapter2.markdown b/chapter2.markdown
index 767c5e1..ff1decb 100644
--- a/chapter2.markdown
+++ b/chapter2.markdown
@@ -1,6 +1,6 @@
 # 第二章 概要
 
-本章将概要介绍一些编写高质量JavaScript的最佳实践、模式和习惯,比如避免全局变量、使用单var声明、预缓存循环中的length、遵守编码约定等等。本章还包括一些编程习惯,这些习惯跟具体的代码关系不大,而是更多关注代码创建的总体过程,包括撰写API文档、code review以及使用JSLint。这些习惯和最佳实践可以帮助你写出更好更易读和可维护性更好的代码,当几个月或数年后你再重读你的代码时,就会深有体会了。
+本章将概要介绍一些编写高质量JavaScript的最佳实践、模式和习惯,比如避免全局变量、使用单`var`声明、预缓存循环中的`length`、遵守编码约定等等。本章还包括一些编程习惯,这些习惯跟具体的代码关系不大,而是更多关注代码创建的总体过程,包括撰写API文档、同事评审以及使用JSLint。这些习惯和最佳实践可以帮助你写出更好更易读和可维护性更好的代码,当几个月或数年后你再重读你的代码时,就会深有体会了。
 
 ## 编写可维护的代码
 
@@ -144,7 +144,7 @@ JavaScript使用函数来管理作用域,在一个函数内定义的变量称
 - 提醒你不要忘记声明变量,顺便减少潜在的全局变量
 - 代码量更少(输入代码更少且更易做代码优化)
 
-单var模式看起来像这样:
+单`var`模式看起来像这样:
 
 	function func() {
 		var a = 1,
@@ -243,7 +243,7 @@ JavaScript允许在函数的任意地方写任意多个`var`语句,但它们
 
 需要注意的是,当你在循环过程中需要修改这个元素集合(比如增加DOM元素)时,你可能需要更新`length`。
 
-按照“单var模式”,你可以将`var`提到循环的外部,比如:
+按照“单`var`模式”,你可以将`var`提到循环的外部,比如:
 
 	function looper() {
 		var i = 0,
@@ -255,7 +255,7 @@ JavaScript允许在函数的任意地方写任意多个`var`语句,但它们
 		}
 	}
 
-当你越来越依赖“单var模式”时,带来的好处就是提高了代码的一致性。而缺点则是在重构代码的时候不能直接复制粘贴一个循环体,比如,你正在将某个循环从一个函数复制至另外一个函数中,那么必须确保`i`和`max`也复制到新函数里,并且需要从旧函数中将这些没用的变量删除掉。
+当你越来越依赖“单`var`模式”时,带来的好处就是提高了代码的一致性。而缺点则是在重构代码的时候不能直接复制粘贴一个循环体,比如,你正在将某个循环从一个函数复制至另外一个函数中,那么必须确保`i`和`max`也复制到新函数里,并且需要从旧函数中将这些没用的变量删除掉。
 
 最后一个需要对循环做出调整的地方是将i++替换成为下面两者之一:
 
@@ -938,33 +938,29 @@ YUIDoc工具是与语言无关的,只解析注释块,而不是JavaScript代
 
 对用于生产环境的脚本做压缩是非常重要的步骤,因为它能提升页面性能,但你应当将这个过程交给工具来完成。千万不要试图手写“压缩好的”代码,你应当在编写代码时坚持使用语义化的变量命名,并保留足够的空格、缩进和注释。你写的代码是需要被人阅读的,所以应当将注意力放在代码可读性和可维护性上,将代码压缩的工作交给工具去完成。
 
-================校对分割线================
-
-## 运行 JSLint
+## 运行JSLint
 
-在上一章我们已经介绍了JSLint,这里我们介绍更多的使用场景。对你的代码进行JSLint检查是非常好的编程习惯,你应该相信这一点。
+在上一章我们已经介绍了JSLint,本章中也提到了数次。到现在你应该已经相信用JSLint检查你的代码是一种好的编程模式了。
 
-JSLint的检查点都有哪些呢?它会对本章讨论过的一些模式(单var模式、parseInt()的第二个参数、总是使用花括号)做检查。JSLint还包括其他方面的检查:
+JSLint的检查点都有哪些呢?它会对本章讨论过的一些模式(单`var`模式、`parseInt()`的第二个参数、总是使用花括号)做检查。JSLint还包括其他方面的检查:
 
-- 不可达代码
-- 在使用变量之前需要声明
+- 不可达代码(译注:指永远不可能运行的代码)
+- 变量在声明之前被使用
 - 不安全的UTF字符
-- 使用void、with、和eval
+- 使用`void`、`with`或者`eval`
 - 无法正确解析的正则表达式
 
-JSLint是基于JavaScript实现的(它是可以通过JSLint检查的),它提供了在线工具,也可以下载使用,可以运行于很多种平台的JavaScript解析器。你可以将源码下载后在本地运行,支持的环境包括WSH(Windows Scripting Host,Windows)、JSC(JavaScriptCore,MacOSX)或Rhino(Mozilla开发的JavaScript引擎)。
+JSLint是基于JavaScript实现的(它自己的代码是可以通过JSLint检查的),它提供了在线工具,也可以下载使用,可以运行于很多种平台的JavaScript解析器。你可以将源码下载后在本地运行,支持的环境包括WSH(Windows Scripting Host,Windows)、JSC(JavaScriptCore,MacOSX)或Rhino(Mozilla开发的JavaScript引擎)。
 
-可以将JSLint下载后和你的代码编辑器配置在一起,着是一个不错的注意,这样每次你保存代码的时候都会自动执行代码检查(比如配置快捷键)。
+将JSLint下载后和你的代码编辑器配置在一起是个很不错的主意,这样每次你保存代码的时候都会自动执行代码检查。(为它配置一个快捷键也很有用)。
 
-
 ## 小结
 
-本章我们讲解了编写可维护性代码的含义,本章的讨论非常重要,它不仅关系着软件项目的成功与否,还关系到参与项目的工程师的“精神健康”和“幸福指数”。随后我们讨论了一些最佳实践和模式,它们包括:
+本章我们讨论了编写可维护性代码的意义,它不仅关系着软件项目的成功与否,还关系到参与项目的工程师的“精神健康”和“幸福指数”。随后我们讨论了一些最佳实践和模式,它们包括:
 
 - 减少全局对象,最好每个应用只有一个全局对象
-- 函数都使用单var模式来定义,这样可以将所有的变量放在同一个地方声明,同时可以避免“声明提前”给程序逻辑带来的影响。
-- for循环、for-in循环、switch语句、“禁止使用eval()”、不要扩充内置原型
-- 遵守统一的编码规范(在任何必要的时候保持空格、缩进、花括号和分号)和命名约定(构造函数、普通函数和变量)。
-
-本章还讨论了其他一些和代码本身无关的实践,这些实践和编码过程紧密相关,包括书写注释、生成API文档,组织代码评审、不要试图去手动了“压缩”(minify)代码而牺牲代码可读性、坚持使用JSLint来对代码做检查。
+- 函数都使用单`var`模式来定义,这样可以将所有的变量放在同一个地方声明,同时可以避免“声明提前”给程序逻辑带来的影响
+- `for`循环、`for-in`循环、`switch`语句、“避免使用`eval()`”、不要扩充内置原型
+- 遵守统一的编码规范(在任何必要的时候保持空格、缩进、花括号和分号)和命名规范(构造函数、普通函数和变量)。
 
+本章还讨论了其他一些和代码本身无关的实践,这些实践和编码过程紧密相关,包括写注释、写API文档、组织同事评审、不要试图去手动“压缩”(minify)代码而牺牲代码可读性、坚持使用JSLint来对代码进行检查。
\ No newline at end of file

From 0d5b3ca5cdc699d5c041862de15b909cf5888fc4 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Thu, 28 Mar 2013 13:03:40 +0800
Subject: [PATCH 166/258] =?UTF-8?q?=E5=BA=8F=E6=A0=A1=E5=AF=B9=E5=AE=8C?=
 =?UTF-8?q?=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter3.markdown | 87 +++++++++++++++++++++++------------------------
 1 file changed, 43 insertions(+), 44 deletions(-)

diff --git a/chapter3.markdown b/chapter3.markdown
index 55c32b0..0bb493a 100644
--- a/chapter3.markdown
+++ b/chapter3.markdown
@@ -1,16 +1,15 @@
-
-# 第三章 直接量和构造函数
+# 第三章 字面量和构造函数
 
-JavaScript中的直接量模式更加简洁、富有表现力,且在定义对象时不容易出错。本章将对直接量展开讨论,包括对象、数组和正则表达式直接量,以及为什么要使用等价的内置构造器函数来创建它们,比如Object()和Array()等。本章同样会介绍JSON格式,JSON是使用数组和对象直接量的形式定义的一种数据转换格式。本章还会讨论自定义构造函数,包括如何强制使用new以确保构造函数的正确执行。
+JavaScript中的字面量模式更加简洁、有表现力,而且在定义对象时不容易出错。本章将会讨论字面量,包括对象、数组和正则表达式字面量,以及为什么字面量要比等价的内置构造函数(如`Object()`、`Array()`等)要更好。本章还会介绍JSON格式,JSON是使用数组和对象字面量的形式定义的一种数据传输格式。本章还会讨论自定义构造函数,包括如何强制使用`new`以确保构造函数正确执行。
 
-本章还会补充讲述一些基础知识,比如内置包装对象Number()、String()和Boolean(),以及如何将它们和原始值(数字、字符串和布尔值)比较。最后,快速介绍一下Error()构造函数的用法。
+为了方便使用字面量而不是构造函数,本章还会补充一些知识,比如内置包装构造函数`Number()`、`String()`和`Boolean()`,以及如何将它们和原始值(数字、字符串和布尔值)比较。最后,快速介绍一下`Error()`构造函数的用法。
 
-
-## 对象直接量
+-------------------校对分隔线-----------------
+## 对象字面量
 
 我们可以将JavaScript中的对象简单的理解为名值对组成的散列表(hash table),在其他编程语言中被称作“关联数组”。其中的值可以是原始值也可以是对象,不管是什么类型,它们都是“属性”(properties),属性值同样可以是函数,这时属性就被称为“方法”(methods)。
 
-JavaScript中自定义的对象(用户定义的本地对象)任何时候都是可变的。内置本地对象的属性也是可变的。你可以先创建一个空对象,然后在需要时给它添加功能。“对象直接量写法(object literal notation)”是按需创建对象的一种理想方式。
+JavaScript中自定义的对象(用户定义的本地对象)任何时候都是可变的。内置本地对象的属性也是可变的。你可以先创建一个空对象,然后在需要时给它添加功能。“对象字面量写法(object literal notation)”是按需创建对象的一种理想方式。
 
 看一下这个例子:
 
@@ -46,7 +45,7 @@ JavaScript中自定义的对象(用户定义的本地对象)任何时候都
 	};
 	dog.fleas = true;
 
-其实不必每次开始都创建空对象,对象直接量模式可以直接在创建对象时添加功能,就像下面这个例子所展示的:
+其实不必每次开始都创建空对象,对象字面量模式可以直接在创建对象时添加功能,就像下面这个例子所展示的:
 
 	var dog = {
 		name: "Benji",
@@ -58,9 +57,9 @@ JavaScript中自定义的对象(用户定义的本地对象)任何时候都
 > 在本书中多次提到“空对象”(“blank object”和“empty object”)。这只是某种简称,要知道JavaScript中根本不存在真正的空对象,理解这一点至关重要。即使最简单的`{}`对象包含从Object.prototype继承来的属性和方法。我们提到的“空(empty)对象”只是说这个对象没有自己的属性,不考虑它是否有继承来的属性。
 
 
-### 对象直接量语法
+### 对象字面量语法
 
-如果你从来没有接触过对象直接量写法,第一次碰到可能会感觉怪怪的。但越到后来你就越喜欢它。本质上讲,对象直接量语法包括:
+如果你从来没有接触过对象字面量写法,第一次碰到可能会感觉怪怪的。但越到后来你就越喜欢它。本质上讲,对象字面量语法包括:
 
 - 将对象主体包含在一对花括号内(`{` and `}`)。
 - 对象内的属性或方法之间使用逗号分隔。最后一个名值对后也可以有逗号,但在IE下会报错,所以尽量不要在最后一个属性或方法后加逗号。
@@ -84,14 +83,14 @@ JavaScript中没有类的概念,这给JavaScript带来了极大的灵活性,
 	var car = new Object();
 	car.goes = "far";
 
-从这个例子中可以看到,直接量写法的一个明显优势是,它的代码更少。“创建对象的最佳模式是使用直接量”还有一个原因,它可以强调对象就是一个简单的可变的散列表,而不必一定派生自某个类。
+从这个例子中可以看到,字面量写法的一个明显优势是,它的代码更少。“创建对象的最佳模式是使用字面量”还有一个原因,它可以强调对象就是一个简单的可变的散列表,而不必一定派生自某个类。
 
-另外一个使用直接量而不是Object构造函数创建实例对象的原因是,对象直接量不需要“作用域解析”(scope resolution)。因为新创建的实例有可能包含了一个本地的构造函数,当你调用Object()的时候,解析器需要顺着作用域链从当前作用域开始查找,直到找到全局Object构造函数为止。
+另外一个使用字面量而不是Object构造函数创建实例对象的原因是,对象字面量不需要“作用域解析”(scope resolution)。因为新创建的实例有可能包含了一个本地的构造函数,当你调用Object()的时候,解析器需要顺着作用域链从当前作用域开始查找,直到找到全局Object构造函数为止。
 
 
 ### 获得对象的构造器
 
-创建实例对象时能用对象直接量就不要使用new Object()构造函数,但有时你希望能继承别人写的代码,这时就需要了解构造函数的一个“特性”(也是不使用它的另一个原因),就是Object()构造函数可以接收参数,通过参数的设置可以把实例对象的创建委托给另一个内置构造函数,并返回另外一个实例对象,而这往往不是你所希望的。
+创建实例对象时能用对象字面量就不要使用new Object()构造函数,但有时你希望能继承别人写的代码,这时就需要了解构造函数的一个“特性”(也是不使用它的另一个原因),就是Object()构造函数可以接收参数,通过参数的设置可以把实例对象的创建委托给另一个内置构造函数,并返回另外一个实例对象,而这往往不是你所希望的。
 
 下面的示例代码中展示了给new Object()传入不同的参数:数字、字符串和布尔值,最终得到的对象都是由不同的构造函数生成的:
 
@@ -117,12 +116,12 @@ JavaScript中没有类的概念,这给JavaScript带来了极大的灵活性,
 	var o = new Object(true);
 	console.log(o.constructor === Boolean); // true
 
-Object()构造函数的这种特性会导致一些意想不到的结果,特别是当参数不确定的时候。最后再次提醒不要使用new Object(),尽可能的使用对象直接量来创建实例对象。
+Object()构造函数的这种特性会导致一些意想不到的结果,特别是当参数不确定的时候。最后再次提醒不要使用new Object(),尽可能的使用对象字面量来创建实例对象。
 
 
 ## 自定义构造函数
 
-除了对象直接量和内置构造函数之外,你也可以通过自定义的构造函数来创建实例对象,正如下面的代码所示:
+除了对象字面量和内置构造函数之外,你也可以通过自定义的构造函数来创建实例对象,正如下面的代码所示:
 
 	var adam = new Person("Adam");
 	adam.say(); // "I am Adam"
@@ -245,7 +244,7 @@ ECMAScript5中修正了这种非正常的行为逻辑。在严格模式中,thi
 		return that;
 	}
 
-如果要创建简单的实例对象,甚至不需要定义一个局部变量that,可以直接返回一个对象直接量,就像这样:
+如果要创建简单的实例对象,甚至不需要定义一个局部变量that,可以直接返回一个对象字面量,就像这样:
 
 	function Waffle() {
 		return {
@@ -298,11 +297,11 @@ ECMAScript5中修正了这种非正常的行为逻辑。在严格模式中,thi
 这里需要说明的是,在任何函数内部都会自行创建一个arguments对象,它包含函数调用时传入的参数。同时arguments包含一个callee属性,指向它所在的正在被调用的函数。需要注意,ES5严格模式中是禁止使用arguments.callee的,因此最好对它的使用加以限制,并删除任何你能在代码中找到的实例(译注:这里作者的表述很委婉,其实作者更倾向于全面禁止使用arguments.callee)。
 
 
-## 数组直接量
+## 数组字面量
 
-和其他的大多数一样,JavaScript中的数组也是对象。可以通过内置构造函数Array()来创建数组,类似对象直接量,数组也可以通过直接量形式创建。而且更推荐使用直接量创建数组。
+和其他的大多数一样,JavaScript中的数组也是对象。可以通过内置构造函数Array()来创建数组,类似对象字面量,数组也可以通过字面量形式创建。而且更推荐使用字面量创建数组。
 
-这里的实例代码给出了创建两个具有相同元素的数组的两种方法,使用Array()和使用直接量模式:
+这里的实例代码给出了创建两个具有相同元素的数组的两种方法,使用Array()和使用字面量模式:
 
 	// array of three elements
 	// warning: antipattern
@@ -315,18 +314,18 @@ ECMAScript5中修正了这种非正常的行为逻辑。在严格模式中,thi
 	console.log(a.constructor === Array); // true
 
 
-### 数组直接量语法
+### 数组字面量语法
 
-数组直接量写法非常简单:整个数组使用方括号括起来,数组元素之间使用逗号分隔。数组元素可以是任意类型,也包括数组和对象。
+数组字面量写法非常简单:整个数组使用方括号括起来,数组元素之间使用逗号分隔。数组元素可以是任意类型,也包括数组和对象。
 
-数组直接量语法简单直接、高雅美观。毕竟数组只是从位置0开始索引的值的集合,完全没必要包含构造器和new运算符的内容(代码会更多),保持简单即可。
+数组字面量语法简单直接、高雅美观。毕竟数组只是从位置0开始索引的值的集合,完全没必要包含构造器和new运算符的内容(代码会更多),保持简单即可。
 
 
 ### 有意思的数组构造器
 
 我们对new Array()敬而远之原因是为了避免构造函数带来的陷阱。
 
-如果给Array()构造器传入一个数字,这个数字并不会成为数组的第一个元素,而是设置数组的长度。也就是说,new Array(3)创建了一个长度为3的数组,而不是某个元素是3。如果你访问数组的任意元素都会得到undefined,因为元素并不存在。下面示例代码展示了直接量和构造函数的区别:
+如果给Array()构造器传入一个数字,这个数字并不会成为数组的第一个元素,而是设置数组的长度。也就是说,new Array(3)创建了一个长度为3的数组,而不是某个元素是3。如果你访问数组的任意元素都会得到undefined,因为元素并不存在。下面示例代码展示了字面量和构造函数的区别:
 
 	// an array of one element
 	var a = [3];
@@ -347,7 +346,7 @@ ECMAScript5中修正了这种非正常的行为逻辑。在严格模式中,thi
 	var a = new Array(3.14); // RangeError: invalid array length
 	console.log(typeof a); // "undefined"
 
-为了避免在运行时动态创建数组时出现这种错误,强烈推荐使用数组直接量来代替new Array()。
+为了避免在运行时动态创建数组时出现这种错误,强烈推荐使用数组字面量来代替new Array()。
 
 >有些人用Array()构造器来做一些有意思的事情,比如用来生成重复字符串。下面这行代码返字符串包含255个空格(请读者思考为什么不是256个空格)。`var white = new Array(256).join(' ');`
 
@@ -383,15 +382,15 @@ ECMAScript 5定义了一个新的方法Array.isArray(),如果参数是数组
 
 ## JSON
 
-上文我们刚刚讨论过对象和数组直接量,你已经对此很熟悉了,现在我们将目光转向JSON。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。很多语言中都实现了JSON,特别是在JavaScript中。
+上文我们刚刚讨论过对象和数组字面量,你已经对此很熟悉了,现在我们将目光转向JSON。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。很多语言中都实现了JSON,特别是在JavaScript中。
 
-JSON格式及其简单,它只是数组和对象直接量的混合写法,看一个JSON字符串的例子:
+JSON格式及其简单,它只是数组和对象字面量的混合写法,看一个JSON字符串的例子:
 
 	{"name": "value", "some": [1, 2, 3]}
 
-JSON和对象直接量在语法上的唯一区别是,合法的JSON属性名均用引号包含。而在对象直接量中,只有属性名是非法的标识符时采用引号包含,比如,属性名中包含空格`{"first name": "Dave"}`。
+JSON和对象字面量在语法上的唯一区别是,合法的JSON属性名均用引号包含。而在对象字面量中,只有属性名是非法的标识符时采用引号包含,比如,属性名中包含空格`{"first name": "Dave"}`。
 
-在JSON字符串中,不能使用函数和正则表达式直接量。
+在JSON字符串中,不能使用函数和正则表达式字面量。
 
 
 ### 使用JSON
@@ -443,12 +442,12 @@ JSON和对象直接量在语法上的唯一区别是,合法的JSON属性名均
 	// {"name":"Fido","dob":"2010-04-11T22:36:22.436Z","legs":[1,2,3,4]}
 
 
-## 正则表达式直接量
+## 正则表达式字面量
 
 JavaScript中的正则表达式也是对象,可以通过两种方式创建它们:
 
 - 使用new RegExp()构造函数
-- 使用正则表达式直接量
+- 使用正则表达式字面量
 
 下面的示例代码展示了创建正则表达式的两种方法,创建的正则用来匹配一个反斜杠(\):
 
@@ -458,14 +457,14 @@ JavaScript中的正则表达式也是对象,可以通过两种方式创建它
 	// constructor
 	var re = new RegExp("\\\\", "gm");
 
-显然正则表达式直接量写法的代码更短,且不必强制按照类构造器的思路来写。因此更推荐使用直接量写法。
+显然正则表达式字面量写法的代码更短,且不必强制按照类构造器的思路来写。因此更推荐使用字面量写法。
 
-另外,如果使用RegExp()构造函数写法,还需要考虑对引号和反斜杠进行转义,正如上段代码所示的那样,用了四个反斜杠来匹配一个反斜杠。这会增加正则表达式的长度,而且让正则变得难于理解和维护。刚开始学习正则表达式不是很容易,所以不要放弃任何一个简化它们的机会,所以要尽量使用直接量而不是通过构造函数来创建正则。
+另外,如果使用RegExp()构造函数写法,还需要考虑对引号和反斜杠进行转义,正如上段代码所示的那样,用了四个反斜杠来匹配一个反斜杠。这会增加正则表达式的长度,而且让正则变得难于理解和维护。刚开始学习正则表达式不是很容易,所以不要放弃任何一个简化它们的机会,所以要尽量使用字面量而不是通过构造函数来创建正则。
 
 
-### 正则表达式直接量语法
+### 正则表达式字面量语法
 
-正则表达式直接量使用两个斜线包裹起来,正则的主体部分不包括两端的斜线。在第二个斜线之后可以指定模式匹配的修饰符用以高级匹配,修饰符不需要引号引起来,JavaScript中有三个修饰符:
+正则表达式字面量使用两个斜线包裹起来,正则的主体部分不包括两端的斜线。在第二个斜线之后可以指定模式匹配的修饰符用以高级匹配,修饰符不需要引号引起来,JavaScript中有三个修饰符:
 
 - g,全局匹配
 - m,多行匹配
@@ -475,14 +474,14 @@ JavaScript中的正则表达式也是对象,可以通过两种方式创建它
 
 	var re = /pattern/gmi;
 
-使用正则表达式直接量可以让代码更加简洁高效,比如当调用String.prototype.prelace()方法时,可以传入正则表达式参数:
+使用正则表达式字面量可以让代码更加简洁高效,比如当调用String.prototype.prelace()方法时,可以传入正则表达式参数:
 
 	var no_letters = "abc123XYZ".replace(/[a-z]/gi, "");
 	console.log(no_letters); // 123
 
 有一种不得不使用new RegExp()的情形,有时正则表达式是不确定的,直到运行时才能确定下来。
 
-正则表达式直接量和Regexp()构造函数的另一个区别是,正则表达式直接量只在解析时创建一次正则表达式对象(译注:多次解析同一个正则表达式,会产生相同的实例对象)。如果在循环体内反复创建相同的正则表达式,则每个正则对象的所有属性(比如lastIndex)只会设置一次(译注:由于每次创建相同的实例对象,每个循环中的实例对象都是同一个,属性也自然相同),下面这个例子展示了两次都返回了相同的正则表达式的情形(译注:这里作者的表述只是针对ES3规范而言,下面这段代码在NodeJS、IE6-IE9、FireFox4、Chrome10、Safari5中运行结果和作者描述的不一致,Firefox 3.6中的运行结果和作者描述是一致的,原因可以在ECMAScript5规范第24页和第247页找到,也就是说在ECMAScript3规范中,用正则表达式创建的RegExp对象会共享同一个实例,而在ECMAScript5中则是两个独立的实例。而最新的Firefox4、Chrome和Safari5都遵循ECMAScript5标准,至于IE6-IE8都没有很好的遵循ECMAScript3标准,不过在这个问题上反而处理对了。很明显ECMAScript5的规范更符合开发者的期望)。
+正则表达式字面量和Regexp()构造函数的另一个区别是,正则表达式字面量只在解析时创建一次正则表达式对象(译注:多次解析同一个正则表达式,会产生相同的实例对象)。如果在循环体内反复创建相同的正则表达式,则每个正则对象的所有属性(比如lastIndex)只会设置一次(译注:由于每次创建相同的实例对象,每个循环中的实例对象都是同一个,属性也自然相同),下面这个例子展示了两次都返回了相同的正则表达式的情形(译注:这里作者的表述只是针对ES3规范而言,下面这段代码在NodeJS、IE6-IE9、FireFox4、Chrome10、Safari5中运行结果和作者描述的不一致,Firefox 3.6中的运行结果和作者描述是一致的,原因可以在ECMAScript5规范第24页和第247页找到,也就是说在ECMAScript3规范中,用正则表达式创建的RegExp对象会共享同一个实例,而在ECMAScript5中则是两个独立的实例。而最新的Firefox4、Chrome和Safari5都遵循ECMAScript5标准,至于IE6-IE8都没有很好的遵循ECMAScript3标准,不过在这个问题上反而处理对了。很明显ECMAScript5的规范更符合开发者的期望)。
 
 	function getRE() {
 		var re = /[a-z]/;
@@ -497,7 +496,7 @@ JavaScript中的正则表达式也是对象,可以通过两种方式创建它
 	reg.foo = "baz";
 	console.log(re2.foo); // "baz"
 
->在ECMAScript5中这种情形有所改变,相同正则表达式直接量的每次计算都会创建新的实例对象,目前很多现代浏览器也对此做了纠正(译注:比如在Firefox4就纠正了Firefox3.6的这种“错误”)。
+>在ECMAScript5中这种情形有所改变,相同正则表达式字面量的每次计算都会创建新的实例对象,目前很多现代浏览器也对此做了纠正(译注:比如在Firefox4就纠正了Firefox3.6的这种“错误”)。
 
 最后需要提一点,不带new调用RegExp()(作为普通的函数)和带new调用RegExp()是完全一样的。
 
@@ -603,22 +602,22 @@ name属性是指创建这个对象的构造函数的名字,通常是“Errora
 
 ## 小结
 
-在本章里,我们讨论了多种直接量模式,它们是使用构造函数写法的替代方案,本章讲述了这些内容:
+在本章里,我们讨论了多种字面量模式,它们是使用构造函数写法的替代方案,本章讲述了这些内容:
 
-- 对象直接量写法——一种简洁优雅的定义对象的方法,名值对之间用逗号分隔,通过花括号包装起来
-- 构造函数——内置构造函数(内置构造函数通常都有对应的直接量语法)和自定义构造函数。
+- 对象字面量写法——一种简洁优雅的定义对象的方法,名值对之间用逗号分隔,通过花括号包装起来
+- 构造函数——内置构造函数(内置构造函数通常都有对应的字面量语法)和自定义构造函数。
 - 一种强制函数以构造函数的模式执行(不管用不用new调用构造函数,都始终返回new出来的实例)的技巧
-- 数组直接量写法——数组元素之间使用逗号分隔,通过方括号括起来
+- 数组字面量写法——数组元素之间使用逗号分隔,通过方括号括起来
 - JSON——是一种轻量级的数据交换格式
-- 正则表达式直接量
+- 正则表达式字面量
 - 避免使用其他的内置构造函数:String()、Number()、Boolean()以及不同种类的Error()构造器
 
-通常除了Date()构造函数之外,其他的内置构造函数并不常用,下面的表格中对这些构造函数以及它们的直接量语法做了整理。
+通常除了Date()构造函数之外,其他的内置构造函数并不常用,下面的表格中对这些构造函数以及它们的字面量语法做了整理。
 
 
-		
+		

From e8ad59ac71d29cb7b4c57bf5863f920fb049d55d Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Thu, 28 Mar 2013 13:26:50 +0800
Subject: [PATCH 167/258] =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E5=AD=97=E9=9D=A2?=
 =?UTF-8?q?=E9=87=8F=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter3.markdown | 69 +++++++++++++++++++++++------------------------
 1 file changed, 33 insertions(+), 36 deletions(-)

diff --git a/chapter3.markdown b/chapter3.markdown
index 0bb493a..437d7e0 100644
--- a/chapter3.markdown
+++ b/chapter3.markdown
@@ -4,48 +4,46 @@ JavaScript中的字面量模式更加简洁、有表现力,而且在定义对
 
 为了方便使用字面量而不是构造函数,本章还会补充一些知识,比如内置包装构造函数`Number()`、`String()`和`Boolean()`,以及如何将它们和原始值(数字、字符串和布尔值)比较。最后,快速介绍一下`Error()`构造函数的用法。
 
--------------------校对分隔线-----------------
 ## 对象字面量
 
-我们可以将JavaScript中的对象简单的理解为名值对组成的散列表(hash table),在其他编程语言中被称作“关联数组”。其中的值可以是原始值也可以是对象,不管是什么类型,它们都是“属性”(properties),属性值同样可以是函数,这时属性就被称为“方法”(methods)。
+我们可以将JavaScript中的对象简单地理解为名值对组成的散列表(hash table,也叫哈希表)。在其他编程语言中被称作“关联数组”。其中的值可以是原始值也可以是对象。不管是什么类型,它们都是“属性”(property),属性值同样可以是函数,这时属性就被称为“方法”(method)。
 
 JavaScript中自定义的对象(用户定义的本地对象)任何时候都是可变的。内置本地对象的属性也是可变的。你可以先创建一个空对象,然后在需要时给它添加功能。“对象字面量写法(object literal notation)”是按需创建对象的一种理想方式。
 
 看一下这个例子:
 
-	// start with an empty object
+	// 定义空对象
 	var dog = {};
 
-	// add one property
+	// 添加一个属性
 	dog.name = "Benji";
 
-	// now add a method
+	// 添加一个方法
 	dog.getName = function () {
 		return dog.name;
 	};
 
 在这个例子中,我们首先定义了一个空对象,然后添加了一个属性和一个方法,在程序的生命周期内的任何时刻都可以:
 
-1.更改属性和方法的值,比如:
+- 更改属性和方法的值,比如:
 
 	dog.getName = function () {
-		// redefine the method to return
-		// a hardcoded value
+		// 重新定义方法,返回一个硬编码的值
 		return "Fido";
 	};
 
-2.完全删除属性/方法
+- 删除属性/方法
 
 	delete dog.name;
 
-3.添加更多的属性和方法
+- 添加更多的属性和方法
 
 	dog.say = function () {
 		return "Woof!";
 	};
 	dog.fleas = true;
 
-其实不必每次开始都创建空对象,对象字面量模式可以直接在创建对象时添加功能,就像下面这个例子所展示的:
+每次都创建空对象并不是必须的,对象字面量模式可以直接在创建对象时添加功能,就像下面这个例子:
 
 	var dog = {
 		name: "Benji",
@@ -54,70 +52,69 @@ JavaScript中自定义的对象(用户定义的本地对象)任何时候都
 		}
 	};
 
-> 在本书中多次提到“空对象”(“blank object”和“empty object”)。这只是某种简称,要知道JavaScript中根本不存在真正的空对象,理解这一点至关重要。即使最简单的`{}`对象包含从Object.prototype继承来的属性和方法。我们提到的“空(empty)对象”只是说这个对象没有自己的属性,不考虑它是否有继承来的属性。
+> 在本书中多次提到“空对象”(“blank object”和“empty object”),这只是一种简称,在JavaScript中根本不存在真正的空对象,理解这一点至关重要。即使最简单的`{}`对象也会包含从`Object.prototype`继承来的属性和方法。我们提到的“空(empty)对象”只是说这个对象没有自有属性(own properties),不考虑它是否有继承来的属性。
 
-
 ### 对象字面量语法
 
-如果你从来没有接触过对象字面量写法,第一次碰到可能会感觉怪怪的。但越到后来你就越喜欢它。本质上讲,对象字面量语法包括:
+如果你从来没有接触过对象字面量的写法,可能会感觉怪怪的。但越到后来你就越喜欢它。本质上讲,对象字面量语法包括:
 
-- 将对象主体包含在一对花括号内(`{` and `}`)。
+- 将对象主体包含在一对花括号内(`{` 和 `}`)。
 - 对象内的属性或方法之间使用逗号分隔。最后一个名值对后也可以有逗号,但在IE下会报错,所以尽量不要在最后一个属性或方法后加逗号。
-- 属性名和值之间使用冒号分隔
-- 如果将对象赋值给一个变量,不要忘了在右括号`}`之后补上分号
+- 属性名和值之间使用冒号分隔。
+- 如果将对象赋值给一个变量,不要忘了在右括号`}`之后补上分号。
 
-
 ### 通过构造函数创建对象
 
-JavaScript中没有类的概念,这给JavaScript带来了极大的灵活性,因为你不必提前知晓关于对象的任何信息,也不需要类的“蓝图”。但JavaScript同样具有构造函数,它的语法和Java或其他语言中基于类的对象创建非常类似。
+JavaScript中没有类的概念,这给JavaScript带来了极大的灵活性,因为你不必提前知晓关于对象的任何信息,也不需要类的“蓝图”(译注:指类的结构)。但JavaScript同样具有构造函数,它的语法和Java或其他语言中基于类的对象创建非常类似。
 
-你可以使用自定义的构造函数来创建实例对象,也可以使用内置构造函数来创建,比如Object()、Date()、String()等等。
+你可以使用自定义的构造函数来创建对象实例,也可以使用内置构造函数来创建,比如`Object()`、`Date()`、`String()`等等。
 
 下面这个例子展示了用两种等价的方法分别创建两个独立的实例对象:
 
-	// one way -- using a literal
+	// 一种方法,使用字面量
 	var car = {goes: "far"};
 
-	// another way -- using a built-in constructor
-	// warning: this is an antipattern
+	// 另一种方法,使用内置构造函数
+	// 注意:这是一种反模式
 	var car = new Object();
 	car.goes = "far";
 
 从这个例子中可以看到,字面量写法的一个明显优势是,它的代码更少。“创建对象的最佳模式是使用字面量”还有一个原因,它可以强调对象就是一个简单的可变的散列表,而不必一定派生自某个类。
 
-另外一个使用字面量而不是Object构造函数创建实例对象的原因是,对象字面量不需要“作用域解析”(scope resolution)。因为新创建的实例有可能包含了一个本地的构造函数,当你调用Object()的时候,解析器需要顺着作用域链从当前作用域开始查找,直到找到全局Object构造函数为止。
+另外一个使用字面量而不是`Object()`构造函数创建实例对象的原因是,对象字面量不需要“作用域解析”(scope resolution)。因为存在你已经创建了一个同名的构造函数`Object()`的可能,当你调用`Object()`的时候,解析器需要顺着作用域链从当前作用域开始查找,直到找到全局`Object()`构造函数为止。
+
+### `Object()`构造函数的参数
 
-
-### 获得对象的构造器
+> 译注:这小节的标题是Object Constructor Catch,恕译者水平有限,实在不知如何翻译,故自作主张修改了本节标题。
 
-创建实例对象时能用对象字面量就不要使用new Object()构造函数,但有时你希望能继承别人写的代码,这时就需要了解构造函数的一个“特性”(也是不使用它的另一个原因),就是Object()构造函数可以接收参数,通过参数的设置可以把实例对象的创建委托给另一个内置构造函数,并返回另外一个实例对象,而这往往不是你所希望的。
+创建实例对象时能用对象字面量就不要使用`new Object()`构造函数,但有时你可能是在别人写的代码基础上工作,这时就需要了解构造函数的一个“特性”(也是不使用它的另一个原因),就是`Object()`构造函数可以接收参数,通过这个参数可以把对象实例的创建过程委托给另一个内置构造函数,并返回另外一个对象实例,而这往往不是你想要的。
 
-下面的示例代码中展示了给new Object()传入不同的参数:数字、字符串和布尔值,最终得到的对象都是由不同的构造函数生成的:
+下面的示例代码中展示了给`new Object()`传入不同的参数(数字、字符串和布尔值),最终得到的对象是由不同的构造函数生成的:
 
-	// Warning: antipatterns ahead
+	// 注意:这是反模式
 
-	// an empty object
+	// 空对象
 	var o = new Object();
 	console.log(o.constructor === Object); // true
 
-	// a number object
+	// 数值对象
 	var o = new Object(1);
 	console.log(o.constructor === Number); // true
 	console.log(o.toFixed(2)); // "1.00"
 
-	// a string object
+	// 字符串对象
 	var o = new Object("I am a string");
 	console.log(o.constructor === String); // true
-	// normal objects don't have a substring()
-	// method but string objects do
+	// 普通对象没有substring()方法,但字符串对象有
 	console.log(typeof o.substring); // "function"
 
-	// a boolean object
+	// 布尔值对象
 	var o = new Object(true);
 	console.log(o.constructor === Boolean); // true
 
-Object()构造函数的这种特性会导致一些意想不到的结果,特别是当参数不确定的时候。最后再次提醒不要使用new Object(),尽可能的使用对象字面量来创建实例对象。
+`Object()`构造函数的这种特性会导致一些意想不到的结果,特别是当参数不确定的时候。最后再次提醒不要使用`new Object()`,尽可能的使用对象字面量来创建实例对象。
 
+-------------------校对分隔线-----------------
 
 ## 自定义构造函数
 

From ccead7489afcee7c02bff00996af942f862cd7c3 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Fri, 29 Mar 2013 21:49:27 +0800
Subject: [PATCH 168/258] =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E6=9E=84?=
 =?UTF-8?q?=E9=80=A0=E5=87=BD=E6=95=B0=20=E7=AC=AC=E4=B8=80=E9=83=A8?=
 =?UTF-8?q?=E5=88=86=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter3.markdown | 32 +++++++++++++++-----------------
 1 file changed, 15 insertions(+), 17 deletions(-)

diff --git a/chapter3.markdown b/chapter3.markdown
index 437d7e0..fca5af7 100644
--- a/chapter3.markdown
+++ b/chapter3.markdown
@@ -114,18 +114,16 @@ JavaScript中没有类的概念,这给JavaScript带来了极大的灵活性,
 
 `Object()`构造函数的这种特性会导致一些意想不到的结果,特别是当参数不确定的时候。最后再次提醒不要使用`new Object()`,尽可能的使用对象字面量来创建实例对象。
 
--------------------校对分隔线-----------------
-
 ## 自定义构造函数
 
-除了对象字面量和内置构造函数之外,你也可以通过自定义的构造函数来创建实例对象,正如下面的代码所示:
+除了对象字面量和内置构造函数之外,你也可以通过自定义的构造函数来创建对象实例,正如下面的代码所示:
 
 	var adam = new Person("Adam");
 	adam.say(); // "I am Adam"
 
-这里用了“类”Person创建了实例,这种写法看起来很像Java中的实例创建。两者的语法的确非常接近,但实际上JavaScript中没有类的概念,Person是一个函数。
+这种写法非常像Java中用`Person`类创建了一个实例,两者的语法非常接近,但实际上JavaScript中没有类的概念,`Person()`是一个函数。
 
-Person构造函数是如何定义的呢?看下面的代码:
+`Person()`构造函数是如何定义的呢?看下面的代码:
 
 	var Person = function (name) {
 		this.name = name;
@@ -134,20 +132,19 @@ Person构造函数是如何定义的呢?看下面的代码:
 		};
 	};
 
-当你通过关键字new来调用这个构造函数时,函数体内将发生这些事情:
+当你通过`new`来调用这个构造函数时,函数体内将发生这些事情:
 
-- 创建一个空对象,将它的引用赋给this,继承函数的原型。
-- 通过this将属性和方法添加至这个对象
-- 最后返回this指向的新对象(如果没有手动返回其他的对象)
+- 创建一个空对象,将它的引用赋给`this`,并继承函数的原型。
+- 通过`this`将属性和方法添加至这个对象。
+- 最后返回this指向的新对象(如果没有手动返回其他的对象)。
 
 用代码表示这个过程如下:
 
 	var Person = function (name) {
-		// create a new object
-		// using the object literal
+		// 使用对象字面量创建新对象
 		// var this = {};
 
-		// add properties and methods
+		// 添加属性和方法
 		this.name = name;
 		this.say = function () {
 			return "I am " + this.name;
@@ -156,24 +153,25 @@ Person构造函数是如何定义的呢?看下面的代码:
 		//return this;
 	};
 
-正如这段代码所示,say()方法添加至this中,结果是,不论何时调用new Person(),在内存中都会创建一个新函数(译注:所有Person的实例对象中的方法都是独占一块内存的)。显然这是效率很低的,因为所有实例的say()方法是一模一样的,因此没有必要“拷贝”多份。最好的办法是将方法添加至Person的原型中。
+上例中,为简便起见,`say()`方法被添加至`this`中,结果就是不论何时调用`new Person()`,在内存中都会创建一个新函数(`say()`),显然这是效率很低的,因为所有实例的`say()`方法是一模一样的。最好的办法是将方法添加至`Person()`的原型中。
 
 	Person.prototype.say = function () {
 		return "I am " + this.name;
 	};
 
-我们将会在下一章里详细讨论原型和继承。现在只要记住将需要重用的成员和方法放在原型里即可。
+我们将会在下一章里详细讨论原型和继承,现在只要记住将需要重用的成员放在原型里即可。
 
-关于构造函数的内部工作机制也会在后续章节中有更细致的讨论。这里我们只做概要的介绍。刚才提到,构造函数执行的时候,首先创建一个新对象,并将它的引用赋给this:
+关于构造函数的内部工作机制也会在后续章节中有更细致的讨论。这里我们只做概要的介绍。刚才提到,构造函数执行的时候,首先创建一个新对象,并将它的引用赋给`this`:
 
 	// var this = {};
 
-事实并不完全是这样,因为“空”对象并不是真的空,这个对象继承了Person的原型,看起来更像:
+其实事实并不完全是这样,因为“空”对象并不是真的空,这个对象继承了`Person`的原型,看起来更像:
 
 	// var this = Object.create(Person.prototype);
 
-在后续章节会进一步讨论Object.create()。
+在后续章节会进一步讨论`Object.create()`。
 
+-------------------校对分隔线-----------------
 
 ### 构造函数的返回值
 

From 4ec19bbcca7de47b1306a042670924f0f5c365d3 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Fri, 29 Mar 2013 21:54:26 +0800
Subject: [PATCH 169/258] =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E6=9E=84?=
 =?UTF-8?q?=E5=BB=BA=E5=87=BD=E6=95=B0=20=E6=A0=A1=E5=AF=B9=E5=AE=8C?=
 =?UTF-8?q?=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter3.markdown | 17 +++++++----------
 1 file changed, 7 insertions(+), 10 deletions(-)

diff --git a/chapter3.markdown b/chapter3.markdown
index fca5af7..57c642d 100644
--- a/chapter3.markdown
+++ b/chapter3.markdown
@@ -171,33 +171,30 @@ JavaScript中没有类的概念,这给JavaScript带来了极大的灵活性,
 
 在后续章节会进一步讨论`Object.create()`。
 
--------------------校对分隔线-----------------
-
 ### 构造函数的返回值
 
-用new调用的构造函数总是会返回一个对象,默认返回this所指向的对象。如果构造函数内没有给this赋任何属性,则返回一个“空”对象(除了继承构造函数的原型之外,没有“自己的”属性)。
+当使用`new`调用的时候,构造函数总是会返回一个对象,默认情况下返回`this`所指向的对象。如果构造函数内没有给`this`赋任何属性,则返回一个“空”对象(除了继承构造函数的原型之外,没有自有属性)。
 
-尽管我们不会在构造函数内写return语句,也会隐式返回this。但我们是可以返回任意指定的对象的,在下面的例子中就返回了新创建的that对象。
+尽管在构造函数中没有`return`语句的情况下,也会隐式返回`this`。但事实上我们是可以返回任意指定的对象的,在下面的例子中就返回了新创建的`that`对象。
 
 	var Objectmaker = function () {
 
-		// this `name` property will be ignored
-		// because the constructor
-		// decides to return another object instead
+		// name属性会被忽略,因为返回的是另一个对象
 		this.name = "This is it";
 
-		// creating and returning a new object
+		// 创建并返回一个新对象
 		var that = {};
 		that.name = "And that's that";
 		return that;
 	};
 
-	// test
+	// 测试
 	var o = new Objectmaker();
 	console.log(o.name); // "And that's that"
 
-我们看到,构造函数中其实是可以返回任意对象的,只要你返回的东西是对象即可。如果返回值不是对象(字符串、数字或布尔值),程序不会报错,但这个返回值被忽略,最终还是返回this所指的对象。
+可以看到,构造函数中其实是可以返回任意对象的,只要你返回的东西是对象即可。如果返回值不是对象(字符串、数字或布尔值),程序不会报错,但这个返回值被忽略,最终还是返回`this`所指的对象。
 
+-------------------校对分隔线-----------------
 
 ## 强制使用new的模式
 

From 185ead3fd64fb654c0c0ed11e95d7817ac0244fc Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sat, 30 Mar 2013 10:37:00 +0800
Subject: [PATCH 170/258] =?UTF-8?q?=E5=BC=BA=E5=88=B6=E4=BD=BF=E7=94=A8new?=
 =?UTF-8?q?=E7=9A=84=E6=A8=A1=E5=BC=8F=20=E7=AC=AC=E4=B8=80=E9=83=A8?=
 =?UTF-8?q?=E5=88=86=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter3.markdown | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)

diff --git a/chapter3.markdown b/chapter3.markdown
index 57c642d..2451419 100644
--- a/chapter3.markdown
+++ b/chapter3.markdown
@@ -194,32 +194,30 @@ JavaScript中没有类的概念,这给JavaScript带来了极大的灵活性,
 
 可以看到,构造函数中其实是可以返回任意对象的,只要你返回的东西是对象即可。如果返回值不是对象(字符串、数字或布尔值),程序不会报错,但这个返回值被忽略,最终还是返回`this`所指的对象。
 
--------------------校对分隔线-----------------
-
 ## 强制使用new的模式
 
-我们知道,构造函数和普通的函数无异,只是通过new调用而已。那么如果调用构造函数时忘记new会发生什么呢?漏掉new不会产生语法错误也不会有运行时错误,但可能会造成逻辑错误,导致执行结果不符合预期。这是因为如果不写new的话,函数内的this会指向全局对象(在浏览器端this指向window)。
+我们知道,构造函数和普通的函数本质一样,只是通过`new`调用而已。那么如果调用构造函数时忘记`new`会发生什么呢?漏掉`new`不会产生语法错误也不会有运行时错误,但可能会造成逻辑错误,导致执行结果不符合预期。这是因为如果不写`new`的话,函数内的`this`会指向全局对象(在浏览器端`this`指向`window`)。
 
-当构造函数内包含this.member之类的代码,并直接调用这个函数(省略new),实际会创建一个全局对象的属性member,可以通过window.member或member访问到它。这必然不是我们想要的结果,因为我们要努力确保全局命名空间的整洁干净。
+当构造函数内包含`this.member`之类的代码,并直接调用这个函数(省略`new`),实际上会创建一个全局对象的属性`member`,可以通过`window.member`或`member`访问到。这不是我们想要的结果,因为我们要努力确保全局命名空间干净。
 
-	// constructor
+	// 构造函数
 	function Waffle() {
 		this.tastes = "yummy";
 	}
 
-	// a new object
+	// 新对象
 	var good_morning = new Waffle();
 	console.log(typeof good_morning); // "object"
 	console.log(good_morning.tastes); // "yummy"
 
-	// antipattern:
-	// forgotten `new`
+	// 反模式,漏掉new
 	var good_morning = Waffle();
 	console.log(typeof good_morning); // "undefined"
 	console.log(window.tastes); // "yummy"
 
-ECMAScript5中修正了这种非正常的行为逻辑。在严格模式中,this是不能指向全局对象的。如果在不支持ES5的JavaScript环境中,仍然后很多方法可以确保构造函数的行为即便在省略new调用时也不会出问题。
+ECMAScript5中修正了这种出乎意料的行为逻辑。在严格模式中,`this`不再指向全局对象。如果在不支持ES5的JavaScript环境中,也有一些方法可以确保有没有`new`时构造函数的行为都保持一致。
 
+-------------------校对分隔线-----------------
 
 ### 命名约定
 

From c87dad45b1774ab33bc0ca22d4988d2421772279 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sat, 30 Mar 2013 10:52:45 +0800
Subject: [PATCH 171/258] =?UTF-8?q?=E5=BC=BA=E5=88=B6=E4=BD=BF=E7=94=A8new?=
 =?UTF-8?q?=E7=9A=84=E6=A8=A1=E5=BC=8F=20=E6=A0=A1=E5=AF=B9=E5=AE=8C?=
 =?UTF-8?q?=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter3.markdown | 29 +++++++++++++----------------
 1 file changed, 13 insertions(+), 16 deletions(-)

diff --git a/chapter3.markdown b/chapter3.markdown
index 2451419..18540cc 100644
--- a/chapter3.markdown
+++ b/chapter3.markdown
@@ -217,24 +217,21 @@ JavaScript中没有类的概念,这给JavaScript带来了极大的灵活性,
 
 ECMAScript5中修正了这种出乎意料的行为逻辑。在严格模式中,`this`不再指向全局对象。如果在不支持ES5的JavaScript环境中,也有一些方法可以确保有没有`new`时构造函数的行为都保持一致。
 
--------------------校对分隔线-----------------
-
-### 命名约定
+### 命名规范
 
-最简单的选择是使用命名约定,前面的章节已经提到,构造函数名首字母大写(MyConstructor),普通函数和方法名首字母小写(myFunction)。
+一种简单解决上述问题的方法就是命名规范,前面的章节已经讨论过,构造函数首字母大写(`MyConstructor()`),普通函数和方法首字母小写(`myFunction`)。
 
-
 ### 使用that
 
-遵守命名约定的确能帮上一些忙,但约定毕竟不是强制,不能完全避免出错。这里给出了一种模式可以确保构造函数一定会按照构造函数的方式执行。不要将所有成员挂在this上,将它们挂在that上,并返回that。
+遵守命名规范有一定的作用,但规范毕竟不是强制,不能完全避免出现错误。这里给出了一种模式可以确保构造函数一定会按照构造函数的方式执行,那就是不要将所有成员添加到`this`上,而是将它们添加到`that`上,并返回`that`。
 
 	function Waffle() {
-	var that = {};
+		var that = {};
 		that.tastes = "yummy";
 		return that;
 	}
 
-如果要创建简单的实例对象,甚至不需要定义一个局部变量that,可以直接返回一个对象字面量,就像这样:
+如果要创建更简单一点的对象,甚至不需要局部变量`that`,直接返回一个对象字面量即可,就像这样:
 
 	function Waffle() {
 		return {
@@ -242,21 +239,20 @@ ECMAScript5中修正了这种出乎意料的行为逻辑。在严格模式中,
 		};
 	}
 
-不管用什么方式调用它(使用new或直接调用),它同都会返回一个实例对象:
+不管用什么方式调用它(使用`new`或直接调用),它都会返回一个实例对象:
 
 	var first = new Waffle(),
 		second = Waffle();
 	console.log(first.tastes); // "yummy"
 	console.log(second.tastes); // "yummy"
 
-这种模式的问题是丢失了原型,因此在Waffle()的原型上的成员不会继承到这些实例对象中。
+这种模式的问题是会丢失原型,因此在`Waffle()`的原型上的成员不会被继承到这些对象中。
 
-> 需要注意的是,这里用的that只是一种命名约定,that不是语言的保留字,可以将它替换为任何你喜欢的名字,比如self或me。
+> 需要注意的是,这里用的`that`只是一种命名规范,`that`并不是语言特性的一部分,它可以被替换为任何你喜欢的名字,比如`self`或`me`。
 
-
 ### 调用自身的构造函数
 
-为了解决上述模式的问题,能够让实例对象继承原型属性,我们使用下面的方法。在构造函数中首先检查this是否是构造函数的实例,如果不是,再通过new调用构造函数,并将new的结果返回:
+为了解决上述模式的问题,能够让对象继承原型上的属性,我们使用下面的方法:在构造函数中首先检查`this`是否是构造函数的实例,如果不是,则通过`new`再次调用自己:
 
 	function Waffle() {
 
@@ -268,7 +264,7 @@ ECMAScript5中修正了这种出乎意料的行为逻辑。在严格模式中,
 	}
 	Waffle.prototype.wantAnother = true;
 
-	// testing invocations
+	// 测试
 	var first = new Waffle(),
 		second = Waffle();
 
@@ -278,14 +274,15 @@ ECMAScript5中修正了这种出乎意料的行为逻辑。在严格模式中,
 	console.log(first.wantAnother); // true
 	console.log(second.wantAnother); // true
 
-另一种检查实例的通用方法是使用arguments.callee,而不是直接将构造函数名写死在代码中:
+还有一种比较通用的用来检查实例的方法是使用`arguments.callee`,而不是直接将构造函数名写死在代码中:
 
 	if (!(this instanceof arguments.callee)) {
 		return new arguments.callee();
 	}
 
-这里需要说明的是,在任何函数内部都会自行创建一个arguments对象,它包含函数调用时传入的参数。同时arguments包含一个callee属性,指向它所在的正在被调用的函数。需要注意,ES5严格模式中是禁止使用arguments.callee的,因此最好对它的使用加以限制,并删除任何你能在代码中找到的实例(译注:这里作者的表述很委婉,其实作者更倾向于全面禁止使用arguments.callee)。
+这种模式利用了一个事实,即在任何函数内部都会创建一个`arguments`对象,它包含函数调用时传入的参数。同时`arguments`包含一个`callee`属性,指向正在被调用的函数。需要注意,ES5严格模式中已经禁止了`arguments.callee`的使用,因此最好对它的使用加以限制,并尽可能删除现有代码中已经用到的地方。
 
+-------------------校对分隔线-----------------
 
 ## 数组字面量
 

From 13909cb44a2ffde2c66c77e721b82bbdfd43cb94 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sat, 30 Mar 2013 11:05:48 +0800
Subject: [PATCH 172/258] =?UTF-8?q?=E6=95=B0=E7=BB=84=E5=AD=97=E9=9D=A2?=
 =?UTF-8?q?=E9=87=8F=20=E6=A0=A1=E5=AF=B9=E5=AE=8C=E4=B8=80=E9=83=A8?=
 =?UTF-8?q?=E5=88=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter3.markdown | 20 +++++++++-----------
 1 file changed, 9 insertions(+), 11 deletions(-)

diff --git a/chapter3.markdown b/chapter3.markdown
index 18540cc..6d978fc 100644
--- a/chapter3.markdown
+++ b/chapter3.markdown
@@ -282,31 +282,29 @@ ECMAScript5中修正了这种出乎意料的行为逻辑。在严格模式中,
 
 这种模式利用了一个事实,即在任何函数内部都会创建一个`arguments`对象,它包含函数调用时传入的参数。同时`arguments`包含一个`callee`属性,指向正在被调用的函数。需要注意,ES5严格模式中已经禁止了`arguments.callee`的使用,因此最好对它的使用加以限制,并尽可能删除现有代码中已经用到的地方。
 
--------------------校对分隔线-----------------
-
 ## 数组字面量
 
-和其他的大多数一样,JavaScript中的数组也是对象。可以通过内置构造函数Array()来创建数组,类似对象字面量,数组也可以通过字面量形式创建。而且更推荐使用字面量创建数组。
+和JavaScript中大多数“东西”一样,数组也是对象。可以通过内置构造函数`Array()`来创建数组,也可以通过字面量形式创建,就像对象字面量那样。而且更推荐使用字面量创建数组。
 
-这里的实例代码给出了创建两个具有相同元素的数组的两种方法,使用Array()和使用字面量模式:
+这里的示例代码给出了创建两个具有相同元素的数组的两种方法,使用`Array()`和使用字面量模式:
 
-	// array of three elements
-	// warning: antipattern
+	// 有三个元素的数组
+	// 注意:这是反模式
 	var a = new Array("itsy", "bitsy", "spider");
 
-	// the exact same array
+	// 完全相同的数组
 	var a = ["itsy", "bitsy", "spider"];
 
-	console.log(typeof a); // "object", because arrays are objects
+	console.log(typeof a); // "object",因为数组也是对象
 	console.log(a.constructor === Array); // true
 
-
 ### 数组字面量语法
 
-数组字面量写法非常简单:整个数组使用方括号括起来,数组元素之间使用逗号分隔。数组元素可以是任意类型,也包括数组和对象。
+数组字面量写法非常简单:整个数组使用方括号括起来,数组元素之间使用逗号分隔。数组元素可以是任意类型,包括数组和对象。
 
-数组字面量语法简单直接、高雅美观。毕竟数组只是从位置0开始索引的值的集合,完全没必要包含构造器和new运算符的内容(代码会更多),保持简单即可。
+数组字面量语法简单直观而且优雅,毕竟数组只是从0开始索引的一些值的集合,完全没必要引入构造器和`new`运算符(还要写更多的代码)。
 
+-------------------校对分隔线-----------------
 
 ### 有意思的数组构造器
 

From ba8ae4484e650f07a12febcc34aeab0cf4902f83 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sat, 30 Mar 2013 20:14:21 +0800
Subject: [PATCH 173/258] =?UTF-8?q?Array()=E6=9E=84=E9=80=A0=E5=87=BD?=
 =?UTF-8?q?=E6=95=B0=E7=9A=84=E2=80=9C=E9=99=B7=E9=98=B1=E2=80=9D=20?=
 =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter3.markdown | 21 ++++++++++-----------
 1 file changed, 10 insertions(+), 11 deletions(-)

diff --git a/chapter3.markdown b/chapter3.markdown
index 6d978fc..0ff6b18 100644
--- a/chapter3.markdown
+++ b/chapter3.markdown
@@ -304,37 +304,36 @@ ECMAScript5中修正了这种出乎意料的行为逻辑。在严格模式中,
 
 数组字面量语法简单直观而且优雅,毕竟数组只是从0开始索引的一些值的集合,完全没必要引入构造器和`new`运算符(还要写更多的代码)。
 
--------------------校对分隔线-----------------
-
-### 有意思的数组构造器
+### Array()构造函数的“陷阱”
 
-我们对new Array()敬而远之原因是为了避免构造函数带来的陷阱。
+我们对`new Array()`敬而远之还有一个原因,就是为了避免构造函数带来的陷阱。
 
-如果给Array()构造器传入一个数字,这个数字并不会成为数组的第一个元素,而是设置数组的长度。也就是说,new Array(3)创建了一个长度为3的数组,而不是某个元素是3。如果你访问数组的任意元素都会得到undefined,因为元素并不存在。下面示例代码展示了字面量和构造函数的区别:
+如果给`Array()`构造函数传入一个数字,这个数字并不会成为数组的第一个元素,而是设置了数组的长度。也就是说,`new Array(3)`创建了一个长度为3的数组,而不是某个元素是3。如果你访问数组的任意元素都会得到`undefined`,因为元素并不存在。下面的示例代码展示了字面量和构造函数的区别:
 
-	// an array of one element
+	// 含有1个元素的数组
 	var a = [3];
 	console.log(a.length); // 1
 	console.log(a[0]); // 3
 
-	// an array of three elements
+	// 含有3个元素的数组
 	var a = new Array(3);
 	console.log(a.length); // 3
 	console.log(typeof a[0]); // "undefined"
 
-尽管构造器的行为并不像我们想象的那样,当给new Array()传入一个浮点数时情况就更糟糕了。这时结果就会出错(译注:给new Array()传入浮点数会报“范围错误”RangError,new Array(3.00)则不会报错),因为数组长度不可能是浮点数。
+构造函数的行为可能有一点出乎意料,但当给`new Array()`传入一个浮点数时情况就更糟糕了,这时会出错(译注:给new Array()传入浮点数会报“范围错误”RangError),因为数组长度不可能是浮点数。
 
-	// using array literal
+	// 使用数组字面量
 	var a = [3.14];
 	console.log(a[0]); // 3.14
 
 	var a = new Array(3.14); // RangeError: invalid array length
 	console.log(typeof a); // "undefined"
 
-为了避免在运行时动态创建数组时出现这种错误,强烈推荐使用数组字面量来代替new Array()。
+为了避免在运行时动态创建数组时出现这种错误,强烈推荐使用数组字面量来代替`new Array()`。
 
->有些人用Array()构造器来做一些有意思的事情,比如用来生成重复字符串。下面这行代码返字符串包含255个空格(请读者思考为什么不是256个空格)。`var white = new Array(256).join(' ');`
+> 有些人用`Array()`构造器来做一些有意思的事情,比如用来生成重复字符串。下面这行代码返回的字符串包含255个空格(请读者思考为什么不是256个空格)。`var white = new Array(256).join(' ');`
 
+-------------------校对分隔线-----------------
 
 ### 检查是不是数组
 

From 119ab3314626781b4395977e91cf23ca0a057fa1 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sat, 30 Mar 2013 20:26:12 +0800
Subject: [PATCH 174/258] =?UTF-8?q?=E6=95=B0=E7=BB=84=E5=AD=97=E9=9D=A2?=
 =?UTF-8?q?=E9=87=8F=20=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter3.markdown | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/chapter3.markdown b/chapter3.markdown
index 0ff6b18..c8736b5 100644
--- a/chapter3.markdown
+++ b/chapter3.markdown
@@ -333,9 +333,7 @@ ECMAScript5中修正了这种出乎意料的行为逻辑。在严格模式中,
 
 > 有些人用`Array()`构造器来做一些有意思的事情,比如用来生成重复字符串。下面这行代码返回的字符串包含255个空格(请读者思考为什么不是256个空格)。`var white = new Array(256).join(' ');`
 
--------------------校对分隔线-----------------
-
-### 检查是不是数组
+### 检查是否数组
 
 如果typeof的操作数是数组的话,将返回“object”。
 
@@ -363,6 +361,7 @@ ECMAScript 5定义了一个新的方法Array.isArray(),如果参数是数组
 		};
 	}
 
+-------------------校对分隔线-----------------
 
 ## JSON
 

From 84793159b1a0c527961bf6d1b65bd56bca9b206e Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sat, 30 Mar 2013 21:13:47 +0800
Subject: [PATCH 175/258] =?UTF-8?q?JSON=20=E7=AC=AC=E4=B8=80=E9=83=A8?=
 =?UTF-8?q?=E5=88=86=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter3.markdown | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)

diff --git a/chapter3.markdown b/chapter3.markdown
index c8736b5..9ae4208 100644
--- a/chapter3.markdown
+++ b/chapter3.markdown
@@ -335,25 +335,24 @@ ECMAScript5中修正了这种出乎意料的行为逻辑。在严格模式中,
 
 ### 检查是否数组
 
-如果typeof的操作数是数组的话,将返回“object”。
+如果`typeof`的操作数是数组的话,将返回“object”。
 
 	console.log(typeof [1, 2]); // "object"
 
-这个结果勉强说得过去,毕竟数组是一种对象,但对我们用处不大。往往你需要知道一个值是不是真正的数组。你可能见到过这种检查数组的方法:检查length属性、检查数组方法比如slice()等等。但这些方法非常脆弱,非数组的对象也可以拥有这些同名的属性。还有些人使用instanceof Array来判断数组,但这种方法在某些版本的IE里的多个iframe的场景中会出问题(译注:原因就是在不同iframe中创建的数组不会相互共享其prototype属性)。
+这个结果勉强说得过去,毕竟数组也是一种对象,但对我们来说这个结果却没什么用,实际上你往往是需要知道一个值是不是真正的数组。有时候你会见到一些检查数组的方法:检查`length`属性、检查数组方法比如`slice()`等等,但这些方法非常脆弱,非数组的对象也可以拥有这些同名的属性。还有些人使用`instanceof Array`来判断数组,但这种方法在某些版本的IE里的多个iframe的场景中会出问题(译注:原因就是在不同iframe中创建的数组不会相互共享其`prototype`属性)。
 
-ECMAScript 5定义了一个新的方法Array.isArray(),如果参数是数组的话就返回true。比如:
+ECMAScript5定义了一个新的方法`Array.isArray()`,如果参数是数组的话就返回true。比如:
 
 	Array.isArray([]); // true
 
-	// trying to fool the check
-	// with an array-like object
+	// 尝试用一个类似数组的对象去测试
 	Array.isArray({
 		length: 1,
 		"0": 1,
 		slice: function () {}
 	}); // false
 
-如果你的开发环境不支持ECMAScript5,可以通过Object.prototype.toString()方法来代替。如调用toString的call()方法并传入数组上下文,将返回字符串“[object Array]”。如果传入对象上下文,则返回字符串“[object Object]”。因此可以这样做:
+如果你的开发环境不支持ECMAScript5,可以通过`Object.prototype.toString()`方法来代替。如调用`toString`的`call()`方法并传入数组上下文,将返回字符串“[object Array]”。如果传入对象上下文,则返回字符串“[object Object]”。因此可以这样做:
 
 	if (typeof Array.isArray === "undefined") {
 		Array.isArray = function (arg) {
@@ -361,20 +360,19 @@ ECMAScript 5定义了一个新的方法Array.isArray(),如果参数是数组
 		};
 	}
 
--------------------校对分隔线-----------------
-
 ## JSON
 
-上文我们刚刚讨论过对象和数组字面量,你已经对此很熟悉了,现在我们将目光转向JSON。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。很多语言中都实现了JSON,特别是在JavaScript中。
+我们刚刚讨论了对象和数组字面量,你应该很熟悉了,现在我们来看一看JSON。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,可以很容易地用在多种语言中,尤其是在JavaScript中。
 
 JSON格式及其简单,它只是数组和对象字面量的混合写法,看一个JSON字符串的例子:
 
 	{"name": "value", "some": [1, 2, 3]}
 
-JSON和对象字面量在语法上的唯一区别是,合法的JSON属性名均用引号包含。而在对象字面量中,只有属性名是非法的标识符时采用引号包含,比如,属性名中包含空格`{"first name": "Dave"}`。
+JSON和对象字面量在语法上的唯一区别是,合法的JSON属性名均需要用引号包含。而在对象字面量中,只有属性名是非法的标识符时才使用引号包含,比如,属性名中包含空格`{"first name": "Dave"}`。
 
 在JSON字符串中,不能使用函数和正则表达式字面量。
 
+-------------------校对分隔线-----------------
 
 ### 使用JSON
 

From 29f7aeb9f2ea775f51daa520e7bdade50ce99eb6 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sat, 30 Mar 2013 21:19:24 +0800
Subject: [PATCH 176/258] =?UTF-8?q?JSON=20=E6=A0=A1=E5=AF=B9=E5=AE=8C?=
 =?UTF-8?q?=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter3.markdown | 27 ++++++++++++---------------
 1 file changed, 12 insertions(+), 15 deletions(-)

diff --git a/chapter3.markdown b/chapter3.markdown
index 9ae4208..2e31fb8 100644
--- a/chapter3.markdown
+++ b/chapter3.markdown
@@ -372,44 +372,41 @@ JSON和对象字面量在语法上的唯一区别是,合法的JSON属性名均
 
 在JSON字符串中,不能使用函数和正则表达式字面量。
 
--------------------校对分隔线-----------------
-
 ### 使用JSON
 
-在前面的章节中讲到,出于安全考虑,不推荐使用eval()来“粗糙的”解析JSON字符串。最好使用JSON.parse()方法,ES5中已经包含了这个方法,而且在现代浏览器的JavaScript引擎中已经内置支持JSON了。对于老旧的JavaScript引擎来说,你可以使用JSON.org所提供的JS文件(http://www.json.org/json2.js)来获得JSON对象和方法。
+在前面的章节中讲到,出于安全考虑,不推荐使用`eval()`来粗暴地解析JSON字符串。最好是使用`JSON.parse()`方法,ES5中已经包含了这个方法,并且现代浏览器的JavaScript引擎中也已经内置支持JSON了。对于老旧的JavaScript引擎来说,你可以使用JSON.org所提供的JS文件()来获得JSON对象和方法。
 
-	// an input JSON string
+	// 输入JSON字符串
 	var jstr = '{"mykey": "my value"}';
 	
-	// antipattern
+	// 反模式
 	var data = eval('(' + jstr + ')');
 
-	// preferred
+	// 更好的方式
 	var data = JSON.parse(jstr);
 
 	console.log(data.mykey); // "my value"
 
-如果你已经在使用某个JavaScript库了,很可能库中提供了解析JSON的方法,就不必再额外引入JSON.org的库了,比如,如果你已经使用了YUI3,你可以这样:
+如果你已经在使用某个JavaScript库了,很可能这个库中已经提供了解析JSON的方法,就不必再额外引入JSON.org的库了,比如,如果你已经使用了YUI3,你可以这样:
 
-	// an input JSON string
+	// 输入JSON字符串
 	var jstr = '{"mykey": "my value"}';
 
-	// parse the string and turn it into an object
-	// using a YUI instance
+	// 使用YUI来解析并将结果返回为一个对象
 	YUI().use('json-parse', function (Y) {
 		var data = Y.JSON.parse(jstr);
 		console.log(data.mykey); // "my value"
 	});
 
-如果你使用的是jQuery,可以直接使用它提供的parseJSON()方法:
+如果你使用的是jQuery,可以直接使用它提供的`parseJSON()`方法:
 
-	// an input JSON string
+	// 输入JSON字符串
 	var jstr = '{"mykey": "my value"}';
 
 	var data = jQuery.parseJSON(jstr);
 	console.log(data.mykey); // "my value"
 
-和JSON.parse()方法相对应的是JSON.stringify()。它将对象或数组(或任何原始值)转换为JSON字符串。
+和`JSON.parse()`方法相对应的是`JSON.stringify()`。它将对象或数组(或任何原始值)转换为JSON字符串。
 
 	var dog = {
 		name: "Fido",
@@ -419,10 +416,10 @@ JSON和对象字面量在语法上的唯一区别是,合法的JSON属性名均
 
 	var jsonstr = JSON.stringify(dog);
 
-	// jsonstr is now:
+	// jsonstr的值为
 	// {"name":"Fido","dob":"2010-04-11T22:36:22.436Z","legs":[1,2,3,4]}
 
-
+-------------------校对分隔线-----------------
 ## 正则表达式字面量
 
 JavaScript中的正则表达式也是对象,可以通过两种方式创建它们:

From a8652e5edbbe267506ded05ce5e321a5a981e159 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sun, 31 Mar 2013 21:04:59 +0800
Subject: [PATCH 177/258] =?UTF-8?q?=E6=AD=A3=E5=88=99=E8=A1=A8=E8=BE=BE?=
 =?UTF-8?q?=E5=BC=8F=E5=AD=97=E9=9D=A2=E9=87=8F=20=E6=A0=A1=E5=AF=B9?=
 =?UTF-8?q?=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter3.markdown | 35 +++++++++++++++++------------------
 1 file changed, 17 insertions(+), 18 deletions(-)

diff --git a/chapter3.markdown b/chapter3.markdown
index 2e31fb8..dc037d9 100644
--- a/chapter3.markdown
+++ b/chapter3.markdown
@@ -419,47 +419,45 @@ JSON和对象字面量在语法上的唯一区别是,合法的JSON属性名均
 	// jsonstr的值为
 	// {"name":"Fido","dob":"2010-04-11T22:36:22.436Z","legs":[1,2,3,4]}
 
--------------------校对分隔线-----------------
 ## 正则表达式字面量
 
 JavaScript中的正则表达式也是对象,可以通过两种方式创建它们:
 
-- 使用new RegExp()构造函数
+- 使用`new RegExp()`构造函数
 - 使用正则表达式字面量
 
-下面的示例代码展示了创建正则表达式的两种方法,创建的正则用来匹配一个反斜杠(\):
+下面的示例代码展示了创建用来匹配一个反斜杠(\)的正则表达式的两种方法:
 
-	// regular expression literal
+	// 正则表达式字面量
 	var re = /\\/gm;
 
-	// constructor
+	// 构造函数
 	var re = new RegExp("\\\\", "gm");
 
-显然正则表达式字面量写法的代码更短,且不必强制按照类构造器的思路来写。因此更推荐使用字面量写法。
+显然正则表达式字面量写法的代码更短,而且不会让你觉得在用像类一样的构造函数的思想在写正则表达式,因此更推荐使用字面量写法。
 
-另外,如果使用RegExp()构造函数写法,还需要考虑对引号和反斜杠进行转义,正如上段代码所示的那样,用了四个反斜杠来匹配一个反斜杠。这会增加正则表达式的长度,而且让正则变得难于理解和维护。刚开始学习正则表达式不是很容易,所以不要放弃任何一个简化它们的机会,所以要尽量使用字面量而不是通过构造函数来创建正则。
+另外,如果使用`RegExp()`构造函数写法,还需要考虑对引号和反斜杠进行转义,正如上段代码所示的那样,用了四个反斜杠来匹配一个反斜杠。这会增加正则表达式的长度,而且让它变得难于理解和维护。正则表达式入门不是件容易的事,所以不要放弃任何一个简化它们的机会,尽量使用字面量而不是通过构造函数来创建正则表达式。
 
-
 ### 正则表达式字面量语法
 
-正则表达式字面量使用两个斜线包裹起来,正则的主体部分不包括两端的斜线。在第二个斜线之后可以指定模式匹配的修饰符用以高级匹配,修饰符不需要引号引起来,JavaScript中有三个修饰符:
+正则表达式字面量使用两个斜杠包裹,主体部分不包括两端的斜线。在第二个斜线之后可以指定模式匹配的修饰符,修饰符不需要用引号引起来,JavaScript中有三个修饰符:
 
-- g,全局匹配
-- m,多行匹配
-- i,忽略大小写的匹配
+- `g`,全局匹配
+- `m`,多行匹配
+- `i`,忽略大小写的匹配
 
-修饰符可以自由组合,而且顺序无关:
+修饰符可以自由组合,而且与顺序无关:
 
 	var re = /pattern/gmi;
 
-使用正则表达式字面量可以让代码更加简洁高效,比如当调用String.prototype.prelace()方法时,可以传入正则表达式参数:
+使用正则表达式字面量可以让代码更加简洁高效,比如当调用`String.prototype.prelace()`方法时,可以传入正则表达式参数:
 
 	var no_letters = "abc123XYZ".replace(/[a-z]/gi, "");
 	console.log(no_letters); // 123
 
-有一种不得不使用new RegExp()的情形,有时正则表达式是不确定的,直到运行时才能确定下来。
+有一种不得不使用`new RegExp()`的场景,就是正则表达式是不确定,只有等到运行时才能确定下来的情况。
 
-正则表达式字面量和Regexp()构造函数的另一个区别是,正则表达式字面量只在解析时创建一次正则表达式对象(译注:多次解析同一个正则表达式,会产生相同的实例对象)。如果在循环体内反复创建相同的正则表达式,则每个正则对象的所有属性(比如lastIndex)只会设置一次(译注:由于每次创建相同的实例对象,每个循环中的实例对象都是同一个,属性也自然相同),下面这个例子展示了两次都返回了相同的正则表达式的情形(译注:这里作者的表述只是针对ES3规范而言,下面这段代码在NodeJS、IE6-IE9、FireFox4、Chrome10、Safari5中运行结果和作者描述的不一致,Firefox 3.6中的运行结果和作者描述是一致的,原因可以在ECMAScript5规范第24页和第247页找到,也就是说在ECMAScript3规范中,用正则表达式创建的RegExp对象会共享同一个实例,而在ECMAScript5中则是两个独立的实例。而最新的Firefox4、Chrome和Safari5都遵循ECMAScript5标准,至于IE6-IE8都没有很好的遵循ECMAScript3标准,不过在这个问题上反而处理对了。很明显ECMAScript5的规范更符合开发者的期望)。
+正则表达式字面量和构造函数还有另一个区别,就是字面量只在解析时创建一次正则表达式对象(译注:多次解析同一个正则表达式,会产生相同的实例对象)。如果在循环体内反复使用相同的字面量创建对象,则会返回第一次创建的对象以及它的属性(比如`lastIndex`)。下面这个例子展示了两次返回相同的正则表达式的情形。
 
 	function getRE() {
 		var re = /[a-z]/;
@@ -474,10 +472,11 @@ JavaScript中的正则表达式也是对象,可以通过两种方式创建它
 	reg.foo = "baz";
 	console.log(re2.foo); // "baz"
 
->在ECMAScript5中这种情形有所改变,相同正则表达式字面量的每次计算都会创建新的实例对象,目前很多现代浏览器也对此做了纠正(译注:比如在Firefox4就纠正了Firefox3.6的这种“错误”)。
+> 在ECMAScript5中这种情况有所改变,相同正则表达式字面量的每次计算都会创建新的实例对象,目前很多现代浏览器也对此做了纠正。
 
-最后需要提一点,不带new调用RegExp()(作为普通的函数)和带new调用RegExp()是完全一样的。
+最后需要提一点,不带`new`调用`RegExp()`(作为普通的函数)和带`new`调用`RegExp()`是完全一样的。
 
+-------------------校对分隔线-----------------
 
 ## 原始值的包装对象
 

From 558b84c6a647905b695bed06a045bd9c32b79177 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sun, 31 Mar 2013 21:24:39 +0800
Subject: [PATCH 178/258] =?UTF-8?q?=E5=8E=9F=E5=A7=8B=E5=80=BC=E7=9A=84?=
 =?UTF-8?q?=E5=8C=85=E8=A3=85=E5=AF=B9=E8=B1=A1=20=E6=A0=A1=E5=AF=B9?=
 =?UTF-8?q?=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter3.markdown | 37 +++++++++++++++++--------------------
 1 file changed, 17 insertions(+), 20 deletions(-)

diff --git a/chapter3.markdown b/chapter3.markdown
index dc037d9..9e349e5 100644
--- a/chapter3.markdown
+++ b/chapter3.markdown
@@ -476,64 +476,61 @@ JavaScript中的正则表达式也是对象,可以通过两种方式创建它
 
 最后需要提一点,不带`new`调用`RegExp()`(作为普通的函数)和带`new`调用`RegExp()`是完全一样的。
 
--------------------校对分隔线-----------------
-
 ## 原始值的包装对象
 
-JavaScript中有五种原始类型:数字、字符串、布尔值、null和undefined。除了null和undefined之外,其他三种都有对应的“包装对象”(wrapper objects)。可以通过内置构造函数来生成包装对象,Number()、String()、和Boolean()。
+JavaScript中有五种原始类型:数字、字符串、布尔值、`null`和`undefined`。除了`null`和`undefined`之外,其他三种都有对应的“包装对象”(primitive wrapper object)。可以通过内置构造函数`Number()`、`String()`和`Boolean()`来生成包装对象。
 
 为了说明数字原始值和数字对象之间的区别,看一下下面这个例子:
 
-	// a primitive number
+	// 一个数字原始值
 	var n = 100;
 	console.log(typeof n); // "number"
 
-	// a Number object
+	// 一个Number对象
 	var nobj = new Number(100);
 	console.log(typeof nobj); // "object"
 
-包装对象带有一些有用的属性和方法,比如,数字对象就带有toFixed()和toExponential()之类的方法。字符串对象带有substring()、chatAt()和toLowerCase()等方法以及length属性。这些方法非常方便,和原始值相比,这让包装对象具备了一定优势。其实原始值也可以调用这些方法,因为原始值会首先转换为一个临时对象,如果转换成功,则调用包装对象的方法。
+包装对象带有一些有用的属性和方法。比如,数字对象就带有`toFixed()`和`toExponential()`之类的方法,字符串对象带有`substring()`、`chatAt()`和`toLowerCase()`等方法以及`length`属性。这些方法非常方便,和原始值相比,这是包装对象的优势,但其实原始值也可以调用这些方法,因为原始值会首先转换为一个临时对象,如果转换成功,则调用包装对象的方法。
 
-	// a primitive string be used as an object
+	// 像使用对象一样使用一个字符串原始值
 	var s = "hello";
 	console.log(s.toUpperCase()); // "HELLO"
 
-	// the value itself can act as an object
+	// 值本身也可以像对象一样
 	"monkey".slice(3, 6); // "key"
 
-	// same for numbers
+	// 数字也是一样
 	(22 / 7).toPrecision(3); // "3.14"
 
 因为原始值可以根据需要转换成对象,这样的话,也不必为了用包装对象的方法而将原始值手动“包装”成对象。比如,不必使用new String("hi"),直接使用"hi"即可。
 
-	// avoid these:
+	// 避免这些:
 	var s = new String("my string");
 	var n = new Number(101);
 	var b = new Boolean(true);
 
-	// better and simpler:
+	// 更好更简洁的办法:
 	var s = "my string";
 	var n = 101;
 	var b = true;
 
-不得不使用包装对象的一个原因是,有时我们需要对值进行扩充并保持值的状态。原始值毕竟不是对象,不能直接对其进行扩充(译注:比如`1.property = 2`会报错)。
+不得不使用包装对象的一个场景是,有时我们需要对值进行扩充并保持值的状态。原始值毕竟不是对象,不能直接对其进行扩充。
 
-	// primitive string
+	// 字符串原始值
 	var greet = "Hello there";
 
-	// primitive is converted to an object
-	// in order to use the split() method
+	// 为使用split方法,原始值被转换为对象
 	greet.split(' ')[0]; // "Hello"
 
-	// attemting to augment a primitive is not an error
+	// 给原始值添加属性并不会报错
 	greet.smile = true;
 
-	// but it doesn't actually work
+	// 但实际上却没有作用
 	typeof greet.smile; // "undefined"
 
-在这段示例代码中,greet只是临时转换成了对象,以保证访问其属性/方法时不会出错。另一方面,如果greet通过new String()定义为一个对象,那么扩充smile属性就会按照期望的那样执行。对字符串、数字或布尔值的扩充并不常见,除非你清楚自己想要什么,否则不必使用包装对象。
+在这段示例代码中,`greet`只是临时被转换成了对象,以保证访问其属性、方法时不会出错。而如果是另一种情况,`greet`通过`new String()`被定义为一个对象,那么扩充`smile`属性的过程就会像我们预期的那样。对字符串、数字或布尔值进行扩充的情况很少见,因此建议只在确实有必要的情况下使用包装对象。
 
-当省略new时,包装器将传给它的参数转换为原始值:
+当省略`new`时,包装对象的构造函数将传给它的参数转换为原始值:
 
 	typeof Number(1); // "number"
 	typeof Number("1"); // "number"
@@ -541,7 +538,7 @@ JavaScript中有五种原始类型:数字、字符串、布尔值、null和und
 	typeof String(1); // "string"
 	typeof Boolean(1); // "boolean"
 
-
+-------------------校对分隔线-----------------
 ## Error 对象
 
 JavaScript中有很多内置的Error构造函数,比如Error()、SyntaxError(),TypeError()等等,这些“错误”通常和throw语句一起使用。这些构造函数创建的错误对象包含这些属性:

From abdb0a818635c071669c8ee2a671476d4279de4f Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sun, 31 Mar 2013 21:33:12 +0800
Subject: [PATCH 179/258] =?UTF-8?q?=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86?=
 =?UTF-8?q?=E5=AF=B9=E8=B1=A1=20=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter3.markdown | 33 ++++++++++++++++-----------------
 1 file changed, 16 insertions(+), 17 deletions(-)

diff --git a/chapter3.markdown b/chapter3.markdown
index 9e349e5..12a5c3a 100644
--- a/chapter3.markdown
+++ b/chapter3.markdown
@@ -538,42 +538,41 @@ JavaScript中有五种原始类型:数字、字符串、布尔值、`null`和`
 	typeof String(1); // "string"
 	typeof Boolean(1); // "boolean"
 
--------------------校对分隔线-----------------
-## Error 对象
+## 错误处理对象
 
-JavaScript中有很多内置的Error构造函数,比如Error()、SyntaxError(),TypeError()等等,这些“错误”通常和throw语句一起使用。这些构造函数创建的错误对象包含这些属性:
+JavaScript中有很多内置的错误处理构造函数,比如`Error()`、`SyntaxError()`,`TypeError()`等等,它们通常和`throw`语句一起被使用。这些构造函数创建的错误对象包含这些属性:
 
-**name**
+- `name`
 
-name属性是指创建这个对象的构造函数的名字,通常是“Errora”,有时会有特定的名字比如“RangeError”
+	name属性是指产生这个对象的构造函数的名字,通常是“Error”,有时会有特定的名字比如“RangeError”
 
-**message**
+- `message`
 
-创建这个对象时传入构造函数的字符串
+	创建这个对象时传入构造函数的字符串
 
-错误对象还有其他一些属性,比如产生错误的行号和文件名,但这些属性是浏览器自行实现的,不同浏览器的实现也不一致,因此出于兼容性考虑,并不推荐使用这些属性。
+错误对象还有一些其他的属性,比如产生错误的行号和文件名,但这些属性是浏览器自行实现的,不同浏览器的实现也不一致,因此出于兼容性考虑,并不推荐使用这些属性。
 
-另一方面,throw可以抛出任何对象,并不限于“错误对象”,因此你可以根据需要抛出自定义的对象。这些对象包含属性“name”和“message”或其他你希望传递给异常处理逻辑的信息,异常处理逻辑由catch语句指定。你可以灵活运用抛出的错误对象,将程序从错误状态恢复至正常状态。
+`throw`可以抛出任何对象,并不限于“错误对象”,因此你可以根据需要抛出自定义的对象。这些对象包含属性“name”和“message”或其他你希望传递给异常处理逻辑的信息,异常处理逻辑由`catch`语句指定。你可以灵活运用抛出的错误对象,将程序从错误状态恢复至正常状态。
 
 	try {
-		// something bad happened, throw an error
+		// 一些不好的事情发生了,抛出错误
 		throw {
-			name: "MyErrorType", // custom error type
+			name: "MyErrorType", // 自定义错误类型
 			message: "oops",
 			extra: "This was rather embarrassing",
-			remedy: genericErrorHandler // who should handle it
+			remedy: genericErrorHandler // 应该由谁处理
 		};
 	} catch (e) {
-		// inform the user
+		// 通知用户
 		alert(e.message); // "oops"
 
-		// gracefully handle the error
-		e.remedy(); // calls genericErrorHandler()
+		// 优雅地处理错误
+		e.remedy(); // 调用genericErrorHandler()
 	}
 
-通过new调用和省略new调用错误构造函数是一模一样的,他们都返回相同的错误对象。
+使用`new`调用和省略`new`调用错误构造函数是一模一样的,他们都返回相同的错误对象。
 
-
+-------------------校对分隔线-----------------
 ## 小结
 
 在本章里,我们讨论了多种字面量模式,它们是使用构造函数写法的替代方案,本章讲述了这些内容:

From db8e8992f26959cac86770ae1c53660a5e8476c1 Mon Sep 17 00:00:00 2001
From: TooBug 
Date: Sun, 31 Mar 2013 21:56:01 +0800
Subject: [PATCH 180/258] =?UTF-8?q?=E7=AC=AC=E4=B8=89=E7=AB=A0=20=E6=A0=A1?=
 =?UTF-8?q?=E5=AF=B9=E5=AE=8C=E6=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 chapter3.markdown | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/chapter3.markdown b/chapter3.markdown
index 12a5c3a..e9963f0 100644
--- a/chapter3.markdown
+++ b/chapter3.markdown
@@ -1,6 +1,6 @@
 # 第三章 字面量和构造函数
 
-JavaScript中的字面量模式更加简洁、有表现力,而且在定义对象时不容易出错。本章将会讨论字面量,包括对象、数组和正则表达式字面量,以及为什么字面量要比等价的内置构造函数(如`Object()`、`Array()`等)要更好。本章还会介绍JSON格式,JSON是使用数组和对象字面量的形式定义的一种数据传输格式。本章还会讨论自定义构造函数,包括如何强制使用`new`以确保构造函数正确执行。
+JavaScript中的字面量模式更加简洁、有表现力,而且在定义对象时不容易出错。本章将会讨论字面量,包括对象、数组和正则表达式字面量,以及为什么字面量要比等价的内置构造函数(如`Object()`、`Array()`等)要更好。本章还会介绍JSON格式,JSON是使用数组和对象字面量的形式定义的一种数据交换格式。本章还会讨论自定义构造函数,包括如何强制使用`new`以确保构造函数正确执行。
 
 为了方便使用字面量而不是构造函数,本章还会补充一些知识,比如内置包装构造函数`Number()`、`String()`和`Boolean()`,以及如何将它们和原始值(数字、字符串和布尔值)比较。最后,快速介绍一下`Error()`构造函数的用法。
 
@@ -572,20 +572,19 @@ JavaScript中有很多内置的错误处理构造函数,比如`Error()`、`Syn
 
 使用`new`调用和省略`new`调用错误构造函数是一模一样的,他们都返回相同的错误对象。
 
--------------------校对分隔线-----------------
 ## 小结
 
 在本章里,我们讨论了多种字面量模式,它们是使用构造函数写法的替代方案,本章讲述了这些内容:
 
-- 对象字面量写法——一种简洁优雅的定义对象的方法,名值对之间用逗号分隔,通过花括号包装起来
-- 构造函数——内置构造函数(内置构造函数通常都有对应的字面量语法)和自定义构造函数。
-- 一种强制函数以构造函数的模式执行(不管用不用new调用构造函数,都始终返回new出来的实例)的技巧
-- 数组字面量写法——数组元素之间使用逗号分隔,通过方括号括起来
-- JSON——是一种轻量级的数据交换格式
+- 对象字面量写法——一种简洁优雅的定义对象的方法,通过花括号包裹,名值对之间用逗号分隔
+- 构造函数——内置构造函数(内置构造函数通常都有对应的字面量语法)和自定义构造函数
+- 一种强制函数以构造函数的模式运行行(不管用不用`new`调用构造函数,都始终返回`new`出来的实例)的技巧
+- 数组字面量写法——通过方括号包裹,数组元素之间使用逗号分隔
+- JSON——一种轻量级的数据交换格式
 - 正则表达式字面量
-- 避免使用其他的内置构造函数:String()、Number()、Boolean()以及不同种类的Error()构造器
+- 避免使用其他的内置构造函数:`String()`、`Number()`、`Boolean()`以及不同种类的`Error()`构造函数
 
-通常除了Date()构造函数之外,其他的内置构造函数并不常用,下面的表格中对这些构造函数以及它们的字面量语法做了整理。
+通常情况下,除了`Date()`之外,其他的内置构造函数并不常用,下面的表格对这些构造函数以及它们的字面量语法做了整理。
 
 
内置构造函数(不推荐)直接量语法和原始值(推荐)字面量语法和原始值(推荐)
var o = new Object();
From e8e8a6b146ae07d8d97469b768dc9dbb9f951a05 Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 14 Apr 2013 11:15:35 +0800 Subject: [PATCH 181/258] =?UTF-8?q?=E8=83=8C=E6=99=AF=E7=9F=A5=E8=AF=86=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index 92c18dd..f266c16 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -1,14 +1,11 @@ - # 函数 -熟练运用函数是JavaScript程序员的必备技能,因为在JavaScript中函数实在是太常用了。它能够完成的任务种类非常之多,而在其他语言中则需要很多特殊的语法支持才能达到这种能力。 +熟练运用函数是JavaScript程序员的必备技能,因为在JavaScript中函数实在是太常用了。它能够完成各种类型的任务,而在其他语言中则需要很多特殊的语法支持才能拥有这种能力。 -在本章将会介绍在JavaScript中定义函数的多种方式,包括函数表达式和函数声明、以及局部作用域和变量声明提前的工作原理。然后会介绍一些有用的模式,帮助你设计API(为你的函数提供更好的接口)、搭建代码架构(使用尽可能少的全局对象)、并优化性能(避免不必要的操作)。 +本章将会介绍在JavaScript中定义函数的几种方式,包括函数表达式和函数声明以及局部作用域和变量声明提前的工作原理。然后会介绍一些有用的模式,帮助你设计API(为你的函数提供更好的接口)、搭建代码架构(使用尽可能少的全局对象)、并优化性能(避免不必要的操作)。 -现在让我们来一起揭秘JavaScript函数,我们首先从一些背景知识开始说起。 +现在让我们来一起揭秘JavaScript函数,首先从一些背景知识开始说起。 - - ## 背景知识 JavaScript的函数具有两个主要特性,正是这两个特性让它们与众不同。第一个特性是,函数是一等对象(first-class object),第二个是函数提供作用域支持。 @@ -16,22 +13,23 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 函数是对象,那么: - 可以在程序执行时动态创建函数 -- 可以将函数赋值给变量,可以将函数的引用拷贝至另一个变量,可以扩充函数,除了某些特殊场景外均可被删除。 -- 可以将函数作为参数传入另一个函数,也可以被当作返回值返回。 +- 可以将函数赋值给变量,可以将函数的引用拷贝至另一个变量,可以扩充函数,除了某些特殊场景外均可被删除 +- 可以将函数作为参数传入另一个函数,也可以被当作返回值返回 - 函数可以包含自己的属性和方法 -对于一个函数A来说,首先它是对象,拥有属性和方法,其中某个属性碰巧是另一个函数B,B可以接受函数作为参数,假设这个函数参数为C,当执行B的时候,返回另一个函数D。乍一看这里有一大堆相互关联的函数。当你开始习惯函数的许多用法时,你会惊叹原来函数是如此强大、灵活并富有表现力。通常说来,一说到JavaScript的函数,我们首先认为它是对象,它具有一个可以“执行”的特性,也就是说我们可以“调用”这个函数。 +有可能会有这样的情况:一个函数A,它也是一个对象,拥有属性和方法,其中某个属性是另一个函数B,B可以接受函数作为参数,假设这个函数参数为C,当执行B的时候,返回另一个函数D。乍一看这里有一大堆相互关联的函数,但当你开始习惯函数的许多用法时,你会惊叹原来函数是如此灵活、强大县且富有表现力。通常说来,一说到JavaScript的函数,我们首先认为它是一个对象,具有一个可以“执行”的特性,也就是说我们可以“调用”这个函数。 -我们通过new Function()构造器来生成一个函数,这时可以明显看出函数是对象: +我们通过`new Function()`构造函数来创建一个函数,这时可以明显看出函数是对象: - // antipattern - // for demo purposes only + // 反模式,仅用于演示 var add = new Function('a, b', 'return a + b'); - add(1, 2); // returns 3 + add(1, 2); // 返回 3 + +在这段代码中,毫无疑问`add()`是一个对象,因为它是由构造函数创建的。这里并不推荐使用`Function()`构造函数来创建函数(和`eval()`一样糟糕),因为程序逻辑代码是以字符串的形式传入构造器的。这样的代码可读性差,写起来也很费劲,你还要对代码中的引号做转义处理,并需要特别关注为了保持可读性而保留的空格和缩进。 -在这段代码中,毫无疑问add()是一个对象,毕竟它是由构造函数创建的。这里并不推荐使用Function()构造器创建函数(和eval()一样糟糕),因为程序逻辑代码是以字符串的形式传入构造器的。这样的代码可读性差,写起来也很费劲,你不得不对逻辑代码中的引号做转义处理,并需要特别关注为了让代码保持一定的可读性而保留的空格和缩进。 +函数的第二个重要特性是它能提供作用域支持。在JavaScript中没有块级作用域(译注:在JavaScript1.7中提供了块级作用域部分特性的支持,可以通过`let`来声明块级作用域内的“局部变量”),也就是说不能通过花括号来创建作用域,JavaScript中只有函数作用域(译注:这里只针对函数而言,此外JavaScript还有全局作用域)。在函数内所有通过`var`声明的变量都是局部变量,在函数外部是不可见的。刚才所说的花括号无法提供作用域支持的意思是说,如果在`if`条件句、`for`或`while`循环体内用`var`定义了变量,这个变量并不是属于`if`语句或`for`(`while`)循环的局部变量,而是属于它所在的函数。如果不在任何函数内部,它会成为全局变量。在第二章里提到我们要减少对全局命名空间的污染,那么使用函数则是控制变量作用域的最佳选择。 -函数的第二个重要特性是它能提供作用域支持。在JavaScript中没有块级作用域(译注:在JavaScript1.7中提供了块级作用域部分特性的支持,可以通过let来声明块级作用域内的“局部变量”),也就是说不能通过花括号来创建作用域,JavaScript中只有函数作用域(译注:这里作者的表述只针对函数而言,此外JavaScript还有全局作用域)。在函数内所有通过var声明的变量都是局部变量,在函数外部是不可见的。刚才所指花括号无法提供作用域支持的意思是说,如果在if条件句内、或在for或while循环体内用var定义了变量,这个变量并不是属于if语句或for(while)循环的局部变量,而是属于它所在的函数。如果不在任何函数内部,它会成为全局变量。在第二章里提到我们要减少对全局命名空间的污染,那么使用函数则是控制变量的作用域的不二之选。 +---------校对分割线--------- ### 术语释义 From 6d4a5e6721fac24a4148011c3249bced6e50ff4a Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 14 Apr 2013 11:50:36 +0800 Subject: [PATCH 182/258] =?UTF-8?q?=E8=83=8C=E6=99=AF=E7=9F=A5=E8=AF=86=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 91 +++++++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 50 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index f266c16..b28d8e7 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -29,113 +29,104 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 函数的第二个重要特性是它能提供作用域支持。在JavaScript中没有块级作用域(译注:在JavaScript1.7中提供了块级作用域部分特性的支持,可以通过`let`来声明块级作用域内的“局部变量”),也就是说不能通过花括号来创建作用域,JavaScript中只有函数作用域(译注:这里只针对函数而言,此外JavaScript还有全局作用域)。在函数内所有通过`var`声明的变量都是局部变量,在函数外部是不可见的。刚才所说的花括号无法提供作用域支持的意思是说,如果在`if`条件句、`for`或`while`循环体内用`var`定义了变量,这个变量并不是属于`if`语句或`for`(`while`)循环的局部变量,而是属于它所在的函数。如果不在任何函数内部,它会成为全局变量。在第二章里提到我们要减少对全局命名空间的污染,那么使用函数则是控制变量作用域的最佳选择。 ----------校对分割线--------- - -### 术语释义 +### 术语 -首先我们先简单讨论下创建函数相关的术语,因为精确无歧义的术语约定和我们所讨论的各种模式一样重要。 +首先我们先简单讨论下与创建函数相关的术语,因为精确无歧义的术语约定非常重要。 看下这个代码片段: - // named function expression + // 具名函数表达式 var add = function add(a, b) { return a + b; }; -这段代码描述了一个函数,这种描述称为“带有命名的函数表达式”。 +这段代码描述了一个函数,这种描述称为“具名函数表达式”。 -如果函数表达式将名字省略掉(比如下面的示例代码),这时它是“无名字的函数表达式”,通常我们称之为“匿名函数”,比如: +如果省略掉函数表达式中的名字(比如下面的示例代码),这时它是“匿名函数表达式”,通常我们称之为“匿名函数”,比如: - // function expression, a.k.a. anonymous function + // 匿名函数表达式,又称匿名函数 var add = function (a, b) { return a + b; }; -因此“函数表达式”是一个更广义的概念,“带有命名的函数表达式”是函数表达式的一种特殊形式,仅仅当需要给函数定义一个可选的名字时使用。 +因此“函数表达式”是一个更广义的概念,“具名函数表达式”是函数表达式的一种特殊形式,仅仅当需要给函数定义一个可选的名字时使用。 -当省略第二个add,它就成了无名字的函数表达式,这不会对函数定义和调用语法造成任何影响。带名字和不带名字唯一的区别是函数对象的name属性是否是一个空字符串。name属性属于语言的扩展(未在ECMA标准中定义),但很多环境都实现了。如果不省略第二个add,那么属性add.name则是"add",name属性在用Firebug的调试过程中非常有用,还能让函数递归调用自身,其他情况可以省略它。 +当省略第二个`add`,它就成了没有名字的函数表达式,这不会对函数定义和调用语法造成任何影响。带名字和不带名字唯一的区别是函数对象的`name`属性是否为空字符串。`name`属性属于语言的扩展(未在ECMA标准中定义),但很多环境都实现了。如果不省略第二个`add`,那么`add.name`是"add",`name`属性在用像Firebug之类的调试工具进行调试的过程中非常有用,它也可以让函数递归调用自身,如果是其他情况,则可以省略它。 最后来看一下“函数声明”,函数声明的语法和其他语言中的语法非常类似: function foo() { - // function body goes here + // 函数体 } -从语法角度讲,带有命名的函数表达式和函数声明非常像,特别是当不需要将函数表达式赋值给一个变量的时候(在本章后面所讲到的回调模式中有类似的例子)。多数情况下,函数声明和带命名的函数表达式在外观上没有多少不同,只是它们在函数执行时对上下文的影响有所区别,下一小节会讲到。 +从语法上来看,具名函数表达式和函数声明非常像,特别是当不需要将函数表达式赋值给一个变量的时候(在本章后面所讲到的回调模式中有类似的例子)。多数情况下,函数声明和具名函数表达式在外观上没有多少不同,只是它们在函数执行时对上下文的影响有所区别,下一小节会讲到。 两种语法的一个区别是末尾的分号。函数声明末尾不需要分号,而函数表达式末尾是需要分号的。推荐你始终不要丢掉函数表达式末尾的分号,即便JavaScript可以进行分号补全,也不要冒险这样做。 ->另外我们经常看到“函数直接量”。它用来表示函数表达式或带命名的函数表达式。由于这个术语是有歧义的,所以最好不要用它。 +> 另外我们经常看到“函数字面量”。它用来表示函数表达式或具名函数表达式。由于这个术语是有歧义的,所以最好不要用它。 - ### 声明 vs 表达式:命名与提前 -那么,到底应该用哪个呢?函数声明还是函数表达式?在不能使用函数声明语法的场景下,只能使用函数表达式了。下面这个例子中,我们给函数传入了另一个函数对象作为参数,以及给对象定义方法: +那么,到底应该用哪个呢?函数声明还是函数表达式?在不能使用函数声明语法的场景下,就只能使用函数表达式了,将函数作为参数传递、在对象字面量中定义方法都是这样的例子: - // this is a function expression, - // pased as an argument to the function `callMe` + // 作为参数传递给callMe的函数表达式 callMe(function () { - // I am an unnamed function expression - // also known as an anonymous function + // 我是匿名函数表达式,也叫匿名函数 }); - // this is a named function expression + // 这是一个具名函数表达式 callMe(function me() { - // I am a named function expression - // and my name is "me" + // 我是具名函数表达式,我的名字是“me” }); - // another function expression + // 另一个函数表达式 var myobject = { say: function () { - // I am a function expression + // 我是函数表达式 } }; -函数声明只能出现在“程序代码”中,也就是说在别的函数体内或在全局。这个定义不能赋值给变量或属性,同样不能作为函数调用的参数。下面这个例子是函数声明的合法用法,这里所有的函数foo(),bar()和local()都使用函数声明来定义: +函数声明只能出现在“程序代码”中,也就是说在别的函数体内或在全局。这个定义不能赋值给变量或属性,同样不能作为函数调用的参数。(译注:注意这里说的是函数声明的语句,而不是通过声明语句定义出来的函数本身。任何函数都是可以被赋值给变量和属性的,也可以被作为参数传递。)下面这个例子是函数声明的合法用法,这里所有的函数`foo()`,`bar()`和`local()`都使用函数声明来定义: - // global scope + // 全局作用域 function foo() {} function local() { - // local scope + // 本地作用域 function bar() {} return bar; } - ### 函数的name属性 -选择函数定义模式的另一个考虑是只读属性name的可用性。尽管标准规范中并未规定,但很多运行环境都实现了name属性,在函数声明和带有名字的函数表达式中是有name的属性定义的。在匿名函数表达式中,则不一定有定义,这个是和实现相关的,在IE中是无定义的,在Firefox和Safari中是有定义的,但是值为空字符串。 +选择用哪种模式定义函数时的另一个考虑是只读属性`name`的可用性。尽管标准规范中并未定义,但很多运行环境都实现了`name`属性,在函数声明和具名函数表达式中是有`name`属性的。在匿名函数表达式中,则不一定有定义,这个是和实现相关的,在IE中是无定义的,在Firefox和Safari中是有定义的,但是值为空字符串。 - function foo() {} // declaration - var bar = function () {}; // expression - var baz = function baz() {}; // named expression + function foo() {} // 函数声明 + var bar = function () {}; // 匿名函数表达式 + var baz = function baz() {}; // 具名函数表达式 foo.name; // "foo" bar.name; // "" baz.name; // "baz" -在Firebug或其他工具中调试程序时name属性非常有用,它可以用来显示当前正在执行的函数。同样可以通过name属性来递归的调用函数自身。如果你对这些场景不感兴趣,那么请尽可能的使用匿名函数表达式,这样会更简单、且冗余代码更少。 +在Firebug或其他工具中调试程序时`name`属性非常有用,它可以用来显示当前正在执行的函数。同样可以通过`name`属性来递归地调用函数自身。如果你对这些场景不感兴趣,那么请尽可能地使用匿名函数表达式,这样会更简单、且冗余代码更少。 -和函数声明相比而言,函数表达式的语法更能说明函数是一种对象,而不是某种特别的语言写法。 +相对函数声明而言,函数表达式的语法更能说明函数是一种和其它对象类似的对象,而不是语言中某种特别的组成部分。 ->我们可以将一个带名字的函数表达式赋值给变量,变量名和函数名不同,这在技术上是可行的。比如:`var foo = function bar(){};`。然而,这种用法的行为在浏览器中的兼容性不佳(特别是IE中),因此并不推荐大家使用这种模式。 +> 我们可以将一个带名字的函数表达式赋值给变量,变量名和函数名不同,这在技术上是可行的。比如:`var foo = function bar(){};`。然而,这种用法的行为在浏览器中的兼容性不好(特别是IE中),因此并不推荐大家使用这种模式。 - -### 函数提前 +### 声明提前 -通过前面的讲解,你可能以为函数声明和带名字的函数表达式是完全等价的。事实上不是这样,主要区别在于“声明提前”的行为。 +通过前面的讲解,你可能以为函数声明和具名函数表达式是完全等价的。事实上并不是这样,主要区别在于“声明提前”的行为。 ->术语“提前”并未在ECMAScript中定义,但是并没有其他更好的方法来描述这种行为了。 +> 术语“提前”并未在ECMAScript中定义,但是它是一种很好地描述这种行为的方法。 我们知道,不管在函数内何处声明变量,变量都会自动提前至函数体的顶部。对于函数来说亦是如此,因为他们也是一种对象,赋值给了变量。需要注意的是,函数声明定义的函数不仅能让声明提前,还能让定义提前,看一下这段示例代码: - // antipattern - // for illustration only + // 反模式,仅用于演示 - // global functions + // 全局函数 function foo() { alert('global foo'); } @@ -151,30 +142,30 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 foo(); // "local foo" bar(); // TypeError: bar is not a function - // function declaration: - // variable 'foo' and its implementation both get hoisted + // 函数声明: + // 变量foo和它的定义实现都被提前了 function foo() { alert('local foo'); } - // function expression: - // only variable 'bar' gets hoisted - // not the implementation + // 函数表达式: + // 只有变量bar被提前,它的定义实现没有被提前 var bar = function () { alert('local bar'); }; } hoistMe(); -在这段代码中,和普通的变量一样,hoistMe()函数中的foo和bar被“搬运”到了顶部,覆盖了全局的foo和bar。不同之处在于,局部的foo()定义提前至顶部并能正常工作,尽管定义它的位置并不靠前。bar()的定义并未提前,只是声明提前了。因此当程序执行到bar()定义的位置之前,它的值都是undefined,并不是函数(防止当前上下文查找到作用域链上的全局的bar(),也就“覆盖”了全局的bar())。 +在这段代码中,和普通的变量一样,`hoistMe()`函数中的`foo`和`bar`被“搬运”到了顶部,覆盖了全局的`foo()`和`bar()`。不同之处在于,本地的`foo()`的位置并不在前面,但它的定义却被提前到了顶部并能正常工作,而`bar()`的定义并未提前,只有声明提前了。因此当程序执行到`bar()`定义的位置之前,它的值都不是函数,而是`undefined`(在此期间全局的`bar()`都是被本地覆盖的)。 -到目前为止我们介绍了必要的背景知识和函数定义相关的术语,下面开始介绍一些JavaScript所提供的函数相关的好的模式,我们从回调模式开始。同样,再次强调JavaScript函数的两个特殊特性,掌握这两点至关重要: +到目前为止我们介绍了必要的背景知识和函数定义相关的术语,下面开始介绍一些JavaScript所提供的函数相关的模式,我们从回调模式开始。再次强调JavaScript函数的两个特性,掌握这两点至关重要: - 函数是对象 -- 函数提供局部变量作用域 +- 函数提供本地变量作用域 +---------校对分割线--------- ## 回调模式 From ad9301537c255db54c807a0b78d72f700c63821d Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 14 Apr 2013 23:08:21 +0800 Subject: [PATCH 183/258] =?UTF-8?q?=E5=9B=9E=E8=B0=83=E6=A8=A1=E5=BC=8F=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E4=B8=80=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 53 ++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index b28d8e7..908a227 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -164,45 +164,41 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 - 函数是对象 - 函数提供本地变量作用域 - ----------校对分割线--------- - ## 回调模式 -函数是对象,也就意味着函数可以当作参数传入另外一个函数中。当你给函数writeCode()传入一个函数参数introduceBugs(),在某个时刻writeCode()执行了(或调用了)introduceBugs()。在这种情况下,我们说introduceBugs()是一个“回调函数”,简称“回调”: +函数是对象,也就意味着函数可以当作参数传入另外一个函数中。给函数`writeCode()`传入一个函数参数`introduceBugs()`,在某个时刻`writeCode()`执行了(或调用了)`introduceBugs()`,在这种情况下,我们称`introduceBugs()`是一个“回调函数”,简称“回调”: function writeCode(callback) { - // do something... + // 做点什么…… callback(); - // ... + // …… } function introduceBugs() { - // ... make bugs + // …… } writeCode(introduceBugs); -注意introduceBugs()是如何作为参数传入writeCode()的,当作参数的函数不带括号。括号的意思是执行函数,而这里我们希望传入一个引用,让writeCode()在合适的时机执行它(调用它)。 +注意`introduceBugs()`作为参数传入`writeCode()`时,函数后面是不带括号的。括号的意思是执行函数,而这里我们希望传入一个引用,让`writeCode()`在合适的时机执行它(调用它)。 - -### 一个回调的例子 +### 回调的例子 -我们从一个例子开始,首先介绍无回调的情况,然后在作修改。假设你有一个通用的函数,用来完成某种复杂的逻辑并返回一大段数据。假设我们用findNodes()来命名这个通用函数,这个函数用来对DOM树进行遍历,并返回我所感兴趣的页面节点: +我们从一个例子开始,首先介绍无回调的情况,然后再进行修改。假设你有一个通用的函数,用来完成某种复杂的逻辑并返回一大段数据。假设这个通用函数叫`findNodes()`,用来对DOM树进行遍历,并返回页面节点: var findNodes = function () { - var i = 100000, // big, heavy loop - nodes = [], // stores the result - found; // the next node found + var i = 100000, // 大量耗时的循环 + nodes = [], // 存储结果 + found; // 标示下找到的节点 while (i) { i -= 1; - // complex logic here... + // 这里是复杂的逻辑…… nodes.push(found); } return nodes; }; -保持这个函数的功能的通用性并一贯返回DOM节点组成的数组,并不会发生对节点的实际操作,这是一个不错的注意。可以将操作节点的逻辑放入另外一个函数中,比如放入一个hide()函数中,这个函数用来隐藏页面中的节点元素: +保持这个函数的功能的通用性,让它只返回DOM节点组成的数组,而不对节点进行操作是一个很好的思想。可以将操作节点的逻辑放入另外一个函数中,比如`hide()`函数,这个函数用来隐藏页面中的节点元素: var hide = function (nodes) { var i = 0, max = nodes.length; @@ -211,27 +207,27 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 } }; - // executing the functions + // 执行函数 hide(findNodes()); -这个实现的效率并不高,因为它将findNodes()所返回的节点数组重新遍历了一遍。最好在findNodes()中选择元素的时候就直接应用hide()操作,这样就能避免第二次的遍历,从而提高效率。但如果将hide()的逻辑写死在findNodes()的函数体内,findNodes()就变得不再通用了(译注:如果我将hide()的逻辑替换成其他逻辑怎么办呢?),因为修改逻辑和遍历逻辑耦合在一起了。如果使用回调模式,则可以将隐藏节点的逻辑写入回调函数,将其传入findNodes()中适时执行: +这个实现的效率并不高,因为它将`findNodes()`所返回的节点数组重新遍历了一遍。更高效的办法是在`findNodes()`中选择元素的时候就直接应用`hide()`操作,这样就能避免第二次的遍历,从而提高效率。但如果将`hide()`的逻辑写死在`findNodes()`的函数体内,`findNodes()`就变得不再通用了,因为修改逻辑和遍历逻辑耦合在一起了。这时候如果使用回调模式,就可以将隐藏节点的逻辑写入回调函数,将其传入`findNodes()`中适时执行: - // refactored findNodes() to accept a callback + // 重构后的findNodes()接受一个回调函数 var findNodes = function (callback) { var i = 100000, nodes = [], found; - // check if callback is callable + // 检查回调函数是否可以执行 if (typeof callback !== "function") { callback = false; } while (i) { i -= 1; - // complex logic here... + // 这里是复杂的逻辑…… - // now callback: + // 回调: if (callback) { callback(found); } @@ -241,25 +237,26 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 return nodes; }; -这里的实现比较直接,findNodes()多作了一个额外工作,就是检查回调函数是否存在,如果存在的话就执行它。回调函数是可选的,因此修改后的findNodes()也是和之前一样使用,是可以兼容旧代码和旧API的。 +这里的实现比较直接,`findNodes()`多作了一个额外工作,就是检查回调函数是否存在,如果存在的话就执行它。回调函数是可选的,因此修改后的`findNodes()`仍然可以和之前一样使用,是可以兼容旧代码和旧API的。 -这时hide()的实现就非常简单了,因为它不用对元素列表做任何遍历了: +这时`hide()`的实现就非常简单了,因为它不用对元素列表做任何遍历了: - // a callback function + // 回调函数 var hide = function (node) { node.style.display = "none"; }; - // find the nodes and hide them as you go + // 找到节点并隐藏它们 findNodes(hide); -正如代码中所示,回调函数可以是事先定义好的,也可以是一个匿名函数,你也可以将其称作main函数,比如这段代码,我们利用同样的通用函数findNodes()来完成显示元素的操作: +回调函数可以是事先定义好的,像上面的代码一样,也可以是一个在调用函数时创建的匿名函数,比如这段代码,我们利用同样的通用函数`findNodes()`来完成显示元素的操作: - // passing an anonymous callback + // 传入匿名回调函数 findNodes(function (node) { node.style.display = "block"; }); +---------校对分割线--------- ### 回调和作用域 From 2ce5b27e8a4c74d8d8bf4bdd0386663e646e75df Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 15 Apr 2013 19:44:37 +0800 Subject: [PATCH 184/258] =?UTF-8?q?=E5=9B=9E=E8=B0=83=E5=92=8C=E4=BD=9C?= =?UTF-8?q?=E7=94=A8=E5=9F=9F=20=E5=B0=8F=E8=8A=82=E6=A0=A1=E5=AF=B9?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index 908a227..ff07ed4 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -256,17 +256,15 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 node.style.display = "block"; }); ----------校对分割线--------- - ### 回调和作用域 在上一个例子中,执行回调函数的写法是: callback(parameters); -尽管这种写法可以适用大多数的情况,而且足够简单,但还有一些场景,回调函数不是匿名函数或者全局函数,而是对象的方法。如果回调函数中使用this指向它所属的对象,则回调逻辑往往并不像我们希望的那样执行。 +尽管这种写法很简单,而且可以适用于大多数的情况,但还有一些场景,回调函数不是匿名函数或者全局函数,而是对象的方法,如果这种情况下回调函数中使用了`this`指向它所属的对象,则回调逻辑就可能不是我们期望的那样。 -假设回调函数是paint(),它是myapp的一个方法: +假设回调函数是`paint()`,它是`myapp`的一个方法: var myapp = {}; myapp.color = "green"; @@ -284,13 +282,13 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 // ... }; -当你调用findNodes(myapp.paint),运行结果和我们期望的不一致,因为this.color未定义。因为findNodes()是全局函数,this指向的是全局对象。如果findNodes()是dom对象的方法(类似dom.findNodes()),那么回调函数内的this则指向dom,而不是myapp。 +当你调用`findNodes(myapp.paint)`时,运行结果和我们期望的不一致,因为`this.color`未定义。这时候`this`指向的是全局对象,因为`findNodes()`是全局函数。如果`findNodes()`是dom对象的方法(类似`dom.findNodes()`),那么回调函数内的`this`指向该dom,而不是`myapp`。 解决办法是,除了传入回调函数,还需将回调函数所属的对象当作参数传进去: findNodes(myapp.paint, myapp); -同样需要修改findNodes()的逻辑,增加对传入的对象的绑定: +同样需要修改`findNodes()`的逻辑,增加对传入的对象的绑定: var findNodes = function (callback, callback_obj) { //... @@ -300,9 +298,9 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 // ... }; -在后续的章节会对call()和apply()有更详细的讲述。 +在后续的章节会对`call()`和`apply()`有更详细的讲述。 -其实还有一种替代写法,就是将函数当作字符串传入findNodes(),这样就不必再写一次对象了,换句话说: +其实还有一种替代写法,就是将函数名称以字符串传入`findNodes()`,这样就不必再写一次对象了,也就是说: findNodes(myapp.paint, myapp); @@ -310,7 +308,7 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 findNodes("paint", myapp); -在findNodes()中的逻辑则需要修改为: +在`findNodes()`中的逻辑则需要修改为: var findNodes = function (callback, callback_obj) { @@ -325,7 +323,7 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 // ... }; - +---------校对分割线--------- ### 异步事件监听 JavaScript中的回调模式已经是我们的家常便饭了,比如,如果你给网页中的元素绑定事件,则需要提供回调函数的引用,以便事件发生时能调用到它。这里有一个简单的例子,我们将console.log()作为回调函数绑定了document的点击事件: From bdd9c605fa458b868ce3c7c4f5200c58401a395e Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 15 Apr 2013 20:28:22 +0800 Subject: [PATCH 185/258] =?UTF-8?q?=E5=BC=82=E6=AD=A5=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E7=9B=91=E5=90=AC=20=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index ff07ed4..6d9dd9e 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -323,17 +323,17 @@ JavaScript的函数具有两个主要特性,正是这两个特性让它们与 // ... }; ----------校对分割线--------- ### 异步事件监听 -JavaScript中的回调模式已经是我们的家常便饭了,比如,如果你给网页中的元素绑定事件,则需要提供回调函数的引用,以便事件发生时能调用到它。这里有一个简单的例子,我们将console.log()作为回调函数绑定了document的点击事件: +JavaScript中的回调模式已经是我们的家常便饭了,比如,如果你给网页中的元素绑定事件,则需要提供回调函数的引用,以便事件发生时能调用到它。这里有一个简单的例子,我们将`console.log()`作为回调函数绑定到了`document`的点击事件上: document.addEventListener("click", console.log, false); -客户端浏览器中的大多数编程都是事件驱动的,当网页下载完成,则触发load事件,当用户和页面产生交互时也会触发多种事件,比如click、keypress、mouseover、mousemove等等。正是由于回调模式的灵活性,JavaScript天生适于事件驱动编程。回调模式能够让程序“异步”执行,换句话说,就是让程序不按顺序执行。 +客户端浏览器中的大多数编程都是事件驱动的,当网页下载完成,则触发`load`事件,当用户和页面产生交互时也会触发多种事件,比如`click`、`keypress`、`mouseover`、`mousemove`等等。JavaScript天生适合事件驱动编程,因为回调模式能够让程序“异步”执行,换句话说,就是让程序不按顺序执行。 -“不要打电话给我,我会打给你”,这是好莱坞很有名的一句话,很多电影都有这句台词。电影中的主角不可能同时应答很多个电话呼叫。在JavaScript的异步事件模型中也是同样的道理。电影中是留下电话号码,JavaScript中是提供一个回调函数,当时机成熟时就触发回调。有时甚至提供了很多回调,有些回调压根是没用的,但由于这个事件可能永远不会发生,因此这些回调的逻辑也不会执行。比如,假设你从此不再用“鼠标点击”,那么你之前绑定的鼠标点击的回调函数则永远也不会执行。 +“不要打电话给我,我会打给你”,这是好莱坞很有名的一句台词,可能很多人会对同一个角色说这句话,而电影中的主角不可能同时应答这些人的电话呼叫。在JavaScript的异步事件模型中也是同样的道理,不同的是,电影中是留下电话号码,JavaScript中是提供一个在适当的时机被调用的回调函数。有时甚至可以提供比实际需要更多的回调函数,因为可能某个特定的事件永远不会发生。比如,假设用户一直不点击“购买”,那么你之前写的用来验证信用卡号格式的函数就永远不会被调用执行。(译注:这段话有点不好翻译,前面的比喻看不懂。后面有两个方面的意思,一方面指回调函数并不一定会被执行,如果事件不发生,那么回调函数就永远不会被执行;另一方面指可以通过多个事件来绑定同一个回调函数,因为你无法确定用户会触发哪一个事件,比如到底是键盘操作还是鼠标操作。) +---------校对分割线--------- ### 超时 From fc5df2777d50385e899bc1304b5ea20597e3cd4a Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 15 Apr 2013 20:32:16 +0800 Subject: [PATCH 186/258] =?UTF-8?q?=E5=BB=B6=E6=97=B6=20=E6=A0=A1=E5=AF=B9?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index 6d9dd9e..fe8c341 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -333,19 +333,18 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 “不要打电话给我,我会打给你”,这是好莱坞很有名的一句台词,可能很多人会对同一个角色说这句话,而电影中的主角不可能同时应答这些人的电话呼叫。在JavaScript的异步事件模型中也是同样的道理,不同的是,电影中是留下电话号码,JavaScript中是提供一个在适当的时机被调用的回调函数。有时甚至可以提供比实际需要更多的回调函数,因为可能某个特定的事件永远不会发生。比如,假设用户一直不点击“购买”,那么你之前写的用来验证信用卡号格式的函数就永远不会被调用执行。(译注:这段话有点不好翻译,前面的比喻看不懂。后面有两个方面的意思,一方面指回调函数并不一定会被执行,如果事件不发生,那么回调函数就永远不会被执行;另一方面指可以通过多个事件来绑定同一个回调函数,因为你无法确定用户会触发哪一个事件,比如到底是键盘操作还是鼠标操作。) ----------校对分割线--------- - -### 超时 +### 延时 -另外一个最常用的回调模式是在调用超时函数时,超时函数是浏览器window对象的方法,共有两个:setTimeout()和setInterval()。这两个方法的参数都是回调函数。 +另外一个最常用的回调模式是在调用延时函数的时候。延时函数是浏览器`window`对象的方法,共有两个:`setTimeout()`和`setInterval()`。这两个方法的参数都是回调函数。 var thePlotThickens = function () { console.log('500ms later...'); }; setTimeout(thePlotThickens, 500); -再次需要注意,函数thePlotThickens是作为变量传入setTimeout的,它不带括号,如果带括号的话则立即执行了,这里只是用到这个函数的引用,以便在setTimeout的逻辑中调用到它。也可以传入字符串“thePlotThickens()”,但这是一种反模式,和eval()一样不推荐使用。 +再次提醒,函数名`thePlotThickens`是作为变量传入`setTimeout`的,它不带括号,如果带括号的话就被立即执行了,而这里只是用到这个函数的引用,以便在`setTimeout()`的逻辑中调用它。也可以传入字符串`"thePlotThickens()"`,但这是一种反模式,和`eval()`一样不推荐使用。 +---------校对分割线--------- ### 库中的回调 From 58f54adcb0af8f5bbb38d65160b4263a837ef3ae Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 15 Apr 2013 20:34:32 +0800 Subject: [PATCH 187/258] =?UTF-8?q?=E7=B1=BB=E5=BA=93=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=20=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index fe8c341..4626e2d 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -344,13 +344,12 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 再次提醒,函数名`thePlotThickens`是作为变量传入`setTimeout`的,它不带括号,如果带括号的话就被立即执行了,而这里只是用到这个函数的引用,以便在`setTimeout()`的逻辑中调用它。也可以传入字符串`"thePlotThickens()"`,但这是一种反模式,和`eval()`一样不推荐使用。 ----------校对分割线--------- - -### 库中的回调 +### 类库中的回调 -回调模式非常简单,但又很强大。可以随手拈来灵活运用,因此这种模式在库的设计中也非常得宠。库的代码要尽可能的保持通用和重用,而回调模式则可帮助库的作者完成这个目标。你不必预料和实现你所想到的所有情形,因为这会让库变的膨胀而臃肿,而且大多数用户并不需要这些多余的特性支持。相反,你将精力放在核心功能的实现上,提供回调的入口作为“钩子”,可以让库的方法变得可扩展、可定制。 +回调模式非常简单,但又很强大,可以信手拈来灵活运用,因此这种模式在类库的设计中也非常得宠。类库的代码要尽可能保持通用和可复用,而回调模式则可帮助库的作者达成这个目标。你不必预料并实现你所想到的所有情形,这会让类库变得臃肿,而且大多数用户并不需要这些多余的特性支持。相反,你将精力放在核心功能的实现上,提供回调的入口作为“钩子”,可以让类库的方法变得可扩展、可定制。 +---------校对分割线--------- ## 返回函数 From 10b9c766eaaebbf3e99905836ea5d79889d3ecfc Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 15 Apr 2013 20:42:48 +0800 Subject: [PATCH 188/258] =?UTF-8?q?=E8=BF=94=E5=9B=9E=E5=87=BD=E6=95=B0=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index 4626e2d..49644f9 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -349,13 +349,11 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 回调模式非常简单,但又很强大,可以信手拈来灵活运用,因此这种模式在类库的设计中也非常得宠。类库的代码要尽可能保持通用和可复用,而回调模式则可帮助库的作者达成这个目标。你不必预料并实现你所想到的所有情形,这会让类库变得臃肿,而且大多数用户并不需要这些多余的特性支持。相反,你将精力放在核心功能的实现上,提供回调的入口作为“钩子”,可以让类库的方法变得可扩展、可定制。 ----------校对分割线--------- - ## 返回函数 -函数是对象,因此当然可以作为返回值。也就是说,函数不一定非要返回一坨数据,函数可以返回另外一个定制好的函数,或者可以根据输入的不同按需创造另外一个函数。 +函数是对象,因此可以作为返回值。也就是说,函数不一定非要返回一坨数据,函数也可以返回另外一个函数,或者可以根据输入的不同按需创造另外一个函数。 -这里有一个简单的例子:一个函数完成了某种功能,可能是一次性初始化,然后都基于这个返回值进行操作,这个返回值恰巧是另一个函数: +这里有一个简单的例子:一个函数完成了某种功能,可能是一次性初始化,然后做了一些对返回值的操作,而这个返回值恰巧是另一个函数: var setup = function () { alert(1); @@ -364,11 +362,11 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 }; }; - // using the setup function + // 使用setup()函数 var my = setup(); // alerts 1 my(); // alerts 2 -因为setup()把返回的函数作了包装,它创建了一个闭包,我们可以用这个闭包来存储一些私有数据,这些私有数据可以通过返回的函数进行操作,但在函数外部不能直接读取到这些私有数据。比如这个例子中提供了一个计数器,每次调用这个函数计数器都会加一: +因为`setup()`包裹了返回的函数,因此它创建了一个闭包,我们可以用这个闭包来存储一些私有数据,这些私有数据可以通过返回的函数进行操作,但在函数外部不能直接读取到这些私有数据。比如这个例子中提供了一个计数器,每次调用这个函数时,计数器都会加一: var setup = function () { var count = 0; @@ -377,13 +375,14 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 }; }; - // usage + // 使用 var next = setup(); - next(); // returns 1 + next(); // 返回 1 next(); // 2 next(); // 3 +---------校对分割线--------- ## 自定义函数 From f052812535d27d2944fe2d0b6b49fc31db594c8c Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 15 Apr 2013 21:09:55 +0800 Subject: [PATCH 189/258] =?UTF-8?q?=E9=87=8D=E5=AE=9A=E4=B9=89=E5=87=BD?= =?UTF-8?q?=E6=95=B0=20=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index 49644f9..e6ab9bf 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -382,11 +382,9 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 next(); // 3 ----------校对分割线--------- - -## 自定义函数 +## 重定义函数 -我们动态定义函数,并将函数赋值给变量。如果将你定义的函数赋值给已经存在的函数变量的话,则新函数会覆盖旧函数。这样做的结果是,旧函数的引用就丢弃掉了,变量中所存储的引用值替换成了新的。这样看起来这个变量指代的函数逻辑就发生了变化,或者说函数进行了“重新定义”或“重写”。说起来有些拗口,实际上并不复杂,来看一个例子: +函数可以被动态定义,也可以被赋值给变量。如果将你定义的函数赋值给已经存在的函数变量的话,则新函数会覆盖旧函数。这样做的结果是,旧函数的引用被丢弃掉,变量中所存储的引用值替换成了新的函数。这样看起来这个变量指代的函数逻辑就发生了变化,或者说函数进行了“重新定义”或“重写”。听起来很麻烦,但实际上并不复杂,来看一个例子: var scareMe = function () { alert("Boo!"); @@ -394,55 +392,56 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 alert("Double boo!"); }; }; - // using the self-defining function + // 使用重定义函数 scareMe(); // Boo! scareMe(); // Double boo! -当函数中包含一些初始化操作,并希望这些初始化只执行一次,那么这种模式是非常适合这个场景的。因为能避免的重复执行则尽量避免,函数的一部分可能再也不会执行到。在这个场景中,函数执行一次后就被重写为另外一个函数了。 +当函数中包含一些初始化操作,并希望这些初始化操作只执行一次,那么这种模式是非常合适的,因为我们要避免重复执行不需要的代码。在这个场景中,函数执行一次后就被重写为另外一个函数了。 -使用这种模式可以帮助提高应用的执行效率,因为重新定义的函数执行更少的代码。 +使用这种模式可以帮助提高应用的执行效率,因为重新定义的函数执行的代码量更少。 ->这种模式的另外一个名字是“函数的懒惰定义”,因为直到函数执行一次后才重新定义,可以说它是“某个时间点之后才存在”,简称“懒惰定义”。 +> 这种模式的另外一个名字是“函数的懒惰定义”,因为直到函数执行一次后才重新定义,可以说它是“某个时间点之后才存在”,简称“懒惰定义”。 -这种模式有一种明显的缺陷,就是之前给原函数添加的功能在重定义之后都丢失了。如果将这个函数定义为不同的名字,函数赋值给了很多不同的变量,或作为对象的方法使用,那么新定义的函数有可能不会执行,原始的函数会照旧执行(译注:由于函数的赋值是引用的赋值,函数赋值给多个变量只是将引用赋值给了多个变量,当某一个变量定义了新的函数,也只是变量的引用值发生变化,原函数本身依旧存在,当程序中存在某个变量的引用还是旧函数的话,旧函数还是会依旧执行)。 +这种模式有一个明显的缺陷,就是之前给原函数添加的功能在重定义之后都丢失了。同时,如果这个函数被重定义为不同的名字,被赋值给不同的变量,或者是作为对象的方法使用,那么重定义的部分并不会生效,原来的函数依然会被执行。 -让我们来看一个例子,scareMe()函数在这里作为一等对象来使用: +让我们来看一个例子,`scareMe()`函数在这里作为一等对象来使用: 1. 给他增加了一个属性 -2. 函数对象赋值给一个新变量 -3. 函数依旧可以作为方法来调用 +2. 函数对象被赋值给一个新变量 +3. 函数还被作为方法来调用 看一下这段代码: - // 1. adding a new property + // 1. 添加一个新属性 scareMe.property = "properly"; - // 2. assigning to a different name + // 2. 被赋值给一个不同名的变量 var prank = scareMe; - // 3. using as a method + // 3. 作为方法使用 var spooky = { boo: scareMe }; - // calling with a new name + // 使用新名字调用 prank(); // "Boo!" prank(); // "Boo!" console.log(prank.property); // "properly" - // calling as a method + // 作为方法调用 spooky.boo(); // "Boo!" spooky.boo(); // "Boo!" console.log(spooky.boo.property); // "properly" - // using the self-defined function + // 使用重定义函数 scareMe(); // Double boo! scareMe(); // Double boo! console.log(scareMe.property); // undefined -从结果来看,当自定义函数被赋值给一个新的变量的时候,这段使用自定义函数的代码的执行结果与我们期望的结果可能并不一样。每当prank()运行的时候,它都弹出“Boo!”。同时它也重写了scareMe()函数,但是prank()自己仍然能够使用之前的定义,包括属性property。在这个函数被作为spooky对象的boo()方法调用的时候,结果也一样。所有的这些调用,在第一次的时候就已经修改了全局的scareMe()的指向,所以当它最终被调用的时候,它的函数体已经被修改为弹出“Double boo”。它也就不能获取到新添加的属性“property”。 +从结果来看,当重定义函数被赋值给一个新的变量的时候,这段使用重定义函数的代码的执行结果与我们期望的结果可能并不一样。每当`prank()`被调用的时候,它都弹出“Boo!”。同时它也重写了`scareMe()`函数,但是`prank()`自己仍然能够使用之前的定义,包括属性`property`。在这个函数被作为`spooky`对象的`boo()`方法调用的时候,结果也一样。所有的这些调用,在第一次的时候就已经修改了全局的`scareMe()`的指向,所以当它最终被调用的时候,它的函数体已经被修改为弹出“Double boo”,也就不能获取到新添加的属性`scareMe.property`。 +---------校对分割线--------- ## 立即执行的函数 From 3ec80da78df23f6116ef25aae58611de627659b2 Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 15 Apr 2013 21:35:47 +0800 Subject: [PATCH 190/258] =?UTF-8?q?=E5=8D=B3=E6=97=B6=E5=87=BD=E6=95=B0=20?= =?UTF-8?q?=E7=AC=AC=E4=B8=80=E9=83=A8=E5=88=86=E6=A0=A1=E5=AF=B9=E5=AE=8C?= =?UTF-8?q?=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index e6ab9bf..f1ff96e 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -441,17 +441,15 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 从结果来看,当重定义函数被赋值给一个新的变量的时候,这段使用重定义函数的代码的执行结果与我们期望的结果可能并不一样。每当`prank()`被调用的时候,它都弹出“Boo!”。同时它也重写了`scareMe()`函数,但是`prank()`自己仍然能够使用之前的定义,包括属性`property`。在这个函数被作为`spooky`对象的`boo()`方法调用的时候,结果也一样。所有的这些调用,在第一次的时候就已经修改了全局的`scareMe()`的指向,所以当它最终被调用的时候,它的函数体已经被修改为弹出“Double boo”,也就不能获取到新添加的属性`scareMe.property`。 ----------校对分割线--------- - -## 立即执行的函数 +## 即时函数 -立即执行的函数是一种语法模式,它会使函数在定义后立即执行。看这个例子: +即时函数是一种语法模式,它会使函数在定义后立即执行。看这个例子: (function () { alert('watch out!'); }()); -这种模式本质上只是一个在创建后就被执行的函数表达式(具名或者匿名)。“立即执行的函数”这种说法并没有在ECMAScript标准中被定义,但它作为一个名词,有助于我们的描述和讨论。 +这种模式本质上只是一个在创建后就被执行的函数表达式(具名或者匿名)。“即时函数”这种说法并没有在ECMAScript标准中被定义,但它作为一个名词,有助于我们的描述和讨论。 这种模式由以下几个部分组成: @@ -465,7 +463,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 alert('watch out!'); })(); -这种模式很有用,它为我们提供一个作用域的沙箱,可以在执行一些初始化代码的时候使用。设想这样的场景:当页面加载的时候,你需要运行一些代码,比如绑定事件、创建对象等等。所有的这些代码都只需要运行一次,所以没有必要创建一个带有名字的函数。但是这些代码需要一些临时变量,而这些变量在初始化完之后又不会再次用到。显然,把这些变量作为全局变量声明是不合适的。正因为如此,我们才需要立即执行的函数。它可以把你所有的代码包裹到一个作用域里面,而不会暴露任何变量到全局作用域中: +这种模式很有用,它为我们提供一个作用域的沙箱,可以在执行一些初始化代码的时候使用。设想这样的场景:当页面加载的时候,你需要运行一些代码,比如绑定事件、创建对象等等。所有的这些代码都只需要运行一次,所以没有必要创建一个带有名字的函数。但是这些代码需要一些临时变量,而这些变量在初始化完之后又不会再次被用到。显然,把这些变量作为全局变量声明是不合适的。正因为如此,我们才需要即时函数。它可以把你所有的代码包裹到一个作用域里面,而不会暴露任何变量到全局作用域中: (function () { @@ -477,13 +475,14 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 }()); // "Today is Fri, 13" -如果这段代码没有被包裹到立即执行函数中,那么变量days、today、msg都会是全局变量,而这些变量仅仅是由因为初始化而遗留下来的垃圾,没有任何用处。 +如果这段代码没有被包裹到立即执行函数中,那么变量`days`、`today`、`msg`都会是全局变量,而这些变量仅仅是因为初始化而遗留下来的垃圾,没有任何用处。 +---------校对分割线--------- -### 立即执行的函数的参数 +### 即时函数的参数 -立即执行的函数也可以接受参数,看这个例子: +即时函数也可以接受参数,看这个例子: // prints: // I met Joe Black on Fri Aug 13 2010 23:26:59 GMT-0800 (PST) @@ -494,14 +493,14 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 }("Joe Black", new Date())); -通常的做法,会把全局对象当作一个参数传给立即执行的函数,以保证在函数内部也可以访问到全局对象,而不是使用window对象,这样可以使得代码在非浏览器环境中使用时更具可移植性。 +通常的做法,会把全局对象当作一个参数传给即时函数,以保证在函数内部也可以访问到全局对象,而不是使用window对象,这样可以使得代码在非浏览器环境中使用时更具可移植性。 -值得注意的是,一般情况下尽量不要给立即执行的函数传入太多的参数,否则会有一件麻烦的事情,就是你在阅读代码的时候需要频繁地上下滚动代码。 +值得注意的是,一般情况下尽量不要给即时函数传入太多的参数,否则会有一件麻烦的事情,就是你在阅读代码的时候需要频繁地上下滚动代码。 -### 立即执行的函数的返回值 +### 即时函数的返回值 -和其它的函数一样,立即执行的函数也可以返回值,并且这些返回值也可以被赋值给变量: +和其它的函数一样,即时函数也可以返回值,并且这些返回值也可以被赋值给变量: var result = (function () { return 2 + 2; @@ -521,9 +520,9 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 return 2 + 2; })(); -前面的例子中,立即执行的函数返回的是一个基本类型的数值。但事实上,除了基本类型以外,一个立即执行的函数可以返回任意类型的值,甚至返回一个函数都可以。你可以利用立即执行的函数的作用域来存储一些私有的数据,这些数据只能在返回的内层函数中被访问。 +前面的例子中,即时函数返回的是一个基本类型的数值。但事实上,除了基本类型以外,一个即时函数可以返回任意类型的值,甚至返回一个函数都可以。你可以利用即时函数的作用域来存储一些私有的数据,这些数据只能在返回的内层函数中被访问。 -在下面的例子中,立即执行的函数的返回值是一个函数,这个函数会简单地返回res的值,并且它被赋给了变量getResult。而res是一个预先计算好的变量,它被存储在立即执行函数的闭包中: +在下面的例子中,即时函数的返回值是一个函数,这个函数会简单地返回res的值,并且它被赋给了变量getResult。而res是一个预先计算好的变量,它被存储在立即执行函数的闭包中: var getResult = (function () { var res = 2 + 2; @@ -532,7 +531,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 }; }()); -在定义一个对象的属性的时候也可以使用立即执行的函数。设想一下这样的场景:你需要定义一个对象的属性,这个属性在对象的生命周期中都不会改变,但是在定义之前,你需要做一点额外的工作来得到正确的值。这种情况下你就可以使用立即执行的函数来包裹那些额外的工作,然后将它的返回值作为对象属性的值。下面是一个例子: +在定义一个对象的属性的时候也可以使用即时函数。设想一下这样的场景:你需要定义一个对象的属性,这个属性在对象的生命周期中都不会改变,但是在定义之前,你需要做一点额外的工作来得到正确的值。这种情况下你就可以使用即时函数来包裹那些额外的工作,然后将它的返回值作为对象属性的值。下面是一个例子: var o = { message: (function () { @@ -555,13 +554,13 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 ### 好处和用法 -立即执行的函数应用很广泛。它可以帮助我们做一些不想留下全局变量的工作。所有定义的变量都只是立即执行的函数的本地变量,你完全不用担心临时变量会污染全局对象。 +即时函数应用很广泛。它可以帮助我们做一些不想留下全局变量的工作。所有定义的变量都只是即时函数的本地变量,你完全不用担心临时变量会污染全局对象。 -> 立即执行的函数还有一些名字,比如“自调用函数”或者“自执行函数”,因为这些函数会在被定义后立即执行自己。 +> 即时函数还有一些名字,比如“自调用函数”或者“自执行函数”,因为这些函数会在被定义后立即执行自己。 这种模式也经常被用到书签代码中,因为书签代码会在任何一个页面运行,所以需要非常苛刻地保持全局命名空间干净。 -这种模式也可以让你包裹一些独立的特性到一个封闭的模块中。设想你的页面是静态的,在没有JavaScript的时候工作正常,然后,本着渐进增强的精神,你给页面加入了一点增加代码。这时候,你就可以把你的代码(也可以叫“模块”或者“特性”)放到一个立即执行的函数中并且保证页面在有没有它的时候都可以正常工作。然后你就可以加入更多的增强特性,或者对它们进行移除、进行独立测试或者允许用户禁用等等。 +这种模式也可以让你包裹一些独立的特性到一个封闭的模块中。设想你的页面是静态的,在没有JavaScript的时候工作正常,然后,本着渐进增强的精神,你给页面加入了一点增加代码。这时候,你就可以把你的代码(也可以叫“模块”或者“特性”)放到一个即时函数中并且保证页面在有没有它的时候都可以正常工作。然后你就可以加入更多的增强特性,或者对它们进行移除、进行独立测试或者允许用户禁用等等。 你可以使用下面的模板定义一段函数代码,我们叫它module1: @@ -578,7 +577,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 ## 立即初始化的对象 -还有另外一种可以避免污染全局作用域的方法,和前面描述的立即执行的函数相似,叫做“立即初始化的对象”模式。这种模式使用一个带有init()方法的对象来实现,这个方法在对象被创建后立即执行。初始化的工作由init()函数来完成。 +还有另外一种可以避免污染全局作用域的方法,和前面描述的即时函数相似,叫做“立即初始化的对象”模式。这种模式使用一个带有init()方法的对象来实现,这个方法在对象被创建后立即执行。初始化的工作由init()函数来完成。 下面是一个立即初始化的对象模式的例子: @@ -607,7 +606,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 ({...}).init(); ({...}.init()); -这种模式的好处和自动执行的函数模式是一样的:在做一些一次性的初始化工作的时候保护全局作用域不被污染。从语法上看,这种模式似乎比只包含一段代码在一个匿名函数中要复杂一些,但是如果你的初始化工作比较复杂(这种情况很常见),它会给整个初始化工作一个比较清晰的结构。比如,一些私有的辅助性函数可以被很轻易地看出来,因为它们是这个临时对象的属性,但是如果是在立即执行的函数模式中,它们很可能只是一些散落的函数。 +这种模式的好处和自动执行的函数模式是一样的:在做一些一次性的初始化工作的时候保护全局作用域不被污染。从语法上看,这种模式似乎比只包含一段代码在一个匿名函数中要复杂一些,但是如果你的初始化工作比较复杂(这种情况很常见),它会给整个初始化工作一个比较清晰的结构。比如,一些私有的辅助性函数可以被很轻易地看出来,因为它们是这个临时对象的属性,但是如果是在即时函数模式中,它们很可能只是一些散落的函数。 这种模式的一个弊端是,JavaScript压缩工具可能不能像压缩一段包裹在函数中的代码一样有效地压缩这种模式的代码。这些私有的属性和方法不被会重命名为一些更短的名字,因为从压缩工具的角度来看,保证压缩的可靠性更重要。在写作本书的时候,Google出品的Closure Compiler的“advanced”模式是唯一会重命名立即初始化的对象的属性的压缩工具。一个压缩后的样例是这样: @@ -1015,7 +1014,7 @@ step 1是一个所谓的部分应用的例子:我们只应用了第一个参 新函数在已有函数的基础上再加上一部分参数构成 2. 初始化模式,这些模式帮助我们用一种干净的、结构化的方法来做一些初始化工作(在web页面和应用中非常常见),通过一些临时变量来保证不污染全局命名空间。这些模式包括: - - 立即执行的函数 + - 即时函数 当它们被定义后立即执行 - 立即初始化的对象 From 49f7044f46abe47e80b6e066b4ad053b60b9a380 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=92=B1=E5=BC=A0=E7=9B=9B?= Date: Thu, 18 Apr 2013 17:05:03 +0800 Subject: [PATCH 191/258] Update chapter1.markdown --- chapter1.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapter1.markdown b/chapter1.markdown index 71d8d20..37bf43b 100644 --- a/chapter1.markdown +++ b/chapter1.markdown @@ -104,7 +104,7 @@ JavaScript语言的核心部分(不包含DOM、BOM和其它宿主对象)是 本书不会讨论ES5新增特性相关的模式,因为在本书截稿时并没有任何浏览器实现了ES5(译注:截止译稿校对时,Chrome/Firefox/IE9+已(部分)实现ES5,具体兼容情况可参考),但本书的示例代码有以下特点,以鼓励开发者向新标准转变: -- 确保所提供的示例代码在严格模式下不包错 +- 确保所提供的示例代码在严格模式下不报错 - 避免使用并明确指出弃用的构造函数相关的属性和方法,比如arguments.callee - 针对ES5中的内置模式比如Object.create(),在ES3中做同样的实现 From 588187d4c0e21c7bf0f499102e67b86924ce68ec Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 19 Apr 2013 12:36:24 +0800 Subject: [PATCH 192/258] =?UTF-8?q?=E5=8D=B3=E6=97=B6=E5=87=BD=E6=95=B0=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index f1ff96e..0f4807d 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -478,13 +478,11 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 如果这段代码没有被包裹到立即执行函数中,那么变量`days`、`today`、`msg`都会是全局变量,而这些变量仅仅是因为初始化而遗留下来的垃圾,没有任何用处。 ----------校对分割线--------- - ### 即时函数的参数 即时函数也可以接受参数,看这个例子: - // prints: + // 打印出: // I met Joe Black on Fri Aug 13 2010 23:26:59 GMT-0800 (PST) (function (who, when) { @@ -493,11 +491,10 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 }("Joe Black", new Date())); -通常的做法,会把全局对象当作一个参数传给即时函数,以保证在函数内部也可以访问到全局对象,而不是使用window对象,这样可以使得代码在非浏览器环境中使用时更具可移植性。 +通常我们会把全局对象当作一个参数传给即时函数,以保证在函数内部也可以访问到全局对象,而不是使用`window`对象,这样可以使得代码在非浏览器环境中使用时更具可移植性。 值得注意的是,一般情况下尽量不要给即时函数传入太多的参数,否则会有一件麻烦的事情,就是你在阅读代码的时候需要频繁地上下滚动代码。 - ### 即时函数的返回值 和其它的函数一样,即时函数也可以返回值,并且这些返回值也可以被赋值给变量: @@ -512,7 +509,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 return 2 + 2; }(); -这种写法更简洁,但是同时也容易造成误解。如果有人在阅读代码的时候忽略了最后的一对括号,那么他会以为result指向了一个函数。而事实上result是指向这个函数运行后的返回值,在这个例子中是4。 +这种写法更简洁,但是同时也容易造成误解。如果有人在阅读代码的时候忽略了最后的一对括号,那么他会以为`result`指向了一个函数。而事实上`result`是指向这个函数运行后的返回值,在这个例子中是4。 还有一种写法也可以得到同样的结果: @@ -520,9 +517,9 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 return 2 + 2; })(); -前面的例子中,即时函数返回的是一个基本类型的数值。但事实上,除了基本类型以外,一个即时函数可以返回任意类型的值,甚至返回一个函数都可以。你可以利用即时函数的作用域来存储一些私有的数据,这些数据只能在返回的内层函数中被访问。 +前面的例子中,即时函数返回的是一个基本类型的数值。但事实上,一个即时函数可以返回任意类型的值,甚至返回一个函数都可以。你可以利用即时函数的作用域来存储一些私有的数据,这些数据只能在返回的内层函数中被访问。 -在下面的例子中,即时函数的返回值是一个函数,这个函数会简单地返回res的值,并且它被赋给了变量getResult。而res是一个预先计算好的变量,它被存储在立即执行函数的闭包中: +在下面的例子中,即时函数的返回值是一个函数,这个函数会简单地返回`res`的值,并且这个值被赋给了变量`getResult`。而`res`是一个预先计算好的变量,它被存储在即时函数的闭包中: var getResult = (function () { var res = 2 + 2; @@ -531,7 +528,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 }; }()); -在定义一个对象的属性的时候也可以使用即时函数。设想一下这样的场景:你需要定义一个对象的属性,这个属性在对象的生命周期中都不会改变,但是在定义之前,你需要做一点额外的工作来得到正确的值。这种情况下你就可以使用即时函数来包裹那些额外的工作,然后将它的返回值作为对象属性的值。下面是一个例子: +在定义一个对象属性的时候也可以使用即时函数。设想一下这样的场景:你需要定义一个对象的属性,这个属性在对象的生命周期中都不会改变,但是在定义之前,你需要做一些计算来得到它的值。这种情况下你就可以使用即时函数来包裹那些额外的计算工作,然后将它的返回值作为对象属性的值。下面是一个例子: var o = { message: (function () { @@ -544,36 +541,36 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 } }; - // usage + // 使用对象 o.getMsg(); // "call me" o.message; // "call me" -在这个例子中,o.message是一个字符串,而不是一个函数,但是它需要一个函数在脚本载入后来得到这个属性值。 +在这个例子中,`o.message`是一个字符串,而不是一个函数,但是它需要一个函数在脚本载入后通过计算得到这个属性值。 - ### 好处和用法 即时函数应用很广泛。它可以帮助我们做一些不想留下全局变量的工作。所有定义的变量都只是即时函数的本地变量,你完全不用担心临时变量会污染全局对象。 > 即时函数还有一些名字,比如“自调用函数”或者“自执行函数”,因为这些函数会在被定义后立即执行自己。 -这种模式也经常被用到书签代码中,因为书签代码会在任何一个页面运行,所以需要非常苛刻地保持全局命名空间干净。 +这种模式也经常被用到书签代码中,因为书签代码有可能会运行在任何一个页面中,所以需要非常苛刻地保持全局命名空间干净。 -这种模式也可以让你包裹一些独立的特性到一个封闭的模块中。设想你的页面是静态的,在没有JavaScript的时候工作正常,然后,本着渐进增强的精神,你给页面加入了一点增加代码。这时候,你就可以把你的代码(也可以叫“模块”或者“特性”)放到一个即时函数中并且保证页面在有没有它的时候都可以正常工作。然后你就可以加入更多的增强特性,或者对它们进行移除、进行独立测试或者允许用户禁用等等。 +这种模式也可以让你包裹一些独立的特性到一个封闭的模块中。设想你的页面是静态的,在没有JavaScript的时候工作正常,然后,本着渐进增强的精神,你给页面加入了一点增强代码。这时候,你就可以把你的代码(也可以叫“模块”或者“特性”)放到一个即时函数中并且保证页面在有没有它的时候都可以正常工作。然后你就可以加入更多的增强特性,或者对它们进行移除、进行独立测试或者允许用户禁用等等。 你可以使用下面的模板定义一段函数代码,我们叫它module1: - // module1 defined in module1.js + // 在module1.js中定义module1 (function () { - // all the module 1 code ... + // 所有module 1的代码…… }()); -套用这个模板,你就可以编写其它的模块。然后在发布到线上的时候,你就可以决定在这个时间节点上哪些特性是可以使用的,然后使用发布脚本将它们打包上线。 +你可以套用这个模板来编写其它的模块,然后在发布到线上的时候,再决定在这个时间节点上哪些特性是稳定可用的,然后使用发布脚本将它们打包上线。 +---------校对分割线--------- ## 立即初始化的对象 From f01033c8e058f3d3a9a192ed8b656845ae097829 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 19 Apr 2013 12:42:31 +0800 Subject: [PATCH 193/258] =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E5=8D=B3=E6=97=B6?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=20=E6=A0=A1=E5=AF=B9=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index 0f4807d..7036fa6 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -570,48 +570,46 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 你可以套用这个模板来编写其它的模块,然后在发布到线上的时候,再决定在这个时间节点上哪些特性是稳定可用的,然后使用发布脚本将它们打包上线。 ----------校对分割线--------- - -## 立即初始化的对象 +## 对象即时初始化 -还有另外一种可以避免污染全局作用域的方法,和前面描述的即时函数相似,叫做“立即初始化的对象”模式。这种模式使用一个带有init()方法的对象来实现,这个方法在对象被创建后立即执行。初始化的工作由init()函数来完成。 +还有另外一种可以避免污染全局作用域的方法,和前面描述的即时函数相似,叫做“对象即时初始化”模式。这种模式使用一个带有`init()`方法的对象来实现,这个方法在对象被创建后立即执行。初始化的工作由`init()`函数来完成。 -下面是一个立即初始化的对象模式的例子: +下面是一个对象即时初始化模式的例子: ({ - // here you can define setting values - // a.k.a. configuration constants + // 这里可以定义一些设置项,比如常量 maxwidth: 600, maxheight: 400, - // you can also define utility methods + // 你也可以定义一些方法 gimmeMax: function () { return this.maxwidth + "x" + this.maxheight; }, - // initialize + // 初始化 init: function () { console.log(this.gimmeMax()); - // more init tasks... + // 更多的初始化任务…… } }).init(); -在语法上,当你使用这种模式的时候就像在使用对象字面量创建一个普通对象一样。除此之外,还需要将对象字面量用括号括起来,这样能让JavaScript引擎知道这是一个对象字面量,而不是一个代码块(if或者for循环之类)。在括号后面,紧接着就执行了init()方法。 +在语法上,当你使用这种模式的时候就像在使用对象字面量创建一个普通对象一样。不同之处在于,需要将对象字面量用括号括起来,这样能让JavaScript引擎知道这是一个对象字面量,而不是一个代码块(`if`或者`for`循环之类)。在括号后面,紧接着就执行了`init()`方法。 -你也可以将对象字面量和init()调用一起写到括号里面。简单地说,下面两种语法都是有效的: +你也可以将对象字面量和`init()`调用一起写到括号里面。简单地说,下面两种语法都是有效的: ({...}).init(); ({...}.init()); -这种模式的好处和自动执行的函数模式是一样的:在做一些一次性的初始化工作的时候保护全局作用域不被污染。从语法上看,这种模式似乎比只包含一段代码在一个匿名函数中要复杂一些,但是如果你的初始化工作比较复杂(这种情况很常见),它会给整个初始化工作一个比较清晰的结构。比如,一些私有的辅助性函数可以被很轻易地看出来,因为它们是这个临时对象的属性,但是如果是在即时函数模式中,它们很可能只是一些散落的函数。 +这种模式的好处和即时函数模式是一样的:在做一些一次性的初始化工作的时候保护全局作用域不被污染。从语法上看,这种模式似乎比即时函数要复杂一些,但是如果你的初始化工作比较复杂(这种情况很常见),它会给整个初始化工作一个比较清晰的结构。比如,一些私有的辅助性函数可以被很轻易地看出来,因为它们是这个临时对象的属性,但是如果是在即时函数模式中,它们很可能只是一些散落的函数。 这种模式的一个弊端是,JavaScript压缩工具可能不能像压缩一段包裹在函数中的代码一样有效地压缩这种模式的代码。这些私有的属性和方法不被会重命名为一些更短的名字,因为从压缩工具的角度来看,保证压缩的可靠性更重要。在写作本书的时候,Google出品的Closure Compiler的“advanced”模式是唯一会重命名立即初始化的对象的属性的压缩工具。一个压缩后的样例是这样: ({d:600,c:400,a:function(){return this.d+"x"+this.c},b:function(){console.log(this.a())}}).b(); -> 这种模式主要用于一些一次性的工作,并且在init()方法执行完后就无法再次访问到这个对象。如果希望在这些工作完成后保持对对象的引用,只需要简单地在init()的末尾加上return this;即可。 +> 这种模式主要用于一些一次性的工作,并且在`init()`方法执行完后就无法再次访问到这个对象。如果希望在这些工作完成后保持对对象的引用,只需要简单地在`init()`的末尾加上`return this;`即可。 +---------校对分割线--------- ## 条件初始化 From d7e13ee802563df2c9082dbfcbdd95487fd41ac2 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 19 Apr 2013 12:46:39 +0800 Subject: [PATCH 194/258] =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=20=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index 7036fa6..2963999 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -609,45 +609,43 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 > 这种模式主要用于一些一次性的工作,并且在`init()`方法执行完后就无法再次访问到这个对象。如果希望在这些工作完成后保持对对象的引用,只需要简单地在`init()`的末尾加上`return this;`即可。 ----------校对分割线--------- - ## 条件初始化 条件初始化(也叫条件加载)是一种优化模式。当你知道某种条件在整个程序生命周期中都不会变化的时候,那么对这个条件的探测只做一次就很有意义。浏览器探测(或者特征检测)是一个典型的例子。 -举例说明,当你探测到XMLHttpRequest被作为一个本地对象支持时,就知道浏览器不会在程序执行过程中改变这一情况,也不会出现突然需要去处理ActiveX对象的情况。当环境不发生变化的时候,你的代码就没有必要在需要在每次XHR对象时探测一遍(并且得到同样的结果)。 +举例说明,当你探测到`XMLHttpRequest`被作为一个本地对象支持时,就知道浏览器不会在程序执行过程中改变这一情况,也不会出现突然需要去处理ActiveX对象的情况。当环境不发生变化的时候,你的代码就没有必要在需要在每次初始化XHR对象时探测一遍(并且得到同样的结果)。 -另外一些可以从条件初始化中获益的场景是获得一个DOM元素的computed styles或者是绑定事件处理函数。大部分程序员在他们的客户端编程生涯中都编写过事件绑定和取消绑定相关的组件,像下面的例子: +另外一些可以从条件初始化中获益的场景是获得一个DOM元素的computed styles或者是绑定事件处理函数。大部分程序员在他们的编程生涯中都编写过事件绑定和取消绑定相关的组件,像下面的例子: - // BEFORE + // 优化前的代码 var utils = { addListener: function (el, type, fn) { if (typeof window.addEventListener === 'function') { el.addEventListener(type, fn, false); } else if (typeof document.attachEvent === 'function') { // IE el.attachEvent('on' + type, fn); - } else { // older browsers + } else { // 老的浏览器 el['on' + type] = fn; } }, removeListener: function (el, type, fn) { - // pretty much the same... + // 和上面很类似的代码…… } }; -这段代码的问题就是效率不高。每当你执行utils.addListener()或者utils.removeListener()时,同样的检查都会被重复执行。 +这段代码的问题就是效率不高。每当你执行`utils.addListener()`或者`utils.removeListener()`时,同样的检查都会被重复执行。 如果使用条件初始化,那么浏览器探测的工作只需要在初始化代码的时候执行一次。在初始化的时候,代码探测一次环境,然后重新定义这个函数在剩下来的程序生命周期中应该怎样工作。下面是一个例子,看看如何达到这个目的: - // AFTER + // 优化后的代码 - // the interface + // 接口 var utils = { addListener: null, removeListener: null }; - // the implementation + // 实现 if (typeof window.addEventListener === 'function') { utils.addListener = function (el, type, fn) { el.addEventListener(type, fn, false); @@ -671,9 +669,10 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 }; } -说到这里,要特别提醒一下关于浏览器探测的事情。当你使用这个模式的时候,不要对浏览器特性过度假设。举个例子,如果你探测到浏览器不支持window.addEventListener,不要假设这个浏览器是IE,也不要认为它不支持原生的XMLHttpRequest,虽然这个结论在整个浏览器历史上的某个点是正确的。当然,也有一些情况是可以放心地做一些特性假设的,比如.addEventListener和.removeEventListerner,但是通常来讲,浏览器的特性在发生变化时都是独立的。最好的策略就是分别探测每个特性,然后使用条件初始化,使这种探测只做一次。 +说到这里,要特别提醒一下关于浏览器探测的事情。当你使用这个模式的时候,不要对浏览器特性过度假设。举个例子,如果你探测到浏览器不支持`window.addEventListener`时,不要假设这个浏览器是IE,也不要认为它不支持原生的`XMLHttpRequest`,虽然这个结论在整个浏览器历史上的某个时间点是正确的。当然,也有一些情况是可以放心地做一些特性假设的,比如`.addEventListener`和`.removeEventListerner`,但是通常来讲,浏览器的特性在发生变化时都是独立的。最好的策略就是分别探测每个特性,然后使用条件初始化,使这种探测只做一次。 +---------校对分割线--------- ## 函数属性——Memoization模式 From 35b61d96f86562a66e82ff749325522db7d8814d Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 19 Apr 2013 12:53:43 +0800 Subject: [PATCH 195/258] =?UTF-8?q?=E8=AE=B0=E5=BF=86=E6=A8=A1=E5=BC=8F=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index 2963999..a04eb68 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -672,32 +672,30 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 说到这里,要特别提醒一下关于浏览器探测的事情。当你使用这个模式的时候,不要对浏览器特性过度假设。举个例子,如果你探测到浏览器不支持`window.addEventListener`时,不要假设这个浏览器是IE,也不要认为它不支持原生的`XMLHttpRequest`,虽然这个结论在整个浏览器历史上的某个时间点是正确的。当然,也有一些情况是可以放心地做一些特性假设的,比如`.addEventListener`和`.removeEventListerner`,但是通常来讲,浏览器的特性在发生变化时都是独立的。最好的策略就是分别探测每个特性,然后使用条件初始化,使这种探测只做一次。 ----------校对分割线--------- - -## 函数属性——Memoization模式 +## 函数属性——记忆模式(Memoization) -函数也是对象,所以它们可以有属性。事实上,函数也确实本来就有一些属性。比如,对一个函数来说,不管是用什么语法创建的,它会自动拥有一个length属性来标识这个函数期待接受的参数个数: +函数也是对象,所以它们可以有属性。事实上,函数也确实本来就有一些属性。比如,对一个函数来说,不管是用什么语法创建的,它会自动拥有一个`length`属性来标识这个函数期待接受的参数个数: function func(a, b, c) {} console.log(func.length); // 3 -任何时候都可以给函数添加自定义属性。添加自定义属性的一个有用场景是缓存函数的执行结果(返回值),这样下次同样的函数被调用的时候就不需要再做一次那些可能很复杂的计算。缓存一个函数的运行结果也就是为大家所熟知的Memoization。 +任何时候都可以给函数添加自定义属性。添加自定义属性的一个有用场景是缓存函数的执行结果(返回值),这样下次同样的函数被调用的时候就不需要再做一次那些可能很复杂的计算。缓存一个函数的运行结果也就是为大家所熟知的记忆模式。 -在下面的例子中,myFunc函数创建了一个cache属性,可以通过myFunc.cache访问到。这个cache属性是一个对象(hash表),传给函数的参数会作为对象的key,函数执行结果会作为对象的值。函数的执行结果可以是任何的复杂数据结构: +在下面的例子中,`myFunc`函数创建了一个`cache`属性,可以通过`myFunc.cache`访问到。这个`cache`属性是一个对象(hash表),传给函数的参数会作为对象的key,函数执行结果会作为对象的值。函数的执行结果可以是任何的复杂数据结构: var myFunc = function (param) { if (!myFunc.cache[param]) { var result = {}; - // ... expensive operation ... + // ……复杂的计算…… myFunc.cache[param] = result; } return myFunc.cache[param]; }; - // cache storage + // 缓存 myFunc.cache = {}; -上面的代码假设函数只接受一个参数param,并且这个参数是基本类型(比如字符串)。如果你有更多更复杂的参数,则通常需要对它们进行序列化。比如,你需要将arguments对象序列化为JSON字符串,然后使用JSON字符串作为cache对象的key: +上面的代码假设函数只接受一个参数`param`,并且这个参数是原始类型(比如字符串)。如果你有更多更复杂的参数,则通常需要对它们进行序列化。比如,你需要将`arguments`对象序列化为JSON字符串,然后使用JSON字符串作为`cache`对象的key: var myFunc = function () { @@ -706,18 +704,18 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 if (!myFunc.cache[cachekey]) { result = {}; - // ... expensive operation ... + // ……复杂的计算…… myFunc.cache[cachekey] = result; } return myFunc.cache[cachekey]; }; - // cache storage + // 缓存 myFunc.cache = {}; 需要注意的是,在序列化的过程中,对象的“标识”将会丢失。如果你有两个不同的对象,却碰巧有相同的属性,那么他们会共享同样的缓存内容。 -前面代码中的函数名还可以使用arguments.callee来替代,这样就不用将函数名硬编码。不过尽管现阶段这个办法可行,但是仍然需要注意,arguments.callee在ECMAScript 5的严格模式中是不被允许的: +前面代码中的函数名还可以使用`arguments.callee`来替代,这样就不用将函数名硬编码。不过尽管现阶段这个办法可行,但是仍然需要注意,`arguments.callee`在ECMAScript5的严格模式中是不被允许的: var myFunc = function (param) { @@ -726,16 +724,17 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 if (!f.cache[param]) { result = {}; - // ... expensive operation ... + // ……复杂的计算…… f.cache[param] = result; } return f.cache[param]; }; - // cache storage + // 缓存 myFunc.cache = {}; +---------校对分割线--------- ## 配置对象 From d62916ddb7700f36ae06d1078c621fb1239f02f4 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 19 Apr 2013 13:05:41 +0800 Subject: [PATCH 196/258] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=AF=B9=E8=B1=A1=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index a04eb68..94dc02c 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -734,15 +734,13 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 myFunc.cache = {}; ----------校对分割线--------- - ## 配置对象 -配置对象模式是一种提供更简洁的API的方法,尤其是当你正在写一个即将被其它程序调用的类库之类的代码的时候。 +配置对象模式是一种为自己的代码提供更简洁的API的方法,如果你正在写一个即将被其它程序调用的类库之类的代码的时候就特别有用。 软件在开发和维护过程中需要不断改变是一个不争的事实。这样的事情总是以一些有限的需求开始,但是随着开发的进行,越来越多的功能会不断被加进来。 -设想一下你正在写一个名为addPerson()的函数,它接受一个姓和一个名,然后在列表中加入一个人: +设想一下你正在写一个名为`addPerson()`的函数,它接受一个姓和一个名,然后在列表中加入一个人: function addPerson(first, last) {...} @@ -754,7 +752,7 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 addPerson("Bruce", "Wayne", new Date(), null, null, "batman"); -传一大串的参数真的很不方便。一个更好的办法就是将它们替换成一个参数,并且把这个参数弄成对象;我们叫它conf,是“configuration”(配置)的缩写: +这样传一大串的参数真的很不方便。一个更好的办法就是将它们替换成一个参数对象,我们叫它`conf`,是“configuration”(配置)的缩写: addPerson(conf); @@ -779,9 +777,10 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 - 需要记住参数的名字 - 参数名字不能被压缩 -举些实例,这个模式对创建DOM元素的函数或者是给元素设定CSS样式的函数会非常实用,因为元素和CSS样式可能会有很多但是大部分可选的属性。 +在实践中,这个模式对创建DOM元素的函数或者是给元素设定CSS样式的函数会非常实用,因为元素和CSS样式可能会有很多属性,但是大部分属性是可选的。 +---------校对分割线--------- ## 柯里化 (Curry) From 92ce4d17cbe8725493bf2ecadbeea03da276a768 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 19 Apr 2013 13:29:43 +0800 Subject: [PATCH 197/258] =?UTF-8?q?=E6=9F=AF=E9=87=8C=E5=8C=96=E8=83=8C?= =?UTF-8?q?=E6=99=AF=E9=83=A8=E5=88=86=E5=BA=94=E7=94=A8=20=E6=A0=A1?= =?UTF-8?q?=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 57 +++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index 94dc02c..319fd95 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -779,35 +779,31 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 在实践中,这个模式对创建DOM元素的函数或者是给元素设定CSS样式的函数会非常实用,因为元素和CSS样式可能会有很多属性,但是大部分属性是可选的。 - ----------校对分割线--------- - ## 柯里化 (Curry) -在本章剩下的部分,我们将讨论一下关于柯里化和部分应用的话题。但是在我们开始这个话题之前,先看一下到底什么是函数应用。 +在本章剩下的部分,我们将讨论一下关于柯里化和部分应用的话题。但是在我们开始这个话题之前,先看一下什么是函数应用。 - ### 函数应用 -在一些纯粹的函数式编程语言中,对函数的描述不是被调用(called或者invoked),而是被应用(applied)。在JavaScript中也有同样的东西——我们可以使用Function.prototype.apply()来应用一个函数,因为在JavaScript中,函数实际上是对象,并且他们拥有方法。 +在一些纯粹的函数式编程语言中,对函数的描述不是被调用(`called`或者`invoked`),而是被应用(`applied`)。在JavaScript中也有同样的东西——我们可以使用`Function.prototype.apply()`来应用一个函数,因为在JavaScript中,函数实际上是对象,并且他们拥有方法。 下面是一个函数应用的例子: - // define a function + // 定义函数 var sayHi = function (who) { return "Hello" + (who ? ", " + who : "") + "!"; }; - // invoke a function + // 调用函数 sayHi(); // "Hello" sayHi('world'); // "Hello, world!" - // apply a function + // 应用函数 sayHi.apply(null, ["hello"]); // "Hello, hello!" -从上面的例子中可以看出来,调用一个函数和应用一个函数有相同的结果。apply()接受两个参数:第一个是在函数内部绑定到this上的对象,第二个是一个参数数组,参数数组会在函数内部变成一个类似数组的arguments对象。如果第一个参数为null,那么this将指向全局对象,这正是当你调用一个函数(且这个函数不是某个对象的方法)时发生的事情。 +从上面的例子中可以看出来,调用一个函数和应用一个函数有相同的结果。`apply()`接受两个参数:第一个是在函数内部绑定到`this`上的对象,第二个是一个参数数组,参数数组会在函数内部变成一个类似数组的`arguments`对象。如果第一个参数为`null`,那么`this`将指向全局对象,这正是当你调用一个函数(且这个函数不是某个对象的方法)时发生的事情。 -当一个函数是一个对象的方法时,我们不再像前面的例子一样传入null。(译注:主要是为了保证方法中的this绑定到一个有效的对象而不是全局对象。)在下面的例子中,对象被作为第一个参数传给apply(): +当一个函数是一个对象的方法时,我们不再像前面的例子一样传入`null`。(译注:主要是为了保证方法中的`this`绑定到一个有效的对象而不是全局对象。)在下面的例子中,对象被作为第一个参数传给`apply()`: var alien = { sayHi: function (who) { @@ -818,68 +814,67 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 alien.sayHi('world'); // "Hello, world!" sayHi.apply(alien, ["humans"]); // "Hello, humans!" -在这个例子中,sayHi()中的this指向alien。而在上一个例子中,this是指向的全局对象。(译注:这个例子的代码有误,最后一行的sayHi并不能访问到alien的sayHi方法,需要使用alien.sayHi.apply(alien, ["humans"])才可正确运行。另外,在sayHi中也没有出现this。) +在这个例子中,`sayHi()`中的`this`指向`alien`。而在上一个例子中,`this`是指向的全局对象。(译注:这个例子的代码有误,最后一行的`sayHi`并不能访问到`alien`的`sayHi`方法,需要使用`alien.sayHi.apply(alien, ["humans"])`才可正确运行。另外,在`sayHi`中也没有出现`this`。) -正如上面两个例子所展现出来的一样,我们将所谓的函数调用当作函数应用的一种语法糖并没有什么太大的问题。 +正如上面两个例子所展现出来的一样,我们将所谓的函数调用当作函数应用的一种语法糖来理解也没有什么太大的问题。 -需要注意的是,除了apply()之外,Function.prototype对象还有一个call()方法,但是它仍然只是apply()的一种语法糖。(译注:这两个方法的区别在于,apply()只接受两个参数,第二个参数为需要传给函数的参数数组,而call()则接受任意多个参数,从第二个开始将参数依次传给函数。)不过有种情况下使用这个语法糖会更好:当你的函数只接受一个参数的时候,你可以省去为唯一的一个元素创建数组的工作: +需要注意的是,除了`apply()`之外,`Function.prototype`对象还有一个`call()`方法,但是它仍然只是`apply()`的一种语法糖。(译注:这两个方法的区别在于,`apply()`只接受两个参数,第二个参数为需要传给函数的参数数组,而`call()`则接受任意多个参数,从第二个开始将参数依次传给函数。)不过有种情况下使用这个语法糖会更好:当你的函数只接受一个参数的时候,你可以省去为唯一的一个元素创建数组的工作: - // the second is more efficient, saves an array + // 第二种更高效,因为节省了一个数组 sayHi.apply(alien, ["humans"]); // "Hello, humans!" sayHi.call(alien, "humans"); // "Hello, humans!" - ### 部分应用 现在我们知道了,调用一个函数实际上就是给它应用一堆参数,那是否能够只传一部分参数而不传全部呢?这实际上跟我们手工处理数学函数非常类似。 -假设已经有了一个add()函数,它的工作是把x和y两个数加到一起。下面的代码片段展示了当x为5、y为4时的计算步骤: +假设已经有了一个`add()`函数,它的工作是把`x`和`y`两个数加到一起。下面的代码片段展示了当`x`为5、`y`为4时的计算步骤: - // for illustration purposes - // not valid JavaScript + // 并不是合法的JavaScript代码,仅用于演示 - // we have this function + // 假设有一个add()函数 function add(x, y) { return x + y; } - // and we know the arguments + // 给定参数 add(5, 4); - // step 1 -- substitute one argument + // 第一步 传入一个参数 function add(5, y) { return 5 + y; } - // step 2 -- substitute the other argument + // 第二步 传入另一个参数 function add(5, 4) { return 5 + 4; } -在这个代码片段中,step 1和step 2并不是有效的JavaScript代码,但是它展示了我们手工计算的过程。首先获得第一个参数的值,然后将未知的x和已知的值5替换到函数中。然后重复这个过程,直到替换掉所有的参数。 +在这个代码片段中,第一步和第二步并不是有效的JavaScript代码,但是它展示了我们手工计算的过程。首先获得第一个参数的值,然后在函数中将未知的`x`值替换为5。然后重复这个过程,直到替换掉所有的参数。 -step 1是一个所谓的部分应用的例子:我们只应用了第一个参数。当你执行一个部分应用的时候并不能获得结果(或者是解决方案),取而代之的是另一个函数。 +第一步是一个所谓的部分应用的例子:我们只应用了第一个参数。当你执行一个部分应用的时候并不能获得结果(或者是解决方案),取而代之的是另一个函数。 -下面的代码片段展示了一个虚拟的partialApply()方法的用法: +下面的代码片段展示了一个虚拟的`partialApply()`方法的用法: var add = function (x, y) { return x + y; }; - // full application + // 完整应用 add.apply(null, [5, 4]); // 9 - // partial application + // 部分应用 var newadd = add.partialApply(null, [5]); - // applying an argument to the new function + // 为新函数传入一个参数 newadd.apply(null, [4]); // 9 -正如你所看到的一样,部分应用给了我们另一个函数,这个函数可以在稍后调用的时候接受其它的参数。这实际上跟add(5)(4)是等价的,因为add(5)返回了一个函数,这个函数可以使用(4)来调用。我们又一次看到,熟悉的add(5, 4)也差不多是add(5)(4)的一种语法糖。 +正如你所看到的一样,部分应用给了我们另一个函数,这个函数可以在稍后调用的时候接受其它的参数。这实际上跟`add(5)(4)`是等价的,因为`add(5)`返回了一个函数,这个函数可以使用`(4)`来调用。我们又一次看到,熟悉的`add(5, 4)`也差不多是`add(5)(4)`的一种语法糖。 -现在,让我们回到地球:并不存在这样的一个partialApply()函数,并且函数的默认表现也不会像上面的例子中那样。但是你完全可以自己去写,因为JavaScript的动态特性完全可以做到这样。 +现在,让我们回到地球:并不存在这样的一个`partialApply()`函数,并且函数的默认表现也不会像上面的例子中那样。但是你完全可以自己去写,因为JavaScript的动态特性完全可以做到这样。 让函数理解并且处理部分应用的过程,叫柯里化(Currying)。 +---------校对分割线--------- ### 柯里化(Currying) From e9b707986bc1193e3e7eb7af5af5702198f59985 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 19 Apr 2013 13:43:45 +0800 Subject: [PATCH 198/258] =?UTF-8?q?=E7=AC=AC4=E7=AB=A0=20=E6=A0=A1?= =?UTF-8?q?=E5=AF=B9=E5=AE=8C=E6=AF=95=20close=20#5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter4.markdown | 59 +++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/chapter4.markdown b/chapter4.markdown index 319fd95..f47c020 100644 --- a/chapter4.markdown +++ b/chapter4.markdown @@ -874,52 +874,48 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 让函数理解并且处理部分应用的过程,叫柯里化(Currying)。 ----------校对分割线--------- - ### 柯里化(Currying) -柯里化和辛辣的印度菜可没什么关系;它来自数学家Haskell Curry。(Haskell编程语言也是因他而得名。)柯里化是一个变换函数的过程。柯里化的另外一个名字也叫schönfinkelisation,来自另一位数学家——Moses Schönfinkelisation——这种变换的最初发明者。 +柯里化这个名字来自数学家Haskell Curry。(Haskell编程语言也是因他而得名。)柯里化是一个变换函数的过程。柯里化的另外一个名字也叫schönfinkelisation,来自另一位数学家——Moses Schönfinkelisation——这种变换的最初发明者。 -所以我们怎样对一个函数进行柯里化呢?其它的函数式编程语言也许已经原生提供了支持并且所有的函数已经默认柯里化了。在JavaScript中我们可以修改一下add()函数使它柯里化,然后支持部分应用。 +所以我们怎样对一个函数进行柯里化呢?其它的函数式编程语言也许已经原生提供了支持并且所有的函数已经默认柯里化了。在JavaScript中我们可以修改一下`add()`函数使它柯里化,然后支持部分应用。 来看一个例子: - // a curried add() - // accepts partial list of arguments + // 柯里化过的add()方法,可以接受部分参数 function add(x, y) { var oldx = x, oldy = y; - if (typeof oldy === "undefined") { // partial + if (typeof oldy === "undefined") { // 部分应用 return function (newy) { return oldx + newy; }; } - // full application + // 完整应用 return x + y; } - // test + // 测试 typeof add(5); // "function" add(3)(4); // 7 - // create and store a new function + // 创建并保存函数 var add2000 = add(2000); add2000(10); // 2010 -在这段代码中,第一次调用add()时,在返回的内层函数那里创建了一个闭包。这个闭包将原来的x和y的值存储到了oldx和oldy中。当内层函数执行的时候,oldx会被使用。如果没有部分应用,即x和y都传了值,那么这个函数会简单地将他们相加。这个add()函数的实现跟实际情况比起来有些冗余,仅仅是为了更好地说明问题。下面的代码片段中展示了一个更简洁的版本,没有oldx和oldy,因为原始的x已经被存储到了闭包中,此外我们复用了y作为本地变量,而不用像之前那样新定义一个变量newy: +在这段代码中,第一次调用`add()`时,在返回的内层函数那里创建了一个闭包。这个闭包将原来的`x`和`y`的值存储到了`oldx`和`oldy`中。当内层函数执行的时候,`oldx`会被使用。如果没有部分应用,即`x`和`y`都传了值,那么这个函数会简单地将他们相加。这个`add()`函数的实现显得有些冗余,仅仅是为了更好地说明问题。下面的代码片段中展示了一个更简洁的版本,没有`oldx`和`oldy`,因为原始的`x`已经被存储到了闭包中,此外我们复用了`y`作为本地变量,而不用像之前那样新定义一个变量`newy`: - // a curried add - // accepts partial list of arguments + // 柯里化过的add()方法,可以接受部分参数 function add(x, y) { - if (typeof y === "undefined") { // partial + if (typeof y === "undefined") { // 部分应用 return function (y) { return x + y; }; } - // full application + // 完整应用 return x + y; } -在这些例子中,add()函数自己处理了部分应用。有没有可能用一种更为通用的方式来做同样的事情呢?换句话说,我们能不能对任意一个函数进行处理,得到一个新函数,使它可以处理部分参数?下面的代码片段展示了一个通用函数的例子,我们叫它schonfinkelize(),正是用来做这个的。我们使用schonfinkelize()这个名字,一部分原因是它比较难发音,另一部分原因是它听起来比较像动词(使用“curry”则不是那么明确),而我们刚好需要一个动词来表明这是一个函数转换的过程。 +在这些例子中,`add()`函数自己处理了部分应用。有没有可能用一种更为通用的方式来做同样的事情呢?换句话说,我们能不能对任意一个函数进行处理,得到一个新函数,使它可以处理部分参数?下面的代码片段展示了一个通用函数的例子,我们叫它`schonfinkelize()`,它正是用来做这个的。我们使用`schonfinkelize()`这个名字,一部分原因是它比较难发音,另一部分原因是它听起来比较像动词(使用“curry”则不是那么明确),而我们刚好需要一个动词来表明这是一个函数转换的过程。 这是一个通用的柯里化函数: @@ -933,56 +929,53 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 }; } -这个schonfinkelize可能显得比较复杂了,只是因为在JavaScript中arguments不是一个真的数组。从Array.prototype中借用slice()方法帮助我们将arguments转换成数组,以便能更好地对它进行操作。当schonfinkelize()第一次被调用的时候,它使用slice变量存储了对slice()方法的引用,同时也存储了调用时的除去第一个之外的参数(stored\_args),因为第一个参数是要被柯里化的函数。schonfinkelize()返回了一个函数。当这个返回的函数被调用的时候,它可以(通过闭包)访问到已经存储的参数stored\_args和slice。新的函数只需要合并老的部分应用的参数(stored\_args)和新的参数(new\_args),然后将它们应用到原来的函数fn(也可以在闭包中访问到)即可。 +这个`schonfinkelize()`可能显得比较复杂了,只是因为在JavaScript中`arguments`不是一个真的数组。从`Array.prototype`中借用`slice()`方法帮助我们将`arguments`转换成数组,以便能更好地对它进行操作。当`schonfinkelize()`第一次被调用的时候,它使用`slice`变量存储了对`slice()`方法的引用,同时也存储了调用时的除去第一个之外的参数(`stored_args`),因为第一个参数是要被柯里化的函数。`schonfinkelize()`返回了一个函数,当这个返回的函数被调用的时候,它可以(通过闭包)访问到已经存储的参数`stored_args`和`slice`。新的函数只需要合并老的部分应用的参数(`stored_args`)和新的参数(`new_args`),然后将它们应用到原来的函数`fn`(也可以在闭包中访问到)即可。 现在有了通用的柯里化函数,就可以做一些测试了: - // a normal function + // 普通函数 function add(x, y) { return x + y; } - // curry a function to get a new function + // 柯里化得到新函数 var newadd = schonfinkelize(add, 5); newadd(4); // 9 - // another option -- call the new function directly + // 另一种选择 直接调用新函数 schonfinkelize(add, 6)(7); // 13 -用来做函数转换的schonfinkelize()并不局限于单个参数或者单步的柯里化。这里有些更多用法的例子: +用来做函数转换的`schonfinkelize()`并不局限于单个参数或者单步的柯里化。这里有些更多用法的例子: - // a normal function + // 普通函数 function add(a, b, c, d, e) { return a + b + c + d + e; } - // works with any number of arguments + // 参数个数可以随意分割 schonfinkelize(add, 1, 2, 3)(5, 5); // 16 - // two-step currying + // 两步柯里化 var addOne = schonfinkelize(add, 1); addOne(10, 10, 10, 10); // 41 var addSix = schonfinkelize(addOne, 2, 3); addSix(5, 5); // 16 - ### 什么时候使用柯里化 -当你发现自己在调用同样的函数并且传入的参数大部分都相同的时候,就是考虑柯里化的理想场景了。你可以通过传入一部分的参数动态地创建一个新的函数。这个新函数会存储那些重复的参数(所以你不需要再每次都传入),然后再在调用原始函数的时候将整个参数列表补全,正如原始函数期待的那样。 +当你发现自己在调用同样的函数并且传入的参数大部分都相同的时候,就是考虑柯里化的理想场景了。你可以通过传入一部分的参数动态地创建一个新的函数。这个新函数会存储那些重复的参数(所以你不需要再每次都传入),然后再在调用原始函数的时候将整个参数列表补全。 - - ##小结 -在JavaScript中,开发者对函数的理解和运用的要求是比较苛刻的。在本章中,主要讨论了有关函数的一些背景知识和术语。介绍了JavaScript函数中两个重要的特性,也就是: +在JavaScript中,对开发者在函数这个话题的理解和运用的要求是比较苛刻的。在本章中,主要讨论了有关函数的一些背景知识和术语。介绍了JavaScript函数中两个重要的特性,也就是: 1. 函数是一等对象,他们可以被作为值传递,也可以拥有属性和方法。 2. 函数拥有本地作用域,而大括号不产生块级作用域。另外需要注意的是,变量的声明会被提前到本地作用域顶部。 创建一个函数的语法有: -1. 带有名字的函数表达式 -2. 函数表达式(和上一种一样,但是没有名字),也就是为大家熟知的“匿名函数” +1. 具名函数表达式 +2. 匿名函数表达式(和上一种一样,但是没有名字),也就是为大家熟知的“匿名函数” 3. 函数声明,与其它语言的函数语法相似 在介绍完背景和函数的语法后,介绍了一些有用的模式,按分类列出: @@ -1004,14 +997,14 @@ JavaScript中的回调模式已经是我们的家常便饭了,比如,如果 - 即时函数 当它们被定义后立即执行 - - 立即初始化的对象 + - 对象即时初始化 初始化工作被放入一个匿名对象,这个对象提供一个可以立即被执行的方法 - 条件初始化 使分支代码只在初始化的时候执行一次,而不是在整个程序生命周期中反复执行 3. 性能模式,这些模式帮助提高代码的执行速度,包括: - - Memoization + - 记忆模式 利用函数的属性,使已经计算过的值不用再次计算 - 自定义函数 From a75fafe9e5e809fbdea854fd4468f03af3d97687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=92=B1=E5=BC=A0=E7=9B=9B?= Date: Sat, 20 Apr 2013 14:54:35 +0800 Subject: [PATCH 199/258] Update chapter2.markdown --- chapter2.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapter2.markdown b/chapter2.markdown index ff1decb..2f69e2d 100644 --- a/chapter2.markdown +++ b/chapter2.markdown @@ -58,7 +58,7 @@ JavaScript使用函数来管理作用域,在一个函数内定义的变量称 因此,为了让你的脚本和这个页面中的其他脚本和谐相处,要尽量少使用全局变量,这一点非常重要。本书随后的章节中会讲到一些减少全局变量的技巧和策略,比如使用命名空间或者即时函数等,但减少全局变量最有效的方法还是坚持使用`var`来声明变量。 -在JavaScript中有意无意地创建全局变量是件很容易的是,因为它有两个特性:首先,你可以不声明而直接使用变量,其次,JavaScirpt中具有“隐式全局对象”的概念,也就是说任何不通过`var`声明的变量都会成为全局对象的一个属性(可以把它们当作全局变量)。(译注:在ES6中可以通过`let`来声明块级作用域变量。)看一下下面这段代码: +在JavaScript中有意无意地创建全局变量是件很容易的事,因为它有两个特性:首先,你可以不声明而直接使用变量,其次,JavaScirpt中具有“隐式全局对象”的概念,也就是说任何不通过`var`声明的变量都会成为全局对象的一个属性(可以把它们当作全局变量)。(译注:在ES6中可以通过`let`来声明块级作用域变量。)看一下下面这段代码: function sum(x, y) { // 反模式:隐式全局变量 @@ -963,4 +963,4 @@ JSLint是基于JavaScript实现的(它自己的代码是可以通过JSLint检 - `for`循环、`for-in`循环、`switch`语句、“避免使用`eval()`”、不要扩充内置原型 - 遵守统一的编码规范(在任何必要的时候保持空格、缩进、花括号和分号)和命名规范(构造函数、普通函数和变量)。 -本章还讨论了其他一些和代码本身无关的实践,这些实践和编码过程紧密相关,包括写注释、写API文档、组织同事评审、不要试图去手动“压缩”(minify)代码而牺牲代码可读性、坚持使用JSLint来对代码进行检查。 \ No newline at end of file +本章还讨论了其他一些和代码本身无关的实践,这些实践和编码过程紧密相关,包括写注释、写API文档、组织同事评审、不要试图去手动“压缩”(minify)代码而牺牲代码可读性、坚持使用JSLint来对代码进行检查。 From 3f32b69037d8cffadd6a6df6f30fffad056fc8cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=92=B1=E5=BC=A0=E7=9B=9B?= Date: Sat, 20 Apr 2013 15:39:05 +0800 Subject: [PATCH 200/258] Update chapter2.markdown --- chapter2.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapter2.markdown b/chapter2.markdown index 2f69e2d..286674e 100644 --- a/chapter2.markdown +++ b/chapter2.markdown @@ -292,7 +292,7 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读 从技术上讲,`for-in`循环同样可以用于数组(JavaScript中数组也是对象),但不推荐这样做。当数组对象被扩充了自定义函数时,可能会产生逻辑错误。另外,`for-in`循环中属性的遍历顺序是不固定的,所以最好数组使用普通的`for`循环,对象使用`for-in`循环。 -可以使用对象的`hasOwnProperty()`方法过来从原型链中继承来的属性,这一点非常重要。看一下这段代码: +可以使用对象的`hasOwnProperty()`方法过滤来自原型链中继承来的属性,这一点非常重要。看一下这段代码: // 对象 var man = { From 9321145440da615115133ee6dba799b3d4228d3c Mon Sep 17 00:00:00 2001 From: TooBug Date: Thu, 25 Apr 2013 10:03:19 +0800 Subject: [PATCH 201/258] =?UTF-8?q?=E5=91=BD=E5=90=8D=E7=A9=BA=E9=97=B4?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=20=E4=BB=8B=E7=BB=8D=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter5.markdown | 48 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/chapter5.markdown b/chapter5.markdown index 7bc94f6..a547138 100644 --- a/chapter5.markdown +++ b/chapter5.markdown @@ -1,65 +1,65 @@ # 对象创建模式 -在JavaScript中创建对象很容易——可以通过使用对象直接量或者构造函数。本章将在此基础上介绍一些常用的对象创建模式。 +在JavaScript中创建对象是件很容易的事情,直接通过对象字面量或者构造函数就可以。本章将在此基础上介绍一些常用的对象创建模式。 -JavaScript语言本身简单、直观,通常也没有其他语言那样的语法特性:命名空间、模块、包、私有属性以及静态成员。本章将介绍一些常用的模式,以此实现这些语法特性。 +JavaScript语言本身很简单、直观,也没有其他语言的一些语言特性:命名空间、模块、包、私有属性以及静态成员。本章将介绍一些常用的模式,以此实现这些语言特性。 -我们将对命名空间、依赖声明、模块模式以及沙箱模式进行初探——它们帮助更好地组织应用程序的代码,有效地减轻全局污染的问题。除此之外,还会对包括:私有和特权成员、静态和私有静态成员、对象常量、链以及类式函数定义方式在内的话题进行讨论。 +我们将对命名空间、依赖声明、模块模式以及沙箱模式进行初探——它们可以帮助我们更好地组织应用程序的代码,有效地减少全局污染的问题。除此之外,还会讨论私有和特权成员、静态和私有静态成员、对象常量、链式调用以及一种像类式语言一样定义构造函数的方法等话题。 -## 命名空间模式(Namespace Pattern) +## 命名空间模式 -命名空间可以帮助减少全局变量的数量,与此同时,还能有效地避免命名冲突、名称前缀的滥用。 +使用命名空间可以减少全局变量的数量,与此同时,还能有效地避免命名冲突和前缀的滥用。 -JavaScript默认语法并不支持命名空间,但很容易可以实现此特性。为了避免产生全局污染,你可以为应用或者类库创建一个(通常就一个)全局对象,然后将所有的功能都添加到这个对象上,而不是到处申明大量的全局函数、全局对象以及其他全局变量。 +JavaScript没有原生的命名空间语法,但很容易可以实现这个特性。为了避免产生全局污染,你可以为应用或者类库创建一个(通常是唯一一个)全局对象,然后将所有的功能都添加到这个对象上,而不是到处声明大量的全局函数、全局对象以及其他的全局变量。 看如下例子: - // BEFORE: 5 globals - // Warning: antipattern - // constructors + // 重构前:5个全局变量 + // 注意:反模式 + // 构造函数 function Parent() {} function Child() {} - // a variable + // 一个变量 var some_var = 1; - // some objects + // 一些对象 var module1 = {}; module1.data = {a: 1, b: 2}; var module2 = {}; -可以通过创建一个全局对象(通常代表应用名)来重构上述这类代码,比方说, MYAPP,然后将上述例子中的函数和变量都变为该全局对象的属性: +可以通过创建一个全局对象(通常代表应用名)比如`MYAPP`来重构上述这类代码,然后将上述例子中的函数和变量都变为该全局对象的属性: - // AFTER: 1 global - // global object + // 重构后:一个全局变量 + // 全局对象 var MYAPP = {}; - // constructors + // 构造函数 MYAPP.Parent = function () {}; MYAPP.Child = function () {}; - // a variable + // 一个变量 MYAPP.some_var = 1; - // an object container + // 一个对象容器 MYAPP.modules = {}; - // nested objects + // 嵌套的对象 MYAPP.modules.module1 = {}; MYAPP.modules.module1.data = {a: 1, b: 2}; MYAPP.modules.module2 = {}; -这里的MYAPP就是命名空间对象,对象名可以随便取,可以是应用名、类库名、域名或者是公司名都可以。开发者经常约定全局变量都采用大写(所有字母都大写),这样可以显得比较突出(不过,要记住,一般大写的变量都用于表示常量)。 +这里的`MYAPP`就是命名空间对象,对象名可以随便取,可以是应用名、类库名、域名或者是公司名都可以。开发者经常约定全局变量都采用大写(所有字母都大写),这样可以显得比较突出(不过要记住,大写的变量也常用于表示常量)。 -这种模式是一种很好的提供命名空间的方式,避免了自身代码的命名冲突,同时还避免了同一个页面上自身代码和第三方代码(比如:JavaScript类库或者小部件)的冲突。这种模式在大多数情况下非常适用,但也有它的缺点: +这种模式是一种很好的提供命名空间的方式,避免了自身代码的命名冲突,同时还避免了同一个页面上自身代码和第三方代码(比如JavaScript类库或者widget)的冲突。这种模式在大多数情况下非常适用,但也有它的缺点: -* 代码量稍有增加;在每个函数和变量前加上这个命名空间对象的前缀,会增加代码量,增大文件大小 -* 该全局实例可以被随时修改 -* 命名的深度嵌套会减慢属性值的查询 +- 代码量稍有增加;在每个函数和变量前加上这个命名空间对象的前缀,会增加代码量,增大文件大小 +- 该全局实例可以被随时修改 +- 命名的深度嵌套会减慢属性值的查询 本章后续要介绍的沙箱模式则可以避免这些缺点。 -###通用命名空间函数 +### 通用命名空间函数 随着程序复杂度的提高,代码会分置在不同的文件中以特定顺序来加载,这样一来,就不能保证你的代码一定是第一个申明命名空间或者改变量下的属性的。甚至还会发生属性覆盖的问题。所以,在创建命名空间或者添加属性的时候,最好先检查下是否存在,如下所示: From 0b44548cc13847d184caf3c404ae4d7b0e5c7259 Mon Sep 17 00:00:00 2001 From: TooBug Date: Thu, 25 Apr 2013 10:29:48 +0800 Subject: [PATCH 202/258] =?UTF-8?q?=E5=91=BD=E5=90=8D=E7=A9=BA=E9=97=B4=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter5.markdown | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/chapter5.markdown b/chapter5.markdown index a547138..1d35ee2 100644 --- a/chapter5.markdown +++ b/chapter5.markdown @@ -58,33 +58,32 @@ JavaScript没有原生的命名空间语法,但很容易可以实现这个特 本章后续要介绍的沙箱模式则可以避免这些缺点。 - ### 通用命名空间函数 -随着程序复杂度的提高,代码会分置在不同的文件中以特定顺序来加载,这样一来,就不能保证你的代码一定是第一个申明命名空间或者改变量下的属性的。甚至还会发生属性覆盖的问题。所以,在创建命名空间或者添加属性的时候,最好先检查下是否存在,如下所示: +随着程序复杂度的提高,代码会被分拆在不同的文件中以按照页面需要来加载,这样一来,就不能保证你的代码一定是第一个定义命名空间或者某个属性的,甚至会发生属性覆盖的问题。所以,在创建命名空间或者添加属性的时候,最好先检查下是否存在,如下所示: - // unsafe + // 不安全的做法 var MYAPP = {}; - // better + // 更好的做法 if (typeof MYAPP === "undefined") { var MYAPP = {}; } - // or shorter + // 简写 var MYAPP = MYAPP || {}; -如上所示,不难看出,如果每次做类似操作都要这样检查一下就会有很多重复性的代码。比方说,要申明**MYAPP.modules.module2**,就要重复三次这样的检查。所以,我们需要一个重用的**namespace()**函数来专门处理这些检查工作,然后用它来创建命名空间,如下所示: +如上所示,如果每次做类似操作都要这样检查一下就会有很多重复的代码。例如,要声明`MYAPP.modules.module2`,就要重复三次这样的检查。所以,我们需要一个可复用的`namespace()`函数来专门处理这些检查工作,然后用它来创建命名空间,如下所示: - // using a namespace function + // 使用命名空间函数 MYAPP.namespace('MYAPP.modules.module2'); - // equivalent to: + // 等价于: // var MYAPP = { - // modules: { - // module2: {} - // } + // modules: { + // module2: {} + // } // }; -下面是上述namespace函数的实现案例。这种实现是无损的,意味着如果要创建的命名空间已经存在,则不会再重复创建: +下面是上述`namespace`函数的实现示例。这种实现是非破坏性的,意味着如果要创建的命名空间已经存在,则不会再重复创建: var MYAPP = MYAPP || {}; MYAPP.namespace = function (ns_string) { @@ -92,13 +91,14 @@ JavaScript没有原生的命名空间语法,但很容易可以实现这个特 parent = MYAPP, i; - // strip redundant leading global + // 去除不必要的全局变量层 + // 译注:因为namespace已经属于MYAPP if (parts[0] === "MYAPP") { parts = parts.slice(1); } for (i = 0; i < parts.length; i += 1) { - // create a property if it doesn't exist + // 如果属性不存在则创建它 if (typeof parent[parts[i]] === "undefined") { parent[parts[i]] = {}; } @@ -107,23 +107,23 @@ JavaScript没有原生的命名空间语法,但很容易可以实现这个特 return parent; }; -上述实现支持如下使用: +上述实现支持如下几种用法: - // assign returned value to a local var + // 将返回值赋给本地变量 var module2 = MYAPP.namespace('MYAPP.modules.module2'); module2 === MYAPP.modules.module2; // true - // skip initial `MYAPP` + // 省略全局命名空间`MYAPP` MYAPP.namespace('modules.module51'); - // long namespace + // 长命名空间 MYAPP.namespace('once.upon.a.time.there.was.this.long.nested.property'); -图5-1 展示了上述代码创建的命名空间对象在Firebug下的可视结果 +图5-1 展示了上述代码创建的命名空间对象在Firebug下的可视化结果 ![MYAPP命名空间在Firebug下的可视结果](./Figure/chapter5/5-1.jpg) -图5-1 MYAPP命名空间在Firebug下的可视结果 +图5-1 MYAPP命名空间在Firebug下的可视化结果 ## 声明依赖 From 9b698977751f0b7a60090b2ef5727a4b323c7791 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 26 Apr 2013 10:29:37 +0800 Subject: [PATCH 203/258] =?UTF-8?q?=E5=A3=B0=E6=98=8E=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=88=90=20=E7=A7=81=E6=9C=89?= =?UTF-8?q?=E6=88=90=E5=91=98=E5=AE=8C=E6=88=90=E4=B8=80=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter5.markdown | 90 +++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 46 deletions(-) diff --git a/chapter5.markdown b/chapter5.markdown index 1d35ee2..74bef9c 100644 --- a/chapter5.markdown +++ b/chapter5.markdown @@ -127,27 +127,26 @@ JavaScript没有原生的命名空间语法,但很容易可以实现这个特 ## 声明依赖 -JavaScript库往往是模块化而且有用到命名空间的,这使得你可以只使用你需要的模块。比如在YUI2中,全局变量YAHOO就是一个命名空间,各个模块作为全局变量的属性,比如YAHOO.util.Dom(DOM模块)、YAHOO.util.Event(事件模块)。 +JavaScript库往往是模块化而且有用到命名空间的,这使得你可以只使用你需要的模块。比如在YUI2中,全局变量`YAHOO`就是一个命名空间,各个模块都是全局变量的属性,比如`YAHOO.util.Dom`(DOM模块)、`YAHOO.util.Event`(事件模块)。 将你的代码依赖在函数或者模块的顶部进行声明是一个好主意。声明就是创建一个本地变量,指向你需要用到的模块: var myFunction = function () { - // dependencies + // 依赖 var event = YAHOO.util.Event, dom = YAHOO.util.Dom; - // use event and dom variables - // for the rest of the function... + // 在函数后面的代码中使用event和dom…… }; 这是一个相当简单的模式,但是有很多的好处: -- 明确的声明依赖是告知你代码的用户,需要保证指定的脚本文件被包含在页面中。 +- 明确的声明依赖是告知使用你代码的开发者,需要保证指定的脚本文件被包含在页面中。 - 将声明放在函数顶部使得依赖很容易被查找和解析。 -- 本地变量(如dom)永远会比全局变量(如YAHOO)要快,甚至比全局变量的属性(如YAHOO.util.Dom)还要快,这样会有更好的性能。使用了依赖声明模式之后,全局变量的解析在函数中只会进行一次,在此之后将会使用更快的本地变量。 -- 一些高级的代码压缩工具比如YUI Compressor和Google Closure compiler会重命名本地变量(比如event可能会被压缩成一个字母,如A),这会使代码更精简,但这个操作不会对全局变量进行,因为这样做不安全。 +- 本地变量(如`dom`)永远会比全局变量(如`YAHOO`)要快,甚至比全局变量的属性(如`YAHOO.util.Dom`)还要快,这样会有更好的性能。使用了依赖声明模式之后,全局变量的解析在函数中只会进行一次,在此之后将会使用更快的本地变量。 +- 一些高级的代码压缩工具比如YUI Compressor和Google Closure compiler会重命名本地变量(比如`event`可能会被压缩成一个字母,如`A`),这会使代码更精简,但这个操作不会对全局变量进行,因为这样做不安全。 -下面的代码片段是关于是否使用依赖声明模式对压缩影响的展示。尽管使用了依赖声明模式的test2()看起来复杂,因为需要更多的代码行数和一个额外的变量,但在压缩后它的代码量却会更小,意味着用户只需要下载更少的代码: +下面的代码片段是关于是否使用依赖声明模式对压缩影响的展示。尽管使用了依赖声明模式的`test2()`看起来复杂,因为需要更多的代码行数和一个额外的变量,但在压缩后它的代码量却会更小,意味着用户只需要下载更少的代码: function test1() { alert(MYAPP.modules.m1); @@ -156,7 +155,7 @@ JavaScript库往往是模块化而且有用到命名空间的,这使得你可 } /* - minified test1 body: + test1()压缩后的函数体: alert(MYAPP.modules.m1);alert(MYAPP.modules.m2);alert(MYAPP.modules.m51) */ @@ -168,7 +167,7 @@ JavaScript库往往是模块化而且有用到命名空间的,这使得你可 } /* - minified test2 body: + test2()压缩后的函数体: var a=MYAPP.modules;alert(a.m1);alert(a.m2);alert(a.m51) */ @@ -183,8 +182,8 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 return this.myprop; } }; - console.log(myobj.myprop); // `myprop` is publicly accessible - console.log(myobj.getProp()); // getProp() is public too + console.log(myobj.myprop); // myprop是公有的 + console.log(myobj.getProp()); // getProp()也是公有的 当你使用构造函数创建对象的时候也是一样的,所有的成员都是公有的: @@ -195,60 +194,60 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 }; } var toy = new Gadget(); - console.log(toy.name); // `name` is public - console.log(toy.stretch()); // stretch() is public + console.log(toy.name); // name是公有的 + console.log(toy.stretch()); // stretch()也是公有的 ### 私有成员 -尽管语言并没有用于私有成员的专门语法,但你可以通过闭包来实现。在构造函数中创建一个闭包,任何在这个闭包中的部分都不会暴露到构造函数之外。但是,这些私有变量却可以被公有方法访问,也就是在构造函数中定义的并且作为返回对象一部分的那些方法。我们来看一个例子,name是一个私有成员,在构造函数之外不能被访问: +尽管语言并没有用于私有成员的专门语法,但你可以通过闭包来实现。在构造函数中创建一个闭包,任何在这个闭包中的部分都不会暴露到构造函数之外。但是,这些私有变量却可以被公有方法访问,也就是在构造函数中定义的并且作为返回对象一部分的那些方法。我们来看一个例子,`name`是一个私有成员,在构造函数之外不能被访问: function Gadget() { - // private member + // 私有成员 var name = 'iPod'; - // public function + // 公有函数 this.getName = function () { return name; }; } var toy = new Gadget(); - // `name` is undefined, it's private + // name是是私有的 console.log(toy.name); // undefined - // public method has access to `name` + // 公有方法可以访问到name console.log(toy.getName()); // "iPod" 如你所见,在JavaScript创建私有成员很容易。你需要做的只是将私有成员放在一个函数中,保证它是函数的本地变量,也就是说让它在函数之外不可以被访问。 ### 特权方法 -特权方法的概念不涉及到任何语法,它只是一个给可以访问到私有成员的公有方法的名字(就像它们有更多权限一样)。 +特权方法的概念不涉及到任何语法,它只是一个给可以访问到私有成员的公有方法的名字(就好像它们有更多权限一样)。 -在前面的例子中,getName()就是一个特权方法,因为它有访问name属性的特殊权限。 +在前面的例子中,`getName()`就是一个特权方法,因为它有访问`name`属性的特殊权限。 ### 私有成员失效 当你使用私有成员时,需要考虑一些极端情况: -- 在Firefox的一些早期版本中,允许通过给eval()传递第二个参数的方法来指定上下文对象,从而允许访问函数的私有作用域。比如在Mozilla Rhino(译注:一个JavaScript引擎)中,允许使用`__parent__`来访问私有作用域。现在这些极端情况并没有被广泛应用到浏览器中。 +- 在Firefox的一些早期版本中,允许通过给`eval()`传递第二个参数的方法来指定上下文对象,从而允许访问函数的私有作用域。比如在Mozilla Rhino(译注:一个JavaScript引擎)中,允许使用`__parent__`来访问私有作用域。这些极端情况现在并没有广泛存在于浏览器中。 - 当你直接通过特权方法返回一个私有变量,而这个私有变量恰好是一个对象或者数组时,外部的代码可以修改这个私有变量,因为它是按引用传递的。 -我们来看一下第二种情况。下面的Gadget的实现看起来没有问题: +我们来看一下第二种情况。下面的`Gadget`的实现看起来没有问题: function Gadget() { - // private member + // 私有成员 var specs = { - screen_width: 320, - screen_height: 480, - color: "white" + screen_width: 320, + screen_height: 480, + color: "white" }; - // public function + // 公有函数 this.getSpecs = function () { return specs; }; } -这里的问题是getSpecs()返回了一个specs对象的引用。这使得Gadget的使用者可以修改貌似隐藏起来的私有成员specs: +这里的问题是`getSpecs()`返回了一个`specs`对象的引用。这使得`Gadget()`的使用者可以修改貌似隐藏起来的私有成员`specs`: var toy = new Gadget(), specs = toy.getSpecs(); @@ -264,25 +263,24 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 图5-2 私有对象被修改了 -这个意外的问题的解决方法就是不要将你想保持私有的对象或者数组的引用传递出去。达到这个目标的一种方法是让getSpecs()返回一个新对象,这个新对象只包含对象的使用者感兴趣的数据。这也是众所周知的“最低授权原则”(Principle of Least Authority,简称POLA),指永远不要给出比需求更多的东西。在这个例子中,如果Gadget的使用者关注它是否适应一个特定的盒子,它只需要知道尺寸即可。所以你应该创建一个getDimensions(),用它返回一个只包含width和height的新对象,而不是把什么都给出去。也就是说,也许你根本不需要实现getSpecs()方法。 +这个问题有点出乎意料,解决方法就是不要将你想保持私有的对象或者数组的引用传递出去。达到这个目标的一种方法是让`getSpecs()`返回一个新对象,这个新对象只包含对象的使用者需要的数据。这也是众所周知的“最低授权原则”(Principle of Least Authority,简称POLA),指永远不要给出比真实需要更多的东西。在这个例子中,如果`Gadget()`的使用者关注它是否适应一个特定的盒子,它只需要知道尺寸即可。所以你应该创建一个`getDimensions()`,用它返回一个只包含`width`和`height`的新对象,而不是把什么都给出去。也就是说,也许你根本不需要实现`getSpecs()`方法。 -当你需要传递所有的数据时,有另外一种方法,就是使用通用的对象复制函数创建specs对象的一个副本。下一章提供了两个这样的函数——一个叫extend(),它会浅复制一个给定的对象(只复制顶层的成员)。另一个叫extendDeep(),它会做深复制,遍历所有的属性和嵌套的属性。 +当你需要传递所有的数据时,有另外一种方法,就是使用通用的对象复制函数创建`specs`对象的一个副本。下一章提供了两个这样的函数——一个叫`extend()`,它会浅复制一个给定的对象(只复制顶层的成员),另一个叫`extendDeep()`,它会做深复制,遍历所有的属性和嵌套的属性。 ### 对象字面量和私有成员 到目前为止,我们只看了使用构建函数创建私有成员的示例。如果使用对象字面量创建对象时会是什么情况呢?是否有可能含有私有成员? -如你前面所看到的那样,私有数据使用一个函数来包裹。所以在使用对象字面量时,你也可以使用一个立即执行的匿名函数创建的闭包。例如: +如你前面所看到的那样,私有数据使用一个函数来包裹。所以在使用对象字面量时,你也可以使用一个即时函数创建的闭包。例如: - var myobj; // this will be the object + var myobj; // 一个对象 (function () { - // private members + // 私有成员 var name = "my, oh my"; - // implement the public part - // note -- no `var` + // 实现公有部分,注意没有var myobj = { - // privileged method + // 特权方法 getName: function () { return name; } @@ -294,10 +292,10 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 还有一个原理一样但看起来不一样的实现示例: var myobj = (function () { - // private members + // 私有成员 var name = "my, oh my"; - // implement the public part + // 实现公有部分 return { getName: function () { return name; @@ -313,23 +311,23 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 使用构造函数创建私有成员的一个弊端是,每一次调用构造函数创建对象时这些私有成员都会被创建一次。 -这对在构建函数中添加到`this`的成员来说是一个问题。为了避免重复劳动,节省内存,你可以将共用的属性和方法添加到构造函数的`prototype`(原型)属性中。这样的话这些公共的部分会在使用同一个构造函数创建的所有实例中共享。你也同样可以在这些实例中共享私有成员。你可以将两种模式联合起来达到这个目的:构造函数中的私有属性和对象字面量中的私有属性。因为`prototype`属性也只是一个对象,可以使用对象字面量创建。 +这对在构建函数中添加到`this`的成员来说是一个问题。为了避免重复劳动,节省内存,你可以将共用的属性和方法添加到构造函数的`prototype`(原型)属性中。这样的话这些公共的部分会在使用同一个构造函数创建的所有实例中共享。你也同样可以在这些实例中共享私有成员,甚至可以将两种模式联合起来达到这个目的,同时使用构造函数中的私有属性和对象字面量中的私有属性。因为`prototype`属性也只是一个对象,可以使用对象字面量创建。 这是一个示例: function Gadget() { - // private member + // 私有成员 var name = 'iPod'; - // public function + // 公有函数 this.getName = function () { return name; }; } Gadget.prototype = (function () { - // private member + // 私有成员 var browser = "Mobile Webkit"; - // public prototype members + // 公有函数 return { getBrowser: function () { return browser; @@ -338,8 +336,8 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 }()); var toy = new Gadget(); - console.log(toy.getName()); // privileged "own" method - console.log(toy.getBrowser()); // privileged prototype method + console.log(toy.getName()); // 自有的特权方法 + console.log(toy.getBrowser()); // 来自原型的特权方法 ### 将私有函数暴露为公有方法 From a7c20d628c211f3deefe57d8ec2c147af243ff99 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 26 Apr 2013 16:06:16 +0800 Subject: [PATCH 204/258] =?UTF-8?q?=E6=9A=B4=E9=9C=B2=E6=A8=A1=E5=BC=8F=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter5.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapter5.markdown b/chapter5.markdown index 74bef9c..1d8abbc 100644 --- a/chapter5.markdown +++ b/chapter5.markdown @@ -341,7 +341,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 ### 将私有函数暴露为公有方法 -“暴露模式”是指将已经有的私有函数暴露为公有方法。当对对象进行操作时,所有功能代码都对这些操作很敏感,而你想尽量保护这些代码的时候很有用。(译注:指对来自外部的修改很敏感。)但同时,你又希望能提供一些功能的访问权限,因为它们会被用到。如果你把这些方法公开,就会使得它们不再健壮,你的API的使用者可能修改它们。在ECMAScript5中,你可以选择冻结一个对象,但在之前的版本中不可用。下面进入暴露模式(原来是由Christian Heilmann创造的模式,叫“暴露模块模式”)。 +“暴露模式”是指将已经有的私有函数暴露为公有方法,它在你希望尽量保护对象内的一些方法不被外部修改干扰的时候很有用。你希望能提供一些功能给外部访问,因为它们会被用到,如果你把这些方法公开,就会使得它们不再健壮,因为你的API的使用者可能修改它们。在ECMAScript5中,你可以选择冻结一个对象,但在之前的版本中这种方法不可用。下面进入暴露模式(原来是由Christian Heilmann创造的模式,叫“暴露模块模式”)。 我们来看一个例子,它建立在对象字面量的私有成员模式之上: @@ -375,7 +375,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 }()); -这里有两个私有变量和两个私有函数——`isArray()`和`indexOf()`。在包裹函数的最后,使用那些允许被从外部访问的函数填充`myarray`对象。在这个例子中,同一个私有函数 `indexOf()`同时被暴露为ECMAScript 5风格的`indexOf`和PHP风格的`inArry`。测试一下myarray对象: +这里有两个私有变量(私有函数)——`isArray()`和`indexOf()`。在包裹函数的最后,用那些允许被从外部访问的函数填充`myarray`对象。在这个例子中,同一个私有函数 `indexOf()`同时被暴露为ECMAScript5风格的`indexOf()`和PHP风格的`inArry()`。测试一下`myarray`对象: myarray.isArray([1,2]); // true myarray.isArray({0: 1}); // false From 34c134d82e38f2fb2f24b07534310d2d4a24280b Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 26 Apr 2013 16:13:17 +0800 Subject: [PATCH 205/258] =?UTF-8?q?=E6=A8=A1=E5=9D=97=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E7=AC=AC=E4=B8=80=E9=83=A8=E5=88=86=20=E6=A0=A1=E5=AF=B9?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter5.markdown | 44 ++++++++++++++------------------------------ 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/chapter5.markdown b/chapter5.markdown index 1d8abbc..92e2d47 100644 --- a/chapter5.markdown +++ b/chapter5.markdown @@ -394,7 +394,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 模块模式是我们目前讨论过的好几种模式的组合,即: - 命名空间模式 -- 立即执行的函数模式 +- 即时函数模式 - 私有和特权成员模式 - 依赖声明模式 @@ -402,7 +402,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 MYAPP.namespace('MYAPP.utilities.array'); -下一步是定义模块。使用一个立即执行的函数来提供私有作用域供私有成员使用。立即执行的函数返回一个对象,也就是带有公有接口的真正的模块,可以供其它代码使用: +下一步是定义模块。使用一个即时函数来提供私有作用域供私有成员使用。即时函数返回一个对象,也就是带有公有接口的真正的模块,可以供其它代码使用: MYAPP.utilities.array = (function () { return { @@ -423,40 +423,28 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 }; }()); -如果需要的话,你可以在立即执行的函数提供的闭包中声明私有属性和私有方法。函数顶部也是声明依赖的地方。在变量声明的下方,你可以选择性地放置辅助初始化模块的一次性代码。函数最终返回的是一个包含模块公共API的对象: +如果需要的话,你可以在即时函数提供的闭包中声明私有属性和私有方法。同样,声明依赖放置在函数顶部,在变量声明的下方可以选择性地放置辅助初始化模块的一次性代码。函数最终返回的是一个包含模块公共API的对象: MYAPP.namespace('MYAPP.utilities.array'); MYAPP.utilities.array = (function () { -<<<<<<< HEAD - // dependencies + // 声明依赖 var uobj = MYAPP.utilities.object, ulang = MYAPP.utilities.lang, - // private properties + // 私有属性 array_string = "[object Array]", ops = Object.prototype.toString; -======= - // dependencies - var uobj = MYAPP.utilities.object, - ulang = MYAPP.utilities.lang, ->>>>>>> 合并改动 - // private properties - array_string = "[object Array]", - ops = Object.prototype.toString; + // 私有方法 + // …… -<<<<<<< HEAD -======= - // private methods - // ... - // end var + // 结束变量声明 ->>>>>>> 合并改动 - // optionally one-time init procedures - // ... + // 选择性放置一次性初始化的代码 + // …… - // public API + // 公有API return { inArray: function (needle, haystack) { @@ -466,19 +454,15 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 } } }, -<<<<<<< HEAD - -======= - ->>>>>>> 合并改动 + isArray: function (a) { return ops.call(a) === array_string; } - // ... more methods and properties + // ……更多的方法和属性 }; }()); -模块模式被广泛使用,这是一种值得强烈推荐的模式,它可以帮助组织代码,尤其是代码量在不断增长的时候。 +模块模式被广泛使用,是一种值得强烈推荐的模式,它可以帮助我们组织代码,尤其是代码量在不断增长的时候。 ### 暴露模块模式 From 3a7d8afdce47273672ce51e3c22ec14a38d8c0cc Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 26 Apr 2013 16:20:51 +0800 Subject: [PATCH 206/258] =?UTF-8?q?=E6=A8=A1=E5=9D=97=E6=A8=A1=E5=BC=8F=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter5.markdown | 55 ++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/chapter5.markdown b/chapter5.markdown index 92e2d47..65a97ec 100644 --- a/chapter5.markdown +++ b/chapter5.markdown @@ -472,25 +472,25 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 MYAPP.utilities.array = (function () { - // private properties + // 私有属性 var array_string = "[object Array]", ops = Object.prototype.toString, - // private methods - inArray = function (haystack, needle) { - for (var i = 0, max = haystack.length; i < max; i += 1) { - if (haystack[i] === needle) { - return i; + // 私有方法 + inArray = function (haystack, needle) { + for (var i = 0, max = haystack.length; i < max; i += 1) { + if (haystack[i] === needle) { + return i; + } } - } - return −1; - }, - isArray = function (a) { - return ops.call(a) === array_string; - }; - // end var + return −1; + }, + isArray = function (a) { + return ops.call(a) === array_string; + }; + // 结束变量定义 - // revealing public API + // 暴露公有API return { isArray: isArray, indexOf: inArray @@ -499,7 +499,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 ### 创建构造函数的模块 -前面的例子创建了一个对象`MYAPP.utilities.array`,但有时候使用构造函数来创建对象会更方便。你也可以同样使用模块模式来做。唯一的区别是包裹模块的立即执行的函数会在最后返回一个函数,而不是一个对象。 +前面的例子创建了一个对象`MYAPP.utilities.array`,但有时候使用构造函数来创建对象会更方便。你也可以同样使用模块模式来做。唯一的区别是包裹模块的即时函数会在最后返回一个函数,而不是一个对象。 看下面的模块模式的例子,创建了一个构造函数`MYAPP.utilities.Array`: @@ -507,23 +507,23 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 MYAPP.utilities.Array = (function () { - // dependencies + // 声明依赖 var uobj = MYAPP.utilities.object, ulang = MYAPP.utilities.lang, - // private properties and methods... - Constr; + // 私有属性和方法…… + Constr; - // end var + // 结束变量定义 - // optionally one-time init procedures - // ... + // 选择性放置一次性初始化代码 + // …… - // public API -- constructor + // 公有API——构造函数 Constr = function (o) { this.elements = this.toArray(o); }; - // public API -- prototype + // 公有API——原型 Constr.prototype = { constructor: MYAPP.utilities.Array, version: "2.0", @@ -535,8 +535,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 } }; - // return the constructor - // to be assigned to the new namespace + // 返回构造函数 return Constr; }()); @@ -547,13 +546,11 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 ### 在模块中引入全局上下文 -作为这种模式的一个常见的变种,你可以给包裹模块的立即执行的函数传递参数。你可以传递任何值,但通常会传递全局变量甚至是全局对象本身。引入全局上下文可以加快函数内部的全局变量的解析,因为引入之后会作为函数的本地变量: +作为这种模式的一个常见的变种,你可以给包裹模块的即时函数传递参数。你可以传递任何值,但通常情况下会传递全局变量甚至是全局对象本身。引入全局上下文可以加快函数内部的全局变量的解析,因为引入之后会作为函数的本地变量: MYAPP.utilities.module = (function (app, global) { - // references to the global object - // and to the global app namespace object - // are now localized + // 全局对象和全局命名空间都作为本地变量存在 }(MYAPP, this)); From 98ac65bd753bffba8e1503eab4e9e1672a5adce5 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 26 Apr 2013 16:31:26 +0800 Subject: [PATCH 207/258] =?UTF-8?q?=E6=B2=99=E7=AE=B1=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E4=BB=8B=E7=BB=8D=E5=92=8C=E4=BD=BF=E7=94=A8=E6=96=B9=E5=BC=8F?= =?UTF-8?q?=20=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter5.markdown | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/chapter5.markdown b/chapter5.markdown index 65a97ec..7fd31b9 100644 --- a/chapter5.markdown +++ b/chapter5.markdown @@ -558,12 +558,12 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 沙箱模式主要着眼于命名空间模式的短处,即: -- 依赖一个全局变量成为应用的全局命名空间。在命名空间模式中,没有办法在同一个页面中运行同一个应用或者类库的不同版本,在为它们都会需要同一个全局变量名,比如`MYAPP`。 +- 依赖一个全局变量成为应用的全局命名空间。在命名空间模式中,没有办法在同一个页面中运行同一个应用或者类库的不同版本,因为它们都会需要同一个全局变量名,比如`MYAPP`。 - 代码中以点分隔的名字比较长,无论写代码还是解析都需要处理这个很长的名字,比如`MYAPP.utilities.array`。 顾名思义,沙箱模式为模块提供了一个环境,模块在这个环境中的任何行为都不会影响其它的模块和其它模块的沙箱。 -这个模式在YUI3中用得很多,但是需要记住的是,下面的讨论只是一些示例实现,并不讨论YUI3中的消息箱是如何实现的。 +这个模式在YUI3中用得很多,但是需要记住的是,下面的讨论只是一些示例实现,并不讨论YUI3中的沙箱是如何实现的。 ### 全局构造函数 @@ -572,19 +572,19 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 使用沙箱模式是像这样: new Sandbox(function (box) { - // your code here... + // 你的代码…… }); `box`对象和命名空间模式中的`MYAPP`类似,它包含了所有你的代码需要用到的功能。 我们要多做两件事情: -- 通过一些手段(第3章中的强制使用new的模式),你可以在创建对象的时候不要求一定有new。 +- 通过一些手段(第3章中的强制使用`new`的模式),你可以在创建对象的时候不要求一定有`new`。 - 让`Sandbox()`构造函数可以接受一个(或多个)额外的配置参数,用于指定这个对象需要用到的模块名字。我们希望代码是模块化的,因此绝大部分`Sandbox()`提供的功能都会被包含在模块中。 有了这两个额外的特性之后,我们来看一下实例化对象的代码是什么样子。 -你可以在创建对象时省略`new`并像这样使用已有的“ajax”和“event”模块: +你可以在创建对象时省略`new`并像这样使用已有的`ajax`和`event`模块: Sandbox(['ajax', 'event'], function (box) { // console.log(box); @@ -596,7 +596,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 // console.log(box); }); -使用通配符“*”来表示“使用所有可用的模块”如何?为了方便,我们也假设没有任何模块传入时,沙箱使用“*”。所以有两种使用所有可用模块的方法: +使用通配符“*”来表示“使用所有可用的模块”是个不错的想法,为了方便,我们也假设没有任何模块传入时,沙箱使用“*”。所以有两种使用所有可用模块的方法: Sandbox('*', function (box) { // console.log(box); @@ -606,23 +606,22 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 // console.log(box); }); -下面的例子展示了如何实例化多个消息箱对象,你甚至可以将它们嵌套起来而互不影响: +下面的例子展示了如何实例化多个沙箱对象,你甚至可以将它们嵌套起来而互不影响: Sandbox('dom', 'event', function (box) { - // work with dom and event + // 使用dom和event模块 Sandbox('ajax', function (box) { - // another sandboxed "box" object - // this "box" is not the same as - // the "box" outside this function + // 另一个沙箱中的box,这个box和外面的box不一样 //... - // done with Ajax + // 使用ajax模块的代码到此为止 + }); - // no trace of Ajax module here + // 这里的代码与ajax模块无关 }); 从这些例子中看到,使用沙箱模式可以通过将代码包裹在回调函数中的方式来保护全局命名空间。 From c030d6657116c055d601efed47fc999d9fad8698 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 26 Apr 2013 16:39:52 +0800 Subject: [PATCH 208/258] =?UTF-8?q?=E6=B2=99=E7=AE=B1=E6=A8=A1=E5=BC=8F=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter5.markdown | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/chapter5.markdown b/chapter5.markdown index 7fd31b9..4a8c4ed 100644 --- a/chapter5.markdown +++ b/chapter5.markdown @@ -5,7 +5,7 @@ JavaScript语言本身很简单、直观,也没有其他语言的一些语言特性:命名空间、模块、包、私有属性以及静态成员。本章将介绍一些常用的模式,以此实现这些语言特性。 我们将对命名空间、依赖声明、模块模式以及沙箱模式进行初探——它们可以帮助我们更好地组织应用程序的代码,有效地减少全局污染的问题。除此之外,还会讨论私有和特权成员、静态和私有静态成员、对象常量、链式调用以及一种像类式语言一样定义构造函数的方法等话题。 - +/Users/TooBug/github/javascript.patterns/chapter5.markdown ## 命名空间模式 使用命名空间可以减少全局变量的数量,与此同时,还能有效地避免命名冲突和前缀的滥用。 @@ -634,7 +634,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 ### 添加模块 -在动手实现构造函数之前,我们来看一下如何添加模块。 +在动手实现构造函数之前,我们先来看一下如何添加模块。 `Sandbox()`构造函数也是一个对象,所以可以给它添加一个`modules`静态属性。这个属性也是一个包含名值(key-value)对的对象,其中key是模块的名字,value是模块的功能实现。 @@ -647,7 +647,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 }; Sandbox.modules.event = function (box) { - // access to the Sandbox prototype if needed: + // 如果有需要的话可以访问Sandbox的原型 // box.constructor.prototype.m = "mmm"; box.attachEvent = function () {}; box.dettachEvent = function () {}; @@ -658,34 +658,33 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 box.getResponse = function () {}; }; -在这个例子中我们添加了`dom`、`event`和`ajax`模块,这些都是在每个类库或者复杂的web应用中很常见的代码片段。 +在这个例子中我们添加了`dom`、`event`和`ajax`模块,这些模块在每个类库或者复杂的web应用中都很常见。 -实现每个模块功能的函数接受一个实例`box`作为参数,并给这个实例添加属性和方法。 +每个模块功能函数接受一个实例`box`作为参数,并给这个实例添加属性和方法。 ### 实现构造函数 最后,我们来实现`Sandbox()`构造函数(你可能会很自然地想将这类构造函数命名为对你的类库或者应用有意义的名字): function Sandbox() { - // turning arguments into an array + // 将参数转换为数组 var args = Array.prototype.slice.call(arguments), - // the last argument is the callback + // 最后一个参数是回调函数 callback = args.pop(), - // modules can be passed as an array or as individual parameters + // 参数可以作为数组或者单独的参数传递 modules = (args[0] && typeof args[0] === "string") ? args : args[0], i; - // make sure the function is called - // as a constructor + // 保证函数是作为构造函数被调用 if (!(this instanceof Sandbox)) { return new Sandbox(modules, callback); } - // add properties to `this` as needed: + // 根据需要给this添加属性 this.a = 1; this.b = 2; - // now add modules to the core `this` object - // no modules or "*" both mean "use all modules" + // 给this对象添加模块 + // 未指明模块或者*都表示“使用所有模块” if (!modules || modules === '*') { modules = []; for (i in Sandbox.modules) { @@ -695,16 +694,16 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 } } - // initialize the required modules + // 初始化指定的模块 for (i = 0; i < modules.length; i += 1) { Sandbox.modules[modules[i]](this); } - // call the callback + // 调用回调函数 callback(this); } - // any prototype properties as needed + // 需要添加在原型上的属性 Sandbox.prototype = { name: "My Application", version: "1.0", @@ -715,7 +714,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 这个实现中的一些关键点: -- 有一个检查`this`是否是`Sandbox`实例的过程,如果不是(也就是调用`Sandbox()`时没有加`new`),我们将这个函数作为构造函数再调用一次。 +- 有一个检查`this`是否是`Sandbox()`实例的过程,如果不是(也就是调用`Sandbox()`时没有加`new`),我们将这个函数作为构造函数再调用一次。 - 你可以在构造函数中给`this`添加属性,也可以给构造函数的原型添加属性。 - 被依赖的模块可以以数组的形式传递,也可以作为单独的参数传递,甚至以`*`通配符(或者省略)来表示加载所有可用的模块。值得注意的是,我们在这个示例实现中并没有考虑从外部文件中加载模块,但明显这是一个值得考虑的事情。比如YUI3就支持这种情况,你可以只加载最基本的模块(作为“种子”),其余需要的任何模块都通过将模块名和文件名对应的方式从外部文件中加载。 - 当我们知道依赖的模块之后就初始化它们,也就是调用实现每个模块的函数。 From ef8ce2d14f2fae74a2d253b7c6bbd338de35fc28 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 26 Apr 2013 16:57:59 +0800 Subject: [PATCH 209/258] =?UTF-8?q?=E5=85=AC=E6=9C=89=E9=9D=99=E6=80=81?= =?UTF-8?q?=E6=88=90=E5=91=98=20=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter5.markdown | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/chapter5.markdown b/chapter5.markdown index 4a8c4ed..ffca775 100644 --- a/chapter5.markdown +++ b/chapter5.markdown @@ -722,33 +722,33 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 ## 静态成员 -静态属性和方法是指那些在所有的实例中保持一致的成员。在基于类的语言中,静态成员是用专门的语法来创建,使用时就像是类自己的成员一样。比如`MathUtils`类的`max()`方法会被像这样调用:`MathUtils.max(3, 5)`。这是一个公有静态成员的示例,即可以在不实例化类的情况下使用。同样也可以有私有的静态方法,即对类的使用者不可见,而在类的所有实例间是共享的。我们来看一下如何在JavaScript中实现公有和私有静态成员。 +静态属性和方法是指那些在所有的实例中都一样的成员。在基于类的语言中,静态成员是用专门的语法来创建,使用时就像是类自己的成员一样。比如`MathUtils`类的`max()`方法会被像这样调用:`MathUtils.max(3, 5)`。这是一个公有静态成员的示例,即可以在不实例化类的情况下使用。同样也可以有私有的静态方法,即对类的使用者不可见,而在类的所有实例间是共享的。我们来看一下如何在JavaScript中实现公有和私有静态成员。 ### 公有静态成员 -在JavaScript中没有专门用于静态成员的语法。但通过给构造函数添加属性的方法,可以拥有和基于类的语言一样的使用语法。之所以可以这样做是因为构造函数和其它的函数一样,也是对象,可以拥有属性。前一章讨论过的Memoization模式也使用了同样的方法,即给函数添加属性。 +在JavaScript中没有专门用于静态成员的语法。但通过给构造函数添加属性的方法,可以拥有和基于类的语言一样的使用语法。之所以可以这样做是因为构造函数和其它的函数一样,也是对象,可以拥有属性。前一章讨论过的记忆模式也使用了同样的方法,即给函数添加属性。 -下面的例子定义了一个构造函数`Gadget`,它有一个静态方法`isShiny()`和一个实例方法`setPrice()`。`isShiny()`是一个静态方法,因为它不需要指定一个对象才能工作(就像你不需要先指定一个工具(gadget)才知道所有的工具是不是有光泽的(shiny))。但setPrice()却需要一个对象,因为工具可能有不同的定价: +下面的例子定义了一个构造函数`Gadget()`,它有一个静态方法`isShiny()`和一个实例方法`setPrice()`。`isShiny()`是一个静态方法,因为它不需要指定一个具体的对象就能工作(你不需要先拿到一个特定的小工具(gadget)才知道所有小工具是不是有光泽的(shiny))。但setPrice()却需要一个对象,因为小工具可能有不同的定价: - // constructor + // 构造函数 var Gadget = function () {}; - // a static method + // 静态方法 Gadget.isShiny = function () { return "you bet"; }; - // a normal method added to the prototype + // 添加到原型的普通方法 Gadget.prototype.setPrice = function (price) { this.price = price; }; -现在我们来调用这些方法。静态方法`isShiny()`可以直接在构造函数上调用,但其它的常规方法需要一个实例: +现在我们来调用这些方法。静态方法`isShiny()`可以直接在构造函数上调用,但其它的方法需要一个实例: - // calling a static method + // 调用静态方法 Gadget.isShiny(); // "you bet" - // creating an instance and calling a method + // 创建实例并调用方法 var iphone = new Gadget(); iphone.setPrice(500); @@ -764,28 +764,28 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 在这种情况下,你需要很小心地处理静态方法内的`this`。当你运行`Gadget.isShiny()`时,在`isShiny()`内部的`this`指向`Gadget`构造函数。而如果你运行`iphone.isShiny()`,那么`this`会指向`iphone`。 -最后一个例子展示了同一个方法被静态调用和非静态调用时明显不同的行为,这取决于调用的方式。这里的`instanceof`用于获方法是如何被调用的: +下面的例子展示了同一个方法被静态调用和非静态调用时明显不同的行为,这取决于调用的方式。这里的`instanceof`用于获取方法是如何被调用的: - // constructor + // 构造函数 var Gadget = function (price) { this.price = price; }; - // a static method + // 静态方法 Gadget.isShiny = function () { - // this always works + // 这句始终正常工作 var msg = "you bet"; if (this instanceof Gadget) { - // this only works if called non-statically + // 这句只有在非静态方式调用时正常工作 msg += ", it costs $" + this.price + '!'; } return msg; }; - // a normal method added to the prototype + // 原型上添加的方法 Gadget.prototype.isShiny = function () { return Gadget.isShiny.call(this); }; From b04ff343e19c93f13f0fc47fc1242ac75c962453 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 26 Apr 2013 17:04:49 +0800 Subject: [PATCH 210/258] =?UTF-8?q?=E7=A7=81=E6=9C=89=E9=9D=99=E6=80=81?= =?UTF-8?q?=E6=88=90=E5=91=98=20=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter5.markdown | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/chapter5.markdown b/chapter5.markdown index ffca775..9e1c1ed 100644 --- a/chapter5.markdown +++ b/chapter5.markdown @@ -806,51 +806,49 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 - 被所有由同一构造函数创建的对象共享 - 不允许在构造函数外部访问 -我们来看一个例子,`counter`是`Gadget`构造函数的一个私有静态属性。在本章中我们已经讨论过私有属性,这里的做法也是一样,需要一个函数提供的闭包来包裹私有成员。然后让这个包裹函数立即执行并返回一个新的函数。将这个返回的函数赋值给`Gadget`作为构造函数。 +我们来看一个例子,`counter`是`Gadget()`构造函数的一个私有静态属性。在本章中我们已经讨论过私有属性,这里的做法也是一样,需要一个函数提供的闭包来包裹私有成员。然后让这个包裹函数立即执行并返回一个新的函数。将这个返回的函数赋值给`Gadget()`作为构造函数。 var Gadget = (function () { - // static variable/property + // 静态变量/属性 var counter = 0; - // returning the new implementation - // of the constructor + // 返回构造函数的新实现 return function () { console.log(counter += 1); }; - }()); // execute immediately + }()); // 立即执行 -这个`Gadget`构造函数只简单地增加私有的`counter`的值然后打印出来。用多个实例测试的话你会看到`counter`在实例之间是共享的: +这个`Gadget()`构造函数只简单地增加私有变量`counter`的值然后打印出来。用多个实例测试的话你会看到`counter`在实例之间是共享的: var g1 = new Gadget();// logs 1 var g2 = new Gadget();// logs 2 var g3 = new Gadget();// logs 3 -因为我们在创建每个实例的时候`counter`的值都会加1,所以它实际上成了唯一标识使用`Gadget`构造函数创建的对象的ID。这个唯一标识可能会很有用,那为什么不把它通用一个特权方法暴露出去呢?(译注:其实这里不能叫ID,只是一个记录有多少个实例的数字而已,因为如果有多个实例被创建的话,其实已经没办法取到前面实例的标识了。)下面的例子是基于前面的例子,增加了用于访问私有静态属性的`getLastId()`方法: +因为我们在创建每个实例的时候`counter`的值都会加1,所以它实际上成了唯一标识使用`Gadget`构造函数创建的对象的ID。这个唯一标识可能会很有用,那为什么不把它通过一个特权方法暴露出去呢?(译注:严格来讲,这里不能叫ID,只是一个记录有多少个实例的数字而已,因为如果有多个实例被创建的话,没有办法取到除了最后一个之外的实例的标识。)下面的例子是基于前面的例子,增加了用于访问私有静态属性的`getLastId()`方法: - // constructor + // 构造函数 var Gadget = (function () { - // static variable/property + // 静态变量/属性 var counter = 0, NewGadget; - // this will become the - // new constructor implementation + // 这将是Gadget的新实现 NewGadget = function () { counter += 1; }; - // a privileged method + // 特权方法 NewGadget.prototype.getLastId = function () { return counter; }; - // overwrite the constructor + // 重写构造函数 return NewGadget; - }()); // execute immediately + }()); // 立即执行 测试这个新的实现: From 4f4f7f622f05255c1b4ff4cf93cc9bf042fb53db Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 26 Apr 2013 17:18:21 +0800 Subject: [PATCH 211/258] =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E5=B8=B8=E9=87=8F=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter5.markdown | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/chapter5.markdown b/chapter5.markdown index 9e1c1ed..150b50b 100644 --- a/chapter5.markdown +++ b/chapter5.markdown @@ -863,7 +863,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 ## 对象常量 -JavaScript中是没有常量的,尽管在一些比较现代的环境中可能会提供`const`来创建常量。 +在一些比较现代的环境中可能会提供`const`来创建常量,但在其它的环境中,JavaScript是没有常量的。 一种常用的解决办法是通过命名规范,让不应该变化的变量使用全大写。这个规范实际上也用在JavaScript原生对象中: @@ -873,32 +873,34 @@ JavaScript中是没有常量的,尽管在一些比较现代的环境中可能 你自己的常量也可以用这种规范,然后将它们作为静态属性加到构造函数中: - // constructor + // 构造函数 var Widget = function () { - // implementation... + // 实现…… }; - // constants + // 常量 Widget.MAX_HEIGHT = 320; Widget.MAX_WIDTH = 480; -同样的规范也适用于使用字面量创建的对象,常量会是使用大写名字的普通名字。 +同样的规范也适用于使用字面量创建的对象,常量会是使用大写名字的属性。 如果你真的希望有一个不能被改变的值,那么可以创建一个私有属性,然后提供一个取值的方法(getter),但不给赋值的方法(setter)。这种方法在很多可以用命名规范解决的情况下可能有些矫枉过正,但不失为一种选择。 -下面是一个通过的`constant`对象的实现,它提供了这些方法: +下面是一个通用的`constant`对象的实现,它提供了这些方法: - set(name, value) 定义一个新的常量 + - isDefined(name) 检查一个常量是否存在 + - get(name) 取常量的值 -在这个实现中,只允许基本类型的值成为常量。同时还要使用`hasOwnproperty()`小心地处理那些恰好是原生属性的常量名,比如`toString`或者`hasOwnProperty`,然后给所有的常量名加上一个随机生成的前缀: +在这个实现中,只允许基本类型的值成为常量。同时还要使用`hasOwnProperty()`小心地处理那些恰好是原生属性的常量名,比如`toString`或者`hasOwnProperty`,然后给所有的常量名加上一个随机生成的前缀: var constant = (function () { var constants = {}, @@ -934,19 +936,19 @@ JavaScript中是没有常量的,尽管在一些比较现代的环境中可能 测试这个实现: - // check if defined + // 检查是否定义 constant.isDefined("maxwidth"); // false - // define + // 定义 constant.set("maxwidth", 480); // true - // check again + // 再次检查 constant.isDefined("maxwidth"); // true - // attempt to redefine + // 尝试重定义 constant.set("maxwidth", 320); // false - // is the value still intact? + // 看看这个值是否被改变 constant.get("maxwidth"); // 480 ## 链式调用模式 From 0bf54df8689ad1606c60ed82e47b6439fc7345d6 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 26 Apr 2013 17:21:27 +0800 Subject: [PATCH 212/258] =?UTF-8?q?=20=E9=93=BE=E5=BC=8F=E8=B0=83=E7=94=A8?= =?UTF-8?q?=20=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter5.markdown | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chapter5.markdown b/chapter5.markdown index 150b50b..6888af2 100644 --- a/chapter5.markdown +++ b/chapter5.markdown @@ -957,7 +957,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 myobj.method1("hello").method2().method3("world").method4(); -当你创建了一个没有有意义的返回值的方法时,你可以让它返回this,也就是这些方法所属的对象。这使得对象的使用者可以将下一个方法的调用和前一次调用链起来: +当你创建了一个没有有意义的返回值的方法时,你可以让它返回`this`,也就是这些方法所属的对象。这使得对象的使用者可以将下一个方法的调用和前一次调用链起来: var obj = { value: 1, @@ -974,10 +974,10 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 } }; - // chain method calls + // 链式方法调用 obj.increment().add(3).shout(); // 5 - // as opposed to calling them one by one + // 单独调用每个方法 obj.increment(); obj.add(3); obj.shout(); // 5 @@ -988,7 +988,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 另外一个好处就是帮助你思考如何拆分你的函数,创建更小、更有针对性的函数,而不是一个什么都做的函数。长时间来看,这会提升代码的可维护性。 -一个弊端是调用这样写的代码会更困难。你可能知道一个错误出现在某一行,但这一行要做很多的事情。当链式调用的方法中的某一个出现问题而又没报错时,你无法知晓到底是哪一个出问题了。《代码整洁之道》的作者Robert Martion甚至叫这种模式为“train wreck”模式。(译注:直译为“火车事故”,指负面影响比较大。) +一个弊端是调试这样写的代码会更困难。你可能知道一个错误出现在某一行,但这一行要做很多的事情。当链式调用的方法中的某一个出现问题而又没报错时,你无法知晓到底是哪一个出问题了。《代码整洁之道》的作者Robert Martion甚至叫这种模式为“train wreck”模式。(译注:直译为“火车事故”,指负面影响比较大。) 不管怎样,认识这种模式总是好的,当你写的方法没有明显的有意义的返回值时,你就可以返回`this`。这个模式应用得很广泛,比如jQuery库。如果你去看DOM的API的话,你会发现它也会以这样的形式倾向于链式调用: From a2b8d044eca606aac85a523906a3723eef274f67 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 26 Apr 2013 17:29:07 +0800 Subject: [PATCH 213/258] =?UTF-8?q?=E7=AC=AC=E4=BA=94=E7=AB=A0=20=E6=A0=A1?= =?UTF-8?q?=E5=AF=B9=E5=AE=8C=E6=AF=95=20close=20#6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter5.markdown | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/chapter5.markdown b/chapter5.markdown index 6888af2..3844d79 100644 --- a/chapter5.markdown +++ b/chapter5.markdown @@ -125,7 +125,7 @@ JavaScript没有原生的命名空间语法,但很容易可以实现这个特 图5-1 MYAPP命名空间在Firebug下的可视化结果 -## 声明依赖 +## 依赖声明 JavaScript库往往是模块化而且有用到命名空间的,这使得你可以只使用你需要的模块。比如在YUI2中,全局变量`YAHOO`就是一个命名空间,各个模块都是全局变量的属性,比如`YAHOO.util.Dom`(DOM模块)、`YAHOO.util.Event`(事件模块)。 @@ -141,7 +141,7 @@ JavaScript库往往是模块化而且有用到命名空间的,这使得你可 这是一个相当简单的模式,但是有很多的好处: -- 明确的声明依赖是告知使用你代码的开发者,需要保证指定的脚本文件被包含在页面中。 +- 明确的依赖声明是告知使用你代码的开发者,需要保证指定的脚本文件被包含在页面中。 - 将声明放在函数顶部使得依赖很容易被查找和解析。 - 本地变量(如`dom`)永远会比全局变量(如`YAHOO`)要快,甚至比全局变量的属性(如`YAHOO.util.Dom`)还要快,这样会有更好的性能。使用了依赖声明模式之后,全局变量的解析在函数中只会进行一次,在此之后将会使用更快的本地变量。 - 一些高级的代码压缩工具比如YUI Compressor和Google Closure compiler会重命名本地变量(比如`event`可能会被压缩成一个字母,如`A`),这会使代码更精简,但这个操作不会对全局变量进行,因为这样做不安全。 @@ -423,12 +423,12 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 }; }()); -如果需要的话,你可以在即时函数提供的闭包中声明私有属性和私有方法。同样,声明依赖放置在函数顶部,在变量声明的下方可以选择性地放置辅助初始化模块的一次性代码。函数最终返回的是一个包含模块公共API的对象: +如果需要的话,你可以在即时函数提供的闭包中声明私有属性和私有方法。同样,依赖声明放置在函数顶部,在变量声明的下方可以选择性地放置辅助初始化模块的一次性代码。函数最终返回的是一个包含模块公共API的对象: MYAPP.namespace('MYAPP.utilities.array'); MYAPP.utilities.array = (function () { - // 声明依赖 + // 依赖声明 var uobj = MYAPP.utilities.object, ulang = MYAPP.utilities.lang, @@ -507,7 +507,7 @@ JavaScript不像Java或者其它语言,它没有专门的提供私有、保护 MYAPP.utilities.Array = (function () { - // 声明依赖 + // 依赖声明 var uobj = MYAPP.utilities.object, ulang = MYAPP.utilities.lang, @@ -1022,7 +1022,7 @@ JavaScript对于习惯于用类来思考的人来说可能会比较费解,这 - 新方法的名字 - 新方法的实现 -然后这个新方法被添加到`Person`“类”。新方法的实现也只是一个函数,在这个函数里面`this`指向由`Person`创建的对象,正如我们期望的那样。 +然后这个新方法被添加到`Person`“类”。新方法的实现也只是一个函数,在这个函数里面`this`指向由`Person()`创建的对象,正如我们期望的那样。 下面是使用`Person()`创建和使用新对象的代码: @@ -1041,17 +1041,14 @@ JavaScript对于习惯于用类来思考的人来说可能会比较费解,这 }; } -在`method()`的实现中,我们首先检查这个方法是否已经被实现过,如果没有则继续,将传入的参数`implementation`加到构造函数的原型中。在这里`this`指向构造函数,而我们要增加的功能正在在这个构造函数的原型上。 +在`method()`的实现中,我们首先检查这个方法是否已经被实现过,如果没有则继续,将传入的参数`implementation`加到构造函数的原型中。在这里`this`指向构造函数,而我们要增加的功能正好在这个构造函数的原型上。 ## 小结 在本章中你看到了好几种除了字面量和构造函数之外的创建对象的方法。 -你看到了使用命名空间模式来保持全局空间干净和帮助组织代码。看到了简单而又有用的依赖声明模式。然后我们详细讨论了有关私有成员的模式,包括私有成员、特权方法以及一些涉及私有成员的极端情况,还有使用对象字面量创建私有成员以及将私有方法暴露为公有方法。所有这些模式都是搭建起现在流行而强大的模块模式的积木。 +你看到了使用命名空间模式来保持全局空间干净和帮助组织代码,看到了简单而又有用的依赖声明模式。然后我们详细讨论了有关私有成员的模式,包括私有成员、特权方法以及一些涉及私有成员的极端情况,还有使用对象字面量创建私有成员以及将私有方法暴露为公有方法。所有这些模式都是搭建起现在流行而强大的模块模式的积木。 然后你看到了使用沙箱模式作为长命名空间的另一种选择,它可以为你的代码和模块提供独立的环境。 -在最后,我们深入讨论了对象常量、静态成员(公有和私有)、链式调用模式,以及神奇的`method()`方法。 - - - +在最后,我们深入讨论了对象常量、静态成员(公有和私有)、链式调用模式,以及神奇的`method()`方法。 \ No newline at end of file From 4f22d6dd973955b970593bc5c279e1c8fff9e7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=92=B1=E5=BC=A0=E7=9B=9B?= Date: Sat, 27 Apr 2013 16:58:23 +0800 Subject: [PATCH 214/258] Update chapter3.markdown --- chapter3.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapter3.markdown b/chapter3.markdown index e9963f0..68a245a 100644 --- a/chapter3.markdown +++ b/chapter3.markdown @@ -450,7 +450,7 @@ JavaScript中的正则表达式也是对象,可以通过两种方式创建它 var re = /pattern/gmi; -使用正则表达式字面量可以让代码更加简洁高效,比如当调用`String.prototype.prelace()`方法时,可以传入正则表达式参数: +使用正则表达式字面量可以让代码更加简洁高效,比如当调用`String.prototype.replace()`方法时,可以传入正则表达式参数: var no_letters = "abc123XYZ".replace(/[a-z]/gi, ""); console.log(no_letters); // 123 From 71b5bc8998e65c72344a11ea053a05bbd858eaa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=92=B1=E5=BC=A0=E7=9B=9B?= Date: Sat, 27 Apr 2013 16:59:21 +0800 Subject: [PATCH 215/258] Update chapter3.markdown --- chapter3.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapter3.markdown b/chapter3.markdown index e9963f0..68a245a 100644 --- a/chapter3.markdown +++ b/chapter3.markdown @@ -450,7 +450,7 @@ JavaScript中的正则表达式也是对象,可以通过两种方式创建它 var re = /pattern/gmi; -使用正则表达式字面量可以让代码更加简洁高效,比如当调用`String.prototype.prelace()`方法时,可以传入正则表达式参数: +使用正则表达式字面量可以让代码更加简洁高效,比如当调用`String.prototype.replace()`方法时,可以传入正则表达式参数: var no_letters = "abc123XYZ".replace(/[a-z]/gi, ""); console.log(no_letters); // 123 From 9754e336051193b1a565f196ac9ec54f0d17c8ad Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 3 May 2013 12:49:44 +0800 Subject: [PATCH 216/258] =?UTF-8?q?=E7=B1=BB=E5=BC=8F=E7=BB=A7=E6=89=BF1?= =?UTF-8?q?=20=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 54 +++++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/chapter6.markdown b/chapter6.markdown index e09321f..91fe61d 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -1,20 +1,18 @@ - # 代码复用模式 -代码复用是一个既重要又有趣的话题,因为努力在自己或者别人写的代码上写尽量少且可以复用的代码是件很自然的事情,尤其当这些代码是经过测试的、可维护的、可扩展的、有文档的时候。 +代码复用是一个既重要又有趣的话题。如果你面对自己或者别人已经写好的代码,而这些代码又是经过测试的、可维护的、可扩展的、有文档的,这时候你只想写尽量少且可以被复用的代码就是一个再自然不过的想法。 -当我们说到代码复用的时候,想到的第一件事就是继承,本章会有很大篇幅讲述这个话题。你将看到好多种方法来实现“类式(classical)”和一些其它方式的继承。但是,最最重要的事情,是你需要记住终极目标——代码复用。继承是达到这个目标的一种方法,但是不是唯一的。在本章,你将看到怎样基于其它对象来构建新对象,怎样使用混元,以及怎样在不使用继承的情况下只复用你需要的功能。 +当我们说到代码复用的时候,想到的第一件事就是继承,本章会有很大篇幅讲述这个话题,你将看到好多种方法来实现“类式(classical)”和一些其它方式的继承。但是,最最重要的事情,是你需要记住终极目标——代码复用。继承是达到这个目标的一种方法,但是不是唯一的。在本章,你将看到怎样基于其它对象来构建新对象,怎样使用混元,以及怎样在不使用继承的情况下只复用你需要的功能。 -在做代码复用的工作的时候,谨记Gang of Four 在书中给出的关于对象创建的建议:“优先使用对象创建而不是类继承”。(译注:《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)是一本设计模式的经典书籍,该书作者为Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides,被称为“Gang of Four”,简称“GoF”。) +在做代码复用的工作的时候,谨记Gang of Four在书中给出的关于对象创建的建议:“优先使用对象创建而不是类继承”。(译注:《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)是一本设计模式的经典书籍,该书作者为Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides,被称为“Gang of Four”,简称“GoF”。) - ## 类式继承 vs 现代继承模式 在讨论JavaScript的继承这个话题的时候,经常会听到“类式继承”的概念,那我们先看一下什么是类式(classical)继承。classical一词并不是来自某些古老的、固定的或者是被广泛接受的解决方案,而仅仅是来自单词“class”。(译注:classical也有“经典”的意思。) -很多编程语言都有原生的类的概念,作为对象的蓝本。在这些语言中,每个对象都是一个指定类的实例(instance),并且(以Java为例)一个对象不能在不存在对应的类的情况下存在。在JavaScript中,因为没有类,所以类的实例的概念没什么意义。JavaScript的对象仅仅是简单的键值对,这些键值对都可以动态创建或者是改变。 +很多编程语言都有原生的类的概念,以此作为对象的蓝本。在这些语言中,每个对象都是一个指定类的实例(instance),并且(以Java为例)一个对象不能在不存在对应的类的情况下存在。在JavaScript中,因为没有类,所以类的实例的概念没什么意义。JavaScript的对象仅仅是简单的键值对,这些键值对都可以动态创建或者是改变。 -但是JavaScript拥有构造函数(constructor functions),并且有语法和使用类非常相似的new运算符。 +但是JavaScript拥有构造函数(constructor functions),并且有语法和使用类非常相似的`new`运算符。 在Java中你可能会这样写: @@ -24,22 +22,21 @@ var adam = new Person(); -除了Java是强类型语言需要给adam添加类型Person外,其它的语法看起来是一样的。JavaScript的创建函数调用看起来感觉Person是一个类,但事实上,Person仅仅是一个函数。语法上的相似使得非常多的开发者陷入对JavaScript类的思考,并且给出了很多模拟类的继承方案。这样的实现方式,我们叫它“类式继承”。顺便也提一下,所谓“现代”继承模式是指那些不需要你去想类这个概念的模式。 +除了Java是强类型语言需要给`adam`添加类型`Person`外,其它的语法看起来是一样的。JavaScript的构造函数调用方式看起来让人感觉`Person()`是一个类,但事实上,`Person()`仅仅是一个函数。语法上的相似使得非常多的开发者陷入对JavaScript类的思考,并且给出了很多模拟类的继承方案。这样的实现方式,我们叫它“类式继承”。顺便也提一下,所谓“现代”继承模式是指那些不需要你去想类这个概念的模式。 当需要给项目选择一个继承模式时,有不少的备选方案。你应该尽量选择那些现代继承模式,除非团队已经觉得“无类不欢”。 本章先讨论类式继承,然后再关注现代继承模式。 - ## 类式继承的期望结果 -实现类式继承的目标是基于构造函数Child()来创建一个对象,然后从另一个构造函数Parent()获得属性。 +实现类式继承的目标是基于构造函数`Child()`来创建一个对象,然后从另一个构造函数`Parent()`获得属性。 > 尽管我们是在讨论类式继承,但还是尽量避免使用“类”这个词。“构造函数”或者“constructor”虽然更长,但是更准确,不会让人迷惑。通常情况下,应该努力避免在跟团队沟通的时候使用“类”这个词,因为在JavaScript中,很可能每个人都会有不同的理解。 -下面是定义两个构造函数Parent()和Child()的例子: +下面是定义两个构造函数`Parent()`和`Child()`的例子: - //parent构造函数 + //Parent构造函数 function Parent(name) { this.name = name || 'Adam'; } @@ -49,52 +46,50 @@ return this.name; }; - //空的child构造函数 + //空的Child构造函数 function Child(name) {} //继承 inherit(Child, Parent); -上面的代码定义了两个构造函数Parent()和Child(),say()方法被添加到了Parent()构建函数的原型(prototype)中,inherit()函数完成了继承的工作。inherit()函数并不是原生提供的,需要自己实现。让我们来看一看比较大众的实现它的几种方法。 +上面的代码定义了两个构造函数`Parent()`和`Child()`,`say()`方法被添加到了`Parent()`构建函数的原型(`prototype`)中,`inherit()`函数完成了继承的工作。`inherit()`函数并不是原生提供的,需要自己实现。让我们来看一看比较常见的实现它的几种方法。 - ## 类式继承1——默认模式 -最常用的一种模式是使用Parent()构造函数来创建一个对象,然后把这个对象设为Child()的原型。这是可复用的inherit()函数的第一种实现方法: +最常用的一种模式是使用`Parent()`构造函数来创建一个对象,然后把这个对象设为`Child()`的原型。这是可复用的`inherit()`函数的第一种实现方法: function inherit(C, P) { C.prototype = new P(); } -需要强调的是原型(prototype属性)应该指向一个对象,而不是函数,所以它需要指向由父构造函数创建的实例(对象),而不是构造函数自己。换句话说,请注意new运算符,有了它这种模式才可以正常工作。 +需要强调的是原型(`prototype`属性)应该指向一个对象,而不是函数,所以它需要指向由被继承的构造函数创建的实例(对象),而不是构造函数自己。换句话说,请注意`new`运算符,有了它这种模式才可以正常工作。 -之后在应用中使用new Child()创建对象的时候,它将通过原型拥有Parent()实例的功能,像下面的例子一样: +之后在应用中使用`new Child()`创建对象的时候,它将通过原型拥有`Parent()`实例的功能,像下面的例子一样: var kid = new Child(); kid.say(); // "Adam" - ### 跟踪原型链 -在这种模式中,子对象既继承了(父对象)“自己的属性”(添加给this的实例属性,比如name),也继承了原型中的属性和方法(比如say())。 +在这种模式中,子对象既继承了(父对象的)“自有属性”(添加给`this`的实例属性,比如`name`),也继承了原型中的属性和方法(比如`say()`)。 -我们来看一下在这种继承模式中原型链是怎么工作的。为了讨论方便,我们假设对象是内存中的一块空间,它包含数据和指向其它空间的引用。当使用new Parent()创建一个对象时,这样的一块空间就被分配了(图6-1中的2号)。它保存着name属性的数据。如果你尝试访问say()方法(比如通过(new Parent).say()),2号空间中并没有这个方法。但是在通过隐藏的链接__proto__指向Parent()构建函数的原型prototype属性时,就可以访问到包含say()方法的1号空间(Parent.prototype)了。所有的这一块都是在幕后发生的,不需要任何额外的操作,但是知道它是怎样工作的以及你正在访问或者修正的数据在哪是很重要的。注意,__proto__在这里只是为了解释原型链,这个属性在语言本身中是不可用的,尽管有一些环境提供了(比如Firefox)。 +我们来看一下在这种继承模式中原型链是怎么工作的。为了讨论方便,我们假设对象是内存中的一块空间,它包含数据和指向其它空间的引用。当使用`new Parent()`创建一个对象时,这样的一块空间就被分配了(图6-1中的2号),它保存着`name`属性的数据。如果你尝试访问`say()`方法(比如通过`(new Parent).say()`),2号空间中并没有这个方法。但是在通过隐藏的链接`__proto__`指向`Parent()`构建函数的原型`prototype`属性时,就可以访问到包含`say()`方法的1号空间(`Parent.prototype`)了。所有的这一块都是在幕后发生的,不需要任何额外的操作,但是知道它是怎样工作的有助于让你明白你正在访问或者修改的数据在哪,这是很重要的。注意,`__proto__`在这里只是为了解释原型链而存在,这个属性在语言本身中是不可用的,尽管有一些环境提供了(比如Firefox)。 ![图6-1 Parent()构造函数的原型链](./Figure/chapter6/6-1.jpg) 图6-1 Parent()构造函数的原型链 -现在我们来看一下在使用inherit()函数之后再使用var kid = new Child()创建一个新对象时会发生什么。见图6-2。 +现在我们来看一下在使用`inherit()`函数之后再使用`var kid = new Child()`创建一个新对象时会发生什么。见图6-2。 ![图6-2 继承后的原型链](./Figure/chapter6/6-2.jpg) 图6-2 继承后的原型链 -Child()构造函数是空的,也没有属性添加到Child.prototype上,这样,使用new Child()创建出来的对象都是空的,除了有隐藏的链接__proto__。在这个例子中,__proto__指向在inherit()函数中创建的new Parent()对象。 +`Child()`构造函数是空的,也没有属性添加到`Child.prototype`上,这样,使用`new Child()`创建出来的对象都是空的,除了有隐藏的链接`__proto__`。在这个例子中,`__proto__`指向在`inherit()`函数中创建的`new Parent()`对象。 -现在使用kid.say()时会发生什么?3号对象没有这个方法,所以通过原型链找到2号。2号对象也没有这个方法,所以也通过原型链找到1号,刚好有这个方法。接下来say()方法引用了this.name,这个变量也需要解析。于是沿原型链查找的过程又走了一遍。在这个例子中,this指向3号对象,它没有name属性。然后2号对象被访问,并且有name属性,值为“Adam”。 +现在使用`kid.say()`时会发生什么?3号对象没有这个方法,所以通过原型链找到2号。2号对象也没有这个方法,所以也通过原型链找到1号,刚好有这个方法。接下来`say()`方法引用了`this.name`,这个变量也需要解析,于是沿原型链查找的过程又走了一遍。在这个例子中,`this`指向3号对象,它没有`name`属性,然后2号对象被访问,并且有`name`属性,值为“Adam”。 -最后,我们多看一点东西,假如我们有如下的代码: +最后,我们看一点额外的东西,假如我们有如下的代码: var kid = new Child(); kid.name = "Patrick"; @@ -106,18 +101,17 @@ Child()构造函数是空的,也没有属性添加到Child.prototype上,这 图6-3 继承并且给子对象添加属性后的原型链 -设定kid.name并没有改变2号对象的name属性,但是它直接在3号对象上添加了自己的name属性。当kid.say()执行时,say方法在3号对象中找,然后是2号,最后到1号,像前面说的一样。但是这一次在找this.name(和kid.name一样)时很快,因为这个属性在3号对象中就被找到了。 +设定`kid.name`并没有改变2号对象的`name`属性,但是却直接在3号对象上添加了自有的`name`属性。当`kid.say()`执行时,`say()`方法会依次在3号对象中找,然后是2号,最后到1号,像前面说的一样。但是这一次在找`this.name`(和`kid.name`一样)时很快,因为这个属性在3号对象中就被找到了。 -如果通过delete kid.name的方式移除新添加的属性,那么2号对象的name属性将暴露出来并且在查找的时候被找到。 +如果通过`delete kid.name`的方式移除新添加的属性,那么2号对象的`name`属性就将被暴露出来并且在查找的时候被找到。 - ### 这种模式的缺点 -这种模式的一个缺点是既继承了(父对象)“自己的属性”,也继承了原型中的属性。大部分情况下你可能并不需要“自己的属性”,因为它们更可能是为实例对象添加的,并不用于复用。 +这种模式的一个缺点是既继承了(父对象的)“自有属性”,也继承了原型中的属性。大部分情况下你可能并不需要“自有属性”,因为它们更可能是为实例对象添加的,并不用于复用。 > 一个在构造函数上常用的规则是,用于复用的成员(译注:属性和方法)应该被添加到原型上。 -在使用这个inherit()函数时另外一个不便是它不能够让你传参数给子构造函数,这些参数有可能是想再传给父构造函数的。考虑下面的例子: +在使用这个`inherit()`函数时另外一个不便是它不能够让你传参数给子构造函数,这些参数有可能是想再传给父构造函数的。考虑下面的例子: var s = new Child('Seth'); s.say(); // "Adam" From 7501f536f62e9e2090ca5e7f58f057c2a655aeb8 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 3 May 2013 12:57:26 +0800 Subject: [PATCH 217/258] =?UTF-8?q?=E7=B1=BB=E5=BC=8F=E7=BB=A7=E6=89=BF2?= =?UTF-8?q?=20=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/chapter6.markdown b/chapter6.markdown index 91fe61d..b366a5e 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -118,18 +118,17 @@ 这并不是我们期望的结果。事实上传递参数给父构造函数是可能的,但这样需要在每次需要一个子对象时再做一次继承,很不方便,因为需要不断地创建父对象。 - ## 类式继承2——借用构造函数 -下面这种模式解决了从子对象传递参数到父对象的问题。它借用了父对象的构造函数,将子对象绑定到this,同时传入参数: +下面这种模式解决了从子对象传递参数到父对象的问题。它借用了父对象的构造函数,将子对象绑定到`this`,同时传入参数: function Child(a, c, b, d) { Parent.apply(this, arguments); } -使用这种模式时,只能继承在父对象的构造函数中添加到this的属性,不能继承原型上的成员。 +使用这种模式时,只能继承在父对象的构造函数中添加到`this`的属性,不能继承原型上的成员。 -使用借用构造函数的模式,子对象通过复制的方式继承父对象的成员,而不是像类式继承1中那样获得引用。下面的例子展示了这两者的不同: +使用借用构造函数的模式,子对象通过复制的方式继承父对象的成员,而不是像类式继承1中那样通过引用的方式。下面的例子展示了这两者的不同: //父构造函数 function Article() { @@ -153,20 +152,19 @@ alert(blog.hasOwnProperty('tags')); // false alert(page.hasOwnProperty('tags')); // true -在上面的代码片段中,Article()被两种方式分别继承。默认模式使blog可以通过原型链访问到tags属性,所以它自己并没有tags属性,hasOwnProperty()返回false。page对象有自己的tags属性,因为它是使用借用构造函数的方式继承,复制(而不是引用)了tags属性。 +在上面的代码片段中,`Article()`被用两种方式分别继承。默认模式使`blog`可以通过原型链访问到`tags`属性,所以它自己并没有`tags`属性,`hasOwnProperty()`返回`false`。`page`对象有自己的`tags`属性,因为它是使用借用构造函数的方式继承,复制(而不是引用)了`tags`属性。 -注意在修改继承后的tags属性时的不同: +注意在修改继承后的`tags`属性时的不同表现: blog.tags.push('html'); page.tags.push('php'); alert(article.tags.join(', ')); // "js, css, html" -在这个例子中,blog对象修改了tags属性,同时,它也修改了父对象,因为实际上blog.tags和article.tags是引向同一个数组。而对pages.tags的修改并不影响父对象article,因为pages.tags在继承的时候是一份独立的拷贝。 +在这个例子中,`blog`对象修改了`tags`属性,同时,它也修改了父对象,因为实际上`blog.tags`和`article.tags`是引向同一个数组。而对`pages.tags`的修改并不影响父对象`article`,因为`pages.tags`在继承的时候是一份独立的拷贝。 - ### 原型链 -我们来看一下当我们使用熟悉的Parent()和Child()构造函数和这种继承模式时原型链是什么样的。为了使用这种继承模式,Child()有明显变化: +我们来看一下当我们使用熟悉的Parent()和Child()构造函数和这种继承模式时原型链是什么样的。为了使用这种继承模式,`Child()`有明显变化: //父构造函数 function Parent(name) { @@ -187,13 +185,12 @@ kid.name; // "Patrick" typeof kid.say; // "undefined" -如果看一下图6-4,就能发现new Child对象和Parent之间不再有链接。这是因为Child.prototype根本就没有被使用,它指向一个空对象。使用这种模式,kid拥有了自己的name属性,但是并没有继承say()方法,如果尝试调用它的话会出错。这种继承方式只是一种一次性地将父对象的属性复制为子对象的属性,并没有__proto__链接。 +如果看一下图6-4,就能发现`new Child()`对象和`Parent()`之间不再有链接。这是因为`Child.prototype`根本就没有被使用,它指向一个空对象。使用这种模式,`kid`拥有了自有的`name`属性,但是并没有继承`say()`方法,如果尝试调用它的话会出错。这种继承方式只是一种一次性地将父对象的属性复制为子对象的属性,并没有`__proto__`链接。 ![图6-4 使用借用构造函数模式时没有被关联的原型链](./Figure/chapter6/6-4.jpg) 图6-4 使用借用构造函数模式时没有被关联的原型链 - ### 利用借用构造函数模式实现多继承 使用借用构造函数模式,可以通过借用多个构造函数的方式来实现多继承: @@ -224,14 +221,13 @@ 图6-5 在Firebug中查看CatWings对象 - ### 借用构造函数的利与弊 这种模式的一个明显的弊端就是无法继承原型。如前面所说,原型往往是添加可复用的方法和属性的地方,这样就不用在每个实例中再创建一遍。 -这种模式的一个好处是获得了父对象自己成员的拷贝,不存在子对象意外改写父对象属性的风险。 +这种模式的一个好处是获得了父对象自有成员的拷贝,不存在子对象意外改写父对象属性的风险。 -那么,在上一个例子中,怎样使一个子对象也能够继承原型属性呢?怎样能使kid可以访问到say()方法呢?下一种继承模式解决了这个问题。 +那么,在上一个例子中,怎样使一个子对象也能够继承原型属性呢?怎样能使`kid`可以访问到`say()`方法呢?下一种继承模式解决了这个问题。 ## 类式继承3——借用并设置原型 From da31a822c6cc11977fa1caa6498f0265153f7a5d Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 3 May 2013 13:00:44 +0800 Subject: [PATCH 218/258] =?UTF-8?q?=E7=B1=BB=E5=BC=8F=E7=BB=A7=E6=89=BF3?= =?UTF-8?q?=20=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/chapter6.markdown b/chapter6.markdown index b366a5e..51977aa 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -229,7 +229,6 @@ 那么,在上一个例子中,怎样使一个子对象也能够继承原型属性呢?怎样能使`kid`可以访问到`say()`方法呢?下一种继承模式解决了这个问题。 - ## 类式继承3——借用并设置原型 综合以上两种模式,首先借用父对象的构造函数,然后将子对象的原型设置为父对象的一个新实例: @@ -239,9 +238,9 @@ } Child.prototype = new Parent(); -这样做的好处是子对象获得了父对象自己的成员,也获得了父对象中可复用的(在原型中实现的)方法。子对象也可以传递任何参数给父构造函数。这种行为可能是最接近Java的,子对象继承了父对象的所有东西,同时可以安全地修改自己的属性而不用担心修改到父对象。 +这样做的好处是子对象获得了父对象的自有成员,也获得了父对象中可复用的(在原型中实现的)方法。子对象也可以传递任何参数给父构造函数。这种行为可能是最接近Java的,子对象继承了父对象的所有东西,同时可以安全地修改自己的属性而不用担心修改到父对象。 -一个弊端是父构造函数被调用了两次,所以不是很高效。最后,(父对象)自己的属性(比如这个例子中的name)也被继承了两次。 +一个弊端是父构造函数被调用了两次,所以不是很高效。最后,(父对象的)自有属性(比如这个例子中的`name`)也被继承了两次。 我们来看一下代码并做一些测试: @@ -267,7 +266,7 @@ delete kid.name; kid.say(); // "Adam" -跟前一种模式不一样,现在say()方法被正确地继承了。可以看到name也被继承了两次,在删除掉自己的拷贝后,在原型链上的另一个就被暴露出来了。 +跟前一种模式不一样,现在`say()`方法被正确地继承了。可以看到`name`也被继承了两次,在删除掉自己的拷贝后,在原型链上的另一个就被暴露出来了。 图6-6展示了这些对象之间的关系。这些关系有点像图6-3中展示的,但是获得这种关系的方法是不一样的。 @@ -275,7 +274,6 @@ 图6-6 除了继承“自己的属性”外,原型链也被保留了 - ## 类式继承4——共享原型 不像前一种类式继承模式需要调用两次父构造函数,下面这种模式根本不会涉及到调用父构造函数的问题。 From 876cc424ae7825dafb9b93cc3ca006e424192c70 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 3 May 2013 13:03:54 +0800 Subject: [PATCH 219/258] =?UTF-8?q?=E7=B1=BB=E5=BC=8F=E7=BB=A7=E6=89=BF4?= =?UTF-8?q?=20=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chapter6.markdown b/chapter6.markdown index 51977aa..00b436d 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -278,15 +278,15 @@ 不像前一种类式继承模式需要调用两次父构造函数,下面这种模式根本不会涉及到调用父构造函数的问题。 -一般的经验是将可复用的成员放入原型中而不是this。从继承的角度来看,则是任何应该被继承的成员都应该放入原型中。这样你只需要设定子对象的原型和父对象的原型一样即可: +一般的经验是将可复用的成员放入原型中而不是`this`。从继承的角度来看,则是任何应该被继承的成员都应该放入原型中。这样你只需要设定子对象的原型和父对象的原型一样即可: function inherit(C, P) { C.prototype = P.prototype; } -这种模式的原型链很短并且查找很快,因为所有的对象实际上共享着同一个原型。但是这样也有弊端,那就是如果子对象或者在继承关系中的某个地方的任何一个子对象修改这个原型,将影响所有的继承关系中的父对象。(译注:这里应该是指会影响到所有从这个原型中继承的对象。) +这种模式的原型链很短并且查找很快,因为所有的对象实际上共享着同一个原型。但是这样也有弊端,那就是如果子对象或者在继承关系中的某个地方的任何一个子对象修改这个原型,将影响所有的继承关系中的父对象。(译注:指会影响到所有从这个原型中继承的对象所依赖的共享原型上的成员。) -如图6-7,子对象和父对象共享同一个原型,都可以访问say()方法。但是,子对象不继承name属性。 +如图6-7,子对象和父对象共享同一个原型,都可以访问`say()`方法。但是,子对象不继承`name`属性。 ![图6-7 (父子对象)共享原型时的关系](./Figure/chapter6/6-7.jpg) From 431361581a71b820f3b7eb20380d62a74aec3125 Mon Sep 17 00:00:00 2001 From: TooBug Date: Fri, 3 May 2013 13:16:12 +0800 Subject: [PATCH 220/258] =?UTF-8?q?=E7=B1=BB=E5=BC=8F=E7=BB=A7=E6=89=BF?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/chapter6.markdown b/chapter6.markdown index 00b436d..3042d02 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -292,12 +292,11 @@ 图6-7 (父子对象)共享原型时的关系 - ## 类式继承5——临时构造函数 下一种模式通过打断父对象和子对象原型的直接链接解决了共享原型时的问题,同时还从原型链中获得其它的好处。 -下面是这种模式的一种实现方式,F()函数是一个空函数,它充当了子对象和父对象的代理。F()的prototype属性指向父对象的原型。子对象的原型是一这个空函数的一个实例: +下面是这种模式的一种实现方式,`F()`函数是一个空函数,它充当了子对象和父对象的代理。`F()`的`prototype`属性指向父对象的原型。子对象的原型是这个空函数的一个实例: function inherit(C, P) { var F = function () {}; @@ -311,15 +310,14 @@ 图6-8 使用临时(代理)构造函数F()实现类式继承 -这种模式通常情况下都是一种很棒的选择,因为原型本来就是存放复用成员的地方。在这种模式中,父构造函数添加到this中的任何成员都不会被继承。 +这种模式通常情况下都是一种很棒的选择,因为原型本来就是存放复用成员的地方。在这种模式中,父构造函数添加到`this`中的任何成员都不会被继承。 我们来创建一个子对象并且检查一下它的行为: var kid = new Child(); -如果你访问kid.name将得到undefined。在这个例子中,name是父对象自己的属性,而在继承的过程中我们并没有调用new Parent(),所以这个属性并没有被创建。当访问kid.say()时,它在3号对象中不可用,所以在原型链中查找,4号对象也没有,但是1号对象有,它在内在中的位置会被所有从Parent()创建的构造函数和子对象所共享。 +如果你访问`kid.name`将得到`undefined`。在这个例子中,`name`是父对象自己的属性,而在继承的过程中我们并没有调用`new Parent()`,所以这个属性并没有被创建。当访问`kid.say()`时,它在3号对象中不可用,所以在原型链中查找,4号对象也没有,但是1号对象有,它在内存中的位置会被所有从`Parent()`创建的构造函数和子对象所共享。 - ### 存储父类(Superclass) 在上一种模式的基础上,还可以添加一个指向原始父对象的引用。这很像其它语言中访问超类(superclass)的情况,有时候很方便。 @@ -333,24 +331,23 @@ C.uber = P.prototype; } - ### 重置构造函数引用 -这个近乎完美的模式上还需要做的最后一件事情就是重置构造函数(constructor)的指向,以便未来在某个时刻能被正确地使用。 +这个近乎完美的模式上还需要做的最后一件事情就是重置构造函数(`constructor`)的指向,以便未来在某个时刻能被正确地使用。 -如果不重置构造函数的指向,那所有的子对象都会认为Parent()是它们的构造函数,而这个结果完全没有用。使用前面的inherit()的实现,你可以观察到这种行为: +如果不重置构造函数的指向,那所有的子对象都会认为`Parent()`是它们的构造函数,而这个结果完全没有用。使用前面的`inherit()`的实现,你可以观察到这种行为: - // parent, child, inheritance + // Parent,Child,实现继承 function Parent() {} function Child() {} inherit(Child, Parent); - // testing the waters + // 测试 var kid = new Child(); kid.constructor.name; // "Parent" kid.constructor === Parent; // true -constructor属性很少用,但是在运行时检查对象很方便。你可以重新将它指向期望的构造函数而不影响功能,因为这个属性更多是“信息性”的。(译注:即它更多的时候是在提供信息而不是参与到函数功能中。) +`constructor`属性很少被用到,但是在运行时检查对象很方便。你可以重新将它指向期望的构造函数而不影响功能,因为这个属性更多是“信息性”的。(译注:即它更多的时候是在提供信息而不是参与到函数功能中。) 最终,这种类式继承的Holy Grail版本看起来是这样的: @@ -366,7 +363,7 @@ constructor属性很少用,但是在运行时检查对象很方便。你可以 > “代理函数”或者“代理构造函数”也是指这种模式,因为临时构造函数是被用作获取父构造函数原型的代理。 -一种常见的对Holy Grail模式的优化是避免每次需要继承的时候都创建一个临时(代理)构造函数。事实上创建一次就足够了,以后只需要修改它的原型即可。你可以用一个立即执行的函数来将代理函数存储到闭包中: +一种常见的对Holy Grail模式的优化是避免每次需要继承的时候都创建一个临时(代理)构造函数。事实上创建一次就足够了,以后只需要修改它的原型即可。你可以用一个即时函数来将代理函数存储到闭包中: var inherit = (function () { var F = function () {}; @@ -378,7 +375,6 @@ constructor属性很少用,但是在运行时检查对象很方便。你可以 } }()); - ## Klass 有很多JavaScript类库模拟了类,创造了新的语法糖。具体的实现方式可能会不一样,但是基本上都有一些共性,包括: From 9bf28bae8073671c6ce28c27470610b89b974af1 Mon Sep 17 00:00:00 2001 From: TooBug Date: Sat, 4 May 2013 17:02:47 +0800 Subject: [PATCH 221/258] =?UTF-8?q?Klass=20=E6=A0=A1=E5=AF=B9=E5=AE=8C?= =?UTF-8?q?=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 43 ++++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/chapter6.markdown b/chapter6.markdown index 3042d02..dab19c2 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -377,15 +377,15 @@ ## Klass -有很多JavaScript类库模拟了类,创造了新的语法糖。具体的实现方式可能会不一样,但是基本上都有一些共性,包括: +有很多JavaScript类库模拟了类,创造了新的语法糖。这些类库具体的实现方式可能会不一样,但是基本上都有一些共性,包括: -- 有一个约定好名字的方法,如initialize、_init或者其它相似的名字,会被自动调用,来充当类的构造函数。 +- 有一个约定好的方法,如`initialize`、`_init`或者其它相似的名字,会被自动调用,来充当类的构造函数 - 类可以从其它类继承 - 在子类中可以访问到父类(superclass) -> 我们在这里做一下变化,在本章的这部分自由地使用“class”单词,因为主题就是模拟类。 +> 我们在这里做一点变化,在本章的这部分自由地使用“class”这个词,因为主题就是模拟类。 -为避免讨论太多细节,我们来看一下JavaScript中一种模拟类的实现。首先,这种解决方案从客户的角度来看将如何被使用? +为避免讨论太多细节,我们来看一下JavaScript中一种模拟类的实现。首先,看一下这种方案将如何被使用? var Man = klass(null, { __construct: function (what) { @@ -397,14 +397,14 @@ } }); -这种语法糖的形式是一个名为klass()的函数。在一些实现方式中,它可能是Klass()构造函数或者是增强的Object.prototype,但是在这个例子中,我们让它只是一个简单的函数。 +这种语法糖的形式是一个名为`klass()`的函数。在一些其它的实现方式中,它可能是`Klass()`构造函数或者是增强的`Object.prototype`,但是在这个例子中,我们让它只是一个简单的函数。 -这个函数接受两个参数:一个被继承的类和通过对象字面量提供的新类的实现。受PHP的影响,我们约定类的构造函数必须是一个名为\_\_construct的方法。在前面的代码片段中,建立了一个名为Man的新类,并且它不继承任何类(意味着继承自Object)。Man类有一个在\_\_construct建立的自己的属性name和一个方法getName()。这个类是一个构造函数,所以下面的代码将正常工作(并且看起来像类实例化的过程): +这个函数接受两个参数:一个被继承的类和通过对象字面量提供的新类的实现。受PHP的影响,我们约定类的构造函数必须是一个名为`__construct()`的方法。在前面的代码片段中,建立了一个名为`Man`的新类,并且它不继承任何类(意味着继承自`Object`)。`Man`类有一个在`__construct()`建立的自有属性`name`和一个方法`getName()`。这个类是一个构造函数,所以下面的代码将正常工作(并且看起来像类实例化的过程): var first = new Man('Adam'); // logs "Man's constructor" first.getName(); // "Adam" -现在我们来扩展这个类,创建一个SuperMan类: +现在我们来扩展这个类,创建一个`SuperMan`类: var SuperMan = klass(Man, { __construct: function (what) { @@ -416,26 +416,25 @@ } }); -这里,klass()的第一个参数是将被继承的Man类。值得注意的是,在getName()中,父类的getName()方法首先通过SuperMan类的uber静态属性被调用。我们来测试一下: +这里,`klass()`的第一个参数是将被继承的`Man`类。值得注意的是,在`getName()`中,父类的`getName()`方法首先通过`SuperMan`类的`uber`静态属性被调用。我们来测试一下: var clark = new SuperMan('Clark Kent'); clark.getName(); // "I am Clark Kent" -第一行在console中记录了“Man's constructor”,然后是“Superman's constructor”。在一些语言中,父类的构造函数在子类构造函数被调用的时候会自动执行,这个特性也可以模拟。 +第一行在console中记录了“Man's constructor”,然后是“Superman's constructor”,在一些语言中,父类的构造函数在子类构造函数被调用的时候会自动执行,这个特性也被模拟了。 -用instanceof运算符测试返回希望的结果: +用`instanceof`运算符测试返回希望的结果: clark instanceof Man; // true clark instanceof SuperMan; // true -最后,我们来看一下klass()函数是怎样实现的: +最后,我们来看一下`klass()`函数是怎样实现的: var klass = function (Parent, props) { var Child, F, i; - // 1. - // new constructor + // 1. 构造函数 Child = function () { if (Child.uber && Child.uber.hasOwnProperty("__construct")) { Child.uber.__construct.apply(this, arguments); @@ -445,8 +444,7 @@ } }; - // 2. - // inherit + // 2. 继承 Parent = Parent || Object; F = function () {}; F.prototype = Parent.prototype; @@ -454,25 +452,24 @@ Child.uber = Parent.prototype; Child.prototype.constructor = Child; - // 3. - // add implementation methods + // 3. 添加方法实现 for (i in props) { if (props.hasOwnProperty(i)) { Child.prototype[i] = props[i]; } } - // return the "class" + // 返回“类” return Child; }; -这个klass()实现有三个明显的部分: +这个`klass()`实现有三个明显的部分: -1. 创建Child()构造函数,这也是最后返回的将被作为类使用的函数。在这个函数里面,如果\_\_construct方法存在的话将被调用。同样是在父类的\_\_construct(如果存在)被调用前使用静态的uber属性。也可能存在uber没有定义的情况——比如从Object继承,因为它是在Man类中被定义的。 -2. 第二部分主要完成继承。只是简单地使用前面章节讨论过的Holy Grail类式继承模式。只有一个东西是新的:如果Parent没有传值的话,设定Parent为Object。 -3. 最后一部分是类真正定义的地方,循环需要实现的方法(如例子中的\_\_constructt和getName),并将它们添加到Child的原型中。 +1. 创建`Child()`构造函数,这也是最后返回的将被作为类使用的函数。在这个函数里面,如果`__construct()`方法存在的话将被调用,同样,如果父类的`__construct()`存在,也将被调用(通过使用静态属性`uber`)。也可能存在`uber`没有定义的情况——比如从`Object`继承,前例中`Man`类即是如此。 +2. 第二部分主要完成继承。只是简单地使用前面章节讨论过的Holy Grail类式继承模式。只有一个东西是新的:如果`Parent`没有传值的话,设定`Parent`为`Object`。 +3. 最后一部分是真正定义类的地方,遍历需要实现的方法(如例子中的`__constructor()`和`getName()`),并将它们添加到`Child()`的原型中。 -什么时候使用这种模式?其实,最好是能避免则避免,因为它带来了在这门语言中不存在的完整的类的概念,会让人疑惑。使用它需要学习新的语法和新的规则。也就是说,如果你或者你的团队对类感到习惯并且同时对原型感到不习惯,这种模式可能是一个可以探索的方向。这种模式允许你完全忘掉原型,好处就是你可以将语法变种得像其它你所喜欢的语言一样。 +什么时候使用这种模式呢?其实,最好是能避免则避免,因为它带来了在这门语言中不存在的完整的类的概念,会让人疑惑。使用它需要学习新的语法和新的规则,也就是说,如果你或者你的团队习惯于使用类并且对原型感到不习惯,这种模式可能是一个可以探索的方向。这种模式允许你完全忘掉原型,好处就是你可以使用像其它语言那样的(变种)语法。 ## 原型继承 From 3cc7f8d78197ab9a622f5c277e5aa839b0d6a255 Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 5 May 2013 22:44:28 +0800 Subject: [PATCH 222/258] =?UTF-8?q?=E5=8E=9F=E5=9E=8B=E7=BB=A7=E6=89=BF=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 56 ++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/chapter6.markdown b/chapter6.markdown index dab19c2..1714952 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -471,25 +471,24 @@ 什么时候使用这种模式呢?其实,最好是能避免则避免,因为它带来了在这门语言中不存在的完整的类的概念,会让人疑惑。使用它需要学习新的语法和新的规则,也就是说,如果你或者你的团队习惯于使用类并且对原型感到不习惯,这种模式可能是一个可以探索的方向。这种模式允许你完全忘掉原型,好处就是你可以使用像其它语言那样的(变种)语法。 - ## 原型继承 -现在,让我们从一个叫作“原型继承”的模式来讨论没有类的现代继承模式。在这种模式中,没有任何类进来,在这里,一个对象继承自另外一个对象。你可以这样理解它:你有一个想复用的对象,然后你想创建第二个对象,并且获得第一个对象的功能。下面是这种模式的用法: +现在,让我们从一个叫作“原型继承”的模式来讨论没有类的现代继承模式。在这种模式中,没有任何类牵涉进来,一个对象继承自另外一个对象。你可以这样理解它:你有一个想复用的对象,然后你想创建第二个对象,并且获得第一个对象的功能。下面是这种模式的用法: - //需要继承的对象 + // 需要继承的对象 var parent = { name: "Papa" }; - //新对象 + // 新对象 var child = object(parent); - //测试 + // 测试 alert(child.name); // "Papa" -在这个代码片段中,有一个已经存在的使用对象字面量创建的对象叫parent,我们想创建一个和parent有相同的属性和方法的对象叫child。child对象使用object()函数创建。这个函数在JavaScript中并不存在(不要与构造函数Object()混淆),所以我们来看看怎样定义它。 +在这个代码片段中,有一个已经存在的使用对象字面量创建的对象叫`parent`,我们想创建一个和`parent`有相同的属性和方法的对象叫`child`。`child`对象使用`object()`函数创建。这个函数在JavaScript中并不存在(不要与构造函数`Object()`混淆),所以我们来看看怎样定义它。 -与Holy Grail类式继承相似,可以使用一个空的临时构造函数F(),然后设定F()的原型为parent对象。最后,返回一个临时构造函数的新实例。 +与Holy Grail类式继承相似,可以使用一个空的临时构造函数`F()`,然后设定`F()`的原型为`parent`对象。最后,返回一个临时构造函数的新实例。 function object(o) { function F() {} @@ -497,69 +496,66 @@ return new F(); } -图6-9展示了使用原型继承时的原型链。在这里child总是以一个空对象开始,它没有自己的属性但通过原型链(\_\_proto\_\_)拥有父对象的所有功能。 +图6-9展示了使用原型继承时的原型链。这样创建的`child`总是一个空对象,它没有自有属性但通过原型链(`__proto__`)拥有父对象的所有功能。 ![图6-9 原型继承模式](./Figure/chapter6/6-9.jpg) 图6-9 原型继承模式 - ### 讨论 -在原型继承模式中,parent不需要使用对象字面量来创建。(尽管这是一种更常用的方式。)可以使用构造函数来创建parent。注意,如果你这样做,那么自己的属性和原型上的属性都将被继承: +在原型继承模式中,`parent`不一定需要使用对象字面量来创建(尽管这是一种常用的方式),也可以使用构造函数来创建。注意,如果你这样做,那么自有属性和原型上的属性都将被继承: - // parent constructor + // 父构造函数 function Person() { - // an "own" property + // 自有属性 this.name = "Adam"; } - // a property added to the prototype + // 原型上的属性 Person.prototype.getName = function () { return this.name; }; - // create a new person + // 使用Person()创建一个新对象 var papa = new Person(); - // inherit + // 继承 var kid = object(papa); - // test that both the own property - // and the prototype property were inherited + // 测试:自有属性和原型上的属性都被继承了 kid.getName(); // "Adam" -在这种模式的另一个变种中,你可以选择只继承已存在的构造函数的原型对象。记住,对象继承自对象,不管父对象是怎么创建的。这是前面例子的一个修改版本: +也可以使用这种模式的一个变种,只继承已存在的构造函数的原型对象。记住,对象继承自对象,而不管父对象是怎么创建的。这是前面例子的一个修改版本: - // parent constructor + // 父构造函数 function Person() { - // an "own" property + // 自有属性 this.name = "Adam"; } - // a property added to the prototype + // 原型上的属性 Person.prototype.getName = function () { }; - // inherit + // 继承 var kid = object(Person.prototype); - typeof kid.getName; // "function", because it was in the prototype - typeof kid.name; // "undefined", because only the prototype was inherited + typeof kid.getName; // "function",因为它在原型中 + typeof kid.name; // "undefined",因为只有原型中的成员被继承了 - -###例外的ECMAScript 5 +### ECMAScript5中的原型继承 -在ECMAScript 5中,原型继承已经正式成为语言的一部分。这种模式使用Object.create方法来实现。换句话说,你不再需要自己去写类似object()的函数,它是语言原生的了: +在ECMAScript5中,原型继承已经正式成为语言的一部分。这种模式使用`Object.create()`方法来实现。换句话说,你不再需要自己去写类似`object()`的函数,它是语言原生的部分了: var child = Object.create(parent); -Object.create()接收一个额外的参数——一个对象。这个额外对象中的属性将被作为自己的属性添加到返回的子对象中。这让我们可以很方便地将继承和创建子对象在一个方法调用中实现。例如: +`Object.create()`接收一个额外的参数——一个对象。这个额外对象中的属性将被作为自有属性添加到返回的子对象中。这让我们可以很方便地将继承和创建子对象在一个方法调用中实现。例如: var child = Object.create(parent, { - age: { value: 2 } // ECMA5 descriptor + age: { value: 2 } // ES5中的属性描述符 }); child.hasOwnProperty("age"); // true -你可能也会发现原型继承模式已经在一些JavaScript类库中实现了,比如,在YUI3中,它是Y.Object()方法: +你可能也会发现原型继承模式已经在一些JavaScript类库中实现了,比如,在YUI3中,它是`Y.Object()`方法: YUI().use('*', function (Y) { var child = Y.Object(parent); From 22df729b57192d457d968b9cb01ce5766dd34a7b Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 5 May 2013 22:51:30 +0800 Subject: [PATCH 223/258] =?UTF-8?q?=E9=80=9A=E8=BF=87=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E7=BB=A7=E6=89=BF=20=E5=92=8C=20=E6=B7=B7?= =?UTF-8?q?=E5=85=83=20=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/chapter6.markdown b/chapter6.markdown index 1714952..86cfeae 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -561,10 +561,9 @@ var child = Y.Object(parent); }); - ## 通过复制属性继承 -让我们来看一下另外一种继承模式——通过复制属性继承。在这种模式中,一个对象通过简单地复制另一个对象来获得功能。下面是一个简单的实现这种功能的extend()函数: +让我们来看一下另外一种继承模式——通过复制属性继承。在这种模式中,一个对象通过简单地复制另一个对象来获得功能。下面是一个简单的实现这种功能的`extend()`函数: function extend(parent, child) { var i; @@ -574,16 +573,16 @@ child[i] = parent[i]; } } - return child; + return child; } -这是一个简单的实现,仅仅是遍历了父对象的成员然后复制它们。在这个实现中,child是可选参数,如果它没有被传入一个已有的对象,那么一个全新的对象将被创建并被返回: +这是一个简单的实现,仅仅是遍历了父对象的成员然后复制它们。在这个实现中,`child`是可选参数,如果它没有被传入一个已有的对象,那么一个全新的对象将被创建并返回: var dad = {name: "Adam"}; var kid = extend(dad); kid.name; // "Adam" -上面给出的实现叫作对象的“浅拷贝”(shallow copy)。另一方面,“深拷贝”是指检查准备复制的属性本身是否是对象或者数组,如果是,也遍历它们的属性并复制。如果使用浅拷贝的话(因为在JavaScript中对象是按引用传递),如果你改变子对象的一个属性,而这个属性恰好是一个对象,那么你也会改变父对象。实际上这对方法来说可能很好(因为函数也是对象,也是按引用传递),但是当遇到其它的对象和数组的时候可能会有些意外情况。考虑这种情况: +上面给出的实现叫作对象的“浅拷贝”(shallow copy),与之相对,“深拷贝”是指检查准备复制的属性本身是否是对象或者数组,如果是,也遍历它们的属性并复制。如果使用浅拷贝的话(因为在JavaScript中对象是按引用传递),如果你改变子对象的一个属性,而这个属性恰好是一个对象,那么你也会改变父对象。实际上这对方法来说可能很好(因为函数也是对象,也是按引用传递),但是当遇到其它的对象和数组的时候可能会有些意外情况。考虑这种情况: var dad = { counts: [1, 2, 3], @@ -594,7 +593,7 @@ dad.counts.toString(); // "1,2,3,4" dad.reads === kid.reads; // true -现在让我们来修改一下extend()函数以便做深拷贝。所有你需要做的事情只是检查一个属性的类型是否是对象,如果是,则递归遍历它的属性。另外一个需要做的检查是这个对象是真的对象还是数组。我们可以使用第3章讨论过的数组检查方式。最终深拷贝版的extend()是这样的: +现在让我们来修改一下`extend()`函数以便实现深拷贝。你需要做的事情只是检查一个属性的类型是否是对象,如果是,则递归遍历它的属性。另外一个需要做的检查是这个对象是真的对象还是数组,可以使用第三章讨论过的数组检查方式。最终深拷贝版的`extend()`是这样的: function extendDeep(parent, child) { var i, @@ -633,11 +632,10 @@ kid.reads.web = true; dad.reads.paper; // true -通过复制属性继承的模式很简单且应用很广泛。例如Firebug(JavaScript写的Firefox扩展)有一个方法叫extend()做浅拷贝,jQuery的extend()方法做深拷贝。YUI3提供了一个叫作Y.clone()的方法,它创建一个深拷贝并且通过绑定到子对象的方式复制函数。(本章后面将有更多关于绑定的内容。) +通过复制属性继承的模式很简单且应用很广泛。例如Firebug(JavaScript写的Firefox扩展)有一个方法叫`extend()`做浅拷贝,jQuery的`extend()`方法做深拷贝。YUI3提供了一个叫作`Y.clone()`的方法,它创建一个深拷贝并且通过绑定到子对象的方式复制函数。(本章后面将有更多关于绑定的内容。) 这种模式并不高深,因为根本没有原型牵涉进来,而只跟对象和它们的属性有关。 - ## 混元(Mix-ins) 既然谈到了通过复制属性来继承,就让我们顺便多说一点,来讨论一下“混元”模式。除了前面说的从一个对象复制,你还可以从任意多数量的对象中复制属性,然后将它们混在一起组成一个新对象。 @@ -665,13 +663,13 @@ {sugar: "sure!"} ); -图6-10展示了在Firebug的控制台中用console.dir(cake)展示出来的混元后cake对象的属性。 +图6-10展示了在Firebug的控制台中用`console.dir(cake)`展示出来的混元后`cake`对象的属性。 ![图6-10 在Firebug中查看cake对象](./Figure/chapter6/6-10.jpg) 图6-10 在Firebug中查看cake对象 -> 如果你习惯了某些将混元作为原生部分的语言,那么你可能期望修改一个或多个父对象时也影响子对象。但在这个实现中这是不会发生的事情。这里我们只是简单地遍历、复制自己的属性,并没有与父对象的链接。 +> 如果你习惯了某些将混元作为原生部分的语言,那么你可能期望修改一个或多个父对象时也影响子对象。但在这个实现中这是不会发生的事情。这里我们只是简单地遍历、复制自有属性,并没有与父对象有任何链接。 ## 借用方法 From 64f74c6a5ef2f4acc715cf2a0f8f4f715ff570f7 Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 6 May 2013 09:11:42 +0800 Subject: [PATCH 224/258] =?UTF-8?q?=E5=80=9F=E7=94=A8=E6=96=B9=E6=B3=95=20?= =?UTF-8?q?=E4=B9=8B=20=E4=BB=8E=E6=95=B0=E7=BB=84=E5=80=9F=E7=94=A8=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/chapter6.markdown b/chapter6.markdown index 86cfeae..4f77c95 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -671,38 +671,36 @@ > 如果你习惯了某些将混元作为原生部分的语言,那么你可能期望修改一个或多个父对象时也影响子对象。但在这个实现中这是不会发生的事情。这里我们只是简单地遍历、复制自有属性,并没有与父对象有任何链接。 - ## 借用方法 -有时候会有这样的情况:你希望使用某个已存在的对象的一两个方法,你希望能复用它们,但是又真的不希望和那个对象产生继承关系,因为你只希望使用你需要的那一两个方法,而不继承那些你永远用不到的方法。受益于函数方法call()和apply(),通过借用方法模式,这是可行的。在本书中,你其实已经见过这种模式了,甚至在本章extendDeep()的实现中也有用到。 +有时候会有这样的情况:你希望使用某个已存在的对象的一两个方法,你希望能复用它们,但是又真的不希望和那个对象产生继承关系,因为你只希望使用你需要的那一两个方法,而不继承那些你永远用不到的方法。得益于函数的`call()`和`apply()`方法,可以通过借用方法模式实现它。在本书中,你其实已经见过这种模式了,甚至在本章`extendDeep()`的实现中也有用到。 -如你所熟知的一样,在JavaScript中函数也是对象,它们有一些有趣的方法,比如call()和apply()。这两个方法的唯一区别是后者接受一个参数数组以传入正在调用的方法,而前者只接受一个一个的参数。你可以使用这两个方法来从已有的对象中借用方法: +在JavaScript中函数也是对象,它们有一些有趣的方法,比如`call()`和`apply()`。这两个方法的唯一区别是后者接受一个参数数组以传入正在调用的方法,而前者只接受一个一个的参数。你可以使用这两个方法来从已有的对象中借用方法: - //call() example + // call()示例 notmyobj.doStuff.call(myobj, param1, p2, p3); - // apply() example + // apply()示例 notmyobj.doStuff.apply(myobj, [param1, p2, p3]); -在这个例子中有一个对象myobj,而且notmyobj有一个用得着的方法叫doStuff()。你可以简单地临时借用doStuff()方法,而不用处理继承然后得到一堆myobj中你永远不会用的方法。 +在这个例子中有一个对象`myobj`,而且`notmyobj`有一个用得着的方法叫`doStuff()`。你可以简单地临时借用`doStuff()`方法,而不用处理继承然后得到一堆`myobj`中无关的方法。 -你传一个对象和任意的参数,这个被借用的方法会将this绑定到你自己的对象上。简单地说,你的对象会临时假装成另一个对象以使用它的方法。这就像实际上获得了继承但又免除了“继承税”(指你不需要的属性和方法)。 +你传一个对象和任意的参数,这个被借用的方法会将`this`绑定到你传递的对象上。简单地说,你的对象会临时假装成另一个对象以使用它的方法。这就像实际上获得了继承但又免除了“继承税”(译注:指不需要的属性和方法)。 - ### 例:从数组借用 这种模式的一种常见用法是从数组借用方法。 -数组有很多很有用但是一些“类数组”对象(如arguments)不具备的方法。所以arguments可以借用数组的方法,比如slice()。这是一个例子: +数组有很多很有用但是一些“类数组”对象(如`arguments`)不具备的方法。所以`arguments`可以借用数组的方法,比如`slice()`。这是一个例子: function f() { var args = [].slice.call(arguments, 1, 3); return args; } - // example + // 示例 f(1, 2, 3, 4, 5, 6); // returns [2,3] -在这个例子中,有一个空数组被创建了,因为要借用它的方法。同样的事情也可以使用一种看起来代码更长的方法来做,那就是直接从数组的原型中借用方法,使用Array.prototype.slice.call(...)。这种方法代码更长一些,但是不用创建一个空数组。 +在这个例子中,有一个空数组被创建了,因为要借用它的方法。也可以使用一种看起来代码更长的方法来做,那就是直接从数组的原型中借用方法,使用`Array.prototype.slice.call(...)`。这种方法代码更长一些,但是不用创建一个空数组。 ### 借用并绑定 From 599b16c1de59f0e2256372694e03065e0dd7b252 Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 6 May 2013 12:59:03 +0800 Subject: [PATCH 225/258] =?UTF-8?q?=E7=AC=AC=E5=85=AD=E7=AB=A0=20=E6=A0=A1?= =?UTF-8?q?=E5=AF=B9=E5=AE=8C=E6=AF=95=20close=20#7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter6.markdown | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/chapter6.markdown b/chapter6.markdown index 4f77c95..f149d9f 100644 --- a/chapter6.markdown +++ b/chapter6.markdown @@ -702,12 +702,11 @@ 在这个例子中,有一个空数组被创建了,因为要借用它的方法。也可以使用一种看起来代码更长的方法来做,那就是直接从数组的原型中借用方法,使用`Array.prototype.slice.call(...)`。这种方法代码更长一些,但是不用创建一个空数组。 - ### 借用并绑定 -当借用方法的时候,不管是通过call()/apply()还是通过简单的赋值,方法中的this指向的对象都是基于调用的表达式来决定的。但是有时候最好的使用方式是将this的值锁定或者提前绑定到一个指定的对象上。 +当借用方法的时候,不管是通过`call()`/`apply()`还是通过简单的赋值,方法中的`this`指向的对象都是基于调用的表达式来决定的。但是有时候最好的使用方式是将`this`的值锁定或者提前绑定到一个指定的对象上。 -我们来看一个例子。这是一个对象one,它有一个say()方法: +我们来看一个例子。这是一个对象`one`,它有一个`say()`方法: var one = { name: "object", @@ -716,10 +715,10 @@ } }; - // test + // 测试 one.say('hi'); // "hi, object" -现在另一个对象two没有say()方法,但是它可以从one借用: +现在另一个对象`two`没有`say()`方法,但是它可以从one借用: var two = { name: "another object" @@ -727,14 +726,13 @@ one.say.apply(two, ['hello']); // "hello, another object" -在这个例子中,say()方法中的this指向了two,this.name是“another object”。但是如果在某些场景下你将函数赋值给了全局变量或者是将这个函数作为回调,会发生什么?在客户端编程中有非常多的事件和回调,所以这种情况经常发生: +在这个例子中,`say()`方法中的`this`指向了`two`,`this.name`是“another object”。但是如果在某些场景下你将函数赋值给了全局变量或者是将这个函数作为回调,会发生什么?在客户端编程中有非常多的事件和回调,所以这种情况经常发生: - // assigning to a variable - // `this` will point to the global object + // 赋值给变量,this会指向全局对象 var say = one.say; say('hoho'); // "hoho, undefined" - // passing as a callback + // 作为回调 var yetanother = { name: "Yet another object", method: function (callback) { @@ -743,7 +741,7 @@ }; yetanother.method(one.say); // "Holla, undefined" -在这两种情况中say()中的this都指向了全局对象,所以代码并不像我们想象的那样正常工作。要修复(换言之,绑定)一个方法的对象,我们可以用一个简单的函数,像这样: +在这两种情况中`say()`中的`this`都指向了全局对象,所以代码并不像我们想象的那样正常工作。要修复(绑定)一个方法的对象,我们可以用一个简单的函数,像这样: function bind(o, m) { return function () { @@ -751,25 +749,24 @@ }; } -这个bind()函数接受一个对象o和一个方法m,然后把它们绑定在一起,再返回另一个函数。返回的函数通过闭包可以访问到o和m。也就是说,即使在bind()返回之后,内层的函数仍然可以访问到o和m,而o和m会始终指向原始的对象和方法。让我们用bind()来创建一个新函数: +这个`bind()`函数接受一个对象`o`和一个方法`m`,然后把它们绑定在一起,再返回另一个函数。返回的函数通过闭包可以访问到`o`和`m`,也就是说,即使在`bind()`返回之后,内层的函数仍然可以访问到`o`和`m`,而`o`和`m`会始终指向原来的对象和方法。让我们用`bind()`来创建一个新函数: var twosay = bind(two, one.say); twosay('yo'); // "yo, another object" -正如你看到的,尽管twosay()是作为一个全局函数被创建的,但this并没有指向全局对象,而是指向了通过bind()传入的对象two。不论无何调用twosay(),this将始终指向two。 +正如你看到的,尽管`twosay()`是作为一个全局函数被创建的,但`this`并没有指向全局对象,而是指向了通过`bind()`传入的对象`two`。不论如何调用`twosay()`,`this`将始终指向`two`。 绑定是奢侈的,你需要付出的代价是一个额外的闭包。 - ### Function.prototype.bind() -ECMAScript5在Function.prototype中添加了一个方法叫bind(),使用时和apply和call()一样简单。所以你可以这样写: +ECMAScript5在`Function.prototype`中添加了一个方法叫`bind()`,使用时和`apply()`/`call()`一样简单。所以你可以这样写: var newFunc = obj.someFunc.bind(myobj, 1, 2, 3); -这意味着将someFunc()和myobj绑定了,并且还传入了someFunc()的前三个参数。这也是一个在第4章讨论过的部分应用的例子。 +这意味着将`someFunc()`和`myobj`绑定了,并且还传入了`someFunc()`的前三个参数。这也是一个在第4章讨论过的部分应用的例子。 -让我们来看一下当你的程序跑在低于ES5的环境中时如何实现Function.prototype.bind(): +让我们来看一下当你的程序跑在低于ES5的环境中时如何实现`Function.prototype.bind()`: if (typeof Function.prototype.bind === "undefined") { Function.prototype.bind = function (thisArg) { @@ -783,22 +780,20 @@ ECMAScript5在Function.prototype中添加了一个方法叫bind(),使用时和 }; } -这个实现可能看起来有点熟悉,它使用了部分应用,将传入bind()的参数串起来(除了第一个参数),然后在被调用时传给bind()返回的新函数。这是用法示例: +这个实现可能看起来有点熟悉,它使用了部分应用,将传入`bind()`的参数串起来(除了第一个参数),然后在被调用时传给`bind()`返回的新函数。这是用法示例: var twosay2 = one.say.bind(two); twosay2('Bonjour'); // "Bonjour, another object" -在这个例子中,除了绑定的对象外,我们没有传任何参数给bind()。下一个例子中,我们来传一个用于部分应用的参数: +在这个例子中,除了绑定的对象外,我们没有传任何参数给`bind()`。下一个例子中,我们来传一个用于部分应用的参数: var twosay3 = one.say.bind(two, 'Enchanté'); twosay3(); // "Enchanté, another object" - ##小结 -在JavaScript中,继承有很多种方案可以选择。学习和理解不同的模式是有好处的,因为这可以增强你对这门语言的掌握能力。在本章中你看到了很多类式继承和现代继承的方案。 - -但是,也许在开发过程中继承并不是你经常面对的一个问题。这一部分是因为这个问题已经被使用某种方式或者某个你使用的类库解决了,另一部分是因为你不需要在JavaScript中建立很长很复杂的继承链。在静态强类型语言中,继承可能是唯一可以利用代码的方法,但在JavaScript中你可能有更多更简单更优化的方法,包括借用方法、绑定、复制属性、混元等。 +在JavaScript中,继承有很多种方案可以选择,在本章中你看到了很多类式继承和现代继承的方案。学习和理解不同的模式是有好处的,因为这可以增强你对这门语言的掌握能力。 -记住,代码复用才是目标,继承只是达成这个目标的一种手段。 +但是,也许在开发过程中继承并不是你经常面对的一个问题。一部分是因为这个问题已经被使用某种方式或者某个你使用的类库解决了,另一部分是因为你不需要在JavaScript中建立很长很复杂的继承链。在静态强类型语言中,继承可能是唯一可以复用代码的方法,但在JavaScript中有更多更简单更优化的方法,包括借用方法、绑定、复制属性、混元等。 +记住,代码复用才是目标,继承只是达成这个目标的一种手段。 \ No newline at end of file From 4251240d0d45473475a7530ed1d77a38f40d68ca Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 6 May 2013 13:30:20 +0800 Subject: [PATCH 226/258] =?UTF-8?q?=E5=8D=95=E4=BE=8B=E6=A8=A1=E5=BC=8F=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 114 +++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 61 deletions(-) diff --git a/chapter7.markdown b/chapter7.markdown index 140bbe7..d2f3ce0 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -1,15 +1,13 @@ - # 设计模式 在GoF(Gang of Four)的书中提出的设计模式为面向对象的软件设计中遇到的一些普遍问题提供了解决方案。它们已经诞生很久了,而且被证实在很多情况下是很有效的。这正是你需要熟悉它的原因,也是我们要讨论它的原因。 尽管这些设计模式跟语言和具体的实现方式无关,但它们多年来被关注到的方面仍然主要是在强类型静态语言比如C++和Java中的应用。 -JavaScript作为一种基于原型的弱类型动态语言,使得有些时候实现某些模式时相当简单,甚至不费吹灰之力。 +JavaScript作为一种基于原型的弱类型动态语言,有些时候实现某些模式时相当简单,甚至不费吹灰之力。 让我们从第一个例子——单例模式——来看一下在JavaScript中和静态的基于类的语言有什么不同。 - ## 单例 单例模式的核心思想是让指定的类只存在唯一一个实例。这意味着当你第二次使用相同的类去创建对象的时候,你得到的应该和第一次创建的是同一个对象。 @@ -28,16 +26,15 @@ JavaScript作为一种基于原型的弱类型动态语言,使得有些时候 obj === obj2; // false obj == obj2; // false -所以你可以说当你每次使用对象字面量创建一个对象的时候就是在创建一个单例,并没有特别的语法迁涉进来。 +所以你可以说当你每次使用对象字面量创建一个对象的时候就是在创建一个单例,并没有什么特别的语法牵涉进来。 -> 需要注意的是,有的时候当人们在JavaScript中提出“单例”的时候,它们可能是在指第5章讨论过的“模块模式”。 +> 需要注意的是,有的时候当人们在JavaScript中提出“单例”的时候,它们可能是在指第五章讨论过的“模块模式”。 - ### 使用new -JavaScript没有类,所以一字一句地说单例的定义并没有什么意义。但是JavaScript有使用new、通过构造函数来创建对象的语法,有时候你可能需要这种语法下的一个单例实现。这也就是说当你使用new、通过同一个构造函数来创建多个对象的时候,你应该只是得到同一个对象的不同引用。 +JavaScript没有类,所以一字一句地说单例的定义并没有什么意义。但是JavaScript有使用`new`、通过构造函数来创建对象的语法,有时候你可能需要这种语法下的一个单例实现。这也就是说当你使用`new`、通过同一个构造函数来创建多个对象的时候,你应该只是得到同一个对象的不同引用。 -> 温馨提示:从一个实用模式的角度来说,下面的讨论并不是那么有用,只是更多地在实践模拟一些语言中关于这个模式的一些问题的解决方案。这些语言主要是(静态强类型的)基于类的语言,在这些语言中,函数并不是“一等公民”。 +> 温馨提示:从一个实用模式的角度来说,下面的讨论并不是那么有用,只是更多地在模拟一些语言中关于这个模式的一些问题的解决方案。这些语言主要是(静态强类型的)基于类的语言,在这些语言中,函数并不是“一等公民”。 下面的代码片段展示了期望的结果(假设你忽略了多元宇宙的设想,接受了只有一个宇宙的观点): @@ -45,126 +42,122 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 var uni2 = new Universe(); uni === uni2; // true -在这个例子中,uni只在构造函数第一次被调用时创建。第二次(以及后续更多次)调用时,同一个uni对象被返回。这就是为什么uni === uni2的原因——因为它们实际上是同一个对象的两个引用。那么怎么在JavaScript达到这个效果呢? +在这个例子中,`uni`只在构造函数第一次被调用时创建。第二次(以及后续更多次)调用时,同一个`uni`对象被返回。这就是为什么`uni === uni2`的原因——因为它们实际上是同一个对象的两个引用。那么怎么在JavaScript达到这个效果呢? -当对象实例this被创建时,你需要在Universe构造函数中缓存它,以便在第二次调用的时候返回。有几种选择可以达到这种效果: +当对象实例`this`被创建时,你需要在`Universe()`构造函数中缓存它,以便在第二次调用的时候返回。有几种选择可以达到这种效果: - 你可以使用一个全局变量来存储实例。不推荐使用这种方法,因为通常我们认为使用全局变量是不好的。而且,任何人都可以改写全局变量的值,甚至可能是无意中改写。所以我们不再讨论这种方案。 -- 你也可以将对象实例缓存在构造函数的属性中。在JavaScript中,函数也是对象,所以它们也可以有属性。你可以写一些类似Universe.instance的属性来缓存对象。这是一种漂亮干净的解决方案,不足之处是instance属性仍然是可以被公开访问的,别人写的代码可能修改它,这样就会失去这个实例。 +- 你也可以将对象实例缓存在构造函数的属性中。在JavaScript中,函数也是对象,所以它们也可以有属性。你可以写一些类似`Universe.instance`的属性来缓存对象。这是一种漂亮干净的解决方案,不足之处是`instance`属性仍然是可以被公开访问的,别人写的代码可能修改它,这样就会失去这个实例。 - 你可以将实例包裹在闭包中。这可以保持实例是私有的,不会在构造函数之外被修改,代价是一个额外的闭包。 让我们来看一下第二种和第三种方案的实现示例。 - ### 将实例放到静态属性中 -下面是一个将唯一的实例放入Universe构造函数的一个静态属性中的例子: +下面是一个将唯一的实例放入`Universe()`构造函数的一个静态属性中的例子: function Universe() { - // do we have an existing instance? - if (typeof Universe.instance === "object") { - return Universe.instance; - } - - // proceed as normal - this.start_time = 0; - this.bang = "Big"; - - // cache - Universe.instance = this; - - // implicit return: - // return this; + // 实例是否已经存在? + if (typeof Universe.instance === "object") { + return Universe.instance; + } + + // 处理普通逻辑 + this.start_time = 0; + this.bang = "Big"; + + // 缓存实例 + Universe.instance = this; + + // 隐式return: + // return this; } - // testing + // 测试 var uni = new Universe(); var uni2 = new Universe(); uni === uni2; // true -如你所见,这是一种直接有效的解决方案,唯一的缺陷是instance是可被公开访问的。一般来说它被其它代码误删改的可能是很小的(起码比全局变量instance要小得多),但是仍然是有可能的。 +如你所见,这是一种直接有效的解决方案,唯一的缺陷是`instance`是可被公开访问的。一般来说它被其它代码误删改的可能是很小的(起码比全局变量`instance`要小得多),但是仍然是有可能的。 - ### 将实例放到闭包中 -另一种实现基于类的单例模式的方法是使用一个闭包来保护这个唯一的实例。你可以通过第5章讨论过的“私有静态成员模式”来实现。唯一的秘密就是重写构造函数: +另一种实现基于类的单例模式的方法是使用一个闭包来保护这个唯一的实例。你可以通过第五章讨论过的“私有静态成员模式”来实现。唯一的秘密就是重写构造函数: function Universe() { - // the cached instance + // 缓存实例 var instance = this; - // proceed as normal + // 处理普通逻辑 this.start_time = 0; this.bang = "Big"; - // rewrite the constructor + // 重写构造函数 Universe = function () { return instance; }; } - // testing + // 测试 var uni = new Universe(); var uni2 = new Universe(); uni === uni2; // true -第一次调用时,原始的构造函数被调用并且正常返回this。在后续的调用中,被重写的构造函数被调用。被重写怕这个构造函数可以通过闭包访问私有的instance变量并且将它返回。 +第一次调用时,原来的构造函数被调用并且正常返回`this`。在后续的调用中,被重写的构造函数被调用。被重写的这个构造函数可以通过闭包访问私有的`instance`变量并且将它返回。 -这个实现实际上也是第4章讨论的自定义函数的又一个例子。如我们讨论过的一样,这种模式的缺点是被重写的函数(在这个例子中就是构造函数Universe())将丢失那些在初始定义和重新定义之间添加的属性。在这个例子中,任何添加到Universe()的原型上的属性将不会被链接到使用原来的实现创建的实例上。(注:这里的“原来的实现”是指实例是由未被重写的构造函数创建的,而Universe()则是被重写的构造函数。) +这个实现实际上也是第四章讨论的重定义函数的又一个例子。如我们讨论过的一样,这种模式的缺点是被重写的函数(在这个例子中就是构造函数`Universe()`)将丢失那些在初始定义和重新定义之间添加的属性。在这个例子中,任何添加到`Universe()`的原型上的属性将不会被链接到使用原来的实现创建的实例上。(注:这里的“原来的实现”是指实例是由未被重写的构造函数创建的,而`Universe()`则是被重写的构造函数。) 下面我们通过一些测试来展示这个问题: - // adding to the prototype + // 添加成员到原型 Universe.prototype.nothing = true; var uni = new Universe(); - // again adding to the prototype - // after the initial object is created + // 在创建一个对象后再添加成员到原型 Universe.prototype.everything = true; var uni2 = new Universe(); - Testing: - // only the original prototype was - // linked to the objects + // 测试: + // 只有原始的原型被链接到对象上 uni.nothing; // true uni2.nothing; // true uni.everything; // undefined uni2.everything; // undefined - // that sounds right: + // constructor看起来是对的 uni.constructor.name; // "Universe" - // but that's odd: + // 但其实不然 uni.constructor === Universe; // false -uni.constructor不再和Universe()相同的原因是uni.constructor仍然是指向原来的构造函数,而不是被重新定义的那个。 +`uni.constructor`不再和`Universe()`相同的原因是`uni.constructor`仍然是指向原来的构造函数,而不是被重新定义的那个。 -如果一定被要求让prototype和constructor的指向像我们期望的那样,可以通过一些调整来做到: +如果一定要让`prototype`和`constructor`的指向像我们期望的那样,可以通过一些调整来做到: function Universe() { - // the cached instance + // 缓存实例 var instance; - // rewrite the constructor + // 重写构造函数 Universe = function Universe() { return instance; }; - // carry over the prototype properties + // 重写prototype属性 Universe.prototype = this; - // the instance + // 创建实例 instance = new Universe(); - // reset the constructor pointer + // 重写constructor属性 instance.constructor = Universe; - // all the functionality + // 其它的功能代码 instance.start_time = 0; instance.bang = "Big"; @@ -173,24 +166,23 @@ uni.constructor不再和Universe()相同的原因是uni.constructor仍然是指 现在所有的测试结果都可以像我们期望的那样了: - // update prototype and create instance + // 修改原型,创建对象 Universe.prototype.nothing = true; // true var uni = new Universe(); Universe.prototype.everything = true; // true var uni2 = new Universe(); - // it's the same single instance + // 它们是同一个实例 uni === uni2; // true - // all prototype properties work - // no matter when they were defined + // 所有的原型上的属性都正常工作,不管是什么时候在哪添加的 uni.nothing && uni.everything && uni2.nothing && uni2.everything; // true - // the normal properties work + // 普通成员也可以正常工作 uni.bang; // "Big" - // the constructor points correctly + // constructor指向正确 uni.constructor === Universe; // true -另一种可选的解决方案是将构造函数和实例包在一个立即执行的函数中。当构造函数第一次被调用的时候,它返回一个对象并且将私有的instance指向它。在后续调用时,构造函数只是简单地返回这个私有变量。在这种新的实现下,前面所有的测试代码也会和期望的一样: +另一种可选的解决方案是将构造函数和实例包在一个即时函数中。当构造函数第一次被调用的时候,它返回一个对象并且将私有的`instance`指向它。在后续调用时,构造函数只是简单地返回这个私有变量。在这种新的实现下,前面所有的测试代码也会和期望的一样: var Universe; @@ -206,7 +198,7 @@ uni.constructor不再和Universe()相同的原因是uni.constructor仍然是指 instance = this; - // all the functionality + // 功能代码 this.start_time = 0; this.bang = "Big"; From 3ea8ab2949aa60d116c1d6efc2fce5adfb52482c Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 7 May 2013 12:47:01 +0800 Subject: [PATCH 227/258] =?UTF-8?q?=E5=B7=A5=E5=8E=82=E6=A8=A1=E5=BC=8F=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/chapter7.markdown b/chapter7.markdown index d2f3ce0..6c51f78 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -206,7 +206,6 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 }()); - ## 工厂模式 使用工厂模式的目的就是创建对象。它通常被在类或者类的静态方法中实现,目的是: @@ -214,15 +213,15 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 - 执行在建立相似的对象时进行的一些重复操作 - 让工厂的使用者在编译阶段创建对象时不必知道它的特定类型(类) -第二点在静态的基于类的语言中更重要,因为在(编译阶段)提前不知道类的情况下,创建类的实例是不普通的行为。但在JavaScript中,这部分的实现却是相当容易的事情。 +第二点在静态的基于类的语言中更重要,因为在(编译阶段)提前不知道类的情况下,创建类的实例是一件多少有些特殊的行为。但在JavaScript中,这部分的实现却是相当容易的事情。 -使用工厂方法(或类)创建的对象被设计为从同一个父对象继承;它们是特定的实现一些特殊功能的子类。有些时候这个共同的父对象就是包含工厂方法的同一个类。 +使用工厂方法(或类)创建的对象被设计为从同一个父对象继承;它们是实现一些特定的功能的子类。有些时候这个共同的父对象就是包含工厂方法的同一个类。 我们来看一个示例实现,我们有: -- 一个共同的父构造函数CarMaker。 -- CarMaker的一个静态方法叫factory(),用来创建car对象。 -- 特定的从CarMaker继承而来的构造函数CarMaker.Compact,CarMaker.SUV,CarMaker.Convertible。它们都被定义为父构造函数的静态属性以便保持全局空间干净,同时在需要的时候我们也知道在哪里找到它们。 +- 一个共同的父构造函数`CarMaker()`。 +- `CarMaker()`的一个静态方法叫`factory()`,用来创建`car`对象。 +- 特定的从`CarMaker()`继承而来的构造函数`CarMaker.Compact()`,`CarMaker.SUV()`,`CarMaker.Convertible()`。它们都被定义为父构造函数的静态属性以便保持全局空间干净,同时在需要的时候我们也知道在哪里找到它们。 我们来看一下已经完成的实现会怎么被使用: @@ -237,24 +236,24 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 var corolla = CarMaker.factory('Compact'); -可能是工厂模式中最知名的。你有一个方法可以在运行时接受一个表示类型的字符串,然后它创建并返回了一个和请求的类型一样的对象。这里没有使用new的构造函数,也没有看到任何对象字面量,仅仅只有一个函数根据一个字符串指定的类型创建了对象。 +可能是工厂模式中最为人熟知的。你有一个方法可以在运行时接受一个表示类型的字符串,然后它创建并返回了一个和请求的类型一样的对象。这里没有使用`new`的构造函数,也没有看到任何对象字面量,仅仅只有一个函数根据一个字符串指定的类型创建了对象。 这里是一个工厂模式的示例实现,它能让上面的代码片段工作: - // parent constructor + // 父构造函数 function CarMaker() {} - // a method of the parent + // 父构造函数的方法 CarMaker.prototype.drive = function () { return "Vroom, I have " + this.doors + " doors"; }; - // the static factory method + // 静态工厂方法factory CarMaker.factory = function (type) { var constr = type, - newcar; + newcar; - // error if the constructor doesn't exist + // 如果指定类型的构造函数不存在则报错 if (typeof CarMaker[constr] !== "function") { throw { name: "Error", @@ -262,18 +261,18 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 }; } - // at this point the constructor is known to exist - // let's have it inherit the parent but only once + // 现在我们确认要用到的构造函数是存在的了 + // 让它继承自父构造函数,但只继承一次 if (typeof CarMaker[constr].prototype.drive !== "function") { CarMaker[constr].prototype = new CarMaker(); } - // create a new instance + // 创建一个新实例 newcar = new CarMaker[constr](); - // optionally call some methods and then return... + // 这里可以选择性地调用一些方法,然后返回实例 return newcar; }; - // define specific car makers + // 创建特定类型的构造函数 CarMaker.Compact = function () { this.doors = 4; }; @@ -284,27 +283,26 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 this.doors = 24; }; -工厂模式的实现中没有什么是特别困难的。你需要做的仅仅是寻找请求类型的对象的构造函数。在这个例子中,使用了一个简单的名字转换以便映射对象类型和创建对象的构造函数。继承的部分只是一个公共的重复代码片段的示例,它可以被放到工厂方法中而不是被每个构造函数的类型重复。(译注:指通过原型继承的代码可以在factory方法以外执行,而不是放到factory中每调用一次都要执行一次。) +工厂模式的实现中没有什么是特别困难的,你需要做的仅仅是寻找请求类型的对象构造函数。在这个例子中,使用了一个简单的名字转换以便映射对象类型和创建对象的构造函数。继承的部分只是一个公共的重复代码片段的示例,它可以被放到工厂方法中而不是被每个构造函数的类型所重复。(译注:指原型继承的代码可以在`factory()`方法以外执行,而不是放到`factory()`中每调用一次都要执行一次。) - ### 内置对象工厂 -作为一个“野生的工厂”的例子,我们来看一下内置的全局构造函数Object()。它的行为很像工厂,因为它根据不同的输入创建不同的对象。如果传入一个数字,它会使用Number()构造函数创建一个对象。在传入字符串和布尔值的时候也会发生同样的事情。任何其它的值(包括空值)将会创建一个正常的对象。 +为了说明工厂模式应用之广泛,我们来看一下内置的全局构造函数`Object()`。它的行为很像工厂,因为它根据不同的输入创建不同的对象。如果传入一个数字,它会使用`Number()`构造函数创建一个对象。在传入字符串和布尔值的时候也会发生类似的事情。任何其它的值(包括空值)将会创建一个正常的对象。 -下面是这种行为的例子和测试,注意Object调用时可以不用加new: +下面是这种行为的例子和测试,注意`Object()`调用时可以不用加`new`: var o = new Object(), n = new Object(1), s = Object('1'), b = Object(true); - // test + // 测试 o.constructor === Object; // true n.constructor === Number; // true s.constructor === String; // true b.constructor === Boolean; // true -Object()也是一个工厂这一事实可能没有太多实际用处,仅仅是觉得值得作为一个例子提一下,告诉我们工厂模式是随处可见的。 +`Object()`也是一个工厂这一事实可能没有太多实际用处,仅仅是觉得值得作为一个例子提一下,告诉我们工厂模式是随处可见的。 From 07d5e5584c2d9d3add6b41bad8b8eb2cc5ee3cfb Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 7 May 2013 12:49:22 +0800 Subject: [PATCH 228/258] =?UTF-8?q?=E8=BF=AD=E4=BB=A3=E5=99=A8=20=E6=A0=A1?= =?UTF-8?q?=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/chapter7.markdown b/chapter7.markdown index 6c51f78..53836fb 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -304,26 +304,24 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 `Object()`也是一个工厂这一事实可能没有太多实际用处,仅仅是觉得值得作为一个例子提一下,告诉我们工厂模式是随处可见的。 - - ## 迭代器 在迭代器模式中,你有一些含有有序聚合数据的对象。这些数据可能在内部用一种复杂的结构存储着,但是你希望提供一种简单的方法来访问这种结构中的每个元素。数据的使用者不需要知道你是怎样组织你的数据的,他们只需要操作一个个独立的元素。 -在迭代器模式中,你的对象需要提供一个next()方法。按顺序调用next()方法必须返回序列中的下一个元素,但是“下一个”在你的特定的数据结构中指什么是由你自己来决定的。 +在迭代器模式中,你的对象需要提供一个`next()`方法。按顺序调用`next()`方法必须返回序列中的下一个元素,但是“下一个”在你的特定的数据结构中指什么是由你自己来决定的。 -假设你的对象叫agg,你可以通过简单地在循环中调用next()来访问每个数据元素,像这样: +假设你的对象叫`agg`,你可以通过简单地在循环中调用`next()`来访问每个数据元素,像这样: var element; while (element = agg.next()) { - // do something with the element ... + // 访问element…… console.log(element); } -在迭代器模式中,聚合对象通常也会提供一个方便的方法hasNext(),这样对象的使用者就可以知道他们已经获取到你数据的最后一个元素。当使用另一种方法——hasNext()——来按顺序访问所有元素时,是像这样的: +在迭代器模式中,聚合对象通常也会提供一个方便的方法`hasNext()`,这样对象的使用者就可以知道他们已经获取到你数据的最后一个元素。当使用`hasNext()`来按顺序访问所有元素时,是像这样的: while (agg.hasNext()) { - // do something with the next element... + // 访问element…… console.log(agg.next()); } From d36dc03e7f826db001c1c28dd0416fcd355597c5 Mon Sep 17 00:00:00 2001 From: TooBug Date: Thu, 9 May 2013 13:00:55 +0800 Subject: [PATCH 229/258] =?UTF-8?q?=E8=A3=85=E9=A5=B0=E5=99=A8=20=E6=A0=A1?= =?UTF-8?q?=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 54 ++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/chapter7.markdown b/chapter7.markdown index 53836fb..a383cf0 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -325,39 +325,36 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 console.log(agg.next()); } - ## 装饰器 在装饰器模式中,一些额外的功能可以在运行时被动态地添加到一个对象中。在静态的基于类的语言中,处理这个问题可能是个挑战,但是在JavaScript中,对象本来就是可变的,所以给一个对象添加额外的功能本身并不是什么问题。 -装饰器模式的一个很方便的特性是可以对我们需要的特性进行定制和配置。刚开始时,我们有一个拥有基本功能的对象,然后可以从可用的装饰器中去挑选一些需要用到的去增加这个对象,甚至如果顺序很重要的话,还可以指定增强的顺序。 +装饰器模式的一个很方便的特性是可以对我们需要的特性进行定制和配置。刚开始时,我们有一个拥有基本功能的对象,然后可以从可用的装饰器中去挑选一些需要用到的去增强这个对象,如果有必要的话,还可以指定增强的顺序。 - ### 用法 -我们来看一下这个模式的示例用法。假设你正在做一个卖东西的web应用,每个新交易是一个新的sale对象。这个对象“知道”交易的价格并且可以通过调用sale.getPrice()方法返回。根据环境的不同,你可以开始用一些额外的功能来装饰这个对象。假设一个场景是这笔交易是发生在加拿大的一个省Québec,在这种情况下,购买者需要付联邦税和Québec省税。根据装饰器模式的用法,你需要指明使用联邦税装饰器和Québec省税装饰器来装饰这个对象。然后你还可以给这个对象装饰一些价格格式的功能。这个场景的使用方式可能是像这样: +我们来看一下这个模式的用法示例。假设你正在做一个卖东西的web应用,每个新交易是一个新的`sale`对象。这个对象“知道”交易的价格并且可以通过调用`sale.getPrice()`方法返回。根据环境的不同,你可以开始用一些额外的功能来装饰这个对象。假设一个场景是这笔交易是发生在加拿大的一个省Québec,在这种情况下,购买者需要付联邦税和Québec省税。根据装饰器模式的用法,你需要指明使用联邦税装饰器和Québec省税装饰器来装饰这个对象。然后你还可以给这个对象装饰一些价格格式的功能。这个场景的使用方式可能是像这样: - var sale = new Sale(100); // the price is 100 dollars - sale = sale.decorate('fedtax'); // add federal tax - sale = sale.decorate('quebec'); // add provincial tax - sale = sale.decorate('money'); // format like money + var sale = new Sale(100); // 价格是100美元 + sale = sale.decorate('fedtax'); // 加上联邦税 + sale = sale.decorate('quebec'); // 加上省税 + sale = sale.decorate('money'); // 格式化 sale.getPrice(); // "$112.88" 在另一种场景下,购买者在一个不需要交省税的省,并且你想用加拿大元的格式来显示价格,你可以这样做: - var sale = new Sale(100); // the price is 100 dollars - sale = sale.decorate('fedtax'); // add federal tax - sale = sale.decorate('cdn'); // format using CDN + var sale = new Sale(100); // 价格是100美元 + sale = sale.decorate('fedtax'); // 加上联邦税 + sale = sale.decorate('cdn'); // 用加拿大元格式化 sale.getPrice(); // "CDN$ 105.00" -如你所见,这是一种在运行时很灵活的方法来添加功能和调整对象。我们来看一下如何来实现这种模式。 +如你所见,这种方法可以在运行时很灵活地添加功能和调整对象。我们来看一下如何来实现这种模式。 - ### 实现 一种实现装饰器模式的方法是让每个装饰器成为一个拥有应该被重写的方法的对象。每个装饰器实际上是继承自已经被前一个装饰器增强过的对象。装饰器的每个方法都会调用父对象(继承自的对象)的同名方法并取得值,然后做一些额外的处理。 -最终的效果就是当你在第一个例子中调用sale.getPrice()时,实际上是在调用money装饰器的方法(图7-1)。但是因为每个装饰器会先调用父对象的方法,money的getPrice()先调用quebec的getPrice(),而它又会去调用fedtax的getPrice()方法,依次类推。这个链会一直走到原始的未经装饰的由Sale()构造函数实现的getPrice()。 +最终的效果就是当你在第一个例子中调用`sale.getPrice()`时,实际上是在调用`money`装饰器的方法(图7-1)。但是因为每个装饰器会先调用父对象的方法,`money`的`getPrice()`先调用`quebec`的`getPrice()`,而它又会去调用`fedtax`的`getPrice()`方法,依次类推。这个链会一直走到原始的未经装饰的由`Sale()`构造函数实现的`getPrice()`。 ![图7-1 装饰器模式的实现](./Figure/chapter7/7-1.jpg) 图7-1 装饰器模式的实现 @@ -375,7 +372,7 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 Sale.decorators = {}; -我们来看一个装饰器的例子。这是一个对象,实现了一个自定义的getPrice()方法。注意这个方法首先从父对象的方法中取值然后修改这个值: +我们来看一个装饰器的例子。这是一个对象,实现了一个自定义的`getPrice()`方法。注意这个方法首先从父对象的方法中取值然后修改这个值: Sale.decorators.fedtax = { getPrice: function () { @@ -385,7 +382,7 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 } }; -使用类似的方法我们可以实现任意多个需要的其它装饰器。他们的实现方式像插件一样来扩展核心的Sale()的功能。他们甚至可以被放到额外的文件中,被第三方的开发者来开发和共享: +使用类似的方法我们可以实现任意多个需要的装饰器。它们的实现方式像插件一样来扩展核心的`Sale()`的功能。它们甚至可以被放到额外的文件中,被第三方的开发者来开发和共享: Sale.decorators.quebec = { getPrice: function () { @@ -407,11 +404,11 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 } }; -最后我们来看decorate()这个神奇的方法,它把所有上面说的片段都串起来了。记得它是这样被调用的: +最后我们来看`decorate()`这个神奇的方法,它把所有上面说的片段都串起来了。记住它是这样被调用的: sale = sale.decorate('fedtax'); -字符串'fedtax'对应在Sale.decorators.fedtax中实现的对象。被装饰过的最新的对象newobj将从现在有的对象(也就是this对象,它要么是原始的对象,要么是经过最后一个装饰器装饰过的对象)中继承。实现这一部分需要用到前面章节中提到的临时构造函数模式。我们也设置一个uber属性给newobj以便子对象可以访问到父对象。然后我们从装饰器中复制所有额外的属性到被装饰的对象newobj中。最后,在我们的例子中,newobj被返回并且成为被更新过的sale对象。 +字符串`'fedtax'`对应在`Sale.decorators.fedtax`中实现的对象。被装饰过的最新的对象`newobj`将从现在有的对象(也就是`this`对象,它要么是原始的对象,要么是经过最后一个装饰器装饰过的对象)中继承。实现这一部分需要用到前面章节中提到的临时构造函数模式。我们也设置一个`uber`属性给`newobj`以便子对象可以访问到父对象。然后我们从装饰器中复制所有额外的属性到被装饰的对象`newobj`中。最后,在我们的例子中,`newobj`被返回并且成为被更新过的`sale`对象。 Sale.prototype.decorate = function (decorator) { var F = function () {}, @@ -428,29 +425,28 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 return newobj; }; - ### 使用列表实现 -我们来看另一个明显不同的实现方法,受益于JavaScript的动态特性,它完全不需要使用继承。同时,我们也可以简单地将前一个方面的结果作为参数传给下一个方法,而不需要每一个方法都去调用前一个方法。 +我们来看另一个明显不同的实现方法,得益于JavaScript的动态特性,它完全不需要使用继承。同时,我们也可以简单地将前一个方面的结果作为参数传给下一个方法,而不需要每一个方法都去调用前一个方法。 这样的实现方法还允许很容易地反装饰(undecorating)或者撤销一个装饰,这仅仅需要从一个装饰器列表中移除一个条目。 -用法示例也会明显简单一些,因为我们不需要将decorate()的返回值赋值给对象。在这个实现中,decorate()不对对象做任何事情,它只是简单地将装饰器加入到一个列表中: +用法示例也会明显简单一些,因为我们不需要将`decorate()`的返回值赋值给对象。在这个实现中,`decorate()`不对对象做任何事情,它只是简单地将装饰器加入到一个列表中: - var sale = new Sale(100); // the price is 100 dollars - sale.decorate('fedtax'); // add federal tax - sale.decorate('quebec'); // add provincial tax - sale.decorate('money'); // format like money + var sale = new Sale(100); // 价格是100美元 + sale.decorate('fedtax'); // 加上联邦税 + sale.decorate('quebec'); // 加上省税 + sale.decorate('money'); // 格式化 sale.getPrice(); // "$112.88" -Sale()构造函数现在有了一个作为自己属性的装饰器列表: +`Sale()`构造函数现在有了一个作为自己属性存在的装饰器列表: function Sale(price) { this.price = price || 100; this.decorators_list = []; } -可用的装饰器仍然被实现为Sale.decorators的属性。注意getPrice()方法现在更简单了,因为它们不需要调用父对象的getPrice()来获取结果,结果已经作为参数传递给它们了: +可用的装饰器仍然被实现为`Sale.decorators`的属性。注意`getPrice()`方法现在更简单了,因为它们不需要调用父对象的`getPrice()`来获取结果,结果已经作为参数传递给它们了: Sale.decorators = {}; @@ -472,7 +468,7 @@ Sale()构造函数现在有了一个作为自己属性的装饰器列表: } }; -最有趣的部分发生在父对象的decorate()和getPrice()方法上。在前一种实现方式中,decorate()还是多少有些复杂,而getPrice()十分简单。在这种实现方式中事情反过来了:decorate()只需要往列表中添加条目而getPrice()做了所有的工作。这些工作包括遍历现在添加的装饰器的列表,然后调用它们的getPrice()方法,并将结果传递给前一个: +最有趣的部分发生在父对象的`decorate()`和`getPrice()`方法上。在前一种实现方式中,`decorate()`还是多少有些复杂,而`getPrice()`十分简单。在这种实现方式中事情反过来了:`decorate()`只需要往列表中添加条目而`getPrice()`做了其它所有的工作,包括遍历现在添加的装饰器的列表,然后调用它们的`getPrice()`方法并将结果传递下去: Sale.prototype.decorate = function (decorator) { this.decorators_list.push(decorator); @@ -490,7 +486,7 @@ Sale()构造函数现在有了一个作为自己属性的装饰器列表: return price; }; -装饰器模式的第二种实现方式更简单一些,并且没有引入继承。装饰的方法也会简单。所有的工作都由“同意”被装饰的方法来做。在这个示例实现中,getPrice()是唯一被允许装饰的方法。如果你想有更多可以被装饰的方法,那遍历装饰器列表的工作就需要由每个方法重复去做。但是,这可以很容易地被抽象到一个辅助方法中,给它传一个方法然后使这个方法“可被装饰”。如果这样实现的话,decorators_list属性就应该是一个对象,它的属性名字是方法名,值是装饰器对象的数组。 +装饰器模式的第二种实现方式更简单一些,并且没有引入继承。装饰的方法也会简单。所有的工作都由“同意”被装饰的方法来做。在这个示例实现中,`getPrice()`是唯一被允许装饰的方法。如果你想有更多可以被装饰的方法,那遍历装饰器列表的工作就需要由每个方法重复去做。但是,这可以很容易地被抽象到一个辅助方法中,给它传一个方法然后使这个方法“可被装饰”。如果这样实现的话,`decorators_list`属性就应该是一个对象,它的属性名字是方法名,值是装饰器对象的数组。 ## 策略模式 From ce36b39c4d263f6417e4bb3549ae393044fd94a8 Mon Sep 17 00:00:00 2001 From: TooBug Date: Thu, 9 May 2013 13:17:16 +0800 Subject: [PATCH 230/258] =?UTF-8?q?=E7=AD=96=E7=95=A5=E6=A8=A1=E5=BC=8F=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 293 +++++++++++++++++++++++----------------------- 1 file changed, 145 insertions(+), 148 deletions(-) diff --git a/chapter7.markdown b/chapter7.markdown index a383cf0..58789e1 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -488,42 +488,40 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 装饰器模式的第二种实现方式更简单一些,并且没有引入继承。装饰的方法也会简单。所有的工作都由“同意”被装饰的方法来做。在这个示例实现中,`getPrice()`是唯一被允许装饰的方法。如果你想有更多可以被装饰的方法,那遍历装饰器列表的工作就需要由每个方法重复去做。但是,这可以很容易地被抽象到一个辅助方法中,给它传一个方法然后使这个方法“可被装饰”。如果这样实现的话,`decorators_list`属性就应该是一个对象,它的属性名字是方法名,值是装饰器对象的数组。 - ## 策略模式 策略模式允许在运行的时候选择算法。你的代码的使用者可以在处理特定任务的时候根据即将要做的事情的上下文来从一些可用的算法中选择一个。 -使用策略模式的一个例子是解决表单验证的问题。你可以创建一个validator对象,有一个validate()方法。这个方法被调用时不用区分具体的表单类型,它总是会返回同样的结果——一个没有通过验证的列表和错误信息。 +使用策略模式的一个例子是解决表单验证的问题。你可以创建一个`validator`对象,有一个`validate()`方法。这个方法被调用时不用区分具体的表单类型,它总是会返回同样的结果——一个没有通过验证的列表和错误信息。 -但是根据具体的需要验证的表单和数据,你代码的使用者可以选择进行不同类别的检查。你的validator选择最佳的策略来处理这个任务,然后将具体的数据检查工作交给合适的算法去做。 +但是根据具体的需要验证的表单和数据,你代码的使用者可以选择进行不同类别的检查。你的`validator`选择最佳的策略来处理这个任务,然后将具体的数据检查工作交给合适的算法去做。 - ### 数据验证示例 假设你有一个下面这样的数据,它可能来自页面上的一个表单,你希望验证它是不是有效的数据: var data = { - first_name: "Super", - last_name: "Man", - age: "unknown", - username: "o_O" + first_name: "Super", + last_name: "Man", + age: "unknown", + username: "o_O" }; -对这个例子中的validator,它需要知道哪个是最佳策略,因此你需要先配置它,给它设定好规则以确定哪些是有效的数据。 +对这个例子中的`validator`而言,它需要知道哪个是最佳策略,因此你需要先配置它,给它设定好规则以确定哪些是有效的数据。 假设你不需要姓,名字可以接受任何内容,但要求年龄是一个数字,并且用户名只允许包含字母和数字。配置可能是这样的: validator.config = { - first_name: 'isNonEmpty', - age: 'isNumber', - username: 'isAlphaNum' + first_name: 'isNonEmpty', + age: 'isNumber', + username: 'isAlphaNum' }; -现在validator对象已经有了用来处理数据的配置,你可以调用validate()方法,然后将任何验证错误打印到控制台上: +现在`validator`对象已经有了用来处理数据的配置,你可以调用`validate()`方法,然后将验证错误打印到控制台上: validator.validate(data); if (validator.hasErrors()) { - console.log(validator.messages.join("\n")); + console.log(validator.messages.join("\n")); } 它可能会打印出这样的信息: @@ -531,89 +529,88 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 Invalid value for *age*, the value can only be a valid number, e.g. 1, 3.14 or 2010 Invalid value for *username*, the value can only contain characters and numbers, no special symbols -现在我们来看一下这个validator是如何实现的。所有可用的用来检查的逻辑都是拥有一个validate()方法的对象,它们还有一行辅助信息用来显示错误信息: +现在我们来看一下这个`validator`是如何实现的。所有可用的用来验证的逻辑都是拥有一个`validate()`方法的对象,它们还有一行辅助信息用来显示错误信息: - // checks for non-empty values + // 验证空值 validator.types.isNonEmpty = { - validate: function (value) { - return value !== ""; - }, - instructions: "the value cannot be empty" + validate: function (value) { + return value !== ""; + }, + instructions: "the value cannot be empty" }; - // checks if a value is a number + // 验证数字 validator.types.isNumber = { - validate: function (value) { - return !isNaN(value); - }, - instructions: "the value can only be a valid number, e.g. 1, 3.14 or 2010" + validate: function (value) { + return !isNaN(value); + }, + instructions: "the value can only be a valid number, e.g. 1, 3.14 or 2010" }; - // checks if the value contains only letters and numbers + // 验证是否只包含字母和数字 validator.types.isAlphaNum = { - validate: function (value) { - return !/[^a-z0-9]/i.test(value); - }, - instructions: "the value can only contain characters and numbers, no special symbols" + validate: function (value) { + return !/[^a-z0-9]/i.test(value); + }, + instructions: "the value can only contain characters and numbers, no special symbols" }; -最后,validator对象的核心是这样的: +最后,`validator`对象的核心是这样的: var validator = { - // all available checks - types: {}, - - // error messages in the current - // validation session - messages: [], - - // current validation config - // name: validation type - config: {}, - - // the interface method - // `data` is key => value pairs - validate: function (data) { - - var i, msg, type, checker, result_ok; - - // reset all messages - this.messages = []; - for (i in data) { - - if (data.hasOwnProperty(i)) { - - type = this.config[i]; - checker = this.types[type]; - - if (!type) { - continue; // no need to validate - } - if (!checker) { // uh-oh - throw { - name: "ValidationError", - message: "No handler to validate type " + type - }; - } - - result_ok = checker.validate(data[i]); - if (!result_ok) { - msg = "Invalid value for *" + i + "*, " + checker.instructions; - this.messages.push(msg); - } - } - } - return this.hasErrors(); - }, - - // helper - hasErrors: function () { - return this.messages.length !== 0; - } + // 所有可用的验证类型 + types: {}, + + // 本次验证所有的错误消息 + messages: [], + + // 本次验证的配置,格式为: + // name: validation type + config: {}, + + // 接口方法 + // `data` 是名值对 + validate: function (data) { + + var i, msg, type, checker, result_ok; + + // 重置所有的错误消息 + this.messages = []; + for (i in data) { + + if (data.hasOwnProperty(i)) { + + type = this.config[i]; + checker = this.types[type]; + + if (!type) { + continue; // 不需要验证 + } + if (!checker) { // 没有对应的验证类型 + throw { + name: "ValidationError", + message: "No handler to validate type " + type + }; + } + + result_ok = checker.validate(data[i]); + if (!result_ok) { + msg = "Invalid value for *" + i + "*, " + checker.instructions; + this.messages.push(msg); + } + } + } + return this.hasErrors(); + }, + + // 辅助方法 + hasErrors: function () { + return this.messages.length !== 0; + } }; -如你所见,validator对象是通用的,在所有的需要验证的场景下都可以保持这个样子。改进它的办法就是增加更多类型的检查。如果你将它用在很多页面上,每快你就会有一个非常好的验证类型的集合。然后在每个新的使用场景下你需要做的仅仅是配置validator然后调用validate()方法。 +如你所见,`validator`对象是通用的,在所有的需要验证的场景下都可以保持这个样子。改进它的办法就是增加更多类型的检查。如果你将它用在很多页面上,那么很快你就会有一个非常好的验证类型的集合。然后在新的使用场景下使用时你需要做的仅仅是配置`validator`然后调用`validate()`方法。 ## 外观模式 @@ -632,35 +629,35 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 这是两个有不同目的的相互独立的方法,他们也应该被保持独立,但与此同时,他们也经常被一起调用。所以为了不在应用中到处重复调用这两个方法,你可以创建一个外观方法来调用它们: var myevent = { - // ... - stop: function (e) { - e.preventDefault(); - e.stopPropagation(); - } - // ... + // ... + stop: function (e) { + e.preventDefault(); + e.stopPropagation(); + } + // ... }; 外观模式也适用于一些浏览器脚本的场景,即将浏览器的差异隐藏在一个外观方法下面。继续前面的例子,你可以添加一些处理IE中事件API的代码: var myevent = { - // ... - stop: function (e) { - // others - if (typeof e.preventDefault === "function") { - e.preventDefault(); - } - if (typeof e.stopPropagation === "function") { - e.stopPropagation(); - } - // IE - if (typeof e.returnValue === "boolean") { - e.returnValue = false; - } - if (typeof e.cancelBubble === "boolean") { - e.cancelBubble = true; - } - } - // ... + // ... + stop: function (e) { + // others + if (typeof e.preventDefault === "function") { + e.preventDefault(); + } + if (typeof e.stopPropagation === "function") { + e.stopPropagation(); + } + // IE + if (typeof e.returnValue === "boolean") { + e.returnValue = false; + } + if (typeof e.cancelBubble === "boolean") { + e.cancelBubble = true; + } + } + // ... }; 外观模式在做一些重新设计和重构工作时也很有用。当你想用一个不同的实现来替换某个对象的时候,你可能需要工作相当长一段时间(一个复杂的对象),与此同时,一些使用这个新对象的代码也在被同步编写。你可以先想好新对象的API,然后使用新的API创建一个外观方法在旧的对象前面。使用这种方式,当你完全替换到旧的对象的时候,你只需要修改少量客户代码,因为新的客户代码已经是在使用新的API了。 @@ -852,48 +849,48 @@ proxy对象创建了一个队列来收集50ms之内接受到的视频ID,然后 下面是proxy对象的代码: var proxy = { - ids: [], - delay: 50, - timeout: null, - callback: null, - context: null, - makeRequest: function (id, callback, context) { - // add to the queue - this.ids.push(id); - - this.callback = callback; - this.context = context; - - // set up timeout - if (!this.timeout) { - this.timeout = setTimeout(function () { - proxy.flush(); - }, this.delay); - } - }, - flush: function () { - - http.makeRequest(this.ids, "proxy.handler"); - - // clear timeout and queue - this.timeout = null; - this.ids = []; - - }, - handler: function (data) { - var i, max; - - // single video - if (parseInt(data.query.count, 10) === 1) { - proxy.callback.call(proxy.context, data.query.results.Video); - return; - } - - // multiple videos - for (i = 0, max = data.query.results.Video.length; i < max; i += 1) { - proxy.callback.call(proxy.context, data.query.results.Video[i]); - } - } + ids: [], + delay: 50, + timeout: null, + callback: null, + context: null, + makeRequest: function (id, callback, context) { + // add to the queue + this.ids.push(id); + + this.callback = callback; + this.context = context; + + // set up timeout + if (!this.timeout) { + this.timeout = setTimeout(function () { + proxy.flush(); + }, this.delay); + } + }, + flush: function () { + + http.makeRequest(this.ids, "proxy.handler"); + + // clear timeout and queue + this.timeout = null; + this.ids = []; + + }, + handler: function (data) { + var i, max; + + // single video + if (parseInt(data.query.count, 10) === 1) { + proxy.callback.call(proxy.context, data.query.results.Video); + return; + } + + // multiple videos + for (i = 0, max = data.query.results.Video.length; i < max; i += 1) { + proxy.callback.call(proxy.context, data.query.results.Video[i]); + } + } }; 了解代理模式后就在只简单地改动一下原来的代码的情况下,将多个web service请求合并为一个。 From 08ca72c28dfd66fd4867ceba15b429ee9b38c433 Mon Sep 17 00:00:00 2001 From: TooBug Date: Thu, 9 May 2013 13:21:44 +0800 Subject: [PATCH 231/258] =?UTF-8?q?=E5=A4=96=E8=A7=82=E6=A8=A1=E5=BC=8F=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/chapter7.markdown b/chapter7.markdown index 58789e1..55cb4e6 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -612,37 +612,36 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 如你所见,`validator`对象是通用的,在所有的需要验证的场景下都可以保持这个样子。改进它的办法就是增加更多类型的检查。如果你将它用在很多页面上,那么很快你就会有一个非常好的验证类型的集合。然后在新的使用场景下使用时你需要做的仅仅是配置`validator`然后调用`validate()`方法。 - ## 外观模式 -外观模式是一种很简单的模式,它只是为对象提供了更多的可供选择的接口。使方法保持短小而不是处理太多的工作是一种很好的实践。在这种实践的指导下,你会有一大堆的方法,而不是一个有着非常多参数的uber方法。有些时候,两个或者更多的方法会经常被一起调用。在这种情况下,创建另一个将这些重复调用包裹起来的方法就变得意义了。 +外观模式是一种很简单的模式,它只是为对象提供了更多的可供选择的接口。使方法保持短小而不是处理太多的工作是一种很好的实践。在这种实践的指导下,你会有一大堆的方法,而不是一个有着非常多参数的`uber`方法。有些时候,两个或者更多的方法会经常被一起调用。在这种情况下,创建另一个将这些重复调用包裹起来的方法就变得意义了。 -例如,在处理浏览器事件的时候,有以下的事件: +例如,在处理浏览器事件的时候,有以下的方法: -- stopPropagation() +- `stopPropagation()` 阻止事件冒泡到父节点 -- preventDefault() +- `preventDefault()` 阻止浏览器执行默认动作(如打开链接或者提交表单) 这是两个有不同目的的相互独立的方法,他们也应该被保持独立,但与此同时,他们也经常被一起调用。所以为了不在应用中到处重复调用这两个方法,你可以创建一个外观方法来调用它们: var myevent = { - // ... + // …… stop: function (e) { e.preventDefault(); e.stopPropagation(); } - // ... + // …… }; 外观模式也适用于一些浏览器脚本的场景,即将浏览器的差异隐藏在一个外观方法下面。继续前面的例子,你可以添加一些处理IE中事件API的代码: var myevent = { - // ... + // …… stop: function (e) { - // others + // 其它浏览器 if (typeof e.preventDefault === "function") { e.preventDefault(); } @@ -657,10 +656,10 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 e.cancelBubble = true; } } - // ... + // …… }; -外观模式在做一些重新设计和重构工作时也很有用。当你想用一个不同的实现来替换某个对象的时候,你可能需要工作相当长一段时间(一个复杂的对象),与此同时,一些使用这个新对象的代码也在被同步编写。你可以先想好新对象的API,然后使用新的API创建一个外观方法在旧的对象前面。使用这种方式,当你完全替换到旧的对象的时候,你只需要修改少量客户代码,因为新的客户代码已经是在使用新的API了。 +外观模式在做一些重新设计和重构工作时也很有用。当你想用一个不同的实现来替换某个对象的时候,你可能需要花相当长一段时间才能完成(一个复杂的对象),与此同时,一些使用这个新对象的代码也在被同步编写。你可以先想好新对象的API,然后在旧的对象前面使用新的API创建一个外观方法。使用这种方式,当你完全替换掉旧的对象的时候,你只需要修改少量的调用代码,因为新的代码已经是在使用新的API了。 ## 代理模式 From dd37788bcabc6e3e33a48ae1a446a4b99dd58c8d Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 12 May 2013 18:35:10 +0800 Subject: [PATCH 232/258] =?UTF-8?q?=E5=88=86=E7=A6=BB=20=E6=A0=A1=E5=AF=B9?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter8.markdown | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/chapter8.markdown b/chapter8.markdown index 57006b3..5b224bb 100644 --- a/chapter8.markdown +++ b/chapter8.markdown @@ -1,6 +1,6 @@ # DOM和浏览器中的模式 -在本书的前面几章中,我们主要关注了JavaScript核心(ECMAScript),并没有涉及太多关于在浏览器中使用JavaScript的内容。在本章,我们将探索一些在浏览器环境中的模式,因为这是最常见的JavaScript程序环境。浏览器脚本编程也是大部分不喜欢JavaScript的人对这门语言的认知。这当然是可以理解,因为在浏览器中有非常多不一致的宿主对象和DOM实现。很明显,任何能够减轻客户端脚本编程的痛楚的最佳初中都是大有益处的。 +在本书的前面几章中,我们主要关注了JavaScript核心(ECMAScript),并没有涉及太多关于在浏览器中使用JavaScript的内容。在本章,我们将探索一些在浏览器环境中的模式,因为这是最常见的JavaScript程序环境。浏览器脚本编程也是大部分不喜欢JavaScript的人对这门语言的认知。这当然是可以理解,因为在浏览器中有非常多不一致的宿主对象和DOM实现。很明显,任何能够减轻客户端脚本编程的痛楚的最佳实践都是大有益处的。 在本章中,你会看到一些零散的模式,包括DOM编程、事件处理、远程脚本、页面脚本的加载策略以及将JavaScript部署到生产环境的步骤。 @@ -20,7 +20,7 @@ JavaScript,用来处理用户交互和页面的动态变化 -尽可能地将这三者分离可以加强应用在各种用户代理(译注:user agent,即为用户读取页面并呈现的软件,一般指浏览器)的可到达性(译注:delivery,指可被用户代理接受并理解的程度),比如图形浏览器、纯文本浏览器、用于残障人士的辅助技术、移动设备等等。分离常常是和渐进增强的思想一起实现的,我们从一个给最简单的用户代理的最基础的体验(纯HTML)开始,当用户代理的兼容性提升时再添加更多的可以为体验加分的东西。如果浏览器支持CSS,那么用户会看到文档更好的呈现。如果浏览器支持JavaScript,那文档会更像一个应用,提供更多的特性来增强用户体验。 +尽可能地将这三者分离可以加强应用在各种用户代理(译注:user agent,即为用户读取页面并呈现的软件,一般指浏览器)的可到达性(译注:delivery,指可被用户代理接受并理解的程度),比如图形浏览器、纯文本浏览器、用于残障人士的辅助技术、移动设备等等。分离常常是和渐进增强的思想一起实现的,我们从一个最基础的体验(纯HTML)开始,它将被用于最简单的用户代理,当用户代理的兼容性提升时再添加更多的可以为体验加分的东西。如果浏览器支持CSS,那么用户会看到文档更好的呈现。如果浏览器支持JavaScript,那文档会更像一个应用,有更多用来增强用户体验的特性。 在实践中,分离意味者: @@ -29,21 +29,21 @@ - 不要使用内联的事件处理(如onclick)或者是内联的style属性,因为它们不属于内容层 - 使用语义化的HTML元素,比如头部和列表等 -JavaScript(行为)层的地位不应该很显赫,也就是说它不应该成为页面正常工作必须的东西,不应该使得用户在使用不支持的浏览器操作时存在障碍。它只应该被用来增强页面。 +JavaScript(行为)层的地位不应该很显赫,也就是说它不应该成为页面正常工作必须依赖的东西,不应该使得用户在使用不支持的浏览器操作时存在障碍。它只应该被用来增强页面。 -通常比较优雅的用来处理浏览器差异的方法是特性检测。它的思想是你不应该使用浏览器类型检测来决定代码的逻辑,而是应该检测在当前环境中你需要使用的某个方法或者是属性是否存在。浏览器检测一般认为是一种“反模式”(译注:anitpattern,指不好的模式)。虽然有的情况下不可避免要使用,但它应该是最后考虑的选择,并且应该只在特性检测没有办法给出明确答案(或者造成明显性能问题)的时候使用: +通常比较优雅的用来处理浏览器差异的方法是特性检测,它的思想是你不应该使用浏览器类型检测来决定代码的逻辑,而是应该检测在当前环境中你需要使用的某个方法或者是属性是否存在。浏览器检测一般认为是一种“反模式”,虽然有的情况下不可避免要使用,但它应该是最后才考虑的选择,并且应该只在特性检测没有办法给出明确答案(或者造成明显性能问题)的时候使用: - // antipattern + // 反模式 if (navigator.userAgent.indexOf('MSIE') !== −1) { document.attachEvent('onclick', console.log); } - // better + // 更好的方式 if (document.attachEvent) { document.attachEvent('onclick', console.log); } - // or even more specific + // 或者还可以再具体一点 if (typeof document.attachEvent !== "undefined") { document.attachEvent('onclick', console.log); } From 5d7c98d1e96e93b6827f3ddaef4d3bcc71a3360e Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 12 May 2013 19:46:50 +0900 Subject: [PATCH 233/258] =?UTF-8?q?DOM=E7=BC=96=E7=A8=8B=20=E6=A0=A1?= =?UTF-8?q?=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter8.markdown | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/chapter8.markdown b/chapter8.markdown index 5b224bb..69a9488 100644 --- a/chapter8.markdown +++ b/chapter8.markdown @@ -52,9 +52,9 @@ JavaScript(行为)层的地位不应该很显赫,也就是说它不应该 ## DOM编程 -操作页面的DOM树是在客户端JavaScript编程中最普遍的动作。这也是导致开发者头疼的最主要原因(这也导致了JavaScript名声不好),因为DOM方法在不同的浏览器中实现得有很多差异。这也是为什么使用一个抽象了浏览器差异的JavaScript库能显著提高开发速度的原因。 +操作页面的DOM树是在客户端JavaScript编程中最普遍的行为。这也是导致开发者头疼的最主要原因(这也导致了JavaScript名声不好),因为DOM方法在不同的浏览器中实现得有很多差异。这也是为什么使用一个抽象了浏览器差异的JavaScript库能显著提高开发速度的原因。 -我们来看一些在访问和修改DOM树时推荐的模式,主要考虑点是性能方面。 +我们来看一些在访问和修改DOM树时推荐的模式,主要考虑性能方面。 ### DOM访问 @@ -62,31 +62,32 @@ DOM操作性能不好,这是影响JavaScript性能的最主要原因。性能 一个原则就是DOM访问的次数应该被减少到最低,这意味者: -- 避免在环境中访问DOM +- 避免在循环中访问DOM - 将DOM引用赋给本地变量,然后操作本地变量 - 当可能的时候使用selectors API -- 遍历HTML collections时缓存length(见第2章) +- 遍历HTML collections时缓存`length`(见第二章) -看下面例子中的第二个(better)循环,尽管它看起来更长一些,但却要快上几十上百倍(取决于具体浏览器): +看下面例子中的第二个循环,尽管它看起来更长一些,但却要快上几十上百倍(取决于具体浏览器): - // antipattern + // 反模式 for (var i = 0; i < 100; i += 1) { document.getElementById("result").innerHTML += i + ", "; } - // better - update a local variable var i, content = ""; + // 更好的方式 - 更新本地变量 + var i, content = ""; for (i = 0; i < 100; i += 1) { content += i + ","; } document.getElementById("result").innerHTML += content; -在下一个代码片段中,第二个例子(使用了本地变量style)更好,尽管它需要多写一行代码,还需要多定义一个变量: +在下一个代码片段中,第二个例子(使用了本地变量`style`)更好,尽管它需要多写一行代码,还需要多定义一个变量: - // antipattern + // 反模式 var padding = document.getElementById("result").style.padding, margin = document.getElementById("result").style.margin; - // better + // 更好的方式 var style = document.getElementById("result").style, padding = style.padding, margin = style.margin; @@ -96,22 +97,22 @@ DOM操作性能不好,这是影响JavaScript性能的最主要原因。性能 document.querySelector("ul .selected"); document.querySelectorAll("#widget .class"); -这两个方法接受一个CSS选择器字符串,返回匹配这个选择器的DOM列表(译注:querySelector只返回第一个匹配的DOM)。selectors API在现代浏览器(以及IE8+)可用,它总是会比你使用其它DOM方法来做同样的选择要快。主流的JavaScript库的最近版本都已经使用了这个API,所以你有理由去检查你的项目,确保使用的是最新版本。 +这两个方法接受一个CSS选择器字符串,返回匹配这个选择器的DOM列表(译注:`querySelector`只返回第一个匹配的DOM)。selectors API在现代浏览器(以及IE8+)中可用,它总是会比你使用其它DOM方法来做同样的选择要快。主流的JavaScript库的最新版本都已经使用了这个API,所以你应该去检查你的项目,确保使用的是最新版本。 -给你经常访问的元素加上一个id属性也是有好处的,因为document.getElementById(myid)是找到一个DOM元素最容易也是最快的方法。 +给你经常访问的元素加上一个`id`属性也是有好处的,因为`document.getElementById(myid)`是找到一个DOM元素最容易也是最快的方法。 ### DOM操作 -除了访问DOM元素之外,你可能经常需要改变它们、删除其中的一些或者是添加新的元素。更新DOM会导致浏览器重绘(repaint)屏幕,也经常导致重排(reflow)(重新计算元素的位置),这些操作代价是很高的。 +除了访问DOM元素之外,你可能经常需要改变它们、删除其中的一些或者是添加新的元素。更新DOM会导致浏览器重绘(repaint)屏幕,也经常导致重排(reflow,重新计算元素的位置),这些操作代价是很高的。 -再说一次,通用的原则仍然是尽量少地更新DOM,这意味着我们可以将变化集中到一起,然后在“活动的”(live)文档树之外去执行这些变化。 +还是那句话,原则是尽量少地更新DOM,这意味着我们可以将变化集中到一起,然后在“活动的”(live)文档树之外去执行这些变化。 当你需要添加一棵相对较大的子树的时候,你应该在完成这棵树的构建之后再放到文档树中。为了达到这个目的,你可以使用文档碎片(document fragment)来包含你的节点。 不要这样添加节点: - // antipattern - // appending nodes as they are created + // 反模式 + // 在节点创建后就插入文档 var p, t; @@ -145,16 +146,16 @@ DOM操作性能不好,这是影响JavaScript性能的最主要原因。性能 document.body.appendChild(frag); -这个例子和前面例子中每段更新一次相比,文档树只被更新了一下,只导致一次重排/重绘。 +这个例子和前面例子中每段更新一次相比,文档树只被更新了一次,只导致一次重排/重绘。 当你添加新的节点到文档中时,文档碎片很有用。当你需要更新已有的节点时,你也可以将这些变化集中。你可以将你要修改的子树的父节点克隆一份,然后对克隆的这份做修改,完成之后再去替换原来的元素。 var oldnode = document.getElementById('result'), clone = oldnode.cloneNode(true); - // work with the clone... + // 修改克隆后的节点…… - // when you're done: + // 结束修改之后: oldnode.parentNode.replaceChild(clone, oldnode); ## 事件 From b0052791cab69d86a70ca276bdcd700642ab140d Mon Sep 17 00:00:00 2001 From: TooBug Date: Sun, 12 May 2013 19:00:54 +0800 Subject: [PATCH 234/258] =?UTF-8?q?=E4=BA=8B=E4=BB=B6=20=E6=A0=A1=E5=AF=B9?= =?UTF-8?q?=E4=B8=80=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter8.markdown | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/chapter8.markdown b/chapter8.markdown index 69a9488..154f414 100644 --- a/chapter8.markdown +++ b/chapter8.markdown @@ -160,13 +160,13 @@ DOM操作性能不好,这是影响JavaScript性能的最主要原因。性能 ## 事件 -在浏览器脚本编程中,另一块充满兼容性问题并且带来很多不愉快的区域就是浏览器事件,比如click,mouseover等等。同样的,一个JavaScript库可以解决支持IE(9以下)和W3C标准实现的双倍工作量。 +在浏览器脚本编程中,另一块充满兼容性问题并且带来很多不愉快的区域就是浏览器事件,比如`click`,`mouseover`等等。同样的,一个JavaScript库可以解决支持IE(9以下)和W3C标准实现带来的双倍工作量。 我们来看一下一些主要的点,因为你在做一些简单的页面或者快速开发的时候可能不会使用已有的库,当然,也有可能你正在写你自己的库。 ### 事件处理 -麻烦是从给元素绑定事件开始的。假设你有一个按钮,点击它的时候增加计数器的值。你可以添加一个内联的onclick属性,这在所有的浏览器中都能正常工作,但是会违反分离和渐进增强的思想。所以你应该尽力在JavaScript中来做绑定,而不是在标签中。 +麻烦是从给元素绑定事件开始的。假设你有一个按钮,点击它的时候增加计数器的值。你可以添加一个内联的`onclick`属性,这在所有的浏览器中都能正常工作,但是会违反分离和渐进增强的思想。所以你应该尽量在JavaScript中来做绑定,而不是在标签中。 假设你有下面的标签: @@ -174,7 +174,7 @@ DOM操作性能不好,这是影响JavaScript性能的最主要原因。性能 你可以将一个函数赋给节点的onclick属性,但你只能这样做一次: - // suboptimal solution + // 不好的解决方案 var b = document.getElementById('clickme'), count = 0; @@ -183,20 +183,20 @@ DOM操作性能不好,这是影响JavaScript性能的最主要原因。性能 b.innerHTML = "Click me: " + count; }; -如果你希望在按钮点击的时候执行好几个函数,那么在维持松耦合的情况下就不能用这种方法来做绑定。从技术上讲,你可以检测onclick是否已经包含一个函数,如果已经包含,就将它加到你自己的函数中,然后替换onclick的值为你的新函数。但是一个更干净的解决方案是使用addEventListener()方法。这个方法在IE8及以下版本中不存在,在这些浏览器需要使用attachEvent()。 +如果你希望在按钮点击的时候执行好几个函数,那么在保持松耦合的情况下就不能用这种方法来做绑定。从技术上讲,你可以检测`onclick`是否已经包含一个函数,如果已经包含,就将它加到你自己的函数中,然后替换`onclick`的值为你的新函数。但是一个更干净的解决方案是使用`addEventListener()`方法。这个方法在IE8及以下版本中不存在,在这些浏览器中需要使用`attachEvent()`。 -当我们回头看条件初始化模式(第4章)时,会发现一个示例实现是一个很好的解决跨浏览器事件监听的套件。现在我们不讨论细节,只看一下如何给我们的按钮绑定事件: +当我们回头看条件初始化模式(第四章)时,会发现其中的一个示例实现就是一个很好的解决跨浏览器事件监听的套件。现在我们不讨论细节,只看一下如何给我们的按钮绑定事件: var b = document.getElementById('clickme'); if (document.addEventListener) { // W3C b.addEventListener('click', myHandler, false); } else if (document.attachEvent) { // IE b.attachEvent('onclick', myHandler); - } else { // last resort + } else { // 为保险起见…… b.onclick = myHandler; } -现在当按钮被点击时,myHandler会被执行。让我们来让这个函数实现增加按钮文字“Click me: 0”中的数字的功能。为了更有趣一点,我们假设有好几个按钮,一个myHandler()函数来处理所有的按钮点击。如果我们可以从每次点击的事件对象中获取节点和节点对应的计数器值,那为每个按钮保持一个引用和计数器就显得不高效了。 +现在当按钮被点击时,`myHandler()`会被执行。我们来让这个函数实现增加按钮文字“Click me: 0”中的数字的功能。为了更有趣一点,我们假设有好几个按钮,一个`myHandler()`函数来处理所有的按钮点击。如果我们可以从每次点击的事件对象中获取节点和节点对应的计数器值,那为每个按钮保持一个引用和计数器就显得不高效了。 我们先看一下解决方案,稍后再来做些评论: @@ -204,16 +204,16 @@ DOM操作性能不好,这是影响JavaScript性能的最主要原因。性能 var src, parts; - // get event and source element + // 获取事件对象和事件来源 e = e || window.event; src = e.target || e.srcElement; - // actual work: update label + // 真正工作的部分:更新文字 parts = src.innerHTML.split(": "); parts[1] = parseInt(parts[1], 10) + 1; src.innerHTML = parts[0] + ": " + parts[1]; - // no bubble + // 阻止冒泡 if (typeof e.stopPropagation === "function") { e.stopPropagation(); } @@ -221,7 +221,7 @@ DOM操作性能不好,这是影响JavaScript性能的最主要原因。性能 e.cancelBubble = true; } - // prevent default action + // 阻止默认行为 if (typeof e.preventDefault === "function") { e.preventDefault(); } @@ -231,24 +231,26 @@ DOM操作性能不好,这是影响JavaScript性能的最主要原因。性能 } -一个在线的例子可以在找到。 +在线的例子可以在找到。 在这个事件处理函数中,有四个部分: -- 首先,我们需要访问事件对象,它包含事件的一些信息以及触发这个事件的页面元素。事件对象会被传到事件处理回调函数中,但是使用onclick属性时需要使用全局属性window.event来获取。 +- 首先,我们需要访问事件对象,它包含事件的一些信息以及触发这个事件的页面元素。事件对象会被传到事件处理回调函数中,但是使用`onclick`属性时需要使用全局属性`window.event`来获取 - 第二部分是真正用于更新文字的部分 -- 接下来是阻止事件冒泡。在这个例子中它不是必须的,但通常情况下,如果你不阻止的话,事件会一直冒泡到文档根元素甚至window对象。同样的,我们也需要用两种方法来阻止冒泡:W3C标准方式(stopPropagation())和IE的方式(使用cancelBubble) -- 最后,如果需要的话,阻止默认行为。有一些事件(点击链接、提交表单)有默认的行为,但你可以使用preventDefault()(IE是通过设置returnValue的值为false的方式)来阻止这些默认行为。 +- 接下来是阻止事件冒泡。在这个例子中它不是必须的,但通常情况下,如果你不阻止的话,事件会一直冒泡到文档根元素甚至`window`对象。同样的,我们也需要用两种方法来阻止冒泡:W3C标准方式(`stopPropagation()`)和IE的方式(使用`cancelBubble`) +- 最后,如果需要的话,阻止默认行为。有一些事件(点击链接、提交表单)有默认的行为,但你可以使用`preventDefault()`(IE是通过设置`returnValue`的值为`false`的方式)来阻止这些默认行为 -如你所见,这里涉及到了很多重复性的工作,所以使用第7章讨论过的外观模式创建自己的事件处理套件是很有意义的。 +如你所见,这里涉及到了很多重复性的工作,所以使用第七章讨论过的外观模式创建自己的事件处理套件是很有意义的。 ### 事件委托 -事件委托是通过事件冒泡来实现的,它可以减少分散到各个节点上的事件处理函数的数量。如果有10个按钮在一个div元素中,你可以给div绑定一个事件处理函数,而不是给每个按钮都绑定一个。 +事件委托是通过事件冒泡来实现的,它可以减少分散到各个节点上的事件处理函数的数量。如果有10个按钮在一个`div`元素中,你可以给`div`绑定一个事件处理函数,而不是给每个按钮都绑定一个。 -我们来的睦一个实例,三个按钮放在一个div元素中(图8-1)。你可以在看到这个事件委托的实例。 +我们来看一个实例,三个按钮放在一个`div`元素中(图8-1)。你可以在看到这个事件委托的实例。 -> (译注: 上面的URL中的例子在IE下单击会没有反应,问题在于使用document.attachEvernt时传递的第一个参数应该是'onclick',而不是'click'.) +> 译注: 上面的URL中的例子在IE下单击会没有反应,问题在于使用`document.attachEvernt()`时传递的第一个参数应该是`'onclick'`,而不是`'click'`。 + +------校对分隔线----- ![图8-1 事件委托示例:三个在点击时增加计数器值的按钮](./Figure/chapter8/8-1.jpg) From c18c2cd85035f5bc23413c78847eebd374826914 Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 13 May 2013 13:26:21 +0800 Subject: [PATCH 235/258] =?UTF-8?q?=E4=BB=A3=E7=90=86=E6=A8=A1=E5=BC=8F=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 74 +++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/chapter7.markdown b/chapter7.markdown index 55cb4e6..f6d5a03 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -661,25 +661,23 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 外观模式在做一些重新设计和重构工作时也很有用。当你想用一个不同的实现来替换某个对象的时候,你可能需要花相当长一段时间才能完成(一个复杂的对象),与此同时,一些使用这个新对象的代码也在被同步编写。你可以先想好新对象的API,然后在旧的对象前面使用新的API创建一个外观方法。使用这种方式,当你完全替换掉旧的对象的时候,你只需要修改少量的调用代码,因为新的代码已经是在使用新的API了。 - ## 代理模式 -在代理设计模式中,一个对象充当了另一个对象的接口的角色。它和外观模式不一样,外观模式带来的方便仅限于将几个方法调用联合起来。而代理对象位于某个对象和它的客户之间,可以保护对对象的访问。 +在代理模式中,一个对象充当了另一个对象的接口的角色。它和外观模式不一样,外观模式带来的方便仅限于将几个方法调用联合起来。而代理对象位于某个对象和它的使用者之间,可以保护对对象的访问。 -这个模式看起来开销有点大,但在出于性能考虑时非常有用。代理对象可以作为对象(也叫“真正的主体”)的保护者,让真正的主体对象做尽量少的工作。 +这个模式看起来开销有点大,但在出于性能考虑时非常有用。代理对象可以作为目标对象的保护者,让目标对象做尽量少的工作。 -一种示例用法是我们称之为“懒初始化”(延迟初始化)的东西。假设初始化真正的主体是开销很大的,并且正好客户代码将它初始化后并不真正使用它。在这种情况下,代理对象可以作为真正的主体的接口起到帮助作用。代理对象接收到初始化请求,但在真正的主体真正被使用之前都不会将它传递过去。 +一种示例用法是“懒初始化”(延迟初始化)。假设负责初始化的对象是开销很大的,并且正好使用者将它初始化后并不真正使用它。在这种情况下,代理对象可以作为目标对象的接口起到帮助作用。代理对象接收到初始化请求,但在目标对象真正被使用之前都不会将请求传递过去。 -图7-2展示了这个场景,当客户代码发出初始化请求时,代理对象回复一切就绪,但并没有将请求传递过去,只有在客户代码真正需要真正的主体做些工作的时候才将两个请求一起传递过去。 +图7-2展示了这个场景,当使用目标对象的代码发出初始化请求时,代理对象回复一切就绪,但并没有将请求传递过去,只有在真正需要目标对象做些工作的时候才将两个请求一起传递过去。 -![图7-2 通过代理对象时客户代码与真正的主体的关系](./Figure/chapter7/7-2.jpg) +![图7-2 通过代理对象时目标对象与使用者的关系](./Figure/chapter7/7-2.jpg) -图7-2 通过代理对象时客户代码与真正的主体的关系 +图7-2 通过代理对象时目标对象与使用者的关系 - ### 一个例子 -在真正的主体做某件工作开销很大时,代理模式很有用处。在web应用中,开销最大的操作之一就是网络请求,此时尽可能地合并HTTP请求是有意义的。我们来看一个这种场景下应用代理模式的实例。 +在目标对象做某件工作开销很大时,代理模式很有用处。在web应用中,开销最大的操作之一就是网络请求,此时尽可能地合并HTTP请求是有意义的。我们来看一个这种场景下应用代理模式的实例。 #### 一个视频列表(expando) @@ -697,14 +695,14 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 这个应用中最主要的角色是两个对象: -- videos +- `videos` - 负责对信息区域展开/收起(videos.getInfo()方法)和播放视频的响应(videos.getPlayer()方法) -- http + 负责对信息区域展开/收起(`videos.getInfo()`方法)和播放视频的响应(`videos.getPlayer()`方法) +- `http` - 负责通过http.makeRequest()方法与服务端通讯 + 负责通过`http.makeRequest()`方法与服务端通讯 -当没有代理对象的时候,videos.getInfo()会为每个视频调用一次http.makeRequest()方法。当我们添加代理对象proxy后,它将位于vidoes和http中间,接手对makeRequest()的调用,并在可能的时候合并请求。 +当没有代理对象的时候,`videos.getInfo()`会为每个视频调用一次`http.makeRequest()`方法。当我们添加代理对象`proxy`后,它将位于`vidoes`和`http`中间,接手对`makeRequest()`的调用,并在可能的时候合并请求。 我们首先看一下没有代理对象的代码,然后添加代理对象来提升应用的响应速度。 @@ -730,13 +728,13 @@ HTML代码仅仅是一个链接列表: #### 事件处理 -现在我们来看一下事件处理的逻辑。首先我们定义一个方便的快捷函数$: +现在我们来看一下事件处理的逻辑。首先我们定义一个方便的快捷函数`$`: var $ = function (id) { return document.getElementById(id); }; -使用事件代理(第8章有更多关于这个模式的内容),我们将所有id="vids"的条目上的点击事件统一放到一个函数中处理: +使用事件代理(第八章有更多关于这个模式的内容),我们将所有`id="vids"`的条目上的点击事件统一放到一个函数中处理: $('vids').onclick = function (e) { var src, id; @@ -764,19 +762,19 @@ HTML代码仅仅是一个链接列表: videos.getInfo(id); }; -#### videos对象 +#### `videos`对象 -videos对象有三个方法: +`videos`对象有三个方法: -- getPlayer() +- `getPlayer()` 返回播放视频需要的HTML代码(跟我们讨论的无关) -- updateList() +- `updateList()` - 网络请求的回调函数,接受从服务器返回的数据,然后生成用于视频详细信息的HTML代码。这一部分也没有什么太有趣的事情。 -- getInfo() + 网络请求的回调函数,接受从服务器返回的数据,然后生成用于视频详细信息的HTML代码。这一部分也没有什么需要关注的事情。 +- `getInfo()` - 这个方法切换视频信息的可视状态,同时也调用http对象的方法,并传递updaetList()作为回调函数。 + 这个方法切换视频信息的可视状态,同时也调用`http`对象的方法,并传递`updaetList()`作为回调函数。 下面是这个对象的代码片段: @@ -803,9 +801,9 @@ videos对象有三个方法: } }; -#### http对象 +#### `http`对象 -http对象只有一个方法,它向Yahoo!的YQL服务发起一个JSONP请求: +`http`对象只有一个方法,它向Yahoo!的YQL服务发起一个JSONP请求: var http = { makeRequest: function (ids, callback) { @@ -829,23 +827,23 @@ http对象只有一个方法,它向Yahoo!的YQL服务发起一个JSONP请求 当所有的六个视频都被选中后,将会向服务端发起六个独立的像这样的YQL请求: -select * from music.video.id where ids IN ("2158073") + select * from music.video.id where ids IN ("2158073") #### 代理对象 -前面的代码工作得很正常,但我们可以让它工作得更好。proxy对象就在这样的场景中出现,并接管了http和videos对象之间的通讯。它将使用一个简单的逻辑来尝试合并请求:50ms的延迟。videos对象并不直接调用后台接口,而是调用proxy对象的方法。proxy对象在转发这个请求前将会等待一段时间,如果在等待的50ms内有另一个来自videos的调用,则它们将被合并为同一个请求。50ms的延迟对用户来说几乎是无感知的,但是却可以用来合并请求以提升点击“toggle”时的体验,一次展开多个视频。它也可以显著降低服务器的负载,因为web服务器只需要处理更少量的请求。 +前面的代码工作得很好,但我们可以让它工作得更好。`proxy`对象就在这样的场景中出现,并接管了`http`和`videos`对象之间的通讯。它将使用一个简单的逻辑来尝试合并请求:50ms的延迟。`videos`对象并不直接调用后台接口,而是调用`proxy`对象的方法。`proxy`对象在转发这个请求前将会等待一段时间,如果在等待的50ms内有另一个来自`videos`的调用,则它们将被合并为同一个请求。50ms的延迟对用户来说几乎是无感知的,但是却可以用来合并请求以提升点击“toggle”时的体验,一次展开多个视频。它也可以显著降低服务器的负载,因为web服务器只需要处理更少量的请求。 合并后查询两个视频信息的YQL大概是这样: select * from music.video.id where ids IN ("2158073", "123456") -在修改后的代码中,唯一的变化是videos.getInfo()现在调用的是proxy.makeRequest()而不是http.makeRequest(),像这样: +在修改后的代码中,唯一的变化是`videos.getInfo()`现在调用的是`proxy.makeRequest()`而不是`http.makeRequest()`,像这样: proxy.makeRequest(id, videos.updateList, videos); -proxy对象创建了一个队列来收集50ms之内接受到的视频ID,然后将这个队列传递给http对象,并提供回调函数,因为videos.updateList()只能处理一次接收到的数据。(译注:指每次传入的回调函数只能处理当次接收到的数据。) +`proxy`对象创建了一个队列来收集50ms之内接受到的视频ID,然后将这个队列传递给`http`对象,并提供回调函数,因为`videos.updateList()`只能处理一个接收到的视频信息。 -下面是proxy对象的代码: +下面是`proxy`对象的代码: var proxy = { ids: [], @@ -854,13 +852,13 @@ proxy对象创建了一个队列来收集50ms之内接受到的视频ID,然后 callback: null, context: null, makeRequest: function (id, callback, context) { - // add to the queue + // 添加到队列 this.ids.push(id); this.callback = callback; this.context = context; - // set up timeout + // 设置延时 if (!this.timeout) { this.timeout = setTimeout(function () { proxy.flush(); @@ -871,7 +869,7 @@ proxy对象创建了一个队列来收集50ms之内接受到的视频ID,然后 http.makeRequest(this.ids, "proxy.handler"); - // clear timeout and queue + // 清除延时和队列 this.timeout = null; this.ids = []; @@ -879,20 +877,20 @@ proxy对象创建了一个队列来收集50ms之内接受到的视频ID,然后 handler: function (data) { var i, max; - // single video + // 单个视频 if (parseInt(data.query.count, 10) === 1) { proxy.callback.call(proxy.context, data.query.results.Video); return; } - // multiple videos + // 多个视频 for (i = 0, max = data.query.results.Video.length; i < max; i += 1) { proxy.callback.call(proxy.context, data.query.results.Video[i]); } } }; -了解代理模式后就在只简单地改动一下原来的代码的情况下,将多个web service请求合并为一个。 +使用代理模式可以在只改动一处原来代码的情况下,将多个web service请求合并为一个。 图7-4和7-5展示了使用代理模式将与服务器三次数据交互(不用代理模式时)变为一次交互的过程。 @@ -905,9 +903,9 @@ proxy对象创建了一个队列来收集50ms之内接受到的视频ID,然后 图7-5 通过一个代理对象合并请求,减少与服务器数据交互 -#### 使用代理对象做缓存 +### 使用代理对象做缓存 -在这个例子中,客户对象(videos)已经可以做到不对同一个对象重复发出请求。但现实情况中并不总是这样。这个代理对象还可以通过缓存之前的请求结果到cache属性中来进一步保护真正的主体http对象(图7-6)。然后当videos对象需要对同一个ID的视频请求第二次时,proxy对象可以直接从缓存中取出,从而避免一次网络交互。 +在这个例子中,目标对象的使用者(`videos`)已经可以做到不对同一个对象重复发出请求,但现实情况中并不总是这样。其实这个代理对象还可以通过缓存之前的请求结果到`cache`属性中来进一步保护`http`对象(图7-6)。然后当`videos`对象需要对同一个ID的视频请求第二次时,`proxy`对象可以直接从缓存中取出,从而避免一次网络交互。 ![图7-6 代理缓存](./Figure/chapter7/7-6.jpg) From 7f971a4a05b9138db2b1b4398b82491b4bbc2161 Mon Sep 17 00:00:00 2001 From: TooBug Date: Mon, 13 May 2013 13:34:09 +0800 Subject: [PATCH 236/258] =?UTF-8?q?=E4=B8=AD=E4=BB=8B=E8=80=85=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=20=E6=A0=A1=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/chapter7.markdown b/chapter7.markdown index f6d5a03..1d20dad 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -911,30 +911,28 @@ HTML代码仅仅是一个链接列表: 图7-6 代理缓存 - ## 中介者模式 -一个应用不论大小,都是由一些彼此独立的对象组成的。所有的对象都需要一个通讯的方式来保持可维护性,即你可以安全地修改应用的一部分而不破坏其它部分。随着应用的开发和维护,会有越来越多的对象。然后,在重构代码的时候,对象可能会被移除或者被重新安排。当对象知道其它对象的太多信息并且直接通讯(直接调用彼此的方法或者修改属性)时,会导致我们不愿意看到的紧耦合。当对象耦合很紧时,要修改一个对象而不影响其它的对象是很困难的。此时甚至连一个最简单的修改都变得不那么容易,甚至连一个修改需要用多长时间都难以评估。 +一个应用不论大小,都是由一些彼此独立的对象组成的。所有的对象都需要一个通讯方式来保持可维护性,即你可以安全地修改应用的一部分而不破坏其它部分。随着应用的开发和维护,会有越来越多的对象。然后,在重构代码的时候,对象可能会被移除或者被重新设计。当对象知道其它对象的太多信息并且直接通讯(直接调用彼此的方法或者修改属性)时,会导致我们不愿意看到的紧耦合。当对象耦合很紧时,要修改一个对象而不影响其它的对象是很困难的。此时甚至连一个最简单的修改都变得不那么容易,甚至连一个修改需要用多长时间都难以评估。 -中介者模式就是一个缓解此问题的办法,它通过解耦来提升代码的可维护性(见图7-7)。在这个模式中,各个彼此合作的对象并不直接通讯,而是通过一个mediator(中介者)对象通讯。当一个对象改变了状态后,它就通知中介者,然后中介者再将这个改变告知给其它应该知道这个变化的对象。 +中介者模式就是一个缓解此问题的办法,它通过解耦来提升代码的可维护性(见图7-7)。在这个模式中,各个彼此合作的对象并不直接通讯,而是通过一个`mediator`(中介者)对象通讯。当一个对象改变了状态后,它就通知中介者,然后中介者再将这个改变告知给其它应该知道这个变化的对象。 ![图7-7 中介者模式中的对象关系](./Figure/chapter7/7-7.jpg) 图7-7 中介者模式中的对象关系 - ### 中介者示例 我们来看一个使用中介者模式的实例。这个应用是一个游戏,它的玩法是比较两位游戏者在半分钟内按下按键的次数,次数多的获胜。玩家1需要按的是1,玩家2需要按的是0(这样他们的手指不会搅在一起)。当前分数会显示在一个计分板上。 对象列表如下: -- Player 1 -- Player 2 -- Scoreboard -- Mediator +- `Player1` +- `Player2` +- `Scoreboard` +- `Mediator` -中介者Mediator知道所有的对象。它与输入设备(键盘)打交道,处理keypress事件,决定现在是哪位玩家玩的,然后通知这个玩家(见图7-8)。玩家负责玩(即给自己的分数加一分),然后通知中介者他这一轮已经玩完。中介者再告知计分板最新的分数,计分板更新显示。 +中介者`Mediator`知道所有的对象,它与输入设备(键盘)打交道,处理`keypress`事件,决定现在是哪位玩家玩的,然后通知这个玩家(见图7-8)。玩家负责玩(即给自己的分数加一分),然后通知中介者他这一轮已经玩完。中介者再告知计分板最新的分数,计分板更新显示。 除了中介者之外,其它的对象都不知道有别的对象存在。这样就使得更新这个游戏变得很简单,比如要添加一位玩家或者是添加另外一个显示剩余时间的地方。 @@ -944,7 +942,7 @@ HTML代码仅仅是一个链接列表: 图7-8 游戏涉及的对象 -玩家对象是通过Player()构造函数来创建的,有自己的points和name属性。原型上的play()方法负责给自己加一分然后通知中介者: +玩家对象是通过`Player()`构造函数来创建的,有自己的`points`和`name`属性。原型上的`play()`方法负责给自己加一分然后通知中介者: function Player(name) { this.points = 0; @@ -955,14 +953,14 @@ HTML代码仅仅是一个链接列表: mediator.played(); }; -scoreboard对象(计分板)有一个update()方法,它会在每次玩家玩完后被中介者调用。计分板根本不知道玩家的任何信息,也不保存分数,它只负责显示中介者给过来的分数: +`scoreboard`对象(计分板)有一个`update()`方法,它会在每次玩家玩完后被中介者调用。计分板根本不知道玩家的任何信息,也不保存分数,它只负责显示中介者给过来的分数: var scoreboard = { - // HTML element to be updated + // 被更新的HTML元素 element: document.getElementById('results'), - // update the score display + // 更新分数显示 update: function (score) { var i, msg = ''; @@ -978,14 +976,14 @@ scoreboard对象(计分板)有一个update()方法,它会在每次玩家 } }; -现在我们来看一下mediator对象(中介者)。在游戏初始化的时候,在setup()方法中创建游戏者,然后放后players属性以便后续使用。played()方法会被游戏者在每轮玩完后调用,它更新score哈希然表然后将它传给scoreboard用于显示。最后一个方法是keypress(),负责处理键盘事件,决定是哪位玩家玩的,并且通知它: +现在我们来看一下`mediator`对象(中介者)。在游戏初始化的时候,在`setup()`方法中创建玩家,然后放入`players`属性以便后续使用。`played()`方法会被玩家在每轮玩完后调用,它更新`score`哈希然表然后将它传给`scoreboard`用于显示。最后一个方法是`keypress()`,负责处理键盘事件,决定是哪位玩家玩的,并且通知它: var mediator = { - // all the players + // 所有的玩家 players: {}, - // initialization + // 初始化 setup: function () { var players = this.players; players.home = new Player('Home'); @@ -993,7 +991,7 @@ scoreboard对象(计分板)有一个update()方法,它会在每次玩家 }, - // someone plays, update the score + // 玩家玩完后更新分数 played: function () { var players = this.players, score = { @@ -1004,14 +1002,14 @@ scoreboard对象(计分板)有一个update()方法,它会在每次玩家 scoreboard.update(score); }, - // handle user interactions + // 处理用户交互 keypress: function (e) { e = e || window.event; // IE - if (e.which === 49) { // key "1" + if (e.which === 49) { // 按键“1” mediator.players.home.play(); return; } - if (e.which === 48) { // key "0" + if (e.which === 48) { // 按键“0” mediator.players.guest.play(); return; } @@ -1020,11 +1018,11 @@ scoreboard对象(计分板)有一个update()方法,它会在每次玩家 最后一件事是初始化和结束游戏: - // go! + // 开始 mediator.setup(); window.onkeypress = mediator.keypress; - // game over in 30 seconds + // 游戏在30秒后结束 setTimeout(function () { window.onkeypress = null; alert('Game over!'); From ceae4a15eebff8e453d8203d48360d94ab054997 Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 14 May 2013 20:13:00 +0800 Subject: [PATCH 237/258] =?UTF-8?q?=E7=AC=AC=E4=B8=83=E7=AB=A0=20=E6=A0=A1?= =?UTF-8?q?=E5=AF=B9=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter7.markdown | 104 ++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 54 deletions(-) diff --git a/chapter7.markdown b/chapter7.markdown index 1d20dad..4e8d4c0 100644 --- a/chapter7.markdown +++ b/chapter7.markdown @@ -304,11 +304,11 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 `Object()`也是一个工厂这一事实可能没有太多实际用处,仅仅是觉得值得作为一个例子提一下,告诉我们工厂模式是随处可见的。 -## 迭代器 +## 遍历模式 -在迭代器模式中,你有一些含有有序聚合数据的对象。这些数据可能在内部用一种复杂的结构存储着,但是你希望提供一种简单的方法来访问这种结构中的每个元素。数据的使用者不需要知道你是怎样组织你的数据的,他们只需要操作一个个独立的元素。 +在遍历模式中,你有一些含有有序聚合数据的对象。这些数据可能在内部用一种复杂的结构存储着,但是你希望提供一种简单的方法来访问这种结构中的每个元素。数据的使用者不需要知道你是怎样组织你的数据的,他们只需要操作一个个独立的元素。 -在迭代器模式中,你的对象需要提供一个`next()`方法。按顺序调用`next()`方法必须返回序列中的下一个元素,但是“下一个”在你的特定的数据结构中指什么是由你自己来决定的。 +在遍历模式中,你的对象需要提供一个`next()`方法。按顺序调用`next()`方法必须返回序列中的下一个元素,但是“下一个”在你的特定的数据结构中指什么是由你自己来决定的。 假设你的对象叫`agg`,你可以通过简单地在循环中调用`next()`来访问每个数据元素,像这样: @@ -318,22 +318,22 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 console.log(element); } -在迭代器模式中,聚合对象通常也会提供一个方便的方法`hasNext()`,这样对象的使用者就可以知道他们已经获取到你数据的最后一个元素。当使用`hasNext()`来按顺序访问所有元素时,是像这样的: +在遍历模式中,聚合对象通常也会提供一个方便的方法`hasNext()`,这样对象的使用者就可以知道他们已经获取到你数据的最后一个元素。当使用`hasNext()`来按顺序访问所有元素时,是像这样的: while (agg.hasNext()) { // 访问element…… console.log(agg.next()); } -## 装饰器 +## 装饰模式 -在装饰器模式中,一些额外的功能可以在运行时被动态地添加到一个对象中。在静态的基于类的语言中,处理这个问题可能是个挑战,但是在JavaScript中,对象本来就是可变的,所以给一个对象添加额外的功能本身并不是什么问题。 +在装饰模式中,一些额外的功能可以在运行时被动态地添加到一个对象中。在静态的基于类的语言中,处理这个问题可能是个挑战,但是在JavaScript中,对象本来就是可变的,所以给一个对象添加额外的功能本身并不是什么问题。 -装饰器模式的一个很方便的特性是可以对我们需要的特性进行定制和配置。刚开始时,我们有一个拥有基本功能的对象,然后可以从可用的装饰器中去挑选一些需要用到的去增强这个对象,如果有必要的话,还可以指定增强的顺序。 +装饰模式的一个很方便的特性是可以对我们需要的特性进行定制和配置。刚开始时,我们有一个拥有基本功能的对象,然后可以从可用的装饰中去挑选一些需要用到的去增强这个对象,如果有必要的话,还可以指定增强的顺序。 ### 用法 -我们来看一下这个模式的用法示例。假设你正在做一个卖东西的web应用,每个新交易是一个新的`sale`对象。这个对象“知道”交易的价格并且可以通过调用`sale.getPrice()`方法返回。根据环境的不同,你可以开始用一些额外的功能来装饰这个对象。假设一个场景是这笔交易是发生在加拿大的一个省Québec,在这种情况下,购买者需要付联邦税和Québec省税。根据装饰器模式的用法,你需要指明使用联邦税装饰器和Québec省税装饰器来装饰这个对象。然后你还可以给这个对象装饰一些价格格式的功能。这个场景的使用方式可能是像这样: +我们来看一下这个模式的用法示例。假设你正在做一个卖东西的web应用,每个新交易是一个新的`sale`对象。这个对象“知道”交易的价格并且可以通过调用`sale.getPrice()`方法返回。根据环境的不同,你可以开始用一些额外的功能来装饰这个对象。假设一个场景是这笔交易是发生在加拿大的一个省Québec,在这种情况下,购买者需要付联邦税和Québec省税。根据装饰模式的用法,你需要指明使用联邦税装饰器和Québec省税装饰器来装饰这个对象。然后你还可以给这个对象装饰一些价格格式的功能。这个场景的使用方式可能是像这样: var sale = new Sale(100); // 价格是100美元 sale = sale.decorate('fedtax'); // 加上联邦税 @@ -352,12 +352,12 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 ### 实现 -一种实现装饰器模式的方法是让每个装饰器成为一个拥有应该被重写的方法的对象。每个装饰器实际上是继承自已经被前一个装饰器增强过的对象。装饰器的每个方法都会调用父对象(继承自的对象)的同名方法并取得值,然后做一些额外的处理。 +一种实现装饰模式的方法是让每个装饰器成为一个拥有应该被重写的方法的对象。每个装饰器实际上是继承自已经被前一个装饰器增强过的对象。装饰器的每个方法都会调用父对象(继承自的对象)的同名方法并取得值,然后做一些额外的处理。 最终的效果就是当你在第一个例子中调用`sale.getPrice()`时,实际上是在调用`money`装饰器的方法(图7-1)。但是因为每个装饰器会先调用父对象的方法,`money`的`getPrice()`先调用`quebec`的`getPrice()`,而它又会去调用`fedtax`的`getPrice()`方法,依次类推。这个链会一直走到原始的未经装饰的由`Sale()`构造函数实现的`getPrice()`。 -![图7-1 装饰器模式的实现](./Figure/chapter7/7-1.jpg) -图7-1 装饰器模式的实现 +![图7-1 装饰模式的实现](./Figure/chapter7/7-1.jpg) +图7-1 装饰模式的实现 这个实现以一个构造函数和一个原型方法开始: @@ -486,7 +486,7 @@ JavaScript没有类,所以一字一句地说单例的定义并没有什么意 return price; }; -装饰器模式的第二种实现方式更简单一些,并且没有引入继承。装饰的方法也会简单。所有的工作都由“同意”被装饰的方法来做。在这个示例实现中,`getPrice()`是唯一被允许装饰的方法。如果你想有更多可以被装饰的方法,那遍历装饰器列表的工作就需要由每个方法重复去做。但是,这可以很容易地被抽象到一个辅助方法中,给它传一个方法然后使这个方法“可被装饰”。如果这样实现的话,`decorators_list`属性就应该是一个对象,它的属性名字是方法名,值是装饰器对象的数组。 +装饰模式的第二种实现方式更简单一些,并且没有引入继承。装饰的方法也会简单。所有的工作都由“同意”被装饰的方法来做。在这个示例实现中,`getPrice()`是唯一被允许装饰的方法。如果你想有更多可以被装饰的方法,那遍历装饰器列表的工作就需要由每个方法重复去做。但是,这可以很容易地被抽象到一个辅助方法中,给它传一个方法然后使这个方法“可被装饰”。如果这样实现的话,`decorators_list`属性就应该是一个对象,它的属性名字是方法名,值是装饰器对象的数组。 ## 策略模式 @@ -1029,44 +1029,42 @@ HTML代码仅仅是一个链接列表: }, 30000); - ## 观察者模式 -观察者模式被广泛地应用于JavaScript客户端编程中。所有的浏览器事件(mouseover,keypress等)都是使用观察者模式的例子。这种模式的另一个名字叫“自定义事件”,意思是这些事件是被编写出来的,和浏览器触发的事件相对。它还有另外一个名字叫“订阅者/发布者”模式。 +观察者模式被广泛地应用于JavaScript客户端编程中。所有的浏览器事件(`mouseover`,`keypress`等)都是使用观察者模式的例子。这种模式的另一个名字叫“自定义事件”,意思是这些事件是被编写出来的,和浏览器触发的事件相对。它还有另外一个名字叫“订阅者/发布者”模式(Pub/Sub)。 -使用这个模式的最主要目的就是促进代码触解耦。在观察者模式中,一个对象订阅另一个对象的指定活动并得到通知,而不是调用另一个对象的方法。订阅者也被叫作观察者,被观察的对象叫作发布者或者被观察者(译注:subject,不知道如何翻译,第一次的时候译为“主体”,第二次译时觉得不妥,还是直接叫被观察者好了)。当一个特定的事件发生的时候,发布者会通知(调用)所有的订阅者,同时还可能以事件对象的形式传递一些消息。 +使用这个模式的最主要目的就是促进代码解耦。在观察者模式中,一个对象订阅另一个对象的指定活动并得到通知,而不是调用另一个对象的方法。订阅者也被叫作观察者,被观察的对象叫作发布者或者被观察者。当一个特定的事件发生的时候,发布者会通知(调用)所有的订阅者,同时还可能以事件对象的形式传递一些消息。 - ### 例1:杂志订阅 -为了理解观察者模式的实现方式,我们来看一个具体的例子。我们假设有一个发布者paper,它发行一份日报和一份月刊。无论是日报还是月刊发行,有一个名叫joe的订阅者都会收到通知。 +为了理解观察者模式的实现方式,我们来看一个具体的例子。我们假设有一个发布者`paper`,它发行一份日报和一份月刊。无论是日报还是月刊发行,有一个名叫`joe`的订阅者都会收到通知。 -paper对象有一个subscribers属性,它是一个数组,用来保存所有的订阅者。订阅的过程就仅仅是将订阅者放到这个数组中而已。当一个事件发生时,paper遍历这个订阅者列表,然后通知它们。通知的意思也就是调用订阅者对象的一个方法。因此,在订阅过程中,订阅者需要提供一个方法给paper对象的subscribe()。 +`paper`对象有一个`subscribers`属性,它是一个数组,用来保存所有的订阅者。订阅的过程就仅仅是将订阅者放到这个数组中而已。当一个事件发生时,`paper`遍历这个订阅者列表,然后通知它们。通知的意思也就是调用订阅者对象的一个方法。因此,在订阅过程中,订阅者需要提供一个方法给`paper`对象的`subscribe()`。 -paper对象也可以提供unsubscribe()方法,它可以将订阅者从数组中移除。paper对象的最后一个重要的方法是publish(),它负责调用订阅者的方法。总结一下,一个发布者对象需要有这些成员: +`paper`对象也可以提供`unsubscribe()`方法,它可以将订阅者从数组中移除。`paper`对象的最后一个重要的方法是`publish()`,它负责调用订阅者的方法。总结一下,一个发布者对象需要有这些成员: -- subscribers +- `subscribers` 一个数组 -- subscribe() +- `subscribe()` 将订阅者加入数组 -- unsubscribe() +- `unsubscribe()` 从数组中移除订阅者 -- publish() +- `publish()` 遍历订阅者并调用它们订阅时提供的方法 -所有三个方法都需要一个type参数,因为一个发布者可能触发好几种事件(比如同时发布杂志和报纸),而订阅者可以选择性地订阅其中的一种或几种。 +所有三个方法都需要一个`type`参数,因为一个发布者可能触发好几种事件(比如同时发布杂志和报纸),而订阅者可以选择性地订阅其中的一种或几种。 -因为这些成员对任何对象来说都是通用的,因此将它们作为独立对象的一部分提取出来是有意义的。然后,我们可以(通过混元模式)将它们复制到任何一个对象中,将这些对象转换为订阅者。 +因为这些成员对任何对象来说都是通用的,因此将它们作为一个单独的对象提取出来是有意义的。然后,我们可以(通过混元模式)将它们复制到任何一个对象中,将这些对象转换为订阅者。 -下面是这些发布者通用功能的一个示例实现,它定义了上面列出来的所有成员,还有一个辅助的visitSubscribers()方法: +下面是这些发布者通用功能的一个示例实现,它定义了上面列出来的所有成员,还有一个辅助的`visitSubscribers()`方法: var publisher = { subscribers: { - any: [] // event type: subscribers + any: [] // 对应事件类型的订阅者 }, subscribe: function (fn, type) { type = type || 'any'; @@ -1099,7 +1097,7 @@ paper对象也可以提供unsubscribe()方法,它可以将订阅者从数组 } }; -下面这个函数接受一个对象作为参数,并通过复制通用的发布者的方法将这个对象转变成发布者: +下面这个函数接受一个对象作为参数,并通过复制通用发布者的方法将这个对象转变成发布者: function makePublisher(o) { var i; @@ -1111,7 +1109,7 @@ paper对象也可以提供unsubscribe()方法,它可以将订阅者从数组 o.subscribers = {any: []}; } -现在我们来实现paper对象,它能做的事情就是发布日报和月刊: +现在我们来实现`paper`对象,它能做的事情就是发布日报和月刊: var paper = { daily: function () { @@ -1122,11 +1120,11 @@ paper对象也可以提供unsubscribe()方法,它可以将订阅者从数组 } }; -将paper对象变成发布者: +将`paper`对象变成发布者: makePublisher(paper); -现在我们有了一个发布者,让我们再来看一下订阅者对象joe,它有两个方法: +现在我们有了一个发布者,让我们再来看一下订阅者对象`joe`,它有两个方法: var joe = {
 drinkCoffee: function (paper) { @@ -1137,12 +1135,12 @@ paper对象也可以提供unsubscribe()方法,它可以将订阅者从数组 } }; -现在让joe来订阅paper: +现在让`joe`来订阅`paper`: paper.subscribe(joe.drinkCoffee); paper.subscribe(joe.sundayPreNap, 'monthly'); -如你所见,joe提供了一个当默认的any事件发生时被调用的方法,还提供了另一个当monthly事件发生时被调用的方法。现在让我们来触发一些事件: +如你所见,`joe`提供了一个当默认的`any`事件发生时被调用的方法,还提供了另一个当`monthly`事件发生时被调用的方法。现在让我们来触发一些事件: paper.daily(); paper.daily(); @@ -1156,23 +1154,23 @@ paper对象也可以提供unsubscribe()方法,它可以将订阅者从数组 Just read big news today About to fall asleep reading this interesting analysis -这里值得称道的地方就是paper对象并没有硬编码写上joe,而joe也同样没有硬编码写上paper。这里也没有知道所有事情的中介者对象。所有涉及到的对象都是松耦合的,而且在不修改代码的前提下,我们可以给paper添加更多的订阅者,同时joe也可以在任何时候取消订阅。 +这里值得称道的地方就是`paper`对象并没有硬编码写上`joe`,而`joe`也同样没有硬编码写上`paper`。这里也没有知道所有事情的中介者对象。所有涉及到的对象都是松耦合的,而且在不修改代码的前提下,我们可以给`paper`添加更多的订阅者,同时`joe`也可以在任何时候取消订阅。 -让我们更进一步,将joe也变成一个发布者。(毕竟,在博客和微博上,任何人都可以是发布者。)这样,joe变成发布者之后就可以在Twitter上更新状态: +让我们更进一步,将`joe`也变成一个发布者。(毕竟,在博客和微博上,任何人都可以是发布者。)这样,`joe`变成发布者之后就可以在Twitter上更新状态: makePublisher(joe); joe.tweet = function (msg) { this.publish(msg); }; -现在假设paper的公关部门准备通过Twitter收集读者反馈,于是它订阅了joe,提供了一个方法readTweets(): +现在假设`paper`的公关部门准备通过`Twitter`收集读者反馈,于是它订阅了`joe`,提供了一个方法`readTweets()`: paper.readTweets = function (tweet) { alert('Call big meeting! Someone ' + tweet); }; joe.subscribe(paper.readTweets); -这样每当joe发出消息时,paper就会弹出警告窗口: +这样每当`joe`发出消息时,`paper`就会弹出警告窗口: joe.tweet("hated the paper today"); @@ -1180,20 +1178,19 @@ paper对象也可以提供unsubscribe()方法,它可以将订阅者从数组 你可以在看到完整的源代码,并且在控制台中运行这个实例。 - ### 例2:按键游戏 -我们来看另一个例子。我们将实现一个和中介者模式的示例一样的按钮游戏,但这次使用观察者模式。为了让它看起来更高档,我们允许接受无限个玩家,而不限于2个。我们仍然保留用来产生玩家的Player()构造函数,也保留scoreboard对象。只有mediator会变成game对象。 +我们来看另一个例子。我们将实现一个和中介者模式的示例一样的按钮游戏,但这次使用观察者模式。为了让它看起来更高档,我们允许接受无限个玩家,而不限于2个。我们仍然保留用来产生玩家的`Player()`构造函数,也保留`scoreboard`对象,只有`mediator`会变成`game`对象。 -在中介者模式中,mediator对象知道所有涉及到的对象,并且调用它们的方法。而观察者模式中的game对象不是这样,它会让对象来订阅它们感兴趣的事件。比如,scoreboard会订阅game对象的scorechange事件。 +在中介者模式中,`mediator`对象知道所有涉及到的对象,并且调用它们的方法。而观察者模式中的`game`对象不是这样,它会让对象来订阅它们感兴趣的事件。比如,`scoreboard`会订阅`game`对象的`scorechange`事件。 -首先我们重新看一下通用的publisher对象,并且将它的接口做一点小修改以更贴近浏览器的情况: +首先我们重新看一下通用的`publisher`对象,并且将它的接口做一点小修改以更贴近浏览器的情况: -- 将publish(),subscribe(),unsubscribe()分别改为fire(),on(),remove() -- 事件的type每次都会被用到,所以把它变成三个方法的第一个参数 -- 可以给订阅者的方法额外加一个context参数,以便回调方法可以用this指向它自己所属的对象 +- 将`publish()`,`subscribe()`,`unsubscribe()`分别改为`fire()`,`on()`,`remove()` +- 事件的`type`每次都会被用到,所以把它变成三个方法的第一个参数 +- 可以给订阅者的方法额外加一个`context`参数,以便回调方法可以用`this`指向它自己所属的对象 -新的publisher对象是这样: +新的`publisher`对象是这样: var publisher = { subscribers: { @@ -1233,7 +1230,7 @@ paper对象也可以提供unsubscribe()方法,它可以将订阅者从数组 }; -新的Player()构造函数是这样: +新的`Player()`构造函数是这样: function Player(name, key) { this.points = 0; @@ -1247,11 +1244,11 @@ paper对象也可以提供unsubscribe()方法,它可以将订阅者从数组 this.fire('play', this); }; -变动的部分是这个构造函数接受key,代表这个玩家在键盘上用来按之后得分的按键。(这些键预先被硬编码过。)每次创建一个新玩家的时候,一个newplayer事件也会被触发。类似的,每次有一个玩家玩的时候,会触发play事件。 +变动的部分是这个构造函数接受`key`,代表这个玩家在键盘上用来按之后得分的按键。(这些键预先被硬编码过。)每次创建一个新玩家的时候,一个`newplayer`事件也会被触发。类似的,每次有一个玩家玩的时候,会触发`play`事件。 -scoreboard对象和原来一样,它只是简单地将当前分数显示出来。 +`scoreboard`对象和原来一样,它只是简单地将当前分数显示出来。 -game对象会关注所有的玩家,这样它就可以给出分数并且触发scorechange事件。它也会订阅浏览吕中所有的keypress事件,这样它就会知道按钮对应的玩家: +`game`对象会关注所有的玩家,这样它就可以给出分数并且触发`scorechange`事件。它也会订阅浏览器中所有的·keypress·事件,这样它就会知道按钮对应的玩家: var game = { @@ -1283,19 +1280,19 @@ game对象会关注所有的玩家,这样它就可以给出分数并且触发s } }; -用于将任意对象转变为订阅者的makePublisher()还是和之前一样。game对象会变成发布者(这样它才可以触发scorechange事件),Player.prototype也会变成发布者,以使得每个玩家对象可以触发play和newplayer事件: +用于将任意对象转变为订阅者的`makePublisher()`还是和之前一样。`game`对象会变成发布者(这样它才可以触发`scorechange`事件),`Player.prototype`也会变成发布者,以使得每个玩家对象可以触发`play`和`newplayer`事件: makePublisher(Player.prototype); makePublisher(game); -game对象订阅play和newplayer事件(以及浏览器的keypress事件),scoreboard订阅scorechange事件: +`game`对象订阅`play`和`newplayer`事件(以及浏览器的`keypress`事件),`scoreboard`订阅`scorechange`事件: Player.prototype.on("newplayer", "addPlayer", game); Player.prototype.on("play", "handlePlay", game); game.on("scorechange", scoreboard.update, scoreboard); window.onkeypress = game.handleKeypress; -如你所见,on()方法允许订阅者通过函数(scoreboard.update)或者是字符串("addPlayer")来指定回调函数。当有提供context(如game)时,才能通过字符串来指定回调函数。 +如你所见,`on()`方法允许订阅者通过函数(`scoreboard.update`)或者是字符串(`"addPlayer"`)来指定回调函数。当有提供`context`(如`game`)时,才能通过字符串来指定回调函数。 初始化的最后一点工作就是动态地创建玩家对象(以及它们对象的按键),用户想要多少个就可以创建多少个: @@ -1317,9 +1314,8 @@ game对象订阅play和newplayer事件(以及浏览器的keypress事件),s 这就是游戏的全部。你可以在看到完整的源代码并且试玩一下。 -值得注意的是,在中介者模式中,mediator对象必须知道所有的对象,然后在适当的时机去调用对应的方法。而这个例子中,game对象会显得笨一些(译注:指知道的信息少一些),游戏依赖于对象去观察特写的事件然后触发相应的动作:如scoreboard观察scorechange事件。这使得对象之间的耦合更松了(对象间知道彼此的信息越少越好),而代价则是弄清事件和订阅者之间的对应关系会更困难一些。在这个例子中,所有的订阅行为都发生在代码中的同一个地方,而随着应用规模的境长,on()可能会被在各个地方调用(如在每个对象的初始化代码中)。这使得调试更困难一些,因为没有一个集中的地方来看这些代码并理解正在发生什么事情。在观察者模式中,你将不再能看到那种从开头一直跟到结尾的顺序执行方式。 +值得注意的是,在中介者模式中,`mediator`对象必须知道所有的对象,然后在适当的时机去调用对应的方法。而这个例子中,`game`对象会显得笨一些(译注:指知道的信息少一些),游戏依赖于对象去观察特定的事件然后触发相应的动作:如`scoreboard`观察`scorechange`事件。这使得对象之间的耦合更松了(对象间知道彼此的信息越少越好),而代价则是弄清事件和订阅者之间的对应关系会更困难一些。在这个例子中,所有的订阅行为都发生在代码中的同一个地方,而随着应用规模的境长,`on()`可能会被在各个地方调用(如在每个对象的初始化代码中)。这使得调试更困难一些,因为没有一个集中的地方来看这些代码并理解正在发生什么事情。在观察者模式中,你将不再能看到那种从开头一直跟到结尾的顺序执行方式。 - ## 小结 在这章中你学习到了若干种流行的设计模式,并且也知道了如何在JavaScript中实现它们。我们讨论过的设计模式有: From 50bbc6a1a170445488ee9847cf6f0214f31abc09 Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 14 May 2013 20:17:04 +0800 Subject: [PATCH 238/258] =?UTF-8?q?=E4=BA=8B=E4=BB=B6=20=E6=A0=A1=E5=AF=B9?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter8.markdown | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/chapter8.markdown b/chapter8.markdown index 154f414..352fa36 100644 --- a/chapter8.markdown +++ b/chapter8.markdown @@ -250,8 +250,6 @@ DOM操作性能不好,这是影响JavaScript性能的最主要原因。性能 > 译注: 上面的URL中的例子在IE下单击会没有反应,问题在于使用`document.attachEvernt()`时传递的第一个参数应该是`'onclick'`,而不是`'click'`。 -------校对分隔线----- - ![图8-1 事件委托示例:三个在点击时增加计数器值的按钮](./Figure/chapter8/8-1.jpg) 图8-1 事件委托示例:三个在点击时增加计数器值的按钮 @@ -264,12 +262,12 @@ DOM操作性能不好,这是影响JavaScript性能的最主要原因。性能 -你可以给包裹按钮的div绑定一个事件处理函数,而不是给每个按钮绑定一个。然后你可以使用和前面的示例中一样的myHandler()函数,但需要修改一个小地方:你需要将你不感兴趣的点击排除掉。在这个例子中,你只关注按钮上的点击,而在同一个div中产生的其它的点击应该被忽略掉。 +你可以给包裹按钮的`div`绑定一个事件处理函数,而不是给每个按钮绑定一个。然后你可以使用和前面的示例中一样的`myHandler()`函数,但需要修改一个小地方:你需要将你不感兴趣的点击排除掉。在这个例子中,你只关注按钮上的点击,而在同一个`div`中产生的其它的点击应该被忽略掉。 -myHandler()的改变就是检查事件来源的nodeName是不是“button”: +`myHandler()`的改变就是检查事件来源的`nodeName`是不是`"button"`: - // ... - // get event and source element + // …… + // 获取事件对象和事件来源 e = e || window.event; src = e.target || e.srcElement; @@ -280,7 +278,7 @@ myHandler()的改变就是检查事件来源的nodeName是不是“button”: 事件委托的坏处是筛选容器中感兴趣的事件使得代码看起来更多了,但好处是性能的提升和更干净的代码,这个好处明显大于坏处,因此这是一种强烈推荐的模式。 -主流的JavaScript库通过提供方便的API的方式使得使用事件委托变得很容易。比如YUI3中有Y.delegate()方法,它允许你指定一个用来匹配包裹容器的CSS选择器和一个用于匹配你感兴趣的节点的CSS选择器。这很方便,因为如果事件发生在你不关心的元素上时,你的事件处理回调函数不会被调用。在这种情况下,绑定一个事件处理函数很简单: +主流的JavaScript库通过提供方便的API的方式使得使用事件委托变得很容易。比如YUI3中有`Y.delegate()`方法,它允许你指定两个CSS选择器,一个用来匹配包裹容器,一个用来匹配你感兴趣的节点。这很方便,因为如果事件发生在你不关心的元素上时,你的事件处理回调函数不会被调用。在这种情况下,绑定一个事件处理函数很简单: Y.delegate('click', myHandler, "#click-wrap", "button"); From 6a9b70be0653dd0c2682813a1b19287d8214f19a Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 14 May 2013 20:20:42 +0800 Subject: [PATCH 239/258] =?UTF-8?q?=E9=95=BF=E6=97=B6=E9=97=B4=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E7=9A=84=E8=84=9A=E6=9C=AC=20=20=E6=A0=A1=E5=AF=B9?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter8.markdown | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/chapter8.markdown b/chapter8.markdown index 352fa36..626bcf9 100644 --- a/chapter8.markdown +++ b/chapter8.markdown @@ -300,11 +300,11 @@ DOM操作性能不好,这是影响JavaScript性能的最主要原因。性能 ## 长时间运行的脚本 -你可能注意到过,有时候浏览器会提示脚本运行时间过长,询问用户是否要停止执行。这种情况你当然不希望发生在自己的应用中,不管它有多复杂。 +你可能注意到过,有时候浏览器会提示脚本运行时间过长,询问用户是否要停止执行。不管应用有多复杂,你都不希望这种情况发生在自己的应用中。 同时,如果脚本运行时间太长的话,浏览器的UI将变得没有响应,用户不能点击任何东西。这是一种很差的用户体验,应该尽量避免。 -在JavaScript中没有线程,但你可以在浏览器中使用setTimeout来模拟,或者在现代浏览器中使用web workers。 +在JavaScript中没有线程,但你可以在浏览器中使用`setTimeout()`来模拟,或者在现代浏览器中使用web workers。 ### setTimeout() @@ -314,7 +314,7 @@ DOM操作性能不好,这是影响JavaScript性能的最主要原因。性能 ### Web Workers -现代浏览器为长时间运行的脚本提供了另一种解决方案:web workers。web workers在浏览器内部提供了后台线程支持,你可以将计算量很大的部分放到一个单独的文件中,比如my_web_worker.js,然后从主程序(页面)中这样调用它: +现代浏览器为长时间运行的脚本提供了另一种解决方案:web workers。web workers在浏览器内部提供了后台线程支持,你可以将计算量很大的部分放到一个单独的文件中,比如`my_web_worker.js`,然后从主程序(页面)中这样调用它: var ww = new Worker('my_web_worker.js'); ww.onmessage = function (event) { @@ -331,14 +331,14 @@ DOM操作性能不好,这是影响JavaScript性能的最主要原因。性能 while (end) { end -= 1; tmp += end; - if (end === 5e7) { // 5e7 is the half of 1e8 + if (end === 5e7) { // 5e7是1e8的一半 postMessage('halfway there, `tmp` is now ' + tmp); } } postMessage('all done'); -web worker使用postMessage()来和调用它的程序通讯,调用者通过onmessage事件来接受更新。onmessage事件处理函数接受一个事件对象作为参数,这个对象含有一个由web worker传过来data属性。类似的,调用者(在这个例子中)也可以使用ww.postMessage()来给web worker传递数据,web worker可以通过一个onmessage事件处理函数来接受这些数据。 +web worker使用`postMessage()`来和调用它的程序通讯,调用者通过`onmessage`事件来接受更新。`onmessage`事件处理函数接受一个事件对象作为参数,这个对象含有一个由web worker传过来`data`属性。类似的,调用者(在这个例子中)也可以使用`ww.postMessage()`来给web worker传递数据,web worker可以通过一个`onmessage`事件处理函数来接受这些数据。 上面的例子会在浏览器中打印出: From 038808d37a83ef9cf0ce27e8c80b01e0f76fb76a Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 14 May 2013 22:55:55 +0800 Subject: [PATCH 240/258] =?UTF-8?q?=E8=BF=9C=E7=A8=8B=E8=84=9A=E6=9C=AC=20?= =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E5=AE=8C=E4=B8=80=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter8.markdown | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/chapter8.markdown b/chapter8.markdown index 626bcf9..835e846 100644 --- a/chapter8.markdown +++ b/chapter8.markdown @@ -351,9 +351,9 @@ web worker使用`postMessage()`来和调用它的程序通讯,调用者通过` ### XMLHttpRequest -现在,XMLHttpRequest是一个特别的对象(构造函数),绝大多数浏览器都可以用,它使得我们可以从JavaScript来发送HTTP请求。发送一个请求有以下三步: +现在,`XMLHttpRequest`是一个特别的对象(构造函数),绝大多数浏览器都可以用,它使得我们可以从JavaScript来发送HTTP请求。发送一个请求有以下三步: -1. 初始化一个XMLHttpRequest对象(简称XHR) +1. 初始化一个`XMLHttpRequest`对象(简称XHR) 2. 提供一个回调函数,供请求对象状态改变时调用 3. 发送请求 @@ -363,11 +363,11 @@ web worker使用`postMessage()`来和调用它的程序通讯,调用者通过` 但是在IE7之前的版本中,XHR的功能是使用ActiveX对象实现的,所以需要做一下兼容处理。 -第二步是给readystatechange事件提供一个回调函数: +第二步是给`readystatechange`事件提供一个回调函数: xhr.onreadystatechange = handleResponse; -最后一步是使用open()和send()两个方法触发请求。open()方法用于初始化HTTP请求的方法(如GET,POST)和URL。send()方法用于传递POST的数据,如果是GET方法,则是一个空字符串。open()方法的最后一个参数用于指定这个请求是不是异步的。异步是指浏览器在等待响应的时候不会阻塞,这明显是更好的用户体验,因此除非必须要同步,否则异步参数应该使用true: +最后一步是使用`open()`和`send()`两个方法触发请求。`open()`方法用于初始化HTTP请求的方法(如GET,POST)和URL。`send()`方法用于传递POST的数据,如果是GET方法,则是一个空字符串。`open()`方法的最后一个参数用于指定这个请求是不是异步的。异步是指浏览器在等待响应的时候不会阻塞,这明显是更好的用户体验,因此除非必须要同步,否则异步参数应该使用true: xhr.open("GET", "page.html", true); xhr.send(); @@ -382,7 +382,7 @@ web worker使用`postMessage()`来和调用它的程序通讯,调用者通过` if (typeof XMLHttpRequest === "function") { // native XHR xhr = new XMLHttpRequest(); - } else { // IE before 7 + } else { // IE7以下 for (i = 0; i < activeXids.length; i += 1) { try { xhr = new ActiveXObject(activeXids[i]); @@ -406,14 +406,14 @@ web worker使用`postMessage()`来和调用它的程序通讯,调用者通过` 代码中的一些说明: -- 因为IE6及以下版本中,创建XHR对象有一点复杂,所以我们通过一个数组列出ActiveX的名字,然后遍历这个数组,使用try-catch块来尝试创建对象。 -- 回调函数会检查xhr对象的readyState属性。这个属性有0到4一共5个值,4代表“complete”(完成)。如果状态还没有完成,我们就继续等待下一次readystatechange事件。 -- 回调函数也会检查xhr对象的status属性。这个属性和HTTP状态码对应,比如200(OK)或者是404(Not found)。我们只对状态码200感兴趣,而将其它所有的都报为错误(为了简化示例,否则需要检查其它不代表出错的状态码)。 +- 因为IE6及以下版本中,创建XHR对象有一点复杂,所以我们通过一个数组列出ActiveX的名字,然后遍历这个数组,使用`try-catch`块来尝试创建对象。 +- 回调函数会检查`xhr`对象的`readyState`属性。这个属性有0到4一共5个值,4代表“complete”(完成)。如果状态还没有完成,我们就继续等待下一次`readystatechange`事件。 +- 回调函数也会检查xhr对象的`status`属性。这个属性和HTTP状态码对应,比如200(OK)或者是404(Not found)。我们只对状态码200感兴趣,而将其它所有的都报为错误(为了简化示例,否则需要检查其它不代表出错的状态码)。 - 上面的代码会在每次创建XHR对象时检查一遍支持情况。你可以使用前面提到过的模式(如条件初始化)来重写上面的代码,使得只需要做一次检查。 ### JSONP -JSONP(JSON with padding)是另一种发起远程请求的方式。与XHR不同,它不受浏览器同源策略的限制,所以考虑到加载第三方站点的安全影响的问题,使用它时应该很谨慎。 +JSONP(JSON with padding)是另一种发起远程请求的方式。与XHR不同,它不受浏览器同源策略的限制,所以考虑到加载第三方站点内容的安全问题,使用它时应该很谨慎。 一个XHR请求的返回可以是任何类型的文档: @@ -422,26 +422,24 @@ JSONP(JSON with padding)是另一种发起远程请求的方式。与XHR不 - JSON数据(轻量、方便) - 简单的文本文件及其它 -使用JSONP的话,数据经常是被包裹在一个函数中的JSON,函数名称在请求的时候提供。 +而使用JSONP的话,返回的数据格式经常是被一个函数包裹的JSON,具体的函数名称在请求的时候提供。 JSONP的请求URL通常是像这样: http://example.org/getdata.php?callback=myHandler -getdata.php可以是任何类型的页面或者脚本。callback参数指定用来处理响应的JavaScript函数。 +`getdata.php`可以是任何类型的页面或者脚本。`callback`参数指定用来处理响应的JavaScript函数(译注:也就是前面提到的包裹JSON的函数)。 -这个URL会被放到一个动态生成的\元素中,像这样: +这个URL会被放到一个动态生成的` - // option 2 + // 第二种选择 但是,当你的目标是要构建一个高性能的web应用的时候,有些模式和考虑点还是应该知道的。 -作为题外话,来看一些比较常见的开发者会用在\元素上的属性: +作为题外话,来看一些比较常见的开发者会用在` - ... + …… @@ -666,7 +666,7 @@ script元素会阻塞页面的下载。浏览器会同时下载好几个组件 - ... + …… @@ -678,32 +678,32 @@ script元素会阻塞页面的下载。浏览器会同时下载好几个组件 My App - ... + …… ### HTTP分块 -HTTP协议支持“分块编码”。它允许将页面分成一块一块发送。所以如果你有一个很复杂的页面,你不需要将那些每个站都多多少少会有的(静态)头部信息也等到所有的服务端工作都完成后再开始发送。 +HTTP协议支持“分块编码”,它允许将页面分成一块一块发送。所以如果你有一个很复杂的页面,你不需要将那些(静态)头部信息也等到所有的服务端工作都完成后再开始发送。 -一个简单的策略是在组装页面其余部分的时候将页面\的内容作为第一块发送。也就是像这样子: +一个简单的策略是在组装页面其余部分的时候将页面``的内容作为第一块发送。也就是像这样子: My App - + - ... + …… - + -这种情况下可以做一个简单的发动,将JavaScript移回\,随着第一块一起发送。 +这种情况下可以做一个简单的改动,将JavaScript移回``,随着第一块一起发送。 -这样的话可以让服务器在拿到head区内容后就开始下载脚本文件,而此时页面的其它部分在服务端还尚未就绪: +这样的话可以让浏览器在拿到`head`区内容后就开始下载脚本文件,而此时页面的其它部分在服务端还尚未就绪: @@ -711,13 +711,13 @@ HTTP协议支持“分块编码”。它允许将页面分成一块一块发送 My App - + - ... + …… - + -一个更好的办法是使用第三块内容,让它在页面尾部,只包含脚本。如果有一些每个页面都用到的静态的头部,也可以将这部分随和一块一起发送: +一个更好的办法是使用第三块内容,让它在页面尾部,只包含脚本。如果有一些每个页面都用到的静态的头部,也可以将这部分随第一块一起发送: @@ -727,35 +727,35 @@ HTTP协议支持“分块编码”。它允许将页面分成一块一块发送 ... - + ... The full body of the page ... - + - + 这种方法很适合使用渐进增强思想的网站(关键业务不依赖JavaScript)。当HTML的第二块发送完毕的时候,浏览器已经有了一个加载、显示完毕并且可用的页面,就像禁用JavaScript时的情况。当JavaScript随着第三块到达时,它会进一步增强页面,为页面锦上添花。 -### 动态\元素实现非阻塞下载 +### 动态` - // becomes: + // 修改后的: - + 对很多应用来说,延迟加载的部分大部分情况下会比核心部分要大,因为我们关注的“行为”(比如拖放、XHR、动画)只在用户初始化之后才会发生。 @@ -841,44 +841,44 @@ frist_script是页面中一定存在的一个script标签,script是你创建 假设你页面的侧边栏上有一些tabs。点击tab会发出一个XHR请求获取内容,然后更新tab的内容,然后有一个更新的动画。如果这是页面上唯一需要XHR和动画库的地方,而用户又不点击tab的话会怎样? -下面介绍按需加载模式。你可以创建一个require()函数或者方法,它接受一个需要被加载的脚本文件的文件名,还有一个在脚本被加载完毕后执行的回调函数。 +下面介绍按需加载模式。你可以创建一个`require()`函数或者方法,它接受一个需要被加载的脚本文件的文件名,还有一个在脚本被加载完毕后执行的回调函数。 -require()函数可以被这样使用: +`require()`函数可以被这样使用: require("extra.js", function () { functionDefinedInExtraJS(); }); -我们来看一下如何实现这样一个函数。加载脚本很简单——你只需要按照动态\元素模式做就可以了。获知脚本已经加载需要一点点技巧,因为浏览器之间有差异: +我们来看一下如何实现这样一个函数。加载脚本很简单——你只需要按照动态`