├── README.md ├── .gitignore ├── JavaScript高级程序设计.md ├── Pragmatic_Guide_to_JavaScript_note.md ├── single_page_apps_in_depth └── index.md ├── seajs源码正则分析.md ├── The_Pragmatic_Programmer-程序员修炼之道.md ├── 深入浅出CoffeeScript.md ├── Metaprograming_Ruby-Ruby元编程.md ├── JavaScript_DOM_编程艺术.md ├── 版本控制之道——使用Git.md ├── HTML5高级程序设计.md └── 基于MVC的JavaScript Web富应用开发.md /README.md: -------------------------------------------------------------------------------- 1 | 我的笔记 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | \#*# 2 | *.swp 3 | .#* 4 | -------------------------------------------------------------------------------- /JavaScript高级程序设计.md: -------------------------------------------------------------------------------- 1 | JavaScript 高级程序设计 笔记 2 | === 3 | 4 | 2.1.2延迟脚本 中提到,HTML4.01 的 script 标签有 defer 属性,JS 脚本能够在页面解析完毕后执行,不知道 HTML5 和 XHTML 还有没有这个属性 5 | 6 | 2.1.3在 XHTML 中的用法 中提到,为了让 JS 中的 < 不被当成新标签的开始,需要加一个 ,这个在 HTML5 中还需要吗? 7 | -------------------------------------------------------------------------------- /Pragmatic_Guide_to_JavaScript_note.md: -------------------------------------------------------------------------------- 1 | JavaScript 修炼之道笔记 2 | === 3 | 4 | 方括号操作符 5 | --- 6 | 利用 JavaScript 的方括号操作符我们可以做到动态的调用方法及熟悉,比如 7 | 8 | window['history'] = ... 9 | 10 | 由于传入的是一个字符串,所以几乎所有结果能够获得字符串的代码都能在方括号里使用,比如: 11 | 12 | window[isHistory?'history':'other'] 13 | window[(enable?'add':'delete')+'action'] 14 | window[{firstName:'foo',lastName:'bar'}[needFirst?'firstName':'lastName']] 15 | 16 | >值得注意的是,如果你在向方法传递的参数上大量使用这个技巧,将使代码变得难以阅读,此时使用常规的 if/else 结构更加明智 17 | 18 | 19 | -------------------------------------------------------------------------------- /single_page_apps_in_depth/index.md: -------------------------------------------------------------------------------- 1 | This free book is the book I would have wanted when I started working with single page apps. It's not an API reference on a particular framework, rather, the focus is on discussing patterns, implementation choices and decent practices. 2 | 3 | 这是一本 free book ,我写他是因为当我刚开始做单页面应用的时候我就希望有这么一本书(当然那个时候没有)。这本书不是某个框架的接口文档,相反,它着重于讨论制作单页面应用的模式,选择和实践。 4 | 5 | I'm taking a "code and concepts" approach to the topic - the best way to learn how to use something is to understand how it is implemented. My ambition here is to decompose the problem of writing a web app, take a fresh look at it and hopefully make better decisions the next time you make one. 6 | 7 | 我给每个主题增加了一个叫做 “代码与概念” 的部分——了解一个东西怎么用的最好方法就是知道它是怎么工作的。我的目的是把写一个 Web App 时会遇到的问题分解开,这样你下次需要做一个单页面应用时就能够针对这些问题做一个更好的解决方案啦。 8 | 9 | I'm releasing this a bit early, since I'm heading to Nodeconf. In my writing, I may sound slightly opinionated, but I am happy to be proven wrong. Your feedback is much appreciated! 10 | 11 | 这本书在我去过 Nodeconf 后我就发布了,在我写这本书的时候,我可能会有点自以为是,but i am happy to be proven wrong,你的反馈就是最好的赞赏了。 12 | 13 | Introduction 大纲 14 | 15 | * [Modern single page apps - an overview](http://singlepageappbook.com/goal.html) 16 | * 现代单页面应用程序 - 一个总览 17 | 18 | Writing maintainable code 编写可维护的代码 19 | 20 | * [Maintainability depends on modularity: Stop using namespaces!](http://singlepageappbook.com/maintainability1.html) 21 | * 可维护性依赖于模块化:别再用命名空间啦 22 | * [Getting to maintainable](http://singlepageappbook.com/maintainability2.html) 23 | * 让代码开始变得可维护起来吧 24 | * [Testing explained](http://singlepageappbook.com/maintainability3.html) 25 | * 有关测试的解释 26 | 27 | Implementation alternatives: a look at the options 实现原理二选一:看看选项 28 | 29 | * [The view layer](http://singlepageappbook.com/detail1.html) 30 | * 视图层 31 | * [The model layer](http://singlepageappbook.com/detail2.html) 32 | * 模型层 33 | 34 | Meditations on Models & Collections 关于模型和集合的思考 35 | 36 | * [Implementing a data source](http://singlepageappbook.com/collections1.html) 37 | * 实现一个一个数据源 38 | * [Implementing a model](http://singlepageappbook.com/collections2.html) 39 | * 实现一个模型 40 | * [Implementing a collection](http://singlepageappbook.com/collections3.html) 41 | * 实现一个集合 42 | * [Implementing a data cache](http://singlepageappbook.com/collections4.html) 43 | * 实现一个数据缓存 44 | * [Implementing associations](http://singlepageappbook.com/collections5.html) 45 | * 实现关联 46 | 47 | Views - templating, behavior and event consumption 视图 - 模板,行为和事件消费 48 | 49 | * [Templating: from data to HTML](http://singlepageappbook.com/views1.html) 50 | * 模板:从数据到 HTML 51 | * [Behavior: binding DOM events to HTML and responding to events](http://singlepageappbook.com/views2.html) 52 | * 行为:在 HTML 上绑定 DOM 事件以及反馈给事件 53 | * [Consuming events from the model layer: communication between views and re-rendering views in response to model data changes](http://singlepageappbook.com/views3.html) 54 | -------------------------------------------------------------------------------- /seajs源码正则分析.md: -------------------------------------------------------------------------------- 1 | # SeaJS 源代码中的正则表达式分析 2 | 3 | 由于 JavaScript 无法在正则表达式中添加注释,故而代码是用 CoffeeScript 写的 4 | 5 | 再规范一下用词 6 | 7 | *: 任意数量(零到无限) 8 | 9 | +: 若干(至少多于一) 10 | 11 | ?:没有或一个 12 | 13 | (.*): 在小括号里的一致称之为子表达式 14 | 15 | ## 一些疑问 16 | str.indexOf(params) === -1 相对于 ~str.indexOf(params) 有任何优势吗? 17 | 18 | 这段不理解 19 | 20 | ``` JavaScript 21 | // For some cache cases in IE 6-9, the script executes IMMEDIATELY after 22 | // the end of the insertBefore execution, so use `currentlyAddingScript` 23 | // to hold current node, for deriving url in `define`. 24 | currentlyAddingScript = node; 25 | head.insertBefore(node, head.firstChild); 26 | currentlyAddingScript = null; 27 | ``` 28 | 说是会立即执行,闭包就不会了? 29 | 而且获取的代码在另外一个函数里,似乎这赋值与消除之间也没有回调函数,怎么能够在 getCurrentScript 函数里获取到 currentlyAddingScript ? 30 | 还有就是为什么要 insertBefore ? 31 | 32 | 这段不理解 33 | 34 | ``` JavaScript 35 | var scripts = head.getElementsByTagName('script'); 36 | for (var i = 0; i < scripts.length; i++) { 37 | var script = scripts[i]; 38 | if (script.readyState === 'interactive') { 39 | interactiveScript = script; 40 | return script; 41 | } 42 | } 43 | ``` 44 | readyState 有四个状态 uninitialized、loading、interactive、complete 为什么 complete 不行?会不会遗漏? 45 | 46 | ## dirname 47 | ``` CoffeeScript 48 | # Extracts the directory portion of a path. 49 | # dirname('a/b/c.js') ==> 'a/b/' 50 | # dirname('d.js') ==> './' 51 | # @see http://jsperf.com/regex-vs-split/2 (传说这样是若干方法中效率最高的) 52 | s = path.match /// 53 | .* # 任意内容 54 | (?= # 被接下来的子正则匹配的内容不会出现在匹配结果中 55 | \/.*$ # 匹配出以“/”开头且到字符串结尾部分依旧没有再出现“/”的字符串 56 | ) 57 | /// 58 | return (if s then s[0] else '.') + '/' # 如果 s 非空,那么就把匹配的第一个结果拿出来(其实打死也就只有一个结果罢了),加上一个 '/' 59 | ``` 60 | 61 | ## realpath 62 | ``` CoffeeScript 63 | # 'file:///a//b/c' ==> 'file:///a/b/c' 64 | path = path.replace /// 65 | ( # 先匹配出以若干斜杠结尾的字符串,比如上面的注释,先匹配出 file: a b 三个字符串 66 | [^:\/] # 过滤掉其中有 : 和 / 的匹配,所以这个时候就只剩下 a 和 b 了 67 | ) 68 | \/+ 69 | ///g, '$1\/' # 将第一个子表达式的匹配结果 70 | ``` 71 | 72 | ## getHost 73 | ``` CoffeeScript 74 | getHost = (url) -> 75 | url.replace /// 76 | ^ # 从字符串头开始 77 | ( 78 | \w+:\/\/ # 若干ASCII单字字符 + :// 79 | [^\/]* # 到 /? 之前的所有除了 / 以外的字符 80 | ) 81 | \/?.*$ # 以上的匹配只能匹配 /? 到字符串尾以外的内容 82 | ///, '$1' # 替换为第一个子表达式 83 | ``` 84 | 85 | ## removeComments 86 | ``` CoffeeScript 87 | removeComments = (code) -> 88 | # 先替换掉 /* */ 形式的注释,再替换掉 // 形式的 89 | code.replace(/// 90 | (?:^|\n|\r) # 匹配行头或者折行,但不记录匹配结果 91 | \s* # 匹配任意数量的空格 92 | \/\*[\s\S]*?\*\/ # 匹配尽量少的匹配(非贪心模式) /* */ 及他们之间的除了换行符以外的任意字符 93 | \s* 94 | (?:\r|\n|$) # 匹配行尾或者折行,但不记录匹配结果 95 | ///g, '\n') 96 | .replace(/// 97 | (?:^|\n|\r) 98 | \s* # 匹配任意数量的空格 99 | \/\/.* # 匹配 // 及 // 后到下个子正则之间的任意字符 100 | (?:\r|\n|$) 101 | ///g, '\n') 102 | ``` 103 | 104 | ## parseDependencies 105 | ``` CoffeeScript 106 | # 正则对象的 exec 函数会返回一个数组,其中下标为 0 的元素为该正则对象匹配的结果,接下 107 | # 来的元素下标为多少就是第几个子正则的匹配结果 108 | pattern = /// 109 | (?:^|[^.]) # 匹配行头或者 \n 但不记录匹配结果 110 | \brequire # 匹配位于边界处(字符串的开头或者结尾)的 require 111 | \s* # 匹配任意数量的空格 112 | \( 113 | \s* 114 | (["']) # 匹配一个 " 或者 ' 115 | ([^"'\s\)]+) # 匹配若干除了 " ' 空格 ) 以外的字符 116 | \1 # 与第一个子正则相同 117 | \s* 118 | \) 119 | ////g; 120 | # ... some code and defined 'code' 121 | match = pattern.exec(code) 122 | while ((match = pattern.exec(code))) { 123 | if (match[2]) { 124 | ret.push(match[2]); 125 | } 126 | } 127 | ``` 128 | -------------------------------------------------------------------------------- /The_Pragmatic_Programmer-程序员修炼之道.md: -------------------------------------------------------------------------------- 1 | 0:前言 2 | --- 3 | 我记得有人说,看书前最好想想自己为什么要看这本书,看这本书是为什么追求什么答案。 4 | 5 | 然而,看这本书前,其实我没有什么疑问的。或者说,我还不会编程。 6 | 7 | 很显然,我十分赞同前言中所述:编程并非只是敲入某种语言的行为。 8 | 9 | 回想起来,到目前为止,我所有的敲代码的行为都是为了自己,为了取乐或者其他目的。 10 | 11 | 但即使如此,我依然希望能够写出更赏心悦目的代码。 12 | 13 | 我怀着好奇之心阅读此书。 14 | 15 | 即便只获益一鳞半爪,依旧令我心怀感激。 16 | 17 | **提示1:关心你的技艺。** 18 | 19 | **提示2:思考!你的工作。** 20 | 21 | 22 | 1:我的源码让猫吃了 23 | --- 24 | 其实在我看来,在事前应承某些要求之前,“分析风险是否超出你的控制”,“对不可能做到的事情或是风险太大的事情”,在我看来,“不去为之负责”本身就是我们的责任。 25 | 26 | 坦白告知对方自己力有不逮,能够在造成损失前规避风险。 27 | 28 | 只有在接受任务并且犯错之后,才需要考虑提示3:提供挽回局面的各种选择,而非找蹩脚的借口。 29 | 30 | 经过多年撒谎的经验以后我得出一个结论,撒出一个近乎完美的谎言/借口的可能性实在是太小了。 31 | 32 | 相信我,谎言在多数情况下其实都是漏洞百出的。 33 | 34 | **提示3:提供各种选择,不要找蹩脚的借口。** 35 | 36 | 37 | 10&11:曳光弹 与 原型与便笺 38 | --- 39 | 曳光弹的做法在多数情况下比费力计算的方法更为行之有效,且更为经济。 40 | 41 | 所以一接到任务就埋头苦干的哥们其实你们做的不算错,当然,如果能分出一点时间来先分析一下就更好了。 42 | 43 | 如同提示15所言,曳光弹的主要用场还是在于找到目标。 44 | 45 | 在我看来,它与原型的区别也在于此: 46 | 47 | 已经有了方向,为了测试与寻求解答而制作的,用过即废,并且可以帮助我们推迟考虑细节的方法应该就是原型的方法。 48 | 49 | 而在探索时所做的,拥有完整结构(异常处理,文档等等),制作的目的是为了真正将代码写入项目代码中去的方法应该就是曳光弹方法。 50 | 51 | 其实说了等于没有说,因为很简单,我没有参与过大的项目,对我而言,我几乎所有的工程都是用类似曳光弹方法做的。 52 | 53 | 所以我来阐述两者的区别意义不大。 54 | 55 | **提示15:用曳光弹来找到目标。** 56 | 57 | **提示16:为了学习而制作原型。** 58 | 59 | 60 | 33:重构 61 | --- 62 | 关于重构其实可以说很多,比如 [酷壳](http://coolshell.com/) ,比如 [黑客志](http://heikezhi.com/) ,有很多文章提到这件事。由此可见,重构确实是在码代码时相当常遇到的事情了。(废话,你会不讨论面向对象,会不讨论设计模式,会不讨论算法优劣,但是我难以想象竟然存在程序员没有重构过代码)。 63 | 64 | 当然,既然可以说的多,褒贬也就不一定了,毕竟这方面没有真理部,没有有关部门统一口径。 65 | 66 | 有文章说不到确实必要时不要去重构代码,也有文章(譬如本书提示47)说要“早重构,常重构”。 67 | 68 | 我所做的无疑都是一些小东西,代码量最大的也不过是几百行代码,所以我的所作所为比较符合书中的说法,我也确实认同书中举出的认为重构必要的原因: 69 | 70 | > 如果它现在有损害,那么以后的损害就会更大。 71 | 72 | 当然,这不代表我不赞同黑客志里文章的说法。 73 | 其实我喜欢的是书中提到的“怎样重构”(P186): 74 | 75 | > * 不要试图在重构的同时增加功能。(我记住了……) 76 | > * 在开始重构前,确保你有良好的测试。尽可能经常运行这些测试。这样,如果你的改动破坏了任何东西,你就能很快知道。(好吧,我知道这个叫做测试驱动开发,但是我还真没尝试过……从来没有写过单元测试什么的……当然,主要还是程序小。) 77 | > * 采取短小、深思熟虑的步骤:把某个字段从一个类移往另一个,把两个类似的方法融合进超类中。重构常常涉及到进行许多局部改动,继而产生更大规模的改动。如果你使你的步骤保持短小,并在每个步骤之后进行测试,你将能够避免长时间的调试。 78 | 79 | 最后还有一点,当然,做不做随便你,反正我是不怎么会去做: 80 | 81 | > 确保对模块作出剧烈改动——比如以一种不兼容的方式更改了其接口或功能——会破坏构建,这样很有帮助。也就是说,这些代码的老客户应该无法通过编译。于是你可以很快找到这些老客户,并做出必要的改动,让它们及时更新。(这个直接要求更新就好了吧 Orz) 82 | 83 | **提示47:早重构,常重构。** 84 | 85 | 8:正交性 86 | --- 87 | 书中说到,正交性的概念很少被直接讲授。就我个人所见而言确实如此。但它的另一个名字却多有被人提及——高内聚,低耦合。 88 | 89 | > 在计算技术中,该术语用于表示某种不相依赖性的或是解耦性。如果两个或更多事物中的一个发生变化,不会影响其他事物,这些事物就是正交的。 90 | 91 | 其实刚看的时候我以为说的只是低耦合的原则,后来看见一道习题,说两个用于把输入行拆分胃字段的类,A的构造函数读入文件流,B读入字符串,问哪个是更为正交(低耦合)的设计。 92 | 93 | 书中的答案是B,说它“更专注于自己的任务,拆分输入行,同时忽略像输入行来自何处这样的细节。这不仅使代码更易于开发,也使得代码更为灵活。拆分的行可以来自文件、可以由另外的例程生成、也可以通过环境传入”。 94 | 95 | 我之前说过,看这本书是为了写出更赏心悦目更易维护效率更高(似乎又多了几个要求?不管它了……)的代码,于是乎,正交性自然就成了其中的标准之一。 96 | 97 | 有人说,好的代码,很多时候代码即注释。我认为这应该也是正交的代码带来的好处之一。(当然,函数名与变量名也很重要 ;D) 98 | 99 | > 对于正交设计,有一种很简单的测试方法。一旦设计好组件,问问你自己:如果我显著地改变某个特定功能背后的需求,有多少模块会受影响?在正交系统中,答案应该是“一个”。 100 | 101 | 当然,这在现实生活中简直有点天方夜谭。但我以为这依旧是一种不错的测试方法(其实主要是想不到其他方法了Orz……) 102 | 我很喜欢文中的,对第三方工具箱与库的选择之道: 103 | 104 | > 是否需要以一种特殊方法才能使用,如果是,那就是非正交的,因为这不易于在将来更换供应商。 105 | 106 | 我认为他想说的是——如果没有这个库或者换个库,你的代码是否需要大幅修改?如果是,那就不正交。 107 | 108 | 以及,如何维持代码的正交性: 109 | 110 | 1. 编写“羞怯”的代码 111 | 2. 避免使用全局数据 112 | 3. 避免编写相似的函数 113 | 114 | 文中提到了项目团队的正交性: 115 | 116 | > 如果要对项目团队进行非正式的衡量。只要看一看,在讨论每个所需改动是需要涉及多少人。人数越多正交性自然越差。 117 | 118 | 然而,我还知道一个理论,似乎称作“巴士效应”:你的团队在被巴士压死多少人以后就无法正常运作了?人数越少,你的团队的结构就越危险。 119 | 120 | 解除与现实世界变化的耦合: 121 | 122 | > 还要问问你自己,你的设计在多大程度上解除了与现实世界中的变化的耦合?你在把电话号码当作顾客标识符吗?如果电话公司重新分配了区号,会怎么样? 123 | 124 | **提示13:消除无关事物之间的影响。** 125 | 126 | 127 | 26:解耦与得墨忒耳法则 128 | --- 129 | 在“正交性”和“按合约设计”中提到,编写“羞怯”的代码是有益的。 130 | 131 | “羞怯”的工作方式有两种:不向别人暴露自己,不与太多人打交道。 132 | 133 | 具体的做法可以参照间谍的组织方式,即:几个人(代码)组成一个最小组织单位(函数),单位与单位之间依靠预先约定好的渠道进行信息的交换。这样,即使一个最小组织单位被发现了,也不会危机到其他的单位。 134 | 135 | 为了实现提示36,我们就需要尽可能地遵守得墨忒耳法则: 136 | > 某个对象的任何方法都应该只调用属于以下情形的方法: 137 | * 它自身 138 | * 传入该方法的任何参数 139 | * 它创建的任何对象 140 | * 任何直接持有的组件对象 141 | 142 | 使用得墨忒耳法则能够缩小调用类中的响应集的规模,出现的错误往往最少,也能够使代码适应性更好、更健壮。 143 | 144 | 但这也有代价,因为这意味着你需要编写大量包装方法,这些包装方法既会带来运行时代价,也会带来空间开销。在有些应用中,*“这可能会有重大影响”*。 145 | 146 | **提示36:使模块之间的耦合减至最少。** 147 | 148 | 2:软件的熵 149 | --- 150 | 这一节讲的是如何保持项目代码的整洁与优雅。 151 | 152 | 熵是一个来自物理学的概念,指的是某个系统中的“无序”的总量。软件的熵与热力学定义的熵一样,总是倾向于最大化。 153 | 154 | > 当软件中的无序增长时,程序员们称之为“软件腐烂”。 155 | 156 | 文中提到,很多因素会引发这种情况,其中最重要的似乎是开发项目时的心理。 157 | 158 | 有一种心理效应被称作“破窗效应”。 159 | > 一扇破窗户,只要有那么一段时间不修理,就会渐渐给建筑的居民带来一种废弃感——一种职权部门不关心这座建筑的感觉。于是又一扇窗户破了。人们开始乱扔垃圾。出现了乱涂乱画。严重的结构损坏开始了。…… 160 | 161 | 开发也是如此。一段设计低劣的代码,团队必须在整个项目开发过程中加以忍受的一项糟糕的管理决策就足以使项目开始衰败。在这样的项目里工作,会很容易产生这样的想法:“这些代码的其余部分也是垃圾,我只要照做就好了。”(这样的事情我也遇到过,当时真的是重构的想死啊,三番两次的出现了这样的想法) 162 | 163 | 与此相反,如果你发现你所在的团队和项目的代码十分漂亮——编写整洁、设计良好、并且很优雅——你就很可能会格外注意不去把它弄脏。就和我们走进一个干净整洁的房间一样,你不会想成为*第一个*弄脏东西的人。 164 | 165 | **提示4:不要容忍破窗户。** 166 | 167 | 3:石头汤与煮青蛙 168 | --- 169 | 这一节讲的是,创造与推动变化,警惕偏离你的图景。 170 | 171 | > 在有些情况下,你或许确切的知道需要做什么,以及怎样去做。整个系统就在你眼前——你知道它是队的。 172 | 173 | 然而请求许可去处理这件事情时,你可能会发现,拖延、漠然、以及该死的官僚主义如此面目可憎地挡在你的前路上。与此同时,你可能还会遇到莫名其妙的委员会。而且,如果涉及到预算,每个人都会开始护卫他们自己的资源。 174 | 175 | > 有时候,这叫做“启动杂役”。(这翻译的名字很古怪啊……) 176 | > 这个时候,设计出你可以合理要求的东西,好好地开发它。一旦完成,就拿给其他人看,让他们大吃一惊。然后告诉他们说:“要是我们增加……可能会更好。” 177 | 178 | 让人们参与正在发生的成功显然要更容易(看看XX工厂的产品你就全懂了。) 179 | 180 | 接下来我们看那只被煮死的青蛙——和写书的人一样,我也没有做过这个。不过根据 [奥克拉何马大学的动物学教授霍奇森的研究](http://www.guokr.com/article/16846/) ,青蛙还是会跳出来的,当然,那其实和文章要说的无关紧要。 181 | 182 | “我们都看见过这样的症状。”项目慢慢地、不可改变地完全失去控制。 183 | 184 | 大多数软件灾难是从微不足道的小事情开始的——想想上一节《软件的熵》——大多数项目的拖延都是一天一天发生的。系统一个特性一个特性偏离规范,一个又一个补丁打到某段代码上……常常是小事情的积累破坏了士气和团队。 185 | 186 | 留心大图景,持续不断的观察周围发生的事,而不是你正在做的。 187 | 188 | **提示5:做变化的催化剂。** 189 | 190 | **提示6:记住大图景。** 191 | -------------------------------------------------------------------------------- /深入浅出CoffeeScript.md: -------------------------------------------------------------------------------- 1 | # 深入浅出 CoffeeScript 2 | 由于使用 CoffeeScript 已经颇有些时日,而且作为一种工具性质的语言,真正的语言细节依旧在于 JavaScript ,所以阅读的目的在于查漏补缺,不会有太多的笔记。 3 | 4 | ## 第三章 集合与迭代 5 | ### 3.2 数组 6 | 7 | ```coffeescript 8 | [][3...0] is [] 9 | [][1..] is [][1...] is [].slice 1 10 | arr[0...-1] is arr.slice 0, arr.length - 1 11 | 12 | ['a', 'c'][1...2] = ['b'] is ['a', 'b'] 13 | ['a', 'c'][1...1] = ['b'] is ['a', 'b', 'c'] 14 | ['a', 'c'][1...] = ['b'] is ['a', 'b'] 15 | ['a', 'c'][1...] = ['b', 'd'] is ['a', 'b', 'd'] 16 | ['a', 'c'][1...-1] = ['b'] is ['a', 'b', 'c'] 17 | ['a', 'c'][1...-2] = ['b'] is ['a', 'b', 'c'] 18 | ``` 19 | 20 | ### 3.3 集合的迭代 21 | 22 | ```coffeescript 23 | for own k, v of obj 24 | # ... 25 | 26 | # is 27 | 28 | for k, v of obj when obj.hasOwnProperty k 29 | # ... 30 | ``` 31 | 32 | > 当写 `for x of obj` 或者 `for x in arr` 时,你其实时在给一个当前作用域内名为 x 的变量赋值。循环结束后还可以继续利用这些变量。 33 | 34 | ```coffeescript 35 | for name, occupation of murder 36 | break if occupation is 'butler' 37 | console.log "#{name} did it!" 38 | 39 | countdown = [10..0] 40 | for num in countdown 41 | break if abortLaunch() 42 | if num is 0 43 | console.log 'We have liftoff!' 44 | else 45 | console.log "Launch aborted with #{num} seconds left" 46 | ``` 47 | 48 | > `for ... in` 还支持 `for ... of` 不支持的补充修饰符 `by` 。 49 | 50 | ```coffeescript 51 | decimate = (army) -> 52 | execute(soldier) for soldier in army by 10 53 | 54 | animate = (startTime, endTime, framesPerSecond) -> 55 | for pos in [startTime..endTime] by 1 / framesPerSecond 56 | addFrame pos 57 | 58 | countdonw = (max) -> 59 | console.log x for x in [max..0] by -1 60 | ``` 61 | 62 | > 但是要注意,数组并不支持负步值。当你写 `for ... in[start..end]` 时,`start` 是第一个迭代值(而 `end` 是最后一个迭代值),只要 `start>end` 负步值就没有问题。但是每当你写 `for ... in arr` 时,第一个迭代索引值总是 0 ,最后一个迭代索引是 `arr.length - 1` 。因此如果 `arr.length` 大于零,则负步值就会产生无限循环——永远不可能达到最后一个迭代索引! 63 | 64 | ### 条件迭代 65 | 66 | ```coffeescript 67 | makeHay() while not sunShines() is makeHay() until sunShines() 68 | ``` 69 | 70 | > 注意,在这两种句法中,如果条件起初就不满足的话 makeHay() 一次都不会执行。 71 | 72 | ```coffeescript 73 | loop 74 | console.log "loop" 75 | 76 | # is 77 | 78 | while true 79 | console.log "loop" 80 | 81 | a = 0 82 | loop break if ++a > 999 83 | console.log a # => 1000 84 | 85 | break if ++a >999 loop # Error 86 | ``` 87 | 88 | ### 列表解析 89 | 90 | ```coffeescript 91 | negativeNumbers = (-num for num in [1, 2, 3, 4]) 92 | keysPressed = (char while char = handleKeyPress()) 93 | code = ["U", "U", "D", "D", "L", "R", "L", "R", "B", "A"] 94 | codeKeyValues = for key in code 95 | switch key 96 | when "L" then 37 97 | when "U" then 38 98 | when "R" then 39 99 | when "D" then 40 100 | when "A" then 65 101 | when "B" then 66 102 | evens = (x for x in [2..10] by 2) 103 | ``` 104 | 105 | > 列表解析式是 CoffeeScript 核心设计哲学的产物: CoffeeScript 中所有的东西都是表达式,并且每个表达式有一个值。 106 | 107 | ## 第四章 模块与类 108 | ### 4.2 原型的威力 109 | http://javascriptweblog.wordpress.com/2010/06/07/understanding-javascript-prototype 110 | 111 | ## 第六章 Node.js 服务器端程序 112 | ### 6.3 异步思想 113 | 114 | > 回忆一下我们在 2.2 节学到的:*只有函数会产生作用域*。在处理异步的回调时,不要期望循环能产生作用域,它会毁了你的,即使在其他方面做的再好也没用。事实上,这通常是在异步代码中出错最多的部分。 115 | 116 | ```coffeescript 117 | sum = 0 118 | while sum < limit 119 | sum += x = nextNum() 120 | getEncryptionKey (key) -> 121 | saveEncryptiond key, x, sum # FAIL! 122 | ``` 123 | 124 | > 在调用 `getEncryptionKey` 的回调函数时, x 和 sum 已经前移了——事实上,整个循环都已结束。因此随着循环对每一个 x 的迭代,最后保存下来的是循环运行结束后的 x 和 sum 的值。 125 | 126 | ```coffeescript 127 | sum = 0 128 | while sum < limit 129 | sum += x = nextNum() 130 | do (x, sum) -> 131 | getEncryptionKey (key) -> 132 | saveEncryptiond key, x, sum 133 | 134 | # is 135 | 136 | sum = 0 137 | while sum < limit 138 | sum += x = nextNum() 139 | ((x, sum) -> 140 | getEncryptionKey (key) -> 141 | saveEncryptiond key, x, sum 142 | ) x, sum 143 | ``` 144 | 145 | ## 附录 B 运行 CoffeeScript 的几种方法 146 | 147 | ### B.3 Rails 中的 CoffeeScript 148 | 149 | > Rails 3.1 通过 [Sprockers2](http://github.com/sstephenson/sprockets) 提供了对 CoffeeScript 的支持。 150 | 151 | > 旧版 Rails 借助于 [Barista](http://github.com/Sutto/barista) 也能直接集成 CoffeeScript 。 152 | 153 | Barista 更进一步,允许将 CoffeeScript 代码打包为 gems 或者从 gems 中获取 CoffeeScript 代码。这是一个将大项目拆成可重用的精简版组件的极佳方式。还能兼容 Heroku ,只需要扩展 [therubyracer-heroku](http://github.com/aler/therubyracer-heroku) 即可。therubyracer-heroku 是一个能运行于所有 Ruby 环境中的 JavaScript 解释器。(这些同样适用于 Rails 3.1 程序。Barista 和 Rails 3.1 都采用同样的 gem 来包装 CoffeeScript 解释器[1]。) 154 | 155 | Barista 和多数为 CoffeeScript 整合的 Web 框架的一个缺点是如果代码中有语法错误(编译未通过),则需要等到刷新页面或遇到不标准的 JavaScript 才能发现。 156 | 157 | > 我为解决这个问题写了一个 Growl 插件来扩展 Barista ,在 CoffeeScript 编译失败时,它能给出醒目的提示。 158 | 159 | ### B.4 CoffeeScript 中间件 160 | 161 | 中间件是一种在 Web 框架和服务器中间进行适配的软件,能让 CoffeeScript 的编译透明化,让程序以为自己使用的就是原来的 JavaScript 。 162 | 163 | [Rack-coffee](http://github.com/mattly/rack-coffee) 兼容 Rails , Sinatra 及其他所有主流的 Ruby Web 框架。 164 | 165 | Barista 也能运行在所有基于 Rack 的框架中。 166 | 167 | [CoffeeCup](http://github.com/dec/coffeecup) 为 Django , Pylons , CherryPy 以及其他无数的基于 WSGI 的框架提供了第一流的 CoffeeScript 支持。 168 | 169 | ### B.5 Node.js 上的 CoffeeScript 170 | 171 | 在 Node.js 中可是使用 coffee 命令直接运行 .coffee 问题,真正困难的是如何为前端提供编译好的 JavaScript 代码。 172 | 173 | Connect (Node.js Web 框架核心标准)提供了自动完成该工作的中间件。 174 | 175 | ```coffeescript 176 | compiler = connect.compiler src: coffeeDir, enable: ['coffeescript'] 177 | static = connect.static coffeeDir 178 | connect.createServer compiler, static 179 | ``` 180 | 181 | ## 附录 C JavaScript 开发者备忘录 182 | 183 | ```coffeescript 184 | x and= y # => x = x && y 185 | x or= y # => x = x || y 186 | x ?= y # => if (typeof x === 'undefined' || x == null) {x=y} 187 | ``` 188 | 189 | CoffeeScript 的 `in` 操作符其实是借用了 Array::indexOf ,如果 Array::indexOf 尚未定义(比如 IE8 ),那就会用等价的函数替代。 190 | 191 | [1]: https://github.com/josh/ruby-coffee-script 192 | -------------------------------------------------------------------------------- /Metaprograming_Ruby-Ruby元编程.md: -------------------------------------------------------------------------------- 1 | # Ruby 2 | Ruby 是们很有意思的语言,它的对象模型与 JavaScript 的原型模型非常相似,同时 Ruby 和 JavaScript 又都是动态语言,所以作笔记的时候,我会力图将书中的代码以 JavaScript 实现一次,代码会附在最后,同时标明章节。所有代码在 Firebug 下进行测试。 3 | 4 | ## 引言 5 | 元编程通常用来处理以下工作: 6 | 7 | * 编写一个外部系统的包装器,由于其动态特性,使得外部系统增加新接口时在不修改 Ruby 代码的情况下自动对其产生支持 8 | * 定义 DSL (特定领域语言) 9 | * 降低代码的重复性 10 | * 改造语言本身,使其满足工作需要 11 | * 等等…… 12 | 13 | ### 元编程的定义 14 | 元编程是编写在运行时候操纵语言构件的代码。(Page xvii) 15 | 16 | ### 静态语言和动态语言的区别:内省 17 | 在静态语言中,语言构件的行为像是幽灵,你可以在源代码中看到它们,然而在运行时它们就消失了。一旦编译器完成了它的工作,像变量和方法这样的东西就看不见摸不着了:它们只是内存的位置而已。 18 | 19 | 在动态语言中,在运行时绝大部分语言构件依然存在,你甚至可以询问关于它自己的问题。这就是内省 (introspection) 。 20 | 21 | ``` Ruby 22 | class Greeting 23 | def initialize(text) 24 | @text = text 25 | end 26 | 27 | def welcome 28 | @text 29 | end 30 | end 31 | 32 | my_object = Greeting.new "hello" 33 | 34 | my_object.class # => Greeting 35 | my_object.class.instance_methods false # => [:welcome] 36 | my_object.instance_variables # => [:@text] 37 | ``` 38 | 39 | ### 在程序运行时添加实例方法 40 | 书中提到了 ActiveRecord 类库,我就直接在最后写个 JavaScript 版的简短代码吧。 41 | 42 | ### 元编程与 Ruby 43 | Ruby 无疑是现今流行语言中对元编程最友好的一种语言。元编程如此的深入 Ruby 语言,甚至无法和“普通”的编程明确区分开来。你无法看着一段 Ruby 代码说,“这部分是元编程,其他不是”。从某种程度上看,元编程不过是每个 Ruby 程序员的例行工作。 44 | 45 | ## 第一章 对象模型 46 | Ruby 世界中对象只是其中一个公民,除此之外还有 类(class)、模块(module)以及实例变量(instance variable)等。元编程操控的就是这些语言构件。 47 | 48 | 这些语言构件存在于其中的系统称之为对象模型(object model)。对象模型是 Ruby 的灵魂,认真钻研它不仅能学到一些功能强大的技术,还能避免陷入某些陷阱。 49 | 50 | ### 1.2 打开类 51 | 52 | ``` Ruby 53 | class String 54 | def to_alphanumeric 55 | gsub /[^\w\s]/, '' 56 | end 57 | end 58 | ``` 59 | 60 | 在 Ruby 中,定义类的语句和其他语句没有本质的区别,你可以在类定义中放置任何语句。 61 | 62 | 从某种意义上来说,Ruby 的 class 关键字更像是一个作用域操作符而不是类型声明语句。它的确可以创建一个还不存在的类,不过也可以把这开成是一种副作用。*对于 class 关键字,其核心任务是把你带到类的上下文中,让你可以在其中定义方法*。你总是可以重新打开以及存在的类并对它进行动态修改。 63 | 64 | #### 隐患:猴子补丁(Monkeypatch) 65 | 如果你粗心地为某个类添加了某些功能,可能会无意地覆盖原有的功能,如果程序的其他部分依赖这个功能,程序就会出错。 66 | 67 | 可以通过 instance.methods 内省得到已有的方法名,避免与其产生冲突。 68 | 69 | ### 1.3 类的真相 70 | #### 什么是对象 71 | * 一组实例变量:可以通过 Object#instance_variables 方法获得一个实例变量的列表。Ruby 中对象和实例变量没有关系,当给实例变量赋值时,它们就生成了,如果没有调用赋值的方法,那么它们就不存在。因此,对同一个类,你可以创建具有不同实例变量的对象。 72 | * 一个指向其类的引用:可以通过 Object#methods 方法获得一个对象的方法列表。一个对象的实例变量存在于对象本身,而对象的方法存在与对象的类。一个对象仅仅包含它的实例变量以及一个对自身类的引用。这就是为什么同一个类的对象共享同样的方法,但不共享实例变量的原因。*对象调用的称之为方法,类中定义的称之为实例方法*,也就是说,需要定义一个类的实例才能调用这个方法。 73 | 74 | #### 什么是类 75 | **类自身也是对象**。 76 | 77 | 类和其他任何对象一样,也有自己的类,它的名字叫做 Class : 78 | 79 | ``` Ruby 80 | "hello".class # => String 81 | String.class # => Class 82 | ``` 83 | 84 | 和任何对象一样,类也有方法 (new) ,类的方法就是 Class 的实例方法。所以的类都最终继承于 Object ,Object 本身继承于 BaseObject ,BaseObject 是 Ruby 对象体系中的根节点。 85 | 86 | ``` Ruby 87 | Class.superclass # => Module 88 | Module.superclass # => Object 89 | ``` 90 | 91 | 因此,一个类不过是一个增强的 Module 。增加了三个方法—— new(), allocate(), superclass() 而已。这几个方法可以让你创建对象并把它纳入到类体系中。除此之外,类和模块基本上是一样的。绝大多数适用于类的内容也适用于模块,反之亦然。 92 | 93 | 类无非是一个对象(Class类的一个实例)外加一组实例方法和一个对其超类的引用。Class类是Module的子类,因此一个类也是一个模块。 94 | 95 | #### 常量 96 | 常量(任何大写字母开头的引用)作用域不同于变量,常量的结构和树形结构类似,模块(还有类)像目录,常量则像文件,只要不在同一个目录下,不同文件的文件名可以重复,甚至可以通过路径方式引用一个常量,如 MyModule::MyClass::MyConstant。 97 | 98 | 跟目录与文件一样,常量也可以通过路径方式来唯一标识。如果深入探究常量的树形结构,可以在常量前加上一组双冒号来表示根路径,从而得到一个绝对路径。 99 | 100 | Module 类提供了两个叫做 constants 的方法,一个是实例方法,一个是类方法。实例方法返回当前范围内的常量,像文件系统的 `ls` 命令。类方法返回当前程序程序中所有顶级常量,包括类名。 101 | 102 | 如果想获得当前常量的路径,可以使用 Module.nesting 方法。 103 | 104 | 使用命名空间(也就是在模块中定义常量),可以避免类名的冲突问题。比如加载文件用到的 load 方法,如果第二个可选参数为 true ,则其常量常量就仅在自身范围内有效。用这种方式加载的文件,Ruby 会创建一个匿名模块,使用它作为命名空间来容纳文件中定义的所有常量,加载完成后,该模块被销毁。 105 | 106 | require 方法和 load 颇为相似,但目的不同。require 用来导入类库,这些类库中的类名通常是导入这些库是希望得到的,因此没有理由在加载后销毁它们。C 107 | 108 | ### 1.5 调用一个方法时发生了什么 109 | 当调用一个方法时,Ruby 会做两件事: 110 | 111 | 1. 找到这个方法。这个过程称为*方法查找*。 112 | 2. 执行这个方法。为了做到这一点,Ruby 需要一个叫做 self 的东西。 113 | 114 | #### 方法查找 115 | 116 | * 接收者:就是调用方法所在的对象。比如 `my_string.reverse()` my_string 就是接收者。 117 | * 祖先链:每个类都有一个祖先链,可以通过调用类的 ancestors() 方法来获得。这个链从自己所属的类开始,向上直到 BaseObject 为止。 118 | 119 | 为了查找一个方法,Ruby 会首先在接收者的类中查找,然后一层层地在祖先链中查找,直到找到这个方法。 120 | 121 | 当你在一个类(甚至在另外一个模块)中包含(include)一个模块时,Ruby 创建了一个封装该模块的匿名类,并把这个匿名类插入到祖先链当中,其在链中的为止正好在包含它的类上方。 122 | 123 | 封装类有时也叫代理类,superclass 方法会当它根本不存在,正常的代码也无法访问这些类。 124 | 125 | Ruby 中有些像 print() 的方法,好像所有对象都有 print() 方法一样。这样的方法其实是 Kernel 模块的私有方法。 126 | 127 | ``` Ruby 128 | Kernel.private_instance_methods.grep /^pr/ # => [:printf, :print, :proc] 129 | ``` 130 | 131 | Object 类包含了 Kernel 模块,因此 Kernel 就进入了每个对象的祖先链。这样在某个对象中可以随意调用 Kernel 模块的方法。使得 print 好像一个语言的关键字。其实它不过是一个方法。 132 | 133 | 如果给 Kernel 模块增加一个方法,这个*内核方法(Kernel Method)*就对所有对象可用。 134 | 135 | #### 执行方法 136 | 当调用一个方法时,Ruby 需要持有一个接收者的引用,正是这个引用的存在,它可以记得哪个对象是接收者,再用它来执行这个方法。 137 | 138 | 每一行代码都会在一个对象中被执行——这个对象就是“当前对象”,用 self 表示,因为可以用 self 关键字来访问它。 139 | 140 | 在给定时刻,只有一个对象能够充当当前对象,但没有哪个对象能够长期充当这个角色。 141 | 142 | *当调用一个方法时,接收者就称为了 self 。从这一刻起,所有的实例变量都是 self 的实例变量,所有没有明确指明接收者的方法都在 self 上调用*。 143 | 144 | 如果没有调用任何方法时,self 由 Ruby 解释器在开始运行 Ruby 程序是就创建的名为 main 的对象担任。这个类有时被称为*顶级上下文(top level context)*。 145 | 146 | 在类或者模块定义中,self 的角色由这个类或模块担任: 147 | 148 | ``` Ruby 149 | class MyClass 150 | self # => MyClass 151 | end 152 | ``` 153 | 154 | #### 私有变量 155 | 私有变量由两条规则一起控制: 156 | 157 | 1. 如果调用方法的接收者不是自己,则必须明确指明一个接收者。 158 | 2. 私有方法只能被隐含接收者(self)调用。 159 | 160 | 这两条规则耦合后的规则被称为“私有规则”: 161 | 162 | 不能明确指定一个接收者来调用一个私有方法。换言之,每次调用一个私有方法时,只能调用与隐含的的接收者——self上。 163 | 164 | ## 附录:JavaScript 的代码实现: 165 | ### 附录 166 | #### 内省 167 | 168 | ``` JavaScript 169 | function Greeting(text) { 170 | this.text = text; 171 | 172 | this.welcome = function() { 173 | return this.text; 174 | }; 175 | } 176 | 177 | var my_object = new Greeting("hello"); 178 | 179 | my_object.constructor; // => Greeting(text) 180 | 181 | // JavaScript 没有实例变量的概念,对它而言,方法也好,变量也罢,无非属性。 182 | for (var attr in my_object) 183 | console.log(attr); // => text welcome 184 | ``` 185 | 186 | #### 在程序运行时添加实例方法 187 | ``` JavaScript 188 | function test(nameList) { 189 | var i, name; 190 | if (Object.prototype.toString.call(nameList) !== '[object Array]') return; 191 | for (var i=0; i < nameList.length; i++) { 192 | name = nameList[i]; 193 | this[name] = function() { 194 | console.log('this is ' + name); 195 | } 196 | } 197 | } 198 | 199 | var a = new test(["hello", "world"]); 200 | ``` 201 | 202 | ### 第一章 203 | #### 1.2 打开类 204 | ``` JavaScript 205 | String.prototype.to_alphanumeric = function() { 206 | return this.replace(/[^\w\s]/g, ""); 207 | }; 208 | ``` 209 | -------------------------------------------------------------------------------- /JavaScript_DOM_编程艺术.md: -------------------------------------------------------------------------------- 1 | # JavaScript DOM 编程艺术 (第二版) 笔记 2 | ## 第三章 DOM P32 3 | D: Document, O: Object, M: Model 4 | 5 | ### 3.2 对象的分类 6 | 对象可以分为三类: 7 | 8 | * 用户定义对象(user-defined object) 9 | * 内建对象(native object):比如 Array,String,Date 等等,不论 JavaScript 所在哪个平台都有的对象 10 | * 宿主对象(host object):比如 window,document,htmlElement 等等,会根据所在平台不同而改变的对象 11 | 12 | window 对象对应浏览器窗口本身,其属性和方法通常称为 BOM(browser object model) 13 | 14 | ### 3.3 什么是 文档对象模型 15 | 文档对象模型就是一颗家谱树,用 parent, child, sibling 来表明成员之间的关系 16 | 17 | ### 3.4 节点 (node) 18 | 节点有三种: 19 | 20 | * 元素节点 : DOM 的原子,标签的名字就是元素的名字 21 | * 文本节点 : 元素节点中的直系文字 22 | * 属性节点 : 元素节点更为具体的描述 23 | 24 | ## 第四章 案例研究:JavaScript 图片库 P46 25 | ### 4.2 JavaScript 26 | #### 4.2.1 非 DOM 解决方案 27 | setAttribute 和 getAttribute 是 “第一级 DOM” 的组成部分 28 | 29 | 推荐使用第一级 DOM 而不使用 element.attr 的原因是代码的兼容性和你需要记住的 API 数量 30 | 31 | ### 4.4 对这个函数进行扩展 32 | * childNodes : 可以用来获取任何一个元素的所有子*节点* 33 | * nodeType : 获取节点的类别:元素节点为1,属性节点为2,文本节点为3 34 | * nodeValue : 得到和设置一个节点的值 35 | * firstChild : 获取 childNodes 返回的列表里的第一个子节点 36 | * lastChild : 获取 childNodes 返回的列表里的最后一个子节点 37 | 38 | ## 第五章 最佳实践 P61 39 | ### 5.2 平稳退化 40 | #### 5.2.1 "javascript:" 伪协议 41 | ”真“协议用于在因特网上的计算机之间传输数据包,比如 HTTP 协议、FTP 协议等 42 | 伪协议是一种非标准化的协议,"javascript:" 伪协议让我们通过一个链接来调用 JavaScript 函数 43 | 44 | #### 5.2.3 谁关心这个 45 | 强调“平稳退化”的意义在于,只有极少数自动化程序(比如搜索机器人)能够读懂 JavaScript 46 | 47 | 做到平稳退化的办法也并不困难,让元素在没有 JavaScript 支持时保持可用即可 48 | 49 | ### 5.5 向后兼容 50 | * 对象检测 51 | * 浏览器嗅探:不提倡的原因是由于浏览器和浏览器版本的增多,会使得浏览器嗅探的代码变得越来越臃肿,而且一旦浏览器发生改变,代码也有可能会需要随之发生改变 52 | 53 | 对象检测代码: 54 | 55 | ``` javascript 56 | if (method) { 57 | // code... 58 | } 59 | ``` 60 | 61 | ## 第六章 案例研究:图片库改进版 P75 62 | ### 6.3 它的 JavaScript 与 Html 标记是分离的吗 63 | > 我们在学校里学过一种理论,叫结构化程序设计 (structed programming),其中有这样一条原则:函数应该只有一个入口和一个出口。 64 | 65 | > 在实际工作中,过分拘泥于这项原则往往会使代码变得非常难以阅读,如果为了避免留下多个出口而去改写那些 if 语句的话,这个函数的核心就会被掩埋在一层又一层的花括号里,就想下面这样 66 | 67 | > 我个人认为,如果一个函数有多个出口,只要这些出口集中出现在函数的开头部分,就是可以接受的。 68 | 69 | ``` javascript 70 | function prepareGallery() { 71 | if (document.getElementsByTagName) { 72 | if (document.getElementById) { 73 | if (document.getElementById("imagegallery")) { 74 | // statements go here ... 75 | } 76 | } 77 | } 78 | } 79 | ``` 80 | 81 | ### 6.6 键盘访问 82 | **小心 onkeypress** 83 | 84 | 这个事件处理函数很容易出问题,用户每按下一个键都会触发它,在某些浏览器里,甚至包括 Tab 键!这意味着如果绑定在 onkeypress 事件上的处理函数返回的是 false ,那些只能用键盘访问的用户将永远无法离开当前链接。 85 | 86 | onclick 事件的名字似乎给人一种只与鼠标点击动作相关的印象,但事实并非如此:在几乎所有的浏览器里,用 Tab 移动到某个链接然后按下回车的动作也会触发 onclick 。 87 | 88 | 最好不要用 onkeypress 事件处理函数,在 onclick 事件处理函数已经能满足需要的情况下。 89 | 90 | ### 6.8 DOM Core 和 HTML-DOM 91 | getElementById、getElementsByTagName、getAttribute、setAttribute 这些方法都是 DOM Core 的组成部分,它们不专属于 JavaScript,支持 DOM 的任何程序设计语言都可以使用它们。它们的用途也并非仅限于此,它们可以用来处理任何一种标记语言(比如 XML)编写出来的文档。 92 | 93 | 在使用 JavaScript 语言和 DOM 为 HTML 文件编写脚本时,还有许多属性可供选用,比如 onclick 。这些属性属于 HTML-DOM ,它们在 DOM Core 出现之前很久就为人所知了。 94 | 95 | 比如: 96 | 97 | * document.getElementsByTagName("form") => document.forms 98 | * element.getAttribute("src") => element.src 99 | 100 | 这些方法和属性可以相互替换。同样的操作既可以用 DOM Core 来实现,也可以使用 HTML-DOM 来实现。HTML-DOM 代码通常更短,但是它们只能用来处理 Web 文档(因为 XML 可以自定义标签) 101 | 102 | ## 第七章 动态创建标记 P96 103 | ### 7.1 一些传统方法 104 | * document.write : 不推荐这个是因为它违背了“行为应该和表现分离”的原则,这样的标记既不容易阅读和编辑,也无法享受到行为与结构分离开来的好处。同时容易导致验证错误,比如在 ` 124 | 125 | ## 第三章 模型和数据 126 | 127 | ### 构建对象关系映射 (ORM) 128 | 129 | > 对象关系映射( Object-relational mapper,简称 ORM )是在除 JavaScript 以外的编程语言中常见的一种数据结构。 130 | 131 | > 本质上讲,ORM 是一个包装了一些数据的对象层。以往 ORM 常用语抽象 SQL 数据库,但在这里 ORM 只是用于抽象 JavaScript 数据类型。这个额外的层有一个好处,我们可以通过给它添加自定义的函数和属性来增强基础数据的功能。 132 | 133 | > 这样能够增加代码的重用率。 134 | 135 | `Object.create()` 只有一个参数即原型对象,它返回一个新对象,这个新对象的原型就是新传入的参数。`Object.create()` 是属于 ES5 规范的特性。 136 | 137 | ### 增加 ID 支持 138 | 139 | Robert Kieffer 写了一个简单明了的 GUID(Globally Unique Identifier) [生成器](http://goo.gl/0b0hu),它使用 `Math.random()` 来产生一个伪随机数的 GUID 。 140 | 141 | ## 第四章 控制器和状态 142 | 143 | > 将状态保存在客户端其中一个主要好处是带来更快速的界面响应。 144 | 145 | > 但将状态保存在客户端也存在诸多挑战。状态保存在哪里? 146 | 147 | > 首先,应当避免将状态或数据保存在 DOM 中,因为根据滑坡理论[9],这会导致程序逻辑变得更加错综复杂且混乱不堪。 148 | 149 | 这个我有点不理解啊,这两者真的有什么关系么? 150 | 151 | > 在我们的例子中使用了 MVC 架构来搭建应用,状态都是保存在应用的控制器里的。 152 | 153 | > 到底什么是控制器?你可以将控制器理解为应用中视图和模型之间的纽带。只有控制器知道视图和模型的存在并将它们连接在一起。 154 | 155 | > 控制器是模块化的且非常独立,了解这一点非常重要。理想状况下不应该定义任何全局变量,而应当定义完全解耦的功能组件。模块模式是处理组件解耦的非常好的方法。 156 | 157 | ### 模块模式 158 | 159 | > 模块模式是用来封装逻辑并避免全局命名空间污染的好方法。 160 | 161 | 其实就是用匿名函数包起来。额外的好处是可以把 window 和 document 等对象传进来然后取个别名,省得打那么长的名字。 162 | 163 | ### 添加少量上下文 164 | 165 | > 使用局部上下文是一种架构模块很有用的方法,特别是当需要给事件注册回调函数时。实际情况是,模块中的上下文都是全局的。 166 | 167 | > 如果想自定义作用域的上下文,则需要将函数添加至一个对象中。比如: 168 | 169 | ```coffeescript 170 | (-> 171 | mod = 172 | load: (func) -> $.proxy func, this 173 | assetsClick: (event) -> # 处理点击 174 | mod.load -> 175 | @view = $ "#view" 176 | @view.find(".assets").click $.proxy @assetsClick, this 177 | )() 178 | ``` 179 | 180 | > 在 `load()` 中的上下文不是全局的,而是 mod 对象。 181 | 182 | #### 文档加载完成后载入控制器 183 | 184 | > Controller 类不一定非要是构造函数,因为这里并不需要在生成子控制时传入上下文。 185 | 186 | ```coffeescript 187 | exports = this 188 | (($) -> 189 | mod = {} 190 | mod.create = (includes) -> 191 | result = -> @init.apply this, arguments 192 | 193 | result.fn = result.prototype 194 | result.fn.init = -> 195 | 196 | result.proxy = (func) -> $.proxy func, this 197 | result.fn.proxy = result.proxy 198 | 199 | result.include = (obj) -> $.extend this.fn, obj 200 | result.extend = (obj) -> $.extend this, obj 201 | 202 | result.include(includes) if includes 203 | result 204 | 205 | exports.Controller = mod 206 | )(jQuery) 207 | ``` 208 | 209 | ```coffeescript 210 | $ -> 211 | ToggleView = Controller.create 212 | init: (view) -> 213 | @view = $ view 214 | @view.mouseover @proxy(@toggleClass), true 215 | @view.mouseout @proxy(@toggleClass), false 216 | toggleClass: (event) -> 217 | @view.toggleClass "over", e.data 218 | 219 | new ToggleView "#view" 220 | ``` 221 | 222 | > 我们还做了一个重要的更改,就是根据实例化的情况来将视图元素传入控制器,而不是将元素直接写死在代码中。 223 | 224 | > 这一步提炼很重要,因为将代码抽离出来,我们就可以将控制器重用于不同的元素,同时保持代码最短。 225 | 226 | #### 访问视图 227 | 228 | > 一种常见的模式是一个视图对应一个控制器。视图包含一个 ID ,因此可以很容易地传入控制器。然后在视图中的元素则使用 className 而不是 ID ,所以和其他视图中的元素不会产生冲突。这种模式为一种通用实践提供了良好的架构,但用法可以很灵活。 229 | 230 | > 本章中所提到的访问视图的方法无非是使用 jQuery() 选择器,将指向视图的本地引用存储在控制器。后续对视图中的元素查找则被视图的引用限制住了范围,从而提高了查找速度。 231 | 232 | > 但是,这的确意味着控制器中会塞满很多选择器,需要不断的查找 DOM 。我们可以在控制器中开辟一个空间专门存放选择器到变量的映射表。 233 | 234 | > 比如: 235 | 236 | ```coffeescript 237 | elements: 238 | "form searchForm": "searchForm" 239 | "form input[type=text]": "searchInput" 240 | ``` 241 | 242 | 原书中写了一个叫做 `refreshElements()` 的函数,用于更新控制器的 `this.searchForm` 和 `this.searchInput` 变量。可以在初始化控制器是调用,也可以在其他任何时候调用。 243 | 244 | 可以写一个函数用于创建 elements 映射表中对应的函数,在实例化控制器时调用,调用对应函数就能够获取到指定的元素。这样就能够获取到实例化后才动态创建的元素了。 245 | 246 | #### 委托事件 247 | 248 | > 同样地,我们可以将绑定的事件都移除,并通过一个 events 对象来代理,这个 events 包含事件类型和选择器到回调函数的映射。这和 elements 对象非常类似,格式是这样的: 249 | 250 | ```coffeescript 251 | events: 252 | "submit form": "submit" 253 | ``` 254 | 255 | 然后在初始化控制器时,使用 jQuery 的 `delegate()` 函数或者从 1.7 开始出现的 `on()` 函数来委托到控制器绑定的视图根元素上, 256 | 257 | ### 状态机 258 | 259 | 状态机,也称之为有限状态机 (Finite State Machines, FSM) ,可以轻松管理很多控制器,根据需要显示和隐藏视图。 260 | 261 | 状态机本质上有两部分构成:状态和转换器。它只有一个活动状态,但也包含很多非活动状态 (passive state) 。当活动状态之间相互切换时就会调用状态转换器。 262 | 263 | 如何实现一个状态机? 264 | 265 | > 首先使用 jQuery 的事件 API 创建一个 Event 对象,给它添加绑定和触发状态机事件的能力: 266 | 267 | ```coffeescript 268 | Event = 269 | bind: -> 270 | @o = $({}) unless @o 271 | @o.bind.apply @o, arguments 272 | 273 | trigger: -> 274 | @o = $({}) unless @o 275 | @o.trigger.apply @o, arguments 276 | ``` 277 | 278 | > 现在我们来创建 StateMachine 类,它包含一个主要的函数 `add()`: 279 | 280 | ```coffeescript 281 | StateMachine = -> 282 | StateMachine.fn = StateMachine.prototype 283 | 284 | $.extend StateMachine.fn, Event 285 | 286 | StateMachine.fn.add = (controller) -> 287 | @bind "change", (event, current) -> 288 | if controller is current 289 | controller.activate() 290 | else 291 | controller.deactivate() 292 | 293 | controller.active = $.proxy (-> 294 | @trigger "change", controller 295 | ), this 296 | ``` 297 | 298 | > 这个状态机的 `add()` 函数将传入的控制器添加至状态列表,并创建一个 `active()` 函数。当调用 `active()` 时,控制器的状态就转换为激活,对于激活状态的控制器,状态机将基于它调用 `activate()` ,对于其他控制器,状态机则调用 `deactivate()` 。 299 | 300 | > 尽管状态机给我们提供了 `active()` 函数,我们同样可以通过手动触发 `change` 事件来改变状态: 301 | 302 | ```coffeescript 303 | stateMachine.trigger "change", randomController 304 | ``` 305 | 306 | ### 路由选择 307 | 308 | 定位单页面应用的 url 不能发生改变,否则会刷新页面,但是用户习惯使用浏览器的前进后退和通过唯一的 url 来获取 web 资源。因此需要将应用的状态反应在 url 的 hash 中,建立状态和 hash 的某种对应关系。 309 | 310 | 太过频繁地设置 hash 也会影响性能,特别是在移动终端的浏览器中,要注意限制滚动,否则可能会造成页面的频繁滚动。 311 | 312 | #### 检测 hash 变化 313 | 314 | 主流浏览器都支持 window 的 hashchange 事件: 315 | 316 | ie >= 8 317 | firefox >= 3.6 318 | chrome 319 | safari > =5 320 | opera >= 10.6 321 | 322 | 有个 jQuery 插件 http://goo.gl/Sf41P 能够为老浏览器添加 hashchange 支持。 323 | 324 | 同时,记得初始化页面的时候手动触发这个事件。 325 | 326 | #### 抓取 ajax 327 | 328 | > 由于很多搜索引擎爬虫程序无法运行 JavaScript ,因此它们也无法得到动态创建的内容。当然页面的 hash 路由也不会起作用。在爬虫程序的眼中,它们看上去都是相同的 URL ,因为 hash 不会发送给服务器。 329 | 330 | 如果想要搜索引擎能够抓取 JavaScript 程序的内容,“工程师想到了一个办法,就是创建内容的镜像[10]。 把这个特殊的页面快照发送给爬虫,而正常的浏览器继续使用 JavaScript 来生成内容。 331 | 332 | > 这增加了工程师的工作量,而且要做很多额外工作,比如浏览器嗅探。通常不推荐在应用中添加浏览器嗅探。幸运的是,Google 对引擎做了改进,它提出了“Ajax 抓取规则”(http://goo.gl/rhNr9)。 333 | 334 | 不管是多么纯净的 HTML 或者文本片段,服务器都可以根据它来定位其资源位置,用这种规则就可以实现资源的索引。 335 | 336 | 如果以及实现了静态页面版本,可以使用 301 重定向到静态页面地址,这样在搜索结果中就依旧是带有 hash 的 URL 。 337 | 338 | 一旦给站点增加了对“Ajax 抓取规则”的支持,可以使用 [Fetch as Googlebot tool](http://www.google.com/support/webmasters/bin/answer.py?hl=en&answer=158587) 来检查工作是否生效。 339 | 340 | #### 使用 HTML5 History API 341 | 342 | > History API 是 HTML5 规范组成的一部分,利用它可以实现将当前地址替换为任意 URL 。你也可以控制是否将新的 URL 添加至浏览器的历史记录中,从而根据需要来控制浏览器的“后退”按钮。和设置地址的 hash 类似,关键是页面不会重新加载,页面状态也会一直保持下来。 343 | 344 | > 支持 History API 的浏览器有: 345 | 346 | Firefox >= 4.0 347 | Safari >= 5.0 348 | Chrome >= 7.0 349 | IE: 不支持 350 | Opera >= 11.5 351 | 352 | 这个 API 主要是 `history.pushState()` 函数和 popstate 事件。 353 | 354 | popstate 是在页面加载后或者 `history.pushState()` 方法调用是触发的。 355 | 356 | 具体可以阅读以下资源: 357 | 358 | https://developer.mozilla.org/en-US/docs/DOM/Manipulating_the_browser_history#Adding_and_modifying_history_entries 359 | 360 | https://developer.mozilla.org/zh-CN/docs/DOM/window.onpopstate 361 | 362 | ## 第五章 视图和模板 363 | 364 | ### 模板 365 | 366 | jQuery.tmpl 是由微软开发的,是在 John Resig 的[原始工作](http://goo.gl/sFh6c)的基础上作的模板插件,该库目前仍在维护中,并在 jQuery 官网上有完整的[文档](http://api.jquery.com/jquery.tmpl)。 367 | 368 | ### 模板 Helpers 369 | 370 | > 有时在视图内部使用“通用 helper 函数”(generic helper function) 是非常好用的,我们需要保持良好的 MVC 架构的思路,而不是直接在视图中任意添加感兴趣的函数 371 | 372 | 我们应当将他抽象出来,并用命名空间进行管理,而不是直接将函数残杂进视图中,这样才能保持逻辑和视图之间的解耦。 373 | 374 | 同时,抽象出 helper 函数还能够提高代码的重用率。 375 | 376 | ### 模板存储 377 | 378 | 可考虑的解决方案有这么几个: 379 | 380 | * 在 JavaScript 中以行内形式存储 381 | * 在自定义 script 中以行内形式存储 382 | * 远程加载 383 | * 在 HTML 中以行内形式存储 384 | 385 | > 你可以将模板保存在 JavaScript 文件中,但并不推荐这样做,因为这需要将视图的代码放入一个控制器中,这违背了 MVC 的原则。 386 | 387 | > 你可以根据需要通过 Ajax 动态加载模板。这种方法的好处是初始化页面的体积非常小,缺点是模板加载时 UI 的渲染会变得很慢。使用 JavaScript 来构建应用的主要原因就是速度问题... 388 | 389 | > 你还可以把模板保存在 HTML 中,以行内的形式保存。好处是它不存在 UI 加载慢的问题,源代码也更清晰。缺点也是显而易见,它增加了页面的体积。坦白讲,这种体积增加造成的性能损失微不足道,特别是当服务器开启了压缩和缓存的情况下。 390 | 391 | 其实 [brunch](http://brunch.io) 提供了另一种思路,基于模块加载器,就是将模板作为一个模块,与其他 JavaScript 模块合并为一个文件,需要模板时直接 `request("templateName")` 。 392 | 393 | ### 绑定 394 | 395 | 从本质上讲,绑定将视图元素和 JavaScript 对象(通常是模型)挂接在一起。当 JavaScript 对象发生改变时,视图会根据新修改后的对象做适时更新。 396 | 397 | 但是还存在一种模式称为 "MVVM" (Model-View-ViewModel) ,不但对模型的修改会体现在界面上,界面上的修改同样能够作用在模型中。 398 | 399 | ## 第六章 依赖管理 400 | 401 | ### CommonJS 402 | 403 | #### 模块和浏览器 404 | 405 | > 译注3: 406 | 407 | > 在客户端,为了处理模块依赖关系不得不将模块主逻辑包含在某个回调函数中,加载模块的过程实际是“保存回调”的过程,最后统一处理这些回调函数的执行顺序。作为模块加载器的鼻祖 YUILoader 就是遵循这种逻辑实现的,并在 YUI3 中形成了其独具特色的 `use()` 和 `add()` 模块化编程风格。为了便于理解客户端模块和加载器的基本原理,可以参照译者实现的一个小型类库 [Sandbox.js](http://github.com/jayli/sandbox) ,文档中详细讲解了模块加载器的基本原理。 408 | 409 | ### 包装模块 410 | 411 | > 可以在服务器端将小文件合并为一个文件输出,这种做法一举两得。这样浏览器只需发起一个 HTTP 请求来抓取一个资源文件,就能将所有的模块都载入进来,显然这种做法更高效。 412 | 413 | > 使用打包工具也是一种明智的做法,这样就不必随意、无组织地打包模块了,而是静态分析这些文件,然后递归地计算它们的依赖关系。打包工具同样会将不符合要求的模块包装成转换格式,这样就不必手动输入代码了。 414 | 415 | > 除了在服务端合并代码,很多模块打包工具也支持代码的压缩(minify),以进一步减小请求的体积。实际上,一些工具——比如 [rack-modulr](http://goo.gl/0YcFK) 和 [Transporter](http://goo.gl/Exkpm) ——已经整合进了 Web 服务器,当首次处理某个请求时会自动处理模块操作。 416 | 417 | ### 模块的按需加载 418 | 419 | > 你可能不想用模块化的方式来写代码,或许是因为现有的代码和库改成用模块化的方式来管理需要做太多改动。幸运的是,有其他地带方案。 420 | 421 | * Sprockets (http://getsprockets.org) 422 | 423 | > Sprockets 给代码添加了同步 `require()` 支持,以 `//=` 的形式书写的注释都会被 Sprockets 进行预处理。比如,`//= require` 指令通知 Sprockets 来检查类库的加载路径,加载它以行内形式包含进来。 424 | 425 | > 尽管 Sprockets 是一个基于命令行的工具,但也有一些工具集成了 Rack 和 Rails ,比如 [rack-sprockets](http://github.com/kelredd/reck-sprockets) ,[PHP 的实现](http://goo.gl/0HvwT) 。 426 | 427 | > Sprockets (包括所有模块包装器)的中心思想是,所有的 JavaScript 文件都需要预处理,不管是在服务端用程序作处理还是使用命令行工具。 [1] 428 | 429 | * LABjs (http://www.labjs.com) 430 | 431 | > LABjs 是最简单的模块依赖管理器[2],它不需要任何服务器支持和 CommonJS 模块支持。使用 LABjs 载入你的脚本代码,减少了页面加载过程中的资源阻塞,这是一种极其简单且极为有效的性能优化方法。 432 | 433 | ### 无交互行为内容的闪烁 (FUBC) 434 | 435 | > 使用加载器来加载页面时,有一点需要尤为注意——用户可能会看到页面闪了一下,出现一部分没有交互行为的内容快速闪过(FUBC),比如在 JavaScript 执行之前会有一部分无样式的页面原始内容闪烁一下。 436 | 437 | 所以那么多 SPA 都会使用菊花来作为页面初始化时的样式,其他无关紧要的可以直接在行内写上样式 `display: none` ,然后再调用 jQuery 的 `show()` 函数来显示。 438 | 439 | ## 第七章 使用文件 440 | ### 浏览器支持 441 | 442 | * Firefox >= 3.6 443 | * Safari >= 6.0 444 | * Chrome >= 7.0 445 | * IE: no supprt 446 | * Opera >= 11.1 447 | 448 | ### 获取文件信息 449 | 450 | HTML5 的文件操作有一定限制,最主要的是只能访问被用户选中的文件。把文件拖曳进浏览器、选择要输入的文件或者粘贴文件都能满足这个安全限制。 451 | 452 | > 尽管已经有人实现了“基于 JavaScript 的文件系统” (http://goo.gl/CbDNt) ,但这里的访问是基于沙箱的。 453 | 454 | HTML5 中使用 File 对象来表示文件,有三个属性: 455 | 456 | * name: 文件名,只读字符串 457 | * size: 文件大小,只读整型 458 | * type: 文件的 MIME 信息,只读字符串,如果没有指定类型就为空字符串 459 | 460 | 文件路径也是没有办法得到的,如果有多个文件,可以通过 FileList 对象来获取,FileList 对象可以理解为 File 对象组成的数组。 461 | 462 | ### 文件输入 463 | 464 | HTML5 的 input:file 支持多文件选择,只要增加一个 `mulitiple` 属性即可,但这个控件的 UI 不完美,用户需要按住 Shift 键进行多选,这甚至没有相关的提示。 465 | 466 | 如果要验证选择的文件的合法性,可以通过 `input.files` 这个只读属性获取 FileList 对象。 467 | 468 | ### 拖曳 469 | 470 | > 早在 1999 年,微软的 IE5 就“设计”并实现了最原始的拖曳,自那时起后续的 IE 版本都支持拖曳。HTML5 规范刚刚增加了拖曳的内容,现在 Safari、Firefox 和 Chrome 也都模仿 IE 的实现提供了拖曳支持。然而,坦白的讲,HTML5 的规范并不明晰 (http://www.quirksmode.org/blog/archives/2009/09/the_html5_drag.html) ,需要重新整理。 471 | 472 | > 关于拖曳的事件至少有七个:dragstart、drag、dragover、dragenter、dragleave、drop 和 dragend 。 473 | 474 | > 即使你的浏览器不支持 HTML5 的文件 API ,但你仍可继续使用拖曳 API ,当前的浏览器支持程度如下: 475 | 476 | * Firefox >= 3.5 477 | * Safari >= 3.2 478 | * Chrome >= 7.0 479 | * IE >= 6.0 480 | * Opera: no support 481 | 482 | #### 拖曳 483 | 484 | > 拖曳[11]的实现非常简单,可以将元素的 darggable 属性设置为 `true` 来启用元素的拖曳。 485 | 486 | 我们可以监听 dragstart 事件,调用事件的 `setData()` 函数给可拖曳的元素关联一点数据: 487 | 488 | ```coffeescript 489 | $("#dragme").on "dragstart", (event) -> 490 | event = event.originalEvent 491 | event.dataTransfer.effectAllowed = "move" 492 | event.dataTransfer.setData "text/plain", $(this).text() 493 | event.dataTransfer.setData "text/html", $(this).html() 494 | event.dataTransfer.setDragImage "images/drag.png", -10, -10 495 | ``` 496 | 497 | 事件包含 dataTransfer 对象,其中包含拖曳和释放所需的方法。通过 `setData()` 函数可以设置“互联网媒体类型” (MIMEType) 和一个字符串数据。当元素释放拖曳,就会触发 drop 事件,这时就可以读取这个数据。如果元素拖曳到浏览器外部,其他的应用也可以根据它们支持的文件类型来处理释放拖曳的数据。 498 | 499 | 拖曳文本的时候推荐使用 text/plain 类型,包括应用的默认类型及释放拖曳的目标不支持其他格式时。 500 | 501 | 拖曳的链接包含两种格式: text/plain 和 text/uri-list 。通过将每个链接合并为一个新行来拖曳多个链接: 502 | 503 | ```coffeescript 504 | event.dataTransfer.setData "text/uri-list", "http://example.com" 505 | event.dataTransfer.setData "text/uri-list", "http://example.com\nhttp://example.com" 506 | ``` 507 | 508 | > `setDragImage()` 是可选的,用它可以设置拖曳操作工程中跟随鼠标移动的图片,它的参数是图片源地址和 x/y 坐标,这个坐标是相对于鼠标位置的。如果没有提供,则会将拖曳的元素复制一份并显示为半透明。 509 | 510 | > 除了 `setDragImage()` 之外还可以使用 `addElement(element, x, y)` ,它使用给定的元素来更新被拖曳的元素。换句话说,你可以为拖曳操作的过程自定义要显示的元素。 511 | 512 | > 同样,可以让用户拖曳浏览器之外的文件,只需设置 DownloadURL 类型即可。你可以将 URL 指定为文件路径,浏览器随后会将它下载下来。 513 | 514 | > 坏消息是,只有 Chrome 支持这个特性,且这个特性还在修订之中,但不影响使用。 515 | 516 | > DownloadURL 值的格式是由冒号风格的文件列表信息:媒体类型 (MIME) 、名称和地址。 517 | 518 | ```coffeescript 519 | $("#preview").on "dragstart", (event) -> 520 | event.originalEvent.dataTransfer.setData "DownloadURL", [ 521 | "application/octet-stream" # MIME 类型 522 | "File.exe" # 文件名 523 | "http://example.com/file.png" # 文件地址 524 | ].join ":" 525 | ``` 526 | 527 | #### 释放拖曳 528 | 529 | 可以在 dragover 事件中通过设置 `event.dataTransfer.dropEffect` 来控制鼠标的样式。 530 | 531 | 只有撤销(也就是阻止冒泡,取消浏览器默认行为)了 dragenter 和 dragover 事件,才能开始监听 drop 事件。当拖曳的元素或者文件在目标区域释放时才会触发 drop 事件,drop 事件的 `dataTransfer` 对象有一个 `files` 属性,它返回所有拖曳的所有文件的 FileList 。 532 | 533 | 可以使用 `dataTransfer.getData()` 函数来获取文件的数据,把支持的格式作为参数传入,如果没有这个格式,就会返回 `undefined` 。 534 | 535 | `dataTransfer` 对象有一个只读属性 `types` ,包含了设置在 dragstart 事件上的媒体类型格式组成的 DOMStringList (本质上是数组)。此外,如果拖曳了其他的文件,其中一个类型是字符串 "Files" 。 536 | 537 | ```coffeescript 538 | dt = event.dataTransfer 539 | for type in dt.types 540 | console.log type, dt.getData type 541 | ``` 542 | 543 | #### 取消默认的 Drag/Drop 544 | 545 | 如果想让文件被拖曳到 web 页面时不重定向到这个文件,可以撤销 body 的 dragover 事件: 546 | 547 | ```coffeescript 548 | $("body").on "dragover", (event) -> 549 | event.stopPropagation() 550 | event.preventDefault() 551 | false 552 | ``` 553 | 554 | ### 复制和粘贴 555 | 556 | 支持复制和粘贴的浏览器 API 还没有被标准化,而且没有纳入 HTML5 规范,所以需要做一些兼容性的工作。 557 | 558 | IE 从 5.0 时代就开始支持复制和粘贴了。WebKit 效仿了微软的 API 并对它做了一定的增强,而且和拖曳的 API 做了整合。两者除了使用的对象不同以外,几乎一模一样。 559 | 560 | > Firefox 不支持复制和粘贴,至少目前不支持,尽管它有一个专用的 API 可以访问剪切板。WebKit (Safari/Chrome) 对复制和粘贴的支持良好,我认为 W3C 最终会将剪切板 API 纳入标准规范。 561 | 562 | > 浏览器的支持情况如下: 563 | 564 | * Safari >= 6.0 565 | * Chrome (only pasting) 566 | * Firefox: no support 567 | * IE >= 5.0 (different API) 568 | 569 | 复制、剪切、粘贴的事件各有两个: 570 | 571 | * beforecopy 572 | * copy 573 | * beforecut 574 | * cut 575 | * beforepaste 576 | * paste 577 | 578 | before 前缀的事件能够根据需要选择是否撤销操作。 579 | 580 | 当用户复制了一些选中的文本时,就触发了 copy 事件,使用 `event.clipboardData` 就可以设置自定义的剪切板数据。 581 | 582 | 和 `dataTransfer` 对象很相似,`clipboardData` 也有 `setData()` 和 `getData()` 函数,前者的参数是媒体格式和字符串值,如果要调用这个函数,需要阻止浏览器的默认行为;后者主要是用于粘贴时根据媒体格式获取数据。不幸的是,根据我的测试结果来看,types 属性总是 null ,所以你无法看到剪切板中的数据支持哪种类型。 583 | 584 | WebKit 的 [午夜版](http://nightly.webkit.org/) 允许你访问 `clipboardData` 的 `files` 属性。 585 | 586 | IE 将 `clipboardData` 对象设置在 `window` 上,而不是事件上,你需要检查事件中是否存在这个对象,如果不存在就再检查一下是否在 window 对象中。 587 | 588 | ### 读文件 589 | 590 | 当获得 `File` 的引用以后,就可以用它来实例化一个 `FileReader` 对象,读取是异步的,所以需要提供回调函数。 591 | 592 | `FileReader` 的具体内容可以参见 https://developer.mozilla.org/en-US/docs/DOM/FileReader 593 | 594 | ```coffeescript 595 | preview = $ "img#preview" 596 | if file.type.match(/image.*/) and file.size < 50000000 597 | reader = new FileReader 598 | reader.onload = (event) -> 599 | data = event.target.result 600 | preview.attr "src", data 601 | reader.readAsDataURL file 602 | ``` 603 | 604 | #### 二进制大文件和文件分割 605 | 606 | `File` 对象有一个实例方法 `slice()` ,使用方法类似字符串的 `slice()` 函数,第一个参数是起始位置,第二个是偏移量。它返回 Blob 对象,我们可以使用支持 `File` 对象的方法对它做操作: 607 | 608 | ```coffeescript 609 | bufferSize = 1024 610 | pos = 0 611 | onload = (event) -> console.log "Read: ", event.target.result 612 | onerror = (event) -> console.log "Error: ", event 613 | while pos < file.size 614 | blob = file.slice pos, bufferSize 615 | reader = new FileReader 616 | reader.onload = onload 617 | reader.onerror = onerror 618 | reader.readAsText blob 619 | pos += bufferSize 620 | ``` 621 | 622 | `FileReader` 对象只能使用一次,之后就需要生成一个新实例了。 623 | 624 | 同时,由于受到 同源策略 的影响,以上代码如果不是通过域名进行的访问的话,就会操作失败并触发 error 事件。 625 | 626 | ### 自定义浏览器按钮 627 | 628 | 如何自定义一个文件输入框的样式?当鼠标移动到按钮上方时,在相同位置放置一个透明的文件输入框,尺寸和按钮一样。透明的文件输入框可以获取任何点击事件,并打开一个浏览文件的对话框。 629 | 630 | 在书的附加文件的 assets/ch07 文件夹里,有一个 jquery.browse.js ,这个文件是基于 jQuery 来实现这种功能的。调用 jQuery 实例的 `browseElement()` 函数来创建一个自定义的浏览按钮。 631 | 632 | ### 上传文件 633 | 634 | [XMLHttpRequest Level 2 规范](http://www.w3.org/TR/XMLHttpRequest2/)赋予了 Ajax 上传文件的能力 635 | 636 | * Safari >= 5.0 637 | * Firefox >= 4.0 638 | * Chrome >= 7.0 639 | * IE: no support 640 | * Opera: no support 641 | 642 | 可以使用现有的 XHR API 来完成文件上传,将 `File` 对象直接传入 `send()`,或者也可以作为 `FormData` 对象的一个参数传入 `send()` 。 643 | 644 | `FormData` 实例用一种非常简单的接口表示表单的内容。可以直接通过抓取一个表单来创建 `FormData` ,或者在实例化对象时传入已经存在的 form 元素。 645 | 646 | 如果在使用 jQuery 处理 Ajax 请求,则需要将 `processData` 选项设置为 `false` 。这样 jQuery 就不会尝试去对数据进行序列化处理了。 647 | 648 | ## 第八章 实时 Web 649 | 650 | > 为什么实时 Web 这么重要?我们生活在一个实时 (real-time) 的世界中,因此 Web 的最终最自然的状态也应当是实时的。 651 | 652 | ### WebSocket 653 | 654 | 在 WebSocket 的设计之初,设计者们希望只要初始连接使用了常用的端口和 HTTP 头字段,就可以和防火墙和代理软件和谐相处,可惜有些代理软件对 WebSocket 最初发起的请求头作了修改,打破了协议规则。当然,版本 76 的协议草案也无意中打破了对反向代理和网关的兼容性。为了更好更成功的使用 WebSocket ,这里给出一些步骤: 655 | 656 | * 使用 wss ,代理软件不会对加密的连接胡乱篡改,此外发送的数据都是加密的,也更不容易被人窃取。 657 | * 在 WebSocket 服务器前使用 TCP 负载均衡器,而不使用 HTTP 负载均衡器,除非某个 HTTP 负载均衡器大肆宣扬自己支持 WebSocket 。 658 | * 不要假设浏览器支持 WebSocket ,虽然浏览器支持 WebSocket 只是时间问题。如果连接无法快速建立,就立刻优雅降级。 659 | 660 | 服务器解决方案: 661 | 662 | * Node.js 663 | * node-Websocket-server (http://github.com/miksago/node-websocket-server) 664 | * Socket.io (http://socket.io) 665 | * Ruby 666 | * EventMachine (http://github.com/igrigorik/em-websocket) 667 | * Cramp (https://github.com/lifo/cramp) 668 | * Sunshowers (http://rainbows.rubyforge.org/sunshowers/) 669 | * Python 670 | * Twisted (http://github.com/rlotun/txWebSocket) 671 | * Apache module (http://code.google.com/p/pywebsocket) 672 | * PHP 673 | * php-Websocket (http://github.com/nicokaiser/php-websocket) 674 | * Java 675 | * Jetty (http://www.eclipes.org/jetty) 676 | * Google Go 677 | * native (http://code.google.com/p/go) 678 | 679 | #### Node.js 和 Socket.IO 680 | 681 | > 译注 4: 682 | 683 | > O'reilly 出版的一本小册子专门介绍 Node.js —— 《什么是 Node 》 (What is Node) 。已经有中译本,请参照: http://jali.github.com/whatisnode/ 684 | 685 | Socket.IO 是一个 Node.js 库,实现了 WebScoket 的 Server 端和 Client 端,同时 Client 端在浏览器不支持 WebSocket 时也能优雅降级为其他连接方式以达到浏览器兼容: 686 | 687 | * WebSocket 688 | * Adobe Flash Socket 689 | * ActiveX HTMLFile(IE) 690 | * 基于 multipart 编码发送 XHR (XHR with multipart encoding) 691 | * 基于长轮询的 XHR 692 | * JSONP 轮询(用于跨域的场景) 693 | 694 | Socket.IO 保证了它能兼容大多数浏览器: 695 | 696 | * Desktop 697 | * Internet Explorer 5.5+ 698 | * Safari 3+ 699 | * Google Chrome 4+ 700 | * Firefox 3+ 701 | * Opera 10.61+ 702 | * Mobile 703 | * iPhone Safari 704 | * iPad Safari 705 | * Android WebKit 706 | * WebOs WebKit 707 | 708 | 现在 Socket.IO 也有了其他语言实现的版本了,比如 709 | 710 | * Ruby (Rack) (http://github.com/markjeee/Socket.IQ-racke) 711 | * Python (Tornado) (http://github.com/MrJoes/tornadio) 712 | * Java (http://code.google.com/p/socketio-java) 713 | * Google Go (http://github.com/madari/go-socket.io) 714 | 715 | 如果想要寻求比 Socket.IO 更高级的解决方案,可以关注一下 [Juggernaut](http://github.com/maccman/juggernaut) ,它就是基于 Socket.IO 实现的。Juggernaut 实现了 WebSocket 的订阅/发布模式。 716 | 717 | > 这个库可以针对不同的客户端和实现环境作灵活扩展,比如 TLS 等。 718 | 719 | 如果你需要虚拟主机中的解决方案,可以参考 [Pusher](http://pusherappcom) 。Pusher 可以让你从繁杂的服务器管理事务中脱离出来,使你能将注意力集中在有意义的部分。 720 | 721 | ### 实时架构 722 | 723 | 实时架构是基于事件驱动的 (event-driven) 。事件往往是由用户交互触发的。要想为你的应用构建实时架构,则需要考虑两件事: 724 | 725 | * 哪个模型需要是实时的? 726 | * 当模型实例发生改变时,需要通知哪些用户? 727 | 728 | 当需要通知用户时,这就引入了一个新问题:如何向特定的用户发送通知? 729 | 730 | 最佳方法就是使用发布/订阅模型:服务器和客户端共同维持一个特定的信道,服务器负责推送,客户端负责订阅以及呈现。 731 | 732 | ### 感知速度 733 | 734 | 速度是 UI 设计最重要的也是最易忽略的问题,速度对用户体验 (UX) 的影响最大,并且直接影响网站的收益。很多大公司一直在研究,调查速度和网站收益之间的关系。 735 | 736 | * Amazon: 页面加载时间每增加 100 毫秒,就会造成 1% 的销售额流失(来源: Greg Linden, Amazon) 737 | * Google: 页面加载时间每增加 500 毫秒,就会造成 20% 的流量损失(来源: Marrissa Mayer, Google) 738 | * Yahoo!: 页面加载时间每增加 400 毫秒,在页面加载完成之前点“后端”按钮的人会增加 5% ~ 9% (来源:Nicole Sullivao, Yahoo!) 739 | 740 | > 感知速度和真实速度同样重要,因为感知速度关系到用户的相关体验。因此,关键是要让用户“感觉”到你的应用很快。 741 | 742 | > 除了交互设计的小技巧以外,Web 应用中最耗时间的部分是新数据的加载。最明智的做法是在用户请求数据之前预测用户的行为并预加载数据。 743 | 744 | > 当用户和你的应用产生交互时,你需要适时给用户一些反馈,通常使用一些可视化的进度指示来给出反馈。用行业术语来讲就是“期望管理”(expectation Managment)——要让用户知道当前项目的状态和估计完成时间。“期望管理”同样适用于用户体验领域,适时地给用户一些反馈,让之用户发生了什么事情,会让用户更有耐心等待程序的运行。 745 | 746 | ## 第九章 测试和调试 747 | 748 | > 很多人认为 JavaScript 的测试是一个鸡肋,因此多数 JavaScript 开发者没有为他们的程序写测试代码。在我看来这种认识的主要原因是,JavaScript 的自动化测试非常困难,且不具备伸缩性。 749 | 750 | > 首先考虑你的站点主要面向哪些人提供服务,然后决定要支持哪些浏览器,而不要太迷信统计数据。然而,根据经验来看,推荐大家主要测试这些浏览器[3]: 751 | 752 | * IE8, 9 753 | * Firefox 3.6 [4] 754 | * Safari 5 755 | * Chrome 11 756 | 757 | 当然,这在我看来也是见仁见智的事情了,如果是个人的项目,我更愿意只测试支持 Firefox, Safari, Chrome, Chromium ,对版本的支持则是上一个稳定版和当前稳定版以及开发者版。 758 | 759 | ### 单元测试 760 | 761 | > 手工测试更像是集成测试,从更高层次上保证应用的正常运行。单元测试则是更低层次的测试。 762 | 763 | > 单元测试的另一个优势是为自动化测试铺平道路。将很多单元测试整合起来就可以做到连续的集成测试了——每次代码有更新时都重新执行一遍所有的单元测试。 764 | 765 | 书里提到了 [QUnit](http://docs.jquery.com/Qunit) 和 [Jasmine](http://pivotal.github.com/jasmine/) 两个单元测试框架。 766 | 767 | ### 驱动 768 | 769 | > 尽管使用测试框架可以做到一定程度的自动化测试,但在各式各样的浏览器中进行测试依然是个问题。每次测试时都要开发者手动执行刷新,这种做法显然很低效。 770 | 771 | > 为了解决这个问题,有人开发了驱动。这里说的驱动其实是一个守护进程,它整合了不同的浏览器,可以自动运行 JavaScript 测试代码,测试不通过的时候会给出提示[5]。 772 | 773 | 当代码发生变动时,本地可以通过监测文件变动进行实时的测试,或者在集成时通过版本管理工具的 post-commit 的 hook 功能来运行测试。 774 | 775 | #### Watir (http://watir.com) 776 | 777 | > Watir 是一个基于 Ruby 的驱动类库,整合了 Chrome, Firefox, Safari 和 IE ,可以通过给 Watir 发送 Ruby 指令来启动浏览器,而且可以象真实用户一样完成点击链接和填写表单等行为。 778 | 779 | 由于浏览器的安装受到操作系统的限制,如果你想测试某些操作系统独有的浏览器,你的持续集成服务器就需要安装特定版本的操作系统 780 | 781 | #### Selenium (http://seleniumhq.com) 782 | 783 | > 这个库提供了一种特定领域语言(Domain Scripting Lanuage, 简称 DSL),用这种特定领域语言可以为多种编程语言编写测试代码,比如 C#, Java, Groovy, Perl, PHP, Python 和 Ruby 。它往往是以后台服务的形式运行于持续集成服务器中。 784 | 785 | > Selenium 的优势在于它支持很多编程语言,同时提供了一个 Firefox 插件 [Selenium IDE](http://seleniumhq.com/projects/ide/) ,这个插件可以记录浏览器的行为并可回放。 786 | 787 | ### 无界面的测试 788 | 789 | 在服务器端编写 JavaScript 程序,需要在脱离浏览器环境的命令行中运行测试代码。 790 | 791 | > 这么做的优势是命令行环境速度快而且易于安装,同时不涉及多浏览器及持续集成服务器环境。它的不足之处也很明显,就是测试代码无法在真实环境中运行。 792 | 793 | 所幸大多数 JavaScript 代码都是应用逻辑,不依赖浏览器的 DOM 和 Event 等内容。 794 | 795 | #### Zombie.js (http://zombie.labnotes.org/) 796 | 797 | Zombie.js 专门为 Node.js 设计,充分利用了它的高性能和异步特性,主要特点是速度快。 798 | 799 | 同时 Zombie.js 还利用了 V8 引擎的上下文特性,能够让测试用例彼此隔离,使不共用同一个全局变量/全局上下文;同时能够并行运行多个测试代码,使用一个异步测试框架,比如 [Vows.js](http://vowsjs.org/) ,减少整个测试单元的运行时间。 800 | 801 | #### Ichabod (http://github.com/maccman/ichabod) 802 | 803 | > 如果你需要一个简洁、高效的测试运行环境,强烈推荐你使用 Ichabod 。 804 | 805 | > Ichabod 的优势还在于它使用了 Webkit 解析引擎,这也是 Safari 和 Chrome 浏览器所使用的解析引擎,但它的缺点是只能运行在 OS X 中,因为它需要 MacRuby 和 OS X WebView API 的支持。 806 | 807 | > Ichabod 目前可以运行 Jasmine 和 QUnit 的测试代码,后续还会增加更多对测试类库的支持。 808 | 809 | ### 分布式测试 810 | 811 | > 跨浏览器测试的一个解决方案是利用外包的专用服务器集群,这些集群都安装有不同的浏览器,这正是 [TestSwarm](http://swarm.jquery.org/) 的做法。 812 | 813 | > 浏览器在 TestSwarm 的终端里运行,并自动执行推送给它们的测试。它们可以部署在任意机器、任意操作系统中,TestSwarm 会自动将得到的测试地址传递给一个新打开的浏览器。 814 | 815 | > 这种方法听起来很简单,但仍需要对持续集成服务器作大量的部署工作,这个过程也非常痛苦和麻烦。 816 | 817 | > 事实上可以将这个工作外包给更专业的社区和公司来做,你只需要告诉它们你想要的测试平台即可。 818 | 819 | > 可以选一些公司,比如 [Sauce Labs](http://saucelabs.com) 提供的成熟的服务,这些公司大多是将浏览器运行于云端,你只需运行一个 Selenium 测试驱动,剩下的工作都交给服务器去做。 820 | 821 | ### 调试工具 822 | 823 | #### Web Inspector 824 | 825 | Web Inspector 是 Safari 和 Chrome 自带的调试工具,Safari 需要在 "Preferences..." / "Advanced" 面板中勾选 "Show Develop menu in menu bar"。 826 | 827 | ### 控制台 828 | 829 | > 我们使用控制台来轻松地执行 JavaScript 代码并检查页面的全局变量。控制台的一个主要优势是你可以使用 `console.log()` 函数直接向他输出 log 。 830 | 831 | > 同样,使用一个代码函数也可以对 log 做命名空间的管理 832 | 833 | ```coffeescript 834 | App = 835 | trace: true 836 | log: (args...) -> 837 | return unless @trace 838 | return unless console? 839 | args.unshift "(App)" 840 | console.log.apply console, args 841 | ``` 842 | 843 | > 这里的 `App.log()` 函数给它的参数都加上了字符串 App 前缀,然后调用了 `console.log()` 844 | 845 | 同时预防了可能环境中没有定义 `console` 对象的情况。 846 | 847 | 其实 `App.trace` 还可以通过 url 的 querystring 来判断,如果 querystring 有一个 debug=1 ,那么 `App.trace` 即为 `true` 848 | 849 | #### 控制台函数 850 | 851 | * `$()` : document.getElementById() 852 | * `$$()` : document.querySelectorAll() 853 | * `$x()` : 返回匹配某个 XPath 表达式的一组元素组成的数组 854 | * `clear()` : 清空控制台的 log 855 | * `dir()` : 输出对象中的所有属性 856 | * `inspect()` : 参数可以是元素、数据库或存储区域,并会自动跳转到调试工具的对应面板 857 | * `keys()` : Object.keys.call(obj) 858 | * `values()` : Object.values.call(obj) 859 | 860 | ### 分析网络请求 861 | 862 | > 调试工具中的网络监控面板现实了本页面发起的所有 HTTP 请求,包括每个请求耗费的时间及何时完成。[6] 863 | 864 | 以下的内容基本上指的的 Web Inspector 的网络监控面板。 865 | 866 | > 你可以看到出事请求的延时是用半透明的颜色表示的。当开始接收数据时,时间轴的颜色就变得不透明。 867 | 868 | > 网络时间轴中的竖线表示了页面加载的状态。蓝色的线表示 DOMContentLoaded 事件的触发时间,红色的线表示页面的 load 事件触发的时间。[7] 869 | 870 | ### Profile 和函数运行时间 871 | 872 | > 如果你所构建的是一个大型 JavaScript 应用,你需要特别关注性能,特别是当你的应用是运行在移动终端时。 873 | 874 | > Profile 代码很简单,只要在你想要统计的代码段两端加上 `console.profile()` 和 `console.profileEnd()` 即可。 875 | 876 | > 译注21: 877 | 878 | > 这里的 Profile 指的是调试工具提供的一个功能,用来统计代码中函数执行的次数和时间,并给出报表。 879 | 880 | > 同样,你也可以使用调试器 Profile 的 record 特性,它的功能和直接嵌入 console 语句是一样的。 881 | 882 | > 你也可以使用 Profile 的快照 (snapshot) 功能生成页面当前的堆 (heap)[8] 的快照 883 | 884 | 快照能够显示当前使用了多少对象,占用了多少内存。 885 | 886 | > 这是查找内存泄露的好方法,因为你可以看到哪些多想被无意间存储在内存中,应当被回收而未被回收。 887 | 888 | > 使用控制台工具函数同样可以查看代码的执行时间,API 和 Profile 类似,只需要给要统计时间的代码前后加上 `console.time(name)` 和 `console.timeEnd(name)` 即可。 889 | 890 | ## 第十章 部署 891 | 892 | ### 性能 893 | 894 | > 提高性能,最简单也是最显著的方法是:减少 HTTP 请求的数量。 895 | 896 | > 保持最小的独立链接数可以保证用户的页面加载速度最快。 897 | 898 | > 让页面和其资源文件保持较小的体积将减少网络用时——对任何互联网上的应用而言,这才是真正的瓶颈。 899 | 900 | 合并 JavaScript 文件,合并 CSS 文件,制作 CSS Sprites ,都是为了这个目的。 901 | 902 | > 避免重定向也是减少 HTTP 请求和数量的方法。你或许认为这很少见,其实 URL 结尾缺少斜线(/)是一个非常常见的重定向场景,而这个斜线不应当被丢掉。 903 | 904 | ### 缓存 905 | 906 | > 缓存就是将最近请求的资源存储到本地,以便接下来的请求能从磁盘中使用这些资源,而不用再次去下载。**明确地告诉浏览器什么是可以被缓存的是很重要的。** 907 | 908 | > 针对静态资源,可通过添加一个表示“在很远的将来才过期”的 Expires 头,让缓存“永不”失效。 909 | 910 | > HTTP 1.1 引入了一类新的头,Cache-Control。它带给开发者更高级的缓存,同时还弥补了 Expires 的不足。Cache-Control 的控制头有很多选项,可用逗号隔开。 911 | 912 | > 查看全部的选项,请访问规范 (http://www.ietf.org/rfc/rfc2616.txt) 913 | 914 | > 给提供服务的资源增加 Last-Modified 头信息也有助于缓存。 915 | 916 | > Last-Modified 的替代方案是 ETag 。 917 | 918 | 但由于 ETag 通常是使用服务器指定的一些属性来构建的,所以两个独立服务器对同样的资源生成的 ETag 可能不一样。随着服务器集群越来越普遍,这成为一个现实问题,所以更推荐 Last-Modified 而非 ETag 。 919 | 920 | ### 源码压缩 (Minification) 921 | 922 | > 文件越小越好,因为在网络上传输的数据越少越好。 923 | 924 | > 译注1: 925 | 926 | > 对于带有中文的 JavaScript 文件来说,仅做源码压缩是不够的,还需要对文件做 Unicode 转码,以保证压缩后的文件不会因为编码问题引入 BUG 。此外,由于每个项目中的 JS 文件往往比较多,也需要一些批量压缩工具来提高效率,这里推荐一个工具 [TPacker](http://github.com/jayli/Tpacker) 。 927 | 928 | 个人比较推荐的组合是 [uglyfy.js](https://github.com/mishoo/UglifyJS) 和 [clean-css](https://github.com/GoalSmashers/clean-css) ,至于为什么么……其实最重要的原因是它们都是 JavaScript 写的。 929 | 930 | ### Gzip 压缩 931 | 932 | > 在 Web 上 Gzip 是最流行并且支持最广泛的压缩方式。它是由 GNU 项目组开发的,在 HTTP/1.1 中开始对其支持。 933 | 934 | > 显然,压缩数据可以减少网络传送时间,但这并没大范围的得以实现。Gzip 通常能减少 70% 的体积,巨大的体积缩减极大地加速了网站的加载速度。 935 | 936 | > 服务器通常已经配置了哪些文件应该被压缩。一条不错的经验就是压缩任何文本类型的响应,例如 HTML、JSON、JavaScript 和样式表。如果文件已经被压缩,例如图片和 PDF ,则不应该再用 Gzip 压缩了,因为体积不会再减小了。 937 | 938 | ### 使用 CDN 939 | 940 | > 内容分发网络(或叫 CDN )为你的站点提供静态资源内容服务,以减少它们的加载时间。用户和 Web 服务器之间的距离对加载时间有直接的影响。CDN 将你的内容部署在跨越多个地理位置的服务器上,故当用户发起一个请求时,可从就近的服务器得到响应资源(理想情况是在同一个国家中)。Yahoo! 已经发现 CDN 可以减少终端用户 20% 或更多的响应时间 (http://developer.yahoo.com/performance/rules.html#cdn) 。 941 | 942 | > Google 为很多流行的开源 JavaScript 库提供了一个免费的 CDN 和加载架构,包括 jQuery 和 jQueryUI 。使用 Google 的 CDN 的其中一个优点就是很多其他网站都在使用它,这会让你要引用的 JavaScript 文件有更多的几率停留在用户浏览器的缓存之中。 943 | 944 | 这些库都能在 http://code.google.com/apis/libraries/devguide.html 中找到。 945 | 946 | 在指定 script 标签的 src 时,可以不指定请求的协议,只使用 // ,这看起来会是这样: 947 | 948 | ```html 949 | 950 | ``` 951 | 952 | > 这个鲜为人知的技巧使得获取脚本文件时,可以使用和宿主页面一样的协议。换言之,如果页面通过安全的 HTTPS 加载,该脚本文件同样会使用 HTTPS ,从而避免所有的安全警告,没有协议的相对 URL 是合法的,与 RTF 规范兼容。更重要的是,它得到的全盘的支持。见鬼了!相对 URL 甚至在 IE 3.0 中也能工作。 953 | 954 | ### 审查工具 955 | 956 | > 如果你想查看你的网站性能详情,它们能给你眼前一亮的惊喜,比如 [YSlow](http://developer.yahoo.com/yslow) 。 957 | 958 | > 该扩展通过一系列的检查来运行,包括缓存、源码压缩、Gzip 压缩和 CDN 等。它将给出网站的评分等级,这依赖于检查过程中的耗费。然后,给出如何提高分数的建议。 959 | 960 | > Google Chrome 和 Safari 也有审查工具,但是它们是内置在浏览器中的。 961 | 962 | > 在 Chrome 中只要简单地找到 Web Inspector 的 Audits 面板并点击 Run 就行了。 963 | 964 | ### 外部资源 965 | 966 | > Yahoo! 和 Google 都用了大量的研究、调查来分析 Web 性能。 967 | 968 | > 两个公司在改善性能上都有优秀的资源,可以在 Google (http://code.google.com/speed/page-speed/docs/payload.html) 和 Yahoo! (地址没有在中文版里写出来,我正在找英文版) 969 | 970 | [1]: Seajs 是一个模块加载器,在客户端实现了同步 `require()` ,使得客户端也能够遵循 CommonJS 标准规范进行编码开发。Seajs 从 1.2.0 开始能够加载未被包装过的 JavaScript 模块。 971 | 972 | [2]: 译注10:作者这里将 LABjs 归类为“依赖管理器”有些勉强,LABjs 给自己的定位是“脚本加载器”,主要是为了解决加载脚本时的性能问题。 973 | 974 | [3]: 译注5:雅虎最早提出了浏览器分级支持 (GBS) ,即根据功能需求的权重来将浏览器支持划分为多个级别,并且非常详细、系统的定义了浏览器测试基准和操作系统支持标准,请参照 http://yuilibrary.com/yui/docs/tutorials/gbc/ 975 | 976 | [4]: 译注6:撰写本书时的 Firefox 版本还是 3.6 ,从 Firefox 4 之后版本升级非常快。更重要的不同是 Firefox 3.6 遵从 ECMAScript 3 ,而 4 及以后的的版本则遵循 ECMAScript 5 ,因此这里更推荐使用 Firefox 4+ 977 | 978 | [5]: 译注9:前端开发自动化测试是目前比较热门的话题,国内也有很多前端团队开始了这方面的研究和尝试,可以参照来自淘宝的“前端测试实践”(http://goo.gl/koPLJ)以及来自新浪的“多浏览器集成的JavaScript单元测试”(http://goo.gl/2CnHe) 979 | 980 | [6]: 译注18:网络面板只能监控页面发出的 HTTP 请求,如果页面中包含 Flash ,则 Flash 发起的请求是无法在网络面板中看到的,只能用更深层的抓包工具,比如 WireShark。 981 | 982 | [7]: 译注20:还有一个非常重要的时间线“页面首次渲染时间”,通常是绿色的线,这条线在 Firebug 和 Web Inspector 中看不到,可以使用 [httpWatch](http://www.httpwatch.com/) 来查看。 983 | 984 | [8]: 译注22:和其他编程语言一样,JavaScript 运行时的内存也划分为堆 (heap) 和栈 (stack) 。栈是用来存储局部变量的原始值和“引用”(这里可以将引用理解为一个内存地址)的,而堆则是存放“引用值”(引用指向的内容)的,这里的快照查看的是堆的内容,而不是栈的内容,主要是因为和堆相比栈的内存占用很小。更多内容请参照 http://goo.gl/lbECT 985 | 986 | [9]: 译注1:滑坡理论 (slippery slope) 也称为滑坡谬误,是一种逻辑错误,即不合理的使用连串的因果关系,将“可能性”转换为“必然性”,以达到某种意欲只结论。 987 | 988 | [10]: 译注3:原文这里是 parallel universe 即平行宇宙,意思是说把由 JavaScript 创建的内容再拼成静态的 html 内容。 989 | 990 | [11]: 译注1:HTTP 是 Web 的基石,HTTP 都是短连接,客户端向服务器发送请求服务器需要做出响应,请求加响应就构成一次完整的 HTTP 连接的过程,响应完成后连接就“断掉”了,所以对于服务器来说,信息推送到客户端都是“被动”的,理论上任何信息从服务器发送到客户端都必须由客户端先发起请求,这就是文中所说的请求/响应模型。 991 | --------------------------------------------------------------------------------