├── README.md ├── Figure ├── chapter6 │ ├── 6-1.jpg │ ├── 6-2.jpg │ ├── 6-3.jpg │ ├── 6-4.jpg │ ├── 6-5.jpg │ ├── 6-6.jpg │ ├── 6-7.jpg │ ├── 6-8.jpg │ ├── 6-9.jpg │ └── 6-10.jpg └── chapter7 │ └── 7-1.jpg ├── chapter1.markdown ├── README.markdown ├── chapter3.markdown ├── chapter6.markdown ├── chapter8.markdown ├── chapter5.markdown ├── chapter2.markdown ├── chapter4.markdown └── chapter7.markdown /README.md: -------------------------------------------------------------------------------- 1 | javascript-patterns 2 | =================== -------------------------------------------------------------------------------- /Figure/chapter6/6-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayli/javascript-patterns/HEAD/Figure/chapter6/6-1.jpg -------------------------------------------------------------------------------- /Figure/chapter6/6-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayli/javascript-patterns/HEAD/Figure/chapter6/6-2.jpg -------------------------------------------------------------------------------- /Figure/chapter6/6-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayli/javascript-patterns/HEAD/Figure/chapter6/6-3.jpg -------------------------------------------------------------------------------- /Figure/chapter6/6-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayli/javascript-patterns/HEAD/Figure/chapter6/6-4.jpg -------------------------------------------------------------------------------- /Figure/chapter6/6-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayli/javascript-patterns/HEAD/Figure/chapter6/6-5.jpg -------------------------------------------------------------------------------- /Figure/chapter6/6-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayli/javascript-patterns/HEAD/Figure/chapter6/6-6.jpg -------------------------------------------------------------------------------- /Figure/chapter6/6-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayli/javascript-patterns/HEAD/Figure/chapter6/6-7.jpg -------------------------------------------------------------------------------- /Figure/chapter6/6-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayli/javascript-patterns/HEAD/Figure/chapter6/6-8.jpg -------------------------------------------------------------------------------- /Figure/chapter6/6-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayli/javascript-patterns/HEAD/Figure/chapter6/6-9.jpg -------------------------------------------------------------------------------- /Figure/chapter7/7-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayli/javascript-patterns/HEAD/Figure/chapter7/7-1.jpg -------------------------------------------------------------------------------- /Figure/chapter6/6-10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayli/javascript-patterns/HEAD/Figure/chapter6/6-10.jpg -------------------------------------------------------------------------------- /chapter1.markdown: -------------------------------------------------------------------------------- 1 | # 第一章 概述 2 | 3 | JavaScript是一门Web开发语言。起初只是用来操作网页中为数不多的元素(比如图片和表单域),但谁也没想到这门语言的成长是如此迅速。除了适用于客户端浏览器编程,如今JavaScript程序可以运行于越来越多的平台之上。你可以用它来进行服务器端开发(使用.Net或Node.js)、桌面应用程序开发(运行于桌面操作系统)、以及应用程序扩展(Firefox插件或者Photoshop扩展)、移动终端应用和纯命令行的批处理脚本。 4 | 5 | JavaScript同样是一门不寻常的语言。它没有类,许多场景中它使用函数作为一等对象。起初,许多开发者认为这门语言存在很多缺陷,但最近几年情况发生了微妙的变化。有意思的是,有一些老牌语言比如Java和PHP也已经开始添加诸如闭包和匿名函数等新特性,而闭包和匿名函数则是JavaScript程序员最愿意津津乐道的话题。 6 | 7 | JavaScript十分灵活,可以用你所熟悉的其他任何编程语言的编程风格来写JavaScript程序。但最好的方式还是拥抱它所带来的变化、学习它所特有的编程模式。 8 | 9 | ## 模式 10 | 11 | 对 “模式”的广义解释是“反复发生的事件或对象的固定用法...可以用来作为重复使用的模板或模型”(http://en.wikipedia.org/wiki/Pattern)。 12 | 13 | 在软件开发领域,模式是指常见问题的通用解决方案。模式不是简单的代码复制和粘贴,而是一种最佳实践,一种高级抽象,是解决某一类问题的范本。 14 | 15 | 识别这些模式非常重要,因为: 16 | 17 | - 这些模式提供了经过论证的最佳实践,它可以帮助我们更好的编码,避免重复制造车轮。 18 | - 这些模式提供了高一层的抽象,某个时间段内大脑只能处理一定复杂度的逻辑,因此当你处理更繁琐棘手的问题时,它会帮你理清头绪,你才不会被低级的琐事阻碍大脑思考,因为所有的细枝末节都可以被归类和切分成不同的块(模式)。 19 | - 这些模式为开发者和团队提供了沟通的渠道,团队开发者之间往往是异地协作,不会有经常面对面的沟通机会。简单的代码编写技巧和技术问题处理方式的约定(代码注释)使得开发者之间的交流更加通畅。例如,“函数立即执行”用大白话表述成“你写好一个函数后,在函数的结束花括号的后面添加一对括号,这样能在定义函数结束后马上执行这个函数”(我的天)。 20 | 21 | 本书将着重讨论下面这三种模式: 22 | 23 | - 设计模式(Design patterns) 24 | - 编码模式(Coding patterns) 25 | - 反模式(Antipatterns) 26 | 27 | 设计模式最初的定义是来自于“GoF”(四人组,94年版“设计模式”的四个作者)的一本书,这本书在1994年出版,书名全称是“设计模式:可复用面向对象软件基础”。书中列举了一些重要的设计模式,比如单体、工厂、装饰者、观察者等等。但适用于JavaScript的设计模式并不多,尽管设计模式是脱离某种语言而存在的,但通常会以某种语言做范例来讲解设计模式,这些语言多是强类型语言,比如C++和Java。有时直接将其应用于弱类型的动态语言比如JavaScript又显得捉襟见肘。通常这些设计模式都是基于语言的强类型特性以及类的继承。而JavaScript则需要某种轻型的替代方案。本书在第七章将讨论基于 JavaScript实现的一些设计模式。 28 | 29 | 编码模式更有趣一些。它们是JavaScript特有的模式和最佳实践,它利用了这门语言独有的一些特性,比如对函数的灵活运用,JavaScript编码模式是本书所要讨论的重点内容。 30 | 31 | 本书中你会偶尔读到一点关于“反模式”的内容,顾名思义,反模式具有某些负作用甚至破坏性,书中会顺便一提。反模式并不是bug或代码错误,它只是一种处理问题的对策,只是这种对策带来的麻烦远超过他们解决的问题。在示例代码中我们会对反模式做明显的标注。 32 | 33 | 34 | ## JavaScript:概念 35 | 36 | 在正式的讨论之前,应当先理清楚JavaScript中的一些重要的概念,这些概念在后续章节中会经常碰到,我们先来快速过一下。 37 | 38 | 39 | ### 面向对象 40 | 41 | JavaScript 是一门面向对象的编程语言,对于那些仓促学习JavaScript并很快丢掉它的开发者来说,这的确有点让人感到意外。你所能接触到的任何JavaScript代码片段都可以作为对象。只有五类原始类型不是对象,它们是数字、字符串、布尔值、null和undefined,前三种类型都有与之对应的包装对象(下一章会讲到)。数字、字符串和布尔值可以轻易的转换为对象类型,可以通过手动转换,也可以利用JavaScript解析器进行自动转换。 42 | 43 | 函数也是对象,也可以拥有属性和方法。 44 | 45 | 在任何语言中,最简单的操作莫过于定义变量。那么,在JavaScript中定义变量的时候,其实也在和对象打交道。首先,变量自动变为一个被称作“活动对象”的内置对象的属性(如果是全局变量的话,就变为全局对象的属性)。第二,这个变量实际上也是“伪对象”,因为它有自己的属性(属性特性),用以表示变量是否可以被修改、删除或在for-in循环中枚举。这些特性并未在ECMAScript3中作规定,而ECMAScript5中提供了一组可以修改这些特性的方法。 46 | 47 | 那么,到底什么是对象?对象能作这么多事情,那它们一定非常特别。实际上,对象是极其简单的。对象只是很多属性的集合,一个名值对的列表(在其他语言中可能被称作关联数组),这些属性也可以是函数(函数对象),这种函数我们称为“方法”。 48 | 49 | 关于对象还需要了解,我们可以随时随地修改你创建的对象(当然,ECMAScript5中提供了可阻止这些修改的API)。得到一个对象后,你可以给他添加、删除或更新成员。如果你关心私有成员和访问控制,本书中我们也会讲到相关的编程模式。 50 | 51 | 最后一个需要注意的是,对象有两大类: 52 | 53 | - 本地对象(Native):由ECMAScript标准规范定义的对象 54 | - 宿主对象(Host):由宿主环境创建的对象(比如浏览器环境) 55 | 56 | 本地对象也可以被归类为内置对象(比如Array,Date)或自定义对象(var o = {})。 57 | 58 | 宿主对象包含window和所有DOM对象。如果你想知道你是否在使用宿主对象,将你的代码迁移到一个非浏览器环境中运行一下,如果正常工作,那么你的代码只用到了本地对象。 59 | 60 | 61 | ### 无类 62 | 63 | 在本书中的许多场合都会反复碰到这个概念。JavaScript中没有类,对于其他语言的编程老手来说这个观念非常新颖,需要反复的琢磨和重新学习才能理解JavaScript只能处理对象的观念。 64 | 65 | 没有类,你的代码变得更小巧,因为你不必使用类去创建对象,看一下Java风格的对象创建: 66 | 67 | // Java object creation 68 | HelloOO hello_oo = new HelloOO(); 69 | 70 | 为了创建一个简单的对象,同样一件事情却重复做了三遍,这让这段代码看起来很“重”。而大多数情况下,我们只想让我们的对象保持简单。 71 | 72 | 在JavaScript中,你需要一个对象,就随手创建一个空对象,然后开始给这个对象添加有趣的成员。你可以给它添加原始值、函数或其他对象作为这个对象属性。“空”对象并不是真正的空,对象中存在一些内置的属性,但并没有“自有属性”。在下一章里我们对此作详细讨论。 73 | 74 | “GoF”的书中提到一条通用规则,“组合优于继承”,也就是说,如果你手头有创建这个对象所需的资源,更推荐直接将这些资源组装成你所需的对象,而不推荐先作分类再创建链式父子继承的方式来创建对象。在JavaScript中,这条规则非常容易遵守,因为JavaScript中没有类,而且对象组装无处不在。 75 | 76 | 77 | ### 原型 78 | 79 | JavaScript中的确有继承,尽管这只是一种代码重用的方式(本书有专门的一章来讨论代码重用)。继承可以有多种方式,最常用的方式就是利用原型。原型(prototype)是一个普通的对象,你所创建的每一个函数会自动带有prototype属性,这个属性指向一个空对象,这个空对象包含一个constructor属性,它指向你新建的函数而不是内置的Object(),除此之外它和通过对象直接量或Object()构造函数创建的对象没什么两样。你可以给它添加新的成员,这些成员可以被其他的对象继承,并当作其他对象的自有属性来使用。 80 | 81 | 我们会详细讨论JavaScript中的继承,现在只要记住:原型是一个对象(不是类或者其他什么特别的东西),每个函数都有一个prototype属性。 82 | 83 | 84 | ### 运行环境 85 | 86 | JavaScript程序需要一个运行环境。一个天然的运行环境就是浏览器,但这绝不是唯一的运行环境。本书所讨论的编程模式更多的和JavaScript语言核心(ECMAScript)相关,因此这些编程模式是环境无关的。有两个例外: 87 | 88 | - 第八章,这一章专门讲述浏览器相关的模式 89 | - 其他一些展示模式的实际应用的例子 90 | 91 | 运行环境会提供自己的宿主对象,这些宿主对象并未在ECMAScript标准中定义,它们的行为也是不可预知的。 92 | 93 | 94 | ## ECMAScript 5 95 | 96 | JavaScript语言的核心部分(不包含DOM、BOM和外部宿主对象)是基于ECMAScript标准(简称为ES)来实现的。其中第三版是在1999年正式颁布的,目前大多数浏览器都实现了这个版本。第四版已经废弃了。第三版颁布后十年,2009年十二月,第五版才正式颁布。 97 | 98 | 第五版增加了新的内置对象、方法和属性,但最重要的增加内容是所谓的严格模式(strict mode),这个模式移除了某些语言特性,让程序变得简单且健壮。比如,with语句的使用已经争论了很多年,如今,在ECMAScript5严格模式中使用with则会报错,而在非严格模式中则是ok的。我们通过一个指令来激活严格模式,这个指令在旧版本的语言实现中被忽略。也就是说,严格模式是向下兼容的,因为在不支持严格模式的旧浏览器中也不会报错。 99 | 100 | 对于每一个作用域(包括函数作用域、全局作用域或在eval()参数字符串的开始部分),你可以使用这种代码来激活严格模式: 101 | 102 | function my() { 103 | "use strict"; 104 | // rest of the function... 105 | } 106 | 107 | 这样就激活了严格模式,函数的执行则会被限制在语言的严格子集的范围内。对于旧浏览器来说,这句话只是一个没有赋值给任何变量的字符串,因此不会报错。 108 | 109 | 按照语言的发展计划,未来将会只保留“严格模式”。因此,现在的ES5只是一个过渡版本,它鼓励开发者使用严格模式,而非强制。 110 | 111 | 本书不会讨论ES5新增特性相关的模式,因为在本书截稿时并没有任何浏览器实现了ES5,但本书的示例代码通过一些技巧鼓励开发者向新标准转变: 112 | 113 | - 确保所提供的示例代码在严格模式下不报错 114 | - 避免使用并明确指出弃用的构造函数相关的属性和方法,比如arguments.callee 115 | - 针对ES5中的内置模式比如Object.create(),在ES3中实现等价的模式 116 | 117 | 118 | ## JSLint 119 | 120 | JavaScript是一种解释型语言,它没有静态编译时的代码检查,所以很可能将带有简单类型错误的破碎的程序部署到线上,而且往往意识不到这些错误的存在。这时我们就需要JSLint的帮助。 121 | 122 | JSLint(http://jslint.com )是一个JavaScript代码质量检测工具,它的作者是 Douglas Crockford,JSLint会对代码作扫描,并针对潜在的问题报出警告。笔者强烈推荐你在执行代码前先通过JSlint作检查。作者给出了警告:这个工具可能“会让你不爽”,但仅仅是在开始使用它的时候不爽一下而已。你会很快从你的错误中吸取教训,并学习这些成为一名专业的JavaScript程序员应当必备的好习惯。让你的代码通过JSLint的检查,这会让你对自己的代码更加有自信,因为你不用再去担心代码中某个不起眼的地方丢失了逗号或者有某种难以察觉的语法错误。 123 | 124 | 当开始下一章的学习时,你将发现JSLint会被多次提到。本书中除了讲解反模式的示例代码外(有清楚的注释说明)、所有示例代码均通过了JSLint的检查(使用JSLint的默认设置)。 125 | 126 | 127 | ## 控制台工具 128 | 129 | console对象在本书中非常常见。这个对象并不是语言的一部分,而是运行环境的一部分,目前大多数浏览器也都实现了这个对象。比如在Firefox中,它是通过Firebug扩展引入进来的。Firebug控制台工具包含UI操作界面,可以让你快速输入并测试JavaScript代码片段,同样用它可以调试当前打开的页面(图1-1)。在这里强烈推荐使用它来辅助学习。在Webkit核心的浏览器(Safari和Chrome)也提供了类似的工具,可以监控页面情况,IE从版本8开始也提供了开发者工具。 130 | 131 | 本书中大多数代码都使用console对象来输出结果,而没有使用alert()或者刷新当前页面。因为用这种方法输出结果实在太简单了。 132 | 133 | 图 1-1 使用Firebug控制台工具 134 | 135 |  136 | 137 | 我们经常使用log()方法,它将传入的参数在控制台输出,有时会用到dir(),用以将传入的对象属性枚举出来,这里是一个例子: 138 | 139 | console.log("test", 1, {}, [1,2,3]); 140 | console.dir({one: 1, two: {three: 3}}); 141 | 142 | 当你在控制台输入内容时,则不必使用console.log()。为了避免混乱,有些代码片段仍然使用console.log()作输出,并假设所有的代码片段都使用控制台来作检测: 143 | 144 | window.name === window['name']; // true 145 | 146 | 这和下面这种用法意思一样: 147 | 148 | console.log(window.name === window['name']); 149 | 150 | 这段代码在控制台中输出为true。 151 | 152 | 153 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # JavaScript Patterns 2 | 3 |  4 | 5 | **“JavaScript patterns”中译本** - 《JavaScript 模式》 6 | 7 | - 作者:[Stoyan Stefanov](http://www.phpied.com/) 8 | - 翻译:[拔赤](http://jayli.github.com/)、[goddyzhao](http://goddyzhao.me)、[TooBug](http://www.toobug.net) 9 | 10 | 偷懒是程序员的优良品质,模式则是先人们总结的偷懒招式。Stoyan Stefanov 的这本书,从 JavaScript 的实际使用场景出发,提炼了不少可以让前端们偷懒的实用招式。模式的探索、创新,将永远是程序员自我提升的一条修炼之道。值得一读。 11 | 12 | # 目录 13 | 14 | ## [第一章 概述](javascript.patterns/blob/master/chapter1.markdown) 15 | 16 | - [模式](javascript.patterns/blob/master/chapter1.markdown) 17 | - [JavaScript:概念](javascript.patterns/blob/master/chapter1.markdown#a2) 18 | - [面向对象](javascript.patterns/blob/master/chapter1.markdown#a3) 19 | - [无类](javascript.patterns/blob/master/chapter1.markdown#a4) 20 | - [原型](javascript.patterns/blob/master/chapter1.markdown#a5) 21 | - [运行环境](javascript.patterns/blob/master/chapter1.markdown#a6) 22 | - [ECMAScript 5](javascript.patterns/blob/master/chapter1.markdown#a7) 23 | - [JSLint](javascript.patterns/blob/master/chapter1.markdown#a8) 24 | - [控制台工具](javascript.patterns/blob/master/chapter1.markdown#a9) 25 | 26 | ## [第二章 高质量JavaScript基本要点](javascript.patterns/blob/master/chapter2.markdown) 27 | 28 | - [编写可维护的代码](javascript.patterns/blob/master/chapter2.markdown#a2) 29 | - [减少全局对象](javascript.patterns/blob/master/chapter2.markdown#a3) 30 | - [全局对象带来的困扰](javascript.patterns/blob/master/chapter2.markdown#a4) 31 | - [忘记var时的副作用](javascript.patterns/blob/master/chapter2.markdown#a5) 32 | - [访问全局对象](javascript.patterns/blob/master/chapter2.markdown#a6) 33 | - [单 var 模式](javascript.patterns/blob/master/chapter2.markdown#a7) 34 | - [声明提前:分散的 var 带来的问题](javascript.patterns/blob/master/chapter2.markdown#a8) 35 | - [for 循环](javascript.patterns/blob/master/chapter2.markdown#a9) 36 | - [for-in 循环](javascript.patterns/blob/master/chapter2.markdown#a10) 37 | - [(不)扩充内置原型](javascript.patterns/blob/master/chapter2.markdown#a11) 38 | - [switch 模式](javascript.patterns/blob/master/chapter2.markdown#a12) 39 | - [避免隐式类型转换](javascript.patterns/blob/master/chapter2.markdown#a13) 40 | - [避免使用 eval()](javascript.patterns/blob/master/chapter2.markdown#a14) 41 | - [使用parseInt()进行数字转换](javascript.patterns/blob/master/chapter2.markdown#a15) 42 | - [编码风格](javascript.patterns/blob/master/chapter2.markdown#a16) 43 | - [缩进](javascript.patterns/blob/master/chapter2.markdown#a17) 44 | - [花括号](javascript.patterns/blob/master/chapter2.markdown#a18) 45 | - [左花括号的放置](javascript.patterns/blob/master/chapter2.markdown#a19) 46 | - [空格](javascript.patterns/blob/master/chapter2.markdown#a20) 47 | - [命名规范](javascript.patterns/blob/master/chapter2.markdown#a21) 48 | - [构造器命名中的大小写](javascript.patterns/blob/master/chapter2.markdown#a22) 49 | - [单词分隔](javascript.patterns/blob/master/chapter2.markdown#a23) 50 | - [其他命名风格](javascript.patterns/blob/master/chapter2.markdown#a24) 51 | - [书写注释](javascript.patterns/blob/master/chapter2.markdown#a25) 52 | - [书写API文档](javascript.patterns/blob/master/chapter2.markdown#a26) 53 | - [一个例子:YUIDoc](javascript.patterns/blob/master/chapter2.markdown#a27) 54 | - [编写易读的代码](javascript.patterns/blob/master/chapter2.markdown#a28) 55 | - [相互评审](javascript.patterns/blob/master/chapter2.markdown#a29) 56 | - [生产环境中的代码压缩(Minify)](javascript.patterns/blob/master/chapter2.markdown#a30) 57 | - [运行JSLint](javascript.patterns/blob/master/chapter2.markdown#a31) 58 | - [小结](javascript.patterns/blob/master/chapter2.markdown#a32) 59 | 60 | ## [第三章 直接量和构造函数](javascript.patterns/blob/master/chapter3.markdown) 61 | 62 | - [对象直接量](javascript.patterns/blob/master/chapter3.markdown#a2) 63 | - [对象直接量语法](javascript.patterns/blob/master/chapter3.markdown#a3) 64 | - [通过构造函数创建对象](javascript.patterns/blob/master/chapter3.markdown#a4) 65 | - [获得对象的构造器](javascript.patterns/blob/master/chapter3.markdown#a5) 66 | - [自定义构造函数](javascript.patterns/blob/master/chapter3.markdown#a6) 67 | - [构造函数的返回值](javascript.patterns/blob/master/chapter3.markdown#a7) 68 | - [强制使用new的模式](javascript.patterns/blob/master/chapter3.markdown#a8) 69 | - [命名约定](javascript.patterns/blob/master/chapter3.markdown#a9) 70 | - [使用that](javascript.patterns/blob/master/chapter3.markdown#a10) 71 | - [调用自身的构造函数](javascript.patterns/blob/master/chapter3.markdown#a11) 72 | - [数组直接量](javascript.patterns/blob/master/chapter3.markdown#a12) 73 | - [数组直接量语法](javascript.patterns/blob/master/chapter3.markdown#a13) 74 | - [有意思的数组构造器](javascript.patterns/blob/master/chapter3.markdown#a14) 75 | - [检查是不是数组](javascript.patterns/blob/master/chapter3.markdown#a15) 76 | - [JSON](javascript.patterns/blob/master/chapter3.markdown#a16) 77 | - [使用JSON](javascript.patterns/blob/master/chapter3.markdown#a17) 78 | - [正则表达式直接量](javascript.patterns/blob/master/chapter3.markdown#a18) 79 | - [正则表达式直接量语法](javascript.patterns/blob/master/chapter3.markdown#a19) 80 | - [原始值的包装对象](javascript.patterns/blob/master/chapter3.markdown#a20) 81 | - [Error对象](javascript.patterns/blob/master/chapter3.markdown#a21) 82 | - [小结](javascript.patterns/blob/master/chapter3.markdown#a22) 83 | 84 | ## [第四章 函数](javascript.patterns/blob/master/chapter4.markdown) 85 | 86 | - [背景知识](javascript.patterns/blob/master/chapter4.markdown#a2) 87 | - [术语释义](javascript.patterns/blob/master/chapter4.markdown#a3) 88 | - [声明 vs 表达式:命名与提前](javascript.patterns/blob/master/chapter4.markdown#a4) 89 | - [函数的name属性](javascript.patterns/blob/master/chapter4.markdown#a5) 90 | - [函数提前](javascript.patterns/blob/master/chapter4.markdown#a6) 91 | - [回调模式](javascript.patterns/blob/master/chapter4.markdown#a7) 92 | - [一个回调的例子](javascript.patterns/blob/master/chapter4.markdown#a8) 93 | - [回调和作用域](javascript.patterns/blob/master/chapter4.markdown#a9) 94 | - [异步事件监听](javascript.patterns/blob/master/chapter4.markdown#a10) 95 | - [超时](javascript.patterns/blob/master/chapter4.markdown#a11) 96 | - [库中的回调](javascript.patterns/blob/master/chapter4.markdown#a12) 97 | - [返回函数](javascript.patterns/blob/master/chapter4.markdown#a12) 98 | - [自定义函数](javascript.patterns/blob/master/chapter4.markdown#a14) 99 | - [立即执行的函数](javascript.patterns/blob/master/chapter4.markdown#a15) 100 | - [立即执行的函数的参数](javascript.patterns/blob/master/chapter4.markdown#a16) 101 | - [立即执行的函数的返回值](javascript.patterns/blob/master/chapter4.markdown#a17) 102 | - [好处和用法](javascript.patterns/blob/master/chapter4.markdown#a18) 103 | - [立即初始化的对象](javascript.patterns/blob/master/chapter4.markdown#a19) 104 | - [条件初始化](javascript.patterns/blob/master/chapter4.markdown#a20) 105 | - [函数属性——Memoization模式](javascript.patterns/blob/master/chapter4.markdown#a21) 106 | - [配置对象](javascript.patterns/blob/master/chapter4.markdown#a22) 107 | - [柯里化 (Curry)](javascript.patterns/blob/master/chapter4.markdown#a23) 108 | - [函数应用](javascript.patterns/blob/master/chapter4.markdown#a24) 109 | - [部分应用](javascript.patterns/blob/master/chapter4.markdown#a25) 110 | - [柯里化](javascript.patterns/blob/master/chapter4.markdown#a26) 111 | - [什么时候使用柯里化](javascript.patterns/blob/master/chapter4.markdown#a27) 112 | - [小结](javascript.patterns/blob/master/chapter4.markdown#a28) 113 | 114 | ## 第五章 对象创建模式 115 | 116 | - 命名空间模式 117 | - 通用的命名空间函数 118 | - 声明依赖 119 | - 私有属性和方法 120 | - 私有成员 121 | - 特权方法 122 | - 私有化失败 123 | - 对象直接量及其私有成员 124 | - 原型及其私有成员 125 | - 将私有函数暴露为共有方法 126 | - 模块模式 127 | - 暴露模块模式 128 | - 创建构造器的模块 129 | - 在模块中引入全局上下文 130 | - 沙箱模式 131 | - 全局构造函数 132 | - 添加模块 133 | - 实现这个构造函数 134 | - 静态成员 135 | - 共有静态成员 136 | - 私有静态成员 137 | - 对象常量 138 | - 链式调用模式 139 | - 链式调用模式的利弊 140 | - method() 方法 141 | - 小节 142 | 143 | ## [第六章 代码复用模式](javascript.patterns/blob/master/chapter6.markdown#a1) 144 | 145 | - [类式继承 vs 现代继承模式](javascript.patterns/blob/master/chapter6.markdown#a2) 146 | - [类式继承的期望结果](javascript.patterns/blob/master/chapter6.markdown#a3) 147 | - [类式继承 1 ——默认模式](javascript.patterns/blob/master/chapter6.markdown#a4) 148 | - [跟踪原型链](javascript.patterns/blob/master/chapter6.markdown#a5) 149 | - [这种模式的缺点](javascript.patterns/blob/master/chapter6.markdown#a6) 150 | - [类式继承 2 ——借用构造函数](javascript.patterns/blob/master/chapter6.markdown#a7) 151 | - [原型链](javascript.patterns/blob/master/chapter6.markdown#a8) 152 | - [利用借用构造函数模式实现多继承](javascript.patterns/blob/master/chapter6.markdown#a9) 153 | - [借用构造函数的利与弊](javascript.patterns/blob/master/chapter6.markdown#a10) 154 | - [类式继承 3 ——借用并设置原型](javascript.patterns/blob/master/chapter6.markdown#a11) 155 | - [类式继承 4 ——共享原型](javascript.patterns/blob/master/chapter6.markdown#a12) 156 | - [类式继承 5 —— 临时构造函数](javascript.patterns/blob/master/chapter6.markdown#a13) 157 | - [存储父类](javascript.patterns/blob/master/chapter6.markdown#a14) 158 | - [重置构造函数引用](javascript.patterns/blob/master/chapter6.markdown#a15) 159 | - [Klass](javascript.patterns/blob/master/chapter6.markdown#a16) 160 | - [原型继承](javascript.patterns/blob/master/chapter6.markdown#a17) 161 | - [讨论](javascript.patterns/blob/master/chapter6.markdown#a18) 162 | - [例外的ECMAScript 5](javascript.patterns/blob/master/chapter6.markdown#a19) 163 | - [通过复制属性继承](javascript.patterns/blob/master/chapter6.markdown#a20) 164 | - [混元(Mix-ins)](javascript.patterns/blob/master/chapter6.markdown#a21) 165 | - [借用方法](javascript.patterns/blob/master/chapter6.markdown#a22) 166 | - [例:从数组借用](javascript.patterns/blob/master/chapter6.markdown#a23) 167 | - [借用并绑定](javascript.patterns/blob/master/chapter6.markdown#a24) 168 | - [Function.prototype.bind()](javascript.patterns/blob/master/chapter6.markdown#a25) 169 | - [小结](javascript.patterns/blob/master/chapter6.markdown#a26) 170 | 171 | ## [第七章 设计模式](javascript.patterns/blob/master/chapter7.markdown#a1) 172 | 173 | - [单例](javascript.patterns/blob/master/chapter7.markdown#a2) 174 | - [使用new](javascript.patterns/blob/master/chapter7.markdown#a3) 175 | - [将实例放到静态属性中](javascript.patterns/blob/master/chapter7.markdown#a4) 176 | - [将实例放到闭包中](javascript.patterns/blob/master/chapter7.markdown#a5) 177 | - [工厂模式](javascript.patterns/blob/master/chapter7.markdown#a6) 178 | - [内置对象工厂](javascript.patterns/blob/master/chapter7.markdown#a7) 179 | - [迭代器](javascript.patterns/blob/master/chapter7.markdown#a8) 180 | - [装饰器](javascript.patterns/blob/master/chapter7.markdown#a9) 181 | - [用法](javascript.patterns/blob/master/chapter7.markdown#a10) 182 | - [实现](javascript.patterns/blob/master/chapter7.markdown#a11) 183 | - [使用列表实现](javascript.patterns/blob/master/chapter7.markdown#a12) 184 | - [策略模式](javascript.patterns/blob/master/chapter7.markdown#a13) 185 | - [数据验证示例](javascript.patterns/blob/master/chapter7.markdown#a14) 186 | - [外观模式](javascript.patterns/blob/master/chapter7.markdown#a15) 187 | - [代理模式](javascript.patterns/blob/master/chapter7.markdown#a16) 188 | - [一个例子](javascript.patterns/blob/master/chapter7.markdown#a17) 189 | - [中介者模式](javascript.patterns/blob/master/chapter7.markdown#a18) 190 | - [中介者示例](javascript.patterns/blob/master/chapter7.markdown#a19) 191 | - [观察者模式](javascript.patterns/blob/master/chapter7.markdown#a20) 192 | - [例1:杂志订阅](javascript.patterns/blob/master/chapter7.markdown#a21) 193 | - [例2:按键游戏](javascript.patterns/blob/master/chapter7.markdown#a22) 194 | - [小结](javascript.patterns/blob/master/chapter7.markdown#a23) 195 | 196 | ## 第八章 DOM和浏览器模式 197 | 198 | - 分离关注点 199 | - DOM 脚本编程 200 | - DOM访问 201 | - DOM操作 202 | - 事件 203 | - 事件处理 204 | - 事件委托 205 | - 长时间运行的脚本 206 | - setTimeout() 207 | - Web Workers 208 | - 远程脚本 209 | - XMLHttpRequest 210 | - JSONP 211 | - Frame和Image加载指示器 212 | - 部署JavaScript 213 | - 合并脚本 214 | - 代码减肥和压缩 215 | - 过期头 216 | - 使用CDN 217 | - 加载策略 218 | - script标签的位置 219 | - HTTP 分块 220 | - 动态插入script标签非阻塞载入脚本 221 | - 延迟加载 222 | - 按需加载 223 | - 预加载 224 | - 小节 225 | 226 | ## 索引 227 | 228 | -------------------------------------------------------------------------------- /chapter3.markdown: -------------------------------------------------------------------------------- 1 | 2 | # 第三章 直接量和构造函数 3 | 4 | JavaScript中的直接量模式更加简洁、富有表现力,且在定义对象时不容易出错。本章将对直接量展开讨论,包括对象、数组和正则表达式直接量,以及为什么要优先使用它们而不是如`Object()`和`Array()`这些等价的内置构造器函数。本章同样会介绍JSON格式,JSON是使用数组和对象直接量的形式定义的一种数据转换格式。本章还会讨论自定义构造函数,包括如何强制使用new以确保构造函数的正确执行。 5 | 6 | 本章还会补充讲述一些基础知识,比如内置包装对象Number()、String()和Boolean(),以及如何将它们和原始值(数字、字符串和布尔值)比较。最后,快速介绍一下Error()构造函数的用法。 7 | 8 | 9 | ## 对象直接量 10 | 11 | 我们可以将JavaScript中的对象简单的理解为名值对组成的散列表(hash table),在其他编程语言中被称作“关联数组”。其中的值可以是原始值也可以是对象,不管是什么类型,它们都是“属性”(properties),属性值同样可以是函数,这时属性就被称为“方法”(methods)。 12 | 13 | JavaScript中自定义的对象(用户定义的本地对象)任何时候都是可变的。内置本地对象的属性也是可变的。你可以先创建一个空对象,然后在需要时给它添加功能。“对象直接量写法(object literal notation)”是按需创建对象的一种理想方式。 14 | 15 | 看一下这个例子: 16 | 17 | // start with an empty object 18 | var dog = {}; 19 | 20 | // add one property 21 | dog.name = "Benji"; 22 | 23 | // now add a method 24 | dog.getName = function () { 25 | return dog.name; 26 | }; 27 | 28 | 在这个例子中,我们首先定义了一个空对象,然后添加了一个属性和一个方法,在程序的生命周期内的任何时刻都可以: 29 | 30 | 1.更改属性和方法的值,比如: 31 | 32 | dog.getName = function () { 33 | // redefine the method to return 34 | // a hardcoded value 35 | return "Fido"; 36 | }; 37 | 38 | 2.完全删除属性/方法 39 | 40 | delete dog.name; 41 | 42 | 3.添加更多的属性和方法 43 | 44 | dog.say = function () { 45 | return "Woof!"; 46 | }; 47 | dog.fleas = true; 48 | 49 | 其实不必每次开始都创建空对象,对象直接量模式可以直接在创建对象时添加功能,就像下面这个例子所展示的: 50 | 51 | var dog = { 52 | name: "Benji", 53 | getName: function () { 54 | return this.name; 55 | } 56 | }; 57 | 58 | > 在本书中多次提到“空对象”(“blank object”和“empty object”)。这只是某种简称,要知道JavaScript中根本不存在真正的空对象,理解这一点至关重要。即使最简单的`{}`对象包含从Object.prototype继承来的属性和方法。我们提到的“空(empty)对象”只是说这个对象没有自己的属性,不考虑它是否有继承来的属性。 59 | 60 | 61 | ### 对象直接量语法 62 | 63 | 如果你从来没有接触过对象直接量写法,第一次碰到可能会感觉怪怪的。但越到后来你就越喜欢它。本质上讲,对象直接量语法包括: 64 | 65 | - 将对象主体包含在一对花括号内(`{` and `}`)。 66 | - 对象内的属性或方法之间使用逗号分隔。最后一个名值对后也可以有逗号,但在IE下会报错,所以尽量不要在最后一个属性或方法后加逗号。 67 | - 属性名和值之间使用冒号分隔 68 | - 如果将对象赋值给一个变量,不要忘了在右括号`}`之后补上分号 69 | 70 | 71 | ### 通过构造函数创建对象 72 | 73 | JavaScript中没有类的概念,这给JavaScript带来了极大的灵活性,因为你不必提前知晓关于对象的任何信息,也不需要类的“蓝图”。但JavaScript同样具有构造函数,它的语法和Java或其他语言中基于类的对象创建非常类似。 74 | 75 | 你可以使用自定义的构造函数来创建实例对象,也可以使用内置构造函数来创建,比如Object()、Date()、String()等等。 76 | 77 | 下面这个例子展示了用两种等价的方法分别创建两个独立的实例对象: 78 | 79 | // one way -- using a literal 80 | var car = {goes: "far"}; 81 | 82 | // another way -- using a built-in constructor 83 | // warning: this is an antipattern 84 | var car = new Object(); 85 | car.goes = "far"; 86 | 87 | 从这个例子中可以看到,直接量写法的一个明显优势是,它的代码更少。“创建对象的最佳模式是使用直接量”还有一个原因,它可以强调对象就是一个简单的可变的散列表,而不必一定派生自某个类。 88 | 89 | 另外一个使用直接量而不是Object构造函数创建实例对象的原因是,对象直接量不需要“作用域解析”(scope resolution)。因为新创建的实例有可能包含了一个本地的构造函数,当你调用Object()的时候,解析器需要顺着作用域链从当前作用域开始查找,直到找到全局Object构造函数为止。 90 | 91 | 92 | ### 获得对象的构造器 93 | 94 | 创建实例对象时能用对象直接量就不要使用new Object()构造函数,但有时你希望能继承别人写的代码,这时就需要了解构造函数的一个“特性”(也是不使用它的另一个原因),就是Object()构造函数可以接收参数,通过参数的设置可以把实例对象的创建委托给另一个内置构造函数,并返回另外一个实例对象,而这往往不是你所希望的。 95 | 96 | 下面的示例代码中展示了给new Object()传入不同的参数:数字、字符串和布尔值,最终得到的对象都是由不同的构造函数生成的: 97 | 98 | // Warning: antipatterns ahead 99 | 100 | // an empty object 101 | var o = new Object(); 102 | console.log(o.constructor === Object); // true 103 | 104 | // a number object 105 | var o = new Object(1); 106 | console.log(o.constructor === Number); // true 107 | console.log(o.toFixed(2)); // "1.00" 108 | 109 | // a string object 110 | var o = new Object("I am a string"); 111 | console.log(o.constructor === String); // true 112 | // normal objects don't have a substring() 113 | // method but string objects do 114 | console.log(typeof o.substring); // "function" 115 | 116 | // a boolean object 117 | var o = new Object(true); 118 | console.log(o.constructor === Boolean); // true 119 | 120 | Object()构造函数的这种特性会导致一些意想不到的结果,特别是当参数不确定的时候。最后再次提醒不要使用new Object(),尽可能的使用对象直接量来创建实例对象。 121 | 122 | 123 | ## 自定义构造函数 124 | 125 | 除了对象直接量和内置构造函数之外,你也可以通过自定义的构造函数来创建实例对象,正如下面的代码所示: 126 | 127 | var adam = new Person("Adam"); 128 | adam.say(); // "I am Adam" 129 | 130 | 这里用了“类”Person创建了实例,这种写法看起来很像Java中的实例创建。两者的语法的确非常接近,但实际上JavaScript中没有类的概念,Person是一个函数。 131 | 132 | Person构造函数是如何定义的呢?看下面的代码: 133 | 134 | var Person = function (name) { 135 | this.name = name; 136 | this.say = function () { 137 | return "I am " + this.name; 138 | }; 139 | }; 140 | 141 | 当你通过关键字new来调用这个构造函数时,函数体内将发生这些事情: 142 | 143 | - 创建一个空对象,将它的引用赋给this,继承函数的原型。 144 | - 通过this将属性和方法添加至这个对象 145 | - 最后返回this指向的新对象(如果没有手动返回其他的对象) 146 | 147 | 用代码表示这个过程如下: 148 | 149 | var Person = function (name) { 150 | // create a new object 151 | // using the object literal 152 | // var this = {}; 153 | 154 | // add properties and methods 155 | this.name = name; 156 | this.say = function () { 157 | return "I am " + this.name; 158 | }; 159 | 160 | //return this; 161 | }; 162 | 163 | 正如这段代码所示,say()方法添加至this中,结果是,不论何时调用new Person(),在内存中都会创建一个新函数(译注:所有Person的实例对象中的方法都是独占一块内存的)。显然这是效率很低的,因为所有实例的say()方法是一模一样的,因此没有必要“拷贝”多份。最好的办法是将方法添加至Person的原型中。 164 | 165 | Person.prototype.say = function () { 166 | return "I am " + this.name; 167 | }; 168 | 169 | 我们将会在下一章里详细讨论原型和继承。现在只要记住将需要重用的成员和方法放在原型里即可。 170 | 171 | 关于构造函数的内部工作机制也会在后续章节中有更细致的讨论。这里我们只做概要的介绍。刚才提到,构造函数执行的时候,首先创建一个新对象,并将它的引用赋给this: 172 | 173 | // var this = {}; 174 | 175 | 事实并不完全是这样,因为“空”对象并不是真的空,这个对象继承了Person的原型,看起来更像: 176 | 177 | // var this = Object.create(Person.prototype); 178 | 179 | 在后续章节会进一步讨论Object.create()。 180 | 181 | 182 | ### 构造函数的返回值 183 | 184 | 用new调用的构造函数总是会返回一个对象,默认返回this所指向的对象。如果构造函数内没有给this赋任何属性,则返回一个“空”对象(除了继承构造函数的原型之外,没有“自己的”属性)。 185 | 186 | 尽管我们不会在构造函数内写return语句,也会隐式返回this。但我们是可以返回任意指定的对象的,在下面的例子中就返回了新创建的that对象。 187 | 188 | var Objectmaker = function () { 189 | 190 | // this `name` property will be ignored 191 | // because the constructor 192 | // decides to return another object instead 193 | this.name = "This is it"; 194 | 195 | // creating and returning a new object 196 | var that = {}; 197 | that.name = "And that's that"; 198 | return that; 199 | }; 200 | 201 | // test 202 | var o = new Objectmaker(); 203 | console.log(o.name); // "And that's that" 204 | 205 | 我们看到,构造函数中其实是可以返回任意对象的,只要你返回的东西是对象即可。如果返回值不是对象(字符串、数字或布尔值),程序不会报错,但这个返回值被忽略,最终还是返回this所指的对象。 206 | 207 | 208 | ## 强制使用new的模式 209 | 210 | 我们知道,构造函数和普通的函数无异,只是通过new调用而已。那么如果调用构造函数时忘记new会发生什么呢?漏掉new不会产生语法错误也不会有运行时错误,但可能会造成逻辑错误,导致执行结果不符合预期。这是因为如果不写new的话,函数内的this会指向全局对象(在浏览器端this指向window)。 211 | 212 | 当构造函数内包含this.member之类的代码,并直接调用这个函数(省略new),实际会创建一个全局对象的属性member,可以通过window.member或member访问到它。这必然不是我们想要的结果,因为我们要努力确保全局命名空间的整洁干净。 213 | 214 | // constructor 215 | function Waffle() { 216 | this.tastes = "yummy"; 217 | } 218 | 219 | // a new object 220 | var good_morning = new Waffle(); 221 | console.log(typeof good_morning); // "object" 222 | console.log(good_morning.tastes); // "yummy" 223 | 224 | // antipattern: 225 | // forgotten `new` 226 | var good_morning = Waffle(); 227 | console.log(typeof good_morning); // "undefined" 228 | console.log(window.tastes); // "yummy" 229 | 230 | ECMAScript5中修正了这种非正常的行为逻辑。在严格模式中,this是不能指向全局对象的。如果在不支持ES5的JavaScript环境中,仍然有很多方法可以确保构造函数的行为即便在省略new调用时也不会出问题。 231 | 232 | 233 | ### 命名约定 234 | 235 | 最简单的选择是使用命名约定,前面的章节已经提到,构造函数名首字母大写(MyConstructor),普通函数和方法名首字母小写(myFunction)。 236 | 237 | 238 | ### 使用that 239 | 240 | 遵守命名约定的确能帮上一些忙,但约定毕竟不是强制,不能完全避免出错。这里给出了一种模式可以确保构造函数一定会按照构造函数的方式执行。不要将所有成员挂在this上,将它们挂在that上,并返回that。 241 | 242 | function Waffle() { 243 | var that = {}; 244 | that.tastes = "yummy"; 245 | return that; 246 | } 247 | 248 | 如果要创建简单的实例对象,甚至不需要定义一个局部变量that,可以直接返回一个对象直接量,就像这样: 249 | 250 | function Waffle() { 251 | return { 252 | tastes: "yummy" 253 | }; 254 | } 255 | 256 | 不管用什么方式调用它(使用new或直接调用),它同都会返回一个实例对象: 257 | 258 | var first = new Waffle(), 259 | second = Waffle(); 260 | console.log(first.tastes); // "yummy" 261 | console.log(second.tastes); // "yummy" 262 | 263 | 这种模式的问题是丢失了原型,因此在Waffle()的原型上的成员不会继承到这些实例对象中。 264 | 265 | > 需要注意的是,这里用的that只是一种命名约定,that不是语言的保留字,可以将它替换为任何你喜欢的名字,比如self或me。 266 | 267 | 268 | ### 调用自身的构造函数 269 | 270 | 为了解决上述模式的问题,能够让实例对象继承原型属性,我们使用下面的方法。在构造函数中首先检查this是否是构造函数的实例,如果不是,再通过new调用构造函数,并将new的结果返回: 271 | 272 | function Waffle() { 273 | 274 | if (!(this instanceof Waffle)) { 275 | return new Waffle(); 276 | } 277 | this.tastes = "yummy"; 278 | 279 | } 280 | Waffle.prototype.wantAnother = true; 281 | 282 | // testing invocations 283 | var first = new Waffle(), 284 | second = Waffle(); 285 | 286 | console.log(first.tastes); // "yummy" 287 | console.log(second.tastes); // "yummy" 288 | 289 | console.log(first.wantAnother); // true 290 | console.log(second.wantAnother); // true 291 | 292 | 另一种检查实例的通用方法是使用arguments.callee,而不是直接将构造函数名写死在代码中: 293 | 294 | if (!(this instanceof arguments.callee)) { 295 | return new arguments.callee(); 296 | } 297 | 298 | 这里需要说明的是,在任何函数内部都会自行创建一个arguments对象,它包含函数调用时传入的参数。同时arguments包含一个callee属性,指向它所在的正在被调用的函数。需要注意,ES5严格模式中是禁止使用arguments.callee的,因此最好对它的使用加以限制,并删除任何你能在代码中找到的实例(译注:这里作者的表述很委婉,其实作者更倾向于全面禁止使用arguments.callee)。 299 | 300 | 301 | ## 数组直接量 302 | 303 | 和其他的大多数一样,JavaScript中的数组也是对象。可以通过内置构造函数Array()来创建数组,类似对象直接量,数组也可以通过直接量形式创建。而且更推荐使用直接量创建数组。 304 | 305 | 这里的实例代码给出了创建两个具有相同元素的数组的两种方法,使用Array()和使用直接量模式: 306 | 307 | // array of three elements 308 | // warning: antipattern 309 | var a = new Array("itsy", "bitsy", "spider"); 310 | 311 | // the exact same array 312 | var a = ["itsy", "bitsy", "spider"]; 313 | 314 | console.log(typeof a); // "object", because arrays are objects 315 | console.log(a.constructor === Array); // true 316 | 317 | 318 | ### 数组直接量语法 319 | 320 | 数组直接量写法非常简单:整个数组使用方括号括起来,数组元素之间使用逗号分隔。数组元素可以是任意类型,也包括数组和对象。 321 | 322 | 数组直接量语法简单直接、高雅美观。毕竟数组只是从位置0开始索引的值的集合,完全没必要包含构造器和new运算符的内容(代码会更多),保持简单即可。 323 | 324 | 325 | ### 有意思的数组构造器 326 | 327 | 我们对new Array()敬而远之原因是为了避免构造函数带来的陷阱。 328 | 329 | 如果给Array()构造器传入一个数字,这个数字并不会成为数组的第一个元素,而是设置数组的长度。也就是说,new Array(3)创建了一个长度为3的数组,而不是某个元素是3。如果你访问数组的任意元素都会得到undefined,因为元素并不存在。下面示例代码展示了直接量和构造函数的区别: 330 | 331 | // an array of one element 332 | var a = [3]; 333 | console.log(a.length); // 1 334 | console.log(a[0]); // 3 335 | 336 | // an array of three elements 337 | var a = new Array(3); 338 | console.log(a.length); // 3 339 | console.log(typeof a[0]); // "undefined" 340 | 341 | 或许上面的情况看起来还不算是太严重的问题,但当 `new Array()` 的参数是一个浮点数而不是整数时则会导致严重的错误,这是因为数组的长度不可能是浮点数。 342 | 343 | // using array literal 344 | var a = [3.14]; 345 | console.log(a[0]); // 3.14 346 | 347 | var a = new Array(3.14); // RangeError: invalid array length 348 | console.log(typeof a); // "undefined" 349 | 350 | 为了避免在运行时动态创建数组时出现这种错误,强烈推荐使用数组直接量来代替new Array()。 351 | 352 | >有些人用Array()构造器来做一些有意思的事情,比如用来生成重复字符串。下面这行代码返字符串包含255个空格(请读者思考为什么不是256个空格)。`var white = new Array(256).join(' ');` 353 | 354 | 355 | ### 检查是不是数组 356 | 357 | 如果typeof的操作数是数组的话,将返回“object”。 358 | 359 | console.log(typeof [1, 2]); // "object" 360 | 361 | 这个结果勉强说得过去,毕竟数组是一种对象,但对我们用处不大。往往你需要知道一个值是不是真正的数组。你可能见到过这种检查数组的方法:检查length属性、检查数组方法比如slice()等等。但这些方法非常脆弱,非数组的对象也可以拥有这些同名的属性。还有些人使用instanceof Array来判断数组,但这种方法在某些版本的IE里的多个iframe的场景中会出问题(译注:原因就是在不同iframe中创建的数组不会相互共享其prototype属性)。 362 | 363 | ECMAScript 5定义了一个新的方法Array.isArray(),如果参数是数组的话就返回true。比如: 364 | 365 | Array.isArray([]); // true 366 | 367 | // trying to fool the check 368 | // with an array-like object 369 | Array.isArray({ 370 | length: 1, 371 | "0": 1, 372 | slice: function () {} 373 | }); // false 374 | 375 | 如果你的开发环境不支持ECMAScript5,可以通过Object.prototype.toString()方法来代替。如调用toString的call()方法并传入数组上下文,将返回字符串“[object Array]”。如果传入对象上下文,则返回字符串“[object Object]”。因此可以这样做: 376 | 377 | if (typeof Array.isArray === "undefined") { 378 | Array.isArray = function (arg) { 379 | return Object.prototype.toString.call(arg) === "[object Array]"; 380 | }; 381 | } 382 | 383 | 384 | ## JSON 385 | 386 | 上文我们刚刚讨论过对象和数组直接量,你已经对此很熟悉了,现在我们将目光转向JSON。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。很多语言中都实现了JSON,特别是在JavaScript中。 387 | 388 | JSON格式及其简单,它只是数组和对象直接量的混合写法,看一个JSON字符串的例子: 389 | 390 | {"name": "value", "some": [1, 2, 3]} 391 | 392 | JSON和对象直接量在语法上的唯一区别是,合法的JSON属性名均用引号包含。而在对象直接量中,只有属性名是非法的标识符时采用引号包含,比如,属性名中包含空格`{"first name": "Dave"}`。 393 | 394 | 在JSON字符串中,不能使用函数和正则表达式直接量。 395 | 396 | 397 | ### 使用JSON 398 | 399 | 在前面的章节中讲到,出于安全考虑,不推荐使用eval()来“粗糙的”解析JSON字符串。最好使用JSON.parse()方法,ES5中已经包含了这个方法,而且在现代浏览器的JavaScript引擎中已经内置支持JSON了。对于老旧的JavaScript引擎来说,你可以使用JSON.org所提供的JS文件(http://www.json.org/json2.js)来获得JSON对象和方法。 400 | 401 | // an input JSON string 402 | var jstr = '{"mykey": "my value"}'; 403 | 404 | // antipattern 405 | var data = eval('(' + jstr + ')'); 406 | 407 | // preferred 408 | var data = JSON.parse(jstr); 409 | 410 | console.log(data.mykey); // "my value" 411 | 412 | 如果你已经在使用某个JavaScript库了,很可能库中提供了解析JSON的方法,就不必再额外引入JSON.org的库了,比如,如果你已经使用了YUI3,你可以这样: 413 | 414 | // an input JSON string 415 | var jstr = '{"mykey": "my value"}'; 416 | 417 | // parse the string and turn it into an object 418 | // using a YUI instance 419 | YUI().use('json-parse', function (Y) { 420 | var data = Y.JSON.parse(jstr); 421 | console.log(data.mykey); // "my value" 422 | }); 423 | 424 | 如果你使用的是jQuery,可以直接使用它提供的parseJSON()方法: 425 | 426 | // an input JSON string 427 | var jstr = '{"mykey": "my value"}'; 428 | 429 | var data = jQuery.parseJSON(jstr); 430 | console.log(data.mykey); // "my value" 431 | 432 | 和JSON.parse()方法相对应的是JSON.stringify()。它将对象或数组(或任何原始值)转换为JSON字符串。 433 | 434 | var dog = { 435 | name: "Fido", 436 | dob:new Date(), 437 | legs:[1,2,3,4] 438 | }; 439 | 440 | var jsonstr = JSON.stringify(dog); 441 | 442 | // jsonstr is now: 443 | // {"name":"Fido","dob":"2010-04-11T22:36:22.436Z","legs":[1,2,3,4]} 444 | 445 | 446 | ## 正则表达式直接量 447 | 448 | JavaScript中的正则表达式也是对象,可以通过两种方式创建它们: 449 | 450 | - 使用new RegExp()构造函数 451 | - 使用正则表达式直接量 452 | 453 | 下面的示例代码展示了创建正则表达式的两种方法,创建的正则用来匹配一个反斜杠(\): 454 | 455 | // regular expression literal 456 | var re = /\\/gm; 457 | 458 | // constructor 459 | var re = new RegExp("\\\\", "gm"); 460 | 461 | 显然正则表达式直接量写法的代码更短,且不必强制按照类构造器的思路来写。因此更推荐使用直接量写法。 462 | 463 | 另外,如果使用RegExp()构造函数写法,还需要考虑对引号和反斜杠进行转义,正如上段代码所示的那样,用了四个反斜杠来匹配一个反斜杠。这会增加正则表达式的长度,而且让正则变得难于理解和维护。刚开始学习正则表达式不是很容易,所以不要放弃任何一个简化它们的机会,所以要尽量使用直接量而不是通过构造函数来创建正则。 464 | 465 | 466 | ### 正则表达式直接量语法 467 | 468 | 正则表达式直接量使用两个斜线包裹起来,正则的主体部分不包括两端的斜线。在第二个斜线之后可以指定模式匹配的修饰符用以高级匹配,修饰符不需要引号引起来,JavaScript中有三个修饰符: 469 | 470 | - g,全局匹配 471 | - m,多行匹配 472 | - i,忽略大小写的匹配 473 | 474 | 修饰符可以自由组合,而且顺序无关: 475 | 476 | var re = /pattern/gmi; 477 | 478 | 使用正则表达式直接量可以让代码更加简洁高效,比如当调用String.prototype.replace()方法时,可以传入正则表达式参数: 479 | 480 | var no_letters = "abc123XYZ".replace(/[a-z]/gi, ""); 481 | console.log(no_letters); // 123 482 | 483 | 有一种不得不使用new RegExp()的情形,有时正则表达式是不确定的,直到运行时才能确定下来。 484 | 485 | 正则表达式直接量和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的规范更符合开发者的期望)。 486 | 487 | function getRE() { 488 | var re = /[a-z]/; 489 | re.foo = "bar"; 490 | return re; 491 | } 492 | 493 | var reg = getRE(), 494 | re2 = getRE(); 495 | 496 | console.log(reg === re2); // true 497 | reg.foo = "baz"; 498 | console.log(re2.foo); // "baz" 499 | 500 | >在ECMAScript5中这种情形有所改变,相同正则表达式直接量的每次计算都会创建新的实例对象,目前很多现代浏览器也对此做了纠正(译注:比如在Firefox4就纠正了Firefox3.6的这种“错误”)。 501 | 502 | 最后需要提一点,不带new调用RegExp()(作为普通的函数)和带new调用RegExp()是完全一样的。 503 | 504 | 505 | ## 原始值的包装对象 506 | 507 | JavaScript中有五种原始类型:数字、字符串、布尔值、null和undefined。除了null和undefined之外,其他三种都有对应的“包装对象”(wrapper objects)。可以通过内置构造函数来生成包装对象,Number()、String()、和Boolean()。 508 | 509 | 为了说明数字原始值和数字对象之间的区别,看一下下面这个例子: 510 | 511 | // a primitive number 512 | var n = 100; 513 | console.log(typeof n); // "number" 514 | 515 | // a Number object 516 | var nobj = new Number(100); 517 | console.log(typeof nobj); // "object" 518 | 519 | 包装对象带有一些有用的属性和方法,比如,数字对象就带有toFixed()和toExponential()之类的方法。字符串对象带有substring()、chatAt()和toLowerCase()等方法以及length属性。这些方法非常方便,和原始值相比,这让包装对象具备了一定优势。其实原始值也可以调用这些方法,因为原始值会首先转换为一个临时对象,如果转换成功,则调用包装对象的方法。 520 | 521 | // a primitive string be used as an object 522 | var s = "hello"; 523 | console.log(s.toUpperCase()); // "HELLO" 524 | 525 | // the value itself can act as an object 526 | "monkey".slice(3, 6); // "key" 527 | 528 | // same for numbers 529 | (22 / 7).toPrecision(3); // "3.14" 530 | 531 | 因为原始值可以根据需要转换成对象,这样的话,也不必为了用包装对象的方法而将原始值手动“包装”成对象。比如,不必使用new String("hi"),直接使用"hi"即可。 532 | 533 | // avoid these: 534 | var s = new String("my string"); 535 | var n = new Number(101); 536 | var b = new Boolean(true); 537 | 538 | // better and simpler: 539 | var s = "my string"; 540 | var n = 101; 541 | var b = true; 542 | 543 | 不得不使用包装对象的一个原因是,有时我们需要对值进行扩充并保持值的状态。原始值毕竟不是对象,不能直接对其进行扩充(译注:比如`1.property = 2`会报错)。 544 | 545 | // primitive string 546 | var greet = "Hello there"; 547 | 548 | // primitive is converted to an object 549 | // in order to use the split() method 550 | greet.split(' ')[0]; // "Hello" 551 | 552 | // attemting to augment a primitive is not an error 553 | greet.smile = true; 554 | 555 | // but it doesn't actually work 556 | typeof greet.smile; // "undefined" 557 | 558 | 在这段示例代码中,greet只是临时转换成了对象,以保证访问其属性/方法时不会出错。另一方面,如果greet通过new String()定义为一个对象,那么扩充smile属性就会按照期望的那样执行。对字符串、数字或布尔值的扩充并不常见,除非你清楚自己想要什么,否则不必使用包装对象。 559 | 560 | 当省略new时,包装器将传给它的参数转换为原始值: 561 | 562 | typeof Number(1); // "number" 563 | typeof Number("1"); // "number" 564 | typeof Number(new Number()); // "number" 565 | typeof String(1); // "string" 566 | typeof Boolean(1); // "boolean" 567 | 568 | 569 | ## Error 对象 570 | 571 | JavaScript中有很多内置的Error构造函数,比如Error()、SyntaxError(),TypeError()等等,这些“错误”通常和throw语句一起使用。这些构造函数创建的错误对象包含这些属性: 572 | 573 | **name** 574 | 575 | name属性是指创建这个对象的构造函数的名字,通常是“Error”,有时会有特定的名字比如“RangeError” 576 | 577 | **message** 578 | 579 | 创建这个对象时传入构造函数的字符串 580 | 581 | 错误对象还有其他一些属性,比如产生错误的行号和文件名,但这些属性是浏览器自行实现的,不同浏览器的实现也不一致,因此出于兼容性考虑,并不推荐使用这些属性。 582 | 583 | 另一方面,throw可以抛出任何对象,并不限于“错误对象”,因此你可以根据需要抛出自定义的对象。这些对象包含属性“name”和“message”或其他你希望传递给异常处理逻辑的信息,异常处理逻辑由catch语句指定。你可以灵活运用抛出的错误对象,将程序从错误状态恢复至正常状态。 584 | 585 | try { 586 | // something bad happened, throw an error 587 | throw { 588 | name: "MyErrorType", // custom error type 589 | message: "oops", 590 | extra: "This was rather embarrassing", 591 | remedy: genericErrorHandler // who should handle it 592 | }; 593 | } catch (e) { 594 | // inform the user 595 | alert(e.message); // "oops" 596 | 597 | // gracefully handle the error 598 | e.remedy(); // calls genericErrorHandler() 599 | } 600 | 601 | 通过new调用和省略new调用错误构造函数是一模一样的,他们都返回相同的错误对象。 602 | 603 | 604 | ## 小结 605 | 606 | 在本章里,我们讨论了多种直接量模式,它们是使用构造函数写法的替代方案,本章讲述了这些内容: 607 | 608 | - 对象直接量写法——一种简洁优雅的定义对象的方法,名值对之间用逗号分隔,通过花括号包装起来 609 | - 构造函数——内置构造函数(内置构造函数通常都有对应的直接量语法)和自定义构造函数。 610 | - 一种强制函数以构造函数的模式执行(不管用不用new调用构造函数,都始终返回new出来的实例)的技巧 611 | - 数组直接量写法——数组元素之间使用逗号分隔,通过方括号括起来 612 | - JSON——是一种轻量级的数据交换格式 613 | - 正则表达式直接量 614 | - 避免使用其他的内置构造函数:String()、Number()、Boolean()以及不同种类的Error()构造器 615 | 616 | 通常除了Date()构造函数之外,其他的内置构造函数并不常用,下面的表格中对这些构造函数以及它们的直接量语法做了整理。 617 | 618 |
| 内置构造函数(不推荐) | 621 |直接量语法和原始值(推荐) | 622 |
| var o = new Object(); | 625 |var o = {}; 626 | | 627 |
| var a = new Array(); 630 | | 631 |var a = []; 632 | | 633 |
| var re = new RegExp("[a-z]","g"); 636 | | 637 |var re = /[a-z]/g; 638 | | 639 |
| var s = new String(); 642 | | 643 |var s = ""; 644 | | 645 |
| var n = new Number(); 648 | | 649 |var n = 0; 650 | | 651 |
| var b = new Boolean(); 654 | | 655 |var b = false; 656 | | 657 |
| throw new Error("uh-oh"); 660 | | 661 |throw { name: "Error",message: "uh-oh"};或者throw Error("uh-oh"); 662 | | 663 |
message from the background thread: " + event.data + "
"; 319 | }; 320 | 321 | 下面展示了一个做1亿次简单的数学运算的web worker: 322 | 323 | var end = 1e8, tmp = 1; 324 | 325 | postMessage('hello there'); 326 | 327 | while (end) { 328 | end -= 1; 329 | tmp += end; 330 | if (end === 5e7) { // 5e7 is the half of 1e8 331 | postMessage('halfway there, `tmp` is now ' + tmp); 332 | } 333 | } 334 | 335 | postMessage('all done'); 336 | 337 | web worker使用postMessage()来和调用它的程序通讯,调用者通过onmessage事件来接受更新。onmessage事件处理函数接受一个事件对象作为参数,这个对象含有一个由web worker传过来data属性。类似的,调用者(在这个例子中)也可以使用ww.postMessage()来给web worker传递数据,web worker可以通过一个onmessage事件处理函数来接受这些数据。 338 | 339 | 上面的例子会在浏览器中打印出: 340 | 341 | message from the background thread: hello there 342 | message from the background thread: halfway there, `tmp` is now 3749999975000001 message from the background thread: all done 343 | 344 | ## 远程脚本编程 345 | 346 | 现代web应用经常会使用远程脚本编程和服务器通讯,而不刷新当前页面。这使得web应用更灵活,更像桌面程序。我们来看一下几种用JavaScript和服务器通讯的方法。 347 | 348 | ### XMLHttpRequest 349 | 350 | 现在,XMLHttpRequest是一个特别的对象(构造函数),绝大多数浏览器都可以用,它使得我们可以从JavaScript来发送HTTP请求。发送一个请求有以下三步: 351 | 352 | 1. 初始化一个XMLHttpRequest对象(简称XHR) 353 | 2. 提供一个回调函数,供请求对象状态改变时调用 354 | 3. 发送请求 355 | 356 | 第一步很简单: 357 | 358 | var xhr = new XMLHttpRequest(); 359 | 360 | 但是在IE7之前的版本中,XHR的功能是使用ActiveX对象实现的,所以需要做一下兼容处理。 361 | 362 | 第二步是给readystatechange事件提供一个回调函数: 363 | 364 | xhr.onreadystatechange = handleResponse; 365 | 366 | 最后一步是使用open()和send()两个方法触发请求。open()方法用于初始化HTTP请求的方法(如GET,POST)和URL。send()方法用于传递POST的数据,如果是GET方法,则是一个空字符串。open()方法的最后一个参数用于指定这个请求是不是异步的。异步是指浏览器在等待响应的时候不会阻塞,这明显是更好的用户体验,因此除非必须要同步,否则异步参数应该使用true: 367 | 368 | xhr.open("GET", "page.html", true); 369 | xhr.send(); 370 | 371 | 下面是一个完整的示例,它获取新页面的内容,然后将当前页面的内容替换掉(可以在" + xhr.responseText + "<\/pre>"; };
399 |
400 | xhr.open("GET", "page.html", true);
401 | xhr.send("");
402 |
403 | 代码中的一些说明:
404 |
405 | - 因为IE6及以下版本中,创建XHR对象有一点复杂,所以我们通过一个数组列出ActiveX的名字,然后遍历这个数组,使用try-catch块来尝试创建对象。
406 | - 回调函数会检查xhr对象的readyState属性。这个属性有0到4一共5个值,4代表“complete”(完成)。如果状态还没有完成,我们就继续等待下一次readystatechange事件。
407 | - 回调函数也会检查xhr对象的status属性。这个属性和HTTP状态码对应,比如200(OK)或者是404(Not found)。我们只对状态码200感兴趣,而将其它所有的都报为错误(为了简化示例,否则需要检查其它不代表出错的状态码)。
408 | - 上面的代码会在每次创建XHR对象时检查一遍支持情况。你可以使用前面提到过的模式(如条件初始化)来重写上面的代码,使得只需要做一次检查。
409 |
410 | ### JSONP
411 |
412 | JSONP(JSON with padding)是另一种发起远程请求的方式。与XHR不同,它不受浏览器同源策略的限制,所以考虑到加载第三方站点的安全影响的问题,使用它时应该很谨慎。
413 |
414 | 一个XHR请求的返回可以是任何类型的文档:
415 |
416 | - XML文档(过去很常用)
417 | - HTML片段(很常用)
418 | - JSON数据(轻量、方便)
419 | - 简单的文本文件及其它
420 |
421 | 使用JSONP的话,数据经常是被包裹在一个函数中的JSON,函数名称在请求的时候提供。
422 |
423 | JSONP的请求URL通常是像这样:
424 |
425 | http://example.org/getdata.php?callback=myHandler
426 |
427 | getdata.php可以是任何类型的页面或者脚本。callback参数指定用来处理响应的JavaScript函数。
428 |
429 | 这个URL会被放到一个动态生成的\
618 | // option 2
619 |
620 |
621 | 但是,当你的目标是要构建一个高性能的web应用的时候,有些模式和考虑点还是应该知道的。
622 |
623 | 作为题外话,来看一些比较常见的开发者会用在\
649 |
650 |
651 |
652 |
653 |
654 | ...
655 |
656 |