├── .gitignore ├── HTML5高级程序设计.md ├── JavaScript_DOM_编程艺术.md ├── JavaScript高级程序设计.md ├── Metaprograming_Ruby-Ruby元编程.md ├── Pragmatic_Guide_to_JavaScript_note.md ├── README.md ├── The_Pragmatic_Programmer-程序员修炼之道.md ├── seajs源码正则分析.md ├── single_page_apps_in_depth └── index.md ├── 基于MVC的JavaScript Web富应用开发.md ├── 深入浅出CoffeeScript.md └── 版本控制之道——使用Git.md /.gitignore: -------------------------------------------------------------------------------- 1 | \#*# 2 | *.swp 3 | .#* 4 | -------------------------------------------------------------------------------- /HTML5高级程序设计.md: -------------------------------------------------------------------------------- 1 | # HTML5 高级程序设计 2 | ## 第一章 HTML5 概述 3 | ### 1.4 新的认识 4 | HTML5 是基于各种各样的理念(在 WHATWG 规范中有描述)进行设计的,这些设计理念体现了对可能性和可行性的新认识。 5 | 6 | * 兼容性 7 | * 实用性 8 | * 互通性 9 | * 通用访问性 10 | 11 | #### 兼容性和存在即合理 12 | #### 效率和用户优先 13 | HTML5引入了一种新的*基于来源*的安全模型。这个安全模型可以让我们做一些以前做不到的事情,不需要借助任何所谓聪明、有创意却不安全的hack就能跨域进行安全对话。 14 | 15 | PS. [利用HTML5的window.postMessage实现跨域通信](http://www.36ria.com/3890) 与 [HTML5:使用postMessage实现Ajax跨域请求](http://yangzebo.com/blog/?p=208) 就提到了这种方式。 16 | 17 | #### 化繁为简 18 | 19 | * 以浏览器原生能力替代复杂的 JavaScript 代码 20 | * 新的简化的 DOCTYPE 21 | * 新的简化的字符集声明 22 | * 简单而强大的 HTML5 API 23 | 24 | 基于多种改进过的、强大的错误处理方案,HTML5 具备了良好的错误处理机制。非常有现实意义的一点是,HTML5 提倡重大错误的平缓恢复,... 25 | 26 | 比如,如果页面中有错误的话,在以前可能会影响整个页面显示,而HTML5不会出现这种情况,取而代之的是以标准方式显示“*broken标记*”,这要归功于HTML5中精确定义的*错误恢复机制* 27 | 28 | PS. 很可惜,我找不到有关 broken标记 的相关资料。 29 | 30 | #### 通用访问 31 | 这个原则可以分成三个概念: 32 | 33 | * 可访问性:出于对残障用户的考虑,HTML5 与 WAI(Web Accessibility Initiative,Web 可访问性倡议) 和 ARIA (Accessible Rich Internet Applications,可访问富 Internet 应用)做到了紧密结合,WAI-ARIA 中以屏幕阅读器为基础的元素已经被添加到 HTML 中。 34 | * 媒体中立:如果可能的话,HTML5 的功能再所有不同的设备和平台上应该都能正常运行。 35 | * 支持所有语种:例如,新的 元素支持在东亚页面排版中会用到的 Ruby 注释。 36 | 37 | ### 1.5 无插件范式 38 | 文中提到了的,属于 HTML5 的,我感兴趣的部分: 39 | 40 | * Canvas (2D和3D) 41 | * Channel 消息传送 42 | * Cross-document 消息传送 43 | * Geolocation 44 | * MathML 45 | * Microdata 46 | * Server-Sent Events 47 | * SVG 48 | * Web Origin Concept 49 | * XML HttpRequest Level 2 50 | * Web SQL database 51 | * Web Workers 52 | 53 | www.caniuse.com 网站按照浏览器版本提供了详尽的 HTML5 功能支持情况。 54 | 55 | 同时也可以使用 Modernizer —— 一个检测浏览器对 HTML5 和 CSS3 的支持程度的 JavaScript 库。 56 | 57 | ### 1.6 HTML5 的新功能 58 | #### HTML5 标记元素的 7 个分类 59 | HTML5 引入了很多新的标记元素,根据内容的不同,这些元素分成了 7 大块: 60 | 61 | * 内嵌:向文档添加其他类型的内容,例如 audio, video, canvas, iframe 等 62 | * 流:在文档和应用的 Body 中使用的元素,例如 form、hl 和 small 等 63 | * 标题:段落标题,例如 h1、h2 和 hgroup 等 64 | * 交互:与用户交互的内容,例如音频和视频的控件、 button 和 textarea 等 65 | * 元数据:通常出现在页面的 head 中,设置页面其他部分的表现和行为,例如 script、style 和 title 等 66 | * 短语:文本和文本标记元素,例如 mark、kbd、sub 和 sup 等 67 | * 片段:用于定义页面片段元素,例如 article、aside 和 title 等 68 | 69 | 上述所有类型的元素都可以通过 CSS 来设定样式。 70 | 71 | #### 新增的标签的语义 72 | 我们说过,HTML5 的宗旨之一就是存在即合理。Google 分析了上百万的页面,从中发现了 DIV 标签和通用 ID 名称重复量很大。例如很多开发人员喜欢使用 footer 和 header 作为标签的 ID ,所以 HTML5 引入了一组新的片段类元素,在目前驻留的浏览器中已经可以使用了。 73 | 74 | * header:标记头部区域的内容(用于整个页面或页面中的一块区域) 75 | * footer:标记脚部区域的内容(用于整个页面活页面中的一块区域) 76 | * section:Web 页面中的一块区域 77 | * atricle:独立的文章内容 78 | * aside:相关内容或者引文 79 | * nav:导航类辅助内容 80 | 81 | #### Selectors API 82 | 83 | * querySelector():根据指定的选择规则,返回在页面中找到的第一个匹配元素 84 | * querySelectorAll():根据指定规则返回页面中所有相匹配的元素 85 | 86 | Selectors API 与现在 CSS 中使用的选择规则一样,同时可以为 Selectors API 函数同时指定多个选择规则,例如: 87 | 88 | ``` JavaScript 89 | // 选择文档中类名为 highClass 或 lowClass 的第一个元素 90 | var x = document.querySelector(".highClass", ".lowClass"); 91 | ``` 92 | 93 | #### DOM Level 3 94 | IE9 将会支持 DOM Level 2 和 DOM Level 3的特性,包括非常重要的 addEventListener() 和 dispatchEvent() 95 | 96 | ## 第二章 Canvas API 97 | PS. 这一部分我不打算看,我打算直接看 Canvas 的书籍 98 | 99 | ## 第三章 音频和视频 100 | ### 3.1 HTML5 Audio 和 Video 概述 101 | #### Audio 和 Video 的限制 102 | 103 | * 流式音频和视频。因为目前 HTML5 视频规范中还没有比特率切换标准,所以对视频的支持只限于加载全部媒体文件,但是将来一旦流媒体格式被 HTML5 支持,则肯定会有相关的设计规范。 104 | * HTML5 的媒体受到 HTML 跨源 (cross-origin) 资源共享的限制。关于跨资源共享的更多信息,请参考第五章。 105 | * 全屏视频无法通过脚本控制。从安全性角度来看,让脚本元素控制全屏操作是不合适的,不过,如果要让用户在全屏方式下播放视频,浏览器可以提供其他的控制手段。 106 | * 对 Audio 元素和 Video 元素的访问尚未完全加入规范中。基于流行的字幕格式 SRT 的字幕支持规范 (WebSRT) 仍在编写中。 107 | 108 | ### 3.2 使用 HTML5 Audio 和 Video API 109 | #### 浏览器支持性检测 110 | 111 | ``` javascript 112 | var hasVideo = !!(document.createElement('video').canPlayType); 113 | ``` 114 | 115 | #### source 标签和 source 标签的 type 属性 116 | 最后,介绍最重要的属性:src。最简单的情况下,src 属性直接指向媒体文件就可以了。但是,万一浏览器不支持相关容器或者编解码器呢(比如 Ogg 和 Vorbis)?这就需要用到备用声明了。备用声明中可以包含多种来源,浏览器可以从这么多来源中进行选择: 117 | 118 | ``` html 119 | 124 | ``` 125 | 126 | 容器和编解码器都可以在 type 属性声明,如果 type 属性中指定的类型与源文件不匹配,浏览器可能就会拒绝播放。。所有的支持列表见 RFC4218,RFC4218 是由 IETF(Internet Engineering Task Force, Internet 工程任务分组)维护的一套文档。以下列出常见组合: 127 | 128 | * 在 Ogg 容器中的 Theora 视频和 Vorbis 音频: `type='video/ogg; codecs="theora, voribis"‘` 129 | * 在 Ogg 容器中的 Vorbis 音频:`type='video/ogg; codecs="voribis"'` 130 | * 在 MP4 容器中的 sinple bashline H.264 视频和 lowcomplexity AAC 音频: `type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"'` 131 | * 在 MP4 容器中的 MPEG-4 简单视频和简单 AAC 音频:`type='video/mp4; codecs="mp4v.20.8, mp4a.40.2"'` 132 | 133 | #### video 元素相对 audio 的额外属性: 134 | 135 | * poster: 在视频加载完成之前,代表视频内容的图片的 URL 地址,可读可修改 136 | * width、height: 读取或者设置显示尺寸。如果设置的宽度与视频本身大小不匹配,可能导致居中显示,上下或者左右可能出现黑色条状区域 137 | * videoWidth、videoHeight: 返回视频的固有或者自适应的宽度和高度,只读 138 | * video 还有一个 audio 元素不支持的关键特性:可被 Canvas 的函数调用 139 | 140 | **P63 起演示了一个创建视频市局查看器的过程** 141 | 142 | ## 第四章 Geolocation API 143 | ### 4.1 位置信息 144 | 位置信息主要由一对纬度和经度坐标组成,经纬度坐标可以用两种方式表示: 145 | 146 | * 十进制格式(例如,39.172 22) 147 | * DMS(Degree Minute Secound,角度)格式(例如, 39° 10′ 20″) 148 | 149 | 除了经纬度坐标,HTML5 Geolocation 还提供位置坐标的*准确度*。除此之外,它还可能会提供一些其他的元数据,具体情况取决于浏览器所在的硬件设备,这些元数据包括海拔、海拔准确度、行驶方向和速度等。如果这些与元数据不存在则返回 null 。 150 | 151 | #### 位置信息的数据源 152 | Geolocation API 不指定设备使用哪种底层技术来定位应用程序的用户。它只是用于检索位置信息的 API,而且通过该 API 检索到的数据只具有某种程度的准确性。它不能保证设备返回的实际位置是精确的。 153 | 154 | 设备可以使用下列数据源: 155 | 156 | * IP 地址 157 | * 三维坐标 158 | - GPS(Global Positioning System,全球定位系统) 159 | - 从 RFID、Wi-Fi 和蓝牙到 Wi-Fi 的 MAC 地址 160 | - GSM 或 CDMA 手机的 ID 161 | * 用户自定义数据 162 | 163 | #### 各个数据源的优缺点 164 | ##### IP 地址地理定位数据 165 | 优点 166 | 167 | * 任何地方都可以用 168 | * 在服务端处理 169 | 170 | 缺点 171 | 172 | * 不精确(一般精确到市级) 173 | * 运算代价大 174 | 175 | ##### GPS 地理定位数据 176 | 优点 177 | 178 | * 很精确 179 | 180 | 缺点 181 | 182 | * 定位时间长,用户耗电大 183 | * 室内效果不好 184 | * 需要额外的硬件设备 185 | 186 | ##### Wi-Fi 地理定位数据 187 | 优点 188 | 189 | * 精确 190 | * 可在室内使用 191 | * 可以简单、快捷定位 192 | 193 | 缺点 194 | 195 | * 在乡村这些无线接入点较少的地区效果不好 196 | 197 | ##### 手机地理定位数据 198 | 优点 199 | 200 | * 相当精确 201 | * 可在室内使用 202 | * 可以简单、快捷定位 203 | 204 | 缺点 205 | 206 | * 需要能够访问手机或者其 modem 的设备 207 | * 在基站较少的偏远地区效果不好 208 | 209 | ##### 用户自定义地理定位数据 210 | 优点 211 | 212 | * 用户可以获得比程序定位服务更准确的位置数据 213 | * 允许地理位置服务的结果作为备用位置信息 214 | * 用户自行输入可能比自动检测更快 215 | 216 | 缺点 217 | 218 | * 可能很不准确,特别是当用户位置变更之后 219 | 220 | ### 4.2 HTML5 Geolocation 的浏览器支持情况 221 | 在 HTML5 的所有功能中,HTML5 Geolocation 是第一批被全部接受和实现的功能之一。 222 | 223 | * Chrome:在带有 Gears 的第二版中被支持 224 | * Firefox:3.5 及以上版本支持 225 | * IE:通过 Gears 插件支持 226 | * Opera:计划在版本 10 中支持 227 | * Safari:在版本 4 中支持以实现在 iPhone 上可用 228 | 229 | ### 4.3 隐私 230 | HTML5 Geolocation 规范提供了一套保护用户隐私的机制。除非得到用户明确许可,否则不可取得位置信息。 231 | 232 | 一般步骤如下: 233 | 234 | * 用户从浏览器打开位置感应应用程序 235 | * 应用程序 Web 页面加载,然后通过 Geolocation 函数调用请求位置坐标。浏览器拦截着一条请求,然后请求用户授权。 236 | * 浏览器从其宿主设备中检索坐标信息。例如:IP 地址、Wi-Fi 或 GPS 坐标。这是浏览器内部功能。 237 | * 浏览器将坐标发送给受信任的外部定位服务,它返回一个详细的位置信息,并将该位置信息发回给 HTML5 Geolocation 应用程序。 238 | 239 | #### 隐私保护机制 240 | 如果仅仅是添加 HTML5 Geolocation 代码,而不被任何方法调用,则不会触发隐私保护机制。只要所添加的 HTML5 Geolocation 代码被执行,浏览器就会提示用户应用程序要共享他们的位置。 241 | 242 | #### 如何处理位置信息 243 | 因为位置数据属于敏感信息,所以接收到之后,必须小心地处理、存储和重传。如果用户没有授权存储这些数据,那么应用程序应该在完成相应任务完成后立即删除它。 244 | 245 | 如果要重传位置数据,建议先对其进行加密。在手机地理定位数据时,应用程序应该着重提示用户以下内容: 246 | 247 | * 会收集位置数据 248 | * 为什么收集位置数据 249 | * 位置数据将保存多久 250 | * 曾一保证数据的安全 251 | * 位置数据怎样共享(如果同意共享) 252 | * 用户怎样检查和更新他们的位置数据 253 | 254 | ### 4.4 使用 HTML5 Geolocation API 255 | #### 浏览器支持性检测 256 | 如果存在地理定位对象,`navigator.geolocation` 调用将返回该对象,否则将触发返回一个空值。 257 | 258 | #### 位置请求 API 259 | 目前,有两种类型的位置要求: 260 | 261 | * 单次定位请求:getCurrentPosition (successCallback, errorCallback, positionOptions) 262 | * 重复性的位置更新请求:watchPosition (successCallback, errorCallback, positionOptions) 263 | 264 | ##### successCallback 265 | successCallback 接受一个参数:位置对象。这个对象包含坐标(coords 属性)和一个获取位置数据时的时间戳。 266 | 267 | 坐标总是有多个属性,但是浏览器和用户的硬件设备会决定这些属性值是否有意义。latitude(纬度)、longitude(经度)、accuracy(准确度)是它的前三个属性。 268 | 269 | 坐标还有一些其他的属性,不能保证浏览器都为其提供支持,但如果不支持就会返回 null 。 270 | 271 | * altitude: 用户位置的海拔高度,以 m 为单位 272 | * altitudeAccuracy:海拔高度的准确度,也是以 m 为单位 273 | * heading:行进方向,相对于正北而言 274 | * speed:地面速度,以 "m/s" 为单位 275 | 276 | ##### errorCallback 277 | errorCallback 接受错误对象作为参数,错误编号设置在错误对象的 code 属性中(原文中全部为大写,我这里为了看起来舒服,全都记作小写): 278 | 279 | * unknown_error:错误编号0,不包括在其他错误编号中的错误。需要通过 message 属性查找错误的更多详细信息。 280 | * permission_denied:错误编号1,用户拒绝浏览器获得其位置信息 281 | * position_unavailable:错误编号2,尝试获取用户位置,但失败了 282 | * timeout:错误编号3,设置了可选的 timeout 值。尝试确定用户位置的过程超时 283 | 284 | ##### 另外的设置参数 285 | 如果要同时处理正常情况和错误情况,就应该把注意力集中到三个可选参数 enableHighAccuracy, timeout, maximumAge 上,将这三个可选参数传递给 HTML5 Geolocation 服务以调整数据收集方式。**这三个参数可以使用 JSON 对象传递**,这样更便于添加到 HTML5 Geolocation 请求调用中。 286 | 287 | * enableHighAccuracy:启用这个参数则通知浏览器启用 HTML5 Geolocation 服务的高精确度模式。默认值为 false 。由于地理定位数据的种种限制(见4.1),启用这个参数可能没有任何差别,可能会导致极其花费更多的时间和资源来确定位置,所以应谨慎使用。 288 | * timeout:可选值,单位为 ms,告诉浏览器计算所允许的最长时间,如果还没有完成计算,就调用错误处理函数,默认值为 Infinity 289 | * maximumAge:表示浏览器重新计算位置的时间间隔。单位为 ms,默认值为零,这意味着浏览器每次请求时都必须立即重新计算位置 290 | 291 | **请注意**API不允许我们为浏览器指定多长时间重新计算一次位置信息。这是完全由浏览器的实现所决定的。我们能做的就是告诉浏览器 maximumAge 的值是什么。 292 | 293 | ##### clearWatch 294 | 如果应用程序不再需要接受 watchPosition 的持续位置更新,则只需调用 clearWatch() 函数,如下所示(watchId 是 watchPosition 函数的返回值): 295 | 296 | ```JavaScript 297 | navigator.geolocation.clearWatch(watchId); 298 | ``` 299 | 300 | ### 4.5 使用 HTML5 Geolocation 构建实时应用 301 | #### 计算地球上两点距离的 Haversine 公式 302 | 距离计算使用众所周知的 Haversine 公式来实现(公式不好输出,请自行 Google;关于这个公式的原理,请查阅中学数学教材)。 303 | 304 | 这个公式能够更具经纬度来计算地球上两点间的距离。以下就是该公式的 JavaScript 实现: 305 | 306 | ```JavaScript 307 | var toRadians = function(degree) {return degree * Math.PI / 180;}; 308 | var distance = function(latitude1, longitude1, latitude2, longitude2) { 309 | // R 是地球的半径,以 km 为单位 310 | var R = 6371; 311 | 312 | var deltaLatitude = toRadians(latitude2 - latitude1); 313 | var deltaLongitude = toRadians(longitude2 - longitude1); 314 | latitude1 = toRadians(latitude1); 315 | latitude2 = toRadians(latitude2); 316 | 317 | var a = Math.sin(deltaLatitude / 2) * Math.sin(deltaLatitude / 2) + 318 | Math.cos(latitude1) * Math.cos(latitude2) * 319 | Math.sin(deltaLongitude / 2) * Math.sin(deltaLongitude / 2); 320 | var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 321 | var d = R * c; 322 | return d; 323 | }; 324 | ``` 325 | 326 | > 作为开发人员,无法选择浏览器计算位置所使用的方法,但可以保持数据的准确度,所以推荐使用 accuracy 属性 —— Brian 327 | 328 | ## 第五章 Communication API 329 | 主要是要讲述 postMessage 和 XMLHttpRequest Level2 330 | 331 | ### 5.1 跨文档消息通信 332 | 出于安全方面的考虑,运行在同一浏览器中的框架、标签页、窗口间的通信一直受到严格限制。然而,现实中存在一些合理的让不同站点的内容能在浏览器内进行交互的需求。Mashup 就是最典型的一个例子。 333 | 334 | 为了满足上述需求,浏览器厂商和标准制定机构一致同意引入一种新功能:跨文档消息通信。跨文档消息通信可以确保 iframe、标签页、窗口间安全的进行跨源通信。 335 | 336 | #### postMessage API 337 | 利用 postMessage 发送消息非常简单: 338 | 339 | ```JavaScript 340 | chatFrame.contentWindow.postMessage("hello world", "http://www.example.com/"); 341 | ``` 342 | 343 | 接收消息: 344 | 345 | ```JavaScript 346 | window.addEventListener("message", function(e) { 347 | switch(e.origin) { 348 | case "frend.example.com": 349 | processMessage(e.data); 350 | break; 351 | default: 352 | // ... 353 | } 354 | },true); 355 | ``` 356 | 357 | data 是发送方传递的实际消息,origin 属性是发送来源,用于忽略来自不可信源的消息。 358 | 359 | PS.可以看看我在第一章 1.4 节提到的两篇文章。 360 | 361 | 鉴于 postMessage API 的一致性和易用性,以及其提供的 JavaScript 环境中的异步通信机制,在同源文档间通信时也推荐使用 362 | 363 | 在 JavsScript 环境的通信中始终应使用 postMessage API,例如使用 Web Workers 通信时。 364 | 365 | #### 5.1.1 理解源安全 366 | HTML5 通过引入源(origin)的概念对域安全进行了阐明与改进。源是网络上用来建立信任关系的地址的子集。 367 | 368 | 源由规则(scheme)、主机(host)、端口(port)组成: 369 | 370 | > http://www.example.com:9000/path/ 371 | > -+-- -------+------- --+- --+-- 372 | > | | | | 373 | > scheme host port 不考虑路径 374 | 375 | HTML5 定义了源的序列化。源在 API 和协议中以字符串的形式出现。这对于使用 XHR 进行跨源 HTTP 请求是非常重要的,对于 WebSocket 也一样。 376 | 377 | postMessage 的安全规则确保了消息不会传递到非预期的的源页面中。发送消息时由发送方指定接收方的源。如果发送方用来调用 postMessage 的窗口不具有特定的源(例如用户跳转到了其他站点),浏览器就不会传送消息。 378 | 379 | 接收信息的时候,发送方的源也作为消息的一部分,为避免伪造,消息源由浏览器提供。接收方可以决定处理与忽略哪些消息。 380 | 381 | 处理跨源通信的信息时,一定要验证每个消息的源,处理消息中的数据也应该谨慎,最好永远不要对来自第三方的字符串求值(用 window.JSON 或者 json.org 提供的解析器处理 JSON 数据而不是用 eval),下面是两个关于内容注入的示例: 382 | 383 | ```JavaScript 384 | // 危险,e.data 里的内容可能会被当成标记 385 | elem.innerHTML = e.data 386 | // 相对安全 387 | elem.textContent = e.data 388 | ``` 389 | 390 | #### 浏览器兼容检测 391 | 392 | ```JavaScript 393 | if (typeof window.postMessage === undefined) { 394 | // 浏览器不支持 postMessage 395 | } 396 | ``` 397 | 398 | ### 5.2 XMLHttpRequest Level 2 399 | 更多关于 XMLHttpRequest 编程的知识,建议阅读 John Resing 撰写的 《Pro JavaScript Techniques》 (Apress, 2006)(中文版《精通 JavaScript》,ISBN: 9787115175403)。 400 | 401 | 作为 XHR 的改进版,XHR Level 2 在功能上有了很大的改进。这一章主要讲两个方面:跨源 XHR 与进度事件 (progress events)。 402 | 403 | #### CORS(Cross Origin Resource Sharing) 404 | 跨源 HTTP 请求包括一个 Origin 头部,它为服务器提供 HTTP 的“源”信息。头部由浏览器保护,不能被应用程序代码修改。从本质上讲,它与跨文档消息通信中消息事件的 origin 属性作用相同。Origin 不同于早先的 Referer[src] 头部,因为后者 Referer 是一个包括了路径的完整 URL。由于路径可能饱含敏感信息,为了保护用户隐私,浏览器并不一定会发送 Referer ,而浏览器在任何必要的时候都会发送 Origin 头部。 405 | 406 | 跨域交换的 HTTP 头部示例: 407 | 408 | 请求的头部示例: 409 | ``` 410 | POST /main HTTP/1.1 411 | Host: www.example.net 412 | User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.3) Gecko/20090910 Ubuntu/9.04 413 | (jaunty) Shiretoko/3.5.3 414 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 415 | Accept-Language: en-us,en;q=0.5 416 | Accept-Encoding: gzip,deflate 417 | Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 418 | Keep-Alive: 300 419 | Connection: keep-alive 420 | Referer: http://www.example.com/ 421 | Origin: http://www.example.com 422 | Pragma: no-cache 423 | Cache-Control: no-cache 424 | Content-Length: 0 425 | ``` 426 | 427 | 响应的头部示例: 428 | ``` 429 | HTTP/1.1 201 Created 430 | Transfer-Encoding: chunked 431 | Server: Kaazing Gateway 432 | Date: Mon, 02 Nov 2009 06:55:08 GMT 433 | Content-Type: text/plain 434 | Access-Control-Allow-Origin: http://www.example.com 435 | Access-Control-Allow-Credentials: true 436 | ``` 437 | 438 | #### Progress event 439 | 新版 XHR 最重要的改进之一是增加了对进度的响应。在 XHR 之前的版本中,仅有 readystatechage 一个事件能够被用来响应进度。更糟糕的是,浏览器对该事件的实现并不兼容,如在 IE 中永远不会触发 readystate 3(表示所有响应头部都收到,响应体开始接收但未完成),此外,readystate 的更改事件缺乏与上传进程通信的方法,在这样的情况下实现进度条是一件麻烦的事情,而且开牵扯到服务器端的编程开发。 440 | 441 | XHR Level 2 用了一个有意义的名字 Progress 进度来命名进度事件,以下为新进度的名称: 442 | 443 | * loadstart 444 | * progress 445 | * abort 446 | * error 447 | * load 448 | * loaded 449 | 450 | 可以通过这些事件实现对事件的监听。 451 | 452 | 出于向后兼容的目的,新版本中 readyState 属性和 readystatechange 事件可以保留。 453 | 454 | #### XHR Level 2 的浏览器支持情况检测 455 | 456 | ```JavaScript 457 | var xhr = new XMLHttpRequest(); 458 | if (typeof xhr.withCredentials === undefined) { 459 | // 浏览器不支持... 460 | } 461 | ``` 462 | 463 | ### 5.3 进阶功能 464 | 早期版本的 postMessage 仅支持字符串。后来的版本支持 JavaScript 对象、canvas imageData 和文件等其他数据类型。*由于不同浏览器对规范支持程度的差异,对不同的对象类型的支持情况也不同。* 465 | 466 | Framebusting 技术可以用来保证某些内容不被加载到 iframe 中: 467 | 468 | ```JavaScrpt 469 | if (window !== window.top) { 470 | window.top.location = location; 471 | } 472 | ``` 473 | 474 | 不过,你可能会希望借助 iframe 导入一些确定的合作网站的页面来充实自身的内容,以下是一种使用 postMessage 实现 iframe 与其父页面间的握手通信: 475 | 476 | ```JavaScript 477 | var framebustTimer, timeout = 3000; 478 | if (window !== window.top) { 479 | framebustTimer = setTimeout( 480 | function() { 481 | window.top.location = location; 482 | }, timeout 483 | ); 484 | } 485 | window.addEventListener("message", function(e) { 486 | switch(e.origin) { 487 | case trustedFramer: 488 | clearTimeout(framebustTimer); 489 | break; 490 | } 491 | }, true); 492 | ``` 493 | 494 | ## 第六章 WebSockets API 495 | ### 6.1 HTML5 WebSockets 概述 496 | #### WebSocket 接口 497 | 498 | [Constructor(in DOMSting url,in optional DOMString protocol)] 499 | interface WebSocket{ 500 | readonly attribute DOMString URL; 501 | 502 | //就绪状态 503 | const unsigned short CONNECTING=0; 504 | const unsigned short OPEN=1; 505 | const unsigned short CLOSED=2; 506 | readonly attribute unsigned short readyState; 507 | readonly attribute unsigned long bufferedAmount; 508 | 509 | //网络 510 | attribute Function onopen; 511 | attribute Function onmessage; 512 | attribute Function onclose; 513 | boolean send(in DOMString data); 514 | void close(); 515 | }; 516 | WebSocket implements EventTarget; 517 | 518 | WebSocket接口的使用很简单。要连接远程主机,只需要新建一个 WebSocket 实例,提供希望连接的对端URL。注意, ws:// 和 wss:// 前缀分别表示 WebSocket 连接和安全的 WebSocket 连接。 519 | 520 | 基于同一底层的 TCP/IP 连接,在客户端和服务器之间的初始握手阶段,将 HTTP 协议升级至 WebSocket 协议, WebSocket 连接就建立完成了。连接一旦建立,WebSocket 数据帧就可以以全双工模式在客户端和服务器之间进行双向传送。连接本身是通过 WebSocket 接口定义的 message 事件和 send 函数来运作的。在代码中,采用异步事件侦听器来控制连接生命周期的每一个阶段。 521 | 522 | myWebSocket.onopen = function(evt){ 523 | alert("Connection open…"); 524 | }; 525 | myWebSocket.onmessage = function(evt){ 526 | alert("Received Message:" + evt.data); 527 | myWebSocket.send("somethings..."); 528 | }; 529 | myWebSocket.onclose = function(evt){ 530 | alert("Connection closed"); 531 | }; 532 | 533 | ### 6.3 编写简单的 Echo WebSocket 服务器 534 | **自 P120 页起至 P126 ,书中详细介绍了如何利用 Python 编写一个简单的 Echo 服务器** 535 | 536 | ### 6.4 使用 HTML5 WebSocket API 537 | #### 浏览器支持情况检测 538 | 539 | ```JavaScript 540 | if (window.WebSocket == null) { 541 | // 浏览器不支持... 542 | } 543 | ``` 544 | ## 第七章 Forms API 545 | ### 7.1 HTML5 Forms 概述 546 | #### XForms 547 | XForms 是一个以 XML 为核心、功能强大却略显复杂的标准,它用于规范客户端表单的行为,而专门的 W3C 工作组研究这些行为已经近十年。XForms 充分的利用了 XML Schema ,制订了针对验证和格式化的精确准则。不过,很遗憾,在没有安装插件的情况下,主流浏览器均不支持 XForms。 548 | 549 | #### HTML5 Forms 的核心设计理念 550 | 规范的核心是功能性动作和语义,而非外观和显示效果。 551 | 552 | HTML5 表单规范更加注重对现有的简单 HTML 表单功能的改进,力求使之饱含更多控件类型。 553 | 554 | 但它并没有规定浏览器应该以何种方式将这些元素呈现给用户。这种做法的好处是:浏览器会竞相改善用户交互方式、分离了样式和语义、在面对专用用户输入设备时,能够灵活调整交互方式。 555 | 556 | ### 7.2 使用 HTML5 Forms API 557 | #### HTML5 新增的 Forms 元素 558 | 在 W3C 网站上可以找到 HTML5 中所有新增和修改的元素,具体地址为: http://dev.w3.org/html5/markup/ 。 559 | 560 | 当浏览器不支持下列 type 属性的值时,标签会退回到文本输入框 561 | 562 | * tel:电话号码 563 | * email:电子邮件地址 564 | * url:网页的 URL 565 | * search:用于搜索引擎,比如在站点顶部显示搜索框 566 | * range:特定值范围内的数值选择器,典型显示方式是滑动条 567 | * number:只能饱含数值的文本框 568 | * color:颜色选择器,基于调色盘或者取色板进行选择 569 | * datetime:显示完整的日期和时间,包括时区 570 | * datetime-local:显示日期和时间,不含时区 571 | * time:不含时区的时间选择器和指示器 572 | * date:日期选择器 573 | * week:某年中的周选择器 574 | * month:某年中的月选择器 575 | 576 | #### list 属性和 detaillist 元素 577 | 通过组合使用 list 和 detaillist 标签,开发人员能够为某个输入型控件构造一张选值列表。使用方法如下: 578 | 579 | * 创建一个带 id 的 detaillist 元素 580 | * 添加若干 option 作为 detaillist 的子元素 581 | * 将 input 元素的 list 属性值设为 detaillist.id 582 | 583 | #### valueAsNumber 函数 584 | valueAsNumber 函数的作用是完成控件值类型在文本和数值间的相互转换。它既是 setter 又是 getter ,作为 getter 时,将文本转换为数值,如果转换失败,则返回 NaN 。 585 | 586 | #### 表单验证 587 | 在支持 HTML5 表单验证的浏览器中,可以通过表单控件来访问 ValidityState 对象:`var valCheck = document.myForm.myInput.validity;`。对象包含了对所有八种验证状态的引用,如果八个约束条件全部通过,那么 `valCheck.valid` 的值就为 true ,否则就是 false 。ValidityState 对象是一个实时更新的对象。获得某表单元素的 ValidityState 对象后,当表单元素内容发生变化时,可以通过它来获得更新后的检测结果。 588 | 589 | 具体八种约束条件可以看这里:http://www.itivy.com/html5/archive/2011/12/24/html5-form-validate.html 590 | 591 | #### 验证反馈 592 | 只要发生表单验证(不管是在提交表单还是直接调用 checkValidity 函数),所有未通过验证的表单都会接收到一个 invalid 事件。可以通过 event 对象的 srcElement 属性得到表单元素,进而得到 validityState 对象。 593 | 594 | #### 关闭验证 595 | 表单本身可以通过代码方式设置 noValidate 属性,所有的验证逻辑都会被放弃,只会单纯的提交表单。 596 | 597 | 更好的办法是在如表单提交按钮控件上设置 formNoValidate 属性,比如:`` 。 598 | 599 | ### 7.3 构建 HTML5 Forms 应用 600 | #### 进阶功能 setCustomValidity 601 | 通过 setCustomValidity 函数可以设置 ValidityState 对象的 customError 属性。 602 | 603 | ## 第八章 Web Workers API 604 | Web Workers 不能直接访问 Web 页面的 DOM API (应该是出于安全性的考虑)。虽然 Web Workers 不会冻结窗口,但仍然会消耗 CPU 周期,导致系统反应速度变慢。 605 | 606 | Web Worker 适用于执行不影响 Web 页面交互性的后台的数据处理和监听由后台服务器广播的新闻信息。 607 | 608 | ### 8.2 使用 HTML5 Web Workers API 609 | Web Workers 的使用方法非常简单,`new Worker("XXX.js")` 创建 Web Workers 对象,传入希望执行的 JavaScript 文件即可。 610 | 611 | Web Workers 初始化时会接受一个 JavaScript 文件的**同源** URL 地址,其中包含了供 Worker 执行的代码。*这段代码会设置事件监听器,并与生成 Worker 的容器进行通信。* 612 | 613 | Web Workers 之间、Web Workers 与页面的数据交换需要通过 postMessage 函数传递。 614 | 615 | #### 浏览器支持性检查 616 | 617 | ``` JavaScript 618 | if (typeof Worker !== "undefined") { 619 | // code ... 620 | } 621 | ``` 622 | 623 | #### importScript 624 | Web Workers 由于没有访问 document 的权限,所以 Worker 中必须使用另外的方法导入其他 JavaScript 文件。 `importScript("helper.js");` 625 | 626 | 导入的 JavaScript 只会在某个已有的 Workers 中加载和执行。 627 | 628 | importScript 可以通过多个参数导入多个脚本,它们会按顺序执行。 629 | 630 | ### 8.3 编写主页 631 | #### 终止 Worker 632 | 可以调用 Worker 的 `terminate` 函数实现终止 Web Workers,被终止的 Web Workers 不再响应任何信息或者执行任何其他的计算,也无法重新启动。但可以使用同样的 URL 创建一个新的 Worker。 633 | 634 | #### Worker 脚本中能够访问到的 API 635 | Worker 的 API 能够在 Web Workers 脚本中被访问到(可以用来创建子 Worker) 636 | 637 | Worker 能够访问到属于 window 对象的定时器 API ,如 setTimeout 函数 638 | 639 | 640 | ## 第九章 Web Storage API 641 | Web Storage 的使用和相关信息并不像上面提到的若干 API 那么少见,而且浏览器对它的支持也是比较全面的,从 IE8 开始,Chrome 3.0,Firefox 3.0,Opera 10.5,Safari 4.0 已经全线支持。不少网站已经开始投入使用。 642 | 643 | ### 9.3 使用 HTML5 Web Storage API 644 | #### 设置和获取数据 645 | Web Storage 主要分为 sessionStorage 和 localStorage 两个种存储模式,都可以通过 `setItem(key, value)` 、 `getItem(key)` 和 `removeItem(key)` 进行操作。`clear()` 函数会删除存储列表中的所有数据。 646 | 647 | 存储在 sessionStorage 中的数据,当浏览器或标签页关闭后就不复存在。同时,数据也只能在构建它们的窗口或者标签页内可见。 648 | 649 | 存储在 localStorage 中的数据,能够被同源的每个窗口或者标签页共享,同时,关闭窗口或者标签页也不会令数据消失。 650 | 651 | #### 更多磁盘空间配额 652 | HTML5 规范中建议浏览器允许每组同源页面使用 5MB 的空间,达到空间配额时浏览器应提示用户分配更多空间,同时提供查看已用空间的方法。 653 | 654 | 事件的实现方式多少有些出入。有些浏览器在不告知用户的情况下允许页面使用更多空间,有些则向用户提示已经扩展了空间,而另外一些则只是抛出 QUOTA _EXCEEDED _ERR 错误。 655 | 656 | #### 事件 657 | 添加事件监听器即可接收同源窗口的 Storage 事件: 658 | 659 | ```JavaScript 660 | window.addEventListener("storage", displayStorageEvent, true); 661 | ``` 662 | 663 | Storage 事件的接口形式如下所示 664 | 665 | ``` 666 | interface StorageEvent:Event{ 667 | readonly attribute DOMString key; //更新或删除的键 668 | readonly attribute any oldValue; //更向前键对应的数据,如果是新添加的数据,则为 null 669 | readonly attribute any newValue; //更新后的数据 670 | readonly attribute DOMString url; //指向 Storage 事件发生的源 671 | readonly attribute Storage storageArea; //一个引用,指向发生改变的 localStorage 或 sessionStorage 672 | } 673 | ``` 674 | 675 | ### 9.5 浏览器数据库存储展望 676 | > 虽然 Web SQL Database 已经在 Safari、Chrome 和 Opera 中实现,但是 Firefox 中并没有实现它,而且它在 WHATWG 维基中也被列为 “停滞” 状态。HTML5 规范中定义了一个执行 SQL 命令的 API ,接收字符串形式的输入,遵从 SQLite 对 SQL 语句的解析规范。由于标准认定直接执行 SQL 不可取, Web SQL Database 已经被较新的规范——索引数据库 (Indexed Database,原为 WebSimpleDB) 所取代。索引数据库更简便,而且也不依赖于特定的 SQL 数据库版本。目前浏览器正在逐步实现对索引数据库的支持。 —— Frank 677 | 678 | ## 第十章 构建离线 Web 应用 679 | ### 10.2 使用 HTML5 离线 Web 应用 API 680 | #### 检查浏览器支持情况 681 | 682 | ``` JavaScript 683 | if (window.applicationCache) { 684 | // code ... 685 | } 686 | ``` 687 | 688 | #### html 元素的 manifest 属性 689 | 想要为 HTML5 应用程序添加离线支持,那么需要在 html 元素中加入 manifest 属性,属性的值为缓存清单文件: 690 | 691 | ``` HTML 692 | 693 | 694 | ... 695 | 696 | ``` 697 | 698 | 缓存清单内容示例: 699 | 700 | ``` 701 | CACHE MANIFEST 702 | example.html 703 | example.js 704 | example.css 705 | example.gif 706 | ``` 707 | 708 | #### 判断是否在线 709 | 是否处于在线状态可以通过检测 window.navigator 对象的属性进行判断。 710 | 711 | `navigator.onLine` 是一个标明浏览器是否处于在线状态的布尔属性。它甚至可以在 IE 中使用。(TODO:IE 几?) 712 | 713 | 当然, onLine 的值为 true 并不能保证 Web 应用程序在用户机器上一定能够访问到相应服务器(是在说中国吗?)。 714 | 715 | 当其值为 false 时,不管浏览器是否真的有联网。应用程序都不会尝试进行网络连接。 716 | 717 | 当在线状态发生变化时,触发 "online" 或者 "offline" 事件。 718 | 719 | #### manifest 文件 720 | manifest 文件的 MIME 类型为 text/cache-manifest 。 721 | 722 | *需要注意, manifest 文件的内容类型必须配置为 text/cache-manifest 发送到浏览器。如果文件类型不正确,即使浏览器支持应用缓存也会返回缓存错误。* 723 | 724 | manifest 文件示例: 725 | 726 | ``` 727 | #注释以#开头 728 | CACHE MANIFEST 729 | #要缓存的文件 730 | about.html 731 | html5.css 732 | index.html 733 | happy-trails-rc.gif 734 | lake-tahoe.jpg 735 | 736 | #不缓存登录页面 737 | NETWORK 738 | signup.html 739 | 740 | #提供获取不到缓存资源时的备选资源路径 741 | FALLBACK 742 | signup.html offline.html 743 | /app/ajax/ default.html 744 | ``` 745 | 746 | #### applicationCache API 747 | applicationCache API 是一个操作应用缓存的接口。新的 window.applicationCache 对象可触发一系列与缓存状态相关的事件。 748 | 749 | 该对象有一个数值型属性 window.applicationCache.status ,代表了缓存的状态。共有 6 种: 750 | 751 | * 0: UNCACHED(未缓存) // 没有指定缓存清单的状况 752 | * 1: IDLE(空闲) // 带有缓存清单的应用程序的典型状态 753 | * 2: CHECKING(检查中) 754 | * 3: DOWNLOADING(下载中) 755 | * 4: UPDATEREADY(更新就绪) 756 | * 5: OBSOLETE(过期) // 缓存曾经有效,但现在 manifests 文件丢失 757 | 758 | 对于上述状态,API 包含了与之对应的事件(和回调特性)。 759 | 760 | * onchecking CHECKING 761 | * ondownloading DOWNLOADING 762 | * onupdateready UPDATEREADY 763 | * onobsolete OBSOLETE 764 | * oncached IDLE 765 | 766 | 此外,没有可用更新或者发生错误时,还有一些表示更新状态的事件: 767 | 768 | * onerror 769 | * onnoupdate 770 | * onprogress 771 | 772 | window.applicationCache 有一个 `update()` 方法。调用 `update()` 方法会请求浏览器更新缓存。包括检查新版本的 manifest 文件并下载必要的新资源。如果没有缓存或者缓存已经过期,则会抛出错误。 773 | 774 | ## 第11章 HTML5未来展望 775 | 只能这么说,不论 HTML5 未来是否前途光明,它始终是前端攻城湿的饭碗。 776 | 777 | ### 触摸屏设备事件 778 | Apple 在推出 iPhone 的同时,也将一系列的特殊事件引入到了其浏览器中。这些事件用来处理多点触摸输入和设备旋转。尽管还没有标准化,但这些事件已经被其他移动设备厂商选用。 779 | 780 | #### 方向事件 781 | 在方向事件处理中,可以引用 `window.orientation` 属性。属性可选值如下所示,它们以页面首次加载时设备的方向为基准: 782 | 783 | * 0 页面当前方向与首次加载时的原始方向一样 784 | * -90 与原始方向比,设备顺时针旋转了 90 度(向右) 785 | * 180 与原始方向比,设备旋转了 180 度(垂直翻转) 786 | * 90 与原始方向比,设备逆时针旋转了 90 度(向左) 787 | 788 | #### 手势事件 789 | 手势事件可以理解为通过多点触摸引发的缩放或者旋转。当用户有两个或多个手指同时在屏幕上挤压 (pinch) 或扭转 (twist) 时,就会触发手势事件。 790 | 791 | 扭转表示旋转,挤压 (pinch in) 和伸展 (pinch out) 分别表示缩小和放大。以下为事件处理程序: 792 | 793 | * ongesturestart 用户将多个手指放在触摸屏上,开始滑动 794 | * ongesturechange 用户正在使用手指动作进行缩放或者旋转操作 795 | * ongestureend 用户移开手指,缩放或旋转操作已经完成 796 | 797 | 在用户做手势的过程中,事件处理程序或灵活检测事件的缩放或旋转属性,并对显示效果进行相应更新。以下为手势处理函数示例: 798 | 799 | ```JavaScript 800 | function gestureChange(event) { 801 | // 获取用户手势生成的缩放量 802 | // 1.0 代表初始大小,小于 1.0 表示缩小,大于 1.0 表示放大 803 | // 基于尺寸大小按比例放大缩小 804 | var scale = event.scale; 805 | 806 | // 获取用户手势生成的旋转量 807 | // 旋转值介于 0 度到 360 度之间 808 | // 正值代表顺时针旋转,负值代表逆时针方向旋转 809 | var rotation = event.rotation; 810 | 811 | // 基于旋转操作更新页面显示 812 | } 813 | // 为文档节点添加手势变化监听程序 814 | node.addEventListener('gesturechange', gestureChange, false); 815 | ``` 816 | 817 | #### 触摸事件 818 | 如果需要在低层次上处理设备事件,可以通过触摸事件获取所需信息。 819 | 820 | * ontouchstart 已经在触摸设备表面放置了一个手指。当多个手指放在设备上时,会发生多点触摸事件 821 | * ontouchmove 在拖动操作中,一个或多个手指发生了移动 822 | * ontouchend 一个或多个手指离开设备表面 823 | * ontouchcancel 意外停止了触摸操作 824 | 825 | 触摸事件需要考虑多点数据同时出现的情况,因此,用于处理触摸事件的 API 会相对复杂一点。 826 | 827 | ```JavaScript 828 | function touchMove(event) { 829 | // touches 变量是一个列表,包含了当前每个手指的触摸点信息 830 | var touches = event.touches; 831 | 832 | // changedTouches 列表包含了当前触摸状态发生变化的手指的触摸点信息,如添加、移开或重放手指 833 | var changedTouches = event.changedTouches; 834 | 835 | // 监听器注册在哪个节点上, targetTouches 就包含哪个节点上发生的触摸操作的触摸点信息 836 | var targetTouches = event.targetTouches; 837 | 838 | // 在取得预定触摸点以后,可以取得能够从其他事件对象中正常获取的大部分信息 839 | var firstTouch = touches[0], 840 | firstTouchX = firstTouch.pageX, 841 | firstTouchY = firstTouch.pageY; 842 | } 843 | // 注册一个触摸事件监听器 844 | node.addEventListener("touchmove", touchMove, false); 845 | ``` 846 | 847 | ### 其他的书中提到的未来: 848 | 849 | #### WebGL 850 | ##### 3D HTML 851 | ##### 3D 着色器 852 | 853 | #### 设备 854 | ##### 音频数据 API 855 | ##### 视频元素改进 856 | ##### P2P 网络 857 | 858 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /深入浅出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 | -------------------------------------------------------------------------------- /版本控制之道——使用Git.md: -------------------------------------------------------------------------------- 1 | # 版本控制之道——使用 Git 2 | ## 第三章 创建第一个项目 3 | ### 3.5 处理发布 4 | #### 给指定分支添加 tag 5 | 6 | ``` bash 7 | $ git tag 8 | ``` 9 | 10 | 这样会在分支 branchName 的末梢打上 tagName 的 tag 11 | 12 | #### 变基命令 13 | See Also [9.3 分支变基](#93-分支变基) 14 | 15 | #### 给 tag 打补丁 16 | See Also [8.1 使用标签标记里程碑](#81-使用标签标记里程碑) 17 | 18 | #### 为代码发布创建归档文件 19 | See Also [9.2 导出版本库](#92-导出版本库) 20 | 21 | ## 添加与提交:Git 基础 22 | ### 添加文件到暂存区 23 | 命令 `git add -p` 能够在不进入交互模式的情况下直接进入补丁模式。 24 | 25 | 补丁模式是 Git 中非常有用的模式,在补丁模式中 Git 会显示这些文件的当前内容与版本库的差异,然后可以据此决定是否添加这些修改到暂存区。 26 | 27 | ### 将文件从暂存区移除 28 | 命令 `git reset HEAD` 可以将所有文件从暂存区移除(也可以是 `git reset` ,因为 `HEAD` 是默认参数),`git reset -- /path/to/file` 将单个文件从暂存区移除。 29 | 30 | ### 提交修改 31 | 命令 `git commit -a` 会把已经纳入 Git 版本控制的文件提交,而不会添加尚未被跟踪的文件。 32 | 33 | ### 查看修改内容 34 | `git diff` 默认会比较工作目录树和暂存区的区别,因此如果已经把文件加入了暂存区,那么 `git diff` 命令是无法看到任何 diff 信息的。这时可以使用 `git diff --cached` 命令来比较暂存区和版本库之间的区别。 35 | 36 | ### 管理文件 37 | #### 文件的重命名与移动 38 | 命令 `git mv ` 打包了 `mv ` 、 `git add ` 和 `git rm ` 三个操作。 39 | 40 | ## 第五章 理解和使用分支 41 | 42 | ### 5.3 合并分支间的修改 43 | #### 直接合并 (straight merge) 44 | 45 | ```bash 46 | $ git merge 47 | ``` 48 | 49 | #### 压合合并 (squashed commits) 50 | 压合指的是 Git 将一条分支上的所有历史提交压合成一个提交,提交到另一条分支上。_要小心使用压合提交_,因为大多数情况下,每个提交都应该作为一个单独的条目存在于历史记录中。 51 | 52 | 如果某条分支上的所有提交都密切相关,应随后作为一个正题记录(在父分支上)时,适合作压合提交。 53 | 54 | ```bash 55 | $ git merge --squash 56 | ``` 57 | 58 | 这条命令会将 branchName 分支上的全部提交压合成当前分支上的一个提交,但这个时候,这些改变还没有提交,需要再次输入 `git commit` 命令进行提交。 59 | 60 | #### 拣选合并 (cherry-picking) 61 | 62 | ```bash 63 | $ git cherry-pick 64 | ``` 65 | 66 | 中间跳过了几个 commit ,可能会要求处理冲突。可以带上 -n 参数,用于拣选多个提交,进行合并操作,等全部拣选完毕后输入 `git commit` 命令提交。 67 | 68 | ### 5.4 冲突处理 69 | 一旦发生冲突后,就会在发生冲突的文件里发现 `<<<<<<< :`,`=======` 和 `>>>>>>> :` 这样的标识。 70 | 71 | `<<<<<<<` 后面跟随的是当前分支 branchName1 中的代码。而 `>>>>>>>` 之前则是另一条分支 branchName2 上的代码。 72 | 73 | 可以使用 `git mergetool` 工具来解决复杂的冲突,不同的系统不同的环境会有不同的工具,Git 会尽可能的找到每一个合并工具。 74 | 75 | 也可以在设置里配置 merge.tool 的值 76 | 77 | 所有合并工具得到的结果应该都差不多,都是显示各冲突区域,供用户选取定夺。 78 | 79 | 解决了所有冲突后,Git 自动暂存修改,等待提交。 80 | 81 | ### 5.6 分支重命名 82 | 命令 `git branch -m ` 可以用来重命名分支。 83 | 84 | ## 第六章 查询 Git 历史记录 85 | ### 6.2 指定查找范围 86 | 在 `git log` 后面可以跟上以下参数用于指定查找范围: 87 | 88 | * `-<数字>` 显示最近若干条提交 89 | * `--since` 最近的时间内的提交 90 | * `--before` 多少时间前的最后一个提交 91 | * `..` 检索从 commit1 到 commit2 之间的提交(不包括 commit1) 92 | 93 | `-since` 和 `--before` 参数接受大多数英文格式的日期。Git 工具本身能够识别诸如 "24 hours"、"1 minute"、"2008-10.01" 这样格式的日期,哪怕日期中间既有连字符又有句点。 94 | 95 | 第三种检索方式可以省略 `` ,这时 commit2 就会被 HEAD 替代,也可以用 tag 名替代。 96 | 97 | 另一种指定版本的常见方法是指出它与另一版本的关系,此时有两种操作符可以使用: 98 | 99 | * ^ 相当于回溯一个版本。`2222222^` 相当于 2222222 的前一个版本; `2222222^^` 代表 2222222 的前第二个版本(也可以写作 `2222222^2`,`^` 其实是 `^1` 的别名) 100 | * ~N 回溯 N 个版本。 `2222222^` 相当于 `222222~1` 101 | 102 | 可以混合使用两种操作符。 103 | 104 | ### 6.3 查看版本之间的差异 105 | 在 `git log` 命令中加入 `-p` 参数,能够显示版本间的内容差异。 106 | 107 | 在 `git diff` 中指定版本范围和 `git log` 一样,唯一差别是 `git diff` 输出的是最老版本和最新版本之间的差异。 108 | 109 | 在 `git diff` 中使用标签作为参数,是一种获取发布版本之间代码量统计的好方法,通过加入一个 `--stat` 参数,可以计算出修改和删除的代码行数。如果只传入一个参数,Git 会默认把工作目录树作为参数。 110 | 111 | ### 6.4 查明该向谁问责 112 | 命令 `git blame` 用于查看特定代码块的历史信息。通过传入 `-L` 参数可以缩小显示的范围,范围用 `,` 表示。范围的值可以是数字,可以是 +N,-N 的形式,也可以是正则表达式。同时,`blame` 命令也可以使用 [指定查找范围](#62-指定查找范围) 中的操作符: 113 | 114 | ```bash 115 | $ git blame -L "/<\/body>/",-2 2222222^ -- hello.html 116 | ``` 117 | 118 | 命令中的 `--` 符号是在通知 Git 查询指定文件。 119 | 120 | ### 6.5 跟踪内容 121 | 命令 `git blame` 加入参数 `-M` 可以检测同一个文件内移动或复制的代码行,用同一个提交名称标识。加入参数 `-C -C` 可以检测文件之间的复制: 122 | 123 | ```bash 124 | $ git blame -C -C cp.txt 125 | ``` 126 | 127 | Git 会输出初始的提交名称和初始的文件名。给 `git log` 传入参数 `-C -C` 也能显示复制信息,如果传入 `-p` 参数,`git log` 还会显示代码的具体变动。 128 | 129 | ### 6.6 撤销修改 130 | #### 增补提交 131 | 在 `git commit` 命令中传入 `-C ` 参数,Git 会复用那个提交的 commit,如果传入的是小写的 c ,就会打开预设的编辑器,以便在已有的提交留言基础上编辑修改。 132 | 133 | 增补提交只能针对最后一个提交,如果想更正几个提交之前的某个错误,就需要用到 `git revert` 命令。 134 | 135 | #### 反转提交 136 | 命令 `git revert ` 通过在版本库中创建一个“反向的”新提交来抵消原来提交的改动。在命令中加入 `-n` 参数可以制止 Git 立即提交反转结果,来进行多次反转。 137 | 138 | 通常,命令会启动默认编辑器并把默认信息加入其中,可以添加参数 `--no-edit` 直接使用默认的 commit 信息。 139 | 140 | #### 复位 141 | 命令 `git reset ` 的默认参数是 HEAD ,可以使用 [指定查找范围](#62-指定查找范围) 中的操作符。如果传入 `--soft` 参数,Git 会暂存所有因果复位带来的差异,但不提交。用户可以修改这些内容再提交。 142 | 143 | 如果传入 `--hard` 参数,Git 会从版本库和工作目录同时删除提交,不可恢复。 144 | 145 | ### 6.7 重新改写历史记录 146 | 命令 `git rebase -i` 能够以人机交互的方式改写历史记录。具体内容可以看 《Pro Git》 的 第六章第四节 [《重写历史》](http://github.danmarner.com/section/ch6-4/) 和 《Git 魔法》的第五章 [《关于历史》](http://www-cs-students.stanford.edu/~blynn/gitmagic/intl/zh_cn/ch05.html) 。 147 | 148 | ## 第七章 与远程版本库合作 149 | ### 7.5 添加新的远程版本库 150 | 在本地版本库中,远程版本库的别名默认是 origin 。它是克隆远程版本库时自动生成的。 151 | 152 | 命令 `git remote add <别名> <版本库全称>` 用于为远程版本库添加别名。 153 | 154 | 命令 `git remote rm` 用于删除别名。 155 | 156 | ## 第八章 管理本地版本库 157 | ### 8.1 使用标签标记里程碑 158 | 159 | ```bash 160 | $ git checkout 161 | ``` 162 | 163 | 这时会检出直到 的历史到 `(no branch)` 上。 164 | 165 | ```bash 166 | $ git [branch|checkout -b] 167 | ``` 168 | 169 | 这时就会检出到 tagName 为止的代码到 branchName 分支 170 | 171 | ## 第九章 高阶功能 172 | ### 9.1 压缩版本库 173 | Git 版本库里存储了*所有的东西*,由此带来的问题是偶尔会留下一些没有用的数据。比如 `--amend` 时 Git 也会记住原来的版本;或者用 `git branch -D` 删除一个试验分支时,Git 依旧会保留该分支上的内容。 174 | 175 | 命令 `git gc` 整理版本库以优化 Git 内部存储历史记录,一个月清理一次,或者大约100次提交清理一次即可。它不改变历史记录,只改变历史记录的存储方式。 176 | 177 | 在命令中带上 `--aggressive` 参数可使版本库得到进一步的优化。这会增加时间,但值得尝试。 178 | 179 | Git 在增量存储单元 (delta) 中存储修改。一般情况下, `git gc` 命令运行时会压缩这些增量存储单元,但是不会重新计算它们。如果使用 `--aggressive` 参数,则 Git 会重新计算它们。 180 | 181 | ### 9.2 导出版本库 182 | 183 | ```bash 184 | $ git archive --format tar \ 185 | --prefix=mysite-1.0/ 1.0 \ 186 | | gzip > mysite-10.tar.gz 187 | ``` 188 | 189 | `--format` 指明要产生 tar 格式的输出;`--prefix` 指明包中所有东西都放到 mysite-1.0/ 目录下;`1.0` 指明需要归档的标签名称。 190 | 191 | 也可以用 192 | 193 | ```bash 194 | $ git archive --format zip \ 195 | --prefix=mysite-1.0/ 1.0 \ 196 | > mysite-10.zip 197 | ``` 198 | 199 | 来产生一个 zip 文件。 200 | 201 | ### 9.3 分支变基 202 | #### 同步分支间的提交记录 203 | 要同步分支之间的历史,除了使用 Git 提供的合并分支功能,还可以使用变基操作。 204 | 205 | ```bash 206 | $ git rebase 207 | ``` 208 | 209 | 变基的意思是“改变分支的基底”,把一条分支上的修改在另一条分支的末梢重现。你可以把这种方式看作是基于一个新的基点,重演分支上发生过的改动。 210 | 211 | 除了同步历史以外, `git rebase` 还拥有修改历史的能力,具体可以参考 [6.7 重新改写历史记录](#67-重新改写历史记录)。 212 | 213 | #### 剥离分支 214 | 命令 `git rebase` 命令还提供了一个 `--onto` 参数,用于将分支3与分支2的提交记录差值变基到分支1上。 215 | 216 | 例如,有三条分支,主分支 master ,从 master 拉出来的 contacts 和从 contacts 拉出来的 search 分支。 217 | 218 | 当完成 search 分支上的代码时,发现不需要任何在 contects 分支上完成的改动,搜索功能即可运行。 219 | 220 | 这时就可以输入以下命令,将 search 分支变基到 master 。 221 | 222 | ```bash 223 | $ git rebase --onto master contacts search 224 | ``` 225 | 226 | 这个命令是将 search 分支从 contacts 分支剥离,移动到主分支上,如果要合并 search 分支上的内容到 master 上,不需要 contacts 上的任何东西,可以使用此方法。当然,search 分支要完全独立于 contacts 分支,尽量避免变基到 master 时出现合并冲突。 227 | 228 | 还可以使用 [6.2 指定查找范围](#62-指定查找范围) 中提到的 *提交范围参数* ,来做一些其他有趣的事情,比如抹消倒数第二个提交。 229 | 230 | ```bash 231 | $ git rebase --onto HEAD^^ HEAD^ HEAD 232 | ``` 233 | 234 | ### 9.4 重现隐藏的历史 235 | 重现是指记录分支末梢变化的情况,并予以复现。第五章[理解和使用分支](第五章-理解和使用分支)中介绍过,分支本质上只是指向最新提交的指针。重现功能可以记录和跟踪所有这些指针的变化。通过使用它,你能找到想要的提交,并据此恢复分支。 236 | 237 | 命令 `git reflog` 的输出结果是按时间倒序排列的,类似 `git log` 。它的职能只是列出重现记录,而想要恢复这个记录的步骤可以参考 [8.1 使用标签标记里程碑](#81-使用标签标记里程碑)。 238 | 239 | 注意,在 [9.1 压缩版本库](#91-压缩版本库) 中提到的 `git gc` 会删除比较旧的重现记录,阀值通常是30天,可以通过修改配置 gc.reflogExpireUnreachable 的值来改变有效期。 240 | 241 | 同时,除非修改 gc.reflogExpire 的值,重现记录通常会在 90 天后过期。 242 | 243 | ### 9.5 二分查找 244 | 命令 `git bisect` 基于一个已知的坏提交和一个已知的好提交,逐步排查版本库中的历史记录。它带领你在版本库中的历史上标出哪些是好提交哪些是坏提交,直到最后找出那个引进 Bug 的提交。 245 | 246 | 首先运行 `git bisect start <某个提交或标签>` 来开始二分查找,然后调用 `git bisect bad|good <某个提交或标签>` 进行标记。如果没有输入提交或标签,则默认为 HEAD 。 247 | 248 | 当找到 Bug 后,需要回到 HEAD 进行修补,这时候使用命令 `git bisect reset` 即可。 249 | 250 | 如果希望直观的理解代码的演进历史,可以使用 `git bisect visualize` 命令来可视化记录。 251 | 252 | 如果喜欢文本输出,可以使用 `git bisect log` 存储成为一个文件;删除该文件中刚才错误的标识操作及其后的所有记录;然后将该文件作为命令 `git bisect replay <某一文件>` 的参数,让 Git 重新执行,直到刚才错误标识之前一步。 253 | 254 | 命令 `git bisect` 还可以适时自动运行自动测试套件。为了使用这一功能,需要构造一个可以在命令行运行的脚本,且该脚本在测试通过是返回 0 ,测试失败时返回一个正数(这个正数通常是 1),如果要跳过一条提交并让 Git 二分查找自动移向下一条提交,则应返回 125 。 255 | 256 | 命令 `git bisect run` 可以用来自动运行这个脚本。例如: 257 | 258 | ```bash 259 | $ git bisect start HEAD 1.0 260 | $ git bisect run /work/run-test 261 | ``` 262 | 263 | 推荐将测试文件放在仓库之外,以确保 Git 不会改变 `git bisect` 正在运行的脚本文件。 264 | 265 | ## 附录A Git 命令快速参考 266 | ### 1 安装和初始化 267 | #### 全局的配置 268 | 269 | ```bash 270 | $ git config --global 271 | ``` 272 | 273 | #### 局部的配置 274 | 只作用于特定版本库的配置 275 | 276 | ```bash 277 | $ git config 278 | ``` 279 | 280 | ### 2 日常操作 281 | #### 暂存已纳入 Git 版本控制之下的文件的修改 282 | 283 | ```bash 284 | $ git add -u [ []] 285 | ``` 286 | 287 | #### 清除工作目录树的修改 288 | 289 | ```bash 290 | $ git checkout HEAD [] 291 | ``` 292 | 293 | #### 取消已暂存但尚未提交的修改的暂存标识 294 | 295 | ```bash 296 | $ git reset HEAD [] 297 | ``` 298 | 299 | #### 使用上次的提交注释 300 | 301 | ```bash 302 | $ git commit -c 303 | ``` 304 | 305 | 如果不想修改,直接使用的话,就将 `-c` 参数变为 `-C` 参数。 306 | 307 | ### 3 分支 308 | 你可以从版本库的任何一个版本开始创建新分支。起始点可以用 分支名称、提交名称或者标签名称。 309 | 310 | ```bash 311 | $ git branch 312 | ``` 313 | 314 | 加入 `-f` 参数可以创建同名新分支,覆盖已有分支。 315 | 316 | 加入 `-m` 用于移动或重命名分支, `-M` 用于重命名时覆盖已有分支。 317 | 318 | ## 附注(也就是书中没有的) 319 | ### hook 320 | 有关 hook 的内容,可以看 《Pro Git》 的 [7.3 自定义 Git - Git挂钩](http://git-scm.com/book/zh/%E8%87%AA%E5%AE%9A%E4%B9%89-Git-Git%E6%8C%82%E9%92%A9)。 321 | 322 | ### diff 二进制文件 323 | See Also [7.2 自定义 Git - Git属性#二进制文件](http://git-scm.com/book/zh/%E8%87%AA%E5%AE%9A%E4%B9%89-Git-Git%E5%B1%9E%E6%80%A7#二进制文件) 324 | 325 | ### 在文件签出/提交前根据关键字自动插入文本 326 | See Also [7.2 自定义 Git - Git属性#关键字扩展](http://git-scm.com/book/zh/%E8%87%AA%E5%AE%9A%E4%B9%89-Git-Git%E5%B1%9E%E6%80%A7#关键字扩展) 327 | --------------------------------------------------------------------------------