领略前端技术 阅读奇舞周刊

第17~20期（2013-08-23）

奇舞周刊（17~20期）

第17期

1. 两个viewport的故事（第一部分）
桌面浏览器、移动开发必备常识。

2. HTML5的Datalist标签，你需要知道什么
HTML5 的 datalist 标签为 input 标签指定了一个预定义的列表选项，实现类似自动完成的效果。

3. 如何在 Internet Explorer 11中开启 WebGL 暨微软和 WebGL 的恩怨情仇录
在此版本的Internet Explorer 11中开启WebGL支持的方法。

4. HTML5触屏版多线程渲染模板技术分享
纯Js前端如何利用HTML5多线程新特性，提高模板的性能呢？一起来看看吧。

5. 用CSS3新增的rem单位设定字体大小
简单说明rem于em的区别。

6. 用脚本画画？Photoshop脚本介绍
自动化对每个设计师都很有帮助，可以节约宝贵的时间去完成一些重复性的工作，并且可以快速、简单的解决某些问题。

7. 面向“接口”编程和面向“实现”编程
思维武装脑袋。

8. 并发的错觉
简单戏谑的语法来解释了并发和并行的区别，很容易懂。

第18期

1. JavaScript instanceof 运算符深入剖析
instanceof真有那么简单吗~来恶补下基础知识吧。

2. 漫谈js自定义事件、DOM/伪DOM自定义事件
介绍的挺详细的，所以有点长，但是写的很有趣，所以能看完。

3. CSS3 Media Queries让你的网页适合各种设备
有代码有理论 介绍Media Queries的好文章。

4. 两个viewport的故事（第二部分）
移动开发必备知识。

5. 禁用状态二三事
本文探讨如何合理使用表单的禁用状态，让用户的操作更加流畅。

6. LiveStyle：实时同步Sublime Text和Chrome调试工具中的CSS
LiveStyle是ZenCoding开发者的又一力作，它能够自动将Sublime中编辑的CSS文件实时同步到Chrome中显示；而是用Chrome调试工具对CSS所做的修改也会实时同步到Sublime中。

7. Bootstrap 3.0 最佳新功能一览
近日，Bootstrap开发者发布了Bootstrap 3.0 RC1，我们可以从中先领略到Bootstrap 3.x带来的全新特性。

第19期

1. [译] JavaScript 开发者经常忽略或误用的七个基础知识点
看看你踩过这些坑么有。

2. CSS创建三角形（小三角）的几种方法
分析6种创建小三角的方法以及利弊。

3. [译] 如何做到一秒渲染一个移动页面
移动端性能优化，我们的目标是不论在任何设备或网络条件下，都要维持用户在网页中的沉浸状态，提供更好的体验。

4. 为什么移动Web应用程序很慢（译）
非常棒的一篇文章，强烈推荐，有点长，但是值得细读。

5. 【译】创建高性能移动 web 站点
针对的是手机，关注两个点：电池容量小，屏幕小。目标不仅仅是提高网站性能，而且也要增加客户收入的。英文原文：http://mobile.smashingmagazine.com/2013/08/12/creating-high-performance-mobile-websites/

6. [小Tip]用好浏览器的控制台 | JerryQu的小站
如何在console里植入广告?~

7. 把握5元素！让页面文字阅读更舒适
是习惯性的使用宋体字或者微软雅黑？是直接设为12px或者14px的字号？你有想过为什么要使用这种字体、字号吗？你有考虑过文字排布与阅读效率的关系吗？你的页面背景会影响到用户的浏览舒适度吗？戳我告诉你答案~

8. 毫秒必争，前端网页性能最佳实践
这篇文章介绍了提高网页性能的最佳实践，以及相应的问题解决方案

9. 老码农教你学英语
对于咱们这些高端大气、时刻需要和国际接轨的码农，英语可是大大滴重要。

10. 使用渐进式JPEG 来提升用户体验
终于明白~那种慢慢清晰的图片咋来的了。

11. 获得屏幕上的像素
文章很长，涉及的知识也比较多，不过读完还是很有帮助的。

第20期

1. Backbone的技巧和模式
用backbone的没有不看的道理。

2. 为什么务必在构建函数里定义实例属性？
关于应不应该把属性的默认值放在prototype里。

3. 非等宽图片列表的布局
对非等宽的图片列表自适应布局的分析。

4. JavaScript 中的陷阱
由于 JavaScript “弱语言”的性质，使得其在使用过程中异常的宽松灵活，但也极为容易“掉进陷阱”，这些陷进往往隐藏的很深，所以要多加小心。

5. 用 Sass 实现酷炫动态文字效果
Sass 可以为 CSS3 如虎添翼，实现很多酷炫和意想不到的效果。

6. CSS 布局的未来
本文为大家从示例到纯粹理论阶段中介绍有关 CSS 布局的工具，也许在这些阶段中你不一定用得到他们，但仍然还很有必要去了解，通过这样可以透过迷雾，看清未来。英文原文地址

7. 教你如何用 CSS 制作圆形导航
圆形的导航你见过吗？用 CSS3 就可以实现。

8. 让你的专题动起来
日常工作中会遇到很多的专题，在没有flash的支持下，如何让你的页面更生动呢？CSS3不是新事物，CSS3动画也逐渐受到大家的关注，这次的案例就是CSS3制作。

9. 字符编码笔记：ASCII，Unicode和UTF-8

10. 互联网协议入门（一）

11. 使用JavaScript创建模块化的双人对战象棋程序

12. Chrome（Extensions）开发攻略
chrome扩展的种种，原文有个错误，extension应为扩展，而非插件。

13. 程序员的困境
不管是什么端，只要是程序员，我们可能都会碰到这样的问题。日复一日的工作，有没有磨掉你的好奇心和编码的热情。考虑下18个月就去外面面一圈？不为换工作，就为看现在的自己到底是不是已经成为了“公司的专家”。 译文地址

14. 如何把你的图标转换成 Web 字体
在这篇教程中，我们将看看如何使用一个免费的 Web 应用程序 IcoMoon 将矢量图转换成 Web 字体。然后在看看如何使用生成的字体通过 CSS 应用到 Web 页面中。英文原文

两个viewport的故事（第一部分）

作者: JeremyWei | 可以转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明
网址: http://weizhifeng.net/viewports.html

原文：http://www.quirksmode.org/mobile/viewports.html

在这个迷你系列的文章里边我将会解释viewport，以及许多重要元素的宽度是如何工作的，比如<html>元素，也包括窗口和屏幕

这篇文章是关于桌面浏览器的，其唯一的目的就是为移动浏览器中相似的讨论做个铺垫。大部分开发者凭直觉已经明白了大部分桌面浏览器中的概念。在移动端我们将会接触到相同的概念，但是会更加复杂，所以对大家已经知道的术语做个提前的讨论将会对你理解移动浏览器产生巨大的帮助。

概念：设备像素和CSS像素

你需要明白的第一个概念是CSS像素，以及它和设备像素的区别。

设备像素是我们直觉上觉得「靠谱」的像素。这些像素为你所使用的各种设备都提供了正规的分辨率，并且其值可以（通常情况下）从screen.width/height属性中读出。

如果你给一个元素设置了width: 128px的属性，并且你的显示器是1024px宽，当你最大化你的浏览器屏幕，这个元素将会在你的显示器上重复显示8次（大概是这样；我们先忽略那些微妙的地方）。

如果用户进行缩放，那么计算方式将会发生变化。如果用户放大到200%，那么你的那个拥有width: 128px属性的元素在1024px宽的显示器上只会重复显示4次。

现代浏览器中实现缩放的方式无怪乎都是「拉伸」像素。所以，元素的宽度并没有从128被修改为256像素；相反是实际像素被放大了两倍。形式上，元素仍然是128个CSS像素宽，即使它占据了256个设备像素的空间。

换句话说，放大到200%使一个CSS像素变成一个设备像素的四倍。（宽度2倍，高度2倍，总共4倍）

一些图片可以解释清楚这个概念。这儿有四个100%缩放比的元素。这儿没有什么值得看的；CSS像素与设备像素完全重叠。

现在让我们缩小。CSS像素开始收缩，这意味着现在一个设备像素覆盖了多个CSS像素。

如果你进行放大，相反的行为会发生。CSS像素开始变大，现在一个CSS像素覆盖了多个设备像素。

这儿的要点是你只对CSS像素感兴趣。这些就是那些控制你的样式表如何被渲染的像素。

设备像素对你（译者：指的是开发者）来说基本上没用。但是对于用户不一样；用户将会放大或者缩小页面直到他能舒服的阅读为止。无论怎样，缩放等级对你不会产生影响。浏览器将会自动的使你的CSS布局被拉伸或者被压缩。

100%缩放

我是以假设缩放等级为100%来开始这个例子的。是时候需要更加严格的来定义一下这个100%了：

在缩放等级100%的情况下一个CSS像素完全等于一个设备像素。

100%缩放的概念在接下来的解释中会非常有用，但是在你的日常工作中你不用过分的担心它。在桌面环境上你将会在100%缩放等级的情况下测试你的站点，但即使用户放大或者缩小，CSS像素的魔力将会保证你的布局保持相同的比率。

屏幕尺寸

screen.width/height

- 意义：用户屏幕的整体大小。

- 度量单位：设备像素。

- 浏览器错误：IE8以CSS像素对其进行度量，IE7和IE8模式下都有这个问题。

让我们看一些实用的度量。我们将会以screen.width和screen.height做为开始。它们包括用户屏幕的整个宽度和高度。它们的尺寸是以设备像素来进行度量的，因为它们永远不会变：它们是显示器的属性，而不是浏览器的。

Fun! 但是这些信息跟对我们有什么用呢？

基本上没用。用户的显示器尺寸对于我们来说不重要－好吧，除非你想度量它来丰富你的web统计数据库。

窗口尺寸

window.innerWidth/Height

- 意义：浏览器窗口的整体大小，包括滚动条。

- 度量单位：CSS像素。

- 浏览器错误：IE7不支持。Opera以设备像素进行度量。

相反，你想知道的是浏览器窗口的内部尺寸。它告诉了你用户到底有多少空间可以用来做CSS布局。你可以通过window.innerWidth和window.innerHeight来获取这些尺寸。

很显然，窗口的内部宽度是以CSS像素进行度量的。你需要知道你的布局空间中有多少可以挤进浏览器窗口，当用户放大的时候这个数值会减少。所以如果用户进行放大操作，那么在窗口中你能获取的空间将会变少，window.innerWidth/Height的值也变小了。 （这儿的例外是Opera，当用户放大的时候window.innerWidth/Height并没有减少：它们是以设备像素进行度量的。这个问题在桌面上是比较烦人的，但是就像我们将要看到的，这在移动设备上是非常严重的。）

注意度量的宽度和高度是包括滚动条的。它们也被视为内部窗口的一部分。（这大部分是因为历史原因造成的。）

滚动距离

window.pageX/YOffset

- 意义：页面滚动的距离。

- 度量：CSS像素。

- 浏览器错误：无。

window.pageXOffset和window.pageYOffset，包含了文档水平和垂直方向的滚动距离。所以你可以知道用户已经滚动了多少距离。

这些属性也是以CSS像素进行度量的。你想知道的是文档已经被滚动了多长距离，不管它是放大还是缩小的状态。

理论上，如果用户向上滚动，然后放大，window.pageX/YOffset将会发生变化。但是，浏览器为了想保持web页面的连贯，会在用户缩放的时候保持相同的元素位于可见页面的顶部。这个机制并不能一直很完美的执行，但是它意味着在实际情况下window.pageX/YOffset并没有真正的更改：被滚动出窗口的CSS像素的数量仍然（大概）是相同的。

概念：viewport

在我们继续介绍更多的JavaScript属性之前，我们必须介绍另一个概念：viewport。

viewport的功能是用来约束你网站中最顶级包含块元素（containing block）<html>的。

这听起来有一点模糊，所以看一个实际的例子。假设你有一个流式布局，并且你众多边栏中的一个具有width: 10%属性。现在这个边栏会随着浏览器窗口大小的调整而恰好的放大和收缩。但是这到底是如何工作的呢？

从技术上来说，发生的事情是边栏获取了它父元素宽度的10%。比方说是<body>元素（并且你还没有给它设置过宽度）。所以问题就变成了<body>的宽度是哪个？

普通情况下，所有块级元素使用它们父元素宽度的100%（这儿有一些例外，但是让我们现在先忽略它）。所以<body>元素和它的父元素<html>一样宽。

那么<html>元素的宽度是多少？它的宽度和浏览器窗口宽度一样。这就是为什么你的那个拥有width: 10%属性的侧边栏会占据整个浏览器窗口的10%。所有web开发者都很直观的知道并且在使用它。

你可能不知道的是这个行为在理论上是如何工作的。理论上，<html>元素的宽度是被viewport的宽度所限制的。<html>元素使用viewport宽度的100%。

viewport，接着，实际上等于浏览器窗口：它就是那么定义的。viewport不是一个HTML结构，所以你不能用CSS来改变它。它在桌面环境下只是拥有浏览器窗口的宽度和高度。在移动环境下它会有一些复杂。

后果　Consequences

这个状况会有产生一些异样的后果。你可以在这个站点看到这些后果中的一个。滚动到顶部，然后放大两次或者三次，之后这个站点的内容就从浏览器窗口溢出了。

现在滚动到右边，然后你将会看见站点顶部的蓝色边栏不再覆盖一整行了。

这个行为是由于viewport的定义方式而产生的一个后果。我之前给顶部的蓝色边栏设置了width: 100%。什么的100%？<html>元素的100%，它的宽度和viewport是一样的，viewport的宽度是和浏览器窗口一样的。

问题是：在100%缩放的情况下这个工作的很好，现在我们进行了放大操作，viewport变得比我的站点的总体宽度要小。这对于viewport它本身来说没什么影响，内容现在从<html>元素中溢出了，但是那个元素拥有overflow: visible，这意味着溢出的内容在任何情况下都将会被显示出来。

但是蓝色边栏并没有溢出。我之前给它设置了width: 100%，并且浏览器把viewport的宽度赋给了它。它们根本就不在乎现在宽度实在是太窄了。

文档宽度？

我真正需要知道的是页面中全部内容的宽度是多少，包括那些「伸出」的部分。据我所知得到这个值是不可能的（好吧，除非你去计算页面上所有元素的宽度和边距，但是委婉的说，这是容易出错的）。

我开始相信我们需要一个我称其为「文档宽度」(document width，很显然用CSS像素进行度量)的JavaScript属性对。

并且如果我们真的如此时髦，为什么不把这个值引入到CSS中？我将会给我的蓝色边栏设置width: 100%，此值基于文档宽度，而不是<html>元素的宽度。（但是这个很复杂，并且如果不能实现我也不会感到惊讶。）

浏览器厂商们，你们怎么认为的？

度量viewport

document.documentElement.clientWidth/Height

- 意义：Viewport尺寸。

- 度量：CSS像素。

- 浏览器错误：无。

你可能想知道viewport的尺寸。它们可以通过document.documentElement.clientWidth和-Height得到。

如果你了解DOM，你应该知道document.documentElement实际上指的是<html>元素：即任何HTML文档的根元素。可以说，viewport要比它更高一层；它是包含<html>元素的元素。如果你给<html>元素设置width属性，那么这将会产生影响。（我不推荐这么做，但是那是可行的。）

在那种情况下document.documentElement.clientWidth和-Height给出的仍然是viewport的尺寸，而不是<html>元素的。（这是一个特殊的规则，只对这个元素的这个属性对产生作用。在任何其他的情况下，使用的是元素的实际宽度。）

所以document.documentElement.clientWidth和-Height一直代表的是viewport的尺寸，不管<html>元素的尺寸是多少。

两个属性对

但是难道viewport宽度的尺寸也可以通过window.innerWidth/Height来提供吗？怎么说呢，模棱两可。

两个属性对之间存在着正式区别：document.documentElement.clientWidth和-Height并不包含滚动条，但是window.innerWidth/Height包含。这像是鸡蛋里挑骨头。

事实上两个属性对的存在是浏览器战争的产物。当时Netscape只支持window.innerWidth/Height，IE只支持document.documentElement.clientWidth和Height。从那时起所有其他浏览器开始支持clientWidth/Height，但是IE没有支持window.innerWidth/Height。

在桌面环境上拥有两个属性对是有一些累赘的　－　但是就像我们将要看到的，在移动端这将会得到祝福。

度量<html>元素

document.documentElement.offsetWidth/Height

- 意义：元素（也就是页面）的尺寸。

- 度量：CSS像素。

- 浏览器错误：IE度量的是viewport，而不是元素。

所以clientWidth/Height在所有情况下都提供viewport的尺寸。但是我们去哪里获取<html>元素本身的尺寸呢？它们存储在document.documentElement.offsetWidth和-Height之中。

这些属性可以使你以块级元素的形式访问<html>元素；如果你设置width，那么offsetWidth将会表示它。

事件中的坐标

pageX/Y, clientX/Y, screenX/Y

- 意义：见正文。

- 度量单位：见正文。

- 浏览器错误：IE不支持pageX/Y。IE和Opera以CSS像素为单位计算screenX/Y。

然后是事件中的坐标。当一个鼠标事件发生时，有不少于五种属性对可以给你提供关于事件位置的信息。对于我们当前的讨论来说它们当中的三种是重要的：

- pageX/Y提供了相对于<html>元素的以CSS像素度量的坐标。

- clientX/Y提供了相对于viewport的以CSS像素度量的坐标。

- screenX/Y提供了相对于屏幕的以设备像素进行度量的坐标。

90%的时间你将会使用pageX/Y；通常情况下你想知道的是相对于文档的事件坐标。其他的10%时间你将会使用clientX/Y。你永远不需要知道事件相对于屏幕的坐标。

媒体查询

媒体查询

- 意义：见正文。

- 度量单位：见正文。

- 浏览器错误：IE不支持它们。

- 如果 device-width/height是以CSS像素进行度量的，那么Firefox将会使用screen.width/height的值。

- 如果width/height是以设备像素进行度量的，那么Safari和Chrome将会使用documentElement.clientWidth/Height的值。

最后，说说关于媒体查询的事。原理很简单：你可以声明「只在页面宽度大于，等于或者小于一个特定尺寸的时候才会被执行」的特殊的CSS规则。比如：

div.sidebar {
width: 300px;
}

@media all and (max-width: 400px) {
// styles assigned when width is smaller than 400px;
div.sidebar {
width: 100px;
}

}

当前sidebar是300px宽，除了当宽度小于400px的时候，在那种情况下sidebar变得100px宽。

问题很显然：我们这儿度量的是哪个宽度？

这儿有两个对应的媒体查询：width/height和device-width/device-height。

1. width/height使用和documentElement .clientWidth/Height（换句话说就是viewport宽高）一样的值。它是工作在CSS像素下的。

2. device-width/device-height使用和screen.width/height（换句话说就是屏幕的宽高）一样的值。它工作在设备像素下面。

你应该使用哪个？这还用想？当然是width。Web开发者对设备宽度不感兴趣；这个是浏览器窗口的宽度。

所以在桌面环境下去使用width而去忘记device-width吧。我们即将看到这个情况在移动端会更加麻烦。

总结

本文总结了我们对桌面浏览器行为的探寻。这个系列的第二部分把这些概念指向了移动端，并显示的指出了与桌面环境上的一些重要区别。

(完)
21 Jul 2013

comments powered by

原文链接：http://weizhifeng.net/viewports.html （本内容由互联网资料整理而成）

HTML5的Datalist标签，你需要知道什么

Jul 23 2013

Autocompletion is a pattern that all web users are familiar with. When you do a search, your search engine suggests terms. When you type a new email message, your mail client suggest recipients. This functionality, however, has not been available to web developers without a nontrivial amount of JavaScript. One of the new HTML5 elements, the <datalist>, brings this autocomplete functionality to the Web natively. In this article, I’ll describe what datalists are, when it’s appropriate to use them, their limitations and what to do for browsers that don’t support them. Let’s get started.

Creating Datalists

To show how a datalist works, let’s start with a traditional text input:

1. <label for="favorite_team">Favorite Team:</label>

2. <input type="text" name="team" id="favorite_team">

This field asks a user to provide his or her favorite sports team. By default, the user will be given no additional help to complete the field. But by using a datalist, you can provide a list of options the user can select from to complete the field. To do this, define a datalist with an option element for each suggestion:

1. <datalist>

2. <option>Detroit Lions</option>

3. <option>Detroit Pistons</option>

4. <option>Detroit Red Wings</option>

5. <option>Detroit Tigers</option>

6. <!-- etc... -->

7. </datalist>

To tie a datalist to an input element, give the input element a list attribute and the datalist an id attribute that match. Here’s an example:

1. <label for="favorite_team">Favorite Team:</label>

2. <input type="text" name="team" id="favorite_team" list="team_list">

3. <datalist id="team_list">

4. <option>Detroit Lions</option>

5. <option>Detroit Pistons</option>

6. <option>Detroit Red Wings</option>

7. <option>Detroit Tigers</option>

8. <!-- etc... -->

9. </datalist>

Notice that the list attribute of the input and the id attribute of the datalist contain the same value, “team_list”. This provides the link between the two.

That’s it. No JavaScript is required to make a datalist work. Figure 1 shows what the user will see in supporting browsers after typing a D.

Figure 1. Datalist Display (top left: Internet Explorer 10; top right: Firefox 18; bottom left: Chrome 24; bottom right: Opera 12)

Note: Internet Explorer 10 and Opera do not require the user to type a character before seeing suggestions, whereas Firefox and Chrome do.

Option elements can also have a value attribute. This is useful when a user might not know the code associated with a given option. Consider the following U.S. state input:

1. <label for="state">State:</label>

2. <input type="text" name="state" id="state" list="state_list">

3. <datalist id="state_list">

4. <option value="AL">Alabama</option>

5. <option value="AK">Alaska</option>

6. <option value="AZ">Arizona</option>

7. <option value="AR">Arkansas</option>

8. <!-- etc -->

9. </datalist>

Here, the user will see a list of full state names, but when the user makes a selection, the text input will be filled with the state’s code rather than the full name. An example is shown in Figure 2.

Figure 2. Selecting a Datalist Option with a Corresponding Value (Firefox 18)

The autocomplete Attribute

If the datalists in Figure 1 and Figure 2 look familiar, it’s because browsers have had autocompletion implemented for a long time. All browsers have some mechanism of storing a user’s inputs to be used for autocompletion later.

Authors can use the autocomplete attribute to control whether the browser should attempt to automatically complete the user’s data. The possible values are shown in the following example:

1. <form>

2. <!--

3. If the autocomplete attribute is not specified, it inherits the value

4. of its parent form element. If not specified, the default value of the

5. form's autocomplete attribute is "on".  Since neither this input nor its

6. form have the attribute specified, the "on" state will be used.

7. -->

8. <input type="text" name="firstName">

9. <!--

10. The "on" state indicates that the browser can remember the value for future

11. use as well as suggest previously stored values.

12. -->

13. <input type="text" name="address" autocomplete="on">

14. <!--

15. The "off" state tells the browser neither to store the value entered for

16. this input nor to suggest previously entered values. This should be

17. used if the data is sensitive or the value will never be reused.

18. -->

19. <input type="text" name="secret" autocomplete="off">

20. </form>

So what’s the difference between the autocomplete attribute and datalists? The autocomplete attribute tells the browser whether to give a user options for completion based on previous input and whether to store the entered value for future completion. Datalists are author-provided lists of suggestions that are always displayed regardless of previous input.

One caveat is that setting the autocomplete attribute to “off” prevents datalists from displaying in Opera. Here’s an example:

1. <!--

2. This datalist will never display in Opera because the autocomplete attribute

3. is set to "off". It will display in all other supporting browsers.

4. -->

5. <input type="text" list="pets" autocomplete="off">

6. <datalist id="pets">

7. <option>Cat</option>

8. <option>Dog</option>

9. <option>Hamster</option>

10. <option>Turtle</option>

11. </datalist>

Other Input Types

While autocompletion is traditionally associated with textual input, datalists can also be used on a number of the new HTML5 input types. Consider the range input type, which allows for the creation of a slider form element. By combining this with a datalist, you can suggest points on the range to the user.

For example, the following input asks the user to provide a donation between $5 and $200 dollars.

1. <label for="donation">Donation Amount (USD):</label>

2. <input type="range" name="donation" id="donation" list="donation_list"

3. step="5" min="5" max="200">

4. <datalist id="donation_list">

5. <option>25</option>

6. <option>50</option>

7. <option>100</option>

8. <option>200</option>

9. </datalist>

Figure 3 and Figure 4 show the display of a range input in Chrome 24 and Internet Explorer 10, respectively.

Figure 3. Range Input with Datalist (Chrome 24)

Figure 4. Range Input with Datalist (Internet Explorer 10)

You can see that each browser displays a tick mark for each datalist option provided. Additionally, Chrome will snap the slider to these predefined values as the user moves the slider near them.

Unfortunately, Internet Explorer and Chrome are the only browsers to support datalists on range inputs at this time. Figure 5 shows support of datalists on common input types in modern browsers.

Figure 5. Browser Support of Datalists on Form Input Types

When to Use a Datalist

Since datalists have no built-in mechanism to require that a user select a provided option, they are well suited for inputs that can accept any value. The earlier example of providing a sports team fits here. While the datalist suggested teams, the user was free to input any value.

Conversely, the U.S. state example fails this test because there is a limited set of valid values that the user must provide. Such a use case is better handled by the select element because it forces a selection.

One exception to this is inputs with a large number of valid selections. For example, consider the traditional country drop-down list shown in Figure 6.

Figure 6. Standard Country Drop-Down

This list impedes users by forcing them to sift through hundreds of options to find the one they’re looking for. An autocomplete feature fits this use case well because a user can quickly filter the list by typing one or two characters.

Here’s how country selection can be implemented with a datalist:

1. <label for="country">Country:</label>

2. <input type="text" id="country" list="country_list">

3. <datalist id="country_list">

4. <option value="AF">Afghanistan</option>

5. <option value="AX">Åland Islands</option>

6. <option value="AL">Albania</option>

7. <option value="DZ">Algeria</option>

8. <!-- more -->

9. </datalist>

Figure 7 shows what the user will see after typing a U.

Figure 7. A Datalist Approach to the Country Form Field

Enforcing a Value

While datalists do not natively allow you to require that an option be selected, you can easily add validation that does so. For example, the following code makes use of the HTML5 constraint validation API to add such validation:

1. // Find all inputs on the DOM which are bound to a datalist via their list attribute.

2. var inputs = document.querySelectorAll('input[list]');

3. for (var i = 0; i < inputs.length; i++) {

4. // When the value of the input changes...

5. inputs[i].addEventListener('change', function() {

6. var optionFound = false,

7. datalist = this.list;

8. // Determine whether an option exists with the current value of the input.

9. for (var j = 0; j < datalist.options.length; j++) {

10. if (this.value == datalist.options[j].value) {

11. optionFound = true;

12. break;

13. }

14. }

15. // use the setCustomValidity function of the Validation API

16. // to provide an user feedback if the value does not exist in the datalist

17. if (optionFound) {

18. this.setCustomValidity('');

19. } else {

20. this.setCustomValidity('Please select a valid value.');

21. }

22. });

23. }

The constraint validation API is implemented in all browsers that support datalists, so if the datalist works, the validation should work as well. Now, when the user attempts to submit a form with an input that has a datalist (and they have not selected an option), they will see the error shown in Figure 8.

Figure 8. Constraint Validation API Error (Internet Explorer 10)

It’s important to note that the constraint validation API does not remove the need for server-side validation. Users working with older browsers will not have the constraint validation API available, and malicious users can easily subvert client-side scripts.

While this approach works in modern browsers, it presents an unusable UI to users running browsers that lack support. Users are told that they must select a valid value, but if their browser doesn’t support datalists, they cannot see the options. Therefore, if you plan on using this approach, it’s essential to provide a UI that works in all browsers. This can be accomplished by detecting whether the browser supports datalists and then polyfilling appropriately.

Unsupporting Browsers

At the time of this writing, datalists for text inputs are supported in Internet Explorer 10, Firefox 4+, Chrome 20+, and Opera, which unfortunately leaves out a large number of users.

Unlike with many new HTML5 features, for most use cases, no extra work needs to be done in browsers that do not support datalists. By default, the list of options you provide are merely suggestions; therefore, users with browsers that lack support will simply need to fill in the text field without any suggestions.

However, some fallback options can be used to provide a fuller experience to users running unsupporting browsers.

Fallback to Alternate HTML Content

One option, popularized by Jeremy Keith, is to take advantage of the fact that browsers that do not support the datalist element will still display child elements to the user. The following shows how the country datalist example can be altered and fallback to using <select>:

1. <label for="country">Country:</label>

2. <datalist id="country_list">

3. <select name="country">

4. <option value="AF">Afghanistan</option>

5. <option value="AX">Åland Islands</option>

6. <option value="AL">Albania</option>

7. <option value="DZ">Algeria</option>

8. <option value="AS">American Samoa</option>

9. <!-- more -->

10. </select>

11. If other, please specify:

12. </datalist>

13. <input type="text" name=”country” id=”country” list="country_list">

The UI presented to users in browsers that support datalists will not change, but users working with browsers without support now see a select element with the country options and a text box they can use to enter any value. This is shown in Figure 9.

Figure 9: A Select Fallback for Datalists (Internet Explorer 9)

Polyfilling

One feature the select fallback doesn’t provide is the autocomplete behavior that datalists offer natively. If that is important for the datalists you’re adding, a second fallback option is to polyfill a datalist implementation.

To start, you first need to determine whether the user’s browser supports datalists. The popular feature detection library Modernizr provides such a test, as shown here:

1. if (Modernizr.input.list) {

2. //The browser supports the list attribute and datalists.

3. } else {

4. //The browser supports neither the list attribute nor datalists.

5. }

6. }

Using this approach, you can now polyfill a datalist implementation for users in unsupporting browsers. While several polyfills are available for datalists, I prefer using jQuery UI’s autocomplete widget. The following code shows a polyfill implementation:

1. var datalist,

2. listAttribute,

3. options = [];

4. // If the browser doesn't support the list attribute...

5. if (!Modernizr.input.list) {

6. // For each text input with a list attribute...

7. $('input[type="text"][list]').each(function() {

8. // Find the id of the datalist on the input

9. // Using this, find the datalist that corresponds to the input.

10. listAttribute = $(this).attr('list');

11. datalist = $('#' + listAttribute);

12. // If the input has a corresponding datalist element...

13. if (datalist.length > 0) {

14. options = [];

15. // Build up the list of options to pass to the autocomplete widget.

16. datalist.find('option').each(function() {

17. options.push({ label: this.innerHTML, value: this.value });

18. });

19. // Transform the input into a jQuery UI autocomplete widget.

20. $(this).autocomplete({ source: options });

21. }

22. });

23. // Remove all datalists from the DOM so they do not display.

24. $('datalist').remove();

25. }

Figure 10 shows the display of the country list example in Safari with the jQuery UI autocomplete polyfill.

Figure 10. Country Datalist Polyfilled Using jQuery UI’s Autocomplete Widget(Safari)

You may have noticed that by default, jQuery UI’s autocomplete widget matches characters anywhere in the options, whereas datalists match options only at the beginning of the string. Unlike the native datalist, the autocomplete widget allows you to customize this to match options however you’d like.

The following example shows how you can build an autocomplete feature that matches options only at the beginning of the string:

1. <input type="text" id="autocomplete">

2. <script>

3. var options = ['Badger', 'Bat', 'Cat', 'Cheetah', 'Crocodile', 'Deer', 'Donkey',

4. 'Elephant', 'Fish', 'Frog', 'Giraffe', 'Gorilla'];

5. $('#autocomplete').autocomplete({

6. // req will contain an object with a "term" property that contains the value

7. // currently in the text input.  responseFn should be invoked with the options

8. // to display to the user.

9. source: function (req, responseFn) {

10. // Escape any RegExp meaningful characters such as ".", or "^" from the

11. // keyed term.

12. var term = $.ui.autocomplete.escapeRegex(req.term),

13. // '^' is the RegExp character to match at the beginning of the string.

14. // 'i' tells the RegExp to do a case insensitive match.

15. matcher = new RegExp('^' + term, 'i'),

16. // Loop over the options and selects only the ones that match the RegExp.

17. matches = $.grep(options, function (item) {

18. return matcher.test(item);

19. });

20. // Return the matched options.

21. responseFn(matches);

22. }

23. });

24. </script>

Older Versions of Internet Explorer

To make any polyfill of the datalist element work in older versions of Internet Explorer, you need to take two extra steps.

Option Elements

The first is that Internet Explorer version 9 and earlier all require that option elements be immediate children of select elements. If they are not, these versions do not recognize them, and they will not be visible to the polyfill.

Luckily, this is easy to work around. By using conditional comments, you can place a select element around the options only for these versions of Internet Explorer. Refer to this example:

1. <datalist>

2. <!--[if IE]><select><!--<![endif]-->

3. <option>Apple</option>

4. <option>Banana</option>

5. <option>Cherry</option>

6. <!- etc -->

7. <!--[if IE]><select><!--<![endif]-->

8. </datalist>

Internet Explorer version 9 and earlier will now detect the options appropriately. Internet Explorer 10 will not be affected since conditional comments were removed from the parser in Internet Explorer 10. All other browsers will also ignore the comments.

HTML5 Shiv

In Internet Explorer version 8 and earlier, unknown elements cannot contain children. Therefore, even if a datalist’s option elements are within select elements, they will still not be recognized.

Fortunately, there’s an easy fix for this issue as well. The popular HTML5 Shiv library allows elements unknown to Internet Explorer 6–8 to have children. This shiv is included by default in Modernizr; just be sure the “html5shiv” check box is selected when you configure your download.

Limitations of Datalists

Although datalists are perfect for adding suggestions to text inputs, they suffer from a lack of customization capabilities that many modern Web applications require. For example, you cannot do the following with datalists:

- Use CSS to change its display or the display of its options.

- Control the positioning of the list.

- Control the number of characters typed before the browser displays results to the user.

- Control what is considered a match (case sensitivity, match at beginning of string vs. anywhere, and so on).

- Tie the input to a server-side datasource.

This means that if you need any of this functionality, you need to look into a more robust autocomplete solution. The jQuery UI autocomplete widget provides the ability to do all of this and more. The autocomplete widget also supports all browsers back to Internet Explorer 7 without the need of a polyfill. For more information on the autocomplete widget, check out its demos and API documentation. (The autocomplete widget handles only text-based inputs, therefore it won’t be able to help you for some of the more specialized input types like range and color.)

Wrapping Up

Datalists provide a quick, native means of displaying input suggestions to the user. Since the options are merely suggestions, for many situations it’s not necessary to provide a fallback for unsupporting browsers; users of these browsers simply will not see the suggestions.

For situations when you do want to provide a functional datalist to all users, you can detect support and polyfill the functionality for browsers that lack support.

While datalists are great for offering suggestions, they are limited in the functionality they provide. If you need a more robust autocomplete solution, jQuery UI’s autocomplete widget is a good place to get started.

TJ VanToll is a Web developer and jQuery UI team member. He lives in Lansing, Michigan, and writes about his experiences with jQuery, HTML 5, CSS and other things that strike his fancy on his blog. He is the proud father of twin sons, and when he’s not on the Internet, he’s usually found chasing them in circles. Find TJ on his Blog and on Twitter-@tjvantoll.

(dpe)

About the Author

The jungle is alive: Be it a collaboration between two or more authors or an article by an author not contributing regularly. In these cases you find the Noupe Editorial Team as the ones who made it. Guest authors get their own little bio boxes below the article, so watch out for these.

Homepage

原文链接：http://www.noupe.com/webdev/html5-datalists-what-you-need-to-know-78024.html （本内容由互联网资料整理而成）

如何在 Internet Explorer 11中开启 WebGL 暨微软和 WebGL 的恩怨情仇录

正如我们上周报道的，国外开发者Francois Remy在泄露版Windows Blue附带的Internet Explorer 11中发现，WebGL接口已经封装完成，但功能上还未能开放支持。在这之后，另一名开发者Rafael Rivera继续深入挖掘，竟然发现了在此版本的Internet Explorer 11中开启WebGL支持的方法。

实际上方法简单地称奇。首先你必须已经安装了Windows 8 Blue build 9364和最新的显卡驱动（系统自带的显卡驱动在OpenGL方面支持不足），然后只需要简单地导入以下的注册表脚本即可。

1
2
3
4
5
6
7
8
9
10
11

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl]

[HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_WEBGL]

"iexplore.exe"=dword:00000001

[HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_WEBGL_HLSL_SHADERS]

"iexplore.exe"=dword:00000000

需要特别值得注意的是其中最后一个FEATURE_WEBGL_HLSL_SHADERS的键值，如果设置为1，那么会让Internet Explorer 11使用IESL代替GLSL作为着色器语言。如果设置为0，则符合先在大多数WebGL应用的习惯。

目前IE 11对WebGL的支持度仍然非常有限，你可以把一下代码另存为HTML文件，然后在IE 11中打开，你会发现网页中出现一片蓝色的区域，而这正代表着网页成功获得了名为“experimental-webgl”的3D绘制上下文——Internet Explorer 11终于支持WebGL了！

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

<body onload="demo()">
<canvas style="height: 100%; width: 100%" id='webgl' />
</body>

<script>
var gl = {};
function demo()
{
var canvas = document.getElementById("webgl");
try
{
gl = canvas.getContext("experimental-webgl");
} catch(e) { }

if (gl) {
gl.clearColor(0, 0.678, 0.937, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
}
}
</script>

下面附上IE 11运行LearningWebGL教程第15课实例的视频：

回顾过去，自从WebGL这项新的网页图形规范提出伊始，微软就从未表露出喜色。在2011年6月，微软更是雇佣一家欧洲安全机构对WebGL提出最强烈的质疑，认为让网页直接暴露给GPU是非常危险的行为。如果真要认真地从技术上讲，微软当时主要发难于跨域资源、DoS攻击以及直接操控GPU。

虽然作为一个刚刚诞生的技术标准，WebGL的确有很多路要走，但是如此兴师动众的指责让很多对这项新技术充满希望的开发者感到微软有些过分。当时开发者普遍认为微软对于WebGL的指责不是从技术角度出发的，而是完全出于商业目的。著名的WebGL开发者和布道师Gregg Tavares更是祭出一篇题目直接带着脏字的战斗檄文直斥微软。参见：《WebGL Security and Microsoft Bullshit》

在震惊业界的斥责之后，WebGL工作组把工作重心转移到了致力于解决安全问题上，一直到今天Khronos Group在GDC 2013上最新公布的WebGL 1.0.2标准中还是把安全放在了首位。

随后在2012年，微软指责WebGL的多项安全软肋都已经取得了极为有效的解决进展：现在CORS已经可以有效的解决跨域资源的问题；而许多厂商也已经部署了GL_ARB_robustness扩展来解决DoS攻击问题，并且WebGL工作组还在继续寻找其他的解决办法。至于直接操控GPU的问题，则是无法避免的，只要一种技术可以直接与GPU对话都会造成一定程度的安全威胁，在2011年于墨尔本召开的一次技术大会上，有人用相同的原理在Microsoft Silverlight 5、Adobe Flash 11和WebGL上实现了同样的漏洞攻击。

尽管微软仍然对WebGL持抗拒态度，但是在2011年8月举办的Siggraph 2011上，却爆出IE 10即将支持Typed Array（类型化数组）的消息。微软拒绝了WebGL，但却抵不住诱惑，引进了因为WebGL才诞生的Typed Array，这项技术允许开发者直接在Web上操控二进制数据。2011年12月1日，微软IE官方博客发表了一片名为《 Working with Binary Data using Typed Arrays》的文章，正式为 IE10 引进了Type Array。

时光荏苒，微软此后在多个场合多次被问到关于WebGL的问题，但微软发言人永远都是微笑的避而不答，当然有时候也会重复一下安全问题这个老梗。

2012年3月6日，在香港举办的World Internet Developers’ Summit活动中，微软高级布道师Giorgio Sardo做了《切绳子游戏的幕后故事》的演讲，在提问环节中有人提出IE将会在什么时候支持WebGL的问题，他的回答是“Not now”。令人感动的是他没直接说“never”实在是太给面子了。

随着WebGL的日渐成熟，越来越多的WebGL实例和应用脱引而出，微软逐渐意识到了不管他们是否要支持WebGL，他们现在缺乏一种网页图形技术来让自己的浏览器在网页视觉领域和Mozilla、Google、Opera来抗衡。

2012年11月，CNET 的记者 Shankland 采访了微软网络标准工作组的领导人、同时也是 W3C HTML 工作组的联合主席 Paul Cotton。他依然用安全问题作为挡箭牌，但是却破天荒指出IE团队对开发者关于IE缺乏图形API的诟病深表理解，他说：“我们依然坚持对 WebGL 的安全问题表示担忧。如果要我们支持 WebGL，那么就必须毫无余地的解决这些安全问题。从 IE8、IE9 到 IE10，每次我们宣布添加新的特性的时候，都会有人反馈要求支持其他功能，而在这些人们呼吁的功能中 WebGL 绝对是位居前五位的。我们对 IE 博客上的留言非常清楚，也很明白现在的状况。在 Web 上开发可交互的 3D 图形的确很棒。如果我们可以解决安全问题，那我想我们会认真地寻找一种为 Web 引入 3D 图形的方法。”

此后，Windows Blue泄露，开发者意外发现IE 11已经封装好WebGL接口，并且可以通过注册表开启支持，这样才有了本文开头的一幕。

这正是四年盘踞龙虎斗，微软终将入大流！欢迎IE 11加入WebGL大家庭！

HiWebGL综合报道，如需转载请注明出处！

WebGL中文教程

第1课至第16课所有教程实例打包下载：

115网盘下载：http://115.com/file/dn34mx83#

新浪微博微盘下载：http://vdisk.weibo.com/s/1tsZE/1323662911

作为目前最好的学习WebGL的网站之一，LearingWebGL.com的作者Giles Thomas提供了一系列通俗易懂、由浅入深的教程，并配有配套的代码实例。长期以来国内还没有相应的WebGL中文教程。因此HiWebGL决定正式启动教程翻译工程！

今后HiWebGL将会陆陆续续的更新中文版的WebGL教程。翻译的内容主要是基于LearningWebGL.com的教程。但由于原文最后更新于2011年3月，其中部分章节已经不适合现在的情况（例如Mac OS X的最新版本已经由Snow Leopard升为Lion、Firefox 6.0已经开始将大部分的Linux下的显卡驱动移除出黑名单等），并且一些内容不适合中国本地情况，我们将会酌情进行适量的修改，以适应目前中国开发者的开发环境。

另外，因为译者个人方便的原因，我们将原教程中的第三方图形库由glMatrix改为Oak3D实现，这不影响到Demo的最终效果和实现，也不影响到WebGL的讲解和学习。原教程正文中相应的代码和讲解也为做了相应修改。

关于Oak3D：Oak3D是一套简单易用、性能优越的WebGL Javascript Library。您可以在他们的主页找到更多信息。

Oak3D主页：http://www.oak3d.com

由于原文基于 Creative Commons Attribution/Share-Alike license，我们的中文翻译也将继承这一协议，欢迎转载或修改并请标明出处。

最后感谢Giles Thomas给我们带来了如此精彩的教程！

原文地址：http://learningwebgl.com/blog/?page_id=1217

Posted in WebGL教程, 推荐 and tagged html5, webgl, 中文教程, 教程. Bookmark the permalink.

HiWebGL译者声明：因为译者个人方便的原因，我们将原教程中的第三方图形库由glMatrix改为Oak3D实现，这不影响到Demo的最终效果和实现，也不影响到WebGL的讲解和学习。原教程正文中相应的代码和讲解也为做了相应修改！本教程由HiWebGL翻译整理，转载请注明出处！

关于Oak3D：Oak3D是一套简单易用、性能优越的WebGL Javascript Library。您可以在他们的主页找到更多信息。Oak3D主页：http://www.oak3d.com

欢迎来到系列教程的第16课！在这一课中，我们将介绍一种非常有用的绘制技术: 将3D场景渲染到一张纹理中。通过这一技术，我们可以在绘制过程中利用上一次绘制的结果来创造一些特殊的效果。同时，这也是一种常用的绘制技巧，除了在一个场景中绘制另一个场景（本课将详细解释这种应用途径）外，渲染到纹理也是做选取（鼠标在3D场景中点选物体）、引用、反射等3D特效的基础技术。

下面的视频就是我们这节课将会完成的最终效果。

点击这里打开一个独立的WebGL页面，如果你的浏览器不支持WebGL，请点击这里。

在本课的演示程序中，你会看到一个综合了多种光照效果(包含笔记本屏幕上的高光)的白色笔记本模型。除了笔记本模型，你会发现一些更加有趣的东西，在笔记本的屏幕上显示了另外一个3D场景，对，没错，那是13课中我们看过的月球和木箱。这个演示程序的思路很清楚，它将13课中的3D场景渲染到了一张2D纹理上，然后将这张2D纹理映射到了笔记本模型的屏幕上。

下面我们来看看它是怎么工作的……

惯例声明：本系列的教程是针对那些已经具备相应编程知识但没有实际3D图形经验的人的；目标是让学习者创建并运行代码，并且明白代码其中的含义，从而可以快速地创建自己的3D Web页面。如果你还没有阅读前一课，请先阅读前一课的内容吧。因为本课中我只会讲解那些与前一课中不同的新知识。

另外，我编写这套教程是因为我在独立学习WebGL，所以教程中可能（非常可能）会有错误，所以还请风险自担。尽管如此，我还是会不断的修正bug和改正其中的错误的，所以如果你发现了教程中的错误，请告诉我。

有两种方法可以获得上面实例的代码：在实例的独立页面中选择“查看源代码”；或者点击这里下载我们为您准备好的压缩包。

你可以使用任何你所熟悉的文本编辑器来查看课程源码（index.html）。本课内容的文件组织与前几课有较大的不同，下面让我们按由下向上的顺序来查看代码内容。首先是webGLStart，发生变化的代码位于第789行。

786
787
788
789
790
791
792
793
794
795
796
797
798
799

function webGLStart() {
var canvas = document.getElementById("lesson16-canvas");
initGL(canvas);
initTextureFramebuffer();
initShaders();
initBuffers();
initTextures();
loadLaptop();

gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);

tick();
}

这段代码看起来并没有什么特别的地方，初始化WebGL，加载Shader，创建用于绘制的顶点Buffer，加载用于月亮和木箱的纹理，及像14课中加载茶壶模型一样，加载JSON格式的笔记本模型文件。在这中间，唯一所不同的，就是我们额外为纹理创建了一个framebuffer。在继续解释代码内容之前，让我们首先来了解一下什么是frame buffer。

当你使用WebGL API渲染3D内容时，显卡需要一块缓冲区来存储渲染的最终结果。通过WebGL 接口，你实际上可以控制这块存储区域的结构类型。首先，你至少需要一块区域来存储每个像素渲染后的颜色结果，同时，你往往还需要一个depth buffer(深度缓冲区)来处理视线遮挡，这样，近处的像素将会遮挡住较远处的像素，depth buffer同样需要一部分存储空间。除此之外，有时我们也会用到一些其它种类的buffer，比如stencil buffer(模板缓冲区)，在接下来的系列教程中，我们会讲到它。

Framebuffer就是用来存储渲染结果的这一类缓冲区的一种集合。如果你没有指定，默认会存在一个”Default” frame buffer，也就是到本课之前，我们一直在使用的frame buffer，它代表最终会被显示在网页中的缓冲区域。除此之外，你可以创建你自己的frame buffer，并指定WebGL将渲染结果输送到你所创建的frame buffer中。在本课中，我们创建了一个自己的frame buffer，并指定它使用一张纹理作为存储像素颜色的缓冲区。同时，我们也需要分配一个深度缓冲区来做深度遮挡计算。

下面，让我们来看一下创建frame buffer的具体代码。在本课的实例中，initTextureFramebuffer函数完成了这个工作，如果你需要查找这个函数，它大概在文件中从上向下浏览的三分之一处。

258
259
260
261

var rttFramebuffer;
var rttTexture;

function initTextureFramebuffer() {

在函数之前，我们定义了两个全局变量，rttFramebuffer用来存储将被渲染到笔记本屏幕的frame buffer，而rttTexture用来存储容纳笔记本屏幕渲染结果的纹理(rttTexture在我们绘制整个笔记本场景时会使用到)。下面，继续看这个函数接下来的部分。

262
263
264
265

rttFramebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer);
rttFramebuffer.width = 512;
rttFramebuffer.height = 512;

第一步是创建一个frame buffer，然后的操作流程很标准，就像纹理和vertex attribute buffer一样，我们将frame buffer指定为WebGL的当前frame buffer。当前frame buffer的含义就是其后的对frame buffer的操作都将作用在当前frame buffer上。此外，我们将笔记本屏幕的绘制尺寸储存在了我们所创建的frame buffer中，注意，这两个尺寸属性并不是frame buffer的固有属性，我只是利用了javascript动态为对象添加属性的技巧，因为在后续对frame buffe的使用中，我们需要这个信息。我们选择绘制一个512×512 的frame buffer，WebGL的只能创建2的整数幂次大小的纹理，而对于我们的应用环境，256×256则会使得画面出现模糊，而更高的1024×1024则不会对提高画面精细度有太大的帮助。

接下来，我们创建一个纹理对象，并初始化相应的参数。

267
268
269
270
271

rttTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, rttTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
gl.generateMipmap(gl.TEXTURE_2D);

之后，我们调用gl.texImage2D来为纹理初始化数据，但在这里我们会发现一些与之前不同的地方。

273

gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, rttFramebuffer.width, rttFramebuffer.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);

通常，当我们想为texture加载一张图片时，我们会使用gl.texImage2D来为texture指定对应的Image，但在这里，我们并没有图片可用来加载，所以我们调用了gl.texImage2D 的另外一个版本，告诉WebGL我们并没有Image可供加载，我们仅仅需要创建一个指定大小的空数据的纹理对象。严格来说，这里的最后一个参数是用来指定传入纹理的像素列表，但在这里，我们不需要纹理对象加载任何数据，所以在这里，我们传入null。（早期的Minefield 需要使用者传入一个指定大小的空数组来初始化纹理，但这个问题目前似乎已经被修复掉了）

现在我们拥有了一张用来存储颜色结果的纹理，接下来，我们需要创建一张depth buffer用来记录深度信息。

275
276
277

var renderbuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, rttFramebuffer.width, rttFramebuffer.height);

这里我们创建了一个render buffer对象，render buffer表示用来存储与frame buffer相关的广义用途的缓冲区,你可以用它来作为深度缓冲，或者模板缓冲，或者两者兼之。像其它缓冲区对象一样，我们绑定render buffer，将它作为WebGL的当前render buffer，并调用gl.renderbufferStorage来通知WebGL当前render buffer用作深度缓冲区，每个像素的深度数据大小16位，同时我们还指定了render buffer的尺寸。

接下来：

279
280

gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, rttTexture, 0);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer);

我们将创建的纹理和render buffer都绑定到当前的frame buffer上(不要忘记，在frame buffer被创建后，我们就一直将它作为WebGL的当前frame buffer)。这里，我们告诉WebGL当前frame buffer使用rttTexture作为颜色缓冲区（gl.COLOR_ATTACHMENT0），使用我们创建的render buffer作为深度缓冲区（gl.DEPTH_ATTACHMENT）。

到此为止，frame buffer的内容初始化工作就完成了，WebGL已经知道我们所创建的frame buffer该绘制到什么地方去；所以，在这之后，我们将当前的texture、renderbuffer和framebuffer重置为默认状态。

282
283
284

gl.bindTexture(gl.TEXTURE_2D, null);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);

现在我们有了一个自定义的frame buffer，那如何去使用它呢? 让我们跳到drawScene函数来看一下，这个函数在文件的底部。在函数的最开始处，在正常的视口设置等初始化工作之前，你应该看到了一些新东西。

685
686
687
688
689
690
691
692
693
694
695
696

var laptopAngle = 0;

function drawScene() {
gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer);
drawSceneOnLaptopScreen();

gl.bindFramebuffer(gl.FRAMEBUFFER, null);

gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

pMatrix = okMat4Proj(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0);

函数的第一行，非常容易理解，我们将当前frame buffer从默认frame buffer切换到之前在initTextureFramebuffer函数中所创建的frame buffer，这样，后续的绘制工作将被导向我们所设置的纹理和深度缓冲，而不再是最终的网页。之后，我们调用drawSceneOnLaptopScreen函数，将需要显示在笔记本屏幕上的3D场景绘制到指定的当前frame buffer中，绘制结束后，当前frame buffer被切换后默认状态。在继续接下来的学习之前，我认为读者有必要了解一下drawSceneOnLaptopScreen这个函数的内容，我不会在这里列出它们，因为所涉及的代码非常简单,基本是第13课中drawscene函数的简化版。这是因为我们之前的绘制代码并没有设置特定的frame buffer，而仅仅是将所有内容绘制到当前frame buffer上，这里与13课中唯一的不同就是我们去掉了可移动光源等在本课中并不需要的场景元素。

所以，当drawScene函数开头的三行代码执行后，我们就拥有了一张存储着第13课场景绘制结果的纹理。绘制代码的剩余部分就是正常绘制出笔记本模型，并将之前存储着场景绘制结果的纹理映射到笔记本模型的屏幕上。首先是初始化model-view矩阵，并将笔记本旋转一定的角度，角度由laptopAngle给出（像之前的课程一样，laptopAngle每帧都会在animate函数中更新来实现笔记本模型的持续旋转效果）。

698
699
700
701
702
703
704

mvMatrix.identity();

mvPushMatrix();

mvMatrix.translate(OAK.SPACE_LOCAL, 0, -0.4, -2.2, true);
mvMatrix.rotY(OAK.SPACE_LOCAL, laptopAngle, true);
mvMatrix.rotX(OAK.SPACE_LOCAL, -90, true);

之后，在下面的代码中，我们将场景中光源的颜色和位置等信息传给显卡。

706
707
708
709
710
711

gl.uniform1i(shaderProgram.showSpecularHighlightsUniform, true);
gl.uniform3f(shaderProgram.pointLightingLocationUniform, -1, 2, -1);

gl.uniform3f(shaderProgram.ambientLightingColorUniform, 0.2, 0.2, 0.2);
gl.uniform3f(shaderProgram.pointLightingDiffuseColorUniform, 0.8, 0.8, 0.8);
gl.uniform3f(shaderProgram.pointLightingSpecularColorUniform, 0.8, 0.8, 0.8);

接下来，我们需要将笔记本的光照材质信息传入显卡，这里有一些之前没有出现过的新内容，但它们与渲染到纹理技术本身并没有关系。你也许还记得，在第7课中，当我们描述Phong光照算法的时候，我提到了材质信息对应不同的光照类型有不同的颜色信息：ambient color（环境反射光），diffuse colour（漫反射光），specular colour（镜面反射光/高光）。在之前的课程中，我们始终假设所有的材质颜色都是纯白色，如果有纹理，我们则取纹理颜色作为所有材质属性的颜色，但在本课中，这种简化后的光照方式将不再试用，原因你将在稍后的文章中看到。在这里，我们需要为材质的每一种属性信息设置单独的数据，同时，我们还引入了一种新的材质属性：emissive colour(自发光)。但是不用担心，对于笔记本模型来说，这个设置过程非常简单，因为笔记本本身是白色的。

713
714
715
716
717
718
719

// The laptop body is quite shiny and has no texture.  It reflects lots of specular light
gl.uniform3f(shaderProgram.materialAmbientColorUniform, 1.0, 1.0, 1.0);
gl.uniform3f(shaderProgram.materialDiffuseColorUniform, 1.0, 1.0, 1.0);
gl.uniform3f(shaderProgram.materialSpecularColorUniform, 1.5, 1.5, 1.5);
gl.uniform1f(shaderProgram.materialShininessUniform, 5);
gl.uniform3f(shaderProgram.materialEmissiveColorUniform, 0.0, 0.0, 0.0);
gl.uniform1i(shaderProgram.useTexturesUniform, false);

下一步，如果笔记本模型的顶点数据已经加载完毕，那就将它绘制出来，这部分代码读者应该已经非常熟悉了，特别是在阅读过14课之后（实际上，大部分是从第14课的内容中复制过来的）。

721
722
723
724
725
726
727
728
729
730
731
732
733
734

if (laptopVertexPositionBuffer) {
gl.bindBuffer(gl.ARRAY_BUFFER, laptopVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, laptopVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

gl.bindBuffer(gl.ARRAY_BUFFER, laptopVertexTextureCoordBuffer);
gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, laptopVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);

gl.bindBuffer(gl.ARRAY_BUFFER, laptopVertexNormalBuffer);
gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute, laptopVertexNormalBuffer.itemSize, gl.FLOAT, false, 0, 0);

gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, laptopVertexIndexBuffer);
setMatrixUniforms();
gl.drawElements(gl.TRIANGLES, laptopVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
}

上面的代码已经将笔记本模型绘制在了网页上，下面，我们需要将纹理映射到笔记本的屏幕上。首先依然是需要设置光照参数，在这里，我们设置了不同的emissive colour。

736
737
738
739
740
741

gl.uniform3f(shaderProgram.materialAmbientColorUniform, 0.0, 0.0, 0.0);
gl.uniform3f(shaderProgram.materialDiffuseColorUniform, 0.0, 0.0, 0.0);
gl.uniform3f(shaderProgram.materialSpecularColorUniform, 0.5, 0.5, 0.5);
gl.uniform1f(shaderProgram.materialShininessUniform, 20);
gl.uniform3f(shaderProgram.materialEmissiveColorUniform, 1.5, 1.5, 1.5);
gl.uniform1i(shaderProgram.useTexturesUniform, true);

下面，我们需要设置用于采样的纹理，也就是之前存储着第一次绘制结果的纹理对象。

752
753
754

gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, rttTexture);
gl.uniform1i(shaderProgram.samplerUniform, 0);

最后，绘制笔记本屏幕。

756
757
758
759

setMatrixUniforms();
gl.drawArrays(gl.TRIANGLE_STRIP, 0, laptopScreenVertexPositionBuffer.numItems);

mvPopMatrix();

整个过程是不是有些由繁入简的感觉呢? 上面就是实现渲染到纹理，并将纹理用作其它绘制的全部代码。

在本文的最后，我们给出了一个用于新材质类型光照的fragment shader，在理解之前课程的基础上，新shader的内容应该非常容易理解。这里唯一的新内容就是增加了emissive colour，而shader对它的处理仅仅的将它简单的加在最终的像素颜色上：

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

precision mediump float;

varying vec2 vTextureCoord;
varying vec3 vTransformedNormal;
varying vec4 vPosition;

uniform vec3 uMaterialAmbientColor;
uniform vec3 uMaterialDiffuseColor;
uniform vec3 uMaterialSpecularColor;
uniform float uMaterialShininess;
uniform vec3 uMaterialEmissiveColor;

uniform bool uShowSpecularHighlights;
uniform bool uUseTextures;

uniform vec3 uAmbientLightingColor;

uniform vec3 uPointLightingLocation;
uniform vec3 uPointLightingDiffuseColor;
uniform vec3 uPointLightingSpecularColor;

uniform sampler2D uSampler;

void main(void) {
vec3 ambientLightWeighting = uAmbientLightingColor;

vec3 lightDirection = normalize(uPointLightingLocation - vPosition.xyz);
vec3 normal = normalize(vTransformedNormal);

vec3 specularLightWeighting = vec3(0.0, 0.0, 0.0);
if (uShowSpecularHighlights) {
vec3 eyeDirection = normalize(-vPosition.xyz);
vec3 reflectionDirection = reflect(-lightDirection, normal);

float specularLightBrightness = pow(max(dot(reflectionDirection, eyeDirection), 0.0), uMaterialShininess);
specularLightWeighting = uPointLightingSpecularColor * specularLightBrightness;
}

float diffuseLightBrightness = max(dot(normal, lightDirection), 0.0);
vec3 diffuseLightWeighting = uPointLightingDiffuseColor * diffuseLightBrightness;

vec3 materialAmbientColor = uMaterialAmbientColor;
vec3 materialDiffuseColor = uMaterialDiffuseColor;
vec3 materialSpecularColor = uMaterialSpecularColor;
vec3 materialEmissiveColor = uMaterialEmissiveColor;
float alpha = 1.0;
if (uUseTextures) {
vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
materialAmbientColor = materialAmbientColor * textureColor.rgb;
materialDiffuseColor = materialDiffuseColor * textureColor.rgb;
materialEmissiveColor = materialEmissiveColor * textureColor.rgb;
alpha = textureColor.a;
}
gl_FragColor = vec4(
materialAmbientColor * ambientLightWeighting
+ materialDiffuseColor * diffuseLightWeighting
+ materialSpecularColor * specularLightWeighting
+ materialEmissiveColor,
alpha
);
}

到此为止，本课的所有内容就结束了。在这一课中，我们学习到了如何将一个场景渲染到一张纹理中，并在另一个场景中使用这张纹理，同时，我们也深入了解了材质属性的细节和它们的工作原理。在下一课中，我们将给出一个渲染到纹理的实际应用：3D场景中的鼠标点选，这个功能可以让用户通过鼠标点击与3D场景中物体进行直接交互。

HiWebGL译者声明：因为译者个人方便的原因，我们将原教程中的第三方图形库由glMatrix改为Oak3D实现，这不影响到Demo的最终效果和实现，也不影响到WebGL的讲解和学习。原教程正文中相应的代码和讲解也为做了相应修改！本教程由HiWebGL翻译整理，转载请注明出处！

关于Oak3D：Oak3D是一套简单易用、性能优越的WebGL Javascript Library。您可以在他们的主页找到更多信息。Oak3D主页：http://www.oak3d.com

欢迎来到系列教程的第15课！在这一课中，我们将介绍高光贴图技术。就像普通纹理用来指定物体表面的颜色一样，高光贴图可以用来指定物体表面每一处细节的光照反射程度，因此可以大大增强物体的真实感。本课中并没有太多新增的代码， 只会在前几课基础上进行简单的改动，但从意义上来讲，本课将引入一种全新的概念。

下面的视频就是我们这节课将会完成的最终效果。

点击这里打开一个独立的WebGL页面，如果你的浏览器不支持WebGL，请点击这里。

在本课的演示程序中，你应当会看到一个旋转的地球，地球表面有从太阳反射出的光斑。如果你仔细观察，你会发现高光反射只存在于地球表面的海洋部分，而对于陆地部分，正像你预想的那样，不会反射太阳的光照。

如果你尝试取消“开启高光贴图”选项(此选项位于canvas的下方)。你会发现高光反射就会出现在陆地上，这种效果非常不真实，看起来像是一个超大的聚光灯正在对着地球照射。这就是高光贴图的作用， 它可以允许你精确的控制物体表面的反光细节。

重新打开“开启高光贴图”选项，并降低光源的漫反射强度, 比如将漫反射强度设为(0.5,0.5,0.5), 之后, 再关掉高光贴图， 你会发现地球表面的颜色纹理已经几乎看不到了，但是高光却没有变化，依然同时出现在海洋和陆地区域。

下面我们来看看它是怎么工作的……

惯例声明：本系列的教程是针对那些已经具备相应编程知识但没有实际3D图形经验的人的；目标是让学习者创建并运行代码，并且明白代码其中的含义，从而可以快速地创建自己的3D Web页面。如果你还没有阅读前一课，请先阅读前一课的内容吧。因为本课中我只会讲解那些与前一课中不同的新知识。

另外，我编写这套教程是因为我在独立学习WebGL，所以教程中可能（非常可能）会有错误，所以还请风险自担。尽管如此，我还是会不断的修正bug和改正其中的错误的，所以如果你发现了教程中的错误，请告诉我。

有两种方法可以获得上面实例的代码：在实例的独立页面中选择“查看源代码”；或者点击这里下载我们为您准备好的压缩包。

在解释代码细节之前，让我们先来了解一下背景知识。到目前为止，我们一直将纹理用作一种蒙在物体表面的图像。我们指定一张图像，并为每个顶点设定纹理坐标（纹理坐标标明顶点应该对应到纹理图像中的哪个位置），然后在fragment shader中, 从sampler中取出对应位置的纹理颜色, 并将它作为当前像素的颜色输出。

高光贴图则将我们上面对纹理的认识扩展了一下。一张纹理有R,G, B, A四个通道，在shader中, 每个通道都是一个float类型的标量值, 但并没有限定我们一定要将纹理数据当做颜色来对待。上一课中，我们了了解到，材质的反光度（shinness）也是一个float类型标量值，所以我们可以通过纹理来将物体表面的反光度属性赋给每一个对应的像素，就像我们使用纹理为像素赋颜色一样。

这就是我们在本课中使用的技巧，我们将两张纹理同时传给用于绘制地球的fragment shader，一张用来指定颜色，如下图：

另一张低分辨率的用来指定反光度：

上图只是一张普通的GIF图片， 是我在PAINT.NET中通过修改颜色纹理生成的。在本课中，我们假设RGB通道的反光度都是相同的， 同时， 我们需要选择一种颜色作为完全不反光的标志（按照我们的设计，如果需要得到反光度32，则纹理中会存储深灰色（32，32，32）），这里我们指定纯白色为完全不反光。

好了，需要预先解释的都已经说清楚了，下面我们来看代码。本课代码与14课内容只有非常微小的改动，而最主要的部分在fragment shader中，所以在这里我们主要看一下shader部分的内容:

第一个不同是shader多了两个新的uniform常量用来标明是否使用颜色和高光纹理 （第17、18行）：

9
10
11
12
13
14
15
16
17
18
19

precision mediump float;

varying vec2 vTextureCoord;
varying vec3 vTransformedNormal;
varying vec4 vPosition;

uniform bool uUseColorMap;
uniform bool uUseSpecularMap;
uniform bool uUseLighting;

接下来，是两个sampler，用于代表两张纹理。这里将颜色纹理重命名为uColorMapSampler，同时，增加了一个sampler用来表示高光贴图（第27、28行）：

21
22
23
24
25
26
27
28

uniform vec3 uAmbientColor;

uniform vec3 uPointLightingLocation;
uniform vec3 uPointLightingSpecularColor;
uniform vec3 uPointLightingDiffuseColor;

uniform sampler2D uColorMapSampler;
uniform sampler2D uSpecularMapSampler;

下面，是常规的切换是否进行光照的代码，并计算法线和光照方向：

31
32
33
34
35
36
37

void main(void) {
vec3 lightWeighting;
if (!uUseLighting) {
lightWeighting = vec3(1.0, 1.0, 1.0);
} else {
vec3 lightDirection = normalize(uPointLightingLocation - vPosition.xyz);
vec3 normal = normalize(vTransformedNormal);

最后，就是实际处理高光贴图的代码。首先，我们定义了一个变量用来表示specular光照的权重；如果最终不应进行高光反射，则这个变量将被赋为0。

39

float specularLightWeighting = 0.0;

接下来，开始处理材质的反光度部分。在这里，如果用户选择不使用高光贴图，我们就会默认将反光度初始化为32，否则，我们会从高光贴图中取出对应的反光数值，就像从颜色纹理中取出颜色一样。因为我们假设RGB通道的反光度相同（这就是为什么本课中的高光贴图看起来是一张灰度图），因此在这里我们只取高光贴图纹理的R通道数据，实际上，你从任何一个通道读取，结果都是一样的。

40
41
42
43

float shininess = 32.0;
if (uUseSpecularMap) {
shininess = texture2D(uSpecularMapSampler, vec2(vTextureCoord.s, vTextureCoord.t)).r * 255.0;
}

现在，你需要根据取出的反光度来判断是否需要进行反光，还记得吗？前文中我们提到将纯白色作为不进行反光的标志，因此在这里，我们将只对小于255的反光度进行实际的高光计算。

44

if (shininess < 255.0) {

下面的代码和上一课中的高光计算基本相同，唯一的区别就是这里的反光度是从高光贴图中取出的，而不像之前那样从shader外部传入。

45
46
47
48
49

vec3 eyeDirection = normalize(-vPosition.xyz);
vec3 reflectionDirection = reflect(-lightDirection, normal);

specularLightWeighting = pow(max(dot(reflectionDirection, eyeDirection), 0.0), shininess);
}

最后，我们将所有类型的光照元素组合到一起，并使用这个结果作为物体颜色的权重。对于物体颜色，当用户选择使用颜色纹理，也就是uUseColorMap为true的时候，物体颜色是从颜色纹理中取出的，而当不使用颜色纹理时，我们默认认为物体颜色为纯白色。

51
52
53
54
55
56
57
58
59
60
61
62
63
64

float diffuseLightWeighting = max(dot(normal, lightDirection), 0.0);
lightWeighting = uAmbientColor
+ uPointLightingSpecularColor * specularLightWeighting
+ uPointLightingDiffuseColor * diffuseLightWeighting;
}

vec4 fragmentColor;
if (uUseColorMap) {
fragmentColor = texture2D(uColorMapSampler, vec2(vTextureCoord.s, vTextureCoord.t));
} else {
fragmentColor = vec4(1.0, 1.0, 1.0, 1.0);
}
gl_FragColor = vec4(fragmentColor.rgb * lightWeighting, fragmentColor.a);
}

当你阅读到这里，你应该已经理解了实现高光贴图所需要的全部知识。除此之外，代码中还有一些其它的改动，但这些细小变化并不值得去细细研究。initShaders函数中添加了用于传输新uniform常量的代码，initTextures则需要加载两张新的纹理，而前一课中加载teapot的代码则被替换为11课中的initBuffers函数，drawScene的内容由绘制茶壶变为了绘制一个球形，同时用户交互UI和animate函数也有对应本课的改动。

That’s it！这就是本课的全部内容。在这一课中，你了解了怎样通过纹理来提供材质反光度所需要的数据，同时，其实你也可以通过纹理来提供其它的表面细节信息，比如，人们经常会用纹理来提供物体表面的法线信息，这样，可以在保持较低顶点数量的前提下，为模型增加一些表面细节，在未来课程中，对这一部分内容我们会特别提到。

原文链接：http://www.hiwebgl.com/?p=1210 （本内容由互联网资料整理而成）

HTML5触屏版多线程渲染模板技术分享

前言：

了解js编译原理的屌丝们都知道，js是单线程的，想当年各路神仙为了实现js的多线程，为了解决innerHTML输出大段HTML卡页面的顽疾，纷纷设计了诸如假冒的“多线程“实现，我自己也在写开源框架KitJs时候，写过类似的组件http://www.cnblogs.com/xueduanyang/archive/2012/05/30/2526422.html ，其原理就是改造代码中的for为setInterval，改递归为尾递归等等，为可怜的刷新率60Hz争取17ms的微弱时间。

当然了，这些都不是真正的多线程。其实W3C很早就有关于纯前端真多线程实现的，就是http://www.w3.org/TR/workers/ ，一直以来打着HTML5的旗号，各大浏览器厂商都都有对应标准的worker实现，具体支持程度我们可以看http://caniuse.com/#feat=webworkers ，值得一提的是咱们的UC也支持WebWorker，这几年随着移动HTML5项目的兴起，手机多核的炒作，现在就连华强北这种屌丝手机都开始以双核为卖点了，而真正从APP或者Web为多线程做优化的项目又有多少开发者呢？

作为一个猥琐的先驱者，本着让用户爽，不爽不舒服斯基的原则，我们率先在触屏项目实现了多个多线程工作模型，本文介绍的多线程渲染模板就是其中的一个。

之前在春田花花群里面，曾今说过我们有个多线程渲染系统，遭到众多同学的反击，今天终于能给我一个机会，为大家展现我们的辛苦付出，欢迎大家看完之后热烈讨论。

架构设计：

上一篇介绍我们的调试系统的时候，我贴过一张咱触屏的架构图，

从图中可以看出，在我们MVVM层次的Modules中，有两个tpl引擎，一个是基本的tpl引擎（引入的是Mustache），被引用指向集成到我们的Common大模块里面了，还有一个是独立的多线程模板引擎（基于HTML5 WebWorker工作模式的双核渲染引擎Mustache+JTemplate）。

该模块名字叫做tplRender（有点土，起名一直是个技术活…），其他业务视图模块VeiwMddules只需要requires模块，通过tplRender.tpl方法渲染模板，默认就会采用多线程工作方式。

我们的多线程工作模板的工作方式见下图

首先，以Page当前页面为一个Application生命周期，在引入该模板的时候，会自动为当前页面初始化一个WebWorker对象，再加上本来的当前工作进程LocalMainThread，组成两个等待作业池

当业务代码执行到模板渲染时，会向tplRender模块买一张门票，门票上记录着将当前页面需要执行模板的相关配置信息（模板所在Dom Element，模板内容，渲染依赖JSON Data，作业总数）以及此次提交给tplRender自动生成的一个MissionId

tplRender模块在内部做协调调度，目前的调度算法实现还比较简单，用的按2取模，均匀分币多个模板渲染任务到Worker和Local（当前主线程）任务队列中，分别计算

没完成一个队列子任务，会默认触发一次回调，用于进度判断，会传递总作业任务剩余数，只有当总业务进度为0时候，表示任务全部完成

计算完毕，收回门票，撕毁门票，告诉主线程去innerHTML渲染页面走人。

这么做的好处是，

首先从原理上看，是将主线程需要同步计算的多个任务打散，分解一半到了异步Worker线程去计算，减轻了主线程的负担

我们都知道

在浏览器中，渲染线程和当前Js主线程是或的关系，但是Worker以及请求和渲染线程是可以并行的，所以使用了异步计算模式既可以计算完一小块即渲染，提高CPU的利用的率

由于是Worker异步计算，对于主线程的页面事件响应，Gif动画，js动画，完全不存在卡住僵死的问题

在浏览器刷新中，有个重要的原则是尽量做局部刷新，不要做整体刷新，刷新范围越小，速度越快，资源消耗越小，我们刚好切合这个原则

另外，可以最大限度的利用现代手机多核的优势，不浪费资源

那么实际效果呢？

我们测试过简单的10X单个1w次整数循环，对比的数据结果是单线程运算比多线程模式平均速度要慢上20%~30%，可以肯定的是，对于复杂的字符计算，正则，多重条件判断等耗时运算，使用多线程双工模式是一个不错的选择。

但是，这里要也要提的是，在简单模板运算上，一般集中在300ms左右的计算中，多线程工作效率要比单线程要低5%左右的差距

为什么会有这5%的差距呢，因为从前面的代码页可以看到，因为异步的加入，导致我们需要维护一系列临时状态去保证正常的执行队列，所以这部分消耗是正常的，另外，不要只看着这5%，要知道这是在牺牲用户触屏响应的前提上做的速度，而多线程方式虽然会慢一点，但是不会牺牲用户触屏的交互响应，也就是常说的不会卡死。

性能

之前群里面讨论，主要集中在对于Worker工作模式的怀疑，下图可以看到Worker工作模式的标准流程

Worker的主要消耗在于

1. 与主线程之间的通信，主要是通过PostMessage（发送），onMessage（接受），消息的回调都是异步的，且触发通信都是在本地进行，消耗在90~100ms之间，此部分消耗是异步的，非阻塞的，不影响当前线程，底层并行实现，且经过多次实验结果表明，与通信数据长度无明显关系，就是说稳定维持在这样的一个开销，单个的话，最小要等这么长时间的，多次通信回调的话，非线程增长，消耗反而会降低（因为worker一旦加载到本地内存中，其实就是本地线程间通信，其速度应该是非常快的，主要耗费在底层实现的调度上）

2. Worker初始化加载，Worker是通过new Worker(url)的方式加载的，一个页面一个worker进程，默认url走的是http请求，也就是说我们可以通过浏览器的Expires，过期头走浏览器缓存，或者可以想办法通过MainFest方式，走本地浏览器缓存

3. Worker自身的计算性能，这点通过PC和手机做了对比，发现一个很奇怪的现象，就是PC主线程的计算1w次循环的速度，一般要比worker中1w次循环的速度快5%~10%不等，这个地方估计要具体有机会看Webkit的实现源代码，才能知道原因，不过一般情况，普通的计算，Worker计算速度与主线程基本无差

多线程计算中的共享对象问题：

一般情况下，我们不会遇到这问题，但是一些特殊情况，比如需要在Worker进程中取得主线程的Window下一些变量等等

我们现在是通过一个全局对象$ENV传递给Worker和LocalThread，在模板里面，都可以访问到$ENV这个全局对象，只要在执行tplrender.tpl方法前，给这个$ENV对象赋值，既可以把值带到模板里面去计算

当然这只是简单的带对象进去，还有做过把对象从Worker中带出来，比如一些特殊需求，需要批量计算步长，瀑布流模式下的索引，埋点上报的ytag自增长等等，在Worker和LocalThread并行同步计算时候，Worker里面对于这个ytag的修改，对于LocalThread也需要有影响，那么就需要解决Worker与主线程的变量同步问题

我们目前采用的方式是单一任务步长变量同步（所谓单一任务，就是一次tplrender.tpl开始的记一次任务，一次任务里面根据需要渲染的元素多有少个，会拆分到不同小子任务，分布到Worker或者LocalThread中去，但是这些子任务一起算一个大的任务），其做法是

我们会在主线程，定义一个全局变量$ENV，在$ENV下挂一个step命名空间

在我们的业务代码里面，默认给每个页面起一个步长的变量ytag=1001

在模板代码里面使用自定义方法去递增步长

这时候，在当前页面的生命周期中，在worker进程和主进程，同一个tpl任务中，worker与主线程各自的ytag是各自自增长，当任务完成时候，会更新当前页面的$ENV.step.ytag到最新最大的值

完成ytag变量的自增长

那如何保证ytag自增长值不会重复呢？我们通过方法判断当前模板计算是在主线程还是在worker里面走不一样的增量即可实现

多线程计算中自定义方法共享：

我们做模板的意义，在于在MVVM框架中，将显示的逻辑从VM中最大限度的剥离出来，直接放于View中，最大限度的解放Modules（其只需要与后端通信，对于取回的数据不需要做过多的处理），因为在业务开发中，View是千变万化的一层，而后端一般不需要大动，所以我们将容易变化的逻辑，比如数据筛选，数据格式，循环显示等等逻辑统统放在了View层进行，我们也引入了Mustache+JTemplate双模板引擎解析的双工模式，去处理带有业务逻辑的模板代码，由于JTemplate的引入，使得我们的模板支持js语法，支持了js语法就意味着我们的一些业务逻辑，就可以抽象成模板方法去复用。

所以这就带来一个问题，我们是怎么复用我们的自定义模板方法的

首先我们实现了一个自定义模板方法对象，所有的符合我么业务要求的自定义模板内使用的js方法都放入这个对象中

第二，我们改造了JTemplate实现，在原有的基础上，修复了”带来的bug，且传入了tplFn这个对象供模板内js使用

第三，我们在Worker实现了最小版本的Require和define，用于适应我们现在r.js的打包工作，实现worker需要的方法自动打包合并

这样一来，在Worker和本地线程都有tplFn这个方法，也就都可以使用自定义方法了

为什么要整合支持Js语法的模板引擎：

其实前面已经说了，我们要将显示的业务逻辑，放到模板里去，简单的模板功能太少，不能发挥并行计算优势，另外对于显示逻辑前置化也是大事所趋，后端只需要关注大数据，对于显示逻辑安排，全部交给前端来做即可。

所以我们在采用Mustache语法系作为我们的基本模板引擎后，加入最小版本的js语法解析引擎jtemplate，在修复了众多bug之后，并入了我们的模板解析中。

为什么模板解析要用大括号开头的{%和{{：

因为{{是Mustache默认的符号，使用jtpl原来的<%，一个是好多HTML编辑器对于<开头会认为是HTML标记，导致显示不正确，因为我们目前和重构的合作模式是重构可以直接修改我们的HTML代码，使用{%，可以最大限度的还原原来重构的页面，及时用浏览器打开我们的模板，也可以直接看到页面

对于不支持多线程的如何处理：

首先我们代码里面有容错，和特诊判断，

其次，我们有是否使用Worker的开关，以及基本的Mustache引擎的tpl可以使用，不一定非是多线程渲染的方式

模板的预编译：

其实理论上这段和多线程没啥关系，既然说到了模板，那么就说说预编译吧，其实预编译没啥神奇的地方，无非就是把html模板转换成js相加的字符串功能，以提高eval这部分的性能，这个是一般模板引擎都会自带的功能，比如Mustache 或者jtpl的预编译功能，只是大多数同学现在研究在后端用nodejs跑这个预编译，出来的直接是js 而不是模板了。

所以说我们也支持这种预编译形式，由于worker的限制，不能传递引用类型的变量，但是在Worker内部空间里面，第一次执行后模板，被编译后，在页面的生命周期内是，是一直存在的，所以说第二次，第三次在渲染，使用的就是第一次编译后的模板，这个加速是存在的。

当然了脱离页面生命周期，这个预编译也就失效了，在js主线程中，由于http缓存策略的原因，这种预编译也还可以存在，但是真正对于真实需要大计算量的js计算，比如正则，条件判断，这种预编译起的效果值得考量，而多线程的工作模式正式为这种应用场景而生的。

后记：

后面我们会继续分享无线前端开发特有的创新，海量离线本地存储以及版本控制策略，敬请期待！模板技术只有结合本地存储，把一个在线WebApp，变成一个真正的离线WebApp，才是真正的价值所在。

posted on 2013-08-01 19:43 薛端阳 阅读(...) 评论() 编辑 收藏

原文链接：http://www.cnblogs.com/xueduanyang/p/3231014.html （本内容由互联网资料整理而成）

用CSS3新增的rem单位设定字体大小

本文节选自《CSS设计指南（第3版》。
第1章完全免费试读，电子书在线热卖中：http://www.ituring.com.cn/book/1111

以下内容节选自第4章。

CSS3新增了一个相对单位rem（root em，根em），这个单位引起了广泛关注。这个单位与em有什么区别呢？区别在于使用rem为元素设定字体大小时，仍然是相对大小，但相对的只是HTML根元素。这个单位可谓集相对大小和绝对大小的优点于一身，通过它既可以做到只修改根元素就成比例地调整所有字体大小，又可以避免字体大小逐层复合的连锁反应。目前，除了IE8及更早版本外，所有浏览器均已支持rem。对于不支持它的浏览器，应对方法也很简单，就是多写一个绝对单位的声明。这些浏览器会忽略用rem设定的字体大小。下面就是一个例子：

/*IE8及之前版本的IE浏览器使用14像素*/
p {font-size:14px; font-size:.875rem;}

在用户使用“查看 > 文字大小”菜单调整网页文本大小的时候，IE9及更早版本只能缩放以相对单位设定的文本（使用像素之类的绝对单位设定的文本无法缩放）。换句话说，使用rem单位在IE7和IE6中存在一个小小的副作用，那就是这些浏览器的用户必须使用“查看 > 缩放”来调整整个页面的大小。当然，这也算一个应该升级到现代浏览器的原因吧。

本文仅用于学习和交流目的，不代表图灵社区观点。非商业转载请注明作译者、出处，并保留本文的原始链接。

原文链接：http://www.ituring.com.cn/article/17969 （本内容由互联网资料整理而成）

用脚本画画？Photoshop脚本介绍

Automation is useful in the work of every designer. It saves precious time on repetitive tasks and helps us solve certain problems more quickly and easily.

You can automate your workflow in Photoshop with actions, which are pretty popular and which most of you already know about and use. Today, we’ll introduce you to an advanced automation technique: scripting. All you need for this is basic knowledge of JavaScript, which some of us Web designers already have.

I’ve known about Photoshop scripts for years but decided to really dive in a few months ago. I had avoided it because I thought it was the domain of smart math-minded programmers. I was wrong, and today I’ll show that, although it requires some basic programming skills, scripting isn’t hard to grasp.

But first, we have to answer the obvious question.

Why Do We Need Scripts?

Why should we would learn to script if Photoshop already has pretty nice actions? The answer is interactivity. When you use an action, you can’t really control how it behaves in different situations; it is like a videotape that just keeps playing again and again without any change.

A script is more dynamic; its behavior changes according to the parameters you input or the context of its application. Sounds useful, no?

Requirements

You don’t have to be an advanced programmer to be able to write scripts; I’m just a graphic designer, like most of you. But you should at least have a basic understanding of JavaScript and some experience with properties and methods to get the most out of this article.

If you are not familiar with JavaScript at all, fear not! There are plenty of places to learn the basics of programming. Codecademy, for example, has pretty neat interactive lessons.

I work in Adobe Photoshop CS5, but everything we’ll cover applies to newer versions, too; Adobe hasn’t made any major updates to its scripting API since CS5. I will refer to the latest version of the scripting documentation, though, which is CS6.

Getting Started

When you record actions in Photoshop, you set the order of the steps to achieve a certain result — that’s your algorithm. Then, you press “Record” and replicate them in Photoshop one by one. Scripting is similar, but instead of doing these steps in Photoshop, you write them down as lines of code. Most actions that you do in Photoshop have their own script equivalent as a function.

Let’s say you are creating an action that scales a document to 150% of its original size. You’d go through these steps:

1. Open Image → Image Size.

2. Enter 150% in width and height.

3. Hit “OK.”

The same process with a script would look like this:

1. Call the application: app

2. Target a document: activeDocument

3. Call the function to resize the image: resizeImage(width, height)

And the code would look like this:

app.activeDocument.resizeImage("150%", "150%");

Language

There are three ways to write scripts for Photoshop: using AppleScript on Mac, VBScript on Windows or JavaScript on either platform. I use the third because it is cross-platform and I already have some experience with it.

Tools

Adobe has its own utility for writing scripts, called ExtendedScript Toolkit.

The main window for Adobe’s ExtendedScript Toolkit. (View large version.)

The toolkit comes with Photoshop, and you can find it in the following folder:

- Mac OS X
/Applications/Utilities/Adobe Utilities CS6/ExtendScript Toolkit CS6/

- Windows
C:\Program Files\Adobe\Adobe Utilities - CS6\ExtendScript Toolkit CS6
(or Program Files (x86) for 64-bit machines)

The user interface of the ExtendedScript Toolkit is pretty straightforward. To start writing scripts, first select the target application in the drop-down menu. If Photoshop is running, then look for the green chain icon near the drop-down menu:

Now you can write something like this:

alert("Hello Photoshop!");

Press ? + R (or just hit the “Play” button in the toolbar) to run your script. ExtendedScript Toolkit should switch to Photoshop and show an alert box:

ExtendedScript Toolkit has some other neat features for debugging scripts, but this is enough for this article. You can learn more about how to use it by going to Help → JavaScript Tools Guide.

You can use any plain-text editor to write a script; just save it as a .jsx file. To run it, you’ll have to go to File → Scripts → Browse in Photoshop and select it. Alternatively, just open the script file with Photoshop. You can also add a line of code at the top of the script so that the file always opens in Photoshop:

#target photoshop

Save your scripts in the Photoshop/Presets/Scripts/ directory, and access them with File → Scripts. You can also set up a hotkey; just go to Edit → Keyboard Shortcuts, navigate to File → Scripts → [your script’s name], and set the shortcut you want.

ExtendedScript Toolkit can run and debug code from the integrated development environment, and it has an object model viewer built in, which is useful. So, I recommend using the toolkit to write your scripts. Unfortunately, the Mac version crashes sometimes, so keep that in mind.

Photoshop Object Model

To make writing scripts easier, you should understand how things relate to each other in Photoshop’s Document Object Model (DOM). Understanding it is not so hard if you look at Photoshop itself. The main object in Photoshop’s DOM is the application. In the application, we have a collection of documents that are currently open in Photoshop.

Each document contains elements — such as layers (called ArtLayers), groups of layers (LayerSets), channels, history states and so on — just like in a regular PSD document.

A simplified visualization of Photoshop’s DOM is below. A more detailed containment hierarchy can be found on page 12 of “Adobe Photoshop CS6 Scripting Guide” (PDF).

A simplified visualization of Photoshop’s DOM.

Each of these objects has its own properties and methods that you can work with. For example, to change the opacity of the selected layer in a document, you would go to Application → Document → Layer → Opacity and set the desired value. The code would look like this:

app.activeDocument.activeLayer.opacity = 50;

As you may have guessed, activeDocument and activeLayer determine the currently selected document and layer.

You can find descriptions of most objects and their properties and methods in “Adobe Photoshop CS6 JavaScript Scripting Reference” (PDF), or in ExtendedScript Toolkit by going to Help → Object Model Viewer.

Let’s see how this works in a real-world example. In this next section, we’ll write our own script based on an action.

Remastering The RotateMe Action As A Script

A few years ago at Christmas time, I had an idea for an action to help me draw snowflakes.

Drawing Snowflake 101

1. Draw one stem of the snowflake with a pattern.

2. Duplicate the stem, and rotate it a few degrees.

3. Repeat the second step until you have a full circle.

Duplicating and rotating each stem manually is tedious, so I came up with an action to automate it. The algorithm looks like this:

1. Duplicate the stem.

2. Rotate it by however many degrees you’ve chosen, using the Transform tool.

3. Duplicate the layer.

4. Use the “Repeat Transform” function.

5. Repeat steps 4 and 5 until you have a full circle.

Pretty neat. But the action had a disadvantage: You can set only a certain number of stems for the snowflake, according to the number of degrees you set in third step of the algorithm.

Back when I wasn’t familiar with scripting, I just made a few versions of the action, each of which produced a snowflake with a different number of stems.

Today, we will remaster this action as a dynamic script that takes your input on the number of stems. Let’s get started!

Algorithm

When you start writing a script, defining the algorithm first before digging into the code itself is always a good idea. In our case, the algorithm will work like this:

1. Ask the user to enter the number of stems.

2. Calculate the rotation angle.

3. Duplicate and rotate the layer by the number set in the first step.

Let’s start with saving the current or selected layer to a variable for further use:

// Save selected layer to variable:
var originalStem = app.activeDocument.activeLayer;

Note that in JavaScript, you can mark a line with double slashes (//) to make it a comment. Comments are used to describe parts of code for future reference and don’t affect the behavior of the script.

Let’s move on to our algorithm now.

1. Ask User for Input

We can take input from the user with the prompt(message, default value[, title]) function. This function shows a dialog box with the message and an input field that contains the default value. When the user hits “OK,” the function returns the inputted value; so, we have to save it to the variable to be able to be used.

// Ask user for input by showing prompt box and save inputted value to variable:
var stemsAmount = prompt("Processing \""+originalStem.name+"\"\nHow many stems do you need?", 12);

Note that I used originalStem.name in the message, so the dialog box will show the name of selected layer.

On Mac OS X, the first line of the message is in bold and functions as the title. So, our main message should be on the second line. To make a new line, type \n.

In Windows, you can specify a third argument in the function to set the title:

// Ask user for input by showing prompt box and save inputted value to variable:
var stemsAmount = prompt("How many stems do you need?", 12, "Processing "+originalStem.name);

If we run the code in Photoshop, it will show this dialog box:

When the user hits “OK,” the inputted value will be saved to the stemsAmount variable. If the user clicks “Cancel,” then the function will return a null value. We’ll use this later.

2. Calculate the Rotation Angle

To calculate the rotation angle, we have to divide 360 degrees (a full circle) by the number of stems:

// Calculate the rotation angle
var angle = 360 / stemsAmount;

3. Duplicate and Rotate

Now we have everything we need to make duplicates of our stem. To do this, we’ll use the for loop. It lets us repeatedly run lines of code as many times as we’d like. Our loop will look like this:

for(var i = 1; i < stemsAmount; i++){
// This code will run "stemAmount - 1" of times
};

Note that the first instance of an object in programming has the value of 0, but because our first layer is already on the canvas, we’re starting the loop from 1 instead.

To duplicate and rotate our layer, we will use the duplicate() and rotate(angle, AnchorPosition) methods: the number of layers to be rotated in angle multiplied by the indexed number of duplicates. AnchorPosition determines the point around which the layer will rotate. You can see this point when you use the Transform tool in Photoshop — it looks like a small circle with a crosshair. In scripting, it has only 9 specified values — i.e. the 9 positions of the anchor point:

In our case, it is the bottom center of the layer, BOTTOMCENTER. Photoshop uses a lot of other constants here and there in some of the functions, which you can find on page 197 of “Adobe Photoshop CS6 JavaScript Reference” (PDF).

So, our loop will look like this:

// Duplicate and rotate layers:
for(var i = 1; i < stemsAmount; i++){
// Duplicate original layer and save it to the variable
var newStem = originalStem.duplicate();

// Rotate new layer
newStem.rotate(angle * i, AnchorPosition.BOTTOMCENTER);
};

And the completed code will look like the following. You can try to run it.

// Save selected layer to variable:
var originalStem = app.activeDocument.activeLayer;

// Ask user for input by showing prompt box and save inputted value to variable:
var stemsAmount = prompt("Processing \""+originalStem.name+"\"\nHow many stems do you need?", 12);

// Calculate the rotation angle:
var angle = 360 / stemsAmount;

// Duplicate and rotate layers:
for(var i = 1; i < stemsAmount; i++){
// Duplicate original layer and save it to the variable
var newStem = originalStem.duplicate();

// Rotate new layer
newStem.rotate(angle * i, AnchorPosition.BOTTOMCENTER);
};

Final Touches

I’ll usually try to achieve the main goal with a script, and when everything works correctly, I’ll start to refine the code. In our case, we have to make sure that the user inputs a valid number in the prompt box — i.e. a positive integer, greater than one.

Also, to prevent Photoshop from going crazy, we will restrict the number of stems — let’s say, to 100. To do this, we will use a while loop to show the user an error message in the event of an invalid submission, and the prompt box will continue to be shown until the user enters a valid value or hits the “Cancel” button (remember that the prompt returns null if the user hits “Cancel”).

The new code looks like this:

// Save selected layer to variable:
var originalStem = app.activeDocument.activeLayer;

// Ask user for input by showing prompt box and save inputted value to variable:
var stemsAmount = prompt ("Processing \""+originalStem.name+"\"\nHow many stems do you need? (From 2 to 100)", 12);

// Check that user entered a valid number and, if invalid, show error message and ask for input again
while(isNaN(stemsAmount) || stemsAmount <= 0 || stemsAmount > 100){
// If user clicks "Cancel" button, then exit loop
if(stemsAmount == null) break;

// Show error message…
alert("Please enter number in range from 2 to 100");
// …and ask for input again
stemsAmount = prompt("Processing \""+originalStem.name+"\"\nHow many stems do you need? (From 2 to 100)", 12);
};

// Run the copying process
if(stemsAmount != null){
// Calculate the rotation angle
var angle = 360 / parseInt(stemsAmount);

// Duplicate and rotate layers:
for(var i = 1; i < stemsAmount; i++){
// Duplicate original layer and save it to the variable
var newStem = originalStem.duplicate();

// Rotate new layer
newStem.rotate(angle * i, AnchorPosition.BOTTOMCENTER);
};
};

As you may have noticed, we’re using the isNaN(value) function, which returns true if value is “not a number” and parseInt(value) to convert the value to an integer when we calculate the rotation angle.

The next thing we will do is manage the layers, renaming our new layers by adding an index to them. Also to make sure that we do not mess up document’s layers, let’s place our stems in a group.

Renaming the layers is not a hard task. We will just use the name property of the layer and add an index number to it:

// Add index to new layers
newStem.name = originalStem.name + " " + (i+1);

A group in Photoshop’s API is called a LayerSet and we can access all groups of the document by calling the LayerSets property. To add a new group to a document, we have to call the LayerSets’ method add():

// Create a group for stems
var stemsGroup = app.activeDocument.layerSets.add();
stemsGroup.name = originalStem.name + " ("+stemsAmount+" stems)";

Then, to add a layer to the group, we will use the move(relativeObject, ElementPlacement) function. Note that the move() function moves a layer in the layer stack, not on the canvas. (You can move a layer on the canvas with the translate(deltaX[, deltaY]) function.)

ElementPlacement is another constant, this one determining how we will place our layer relative to… well, relativeObject. In our case, we will use ElementPlacement.INSIDE to place the original layer inside a group:

// Place original layer in group
originalStem.move(stemsGroup, ElementPlacement.INSIDE);

We will place each new copy of the layer at the bottom of all layers in the group using ElementPlacement.PLACEATEND. The result is all of our layers arranged in ascending order, the first layer at the top and the last at the bottom:

// Place new layer inside stems group
newStem.move(stemsGroup, ElementPlacement.PLACEATEND);

You can read more about the ElementPlacement constant on page 202 of “Adobe Photoshop CS6 JavaScript Reference” (PDF).

Final Code

That’s it! RotateMe.jsx is done. Our final code looks like this:

// Save selected layer to variable:
var originalStem = app.activeDocument.activeLayer;

// Ask user for input by showing prompt box and save inputted value to variable: var stemsAmount = prompt ("Processing \""+originalStem.name+"\"\nHow many stems do you need? (From 2 to 100)", 12); // Check that user entered a valid number and, if invalid, show error message and ask for input again while(isNaN(stemsAmount) || stemsAmount <= 0 || stemsAmount > 100){ // If user clicks "Cancel" button, then exit loop if(stemsAmount == null) break; // Show error message… alert("Please enter number in range from 2 to 100"); // …and ask for input again stemsAmount = prompt("Processing \""+originalStem.name+"\"\nHow many stems do you need? (From 2 to 100)", 12); }; // Run the copying process if(stemsAmount != null){ // Calculate the rotation angle var angle = 360 / parseInt(stemsAmount); // Create a group for stems var stemsGroup = app.activeDocument.layerSets.add(); stemsGroup.name = originalStem.name + " ("+stemsAmount+" stems)"; // Place original layer in group originalStem.move(stemsGroup, ElementPlacement.INSIDE); // Duplicate and rotate layers: for(var i = 1; i < stemsAmount; i++){ // Duplicate original layer and save it to the variable var newStem = originalStem.duplicate(); // Rotate new layer newStem.rotate(angle * i, AnchorPosition.BOTTOMCENTER); // Add index to new layers newStem.name = originalStem.name + " " + (i+1); // Place new layer inside stems group newStem.move(stemsGroup, ElementPlacement.PLACEATEND); }; // Add index to the original layer originalStem.name += " 1"; };

That wasn’t too hard, was it?

Now you can put it in the Photoshop/Presets/Scripts/ folder and run it by going to File → Scripts in Photoshop. Using different shapes with different values can yield interesting results:

- Download the RotateMe.jsx source file.

Conclusion

As you can see from the number of links in the resources section below, there’s much more to say about scripting than can fit in an introductory article. But I hope the little that we’ve described today piques your interest and shows how powerful and helpful scripting is.

If you decide to dive into it, let’s learn together and share our experience. Ask your questions and share what you’ve done in the comments. If you are not a coder, consider leaving an idea for a script; maybe another reader will make it happen.

Let’s make Photoshop more useful together!

Resources

I’m still learning about Photoshop scripts, too, and here are some resources that are helping me along the way:

- “Adobe Photoshop Scripting,” Adobe Developer Connection
All of the documentation and utilities for scripting.

- “Adobe Introduction to Scripting” (PDF), Adobe
Here are the basics on scripting for Adobe applications. The nice thing about scripting for Photoshop is that you can apply your knowledge to other Adobe products; you just need to learn the application’s DOM, and you’ll be ready to go.

- “Adobe Photoshop CS6 Scripting Guide” (PDF), Adobe
In this introductory guide to scripting for Photoshop, you’ll find the basics on getting started with scripting.

- “Photoshop CS6 JavaScript Reference” (PDF), Adobe
This describes all of the objects and their functions and methods that you can use in scripting for Photoshop. This is one of the documents I use most when writing scripts.

- “JavaScript,” Mozilla Developer Network
Here are answers to all kinds of questions about general JavaScript functions and usage.

- “JavaScript Tools Guide” (PDF), Adobe
This has basic information about ExtendedScript Toolkit and some advanced techniques, such as file system access and ScriptUI and working with XML, sockets and more.

- PS-Scripts
An independent forum about scripting for Photoshop. I haven’t signed up to participate in discussions, but it has plenty of answered questions and solved problems to discover.

- Photoshop Scripting, Adobe Community
Adobe’s official forum for Photoshop scripting has some good discussion on problems encountered by users.

(al) (ea)

原文链接：http://www.smashingmagazine.com/2013/07/25/introduction-to-photoshop-scripting/ （本内容由互联网资料整理而成）

面向“接口”编程和面向“实现”编程

如果你已经读了我的前几篇关于面向对象范式因为受到Rust and Go等语言的影响而发生变化的文章，看到了我正在研究的Rust设计模式，你会发现我对Rust语言十分的偏爱。

除此之外，就在上周末，我读完了经典的《设计模式：可复用面向对象软件的基础》。这些种种，引起了我对这本书中谈及的一个核心原则的思考：

面向‘接口’编程，而不是面向‘实现’。

这是什么意思？

首先我们需要理解什么是‘接口’，什么是‘实现’。简言之，一个接口就是我们要调用的一系列方法的集合，有对象将会响应这些方法调用。

一个实现就是为接口存放代码和逻辑的地方。

本质上讲，这个原则倡导的是，当我们写一个函数或一个方法时，我们应该引用相应的接口，而不是具体的实现类。

面向‘实现’编程

首先我们看看，如果不遵循这个原则会发生什么。

假设你是《华氏451度》这本书里的“Montag”这个人。大家都知道，书在华氏451度会烧着的。小说中的消防队员只要看到了书就会把它们丢到火里。我们用面向对象的视角说问题，书有一个叫做burn()的方法。

书并不是唯一会燃烧的东西。假设我们还有另外一个东西，比如木头，它也有一个方法叫做burn()。我们用Rust语言来写这段代码，看看在不是面向‘接口’编程的情况下它们是如何燃烧的。

struct Book {
title: @str,
author: @str,
}

struct Log {
wood_type: @str,
}

很直接。我们创建了两个结构体来表示一本书(Book)和一个木头(Log)。下面我们为结构体实现它们的方法：

impl Log {
fn burn(&self) {
println(fmt!("The %s log is burning!", self.wood_type));
}
}

impl Book {
fn burn(&self) {
println(fmt!("The book %s by %s is burning!", self.title, self.author));
}
}

现在Log 和 Book 都有了 burn() 方法，让我们把它们放到火上。

我们首先把木头放到火上：

fn start_fire(lg: Log) {
lg.burn();
}

fn main() {
let lg = Log {
wood_type: @"Oak",
length: 1,
};

// Burn the oak log!
start_fire(lg);
}

非常顺利，我们得到了输出 “The Oak log is burning!”.

现在，因为我们已经写了一个 start_fire 函数，是否我们可以把书也传进去，因为它们都有 burn()。让我们试一下：

fn main() {
let book = Book {
title: @"The Brothers Karamazov",
author: @"Fyodor Dostoevsky",
};

// Let's try to burn the book...
start_fire(book);
}

可行吗？不行。出现了下面的错误：

mismatched types: expected Log but found Book (expected struct Log but
found struct Book)

说的非常清楚，因为我们写出的函数需要的是一个Log结构体，而不是我们传进去的 Book 结构体。如何解决这个问题？我们可以再写一个这样的方法，把参数改成Book结构体。然而，这并不是一个好的方案。我在两个地方有了两个几乎一样的函数，如果一个修改，我们需要记得修改另外一个。

现在让我们看看面向‘接口’编程如何能解决这个问题。

面向接口编程

我们仍然使用前面的结构体，但这次我们加一个接口。在Rust语言里，接口叫做traits：

struct Book {
title: @str,
author: @str,
}

struct Log {
wood_type: @str,
}

trait Burnable {
fn burn(&self);
}

现在，除了两个结构体外，我们又多了一个叫做Burnable的接口。它的定义里只有一个叫做burn()的方法。我们来为每个结构体实现它们的接口：

impl Burnable for Log {
fn burn(&self) {
println(fmt!("The %s log is burning!", self.wood_type));
}
}

impl Burnable for Book {
fn burn(&self) {
println(fmt!("The book \"%s\" by %s is burning!", self.title, self.author));
}
}

看起来并没有多大的变化。这就是面向接口编程的强大之处：

fn start_fire<T: Burnable>(item: T) {
item.burn();
}

不仅仅只能接收一个Book对象或Log对象做参数，我们可以往里面传入任何实现了 Burnable 接口的类型(我们叫它类型T)。这使得我们的主函数可以写成这样：

fn main() {
let lg = Log {
wood_type: @"Oak",
};

let book = Book {
title: @"The Brothers Karamazov",
author: @"Fyodor Dostoevsky",
};

// Burn the oak log!
start_fire(lg);

// Burn the book!
start_fire(book);
}

正如期望的，我们得到了下面的输出：

The Oak log is burning!

The book “The Brothers Karamazov” by Fyodor Dostoevsky is burning!

这跟我们期望的完全一致。

结论

遵循“面向‘接口’编程”原则，我们可以写出一个函数，使其能完全能复用任何实现了Burnable接口的对象。因为很多的程序员都是按小时收费的，我们写出越多可复用的代码，用于维护它们的时间就会越少，也就是更好。

因此，这是一个非常强大的编程思想。

并不是什么时候都可以面向接口编程的，但遵循这种原则会让你更容易的写出可复用的更优雅的代码。接口提供了非常优秀的抽象归纳，让我们的开发工作变得容易很多。

原文链接：http://www.oschina.net/news/42735/program-to-an-interface-fool （本内容由互联网资料整理而成）

并发的错觉

在计算机发展初期，CPU的计算能力非常有限，计算资源稀缺而昂贵。最早的时候一个CPU只能同时运行一个任务，这简直让人无法忍受。什么叫做只能运行一个程序呢？这就像大学上自习占座一样，一旦一本书、一张纸、一个包或一个活人占有了那个桌子，其他人就再也没法用了，无论是这个人出去上厕所，踢球，你都不能去用那个座位，如果你贼胆包天敢偷着去坐，这时候就会有个神秘人突然拍拍你的肩膀告诉你「童靴，这里有人」，这就是常说的「见鬼的故事」。故事里的座位就是CPU，无论当前任务在使用CPU进行计算，还是在读写磁盘IO或进行网络交互，丫都得占着CPU，黑客极客和各种无证程序员们觉得，这，不，科，学！

于是大家试图通过各种方式来改变这一现象。首先出场的是多通道程序，程序员们很快写了一个监控程序，发现当前任务不用CPU计算时，就唤醒其他等待CPU资源的程序，让CPU资源能够得到充分利用。但多通道的问题是调度乏力，不分青红皂白和轻重缓急，不管是急诊还是普通门诊，该等都得等。

第二出场的是分时系统，分时系统是一种协作模式，每个程序运行一小段时间都得主动把CPU让出来给其他程序，这样每个程序都有机会用到CPU一小段时间。这时操作系统的监控程序也完善了一些，能够处理相对复杂的请求。早期的Windows和Mac OS（注意没有X）都是采用这种方式来调度程序的。分时系统的问题是，一旦某个程序死循环，系统就没招了，只能干等着，就像死机了一模一样，程序员们说，这是不可接受的！

第三个隆重登场的是多任务系统，程序员们让操作系统接管了所有的硬件资源，变得更加高级智能，系统进程开始分级，有的是特权级别，有的是平民级别（你就知道，在计算机世界都特么是这个样子！），所有的应用程序以进程和线程方式运行，CPU的分配方式采用了抢占式，就是说操作系统可以强制把CPU的资源分配给目前最需要的程序。程序员们成功了，几乎完美的控制了一切，并造成了很多任务都在同时运行的假象，如果用两个字来形容的话，那就是「和谐」！目前OS X、Unix、Linux、Windows都是采用这种方式进行任务管理的。

以上都是单核单CPU的情况，但无论线程间的切换多么快，这些都是并发，而不是并行。

好吧，中间插播一段并发和并行的区别。并发的英文单词是Concurrency，并行是Parallelism。如果一个系统支持两个或多个动作（Action）同时存在，那就是一个并发系统。如果一个系统支持两个或多个动作同时执行，那就是一个并行系统。也就是说，单个CPU永远无法同时执行两个或以上的任务，但是允许任务同时存在。所以，只有多核或多个CPU才可能发生并行，如果单核单CPU只能发生并发行为。

如果有人以为单核单CPU的并发就是同时执行很多任务，那么这是个错觉。

不知道解释清楚了木有。插播完毕！

终于，多核CPU和分布式系统被干出来了，一台计算机可以拥有多颗CPU，每颗CPU可以有多核，同时，成千上万台的机器被连接在一起进行计算，大家一看都晕了，史称「云计算」。随着硬件的变化，软件技术同时开始革新，各种语言开始支持并行计算，比如Erlang/Scala的Actor&Message模型，Go语言的goroutine机制，Java的ForkJoinPool，Objective-C的Grand Central Dispatch技术，当然还有Hadoop等分布式框架。

总之，到了目前这个阶段，无论是并发，还是并行，计算机和CPU都算是解放了，它们不仅在单台机器上可以执行并行计算，在横向扩展上也变得随心所欲，各种云平台应运而生，公有云私有云混合云balabala，反正是比较晕……

人脑就比较惨了，在电脑突飞猛进的这几十年里，几乎没有任何进展，脑袋仍然只有一个，也没有裂变出多核……

上文书谈了电脑的并发和并行的事情，有读者反馈，像一个有趣的教科书！我特么最烦教科书，就因为教科书，哥写了几年程序才把这些事捋清楚，为了让你们不再重蹈覆辙，我容易吗我！

好，下面我们谈一下人脑的并发，先看一个读者反馈，你们感受一下：

看过一个关于人脑的理论，说不清是并发还是异步。有时候我们很努力的想一个问题，但却怎么也想不起来，于是我们放弃了。但是大脑并没有放弃，此时它会自动起一个gorouting，继续再大脑的各个角落去寻找这个记忆碎片，当找到时执行回调告诉你。而此时你可能正边洗澡边哼着小曲儿！

关于人脑的机制，其复杂程度超过CPU何止万倍，比如上古奇人周伯通郭靖小龙女的双手互搏到底是并发还是并行呢，Mac君万不敢断言，未来还是让研究人脑图谱的人去探索其真正的奥秘吧。我们在这篇文章中把「一心二用」或「一心多用」统称为人脑的并发。

俗话说「一心不能二用」，这句话常常送给那些做事三心二意的人，但是我们真的不能一心多用吗？或者说并发带给我们的到底是效率的提升还是状态的下降？关于这件事Mac君的看法是，不可一概而论。「好吧，那位同学请把砖头继续放入怀中，我们还没有讲完」。

关于人脑的多任务处理，应该从个人特点、所处环境和任务特性来考虑。

其实人脑天生就是用来处理多任务的，比如你可以一边洗澡一边唱歌，一边看电影一边磕瓜子还要注意不要被飞来的砖头砸到等等，不过这样的多任务都是在放松环境下的简单任务，对我们提升效率没什么意义。

但是，当我们在健身房跑步时听英语，写文章或编码的时候听歌（所有不让听音乐编程的公司都将死于心碎），坐地铁的时候阅读，步行的时候思考，这就变得非常有意义的，因为我们在一个相对宽松的环境下把复杂的逻辑任务和简单的机械任务结合在一起，既不影响A，也不会干扰B，这种情况是我们优先要采取的并发策略。

类似的事情，比如开车时听英语，就要因人而异了。我有近10年的驾驶经验，喜欢开车，驾驶基本上已经形成下意识的动作，从出发到目的地往往不会记得自己做了哪些操作，所以我经常开车时听英语并有所收获。但有些人开车仅仅是驾驶已经够紧张忙乱了，倒一次车能车头入绝不车尾进，开次长途出的汗够洗澡的，那么就专心开车好了，车内最好保持安静或听一些舒缓的音乐。

我曾经看过一本叫做《错觉》的书，书中有一段描述了一位机长在飞机飞行的过程中发现机上设备出了点小故障，于是他和副机长一起排查，接着又找来机械师，哥三忙的不亦乐乎，过了一段时间，有人问，谁在开飞机呢？这时飞机无人驾驶已经很久了，等反应过来之后，飞机已经开始俯冲坠地，机上人员全部罹难！这种空难并不是意外，一架状况良好的飞机直接撞向地面不是偶尔发生，这种现象在航空领域被称作「可控飞行撞地」，其根本原因就是，人们太相信自己的多任务处理能力！

驾车虽然比驾驶飞机简单多了，但同样是一项非常危险的工作，所以我建议大家，听听音乐就好，另外千万别玩手机。

还有一种情况就是，在同一时间做两项或多项复杂任务，比如你让程序员在编码的同时帮助别人解决问题，能不能做好？也许有人可以，但我的感觉是，这种安排效率反而会打折扣。 人们在很多时候会低估自己的能力，但在更多时候会高估自己。在复杂任务并发处理的时候，人脑往往会高估自己的处理能力，以为可以，其实任务的并行，上下文的切换，注意力的分散，都会让你的效率大打折扣，所以设计模式中的职责单一原则不是盖的，一个类尽可能只做一件事情，无论是效率还是后期维护都会好很多，人脑其实也是一样。

总结一下： 1、简单任务的并发是大脑天生的nature，每个人都不在不自觉的应用。 2、在宽松的环境中让简单机械的任务和复杂有机的任务并行完成是非常不错的做法，提高效率节省时间。 3、在高危环境中（驾驶、高空作业等等）我们应该专心致志的只做当前的工作。 4、对于复杂任务，我们最好一件一件完成，即使有些人能够同时处理多重任务，那也需要长期的艰苦训练，比如郭靖君，你能否做到，就得看有没有周伯通那样的大哥！

今天给大家介绍一个命令来隐藏文件和文件夹，比如你不想在桌面上看到那么多文件和图标，或者不想让别人看到，只需要打开终端输入如下命令：

chflags hidden ~/Desktop/*

想恢复原状：

chflags nohidden ~/Desktop/*

文章内容来自微信公众账号：MacTalk · 微信号：sagacity-mac · 回复 m 可以查阅所有历史文章和Mac Tips · Web站点：macshuo.com

如果希望持续听MacTalk，点击{阅读原文}对作者进行小额赞助，想捐助美刀的，去macshuo.com看看

原文链接：http://macshuo.com/?p=614 （本内容由互联网资料整理而成）

JavaScript instanceof 运算符深入剖析

instanceof 运算符简介

在 JavaScript 中，判断一个变量的类型尝尝会用 typeof 运算符，在使用 typeof 运算符时采用引用类型存储值会出现一个问题，无论引用的是什么类型的对象，它都返回 "object"。ECMAScript 引入了另一个 Java 运算符 instanceof 来解决这个问题。instanceof 运算符与 typeof 运算符相似，用于识别正在处理的对象的类型。与 typeof 方法不同的是，instanceof 方法要求开发者明确地确认对象为某特定类型。例如：

清单 1. instanceof 示例

var oStringObject = new String("hello world");
console.log(oStringObject instanceof String);     // 输出 "true"

这段代码问的是“变量 oStringObject 是否为 String 对象的实例？”oStringObject 的确是 String 对象的实例，因此结果是"true"。尽管不像 typeof 方法那样灵活，但是在 typeof 方法返回 "object" 的情况下，instanceof 方法还是很有用的。

instanceof 运算符的常规用法

通常来讲，使用 instanceof 就是判断一个实例是否属于某种类型。例如：

清单 2. instanceof 常规用法

// 判断 foo 是否是 Foo 类的实例
function Foo(){}
var foo = new Foo();
console.log(foo instanceof Foo)//true

另外，更重的一点是 instanceof 可以在继承关系中用来判断一个实例是否属于它的父类型。例如：

清单 3. instanceof 在继承中关系中的用法

// 判断 foo 是否是 Foo 类的实例 , 并且是否是其父类型的实例
function Aoo(){}
function Foo(){}
Foo.prototype = new Aoo();//JavaScript 原型继承

var foo = new Foo();
console.log(foo instanceof Foo)//true
console.log(foo instanceof Aoo)//true

上面的代码中是判断了一层继承关系中的父类，在多层继承关系中，instanceof 运算符同样适用。

你真的了解 instanceof 操作符吗？

看了上面的代码示例，是不是觉得 instanceof 操作符很简单，下面来看点复杂的用法。

清单 4. instanceof 复杂用法

console.log(Object instanceof Object);//true
console.log(Function instanceof Function);//true
console.log(Number instanceof Number);//false
console.log(String instanceof String);//false

console.log(Function instanceof Object);//true

console.log(Foo instanceof Function);//true
console.log(Foo instanceof Foo);//false

看了上面的代码是不是又晕头转向了？为什么 Object 和 Function instanceof 自己等于 true，而其他类 instanceof 自己却又不等于 true 呢？如何解释？要想从根本上了解 instanceof 的奥秘，需要从两个方面着手：1，语言规范中是如何定义这个运算符的。2，JavaScript 原型继承机制。

回页首

详细剖析 ECMAScript-262 edition 3 中 instanceof 运算符的定义

语言规范对中 instanceof 运算符的定义如下：

清单 5. 规范中 instanceof 运算符定义

11.8.6 The instanceof operator
The production RelationalExpression:
RelationalExpression instanceof ShiftExpression is evaluated as follows:

1. Evaluate RelationalExpression.
2. Call GetValue(Result(1)).// 调用 GetValue 方法得到 Result(1) 的值，设为 Result(2)
3. Evaluate ShiftExpression.
4. Call GetValue(Result(3)).// 同理，这里设为 Result(4)
5. If Result(4) is not an object, throw a TypeError exception.// 如果 Result(4) 不是 object，
//抛出异常
/* 如果 Result(4) 没有 [[HasInstance]] 方法，抛出异常。规范中的所有 [[...]] 方法或者属性都是内部的，
在 JavaScript 中不能直接使用。并且规范中说明，只有 Function 对象实现了 [[HasInstance]] 方法。
所以这里可以简单的理解为：如果 Result(4) 不是 Function 对象，抛出异常 */
6. If Result(4) does not have a [[HasInstance]] method,
throw a TypeError exception.
// 相当于这样调用：Result(4).[[HasInstance]](Result(2))
7. Call the [[HasInstance]] method of Result(4) with parameter Result(2).
8. Return Result(7).

// 相关的 HasInstance 方法定义
15.3.5.3 [[HasInstance]] (V)
Assume F is a Function object.// 这里 F 就是上面的 Result(4)，V 是 Result(2)
When the [[HasInstance]] method of F is called with value V,
the following steps are taken:
1. If V is not an object, return false.// 如果 V 不是 object，直接返回 false
2. Call the [[Get]] method of F with property name "prototype".// 用 [[Get]] 方法取
// F 的 prototype 属性
3. Let O be Result(2).//O = F.[[Get]]("prototype")
4. If O is not an object, throw a TypeError exception.
5. Let V be the value of the [[Prototype]] property of V.//V = V.[[Prototype]]
6. If V is null, return false.
// 这里是关键，如果 O 和 V 引用的是同一个对象，则返回 true；否则，到 Step 8 返回 Step 5 继续循环
7. If O and V refer to the same object or if they refer to objects
joined to each other (section 13.1.2), return true.
8. Go to step 5.

上面的规范定义很晦涩，而且看起来比较复杂，涉及到很多概念，但把这段规范翻译成 JavaScript 代码却很简单，如下：

清单 6. JavaScript instanceof 运算符代码

function instance_of(L, R) {//L 表示左表达式，R 表示右表达式
var O = R.prototype;// 取 R 的显示原型
L = L.__proto__;// 取 L 的隐式原型
while (true) {
if (L === null)
return false;
if (O === L)// 这里重点：当 O 严格等于 L 时，返回 true
return true;
L = L.__proto__;
}
}

回页首

JavaScript 原型继承机制

由于本文主要集中在剖析 JavaScript instanceof 运算符，所以对于 JavaScript 的原型继承机制不再做详细的讲解，下面参考来自 http://www.mollypages.org/misc/js.mp 的一张图片，此图片详细的描述了 JavaScript 各种对象的显示和隐式原型链结构。

由其本文涉及显示原型和隐式原型，所以下面对这两个概念作一下简单说明。在 JavaScript 原型继承结构里面，规范中用 [[Prototype]] 表示对象隐式的原型，在 JavaScript 中用 __proto__ 表示，并且在 Firefox 和 Chrome 浏览器中是可以访问得到这个属性的，但是 IE 下不行。所有 JavaScript 对象都有 __proto__ 属性，但只有 Object.prototype.__proto__ 为 null，前提是没有在 Firefox 或者 Chrome 下修改过这个属性。这个属性指向它的原型对象。 至于显示的原型，在 JavaScript 里用 prototype 属性表示，这个是 JavaScript 原型继承的基础知识，在这里就不在叙述了。

图 1. JavaScript 原型链

回页首

讲解 instanceof 复杂用法

有了上面 instanceof 运算符的 JavaScript 代码和原型继承图，再来理解 instanceof 运算符将易如反掌。下面将详细讲解 Object instanceof Object，Function instanceof Function 和 Foo instanceof Foo 三个示例，其它示例读者可自行推演。

清单 7. Object instanceof Object

// 为了方便表述，首先区分左侧表达式和右侧表达式
ObjectL = Object, ObjectR = Object;
// 下面根据规范逐步推演
O = ObjectR.prototype = Object.prototype
L = ObjectL.__proto__ = Function.prototype
// 第一次判断
O != L
// 循环查找 L 是否还有 __proto__
L = Function.prototype.__proto__ = Object.prototype
// 第二次判断
O == L
// 返回 true

清单 8. Function instanceof Function

// 为了方便表述，首先区分左侧表达式和右侧表达式
FunctionL = Function, FunctionR = Function;
// 下面根据规范逐步推演
O = FunctionR.prototype = Function.prototype
L = FunctionL.__proto__ = Function.prototype
// 第一次判断
O == L
// 返回 true

清单 9. Foo instanceof Foo

// 为了方便表述，首先区分左侧表达式和右侧表达式
FooL = Foo, FooR = Foo;
// 下面根据规范逐步推演
O = FooR.prototype = Foo.prototype
L = FooL.__proto__ = Function.prototype
// 第一次判断
O != L
// 循环再次查找 L 是否还有 __proto__
L = Function.prototype.__proto__ = Object.prototype
// 第二次判断
O != L
// 再次循环查找 L 是否还有 __proto__
L = Object.prototype.__proto__ = null
// 第三次判断
L == null
// 返回 false

回页首

简析 instanceof 在 Dojo 继承机制中的应用

在 JavaScript 中，是没有多重继承这个概念的，就像 Java 一样。但在 Dojo 中使用 declare 声明类时，是允许继承自多个类的。下面以 Dojo 1.6.1 为例。

清单 10. Dojo 中多重继承

dojo.declare("Aoo",null,{});
dojo.declare("Boo",null,{});
dojo.declare("Foo",[Aoo,Boo],{});

var foo = new Foo();
console.log(foo instanceof Aoo);//true
console.log(foo instanceof Boo);//false

console.log(foo.isInstanceOf(Aoo));//true
console.log(foo.isInstanceOf(Boo));//true

上面的示例中，Foo 同时继承自 Aoo 和 Boo，但当使用 instanceof 运算符来检查 foo 是否是 Boo 的实例时，返回的是 false。实际上，在 Dojo 的内部，Foo 仍然只继承自 Aoo，而通过 mixin 机制把 Boo 类中的方法和属性拷贝到 Foo 中，所以当用 instanceof 运算符来检查是否是 Boo 的实例时，会返回 false。所以 Dojo 为每个类的实例添加了一个新的方法叫 isInstanceOf，用这个方法来检查多重继承。

结束语

本文详细介绍了 JavaScript 语言中 instanceof 运算符，并且结合语言规范深入剖析了此操作符的算法。对读者使用 JavaScript 编写复杂的面向对象程序会有很大的帮助。本文所有代码在 Firefox 15 下通过测试。

参考资料

- 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基，并与其他 developerWorks 用户交流。

关于作者

姜俊杰，IBM 中国软件开发中心的一名软件工程师，在 ECM 部门主要从事 IBM Case Manager 相关解决方案的开发。

关于报告滥用的帮助

报告滥用

关于报告滥用的帮助

报告滥用

IBM ID：
需要一个 IBM ID？
忘记 IBM ID？

密码：
忘记密码？
更改您的密码

保持登录。

单击提交则表示您同意developerWorks 的条款和条件。 使用条款

当您初次登录到 developerWorks 时，将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人，但您可以随时修改这些信息的显示状态。您的姓名（除非选择隐藏）和昵称将和您在 developerWorks 发布的内容一同显示。

当您初次登录到 developerWorks 时，将会为您创建一份概要信息，您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的，并且出于隐私保护的原因，不能是您的电子邮件地址。

昵称：

单击提交则表示您同意developerWorks 的条款和条件。 使用条款.

为本文评分

回页首

原文链接：http://www.ibm.com/developerworks/cn/web/1306_jiangjj_jsinstanceof/index.html （本内容由互联网资料整理而成）

漫谈js自定义事件、DOM/伪DOM自定义事件

by zhangxinxu from http://www.zhangxinxu.com
本文地址：http://www.zhangxinxu.com/wordpress/?p=2330

一、说明、引言

我JS还是比较薄弱的，本文的内容属于边学边想边折腾的碎碎念，可能没什么条理，可能有表述不准确的地方，可能内容比较拗口生僻。如果您时间紧迫，或者JS造诣已深，至此您就可以点击右侧广告（木有？则RSS或盗版）然后撤了。

事件是个大课题，真要从断奶开始讲起的话，可以写个12期的连载。关于JS事件的文章（类似DOM/BOM事件模型，IE与其他浏览器事件差异，DOM1/DOM2事件定义等）落叶般随处可见。熟豆子反复炒一点意思都没有，因此，这里谈谈自己感兴趣的自定义事件以及周边。

所谓自定义事件，就是有别于有别于带有浏览器特定行为的事件(类似click, mouseover, submit, keydown等事件)，事件名称可以随意定义，可以通过特定的方法进行添加，触发以及删除。

二、JS自定义事件

循序渐进便于接收。慢慢来~~

先看个简单的事件添加的例子：

element.addEventListener("click", function() {
// 我是临时工
});

这是个简单的为DOM元素分配事件处理函数的方法(IE 不支持)，有别于：

element.onclick = function() {
// 我是临时工
};

addEventListener()可以为元素分配多个处理函数（而非覆盖），因此，我们可以继续：

element.addEventListener("click", function() {
// 我是二代临时工
});

然后，当element被click(点击)的时候，就会连续触发“临时工”和“二代临时工”函数。

抽象→具象→本质→数据层
你有没有觉得这种行为表现有点类似于往长枪里面塞子弹(add)，（扣动扳手 – click）发射的时候按照塞进去的顺序依次出来。这种行为表现为我们实现自定义事件提供了思路：我们可以定义一个数组，当添加事件的时候，我们push进去这个事件处理函数；当我们执行的时候，从头遍历这个数组中的每个事件处理函数，并执行。

当多个事件以及对应数据处理函数添加后，我们最终会得到一个类似下面数据结构的对象：

_listener = {
"click": [func1, func2],
"custom": [func3],
"defined": [func4, func5, func6]
}

因此，如果我们脱离DOM, 纯碎在数据层面自定义事件的话，我们只要以构建、遍历和删除_listener对象为目的即可。

函数式实现
还是那句话，循序渐进，我们先看看函数式的实现（只展示骨干代码）：

var _listener = {};
var addEvent = function(type, fn) {
// 添加
};
var fireEvent = function(type) {
// 触发
};
var removeEvent = function(type, fn) {
// 删除
};

上面的代码虽然显得比较初级，但是目的亦可实现。例如：

addEvent("alert", function() {
alert("弹出！");
});

// 触发自定义alert事件
fireEvent("alert");

但是，函数式写法缺点显而易见，过多暴露在外的全局变量（全局变量是魔鬼），方法无级联等。这也是上面懒得显示完整代码的原因，略知即可。

字面量实现
众所周知，减少全局变量的方法之一就是使用全局变量（其他如闭包）。于是，我们稍作调整（代码较长，为限制篇幅，使用了滚动条，完整显示点击这里 – JS交互, RSS中无效果）：

var Event = {
_listeners: {},
// 添加
addEvent: function(type, fn) {
if (typeof this._listeners[type] === "undefined") {
this._listeners[type] = [];
}
if (typeof fn === "function") {
this._listeners[type].push(fn);
}
return this;
},
// 触发
fireEvent: function(type) {
var arrayEvent = this._listeners[type];
if (arrayEvent instanceof Array) {
for (var i=0, length=arrayEvent.length; i<length; i+=1) {
if (typeof arrayEvent[i] === "function") {
arrayEvent[i]({ type: type });
}
}
}
return this;
},
// 删除
removeEvent: function(type, fn) {
var arrayEvent = this._listeners[type];
if (typeof type === "string" && arrayEvent instanceof Array) {
if (typeof fn === "function") {
// 清除当前type类型事件下对应fn方法
for (var i=0, length=arrayEvent.length; i<length; i+=1){
if (arrayEvent[i] === fn){
this._listeners[type].splice(i, 1);
break;
}
}
} else {
// 如果仅仅参数type, 或参数fn邪魔外道，则所有type类型事件清除
delete this._listeners[type];
}
}
return this;
}
};

使用类似下面：

Event.addEvent("alert", function() {
alert("弹出！");
});

// 触发自定义alert事件
Event.fireEvent("alert");

您可以狠狠地点击这里：JS自定义事件字面量书写demo

默认页面document通过Event.addEvent()绑定了两个自定义的alert事件，因此，此时您点击页面的空白区域（非按钮与示例代码区域），就会有如下图所示的连续两个alert框：

demo页面还有两个按钮，用来清除已经绑定的alert事件。第一个按钮清除所有alert事件，而点击第二个按钮清除第一个alert事件。例如我们点击第二个按钮：

清除完毕后再点击页面的空白区域， 您会发现只会弹出“第二个弹出！”字样的弹出框了。这表明，第一个绑定自定义事件被remove掉了。

字面量实现虽然减少了全局变量，但是其属性方法等都是暴露而且都是唯一的，一旦某个关键属性(如_listeners)不小心在某事件处reset了下，则整个全局的自定义事件都会崩溃。因此，我们可以进一步改进，例如，使用原型链继承，让继承的属性(如_listeners)即使出问题也不会影响全局。

原型模式实现
代码如下（相比上面增加了addEvents, fireEvents, removeEvents多事件绑定、执行与删除方法，篇幅较长，增加滚动限高，点击这里完整展示 – JS交互, RSS中无效果）（一堆代码看得头大，建议直接跳过）：

var EventTarget = function() {
this._listener = {};
};

EventTarget.prototype = {
constructor: this,
addEvent: function(type, fn) {
if (typeof type === "string" && typeof fn === "function") {
if (typeof this._listener[type] === "undefined") {
this._listener[type] = [fn];
} else {
this._listener[type].push(fn);
}
}
return this;
},
addEvents: function(obj) {
obj = typeof obj === "object"? obj : {};
var type;
for (type in obj) {
if ( type && typeof obj[type] === "function") {
this.addEvent(type, obj[type]);
}
}
return this;
},
fireEvent: function(type) {
if (type && this._listener[type]) {
var events = {
type: type,
target: this
};

for (var length = this._listener[type].length, start=0; start<length; start+=1) {
this._listener[type][start].call(this, events);
}
}
return this;
},
fireEvents: function(array) {
if (array instanceof Array) {
for (var i=0, length = array.length; i<length; i+=1) {
this.fireEvent(array[i]);
}
}
return this;
},
removeEvent: function(type, key) {
var listeners = this._listener[type];
if (listeners instanceof Array) {
if (typeof key === "function") {
for (var i=0, length=listeners.length; i<length; i+=1){
if (listeners[i] === listener){
listeners.splice(i, 1);
break;
}
}
} else if (key instanceof Array) {
for (var lis=0, lenkey = key.length; lis<lenkey; lis+=1) {
this.removeEvent(type, key[lenkey]);
}
} else {
delete this._listener[type];
}
}
return this;
},
removeEvents: function(params) {
if (params instanceof Array) {
for (var i=0, length = params.length; i<length; i+=1) {
this.removeEvent(params[i]);
}
} else if (typeof params === "object") {
for (var type in params) {
this.removeEvent(type, params[type]);
}
}
return this;
}
};

啰哩吧嗦的代码直接跳过，其实上面代码跟字面量方法相比，就是增加了下面点东西：

var EventTarget = function() {
this._listener = {};
};

EventTarget.prototype = {
constructor: this,
// .. 完全就是字面量模式实现脚本
};

然后，需要实现自定义事件功能时候，先new构造下：

var myEvents = new EventTarget();
var yourEvents = new EventTarget();

这样，即使myEvents的事件容器_listener跛掉，也不会污染yourEvents中的自定义事件(_listener安然无恙)。

您可以狠狠地点击这里：原型模式下的JS自定义事件demo

从demo右半区域的源代码展示可以看出如何使用addEvents, fireEvents方法同时添加和触发多个自定义事件的。

//zxx: 下面为广告~~注意不要勿点~~嘻嘻~~

三、DOM自定义事件

我们平常所使用的事件基本都是与DOM元素相关的，例如点击按钮，文本输入等，这些为自带浏览器行为事件，而自定义事件与这些行为无关。例如：

element.addEventListener("alert", function() {
alert("弹出！");
});

这里的alert就属于自定义事件，后面的function就是自定义事件函数。而这个自定义事件是直接绑定在名为element的DOM元素上的，因此，这个称之为自定义DOM事件。

由于浏览器的差异，上面的addEventListener在IE浏览器下混不来(attachEvent代替)，因此，为了便于规模使用，我们需要新的添加事件方法名（合并addEventListener和attachEvent），例如addEvent, 并附带事件触发方法fireEvent, 删除事件方法removeEvent，(命名均参考自MooTools库)。

如何直接在DOM上扩展新的事件处理方法，以及执行自定义的事件呢？

如果不考虑IE6/7浏览器，我们可以直接在DOM上进行方法扩展。例如添加个addEvent方法：

HTMLElement.prototype.addEvent = function(type, fn, capture) {
var el = this;
if (window.addEventListener) {
el.addEventListener(type, function(e) {
fn.call(el, e);
}, capture);
} else if (window.attachEvent) {
el.attachEvent("on" + type, function(e) {
fn.call(el, e);
});
}
};

//zxx: 上面代码中的HTMLElement表示HTML元素。以一个<p>标签元素举例，其向上寻找原型对象用过会是这样：HTMLParagraphElement.prototype → HTMLElement.prototype → Element.prototype → Node.prototype → Object.prototype → null。这下您应该知道HTMLElement所处的位置了吧，上述代码HTMLElement直接换成Element也是可以的，但是会让其他元素（例如文本元素）也扩展addEvent方法，有些浪费了。

这样，我们就可以使用扩展的新方法给元素添加事件了，例如一个图片元素：

elImage.addEvent("click", function() {
alert("我是点击图片之后的弹出！");
});

由于IE6, IE7浏览器的DOM水平较低，无法直接进行扩展，因此，原型扩展的方法在这两个浏览器下是行不通的。要想让这两个浏览器也支持addEvent方法，只能是页面载入时候遍历所有DOM，然后每个都直接添加addEvent方法了。

var elAll = document.all, lenAll = elAll.length;
for (var iAll=0; iAll<lenAll; iAll+=1) {
elAll[iAll].addEvent = function(type, fn) {
var el = this;
el.attachEvent("on" + type, function(e) {
fn.call(el, e);
});
};
}

您可以狠狠地点击这里：基于DOM扩展自定义方法demo

点击demo页面张含韵小姐年轻时候相片，就会有该图片alt属性值。

测试代码如下(demo页面有代码完整展示)：

<img id="image" src="http://image.zhangxinxu.com/image/study/s/s256/mm1.jpg" alt="年轻的张含韵" />

document.getElementById("image").addEvent("click", function() {
alert("这是：" + this.alt);
});

只能点到为止
直接在DOM上进行事件方法扩展其实是个糟糕的做法，因此，这里我并没有对自定义事件做进一步深入探讨（这个下一部分会讲）。

基于DOM扩展缺点有：缺少标准无规律、提高冲突可能性、性能以及浏览器支持。
扩展名字任意命，很有可能就会与未来DOM浏览器本身支持的方法相互冲突；扩展无规律，很有可能出现A和B同名不同功能的扩展而造成冲突；IE6-7浏览器下所有扩展都要通过遍历支持，其性能开销可想而知；另外IE8对DOM扩展的支持并不完整，例如其支持Element.prototype，却没有HTMLElement.prototype.

虽然我从事的站点就是基于MooTools库的，但是，我对MooTools库基于DOM扩展方法的做法是不支持的。相反，我更亲近jQuery库的做法，也就是下面要讲的“伪DOM自定义事件”。

四、伪DOM自定义事件

这里的“伪DOM自定义事件”是自己定义的一个名词，用来区分DOM自定义事件的。例如jQuery库，其是基于包装器（一个包含DOM元素的中间层）扩展事件的，既与DOM相关，又不直接是DOM，因此，称之为“伪DOM自定义事件”。

//zxx: 下面即将展示的代码目的在于学习与认识，要想实际应用可能还需要在细节上做些调整。例如，下面测试的包装器仅仅只是包裹DOM元素，并非选择器之类；$符号未增加冲突处理，且几个重要方法都暴露在全局环境中，没有闭包保护等。

原型以及new函数构造不是本文重点，因此，下面这个仅展示：

var $ = function(el) {
return new _$(el);
};
var _$ = function(el) {
this.el = el;
};
_$.prototype = {
constructor: this,
addEvent: function() {
// ...
},
fireEvent: function() {
// ...
},
removeEvent: function() {
// ...
}
}

于是我们就可以使用类似$(dom).addEvent()的语法为元素添加事件了（包括不包含浏览器行为的自定义事件）。

自定义事件的添加
如果只考虑事件添加，我们的工作其实很简单，根据支持情况，addEventListener与attachEvent方法分别添加事件（attachEvent方法后添加事件先触发）即可：

addEvent: function(type, fn, capture) {
var el = this.el;
if (window.addEventListener) {
el.addEventListener(type, fn, capture);
} else if (window.attachEvent) {
el.attachEvent("on" + type, fn);
}
return this;
}

显然，事情不会这么简单，有句古话叫做“上山容易下山难”，自定义事件添加容易，但是如何触发它们呢？——考虑到自定义事件与浏览器行为无关，同时浏览器没有直接的触发事件的方法。

自定义事件的触发
又是不可避免的，由于浏览器兼容性问题，我们要分开说了，针对标准浏览器和IE6/7等考古浏览器。

1. 对于标准浏览器，其提供了可供元素触发的方法：element.dispatchEvent(). 不过，在使用该方法之前，我们还需要做其他两件事，及创建和初始化。因此，总结说来就是：

document.createEvent()
event.initEvent()
element.dispatchEvent()

举个板栗：

$(dom).addEvent("alert", function() {
alert("弹弹弹，弹走鱼尾纹~~");
});

// 创建
var evt = document.createEvent("HTMLEvents");
// 初始化
evt.initEvent("alert", false, false);

// 触发, 即弹出文字
dom.dispatchEvent(evt);

createEvent()方法返回新创建的Event对象，支持一个参数，表示事件类型，具体见下表：

参数

事件接口

初始化方法

HTMLEvents

HTMLEvent

initEvent()

MouseEvents

MouseEvent

initMouseEvent()

UIEvents

UIEvent

initUIEvent()

关于createEvent()方法我自己了解也不是很深入，不想滥竽充数，误人子弟，所以您有疑问我可能作答不了，希望对熟知该方法的人可以做进一步的解释说明（例如事件接口与document关系，UIEvent是什么东西等）。

initEvent()方法用于初始化通过DocumentEvent接口创建的Event的值。支持三个参数：initEvent(eventName, canBubble, preventDefault). 分别表示事件名称，是否可以冒泡，是否阻止事件的默认操作。

dispatchEvent()就是触发执行了，dom.dispatchEvent(eventObject), 参数eventObject表示事件对象，是createEvent()方法返回的创建的Event对象。

2. 对于IE浏览器，由于向下很多版本的浏览器都不支持document.createEvent()方法，因此我们需要另辟蹊径（据说IE有document.createEventObject()和event.fireEvent()方法，但是不支持自定义事件~~）。

IE浏览器有不少自给自足的东西，例如下面要说的这个"propertychange"事件，顾名思意，就是属性改变即触发的事件。例如文本框value值改变，或是元素id改变，或是绑定的事件改变等等。

我们可以利用这个IE私有的东西实现自定义事件的触发，大家可以先花几分钟想想……

// zxx: 假设几分钟已经过去了……

大家现在有思路了没？其实说穿了很简单，当我们添加自定义事件的时候，顺便给元素添加一个自定义属性即可。例如，我们添加自定义名为"alert"的自定义事件，顺便我们可以对元素做点小手脚：

dom.evtAlert = "2012-04-01";

再顺便把自定义事件fn塞到"propertychange"事件中：

dom.attachEvent("onpropertychange", function(e) {
if (e.propertyName == "evtAlert") {
fn.call(this);
}
});

这个，当我们需要触发自定义事件的时候，只要修改DOM上自定义的evtAlert属性的值即可：

dom.evtAlert = Math.random();    // 值变成随机数

此时就会触发dom上绑定的onpropertychange事件，又因为修改的属性名正好是"evtAlert", 于是自定义的fn就会被执行。这就是IE浏览器下事件触发实现的完整机制，应该说讲得还是蛮细的。

自定义事件的删除
与触发事件不同，事件删除，各个浏览器都提供了对于的时间删除方法，如removeEventListener和detachEvent。不过呢，对于IE浏览器，还要多删除一个事件，就是为了实现触发功能额外增加的onpropertychange事件：

dom.detachEvent("onpropertychange", evt);

大综合
结合上面所有论述与展示，我们可以得到类似下面的完整代码（为限制篇幅，滚动定高，想查看完整代码推荐去原demo，或是点击这里完整显示– js交互，RSS中无效果。）：

var $ = function(el) {
return new _$(el);
};
var _$ = function(el) {
this.el = (el && el.nodeType == 1)? el: document;
};
_$.prototype = {
constructor: this,
addEvent: function(type, fn, capture) {
var el = this.el;
if (window.addEventListener) {
el.addEventListener(type, fn, capture);
var ev = document.createEvent("HTMLEvents");
ev.initEvent(type, capture || false, false);

if (!el["ev" + type]) {
el["ev" + type] = ev;
}
} else if (window.attachEvent) {
el.attachEvent("on" + type, fn);
if (isNaN(el["cu" + type])) {
// 自定义属性
el["cu" + type] = 0;
}
var fnEv = function(event) {
if (event.propertyName == "cu" + type) { fn.call(el); }
};
el.attachEvent("onpropertychange", fnEv);
if (!el["ev" + type]) {
el["ev" + type] = [fnEv];
} else {
el["ev" + type].push(fnEv);
}
}
return this;
},
fireEvent: function(type) {
var el = this.el;
if (typeof type === "string") {
if (document.dispatchEvent) {
if (el["ev" + type]) {
el.dispatchEvent(el["ev" + type]);
}
} else if (document.attachEvent) {
el["cu" + type]++;
}
}
return this;
},
removeEvent: function(type, fn, capture) {
var el = this.el;
if (window.removeEventListener) {
el.removeEventListener(type, fn, capture || false);
} else if (document.attachEvent) {
el.detachEvent("on" + type, fn);
var arrEv = el["ev" + type];
if (arrEv instanceof Array) {
for (var i=0; i<arrEv.length; i+=1) {
el.detachEvent("onpropertychange", arrEv[i]);
}
}
}
return this;
}
};

您可以狠狠地点击这里：JS DOM自定义事件demo

demo页面中的的张含韵小姐图片上通过级联形式联系添加了三个事件（一个是包含浏览器行为的click事件，还有两个是自定义不含行为的alert事件）：

$(elImage)
.addEvent("click", funClick);
.addEvent("alert", funAlert1)
.addEvent("alert", funAlert2);

而funClick方法中有等同下面脚本：

$(e.target).fireEvent("alert");

因此，点击图片，才会出现三个弹出框：用户点击图片 → 执行funClick → 第一个弹框 → 执行fireEvent → 触发自定义"alert"事件 → 连续两个"alert"事件弹框

当点击图片下面的按钮清除掉自定义"alert"事件后，再点击图片就只有一个弹出咯（funAlert1和funAlert2提前回家扫墓去了）！

五、清明节前的结语

明天回家，很显然，我要钓鱼钓死在河边上。

时间等客观原因，本文展示的些脚本并未做非常详尽严谨的测试，因此，不建议直接Copy到实际项目中应用，更多旨在相互交流与学习。例如在IE浏览器下，最后的“伪DOM自定义事件”，click事件通过点击触发时的事件类型(event.type)是click, 但是通过fireEvent触发的时候事件类型是propertychange, 这些细节在测试学习的时候都是可以忽略的，但是要是实际应用，这都是需要完善的。

本想以很通俗易懂的语言阐述我想表达的内容，但是，现在回过头看看，做得并不好，术语，啰嗦的话语还是显得多了点，这方面的功力还需要加强，或许是本身理解不透彻的缘故，无法驾驭自然无法语言通俗化。

虽说自己JS方面的学习比两年前要好多了（那个时候连addEventListener和attachEvent放在一起干嘛的都不清楚），但是心里清楚的很，JS还是很薄弱的，跟真正优秀的JS开发人员相比，要积累的还有很多。什么时候能够像看有色小说一样把《JavaScript语言精粹》一书读下来，恩，估计可以有脸得瑟得瑟了~~

本文涉及的一些知识点欢迎补充提点，有表述不准确的地方欢迎指正。

最后，祝大家清明节快乐！额？怎么这句话怪怪的——上坟一般快乐不起来吧~~那大家祝我清明回家钓鱼大丰收，大爆箱！！哈哈！！

原创文章，转载请注明来自张鑫旭-鑫空间-鑫生活[http://www.zhangxinxu.com]
本文地址：http://www.zhangxinxu.com/wordpress/?p=2330

（本篇完）

标签： addEventListener, attachEvent, createEvent, dispatchEvent, dom, javascript, onpropertychange, 兼容性, 原型, 对象字面量, 继承, 自定义事件

原文链接：http://www.zhangxinxu.com/wordpress/?p=2330 （本内容由互联网资料整理而成）

CSS3 Media Queries让你的网页适合各种设备

Media Queries直译过来就是“媒体查询”，在我们平时的Web页面中head部分常看到这样的一段代码：

<link href="css/reset.css" rel="stylesheet" type="text/css" media="screen" />
<link href="css/style.css" rel="stylesheet" type="text/css" media="all" />
<link href="css/print.css" rel="stylesheet" type="text/css" media="print" />

或者这样的形式：

<style type="text/css" media="screen">
@import url("css/style.css");
</style>

不知道大家留意没有，其中两种方式引入CSS样式都有一个共同的属性“media”，而这个“media”就是用来指定特定的媒体类型，在HTML4和CSS2中充许你使用“media”来指定特定的媒体类型，如屏幕（screen）和打印（print）的样式表，当然还有其他的，比如说“TV”,“handheld”等，其中“all”表示的是支持所有媒体介质。有关于更多的Media类型，可以点击这里。

上面简单说了一下HTML4和CSS2的“Media Queries”,而今天的主要是来学习CSS3中的"Media Queries"的更多使用方法和相关知识，下面我们开始进入今天的主题。CSS3中的Media Queries增加了更多的媒体查询，同时你可以添加不同的媒体类型的表达式用来检查媒体是否符合某些条件，如果媒体符合相应的条件，那么就会调用对应的样式表。换句简单的说，“在CSS3中我们可以设置不同类型的媒体条件，并根据对应的条件，给相应符合条件的媒体调用相对应的样式表”。现在最常见的一个例子，你可以同时给PC机的大屏幕和移动设备设置不同的样式表。这功能是非常强大的，他可以让你定制不同的分辨率和设备，并在不改变内容的情况下，让你制作的web页面在不同的分辨率和设备下都能显示正常，并且不会因此而丢失样式。

首先来看一个简单的实例：

<link rel="stylesheet" media="screen and (max-width: 600px)" href="small.css" />

上面的media语句表示的是：当页页宽度小于或等于600px,调用small.css样式表来渲染你的Web页面。首先来看media的语句中包含的内容：

1、screen：这个不用说大家都知道，指的是一种媒体类型；

2、and：被称为关键词，与其相似的还有not,only，稍后会介绍；

3、（max-width:600px）：这个就是媒体特性，说得通俗一点就是媒体条件。

前面这个简单的实例引出两个概念性的东西，一个就是媒体类型（Media Type）和 媒体特性（Media Query），首先一起来理解一下这两个概念：

一、媒体类型(Media Type)

媒体类型(Media Type)在css2中是一个常见的属性，也是一个非常有用的属性，可以通过媒体类型对不同的设备指定不同的样式，在css2中我们常碰到的就是all（全部）,screen（屏幕）,print（页面打印或打邱预览模式）,其实在媒体类型不止这三种，w3c总共列出了10种媒体类型。

页面中引入媒体类型方法也有多种：

1、link方法引入

<link rel="stylesheet" type="text/css" href="../css/print.css" media="print" />

2、xml方式引入

<?xml-stylesheet rel="stylesheet" media="screen" href="css/style.css" ？>

3、@import方式引入

@import引入有两种方式，一种是在样式文件中通过@import调用别一个样式文件；另一种方法是在<head></head>中的<style>...</style>中引入，单这种使用方法在ie6-7都不被支持 如

样式文件中调用另一个样式文件：

@import url("css/reset.css") screen;
@import url("css/print.css") print;

在<head></head>中的<style>...</style>中调用：

<head>
<style type="text/css">
@import url("css/style.css") all;
</style>
</head>

4、@media引入

这种引入方式和@import是一样的，也有两种方式：

样式文件中使用：

@media screen{
选择器{
属性：属性值；
}
}

在<head>>/head>中的<style>...</style>中调用：

<head>
<style type="text/css">
@media screen{
选择器{
属性：属性值；
}
}
</style>
</head>

以上几种方法都有其各自的利弊，在实际应用中我建议使用第一种和第四种，因为这两种方法是在项目制作中是常用的方法，对于他们的具体区别，我就不说了，想了解的大家可以去找度娘或Ｇ爸，他们能帮你解决。

二、媒体特性（Media Query）

前面有简单的提到，Media Query是CSS3 对Media Type的增强版，其实可以将Media Query看成Media Type(判断条件)+CSS(符合条件的样式规则)，常用的特性w3c共列出来13种。具体的可以参阅：Media features。为了更能理解Media Query，我们在次回到前面的实例上：

<link rel="stylesheet" media="screen and (max-width: 600px)" href="small.css" />

转换成css中的写法为：

@media screen and (max-width: 600px) {
选择器 {
属性：属性值；
}
}

其实就是把small.css文件中的样式放在了@media srceen and (max-width;600px){...}的大括号之中。在语句上面的语句结构中，可以看出Media query和css的属性集合很相似，主要区别在：

1、Media query只接受单个的逻辑表达式作为其值，或者没有值；

2、css属性用于声明如何表现页页的信息；而Media Query是一个用于判断输出设备是否满足某种条件的表达式；

3、Media Query其中的大部分接受min/max前缀，用来表示其逻辑关系，表示应用于大于等于或者小于等于某个值的情况

4、CSS属性要求必须有属性值，Media Query可以没有值，因为其表达式返回的只有真或假两种

常用的Media Query如下表所示：

兼容的浏览器：

下面我们一起来看看Media Queries的具体使用方式

一、最大宽度Max Width

<link rel="stylesheet" media="screen and (max-width:600px)" href="small.css" type="text/css" />

上面表示的是：当屏幕小于或等于600px时，将采用small.css样式来渲染Web页面。

二、最小宽度Min Width

<link rel="stylesheet" media="screen and (min-width:900px)" href="big.css" type="text/css"  />

上面表示的是：当屏幕大于或等于900px时，将采用big.css样式来渲染Web页面。

三、多个Media Queries使用

<link rel="stylesheet" media="screen and (min-width:600px) and (max-width:900px)" href="style.css" type="text/css" />

Media Query可以结合多个媒体查询，换句话说，一个Media Query可以包含0到多个表达式，表达式又可以包含0到多个关键字，以及一种Media Type。正如上面的其表示的是当屏幕在600px-900px之间时采用style.css样式来渲染web页面。

四、设备屏幕的输出宽度Device Width

<link rel="stylesheet" media="screen and (max-device-width: 480px)" href="iphone.css" type="text/css" />

上面的代码指的是iphone.css样式适用于最大设备宽度为480px，比如说iPhone上的显示，这里的max-device-width所指的是设备的实际分辨率，也就是指可视面积分辨率

五、iPhone4

<link rel="stylesheet" media="only screen and (-webkit-min-device-pixel-ratio: 2)" type="text/css" href="iphone4.css" />

上面的样式是专门针对iPhone4的移动设备写的。

六、iPad

<link rel="stylesheet" media="all and (orientation:portrait)" href="portrait.css" type="text/css" />
<link rel="stylesheet" media="all and (orientation:landscape)" href="landscape.css"  type="text/css" />

在大数情况下，移动设备iPad上的Safari和在iPhone上的是相同的，只是他们不同之处是iPad声明了不同的方向，比如说上面的例子，在纵向(portrait)时采用portrait.css来渲染页面；在横向（landscape）时采用landscape.css来渲染页面。

七、android

/*240px的宽度*/
<link rel="stylesheet" media="only screen and (max-device-width:240px)" href="android240.css" type="text/css" />
/*360px的宽度*/
<link rel="stylesheet" media="only screen and (min-device-width:241px) and (max-device-width:360px)" href="android360.css" type="text/css" />
/*480px的宽度*/
<link rel="stylesheet" media="only screen and (min-device-width:361px) and (max-device-width:480px)" href="android480.css" type="text/css" />

我们可以使用media query为android手机在不同分辨率提供特定样式，这样就可以解决屏幕分辨率的不同给android手机的页面重构问题。

八、not关键字

<link rel="stylesheet" media="not print and (max-width: 1200px)" href="print.css" type="text/css" />

not关键字是用来排除某种制定的媒体类型，换句话来说就是用于排除符合表达式的设备。

九、only关键字

<link rel="stylesheet" media="only screen and (max-device-width:240px)" href="android240.css" type="text/css" />

only用来定某种特定的媒体类型，可以用来排除不支持媒体查询的浏览器。其实only很多时候是用来对那些不支持Media Query但却支持Media Type的设备隐藏样式表的。其主要有：支持媒体特性（Media Queries）的设备，正常调用样式，此时就当only不存在；对于不支持媒体特性(Media Queries)但又支持媒体类型(Media Type)的设备，这样就会不读了样式，因为其先读only而不是screen；另外不支持Media Qqueries的浏览器，不论是否支持only，样式都不会被采用。

十、其他

在Media Query中如果没有明确指定Media Type，那么其默认为all，如：

<link rel="stylesheet" media="(min-width: 701px) and (max-width: 900px)" href="medium.css" type="text/css" />

另外还有使用逗号（，）被用来表示并列或者表示或，如下

<link rel="stylesheet" type="text/css" href="style.css" media="handheld and (max-width:480px), screen and (min-width:960px)" />

上面代码中style.css样式被用在宽度小于或等于480px的手持设备上，或者被用于屏幕宽度大于或等于960px的设备上。

关于Media Query的使用这一节就介绍到此，最后总体规纳一下其功能，个人认为就是一句话：Media Queries能在不同的条件下使用不同的样式，使用页面达到不同的渲染效果。下一节将会针对Media Queries介绍几个实例，如果感兴趣的朋友记得观注本站的更新。

如需转载烦请注明出处：W3CPLUS

原文链接：http://www.w3cplus.com/content/css3-media-queries （本内容由互联网资料整理而成）

两个viewport的故事（第二部分）

原文出处： quirksmode   译文出处： Zhao Yuhao

【感谢@千叶_V 的翻译投稿。如果其他朋友也有不错的原创或译文，可以尝试推荐给伯乐在线。】

在 @伯乐在线官方微博 看到这篇文章的第一部分，信息量很大。看完之后，对wap网站开发和响应式设计有更深入的认知，可惜伯乐在线迟迟没有发布第二部分的译文，于是我试着翻译一部分，顺便整理下思路。

1.手机浏览器与桌面浏览器的不同

现在手机浏览器的显示分辨率与桌面浏览器差不多，但是手机的尺寸比电脑要小很多。一个没做过响应式处理的网站，在手机和电脑上显示完全一样的内容，不可避免的会出现字体被缩小的现象。想象一下电脑里12号字体缩小好几倍的效果吧。 所以如果将web网页移植为wap应用上，还是有许多问题需要考虑。

2.理解两个viewport的概念

做过wap开发的都知道 html的 <meta name=”viewport”> ,这是一个从iphone引入的meta，现在几乎所有手机浏览器都支持。下边要讲的 viewport 是从手机浏览器的角度出发，而不是html，请不要混淆。

把 viewport 分为2个方面来学习，更有助于理解它的原理：

- visual viewport

- layout viewport

想象有个房间，你可以操控它放大变小，现在你站在它的窗户前。正对着窗户的墙壁涂满了壁画，你走到离窗口1米的位置往房间里看，假设房间现在很大很大，你能看到对面墙壁上的整个壁画，但是因为距离太远了，你看不清壁画上的题词，于是你让房间缩小，缩小到壁画离你很近，近到能看清壁画上的细节。在这里 窗户就是 visual viewport。墙壁就是 layout viewport

对应到手机浏览器，visual viewport就是当前显示给用户内容的窗口，你可以拖动或者放大缩小网页，来看清楚网页的内容。layout viewport 有网页的所有内容，他可以全部或者部分展示给用户。

对于css布局，特别是用宽度百分比做排版的时候，比率是按照layout viewport 来计算的。也就是说如果一个div相对的宽度50%，用户在手机浏览器放大缩小，DIV的宽度不会一直显示相对于窗口50%，这个div可能会填满整个窗口或小到看不见。(可以电脑和手机分别体验一下这个网址:http://zhaoyuhao.com/demo/34.html)

那么layout viewport有多宽？不同的设备、不同的浏览器都不相同。 Safari浏览器为980px，Opera 850px，Android WebKit 800px， IE 974px.(手机像素宽度、浏览器像素、设备像素是不同的概念，这个需要注意.)

原文对于这两者还有一些其他的分析，不过不是什么重点，就不翻了

3.viewport 长宽的测量

上文提到有2个viewport，分别有2对属性值对应这两者。(这里有个故事，按道理桌面浏览器是只需要一个属性对的，可是由于浏览器大战导致出现了不同标准，刚好手机浏览器用上了，也算是一种应祸得福吧。)

layout viewport 的 长宽 (document.documentElement.clientWidth / document.documentElement.clientHeight)

visual viewport 的 长宽 (window.innerWidth / window.innerHeight)

4.screen（设备） 长宽的测量

screen 的大小其实就是设备的像素大小，在针对桌面浏览器的开发中，这个数值不重要，你不需要关系电脑屏幕的像素，但是对于wap开发，这个数值有它的含义，因为手机浏览器宽度=设备宽度，可以通过他们的比例计算到页面的缩放比例

screen.width/height

5. Scrolling offset

我们有时候要知道visual viewport与layout viewport的相对距离 ：

window.pageXOffset window.pageYOffset

6.Media queries

Media queries 是html5的特性 可以根据 device-width(设备的宽度 screen.width) 来确定显示不同CSS。

我在IPHONE4S – CHROME 浏览器 ，测试结果如下：

1. visual viewport 宽度 : 默认980 实际大小与缩放比例相关，可以通过meta的viewport属性修改
2. layout viewport 宽度 : 980
3. screen.width         ：320

可见IPHONE4S 在做响应设计的时候 ，width应该是320px。

你也可以在 http://zhaoyuhao.com/demo/35.html 测试你设备的情况

7.Meta viewport

最后，我们讨论下 <meta name=”viewport” content=”width=device-width”> 还记得之前窗户和壁画的例子么？设置viewport就相当于放大和缩小房间，找到合适的像素给用户最好的体验。

我们一步步分析：

1.假设你有个简单的页面，不给里面的DIV设置宽度，默认是相对于 layout viewport 的 100%。对于iphone4S而言，这个宽度的数值是980，所以显示出来的效果是这样

2.用户通过放大网页比率，缩小visual viewport的值，相对的用户就能看清楚DIV里的内容，但是layout viewport并没有变，所以会出现下边的效果，部分文档显示在了浏览器外边，需要通过滚动条查看全部文档

3.所以为了解决这个问题，引入了viewport标签。当你看到 <meta name=”viewport” content=”width=device-width”> 说明这个网页把layout viewport的像素设置成了设备的像素，这样实现了 visual viewport = layout viewport = screen.width的最佳体验。

8.小结

文中viewport的介绍不确定是所有浏览器产商实现浏览器的原理，但是对于wap开发人员而言很有帮助。

(2 个评分，平均: 4.50)

Loading ...

原文链接：http://blog.jobbole.com/44903/ （本内容由互联网资料整理而成）

禁用状态二三事

前言
禁用，顾名思义，代表不可用的意思。有一个禁用，就一定对应着一个可用。不论是表单，还是复杂性管理界面，抑或是分步引导，这些场景中都有可能出现禁用状态，而如何合理运用禁用状态，让它成为体验的加分项而不是减分项，正是设计师要不断思考、深入实践的。这里抛一些砖出来，希望和大家一起探讨。

“这个不可点的按钮放在这里很鸡肋，我们是不是可以考虑去掉？”
“默认放出按钮吧，不能点也没关系，不然用户怎么了解产品中有哪些功能？”
禁用状态可看为应用程序机制对外暴露的一个直接体现，什么时候展现，什么时候隐藏，也是设计中经常碰到的争议话题。

Alan Cooper曾提到，激活和禁止菜单可以帮助用户了解菜单所反映的用途，从而帮助菜单成为更好的教学工具。例如我们常用的Photoshop选择菜单：

菜单作为软件中最完备的功能集合，应该呈现所有的功能，这是第一个前提。在这个前提下，再根据具体交互的流程来展示哪些操作可用，哪些禁用。仔细想想，在我们日常所见的各种软件中，你并不会对菜单里的禁用项感到陌生。

菜单有一个好基友叫做工具栏。作为一种直观、快捷的功能呈现形式，有幸出现在工具栏上的操作一般来说是软件中最常用的功能，为用户提供快速、高效的操作入口。

工具栏给用户的预期应该是稳定的，可靠的，用户会不知不觉地对工具栏的布局进行学习，记住常用操作的位置，形成使用惯性。如果某个按钮有时消失，有时出现，用户会感到迷惑，也不利于提高界面操作的效率。尤其是当工具栏上有大量操作时，固定位置的好处显而易见，例如Ribbon界面，每个子面板下都包括至少5个以上的按钮：

看上去，对于菜单、工具栏中的操作，不论是否有禁用状态，默认放出来是最好的选择。然而当操作较少时，设计师们仍然会做出思考和改进。

浏览器，一个所有人都离不开的软件，前进、后退是它的核心功能之一。在这件事上，Chrome和Firefox采取了不同的设计思路。Chrome的前进、后退按钮将位置固定在地址栏左侧，根据当前网页的访问路径判断该按钮是否展示为可用状态，如下图所示：

这样设计的好处在于，用户第一眼就能明白功能的位置，便于形成操作惯性。虽然在某些情况下前进按钮会由可用样式改变为禁用样式，但用户浏览网页时几乎全部注意力都在网页内容上，这里的状态变化并不会造成困扰。

在Firefox中，打开新网页时地址栏左侧仅有一个后退按钮。点击后退按钮后，一个可用的前进按钮快速滑出。也就是说，仅当前进按钮可用时，它才会出现，在不可用时它会被隐藏掉。

如果用户是按照开新窗口的模式打开新页面，浏览完毕就关闭窗口，前进按钮并不会出现；如果用户在同一窗口内新开页面，再点击后退时，前进按钮快速滑出，动画的衔接让它具备了功能自解释的属性，不会给用户造成理解上的困惑。当页面不能继续前进的时候，此时前进按钮瞬间变为禁用状态且快速滑动收起消失，用户也没有机会去点击。这样一来，Firefox地址栏左侧的空间在大部分情况下视觉感受轻量简洁，仅有一个最重要的后退按钮，而在需要使用前进按钮的场景下，操作也是流畅、自然的。

这里不评判两种设计孰优孰劣，在处理前进按钮的这个问题上，Chrome和Firefox都有各自的答案。Firefox之所以采用这种较为大胆的设计，我认为和前进按钮的使用频率、使用场景有着密不可分的关系。再换个角度，Chrome也并不适用Firefox的设计：在Chrome地址栏左侧有3个按钮，如果中间的按钮有时会隐藏，那么最后一个刷新按钮的位置无法固定，在用户看来这个“工具栏”是有明显跳动存在的，而firefox的刷新按钮是在地址栏右侧，并不会有这样的问题。

上文是一层操作的例子。那么多层操作呢？假如你面对一组信息列表，针对全部列表有一套操作，针对每条信息又都有一套新的操作时，就必须要考虑隐藏的设计了。

Gmail的邮件列表页在未选中邮件时，工具栏只包括3个针对列表的功能。选中具体邮件后，针对该邮件的操作出现在工具栏中，原有的操作消失。这样既可以避免未选中邮件时大量禁用操作摆放在界面上给用户造成负担，又保证工具栏在默认状态下的清爽简洁。然而，这种设计也给用户带来一定的学习成本，新手用户在初次使用时需要探索一番才能明白邮件选中前后的区别。

Dropbox也采用了类似的设计，在默认界面的表格标题栏中不呈现任何操作，单击选中文件后，具体操作展现在工具栏上。

当我们面临不那么复杂的操作时，例如web上的发布表单、提交流程等，对禁用状态的取舍则需要更加细致入微的思考。以下是一些设计建议供参考：

1.需要应引导用户操作时，扔掉禁用

先来看看新浪微博的发布按钮。这个按钮仅当发布框中有文字时展示为明确可点击的红色；若发布框中没有文字，按钮显示为灰色，点击时发布框背景色变红闪烁，提示用户输入内容。

这里设计要表达的目的很明确，既输入文字后才可以发布。然而，当发布按钮看上去呈现不可点击的灰色时，鼠标移上按钮仍然为手型，点击后系统也给出了交互上的反馈，这样一来按钮就被赋予了多重含义，在变灰时也可以点击，给用户造成了理解上的困惑。按照用户的认知，如果一个按钮看上去可点击，那么它应该实际也可点击。如果看上去不像能点的样子，那它就应该不可点。

facebook 的状态发布按钮并没有设计禁用状态，当输入框内容为空时，点击Post按钮，页面上不会产生变化。

腾讯微博的发布按钮也采用同样的设计，假如内容为空时点击按钮，在输入框的右下角会以橙色文字提示用户输入内容。

对于微博或类SNS的网站，发布状态是最重要的写操作入口，按钮在界面上可以起到良好的引导作用。这种情况下，仅赋予按钮一种状态，是不是更简单、更容易理解呢？

2.明确区分禁用和可用样式

如果某个操作确实需要禁用状态，那么在视觉上需要明确区分可用的样式与禁用的样式。举一个反例，在tumblr发布文字时，左下角的发布按钮很难看清什么情况下是可用，什么情况下是禁用。

上图中，红色圈出的部分是禁用状态。

上图为输入文字后的状态，左下角的Publish此时已经可点击了，但按钮的颜色、立体效果均没有改变，仅文字变亮，而文字的亮度和禁用状态下仅有细微的差别，在比较差的显示器上是无法分辨的。

3.在恰当的时候给出恰当的解释

当禁用状态的变化对用户来说有些突然时，可以考虑设计清晰易懂的解释性说明。例如Bing搜索首页，当某张图片不能下载时，下载按钮置灰，当鼠标移动到按钮上时，tips解释了该按钮置灰的原因。

OS X的设置窗口中，Advanced按钮默认是禁用的。在这种类似的设置窗口中，OS X使用左下角的带锁开关来控制高级的操作。对于那些关键性的系统级设置，需要开锁后才能进行更改。

4. 赋予禁用状态更多可能性

禁用状态总是会令人联想到冷冰冰的系统规则，但如果善用禁用状态，信息传达将更有效。例如在Twitter发推时，提交过程中Tweet按钮变为禁用，同时在按钮左侧出现loading动画，通过两个UI元素的组合表达了提交中这一动作。

新版flickr的上传图片工具中，点击提交后，按钮立刻变为禁用状态，而有趣的文案“Hold on there，tiger”也给界面带来生动感。

禁用状态是否始终可见，取决于它所属操作在界面中的功能属性。最重要的是保证核心功能交互体验流畅，用户不会形成理解上的困惑，然后再根据具体界面环境进行分析。如果需要展示禁用状态，我们必须让用户明白该操作变得可用的方法；如果需要隐藏禁用状态，在它出现时要让用户自然接受而不是“被吓了一跳”。更进一步，如果展示了禁用状态，那么就尽量避免样式上和可用状态的混淆，明确可点与不可点；在必要的时候给出解释和说明，不要让用户去猜测揣摩。

原文链接：http://cdc.tencent.com/?p=7668 （本内容由互联网资料整理而成）

LiveStyle：实时同步Sublime Text和Chrome调试工具中的CSS

In the past, we featured some exciting tools and libraries: PrefixFree, Foundation, Sisyphus.js, GuideGuide, Gridpak, JS Bin and CSSComb. All of them have been developed and released by active members of the Web design community as open-source projects. Today, we present LiveStyle, a plugin for live bi-directional (editor ↔ browser) CSS editing of the new generation! — Ed.

Tools for live CSS editing aren’t new these days. You may already be familiar with tools like LiveReload, CodeKit and Brackets. So, why would someone ever need to create yet another tool and even call it a “live CSS editor of the new generation”?

The tool I’d like to introduce to you today is Emmet LiveStyle. This plugin takes a completely different approach on updating CSS. Unlike other live editors, it doesn’t simply replace a whole CSS file in a browser or an editor, but rather maps changes from one CSS file to the other.

In order to better understand how LiveStyle works, let us first take a look at the current state of live edit tools.

A State Of Live Edit Tools

Most live reload/edit tools work in a pretty simple manner: They look out for CSS files in a special folder and update the Web browser once something has been changed. So users have to edit the CSS file and save it before they can preview the changes. Not exactly a “live” update, but this simplicity has its own benefits. You can use these tools together with preprocessors so your webpage gets updated automatically whenever you save your LESS or SASS file.

About a year ago, a new breed of live editing tools appeared. Editors like Brackets and WebStorm integrate with Web browsers (more specifically, with Google Chrome) directly and allow you to see updates instantly, e.g. without saving a file. They send the updated file content to the browser every time you change something. But in order to use live edit, they require a special built-in Web server to be used to properly map your local files with browser URLs.

Getting changes from DevTools back into your CSS file is not so popular. There are a few tools like Tin.cr that allow you to save your DevTools changes back to the file (the Chrome Dev team introduced Chrome Workspaces recently for this very same purpose).

Summing up, to use these tools for truly live development (deliver updates from editor to browser and vice versa), you have to:

- Use the same CSS files in your text editor and Web browser.

- Keep your files in a local file system.

- In some cases, use a special tooling Web server.

All of these tools work just fine once you’ve started your project development, but what happens when your website goes into production? What if you concatenate and minify your CSS code for better performance and UX? Most of these tools become pretty much useless:

- You can’t use the tooling Web server because you need to use your own one for backend/CMS.

- You can’t get the DevTools changes back into your file since (concatenated and minified) CSS within browser is not the same as your source code.

- In some large projects, you can’t use a local file system — your files might be in your private sandbox on the dev server.

So, you don’t have much options now, right? If you ask me, there are two things that must be eliminated:

1. Any middleware between the browser and editor. The editor should be able to talk to the browser directly, without using any files or Web servers.

2. Full CSS content swapping. The browser or editor must receive only the updated sections, not the entire source.

To solve these issues, LiveStyle was created. Unlike other tools, it does not work directly with files, nor does it replace them in the browser or editor. It maps changes from one source to the other.

How LiveStyle Works

Imagine you’re editing a CSS file and I ask you, “What did you just change?”

Your answer could be: “On line 2, I replaced the characters from 12 to 16 with the word red.” But I’m pretty sure your answer will rather end up being: “In the body selector, I changed the background property value to red.” In other words, instead of describing how bytes were changed within the text file, you would describe how the structure of the CSS file was changed.

But the thing is, if you pass this very same information to another developer, i.e. “in body, change background value to red,” he can perform the very same changes in his own CSS file and get the very same result!

This is exactly how LiveStyle works. Whenever you update a CSS source, it performs structural comparisons with the previous state and creates a special patch that describes how the CSS structure was changed. This patch is then transmitted to all clients and is applied to the associated CSS source.

This approach gives you the following benefits:

- You can associate two completely different CSS sources for live editing. For example, you can take a minified and concatenated CSS source in a browser, associate it with one of the source CSS modules in an editor, and use them for fully bi-directional live editing.

- You don’t need to keep your files in a local file system. You can simply open it directly from the FTP, your fancy network mount, or whatever. If a file can be opened by a text editor, you can use it for live editing as well.

- You can even create new, untitled files and use them for live editing right away!

- You don’t need a special Web server, code snippet or file watcher, everything works in the editor and browser.

Here’s a demo video demonstrating how this approach works in real life:

In the video above, I used the Facebook main page to demonstrate the power of LiveStyle. There’s no doubt it’s one of the largest and complex websites on the planet and I don’t have access to either the Facebook server nor its CSS source. But I need just a few clicks to start the live CSS editing. Imagine how easy it is for you to do the very same for your own website!

Installation

Currently, LiveStyle works in Google Chrome, WebKit Nightly (for iOS apps live editing) and Sublime Text. The installation process is pretty straightforward:

The WebKit extension can be installed directly from Sublime Text, just pick Tools → Install LiveStyle for WebKit extension menu item, or run the LiveStyle: Install WebKit extension command from the Command Palette.

That’s it! Now you can start using LiveStyle to tweak your websites. If you have any trouble with the LiveStyle installation or need any further assistance, please go to the official installation guide.

Using LiveStyle

To start with the live CSS editing, simply follow these four easy steps:

1. Start Sublime Text and open a CSS file or create new one.

2. Start your Chrome browser and go to the page you wish to edit.

3. Open DevTools, go to the LiveStyle panel and check the Enable LiveStyle for current page option.

4. Once enabled, you’ll see a list of the external style sheets on the left and a list of editor files on the right. Simply pick the editor file that should be associated with the browser and you are done!

Note that the editor’s files list is automatically updated every time you create, open or close files within the editor.

Important: You must keep DevTools open during your live editing session and for every window (in multi-view mode). You don’t have to be on the LiveStyle panel all of the time, but DevTools must remain open otherwise the incoming updates won’t be applied.

New Workflows

LiveStyles’ CSS patching concept introduces a number of workflows you can use in your development process:

Simple Mode

This one’s a basic one-to-one live editing mode. Simply associate any external CSS file in the browser and the editor, and start editing. All your editor changes will be automatically reflected in the browser and your DevTools updates will be reflected in the editor.

If your browser file is large enough, your editor updates may take some time to apply. If you want to speed things up or you don’t have any external style sheets on your page, you can create a new style sheet by pressing the Add file button and using it for live updates.

Multi-View Mode

Multi-view mode is ideal for tweaking responsive Web designs. Open multiple windows of the same page and resize them for your RWD breakpoints. DevTools must be open for each window, otherwise it won’t apply any updates.

In multi-view mode:

- All editor updates will be applied to all windows.

- All DevTools updates will be applied to the editor and all other windows with the same page.

- All LiveStyle panel updates (like file associations) will be automatically applied to all other windows with the same page.

Multi-Site Mode

This mode is useful if your Web project has different versions of desktop and mobile websites but shares the same CSS code base.

Just like in the “multi-view mode”, you need to open a few windows with your website’s versions and associate your browser CSS files in the LiveStyle panel with the same editor file. LiveStyle will use this editor file as a reference to patch your browser files with incoming updates, even from DevTools.

Designer Mode

This mode is for designers who work on large projects and don’t have direct access to the CSS sources. (Please note that this is an experimental mode and is subject to change!)

Imagine you spot an error on your production website. Instead of asking the developer to spend some time with you to fix these issues, you can fix them by yourself and send the developer a patch so he can apply it later to the original source.

All LiveStyle updates are recorded into the “Patch history”, available in the LiveStyle DevTools panel. A new patch history entry is created automatically every time you open or refresh a Web page. Click on the “Patch history” pop-up entry to apply recorded patches and click on the red icon on the right to download it.

So, all you have to do is to tweak the layout in DevTools and download the most recent patch history entry. You can send the downloaded patch to the developer so he can apply it to original CSS source.

Note that in this mode you don’t need Sublime Text extension at all; you just need the DevTools extension.

Behind The Scenes

I’m pretty sure anyone who’s tech savvy is interested in how LiveStyle works and what lessons I learned from this project.

How LiveStyle Patches CSS

When you edit styles in DevTools, you see that properties in selectors are modified accordingly: Existing selectors are updated and absent ones are created — even whole CSS selectors are automatically created (and I really hope this is the only thing you see).

But did you notice there’s no CSS formatting configuration step? You didn’t have to open any preferences file to specify that you don’t need space after a colon and all your properties should be written in single lines.

That’s because LiveStyle tries to match your coding style as close as possible. Whenever it needs to insert something in the document, it analyzes your personal coding style and automatically creates formatting rules for you.

This is also possible due to the original Emmets’ cssEditTree component. This module provides a DOM-like interface for CSS modifications, e.g. rule.value('background', 'red'), rule.add('padding', '10px'), but also keeps track of CSS token positions and inherits formatting rules for newly created properties. You can actually create an Emmet extension and reuse this module to automate your CSS modification tasks (for example, like in the Update Image Size action).

Performance

As described earlier, LiveStyle doesn’t simply swap CSS content, it parses CSS into a tree, compares it with its previous state and generates a special patch. On the other end, it also has to parse CSS, locate proper place to patch, analyze your coding style and then create patched CSS source. And everything must be done after each keystroke which means that LiveStyle should be fast — blazingly fast.

I had to use some advanced tricks to make this possible; I had to optimize for Garbage Collector, optimize for JIT, optimize function inlining and even multi-threading programming.

JavaScript Optimization

LiveStyle is written entirely in JavaScript. Thanks to Google DevOps, we have a brilliant V8 JavaScript engine (powers Chrome and PyV8, used to run JS in Sublime Text) and DevTools for debugging JS performance.

V8 can run JavaScript very fast, but it’s not a magic box. We have to obey some rules to make it work that way.

The very first thing we need to be able to start optimization is a working product, covered by unit tests. “Premature optimization is the root of all evil”, you know.

Once we have our product up and running, start using Profiler to determine slow parts of your code. Chrome and Firefox have awesome built-in profiles and many tutorials on how to use them, so this shouldn’t be a problem.

Among other things, the great performance boost was achieved by replacing array iterators like Array.forEach with plain loops in critical parts of the performance. This technique is called “Inlining“. Plain loops are much faster than native implementations of Array.forEach and some libraries like Lo-Dash use the very same technique to run faster. Despite the fact that I’ve heavily used Lo-Dash in LiveStyle, I did use plain loops in performance-critical parts since every function call has its own small performance penalty.

As soon as all parts were optimized, the slowest process was the garbage collection (GC). GC is a process of removing unneeded data from memory. In JavaScript, we don’t have direct access to the garbage collector so we can’t, for example, delay its execution and explicitly call it again later. The only thing we can do here is to avoid producing so much garbage.

Consider the following example:

function getSize(obj) {
return {
width:  obj.left - obj.right,
height: obj.bottom - obj.top
};
}

var size = getSize(parent);
child.style.width  = size.width;
child.style.height = size.height;

In this example, we use the getSize() function as the utility method to calculate width and height from the given object. While this example is pretty simple, it actually produces a lot of garbage; If we called the getSize() method, say, 10,000 times, it will generate 10,000 objects that are not required for further program execution, so they must be collected by the GC.

A better implementation of the getSize() function may look like this:

var _size = {};
function getSize(obj) {
_size.width  = obj.left - obj.right;
_size.height = obj.bottom - obj.top;
return _size;
}

In this example, even if the getSize() function is called 100,000 times, only one object will be created in the memory — which greatly reduces GC calls.

I achieved a huge performance boost with all of these manipulations, and it still wasn’t the end. Now, we can make our app run even faster with the help of the just-in-time (JIT) compiler.

Since LiveStyle parses CSS, it creates a lot token objects that should be accessed by the patcher very often. I analyzed my code and saw that these token objects are modified during runtime, e.g. new properties were added to some of these objects.

V8′s JIT optimizer has a so-called “Hidden Class” feature, a special assembly that optimizes access to properties of similar objects. And every time we add a new property to an existing object, we break this optimization.

So I made my general optimization: I rewrote part of LiveStyle’s engine so token objects could be automatically created with all of the properties required in advance and that these objects can be reused across different parts of the app, which reduces garbage collection in general.

About a half of this enormous performance boost was achieved by optimizing JS for V8 internals. In order to demonstrate how much the LiveStyle performance boost was optimized, here are some numbers from my MacBook Air:

- Creating patch from 15 Kb CSS source: 18 ms

- Applying patch on 584 Kb CSS source: 212 ms

Pretty good I’d say, assuming that most live reload tools need a 1-second timeout before reloading the browser after a CSS file has been changed.

Future Plans

During the first days of public beta testing, LiveStyle proved that its patching algorithm is pretty stable and solid. There have been no reports of broken CSS or invalid results. In fact, LiveStyle actually helped some people to find bugs in their CSS. And there’s still a lot of work left to do: Support more browsers and editors and, of course, add preprocessors support.

In the demo video above, you saw how the live bi-directional SCSS editing is done. The editor changes in SCSS are instantly reflected in the browser’s CSS, and browser changes in plain CSS are instantly pushed into the right places of SCSS. But this is just an experiment demonstrating how powerful LiveStyle can be. For real-world usage, it basically requires a new SCSS processor to be written.

So, I hope you’ll find LiveStyle useful and spread the word! If the community support is strong enough, I’ll try my best to find funding for further development. LiveStyle is currently free during public beta testing, but will be available for a small fee after its official release.

If you experience any issues with LiveStyle or have any suggestions, feel free to create a ticket at plugin repo. Thanks!

Further Reading

原文链接：http://coding.smashingmagazine.com/2013/08/08/release-livestyle-css-live-reload/ （本内容由互联网资料整理而成）

Bootstrap 3.0 最佳新功能一览

近日，Bootstrap开发者发布了Bootstrap 3.0 RC1，我们可以从中先领略到Bootstrap 3.x带来的全新特性。

从目前来看，Bootstrap 3.0已经远远优于2.x版本了。下面就来看看该版本中引入了哪些新特性。

扁平化和整洁的UI

又是扁平化！自从苹果iOS的UI从拟物化过渡到扁平化后，这种风格如今已经成为了现代化设计的代名词。而标榜“移动优先”的Bootstrap 3没有理由不扁平化。

Bootstrap 2.0通过一个简单的应用和一个统一的界面使之风靡互联网，而Bootstrap 3将这种风格上升到了另一个层次。除了带来了扁平化的UI外，Bootstrap 3还将标志性的紫色和导航栏用到了文档的主题中。

这样使得界面更加整洁，也使得Bootstrap可以更好地跨设备和浏览器。

图标字体

图标字体（glyphicons）现在已经从Bootstrap代码库中分离出来，你可以作为插件来使用它。

图标得到了重新设计，并且提供了一些新的有趣的图标。

新的网格系统

Bootstrap 3中的新的网格系统允许根据设备大小通过变量来声明。比如，你可以针对桌面环境设置4列布局，针对平板电脑设置2列布局，针对手机设置1列布局。这样，你就可以很容易地控制你的页面的跨设备显示效果。

根据列的表单布局

Bootstrap终于摆脱了它的表单控件类，在Bootstrap 3中你可以使用更整洁的网格类。比如：

<div class="row">
<div class="col-4">
<label>
</div>
<div class="col-8">
<input type="text">
</div>
</div>

列表组（List group）

List group是一个新的组件，现在你也可以使用Bootstrap来创建一些应用原型，比如RSS Feed、状态列表、链接列表等。

如上图，通过自定义内容列表组，您可以轻松地创建reddit网站原型或者其他元素。现在，Bootstrap也多少开始朝着CSS原型框架的方向发展了。

上下文面板

Bootstrap现在开始更加认真地对待其着色系统，无论是警告、通知还是其他元素，现在都根据整体着色来进行风格化。

Row Mixins

现在你可以轻松地创建自定义的网格，或打散网格只定义那些你需要的部分（比如内容、侧边栏），而无需自定义一个完整的12列网格。

除此之外，你还可以为你的流体形式的行指定断点。下面是Bootstrap使用的代码：

你可以通过各种变量来修改Mixins的行为，比如@grid-float-breakpoint、@grid-columns和@grid-gutter-width等。如果想重建网站的布局，可以这样做：

@grid-float-breakpoints: 768px
@grid-columns: 16
//I like 16 col layouts
@grid-gutter-width: 20px

#wrap {
.make-row();
}

#header {
.make-column(3);
}

#inner {
.make-column(13);
}

.content-sidebar-wrap {
.make-row();
}

#content {
.make-column(12);
}

#sidebar {
.make-column(4);
}

更多信息：Best New Features in Bootstrap 3.0

Bootstrap 3.0 RC1下载：http://getbootstrap.com/

- 大小: 17.8 KB

- 大小: 15 KB

- 大小: 55.6 KB

- 大小: 66.4 KB

- 大小: 23.2 KB

- 大小: 88.6 KB

- 大小: 69.6 KB

- 大小: 25.4 KB

原文链接：http://www.iteye.com/news/28140 （本内容由互联网资料整理而成）

[译] JavaScript 开发者经常忽略或误用的七个基础知识点

JavaScript, at its base, is a simple language that we continue to evolve with intelligent, flexible patterns. We've used those patterns in JavaScript frameworks which fuel our web applications today. Lost in JavaScript framework usage, which many new developers are thrust right into, are some of the very useful JavaScript techniques that make basic tasks possible. Here are seven of those basics:

JavaScript 本身可以算是一门简单的语言，但我们也不断用智慧和灵活的模式来改进它。昨天我们将这些模式应用到了 JavaScript 框架中，今天这些框架又驱动了我们的 Web 应用程序。很多新手开发者被各种强大的 JavaScript 框架吸引进来，但他们却忽略了框架身后浩如星海的 JavaScript 实用技巧。本文将为你呈献其中七个基础知识点：

1. String.prototype.replace: /g and /i Flags

1. 在 String.prototype.replace 方法中使用 /g 和 /i 标志位

One surprise to many JavaScript newbies is that String's replace method doesn't replace all occurrences of the needle -- just the first occurrence. Of course seasoned JavaScript vets know that a regular expression and the global flag (/g) need to be used:

令很多 JavaScript 初学者意外的是，字符串的 replace 方法并不会 替换所有匹配的子串——而仅仅替换第一次匹配。当然 JavaScript 老手们都知道这里可以使用正则表达式，并且需要加上一个全局标志位（/g）：

// Mistake
// 踩到坑了
var str = "David is an Arsenal fan, which means David is great";
str.replace("David", "Darren"); // "Darren is an Arsenal fan, which means David is great"

// Desired
// 符合预期
str.replace(/David/g, "Darren"); // "Darren is an Arsenal fan, which means Darren is great"

Another basic logical mistake is not ignoring case when case is not critical to the validation (letters may be uppercase or lowercase), so the /i flag is also useful:

另一个基本的逻辑错误就是在大小写不敏感的校验场合（字母可大写可小写）没有忽略大小写，此时 /i 标志位就很实用：

str.replace(/david/gi, "Darren"); // "Darren will always be an Arsenal fan, which means Darren will always be great"

（译注：上面这段例程我没有看懂用意，可能是注释有误吧……）

Every JavaScript developer has been bitten by each of the flags in the past -- so be sure to use them when when appropriate!

每个 JavaScript 开发者都曾踩过这两个标志位的坑——因此别忘了在适当的时候用上它们！

2. Array-Like Objects and Array.prototype.slice

2. 类数组对象和 Array.prototype.slice 方法

Array's slice method is principally for grabbing segments of an array. What many developers don't know is that slice can be used to covert Array-like objects like arguments, NodeLists, and attributes into true arrays of data:

数组的 slice 方法通常用来从一个数组中抽取片断。但很多开发者不了解的是，这个方法还可以用来将“类数组”元素（比如 arguments 参数列表、节点列表和 attributes）转换成真正的数组：（译注：抱歉，实在不明白这里的 attributes 是指什么。）

var nodesArr = Array.prototype.slice.call(document.querySelectorAll("div"));
// "true" array of DIVs
// 得到一个由 div 元素组成的“真正的”数组

var argsArr = Array.prototype.slice.call(arguments);
// changes arguments to "true" array
// 把 arguments 转换成一个“真正的”数组

You can even clone an array using a simple slice call:

你还可以使用一次简单的 slice 调用来克隆一个数组：

var clone = myArray.slice(0); // naive clone
// 浅克隆

（译注：这里的参数 0 也可以省略，我估计 undefined 被 slice 方法自动转换为 0 了吧。）

Array.prototype.slice is an absolute gem in the world of JavaScript, one that even novice JavaScript developers don't know the full potential of.

Array.prototype.slice 绝对是 JavaScript 世界中的一玫珍宝，但 JavaScript 初学者们显然还没有意识到它的全部潜力。

3. Array.prototype.sort

3. Array.prototype.sort 方法

The Array sort method is vastly underused and probably a bit more powerful than most developers believe. Many developers would assume sort would do something like this:

数组的 sort 方法 远远没有被充分利用，而且可能比开发者们想像的更加强大。很多开发者可能觉得 sort 方法可以用来做这种事情：

[1, 3, 9, 2].sort();
// Returns: [1, 2, 3, 9]
// 返回 [1, 2, 3, 9]

...which is true, but sort has more powerful uses, like this:

……这没错，但它还有更强大的用法，比如这样：

[
{ name: "Robin Van PurseStrings", age: 30 },
{ name: "Theo Walcott", age: 24 },
{ name: "Bacary Sagna", age: 28  }
].sort(function(obj1, obj2) {
// Ascending: first age less than the previous
// 实现增序排列：前者的 age 小于后者
return obj1.age - obj2.age;
});
// Returns:
// [
//    { name: "Theo Walcott", age: 24 },
//    { name: "Bacary Sagna", age: 28  },
//    { name: "Robin Van PurseStrings", age: 30 }
// ]

You can sort objects by property, not just simple basic items. In the event that JSON is sent down from the server and objects need to be sorted, keep this in mind!

你不仅可以对简单类型的数组项进行排序，可以通过属性来排序对象。如果哪天服务器端发来一段 JSON 数据，而且其中的对象需要排序，你可别忘了这一招！

4. Array Length for Truncation

4. 用 length 属性来截断数组

There's not a developer out there that hasn't been bitten by JavaScript's pass-objects-by-reference nature. Oftentimes developers will attempt to empty an array but mistakenly create a new one instead:

几乎所有开发者都踩过 JavaScript 的这个坑——“传对象只是传引用”。开发者们经常会试图 把一个数组清空，但实际上却错误地创建了一个新数组。

var myArray = yourArray = [1, 2, 3];

// :(
// 囧
myArray = []; // `yourArray` is still [1, 2, 3]
// `yourArray` 仍然是 [1, 2, 3]

// The right way, keeping reference
// 正确的方法是保持引用
myArray.length = 0; // `yourArray` and `myArray` both []
// `yourArray` 和 `myArray`（以及其它所有对这个数组的引用）都变成 [] 了

What these developers probably realize is that objects are passed by reference, so while setting myArray to [] does create a new array, other references stay the same! Big mistake! Use array truncation instead.

坑里的人们终于明白，原来传对象只是在传引用。因此当我把 myArray 重新赋值为 [] 时，确实会创建出一个新的空数组，但其它对老数组的引用仍然没变！大坑啊！还是换用截断的方法吧，少年。

5. Array Merging with push

5. 使用 push 来合并数组

I showed in point 2 that Array's slice and apply can do some cool stuff, so it shouldn't surprise you that other Array methods can do the same trickery. This time we can merge arrays with the push method:

在上面的第 2 节里，我展示了数组的 slice 和 apply 方法所能组合出的几个小妙招，所以对于数组方法的其它技巧，你应该已经做好心理准备了吧。这次我们使用 push 方法来合并数组：

var mergeTo = [4,5,6];
var mergeFrom = [7,8,9];

Array.prototype.push.apply(mergeTo, mergeFrom);

mergeTo; // is: [4, 5, 6, 7, 8, 9]

A wonderful example of a lessor-known, simple native method for completing the basic task of array merging.

这是一个典型的冷知识，简单的方法就可以实现数组合并这样的常见任务。

（译注：真正的技巧不在于 push 方法，而在于 aplly 方法的第二个参数。）

6. Efficient Feature/Object Property Detection

6. 高效探测功能特性和对象属性

Oftentimes developers will use the following technique to detect a browser feature:

很多时候开发者们会像下面这样来探测浏览器的某个特性：

if(navigator.geolocation) {
// Do some stuff
// 在这里干点事情
}

While that works correctly, it isn't always efficient, as that method of object detection can initialize resources in the browser. In the past, the snippet above caused memory leaks in some browsers. The better and more efficient route is checking for a key within an object:

当然这可以正常工作，但它并不一定有很好的效率。因为这个对象探测方法会在浏览器中初始化资源。在过去，上面的代码片断可能会在某些浏览器下导致内存泄露。更好、更快的方法是检查对象是否包含某个键名：

if("geolocation" in navigator) {
// Do some stuff
// 在这里干点事情
}

This key check is as simple as it gets and may avoid memory problems. Also note that if the value of a property is falsy, your check will fail despite the key being present.

键名检查十分简单，而且可以避免内存泄露。另外请注意，如果这个属性的值是假值，那么前一种探测方式将会得到“否”的结果，并不能真正探测出这个键名是否存在。

7. Event preventDefault and stopPropagation

7. 事件对象的 preventDefault 和 stopPropagation 方法

Oftentimes we trigger functionality when action elements like links are clicked. Obviously we don't want the browser to follow the link upon click, so we use our handy JavaScript library's Event.stop method:

很多时候，当一个动作元素（比如链接）被点击时，会触发某个功能。显然我们并不希望点击链接时浏览器顺着这个链接跳转，于是我们会习惯性地使用 JavaScript 类库的 Event.stop 方法：

$("a.trigger").on("click", function(e) {
e.stop();

// Do more stuff
// 在这里干点事情
});

（译注：不知道哪个类库有这个方法，估计其作用相当于 return false; 吧。语法看起来像 jQuery，但 jQuery 并没有这个方法，而且 jQuery 是支持 e.preventDefault 和 e.stopPropagation 方法的。）

The problem with this lazy method of stopping the event is that not only does it prevent the default action, but it stops propagation of the event, meaning other event listeners for the elements wont fire because they don't know about the event. It's best to simply use preventDefault!

这个懒方法有一个问题，它不仅阻止了浏览器的默认动作，同时也阻止了事件继续冒泡。这意味着元素上绑定的其它事件监听器将不会被触发，因为其它事件监听器根本就不知道有事件发生。此时不妨使用 preventDefault 吧！

Seasoned JavaScript developers will see this post and say "I knew those," but at one point or another, they got tripped up on some of these points. Be mindful of the little things in JavaScript because they can make a big difference.

JavaScript 老鸟们看到这篇文章可能会说“我早知道了”，但说不定什么时候，他们就会在某一点上栽跟头。提醒大家留意 JavaScript 中的各种小细节，失之毫厘谬以千里啊！

© Creative Commons BY-NC-ND 3.0   |   我要订阅   |   我要捐助

原文链接：https://github.com/cssmagic/blog/issues/21 （本内容由互联网资料整理而成）

CSS创建三角形（小三角）的几种方法

你可以在很多地方看到三角形（小三角）：tooltips提示框、下拉菜单、甚至在loading载入动画里。不管你喜欢还是不喜欢，这些小元素对各UI元素之间的联系关系式很重要的。

有一些不同的方法来设计并制作一个三角形，在本文中，我将介绍：

点此查看实例展示

编码 图片

假如你已经有了三角形的图片，并且减少HTTP请求，那么将这个图片转换成一个BASE64字符串是最好的解决方案。

有用的工具（用于图片转换成BASE64编码）:

优点

- 你可以按照自己的思想设计阴影，渐变等，然后对其进行转换编码

缺点

- 你需要使用一个图片编辑软件去设计

- 对于较大的图片，最终转换成字符串占用大小会很大

- 旧版本的浏览器，如：IE6/IE7是不兼容的

CSS 边框

这也是一个常用的使用方式，如tooltips信息提示框和下拉菜单。以上的示例，这是一个我最喜欢的方法创建小而且有用的三角形。

优点

- 很容易的通过修改一些CSS代码属性值而更改颜色和大小

- 这是一个跨浏览器的解决方案。

缺点

- 这个方式使用的是border,所以你不能添加阴影、渐变、和其他一些CSS3效果

- 请记住，IE6是不支持透明边界的-如果你关心这个问题

- 如果你使用火狐浏览器，要知道，CSS的“透明”有时可能不会是透明的，特别是在对角线边框，越多更多 here.

HTML 字符

它是基于使用可用的Unicode字符列表的字符。

优点

- 它是一个跨浏览器的技术

- 您可以使用CSS3的text-shadow属性添加阴影。

缺点

- 不能使用太多的CSS3效果，除了使用文字阴影。

- 在所有的浏览器，这是相当不可能实现像素完美。

CSS 旋转正方形

理论上,这种方式，你需要使用两个内容块，但是，并没有限制是使用两个元素，所以可以使用一个元素加一个伪元素。

- 创建一个内容里。例如100×100像素 – 这将包含旋转块。

- 旋转包含的这个块45度，从而获得一个菱形

- 将菱形的块向顶部便宜，然后设置溢出，设置父层容器截断

- There you go!

优点

缺点

- 这个解决方案不是跨浏览器的,首先是因为CSS3旋转。

HTML5 Canvas

在你的HTML文件中有以下的canvas元素：

<canvas id="triangle" height="100" width="100">Triangle</canvas>

这里的如何使用JavaScript绘制一个三角形：

var canvas = document.getElementById('triangle');
var context = canvas.getContext('2d');

context.beginPath();
context.moveTo(0, 0);
context.lineTo(100, 0);
context.lineTo(50, 100);

context.closePath();

context.fillStyle = "rgb(78, 193, 243)";
context.fill();

SVG (Scalable Vector Graphics)

这是如何在您的标签，你可以定义一个内联SVG三角形：

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" class="svg-triangle">
<polygon points="0,0 100,0 50,100"/>
</svg>

然后，只需添加一些样式：

.svg-triangle{
margin: 0 auto;
width: 100px;
height: 100px;
}

.svg-triangle polygon {
fill:#98d02e;
stroke:#65b81d;
stroke-width:2;
}

点此查看实例展示

最后的话

我必须承认，我并没有介绍太多在最后两个方式在这篇文章中所描述的：Canvas和SVG。这是因为他们是非常强大的，我必须介绍因为未来他很强大。不管怎样，事实是，他们的能力远远大于这些微不足道的三角形。这些方法，建立三角形，让我知道你的想法，你更经常使用的是什么方法？

转载请注明：大前端 » CSS创建三角形（小三角）的几种方法

上一篇 携程UED聘HTML+CSS高手（携程上海总部） 大前端D7主题1.5版本将在10月31日发布 下一篇

原文链接：http://www.daqianduan.com/css-arrow-3/ （本内容由互联网资料整理而成）

[译] 如何做到一秒渲染一个移动页面

声明：原文版权属于 Google，原文以 CC BY 3.0 协议发布，原文中的代码部分以 Apache 2.0 协议发布。中文翻译部分并非官方文档，仅供参考。

PageSpeed Insights analyzes a page to see if it follows our recommendations for making a page render in under a second on a mobile network. Research has shown that any delay longer than a second will cause the user to interrupt their flow of thought, creating a poor experience. Our goal is to keep the user engaged with the page and deliver the optimal experience, regardless of device or type of network.

PageSpeed Insights 可以分析一个网页是否遵循了我们的攻略，从而在移动网络上用不到一秒的时间完成一次页面渲染。研究显示，任何超过一秒钟的延迟都将打断用户的思维顺流状态，带来较差的体验。我们的目标是不论在任何设备或网络条件下，都要维持用户在网页中的沉浸状态，提供更好的体验。

It is not easy to meet the one second time budget. Luckily for us, the whole page doesn’t have to render within this budget, instead, we must deliver and render the above the fold (ATF) content in under one second, which allows the user to begin interacting with the page as soon as possible. Then, while the user is interpreting the first page of content, the rest of the page can be delivered progressively in the background.

想要满足一秒的时间预算并不是那么容易。不过幸运的是，我们并不需要在这个时间预算内渲染出整个页面，而是 要在一秒以内传输并渲染出首屏内容，让用户尽快与页面互动。然后，当用户在和首屏内容进行互动时，页面的剩余部分可以在后台持续加载完整。

（译注：原文中的“ATF”一词源自传统出版业，指的是报纸头版的中折线以上区域，这块黄金区域往往用来刊登最有吸引力的新闻。延伸到互联网领域，ATF 指的是页面中不需要滚动就可以看到的首屏内容。）

Adapting to high latency mobile networks

适应高延迟的移动网络

Meeting the one second ATF criteria on mobile devices poses unique challenges which are not present on other networks. Users may be accessing your site through a variety of different 2G, 3G, and 4G networks. Network latencies are significantly higher than a wired connection, and consume a significant portion of our 1000 ms budget to render the ATF content:

在移动设备上达到“首屏秒开”的准则，需要面对其它网络所遇不到的独特挑战。用户可能正通过 2G、3G 或 4G 等各种各样的网络来访问你的网站。移动网络的延迟要明显高于有线连接，并且将消耗我们“首屏秒开”预算中的一大部分：

- 200-300 ms roundtrip times for 3G networks

- 50-100 ms roundtrip times for 4G networks

- 3G 网络的往返时间将消耗 200-300 ms

- 4G 网络的往返时间将消耗 50-100 ms

3G is the dominant network type around the world, and while 4G deployments are in progress around the world, you should still expect that the majority of users will be accessing your page on a 3G network. For this reason, we have to assume that each network request will take, on average, 200 milliseconds.

3G 是全球范围内占据统治地位的移动网络，而 4G 网络正在普及之中，你需要明白你的主流用户仍然在使用 3G 网络来访问你的页面。基于这个原因，我们不得不假设平均每次网络请求将消耗 200 ms。

With that in mind, let’s now work backwards. If we look at a typical sequence of communication between a browser and a server, 600 ms of that time has already been used up by network overhead: a DNS lookup to resolve the hostname (e.g. google.com) to an IP address, a network roundtrip to perform the TCP handshake, and finally a full network roundtrip to send the HTTP request. This leaves us with just 400 ms!

明白了这一点之后，我们来倒推一下时间。如果我们来分析一下浏览器与服务器之间一次典型的通信过程，会发现 600 ms 的时间就已经被网络本身消耗掉了：一次 DNS 查询用于将主机名（比如 google.com）解析为 IP 地址，一次网络往返用于发起 TCP 握手，以及最后一次完整的网络往返用于发送 HTTP 请求。我们就只剩下 400 ms 了！

[Figure 1] Render a mobile page in 1 second

[图 1] 一秒渲染一个移动端页面

- DNS Lookup (200 ms)

- TCP Connection (200 ms)

- HTTP Request and Response (200 ms)

- DNS 查询 (200 ms)

- TCP 连接 (200 ms)

- HTTP 请求与响应 (200 ms)

600 ms mandatory 3G network overhead which you cannot do anything about

这 600 ms 是不可避免的 3G 网络消耗，你对此无能为力。

- Server Response Time (200 ms)

- Client-Side Rendering (200 ms)

- 服务器响应时间 (200 ms)

- 客户端渲染 (200 ms)

400 ms which you can optimize by updating your server and structuring your page appropriately (what the tool tries to help you with)

这 400 ms 是你可以优化的，只要你合理地更新你的服务器，并且适当地构建你的页面（这正是 PageSpeed Insights 这个工具可以帮到你的）。

Delivering the sub one second rendering experience

实现半秒渲染的体验

After subtracting the network latency, we are left with just 400 milliseconds in our budget, and there is still a lot of work to do: server must render the response, client-side application code must execute, and the browser must layout and render the content. With that in mind, the following criteria should help us stay within the budget:

在除去网络延迟之后，我们的预算只剩下区区 400 ms 了，但我们仍然还有大量的工作要做：服务器必须渲染出响应内容，客户端应用程序代码必须执行，而且浏览器必须完成内容的布局和渲染。了解了这些之后，下面这些准则将帮助我们控制住预算：

(1) Server must render the response (< 200 ms)

(1) 服务器必须在 200 ms 以内渲染出响应内容

Server response time is the time it takes for a server to return the initial HTML, factoring out the network transport time. Because we only have so little time, this time should be kept at a minimum - ideally within 200 milliseconds, and preferably even less!

服务器响应时间就是在除去网络传输时间之后，一台服务器首先返回 HTML 所花费的时间。因为我们剩下的时间实在太少了，这个时间应该控制在最低限度——理想情况下应该低于 200 ms，而且越少越好！

(2) Number of redirects should be minimized

(2) 重定向的次数应该减至最少

Additional HTTP redirects can add one or two extra network roundtrips (two if an extra DNS lookup is required), incurring hundreds of milliseconds of extra latency on 3G networks. For this reason, we strongly encourage webmasters to minimize the number, and ideally eliminate redirects entirely - this is especially important for the HTML document (avoid “m dot” redirects when possible).

额外的 HTTP 重定向会增加一次或两次额外的网络往返（如果需要再次查询 DNS 的话就是两次），这在 3G 网络上将导致几百毫秒的额外延迟。因此，我们强烈建议网站管理员们减少重定向的次数，而且最好完全消除重定向——这对 HTML 文档来说尤其重要（尽可能避免重定向到 “m.” 二级域名的行为）。

(3) Number of roundtrips to first render should be minimized

(3) 首次渲染所需的网络往返次数应该减至最少

Due to how TCP estimates the capacity of a connection (i.e. TCP Slow Start), a new TCP connection cannot immediately use the full available bandwidth between the client and the server. Because of this, the server can send up to 10 TCP packets on a new connection (~14KB) in first roundtrip, and then it must wait for client to acknowledge this data before it can grow its congestion window and proceed to deliver more data.

由于 TCP 在评估连接状况方面采用了一套特殊机制（参见 TCP 慢启动），一次全新的 TCP 连接无法立即用满客户端和服务器之间的全部有效带宽。在这种情况下，服务器在首次网络往返中，通过一个新连接（约 14kB）只能发送最多 10 个 TCP 包，接下来它必须等待客户端应答这些数据，然后才能增加它的拥塞窗口并继续发送更多数据。

Due to this TCP behavior, it is important to optimize your content to minimize the number of roundtrips required to deliver the necessary data to perform the first render of the page. Ideally, the ATF content should fit under 14KB - this allows the browser to paint the page after just one roundtrip. Also, it is important to note that the 10 packet (IW10) limit is a recent update to the TCP standard: you should ensure that your server is upgraded to latest version to take advantage of this change. Otherwise, the limit will likely be 3-4 packets!

考虑到 TCP 的这种行为，优化你的内容就显得十分重要。传输必要数据、完成页面首次渲染所需的网络往返次数应该减至最少。理想情况下，首屏内容应该小于 14KB——这样浏览器才能在一次网络往返之后就可以绘制页面。同时，还有一个关键点需要留意，10 个数据包上限（IW10）源自 TCP 标准的最近一次更新：你应该确保你的服务器软件已经升级到最新版本，以便从这次更新中获益。否则，这个上限将可能降到 3～4 个数据包。

(4) Avoid external blocking JavaScript and CSS in above-the-fold content

(4) 避免在首屏内容中出现外链的阻塞式 JavaScript 和 CSS

Before a browser can render a page to the user, it has to parse the page. If it encounters a non-async or blocking external script during parsing, it has to stop and download that resource. Each time it does that, it is adding a network round trip, which will delay the time to first render of the page.

浏览器必须先解析页面，然后才能向用户渲染这个页面。如果它在解析期间遇到一个非异步的或具有阻塞作用的外部脚本的话，它就不得不停下来，然后去下载这个资源。每次遇到这种情况，都会增加一次网络往返，这将延后页面的首次渲染时间。

As a result, the JavaScript and CSS needed to render the above the fold region should be inlined, and JavaScript or CSS needed to add additional functionality to the page should be loaded after the ATF content has been delivered.

结论就是，首屏渲染所需的 JavaScript 和 CSS 应该内嵌到页面中，而用于提供附加功能的 JavaScript 和 CSS 应该在首屏内容已经传输完成之后再加载。

(5) Reserve time for browser layout and rendering (200 ms)

(5) 为浏览器的布局和渲染工作预留时间 (200 ms)

The process of parsing the HTML, CSS, and executing JavaScript takes time and client resources! Depending on the speed of the mobile device, and the complexity of the page, this process can take hundreds of milliseconds. Our recommendation is to reserve 200 milliseconds for browser overhead.

解析 HTML 和 CSS、执行 JavaScript 的过程也将消耗时间和客户端资源！取决于移动设置的速度和页面的复杂度，这个过程将占用几百毫秒。我们的建议是预留 200 ms 作为浏览器的时间消耗。

(6) Optimize JavaScript execution and rendering time

(6) 优化 JavaScript 执行和渲染耗时

Complicated scripts and inefficient code can take tens and hundreds of milliseconds to execute - use built-in developer tools in the browser to profile and optimize your code. For a great introduction, take a look at our interactive course for Chrome Developer Tools.

执行复杂的脚本和低效的代码可能会耗费几十或上百毫秒——可以使用浏览器内建的开发者工具来收集概况、优化代码。如果你想深入这个话题，不妨看看我们的《Chrome 开发者工具交互教程》。

**Note: **The above is also not the complete list of all possible optimizations - it is a list of top mobile criteria to deliver a sub one second rendering time - and all other web performance best practices should be applied. Check out PageSpeed Insights for further advice and recommendations.

请注意：以上并未列举出所有可能的优化方案——只列出了一些移动端达成半秒渲染时间的基本准则——其它所有的网页性能最佳实践也应该运用起来。到 PageSpeed Insights 来看看，获取进一步的建议和推荐方案。

For a deep-dive on the above mobile criteria, also check out

如果需要深入探索上述移动端准则，请参阅：

- Optimizing the Critical Rendering Path for Instant Mobile Websites (slides, video).

- Instant Mobile Websites: Techniques and Best Practices (slides, video)

- 为极速移动网站优化渲染的关键路径 (幻灯片、视频).

- 极速移动网站：技巧与最佳实践 (幻灯片, 视频)

FAQ

常见问题

How do 4G networks impact above mobile criteria?

4G 网络对上述移动端准则有何影响？

Lower roundtrip latencies are one of the key improvements in 4G networks. This will help enormously, by reducing the total network overhead time, which is currently over 50% of our one second budget on 3G networks. However, 3G is the dominant network type around the world, and will be for years to come - you have to optimize pages with 3G users in mind.

较低的网络往返延迟是 4G 网络的一处关键改良。减少所有的网络损耗时间对网页性能将有巨大帮助，而目前在 3G 网络上这些损耗就占用了我们一秒预算中的大半时间。不管怎样，3G 仍然是全球最主流的移动网络，并且在今后几年都将如此——我们在优化网页时不得不把 3G 用户放在心上。

I am using a JavaScript library, such as JQuery, any advice?

我正在使用一个 JavaScript 类库，比如 jQuery，有什么建议吗？

Many JavaScript libraries, such as JQuery, are used to enhance the page to add additional interactivity, animations, and other effects. However, many of these behaviors can be safely added after the ATF content is rendered. Investigate moving the execution and loading of such JavaScript until after the page is loaded.

大多数 JavaScript 类库，比如 jQuery，通常用来增强页面、提供附加的交互、动画和其它效果。但是，大多数这些行为可以安全地在首屏渲染之后再加入进来。研究一下是否可以把这些 JavaScript 的加载和执行推迟到页面加载之后。

I am using a JavaScript framework, to construct the page, any advice?

我在正使用一个 JavaScript 框架来渲染整个页面，有什么建议吗？

If the content of the page is constructed by client-side JavaScript, then you should investigate inlining the relevant JavaScript modules to avoid extra network roundtrips. Similarly, leveraging server-side rendering can significantly improve first page load performance: render JS templates on the server, inline the results into HTML, and then use client-side templating once the application is loaded.

如果页面内容是由客户端 JavaScript 来渲染的，那么你应该研究一下是否可以把相关的 JavaScript 模块都内嵌进来，以免产生额外的网络往返开销。同样，利用服务器端渲染可以显著地改善首次页面加载的性能：在服务器端渲染 JS 模板，并内嵌到 HTML 中，然后一旦应用程序加载完成就立即在客户端渲染模板。

How will SPDY and HTTP 2.0 help?

SPDY 和 HTTP 2.0 协议会有什么帮助？

SPDY and HTTP 2.0 both aim to reduce latency of page loads by making more efficient use of the underlying TCP connection (multiplexing, header compression, prioritization). Further, server push can further help improve performance by eliminating extra network latency. We encourage you to investigate adding SPDY support on your servers, and switching to HTTP 2.0 once the standard is ready.

SPDY 和 HTTP 2.0 协议的目标都是通过更有效地利用底层的 TCP 连接（多路复用、头压缩、优先化处理），来减少页面的加载延迟。而且服务器 push 通过消除额外的网络延迟，可以进一步促进性能的改善。我们建议你为服务器增加对 SPDY 协议的支持，并且当 HTTP 2.0 标准就绪之后就立即切换过去。

How do I identify critical CSS on my page?

如何识别页面中的要害 CSS？

In Chrome Developer Tools, open the Audits panel, and run a Web Page Performance report, in the generated report, look for Remove unused CSS rules. Or use any other third party tool, or script, to identify which CSS selectors are applied on each page.

在 Chrome 开发者工具中，打开审计（Audits）面板，然后运行一次网页性能（Web Page Performance）报告。在生成的报告中，找一下“删除未使用的 CSS 规则（Remove unused CSS rules）”。也可以使用其它第三方工具或脚本，来识别每个页面分别应用了哪些 CSS 选择符。

Can these best practices be automated?

这些最佳实践可以自动化吗？

Absolutely. There are many commercial and open-source web performance optimization (WPO) products which can help you meet some or all of the criteria above. For open-source solutions, take a look at the PageSpeed optimization tools.

当然可以。有很多商业的或开源的网页性能优化（WPO）产品都可以帮你达成上述部分或全部准则。对于开源解决方案，不妨看看 PageSpeed 优化工具。

How do I tune my server to meet these criteria?

我怎样调整我的服务器来符合这些准则？

First, ensure that your server is running an up-to-date version of the operating system. In order to benefit from the increased initial TCP congestion window size (IW10), you will need Linux kernel 2.6.39+. For other operating systems, consult the documentation.

首先，确保你的服务器正在运行最新版的操作系统。为了从 TCP 初始拥塞窗口数量的增加（IW10）中获益，你需要 2.6.39+ 版本的 Linux 内核。对于其它操作系统，请查阅相关文档。

To optimize server response time, instrument your code, or use an application monitoring solution to identify your bottleneck - e.g., scripting runtime, database calls, RPC requests, rendering, etc. The goal is to render the HTML response within 200 milliseconds.

为了优化服务器的响应时间，请测评你的代码性能，或使用监控程序来发现性能瓶颈——比如脚本运行时、数据库调用、RPC 请求、渲染等等。最终目标就是在 200 ms 内渲染出 HTML 响应内容。

What about Content Security Policy?

内容安全策略（Content Security Policy）怎么办？

If you are using CSP, then you may need to update your default policy.

如果你正在使用 CSP，那么你可能需要更新你的默认策略。（译注：CSP 是一项用于防范 XSS 的安全性规范，具体参见 Content Security Policy - 维基百科。）

First, inline CSS attributes on HTML elements(e.g., <p style=...>) should be avoided where possible, as they often lead to unnecessary code duplication, and are blocked by default with CSP (disabled via “unsafe inline” on “style-src”). If you have inline JavaScript, then you will need to update the CSP policy with “unsafe-inline” to enable its execution. By default, CSP will block all inline script tags.

首先，尽可能避免在 HTML 元素中内联 CSS 属性（比如这样 <p style=...>），因为它们常常导致不必要的重复代码，而且默认会被 CSP 拦截（对 style-src 字段使用 unsafe-inline 指令可以禁用此类拦截）。如果你使用了内联的 JavaScript，那么你需要在 CSP 策略中使用 unsafe-inline 指令来令其执行。默认情况下，CSP 将拦截所有内联脚本标签。（译注：这里的“内联 JavaScript”包括多种形态的 HTML 内部的脚本代码，包括类似 <script>foo();</script> 这样的内嵌脚本标签、<a href="javascript:foo();"> 这样的伪协议 URL、以及 <a href="#" onclick="foo();"> 这样的事件处理属性。）

Have more questions? Please feel free to ask and provide feedback in our discussion group at page-speed-discuss.

还有其它问题？请在我们的 page-speed-discuss 讨论组内随意提问或提供反馈。

原文版本：2013-08-06 （如果原文已更新，请提醒我。）

© Creative Commons BY-NC-ND 3.0   |   我要订阅   |   我要捐助

原文链接：https://github.com/cssmagic/blog/issues/20 （本内容由互联网资料整理而成）

为什么移动Web应用程序很慢（译）

前些日子，看到Herb Sutter在自己的博客中推荐了一篇文章《Why mobile web apps are slow》，在推荐里他这样写道：

“I don’t often link to other articles, but this one is worth reading.”

我不经常链接到其它文章，但是这篇文章的确值得一读。

“He offers data (imagine!) to justly debunk many common memes and “easy answers” that routinely litter HN/Reddit/Slashdot comment threads.”

这句话挺难翻译的，大概意思应该是作者使用了确切的数据来支持自己的观点，而不像其他很多人一样只是随意地发出毫无根据的评论。”

“Don’t be distracted by the author’s viewpoint and emphasis on “iOS and Javascript” development – the article covers lots of important ground, including:

- developing for ARM vs. x86;

- developing for desktop vs. mobile;

- managed vs. native code performance;

- JIT issues vs. inherent language design tensions;

- why garbage collection is not at all the panacea it’s often billed to be and often needs to be emphatically avoided (did you realize Apple already jettisoned GC?); and

- as many of you know already, why if you’re serious about performance you’ll be seriously serious about memory usage and access patterns as a first-order issue.”

不要被作者的观点以及iOS和Javascript开发等字眼分散注意力——这篇文章包含了很多重要的基础知识，包括：

- ARM平台编程和x86平台编程比较；

- 桌面环境编程和移动设备编程比较；

- 托管代码和原生代码性能比较；

- JIT相关话题和语言内在的设计张力；

- 为什么垃圾回收不是宣传中所说的万能药，而且经常被强调要避免使用（你意识到苹果公司已经抛弃了GC吗？）；

- 就像你们中的很多人已经知道的那样，为什么如果你很在乎性能，那么你就应该认真严肃地将内存使用和访问模式作为最优先需要考虑的问题。

既然Sutter大神如此推荐，我就好好把这篇文章看下来了，的确收获颇丰，所以特意把这篇文章翻译下来，一方面加深理解，另一方面跟大家分享。我翻译的首要目标是可读性和流畅性，并不一定拘泥于字眼；难以翻译和习惯用英文表达的词汇会保留。我可以保证理解作者95%以上的意思（毕竟是技术类文章），但是作者的一些幽默我很可能没法传神地翻译出来，还请大家包涵。

（提示：这是一篇非常长的文章，认真读下来可能需要一段时间。下面是正文翻译。）

我写过不少文章来讨论为什么移动Web应用程序很慢，这也引起了不少的讨论。但是不幸的是，这些讨论没有像我喜欢的那样的基于事实。

所以我这篇文章的目地就是给这些问题带来一些真正的证据，而不是仅仅过来对骂。在这篇文章的中，你可以看到基准测试（benchmark），可以看到专家的观点，你甚至可以看到非常诚实（honest-to-God）的期刊文章。这篇文章有超过100个引用（不是开玩笑）。我不保证这篇文章能使你信服，甚至不保证这篇文章中的所有内容都是正确的（在这样大规模的文章中做到这一点几乎是不可能的），但是我可以保证这是一篇关于许多iOS开发者都抱有的想法——移动Web应用很慢并且会在可预计的未来继续如此——分析最完备和全面的文章。

现在我要警告你：这是一篇长得吓人的文章，差不多10000字。当然，这是我故意的。我更喜欢好文章，而不是流行的文章。我尝试使得这篇文章成为前者，同时宣扬我认同的风气：我们应该鼓励那些优秀的、基于证据的、有趣的讨论，不鼓励那些诙谐、哗众取宠的评论。

我写这篇的文章，在某种程度上是因为这是话题已经到了一种争论不休的地步。这不是另一篇争论的文章，如果你想看到30秒左右的对骂：“真的！Web应用很渣！”和“谁说的？Web程序挺好！”，那么这篇文章不适合你。另一方面，据我所知，到现在为止还没有一个关于这个话题全面的、正式的、理性的讨论。这篇文章中我尝试去理性地讨论这个激起千层浪的话题，尽管这可能是一个非常愚蠢的想法。这里我给自己辩护一下，我相信这个问题与那些本来可以更好地去讨论却没有这样做的人更有关系，而不是主题本身。

如果你想知道你那些原生代码（native code）程序员朋友为什么在如今开放的网络革命时期还在写着万恶的原生代码，那就把本页面加入书签吧，给自己倒杯咖啡，找出一个下午的时间，找到一个舒服的椅子，然后我们就正式开始吧！

简单回顾

我上一篇博客中写道：基于SunSpider的benchmark给出的数据可以看出当今的移动Web应用很慢。

如果你认为“Web应用程序”就是“一个网页加上一两个按钮”，那么你就可以让那些花哨的benchmark——比如SunSpider——滚一边去。但是如果你认为“Web应用程序”是指“简单的文字处理，简单的照片编辑，本地存储和屏幕之间的切换动画”，那么除非你有想死的心，否则你永远不会愿意在ARM上写Web应用程序。

你应该先读一下那篇文章，但是我还是在这边给你看下benchmark：

关于这个benchmark，主要三种主要的批评：

1. JS比原生代码要慢并不是什么新鲜事了，每个人在上第一学期的计算机基础课时讨论编译型语言、JIT语言和解释型语言是时候就知道了。问题是JS是不是慢到已经成为你现在所写软件的大问题了，但是像这样的benchmark并不能说明这个问题。

2. JS是很慢，这也的确是个问题，但是它在变得越来越快，所以在不久的未来，我们可以发现它不会那么慢了。所以大家一起学JS吧。

3. 我是Python/PHP/Ruby的服务器端的开发者，我不知道你们在说什么。我知道我的服务器比你们的移动设备快，但是如果我可以自信地保证使用真正的解释型语言写出支持上千个用户的代码，你们难道不能用一个带有高性能JIT的语言写出一个支持单个用户的代码吗？真的有那么难吗？

我有一个相当高的目标，那就是反驳以上所有观点：是的，JS的确是慢到一定程度了；不，它在不久的未来不会变得有多快；不，你在服务器端的编程经验不能正确地映射到移动应用中。

但是真正的问题在于，在所有讨论这个话题的文章里面，基本上没有人真正量化JS到底有多慢，或者提供某种真正有用的比较标准（相对于什么来说慢）。为了纠正这样的情况，我在这篇文章中提出了三种（不仅仅是一种）比较JavaScript性能的办法。我不会说“JS在什么情况下都慢”，而是真正量化它慢的程度，并且将它跟我们再平常编程经验中的事情做对比，这样你就可以根据这个结果结合自己的编程平台做出决定，你也可以自己计算下看看是否JavaScript适合你自己的特定问题。

OK，但是JS的性能相比于原生代码到底如何？

这是一个好问题。为了回答这个问题，我从Benchmark Game中随意抓取了一个基准测试。然后我找到了一个做同样benchmark的较老的C程序（老到不像很多新程序有一些x86特性）。我在自己的iPhone 4S上分别测试Nitro和LLVM。所有的代码已经传到了Github。

这是一个随机的测试，正如日常生活中运行的代码一样。如果你想要一个更好的实验，可以自己运行。我运行这个实验还有另外一个原因，就是因为其它的实验都不存在LLVM和Nitro的对比。

在这个综合的基准测试中，LLVM一致地比Nitro快4.5倍：

如果你在想“如果是计算密集型（CPU-bound）的功能，本地代码比Nitro JS快多少呢”，那么答案是差不多5倍。这个结果大致上和Benchmark Game在x86/GCC/V8上面的结果一致，那里面的GCC/x86通常比V8/x86快2到9倍。所以结果大致上是正确的，无论是ARM还是x86。

但是1/5的性能对每个人来说还不够好吗？

在x86上是足够好了。当渲染一个电子表格的时候，CPU的计算能有多密集呢？其实并不是那么难。问题是，ARM不是x86。

根据GeekBench的结果，最新的MacBook Pro的性能是最新的iPhone性能的10倍。这其实不算太大问题——电子表格没那么复杂。我们可以忍受10%的性能。但是我还要把它除以5？好家伙！我们现在只有桌面性能的2%了。

OK，但是文字处理到底有多难？我们可不可以用一个m68k芯片加上一个协处理器来搞定呢？这是一个可以回答的问题。你可能记不起来，Google Doc的实时协作之前事实上还不是一个正式的功能，后来他们进行了大规模的重写并且在2010年4月份加入到Google Doc里面。我们来看一下2010年浏览器的性能：

从图中可以清晰地看到，iPhone 4S在Google Docs的实时协作方面完全不是桌面网页浏览器的对手。当然了，它还是可以跟IE8比上一比的。恭喜iPhone 4S，可喜可贺。

我们再看看另外一个正经的JavaScript应用：Google Wave。Wave从来没有支持IE8，因为它实在是太慢了。

看到这些浏览器比iPhone 4S快多少了吗？

注意，所有支持的浏览器的得分都低于1000，其中那个得分3800的因为太慢了而被忽略了。iPhone得分为2400。差不多和IE8一样，太慢几乎无法运行。

这边要说明的是，在移动设备上实现实时协作是可能的，只是不太可能用JavaScript来实现。原生代码和Web应用的性能差距基本上和Firefox与IE8的性能差距差不多，这么大的差距足以影响正常的工作。

但是我感觉V8或者是现代JS已经有了接近C的性能了？

这取决于你怎么理解“接近”了。如果你的C程序运行了10ms，那么一个运行50ms的JavaScript程序差不多是接近C的速度了。如果你的C程序运行了10s，那么一个运行了50s的JavaScript程序对于大多数正常人来说很可能就不是接近C的速度了。

硬件角度

1/5的速度在x86上是没问题的，毕竟x86起点就比ARM快10倍，你还有很多上升空间。解决方案显然是让ARM变成10倍快，这样就可以跟x86竞争了，然后我们就可以不用做任何工作就可以得到桌面环境的JS性能了。

这个方法行不行得通取决于你是否相信摩尔定律，以及给每个芯片配置一个3盎司的电池是否可行。我不是一个硬件工程师，但是我曾经为一家大型半导体公司工作过，那里的人告诉我说当今硬件的性能基本上是制作工艺（process）起的作用。iPhone 5令人印象深刻的性能主要是因为其芯片工艺从45nm做到了32nm，减少了差不多1/3。但是如果想继续这么做，苹果就要达到22nm的工艺。

顺便提一下，Intel22nm工艺的Atom处理器现在还没上市。而且Intel不得不重新发明全新的半导体，毕竟原来的半导体在22nm级别已经不适用了。他们会把工艺授权给ARM？再好好想想吧。如今22nm的产品少之又少，而且大部分被Intel掌控着。

事实上，ARM似乎已经在着手在明年尝试28nm了（看看A7），同时Intel正在尝试22nm甚至在稍微晚些时候尝试20nm。从纯硬件的角度，我感觉具有x86级别性能的x86芯片很可能远远比具有x86性能的ARM芯片更早登录智能手机市场。

看一个前Intel工程师给我发的邮件：

我是一个前Intel工程师，刚开始从事于移动微处理器的工作，后来工作转向了Atom处理器。无论如何，我有一个很偏激的观点，即x86从较大的核心转向手机市场的难度远比ARM从头开始设计技术细节以达到x86的性能级别的难度要低很多。

再看一个机器人领域的工程师给我发的邮件：

你说得非常对，这些（译注：指的是ARM的发展）不会带来多大的性能提升，Intel可能在近几年之内就会有更高性能的移动处理器。事实上，移动处理器当前和桌面处理器面临着同样的问题，即工作频率达到3GHz左右的时候，再提高时钟速度就不可避免地使得功耗大大增加。这种情况同样会发生在下一代工艺上，尽管IPC（Instruction per Clock，即CPU每一时钟周期内所执行的指令多少）会得到一些提高（差不多10%-20%）。在面临这种限制的情况下，桌面处理器开始向双核和四核方向变化，但是移动处理器现在已经是双核和四核了，所以想提高性能不是那么容易。

摩尔定律无论怎么说都可能是正确的，但是这需要整个移动生态环境向x86环境转变。这并非完全不可能，毕竟曾经有人做过这样的事。但那是在移动处理器一年才卖出去100万个的时候做的，不像现在，每个季度就可以卖出62万个芯片。那个时候现成的虚拟化环境可以模拟出老架构的60%的速度，而按照现在的研究来看，虚拟化系统上运行优化过的（O3）ARM代码的速度已经接近27%了。

如果你坚信JavaScript的性能最终会到达一个合理的水平，那么硬件性能的提升绝对是最好的方式。要么Intel会在5年之内开发出可行的iPhone芯片（这是有可能的），并且苹果迅速转向x86架构（这是不太可能的），或者ARM能够在未来的10年之内得到性能的飞跃。但是在我看来，10年是一个很长的时间，长到足够使某件事情可能成功。

恐怕我的硬件的知识只能分析到这里了。我可以告诉你的是，如果你相信ARM可以在未来的5年之内填补与x86之间的性能差距，那么第一步就是找到一个在ARM或者x86上工作的人（也就是真正懂硬件的人），让他同意你的看法。我写这篇文章之前，曾近咨询过很多有很高资质的硬件工程师，他们所有人都拒绝公开发表这个观点，这让我感觉这个观点不是很靠谱。

软件角度

这是一个很多优秀软件工程师犯错误的地方。他们的思路是这样的：JavaScript已经变得更快了，并且它会变得更快。

这个观点的前一部分是正确的，JavaScript的确变得快很多。但是我们现在已经达到了JavaScript性能的顶点了，它不可能变得更快多少。

为什么？其实前一部分JavaScript的性能提升从某种程度上是硬件的原因，正如Jeff Atwood写道：

我感觉从1996到2006之间JavaScript的性能变快了100倍。如果Web 2.0主要建立在JavaScript上的话，这很可能主要是因为摩尔定律所带来的硬件性能提升。

如果我们把JS的性能提升总结为硬件性能提升的话，那么JS的已有的硬件性能提升不能预测未来的软件性能提升。这就是为什么如果你相信JS会变得更快的话，最有可能的方式就是硬件变得更快，因为历史趋势就是如此。

那么JIT如何呢？V8，Nitro/SFX，TraceMonkey/IonMonkey，Chakra等等？当然，当他们刚刚问世的时候，的确是很了不起的（但或许不像你认为的那么了不起）。V8在2008年9月发布，我找到了一份差不多那个时候同期的Firefox 3.0.3，看看它的性能：

不要误解我的意思，9倍的性能提升的确值得称赞，毕竟这差不多是ARM和x86之间的性能差距了。即便如此，Chrome 8和Chrome 26之间的性能却呈现出了水平线，因为自从2008开始，几乎没有什么重大的事情发生。其他浏览器厂商都已经赶上来了，有些快有些慢，但是没人真正提高过JavaScript的性能了。

JavaScript性能在提升吗？

这是我Mac上的Chrome 8（可运行的最早版本，2010年12月份的版本）和Chrome 26。

看不出差别？因为根本没有差别。JavaScript的性能最近根本没有得到大的提升。

如果你感觉现在的浏览器比2010年的浏览器跑的快的话，那很可能是因为你有了一台更快的电脑，但是这与Chrome的性能提升没什么关系。

更新：有些聪明的人指出SunSpider现在不是一个好的benchmark（并且拒绝提供任何实际的数字或其它什么）。为了能够可以理性地讨论，我在一些旧版本的Chrome上面运行Octane（一个Google的benchmark），的确显示出了一些性能提升：

在我看来，在这个期间的性能提升还是太小，不足以支撑JS马上就会足够快这样的论调。然而，要说我过分强调这个情况也没错，毕竟JavaScript的计算密集型操作的确在发生变化。但是推我来说，这些数字可以得出更大的推断：这些性能提升的幅度还不足以在一定时间之内使得JavaScript的速度赶上原生代码。你需要性能达到2-9倍才能跟LLVM竞争。这些提升是好的，但还不足够好。更新结束。

问题是，让JavaScript采用JIT技术是一个60年前就有的想法，并且这60年来一直有人在研究。数以千计的你可能想到的编程语言对JIT的实现都证明这是一个好主意。但是既然我们已经做到了，我们已经用完了这个60年前的想法。伙计们，就是这样的，表演结束了。或许我们可以在未来的60年之内想到另一个好办法。

但是Safari恐怕比以前要快吧？

Safari 7 是不是比其它的浏览器快3.8倍？

这个结论或许对苹果来说很容易得到，但是这个版本的Safari在NDA协议之下，所以没有人能够公开关于Safari性能的独立参数。但是我可以仅仅根据现在已经得到的信息来做一些分析。

我发现一些现象很有意思。第一、苹果官方在公开的JSBench上的数据要比在他们在较老的benchmark（如SunSpider）上给出的数据高出不少。现在JSBench背后有一些非常酷的名字，包括JavaScript之父Brenden Eich。但是和传统的benchmark不一样，JSBench的工作方式不会考虑整数和其它重要因素，它反而自动为Amazon、Facebook和Twitter提供的内容进行优化，而且根据它们提供的内容来建立benchmark。

如果你在写一个多数人用来浏览Facebook的浏览器，我可以理解用一个只测试Facebook性能的benchmark是很有用的。但是从另外的角度讲，如果你在写一个电子表格的程序，或者游戏，或是一个图片过滤应用，在我看来传统的benchmark（注重整数运算和md5哈希）会更能够准确地帮你预测出Facebook的分析代码有多快。

另一个重要的事实是，苹果声称的在SunSpider上性能的提升并不能代表其它东西的提升，Eich et al在这篇提到苹果所偏爱的benchmark的文章中写道：

图中清楚地显示出了Firefox的3.6版本比1.5版本在SunSpider的benchmark上性能提高了13倍。但是当我们看它在amazon的benchmark上的性能表现时，发现只有较谦虚的3倍的提升。更有意思的是，在过去的两年时间，在amazon的benchmark上的性能提升其实是被夸大了。这意味着在SunSpider上做的一些优化几乎对amazon没有太大作用。

在这篇文章中，JavaScript之父和Mozilla的首席架构师之一曾公开承认在过去的两年之内Amazon的JavaScript性能几乎没有提升，没有发生过什么特别重要的事情。从这一点你也可以看出来，那些营销人员这些年都在过分夸大自己产品的性能。

他们继续争辩道：对于那些人们用来浏览Amazon网页的浏览器来说，运行Amazon的benchmark比运行其它benchmark要更能准确地预测出浏览器的性能（这是当然了……），但是这些手段不会帮助你更好地写出一个照片处理程序。

但是无论怎么说，从我可以看到的公开信息来看，苹果声称的3.8倍的性能提升对你来说几乎没有什么太有用的东西。我可以告诉你，如果我有一些能够反驳苹果声称击败Chrome的benchmark的话，我将不被允许发布它们。

所以，我们总结一下这一节，如果有一些人拿出一个柱状图来显示网页浏览器变得更快了，那并不能真正说明整个JS变得更快了。

但是还有一个更大的问题。

并非为性能而设计

下面这段话出自于Herb Sutter，现代C++中最著名的人物之一：

在过去的20年里，有一种很难根除的文化基因——只要的等下一代的（包括JIT和静态）编译器出来，托管语言就会变得和原生语言一样高效。是的，我完全希望C#和Java编译器能够不断提高，包括JIT和类NGEN的静态编译器。但是，它们永远不会消除与原生代码之间的效率差距，有两个原因：一、JIT编译不是主要问题。根本原因更为基本：托管语言在编程人员的开发效率（当时的确是个问题）和程序的运行效率之间从设计上做了故意的妥协。特别的，托管语言选择选择在所有的程序上添加额外的性能开销，尽管你根本没有用到一个特性，你都会受到这个特性带来的额外的性能开销。主要的例子是assumption/reliance、垃圾回收、虚拟运行环境和元数据等功能在托管语言中是默认打开。当然还有其它的例子，比如托管代码中函数默认是virtual的，而C++代码中的函数是默认inline的。1盎司（译注：12盎司=1磅）的内联阻止（inlining prevention）抵得上1磅的去虚拟化优化（devirtualization optimization cure）。

下面这段话出自于Mono项目组的Miguel de Icaza，他是为数不多的“维护着一个主流JIT编译器的人”。他说道：

关于主流托管语言（.NET、Java和JavaScript）的虚拟机之间的差异，有一个比较准确的说法。托管语言的设计者在他们设计一门语言的过程中更倾向于安全性，而不是性能。

或者你可以找Alex Gaynor谈一谈，他负责维护和优化Ruby的JIT编译器，并且也为Python的JIT的优化工作做出了贡献：

这是加在这些具有高生产力的动态语言身上的诅咒。它们使得创建一个哈希表十分容易。这是一件非常好的事情。我认为C程序员多数不太会使用哈希表，因为对他们来说用哈希表实在是是一件痛苦的事情。原因有二：第一，你没有一个内置的哈希表；第二、当你尝试去使用的时候，你会左右碰壁。对比来看，Python、Ruby和JavaScript程序员都过度使用哈希表了，因为使用它们实在太容易了……所以大家都不在乎。

Google似乎意识到了JavaScript正面临着性能的瓶颈：

复杂的Web应用（这是Google比较擅长的）在某些平台上正在面临着不小的挣扎，主要是因为这些应用用到了一些不能被性能调优的语言，这些语言有内在的性能问题。

最后，我们听听权威人士的意见。我的一个读者向我指出这段Brenden Eich的评论。正如你所知，他是JavaScript之父。

有一点Mike没有强调：得到一个更简单的语言。Lua比JS简单得多。这意味着你可以写出一个简单的解释器使得它跑得足够快，同时能够保持对trace-JIT代码的尊重（这和JS不同）。

稍微下面一点又提到：

关于JS和Lua之间的差别，你可以说这完全是正确的设计和工程上的问题，但是内在的复杂性区别还是很大。你当然可以把较难的案例从热路径中删除，但是他们也会因此付出代价。JS比Lua有更多的更难的案例。一个例子是：Lua（没有显式的元表使用）没有像JS中的原型对象链（prototype object chain）的东西。

在这些真正从事相关工作的人当中，持有JS或者是其它动态语言能够赶上C语言性能这个观点的，只占极少数（very much the minority）。到处都有和主流想法不同的人，所以根本无法没有什么办法能够达到真正的一致。但是，从语言的角度说到JIT语言是否能够赶上原生语言的效率，他们给出的答案都是“不，不可能，除非修改语言本身或者API”。

但是还有一个更更大的问题。

都是因为垃圾回收

你可以发现，CPU问题、CPU相关的benchmark以及所有有关CPU的设计决定，都只是故事的一半。故事的另一半是内存。内存问题现在看来是如此的巨大，大到使整个CPU的问题看上去都仅仅是冰山一角。实际上可以讨论的是，所有关于CPU的讨论都是转移注意力的话题（red herring）。你接下来要阅读的应该会完全改变你对移动设备软件开发的理解。

2012年，苹果做了一件非常奇怪的事情（当然了，除非你是John Gruber，能够看到它的到来）。他们把垃圾回收从OSX中除去了。真的，你可以去看看程序员指南。标题右边有一个大大的“不推荐（Not Recommended）”。如果你之前是Ruby、Python、JavaScript、Java、C#或是其它任何1990年代之后诞生的语言的开发者，这应该会让你感觉很奇怪。但是这很可能不会影响到你，因为你很可能不在Mac下面使用ObjC，在HN点击下一个链接。但是这仍然看上去很奇怪，毕竟GC一直被大家使用着，而且它的价值也得到了证明。为什么你要反对它呢？苹果是这么说的：

我们十分坚信ARC才是内存管理的正确方式，所以我们决定使OSX上的垃圾回收变成过时的（deprecate）。——ession 101, Platforms Kickoff, 2012, ~01:13:50

这段话没有告诉你的是，当听到这句话的时候，台下的观众爆发出了热烈的掌声。OK，这就变得真的非常奇怪了。你是不是在告诉我有那么一个屋子里的程序员在为了垃圾回收之前的那种混乱的回归而鼓掌？你可以想象下如果Matz在RubyConf上宣布GC过时的时候整个会场的寂静，几乎一颗针掉在地上都听得到声音。而这群人却因此而高兴？太古怪了吧？

你应该根据这些古怪的反应发现一些你现在看不到但是却是在真正发生的事情，而不是仅仅把这些事情归结于这群人对于苹果的狂热。这些正在发生的事情就是我们下面就要讨论的主题。

思维过程是这样的：把一个工作得好好的垃圾回收器从一个语言中拿出来简直是疯了吧？一个简答的解释可能是ARC可能仅仅是苹果为了给垃圾回收披上一层美丽新装而创造的一个营销词汇，所以这些开发者是为了这种升级而不是降级而鼓掌的。事实上，这就是很多iOS簇拥们的抱有的想法。

ARC不是一个垃圾收集器

所有的那些认为ARC是某种垃圾回收器的人，我想通过下面这个苹果的幻灯片给你迎头一击（beat your face）：

这与和垃圾回收有类似名字的算法无关。它不是垃圾回收，他不是什么像垃圾回收的东西，它表现得一点都不像垃圾回收，它不会打乱任何保留周期，它没有去回收任何东西，它甚至没有去做扫描。OK，故事结束，它绝对不是垃圾回收。

因为正式的文档还在NDA协议下，所以有很多传言认为这并不是真的（但是细则已经可以看得到了，没有任何借口了），而且很多博客都纷纷说这些不是真的。它是真的。不要再讨论了。

垃圾回收不像你的经验让你感觉的那样可行

这是苹果在压力之下给出的关于ARC和GC的说法：

在愿望清单的顶端上我们能为你们做的最重要的事情就是把垃圾回收带到了iOS中，而这恰恰是我们最不应该做的。不幸的是，垃圾回收给性能带来了很多次优的影响。你程序中的垃圾回收会使得你的内存使用率变得很高，而且垃圾回收器经常在不确定的时间点上被触发而导致非常高的CPU使用率，从而打断用户正在做的事情。这就是为什么GC不适合在我们的移动平台上使用的原因。对比来看，带有获取和释放（retain/release）的手动内存管理学起来比较难，坦率的讲有些像痔疮（译注：这个翻译可能不准确，原文是pain in the ass）。但是它产生了更好更可预测的性能，这也是为什么我们选择手动内存管理作为我们内存管理策略的基础的原因。因为在外面真实的世界，高性能以及用户体验的连续性是我们的用户最看重的。（译注：在苹果看来，用户体验要比开发者体验重要。）~Session 300, Developer Tools Kickoff, 2011, 00:47:49

但是这还是完全疯狂了，不是吗？这只是开始：

1. 这可能会直接影响你整个职业生涯对于垃圾回收语言给桌面和服务器上带来影响的理解；

2. Windows Mobile、Andriod、Mono Touch以及所有其它移动平台上的GC似乎都可以挺好地工作。

所以让我们反过来看这个问题。

移动平台上的GC和桌面平台上的GC不是同一回事

我知道你在想什么，你是一个有了N年开发经验的Python程序员。现在是2013年了，垃圾回收完全可以解决问题。

这是一篇你想看到的文章，似乎问题并没有解决：

如果你在这篇文章中其它什么都记不得，那么请记住这张图。Y轴是垃圾回收所用的时间，X轴是“相对的内存足迹”，相对于什么？相对于所需的最小内存。

这张图想说明的是，“如果你有6倍以上你实际需要的内存，那么使用垃圾回收是没有问题的。但是如果你只有小于4倍你实际需要的内存，那么灾难就要降临了。”。但是不要相信我的话：

特别的，如果垃圾回收时系统拥有5倍于所需的内存时，它的运行时性能差不多甚至是超过显式内存管理。但是，垃圾回收的性能在必须使用小堆（small heap）的情况下会出现急剧下降。如果有3倍于所需的内存的话，它会跑得慢17%；如果只有2倍于所需的内存的话，会慢70%。垃圾回收比物理内存的换页更容易受到内存不足的影响。在这种情况下，我们所测试的所有垃圾回收器相对于手动内存管理都出现指数级的性能下降。

现在我们再来比较一下显式内存管理的策略：

这些图显示，如果可用内存在合理的范围的情况下（但不足以容得下整个应用），显式内存管理器都要比垃圾回收器快太多。比如说，pesudoJBB以63M的可用内存运行，Lea allocator在25s的时间内完成运行。在相同可用的内存下运行GenMS，花了超过10倍的时间来运行（255s）。我们可以看到其它benchmark套件的相同趋势。最值得一提的例子是213 javac和Lea allocator一起在36M的内存下运行，总体运行时间是14s，而与GenMS一起运行的情况下，运行时间为211s，用了超过15倍的时间。

基本的事实是，在内存受限的环境下垃圾回收性能的下降是指数级的。如果你在桌面电脑上写Python或者JS程序的话，你整个的体验可能是这幅图的右边部分，你可以一辈子都体会不到垃圾回收带来的性能问题。花点时间想想这幅图的左边部分，并且想想我们该如何应对。

iOS上有多少可用内存？

这一点很难准确地描述。从iPhone 4到iPhone 5，这些设备上的物理内存从512M到1G不等。但是其中很大一部分为系统预留了，还有更大的一部分为多任务处理预留了。所以唯一真正的方法是在不同的情况下进行尝试。Jan Ilavsky写了一个非常有用的工具来完成这个任务，但是貌似没有人公开任何数据。但这一点现在已经改变了。

现在，在一种“正常”的情况下（这一点很难具体说清楚是什么意思）进行测试是非常重要的，因为如果你在一台刚刚启动的机器上测试的话，你会得到更好的数据，毕竟你的系统里面没有Safari所打开的页面。所以我就在“真实世界”的情况下拿出一些我公寓里的设备进行本次benchmark。

你可以点击进去看看详细的结果，大体上来说，在iPhone 4S上，当你的程序使用了40M内存的时候就会得到警告，而使用了213M内存的时候，程序就会被杀死；在iPad 3上，使用400M左右时获得警告，而使用550M左右的时候，程序被杀死。当然了，这些也仅仅是数字而已，如果你的用户在听音乐或者在后台跑一些程序，你可用的内存会比我测试里面可用的内存更少，这只是给你一个思路而已。这么多内存看上去不少（213M应该对每个人来说都足够了，是吧？），但实际上这还不够。举个例子，iPhone 4S拍照时的分辨率为3264*2448，每张照片有超过30M的位图数据。如果你在内存里加载了2张照片你就会获得警告，而如果加载了7张照片，程序就会被杀死。哦，你打算给你写个循环在你的相册中逐个处理？程序会被杀死。

还有一点需要十分注意：实际的情况下，一张照片可能存在于内存的多个位置。比如说，如果你在拍照片，那么你在以下位置都有数据：1) 你通过屏幕看到的摄像头中的数据，2) 摄像头实际上拍到的照片数据，3) 你尝试写到存储卡中的压缩JPEG缓冲数据；4) 你准备在下一个屏中显示的数据；5) 你准备上传到某个服务器中的数据。

在一些点上你会发现，保留30M的缓冲区去显示照片缩略图是一个非常不好的想法，因为这样你会引入更多的数据：6) 用来保留下一屏显示合适大小照片的缓存；7) 用于在后台重新调整照片大小的缓存（在前台做实在太慢了）。然后你发现你可能真正需要5个不同的大小，然后你的程序就不是一般的慢了，而是慢到让人抓狂。在实际的应用程序中，仅仅是处理一个照片就会遇到内存的瓶颈并非罕见的事情。但是不要相信我说的话：

你能做的最糟糕的事情就是在内存不充足的情况下在内存中缓存图片。当一张图片被画成位图或者显示到屏幕上时，我们就不得不把照片解码为位图。位图的每个像素点为4字节，无论原始图片多大都是如此。每当我们将它解码一次，位图就会绑定到图片本身并且一直维持到这个对象生命周期结束之时。所以如果你把图片加载到内存而且曾经显示过一次，那你现在就会在内存中保留整个位图，直到你释放它为止。所以永远不要把UIImage或者CGImage放到缓存中，除非你有一个非常明确（但愿是非常短期的）的目标。- Session 318, iOS Performance In Depth, 2011

你甚至不要相信上面的话！你给自己分配的内存其实只是冰山一角。下图是苹果一张幻灯片中给出的冰山的全图。Session 242, iOS App Performance – Memory, 2012:

你可以从两方面考虑这个问题。第一、在213M可用内存的情况下，在iOS上写一个照片处理程序比在桌面上写一个要困难许多。第二，你在iOS上写一个照片处理程序时，你对内存的需要会更多，因为你的桌面程序没有一个可以放进你口袋的摄像头。

我们可以看看另外一个例子：在iPad 3上，你要显示一个视频，这种照片的大小很可能比你电脑上的视频要大不少（后面的高分辨率摄像头，差不多2000-4000像素）。每一帧要显示的就是一个12M的位图。如果你对内存的使用很节省的话，每一时刻你可以在内存中保留45帧的未压缩视频或动画缓存，也就是在30fps的情况下每1.5s，在60fps的情况下0.75s。你想为一个全屏的动画预留缓存？应用被杀死。值得指出的是，AirPlay的延迟是2s，所以对于任何多媒体类型的应用，你几乎是保证没有足够的内存。

这种情况下我们同样面临着和照片的多个数据拷贝差不多的问题。比如说，苹果指出，“每一个UIView背后都有一个CALayer，而且只要CALayer存在于在这个层次中，对应的图片数据会一直保存在内存中”。这意味着，很可能有许多中间的渲染数据的拷贝存在于内存中。

还有剪切矩形和备份存储这些可能会占用内存的事情。这样的数据处理架构事实上是非常高效的，但是这带来的代价是程序会尽可能地占用内存。iOS不是为低内存使用而设计的，它是为了快速运行而设计的。这没有和垃圾回收扯在一起。

我们同样需要从两个方面考虑问题。第一，你在一种内存非常紧缺的情况下做出动画效果；第二、做出这样超级高质量的视频和动画是需要极大的内存的。而为了使得普通消费者买得起消费级别的、具有高摄像头分辨率的产品，这种糟糕的、内存受限的环境几乎是必然的选择。如果你想写一个软件来毫无压力地播放视频，那么你就得说服别人为了屏幕多花700美元，或者花500美元买一个iPad，它实际上已经包含了一个内置的电脑。

我们会获得更多内存吗？（更新）

一些聪明的人说：“OK，你说了很多关于我们不会有更快的CPU。但是我们应该回有更多的内存吧？这正是桌面环境上发生的事情。”

这种理论的一个问题是，ARM平台上的内存就在处理器本身上，这被称为package on package。所以在ARM上获得更多的内存几乎和提高CPU性能是同一个问题，因为它们归根结底是同一件事情：在CPU上集成更多的晶体管。内存晶体管处理起来稍微容易一些，因为它们是统一的，所以不是那么难，但实际上也不是那么简单。

如果你看看iFixit的A6的照片，你会发现在CPU模具最表面的硅几乎100%都是内存。这意味着，如果你想拥有更多的内存，你要么使得制作工艺更精细，要么提高模具的大小。事实上，如果你把工艺的大小做归一化处理，那么其实伴随着每次内存升级，你的模具都在变得更大。

硅其实是一种不完美的材料，为了获得更大的尺寸所付出的的代价是指数级增长的。它们也很难维持较低的温度，也很难放进小设备中。它们和制作出更好的CPU的目标是重复的，因为内存也面临同样的问题：CPU最上层的硅中需要放进更多的晶体管。

我搞不懂的是，面临着PoP的这些问题，CPU厂商们继续使用PoP的方式为系统提供内存。我还没有遇到任何ARM工程师能够解释这一点。或许以下的评论可能会帮助我们理解。我们有可能从PoP架构转向电脑中采用的分离内存模块，而且我感觉这比较可行。原因很简单，将内存分为单独的模块比制造出更大的芯片和进一步减小制作工艺对厂商来说毫无疑问成本是更低的。但是现在所有的厂商都在不停地尝试提高制作工艺或者制作出更大的芯片，而不是把内存模块独立出来。

然而，一些聪明的工程师曾经给我发了邮件让我填补了这方面的空白。

一个前Intel工程师说道：

PoP内存模型可以大量减少内存延迟，也可以减轻路由问题。但是我不是ARM工程师，也不确定这是否是全部的原因。

一个机器人学的工程师提到：

当PoP内存不够用时，“3D”内存会提供足够大的内存：内存芯片在生产的时候堆叠在一起，1G的RAM在同一层堆成10层向上，就像现在的硬件模型一样。但是，这样的开销会很大，频率和电压都要相应地降低使得电力消耗处于一个合理的水平。

移动RAM的带宽不会像最近提高得这么快了。带宽被连接SoC和RAM包的总线的数量所限制。当前RAM的总线多数使用的是高性能SoC的圆柱体表面。SoC的中间部分不能用来加入RAM总线，因为这些RAM包是层叠的。接下来的重大改变应该回来自于将SoC和内存放在一个单独的、高度集成的包中，允许更小、更密集和大量的RAM总线（更大的带宽），并给SoC设计和更低的RAM电压带来更多自由。根据这样的设计，更大的缓存也就可能成为现实，因为RAM可能用更高的带宽放在SoC模具中。

但是Mono/Andriod/Windows Mobile平台怎么解决这个问题呢？

这个问题事实上有两个答案。第一个答案我们可以从图中看出。如果你发现你有6倍于所需的内存，垃圾回收其实是非常快的。举个例子来说，如果你在写一个文本编辑器，你可能可以用35M的内存完成自己要做的所有事情，这是我的iPhone 4S会崩溃内存上界的1/6。你可能在Mono上写个文本编辑器，看到非常不错的性能，然后从这个例子中得出如下结论：垃圾回收十分适合这个任务。你是对的。

然而Xamarin框架在案例中有一个飞行器模拟器。很显然的是，垃圾回收对于现实生活中的较大的应用程序来说是不适合的。难道不是吗？

你在开发和维护这个游戏时一定会遇到什么样的问题？“性能一直是一个大问题，而且将持续是我们再跨平台中会遇到的最大的问题之一。最初的Windows Phone设备是非常慢的，我们不得不花很多时间来优化程序，使得它达到一个体面的帧率。我们不仅仅在飞行模拟代码上进行优化，而且在3D引擎上优化。垃圾回收和GPU的弱点是最大的瓶颈。

程序员不约而同地声称垃圾回收是最大的瓶颈。当你的案例中的人在抱怨的时候，那应该是一个足够引起你重视的线索了。但是Xamarin可能是一个局外人，我们还是来看看Andriod开发者怎么说的吧：

请记住下面是我在我的Galaxy Nexus上运行的情况：无论怎么说都是性能非常不错的设备。但是看看渲染的时间！我在电脑上只要花几百毫秒就可以渲染出这些图片，可是在这台手机上却花了超过两个数量级的时间。渲染“inferno”图片超过6s？这简直是疯了吧！要生成一副图片，需要10-15倍的时间来运行垃圾回收器。

另一个开发者：

如果你想在Andriod手机上为实时物体识别或者基于内容的现实增强进行对照相机图片的处理，那么你很可能听说过照相机预览回调（Camera Preview Callback）的内存问题。每次Java程序尝试从系统获取预览图片时，系统就会创建一大块新的内存。当垃圾回收器释放这块内存时，系统会卡住（freezes）100ms到200ms。如果系统在高负载的情况下，事情可能会变得非常糟糕（我曾经在手机上做过物体识别——天呐，它几乎把整个CPU都占用了）。如果你看过Andriod 1.6的源代码你就会知道，这只是因为这个功能的包装类（wrapper，用来包装原生代码）每次在一个新的帧可用时都会申请一个新的字节数组。当然，内置的原生代码可以避免这个问题。

或者，我还可以去看看Stack Overflow：

我负责在Andriod平台上为Java写的交互式游戏进行性能调优。很多时候，当候垃圾回收开始工作会让使得游戏的画图和交互功能发生打嗝。通常情况下这种打嗝持续不到1/10s，但有的时候在比较慢的设备上会长达200ms。如果我在一个内部循环中使用树或者哈希表，我就就知道我要很小心，或者甚至不用Java标准的Collections框架，而是自己重新实现一个，因为我承担不起垃圾回收带来的额外开销。

这是一个“接受的答案（accepted answer）”，有27个人赞同：

我也是Java手机游戏的开发者……避免垃圾回收（垃圾回收可能在某个点被出发从而大幅降低你游戏的性能）的最好方法就是不要再游戏的主循环中创建对象。实在没有什么“简洁”的方式来处理这种问题，或许只有手动追踪这些对象了，真悲哀。这地也是目前大部分当前移动设备上性能优良的Java游戏所采取的的方式。

我们来看看Facebook的Jon Perlow怎么看待这个问题：

对于开发流畅的安卓应用来说，GC是一个非常大的性能问题。在Facebook，我们遇到的最大问题之一是GC会使得UI线程暂停。当我们处理很多位图数据时，GC被触发的频率很高，而且难以避免。GC经常导致掉帧的问题。即使GC只会阻塞UI线程几毫秒，但这却会严重影响原本需要16毫秒的帧渲染。

OK，我们再听听一个微软MVP的说法：

通常情况下，你的代码在33.33ms之内就会完成执行，从而使得30fps的帧率变得很不错。但是当GC运行的时候，它会占用那个时间。如果你的堆比较整洁和简单，那么GC一般可以运行得不错，不会对程序产生什么影响。但是让一个简单的堆处于一个使GC可以快速运行的情形是一件困难的编程任务，它要求大量的计划和/或程序重写，即使是这样也不是完全安全的（有时在一个复杂的、有很多玩意的游戏中你的堆里面有很多内容）。更简单的方法是（假设你能这么做），在游戏过程中限制甚至是禁用内存分配。

在有垃圾回收的情况下，在游戏中保证一定会赢的方法就是不要玩（作者的幽默，原文：the winning move is not play）。比这种哲理稍弱的一种形式如Andriod官方文档中所说的：

对象创建永远不是免费的。带有线程级别内存池的垃圾回收器使得内存分配的成本变得稍微低一些，但是分配内存永远比不分配内存开销大。因为当你在程序中分配对象时，你会强制垃圾回收周期性地工作，从而使得用户体验不是那么流畅。Andriod 2.3当前引入的垃圾回收器有一些作用，但是要是应该避免不必要的工作。因此，你应该避免创建你不需要的对象实例。一般而言，尽可能不要创建短期临时对象。越少的对象创建就意味着越低频率的垃圾回收，从而提高用户体验。

还不信？那让我来问问一位真正从事垃圾回收工作的工程师，他为移动设备实现垃圾回收器。

然而，WP7系统的手机CPU和内存性能正在大幅度地提升。游戏和大型Silverlight应用越来越多，这些程序会占用100M左右的内存。随着内存的变得越来越大，很多对象拥有的引用会指数级地变多。在上面解释的模式中，GC不得不去遍历每个对象以及他们的引用，标记它们，然后清理没哟引用指向的内存。所有GC的时间也大幅增加，并且成为这个应用的工作集（workingset）的一个函数。这会在大型XN游戏中和SL应用中导致程序卡住，体现在很长的启动时间（因为GC有可能在游戏启动的时候运行）或者游戏过程中的小问题。

还是不信？Chrome有一个测量GC性能的benchmark。我们来看看它都干嘛了：

你可以看到很多GC导致的卡顿。当然了，这是一个压力测试，但还是能说明问题的。你真的愿意花几秒时间来渲染一帧？你疯了吧。

这么多引用，我才不会挨个看呢，直接告诉我结论就行了。

结论是：移动设备上的内存管理很难。iOS平台的开发者已经形成了一种文化，即手动做大部分事情，让编译器做其它容易的部分。Andriod平台形成的文化是，提高垃圾收集器的性能，但事实上开发者在实际开发中尽量避免使用它。这两者的共同点是，大家在开发移动应用时，开始越来越多地考虑内存管理问题了。

当JavaScript、Ruby或是Python开发者听到“垃圾收集器”这个词时，他们习惯将它理解为“银弹（silver bullet）垃圾收集器”，也就是“让我不要让我再考虑内存问题的垃圾收集器”。但是移动设备上根被没有银弹可言，每个人写移动应用时都在考虑内存问题，不管他们是否使用了垃圾收集器。获得“银弹”内存管理方式的唯一方式就像我们在桌面环境上一样，拥有10倍于程序实际需要内存。

JavaScript的整个设计基于一个思想，即不要担心内存。看看Chromium开发者们怎么说：

有没有任何一种方式强制chrome的js引擎进行垃圾回收？一般意义而言，没有，这是从设计的角度就已经确定了的。

ECMAScript规范没有提到“分配（allocation）”这个词，唯一与“内存”相关的话题本质上就是说整个主题都是“实现相关的（host-defined）”。

ECMA 6的维基页面上有几页提案的草稿，归根结底是说（不是开玩笑）：

“垃圾回收器不可以回收那些程序需要继续使用来完成正确执行的内存。所有不能从根节点传递遍历到的对象都应该最终被销毁，防止程序因为内存耗尽而发生错误。”

是的，他们的确在思考将这个需求规约：垃圾回收器不应该回收那些不应该被回收的东西，但是应该回收那些需要被回收的。欢迎来到tautology club。但是下面这段话可能与我们的话题更相关：

然而，并没有规范说明单个对象占用多少内存，也不太可能会有。因此当任何程序在内存耗尽的情况下，我们永远不会得到任何保证，所以任何准确的、可观察的下界。

用英语来说就是，JavaScript的思想（如果这算是一种思想的话）是你不应该能够观察到系统内存中的情况，想都不要想。这种思想和人们在写实际的程序时候的想法简直是令人难以置信的背道而驰（so unbelievably out of touch），我甚至找不到正确的词语来向你形容。我的意思是，在iOS的世界里，我们并不相信垃圾回收器，我们感觉Andriod开发者都疯了（nuts）。我怀疑Andriod开发者会这么认为：iOS开发者竟然会用手动内存管理，简直是疯子。但是你知道这两个水火不容的阵营的人可以在哪件事情上达成共识吗？那就是JavaScript开发者是真正的疯子。你在移动平台上写出一个有点意思的程序，而从来不关心系统内存的分配和释放，是绝对不可能的（absolutely zero chance）。绝对不可能。暂时把SunSpider的benchmark上的问题和CPU计算密集型的问题都抛开，我们可以得出这样的结论：JavaScript，尽管现在存在着，是和移动平台软件开发过程中绝对重要的思想，即永远要考虑内存问题，从根本上是背道而驰的。

只要人们想要人们想在移动设备上开发各种视频和照片处理程序（不像桌面电脑），只要移动设备的内存不是那么充足，这个问题就是非常棘手的。你在移动设备上需要理性的、正式的内存管理保证。而JavaScript从设计上来说是拒绝提供这些的。

假设它能够提供这些

现在你可能会问，“OK，桌面环境上的JS开发者不会移动设备上的开发者遇到的问题。假设他们相信你说的，或者假设有一些知道这些问题的移动开发者们根据JS重新设计一门语言。你感觉理论上他们可以做哪些事情？”

我不确定这是否是解决的，但是我可以在这个问题上放一些边界。有另一群人曾经尝试在JS的基础上设计一门适合移动开发者的语言——RubyMotion。

这些人非常聪明，他们很了解Ruby。然后这些Ruby开发者认为垃圾回收对于他们的语言来说是一个糟糕的想法。（GC倡导者们，你们看到我说的了吗？）所以他们用了一种非常类似ARC的技术然后嫁接到语言当中，然而却没有成功。

总结：很多人正经历着由于RM-3或者其它难以辨别的问题所导致的内存相关议题，我们可以看看他们怎么说。

Ben Sheldon说：

不仅仅是你，我也面临着内存相关的程序崩溃（比如SIGSEGV和SIGBUS）生产环境下有10-20%的用户遇到过这种情况。

有一些人怀疑这个问题是否易于处理：

我在最近的一次Motion Meetup会上提出关于RM-3的问题，Laurent和Watson都对此提出了自己的看法。Watson提到说，RM-3是最难修复的bug；Laurent说他尝试了很多方法，但最终都没有很好地解决这个问题。他们两个人都是非常聪明和厉害的程序员，所以我相信他们说的话。

还有一些人怀疑编译器理论上是否能够解决这个问题：

很长的一段时间内，我都认为编译器可以简单明确地处理程序块，即静态地分析程序块内部的内容来判断程序块是否引用了这个程序块外部的变量。我认为，对于所有这些变量，编译器可以在程序块创建时获取，在程序块销毁时释放。这个过程吧这些变量的生命周期绑定到程序块上（当然，在某种情况下不是“完整的”生命周期）。有一个问题是instance_eval（译注：Ruby中Object类的方法）。程序块中的内容或许是按你提前知道的方式使用的，但也有可能并不是你能够提前知道的。

RubyMotion还有一个对立的问题：内存泄露，而且它还有可能有其它问题。没有人真正知道程序崩溃时内存泄露有2个原因还是有200个原因。

所以不管怎么说，我们的结论是：一部分世界上最好的Ruby程序员专门为移动设备开发设计了一种语言，他们设计了一个系统，这个系统不仅会崩溃，而且还会内存泄露，这些问题都是你可能会面临到的。至今为止他们并没有能够处理这个问题，尽管他们已经非常尽力了。对了，他们也表示他们“自己尝试了不少次，但没有能够能够找到一个好的并且能够保持高性能的解决方案”。

我并不是说在JavaScript的基础上创建一门具有较高内存性能的语言是不可能的，我只是想说很多证据显示这个问题会非常难。

更新：一个Rust语言的贡献者提到：

我为Rust项目工作，我的主要目标是实现零额外开销的内存安全。我们通过“@-boxes”（@T声明的类型是任何类型T）的方式来支持通过GC处理的对象，而我们最近遇到的比较麻烦的事情是，GC触碰到语言中的所有内容。如果你想支持GC但却不需要它，你就要非常仔细地设计你的怨言来支持零额外开销的非GC指针。这不是一个简单的问题，我不认为可以通过建立在JS的基础上创建一门新语言来解决。

OK，但是ASM.JS如何呢？

asm.js就比较有趣了，因为它提供了一个JavaScript模型，但这个模型严格意义上不是建立在垃圾回收的基础上的。所以从理论上讲，使用正确的网页浏览器，使用正确的API就可以了。问题是，“我们会得到正确的浏览器吗？”

Mozilla显然在这个概念上被出卖了，作为这个技术的作者，他们的实现今年晚些时候实现了它。Chrome的反应一直是含糊不清的，因为这个技术显然和Google的其它提案，包括Dart和PNaC1有直接的竞争关系。关于它有一个开发的bug清单，但是一个V8的黑客对此不满意。至于Apple阵营，按照我现在看来，WebKit那群人对比完全保持沉默。IE？我从来就没有抱任何希望。

无论如何，现在还不能说asm.js就是真正解决JavaScript问题，且能够击败所有其它提案的方法。另外，如果它能做到，它真的不可能是JavaScript，毕竟它能够可行的原因就是抛开了麻烦的垃圾回收器。所以它有可能和C/C++或者其它手动管理内存的语言的前端一起工作，但肯定不和我们现在知道并且喜欢的动态语言一样。

相对什么来说慢？

当一些文章里面说“X慢”和“X不慢”的时候，一个问题是，没有人真正说的清楚它们的参照系是什么。对于一个网页浏览器开发者，和对于一个高性能集群的开发者，以及对于一个嵌入式系统的开发者，等等，“慢”的含义是不一样的。既然我们已经闯过了战壕而且做了这么多benchmark，我可以给出你三个有用且大致正确的坐标系。

如果你是一个Web开发者，把iPhone 4S的Nitro当做IE8来看，因为它们的benchmark成绩差不多。这就给了你写代码时的正确坐标系。写代码的时候应该谨慎地使用JS，否则你会面临一大堆平台相关的性能问题要处理。有些应用用JS来写性价比是不高的，即使是流行的浏览器。

如果你是x86平台上的C/C++开发者，把iPhone 4S的Web开发环境当成只有桌面开发环境性能的1/50。其中1/10来自于ARM相对于x86的性能差距，1/5来自于JavaScript相对于C/C++的性能差距。在非JavaScript、性能为桌面环境1/10的情况下，仔细考虑正反面的因素。

如果你是Java、Ruby、Python或者C#开发者，按照以下方式去理解iPhone 4S的Web开发环境：它的性能是你电脑的1/10（ARM的因素），并且如果你的内存使用超过35M，性能会指数级下降，这是由垃圾回收的工作方式决定的。还有，如果你的程序分配了超过213M的内存，程序就会崩溃。注意，没有人“从设计的角度”在运行时刻给你这个信息。对了，人们都希望你在这种环境下写出很耗内存的照片处理和视频应用。

这是一篇非常长的文章

下面是你应该记得的内容：

- 2013年，用JavaScript写的移动应用（如照片编辑等）实在是太慢了。

- 比原生代码慢5倍

- 性能和IE差不多

- 比x86平台上的C/C++代码慢50倍

- 如果你的程序所有的内存不超过35M，比服务器端的Java/Ruby/Python/C#慢10倍；如果内存使用超过这个数，性能开始指数级下降

- 要使这个速度变得快一些，最可能的方式是让硬件性能达到桌面水平的性能。从长远来看这是可行的，但是看起来要等很长时间。

- 最近一段时间JavaScript语言本身并没有变得更快，在JavaScript上工作的人认为，在现有的语言和API下，它永远不会向原生代码那么快。

- 垃圾回收在内存受限的环境下会呈现指数级的性能下降，这一点比桌面和服务器级别的情况差很多。

- 任何能干的移动开发者都花很多时间来为目标设备考虑内存性能问题，不管他们是否使用具有GC的环境。

- 当前的JavaScript，从本质上是和允许程序为目标设备考虑内存性能问题这一点背道而驰的。

- 如果JavaScript的工作者们意识到这问题并且做出改变，允许开发者考虑内存问题，经验表明这是技术上的难题。

- asm.js让人看到了一些希望，但是就算它能成功，它应该是用了C/C++或者类似的“过时的”语言的前端，而不是像JavaScript这样的前端。

让我们提高争论的层次

毫无疑问的一点是，我不久就将收到上百封邮件，这些邮件圈出我说的某句话，然后在不提供任何实际的证据（或者根本不能算是证据）的情况下，指出我说得不对。或者说“我曾经用JavaScript写过一个文本编辑器，挺好”，或者说“有些我从来没见过的人写了一个飞行模拟器，但是从来没有给我写邮件说明他们遇到性能问题”，这些邮件我会一律删除。

如果我们想要在移动Web开发（或者是原生应用，或者是任何其它事情）上取得一些进展，我们都需要各种至少看上去有说服力（at least appear to have a plausible basis）的讨论，包括benchmark、期刊以及编译器作者们的引用等等。网上有很多HN关于“我曾经写了一个Web应用，挺好”的评论，还有很多关于Facebook在知道他们将会知道现在应该知道的东西的情况下（译注：原味knowing what they would have known then what they could have known now，不知这样翻译对不对？）是选择HTML5还是原生应用是对是错的争论（译注：原文为bikeshedding，这是一个比较有意思的词，意思是在还没完成自行车车架还没弄好的情况下就去讨论车的颜色，意指过于关心细节和边缘的问题，而忽视主要问题）。

对于我们来说，剩下来的任务是，明确地量化如何使得移动Web和原生生态环境变得越来越好，接着为此做出一些事情。正如你所知，这也是一个软件开发者应该做的事情。

翻译到此结束，翻译过程前前后后花了4天的时候。当快回宿舍之前，终于可以放到博客上去时，竟然感觉有些不可思议。谢谢大家支持！

原文链接：http://www.cnblogs.com/codemood/p/3213459.html （本内容由互联网资料整理而成）

【译】创建高性能移动 web 站点

如果你的网站3秒钟没有响应，人们就会失去兴趣了。为了满足响应快这个愿望，需要一个不同的方法在手机上进行分析，设计和测试。

这篇文章将会对Johan Johansson在2013年4月提出" 怎样让你的网站在手机上也很快"的这种理念进行扩展。我们将提出论证方法来确认人们在手机上与网站的交互方式和以前是不一样的，特别是设计也是基于此理念的。我们的目标不仅仅是提高网站性能，而且也要增加客户收入的。

我们将关注手机两个特性，这两个特性短期内也不会有变化：电池容量小，屏幕小。

顶 翻译的不错哦!

电量小

手机的通讯要用无线电，但手机的电池很小，所以要非常谨慎的用电以防止把电用光。这样，如果无线电不用的时候就会迅速关掉，这样就增加了网页出现的时间。2G和3G无线技术需要2秒钟来建立HTTP链接。如果我接受“用户会在3秒后失去兴趣”的观点的话，那我们的网站只有1秒来响应了。想想这“黄金般的一秒”吧。

最大化利用这“黄金一秒”

顶 翻译的不错哦!

小屏幕

在物理世界中，广告牌和杂志的内容都是根据媒介的大小和观看距离来定制的。在数字世界中，一个典型的中档智能手机拥有几乎6平方英寸大小的屏幕。15英寸的MacBook Pro电脑屏幕拥有超过100平方英寸的大小。这样，我们不仅可以通过减少发送到手机端的内容优化网站性能，而且可以优化业务流程来提高网站所有者的投资回报。

本文的代码示例是由.NET提供。我已经在companion article文章中展示了用PHP, Java, C 和Python达到同样的效果。我在这篇文章的结尾会解释为什么选用.NET。

顶 翻译的不错哦!

最大化利用 “黄金一秒”

网站设计者和开发者们常常想当然的认为用户应该用高带宽Wi-Fi和固网来连接。响应式网站设计（RWD）强制在不同设备上（不论其性能好坏）显示相同的内容、导航和业务流程，限制了创新。

确保我们能够容易的进行性能测量，进行用户行为监控的基于不同设备特性的解决方案以及低带宽设备网页访问优化都需要最大限度的利用这“黄金一秒”。

顶 翻译的不错哦!

其它翻译版本(1)

正在加载...

模拟现实网络

现实移动带宽模拟测试是一个必不可少的移动Web性能测试。很多100美元以下的廉价无线路由都提供了限制带宽功能，测试仅仅只涉及到了局域网内的客户端的上行和下行带宽限制功能。如果路由不支持这个功能话，那么试试用DD-WRT（DD-WRT是一个开源升级固件，可以替代目前主流路由的默认操作系统）来限制带宽。

我用DD-WRT升级了Linksys E3000路由。路由升级的过程非常简单，DD-WRT官网上提供了完整的说明。

安装好DD-WRT后去到QoS菜单，启用带宽限制。设置上行和下行带宽的值，我习惯将下行带宽设置为256kbps,上行带宽设置为28kbps来模拟移动网络的平均带宽。

顶 翻译的不错哦!

在“Quality of Service”选项中限定带宽

现在无论是以Wi-Fi或网线连接到路由器的设备的带宽都被人为的限制了。我们可以监视带宽实际的使用情况。

用DD-WRT监视带宽使用

虽然这种测试方法并没有包括随机的掉线、可变带宽条件和由信号强弱引起的延迟等情况，但是比起你在快速、低延迟带宽下做的其他测试效果要好。在网站开发初期，这是一个在开发过程中对Web性能进行非正式测试的简单的方法，能够确保你在正式测试过程中不出现任何讨厌的问题。

顶 翻译的不错哦!

你不能管理所不能测量的事

管理顾问 Peter Drucker 曾经说过一句名言：“如果你无法测量某件事，你就无法管理它。”

平均屏幕尺寸随时间的推移的增长情况

持续根据设备特性（比如无线支持或屏幕大小）对用户查看的内容进行监控，或多或少将会有助于你识别手机上流行的内容和服务。也许你将看不到任何区别，但是除非你测量过，否则无法确定。

顶 翻译的不错哦!

例子：“现在我要吃”站点

一个全球化的快餐特许经营店需要创建针对移动终端的大屏幕互联网站点的优化版本。在创建第一个针对移动终端优化的互联网站点之前，执行分析以确定大屏幕互联网站点的哪些项是小屏幕设备的用户可以访问的。主菜单、特卖品和分店查找是最受欢迎的，因此创建针对移动设备优化的互联网站点就集中在这些方面。

工作不能停留在此。接着的分析显示出分店查找是最受欢迎的。因此再次修改移动设备的主页以关注分店查找。继续的监视显示出多少访问者选择其他选项，然后依此不断改善地这个互联网站点，以确保用最简单可行的方法实现最受欢迎的栏目。

顶 翻译的不错哦!

好的日志

Google Analytics 提供了一些关于设备模型的信息，但它缺乏我们需要基于屏幕尺寸和输入方法作出明智决定的细节。幸运的是，一个全面的设备检测库（DDR）可以将此信息添加到现有日志文件中。下面的代码片段可以添加到 .NET网站中，参考51degrees.mobi(可通过   NuGet  ) 获取屏幕的物理尺寸和输出到一个简单的CSV文件中。

// Write a log file containing the current time, and the screen
// size of the requesting device in inches.
File.AppendAllText(
Path.Combine(
AppDomain.CurrentDomain.BaseDirectory, String.Format(
"App_Data\\Simple_Log_{0:yyyyMMdd}.csv",
DateTime.UtcNow)),
String.Format("{0:s},{1},{2},{3}\r\n",
DateTime.UtcNow,
Request.Path,
Request.Browser["ScreenInchesWidth"],
Request.Browser["ScreenInchesHeight"]));

顶 翻译的不错哦!

第一行是处理请求的日期和时间。第二行是请求的页面。最后两行是设备屏幕的宽度和高度。抓取足够多的数据和平均屏幕的尺寸大小绘制出了下面的图表：

比较设备屏幕的平均大小超过20个月

分析可以缩小到具体的页面。有关设备的特性，操作系统和浏览器也可以被添加到列中。

类似的代码可以使用PHP、Java、Python和其他环境语言。

顶 翻译的不错哦!

已有的日志文件

有时，已有的Web页面不能按照上面的方式修改。在这样的情况下，DDR可以用来执行含有用户代理的日志日文的离线分析了。下面的.NET代码是一个实用的命令行程序，它解析空格分隔的日志文件，然后计算出日志所表示的请求以平方英尺为单位的平均屏幕尺寸。第一个参数是日志文件的位置，第二个参数是日志文件里用户代理所在列的索引。

using System;
using FiftyOne.Foundation.Mobile.Detection.Binary;
using System.IO;

namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
// The number of devices read from the log file.
int count = 0;

// The column in the input file the user agent is held in.
int column = int.Parse(args[1]);

// Screen dimension variables.
double total = 0, width, height, squareInches;

// Create a provider to determine the device capabilities.
var provider = Reader.Create("51Degrees.mobi.dat");

// Read each line of the log file provided in argument  0.
// Assume the value at column 8 is the UserAgent string.
using (var reader = File.OpenText(args[0]))
{
while(reader.EndOfStream == false)
{
var values = reader.ReadLine().Split(new[] { ' ' });
if (values.Length >= column)
{
// Get the device information based on the UserAgent.
var device = provider.GetDeviceInfo(
values[column - 1].Replace("+", " "));
if (device != null)
{
// Determine the screen dimensions in inches.
double.TryParse(
device.GetFirstPropertyValue("ScreenInchesWidth"),
out width);
double.TryParse(
device.GetFirstPropertyValue("ScreenInchesHeight"),
out height);
squareInches = width * height;
// If valid values are available (not a desktop/laptop)
// then add the values to the results.
if (squareInches > 0)
{
total += squareInches;
count++;
}
}
}
}
}

Console.WriteLine(
"Average screen size '{0:#.00}' square inches from '{1}' devices",
total / count,
count);
Console.ReadKey();
}
}
}
分析日志文件很不准确，因为除了用户代理外的其他HTTP头都影响着检测结果。对Opera Mini和Opera 移动浏览器来说尤其是这样的。在这两个浏览器里，第二个HTTP头，也就是名字为Device-Stock-UA的头常常用来提供标准用户代理里没有的有关物理硬件的信息。

顶 翻译的不错哦!

为什么监控？

监控使得我们能够将不受欢迎的内容从主页中删除，以此提升更重要的内容或相关的内容的性能。删除的内容应该仍可以通过二级页面访问到——只是不放在首页，不然的话它们会消耗宝贵的带宽并降低性能体验。

那么，我们怎样来创建一个独立的性能优化的移动网站呢？

顶 翻译的不错哦!

分而治之

我能理解为什么RWD（响应web设计）从用户界面设计的角度来说很有意义。对于6平方英寸屏幕和10平方英寸屏幕，以及仅仅是需要进行改动的布局来说，在内容，导航以及业务流程需求方面可以完全一致，这实在是太棒了。

平均设备屏幕尺寸。

但是，在上述条件不为真或者对性能要求严格的时候有一个独立的移动网站 具有特别的意义。

顶 翻译的不错哦!

独立的移动网站常常表现出一种不良的用户体验。通过给网站惩罚赋以较低的搜索引擎等级，Google现在投射出一缕曙光 到这些普通的问题上。问题包括了将每个桌面页发送到单独的移动主页，重定向到应用下载页，阻止用户访问大屏的网站，对所有带特定操作系统的设备以相同的方式处理。

这些糟糕的实现让人对这些概念有一个坏的印象。这里是一些简单又正确的做法。

顶 翻译的不错哦!

下面的 .NETweb.config片段将把来自智能手机的第一个请求，重定向到网站上“Smartphone”部分指定的等价页面。 重要的是，查询字符串与页面名字在重定向的过程中一直保持着。

<redirect firstRequestOnly="true"
mobileHomePageUrl="~/Mobile/Default.aspx"
timeout="20"
devicesFile="~/App_Data/Devices.dat"
mobilePagesRegex="/(Mobile|Smartphone)/" >
<locations>
<!--Send smartphones to an equivalent version of the original page, preserving the page name and query string.-->
<location name="smartphone" url="~/Smartphone/{0}" matchExpression="(?<=^\w+://.+/).+">
<add property="IsSmartphone" matchExpression="true"/>
</location>
</locations>
</redirect>

在大多数情形，当重定向到替代页面时，如果愿意的话用户应当可以返回原始的页面；或许他们对网站的大屏幕版本更熟悉呢。firstRequestOnly属性保证了只有来自设备的第一次请求才被重定向。devicesFile属性是用来对不支持cookies的设备进行跟踪。timeout属性控制了在多长时间内该设备被记忆（为了重定向的目的）。

顶 翻译的不错哦!

重定向系统还必须知道哪个页面是针对哪种设备设计的。mobilePagesRegex属性被应用到请求URLs。如果存在匹配，页面将不适用重定向。这阻止了无穷重定向的情况。

locations元素允许配置定义不同的地址，以及相关的规则。这个例子将Smartphone目录插入到原始的URL。查询字符串和其他的URL信息在重定向过程中一直保持。所有影响到请求上下文的信息必须被传送，以便用户获得他们期望的内容。

这个简单的方法使得一个搜索引擎友好的，兼容Google的，移动手机优化的网站，在传送的过程中有良好的用户体验和优异的性能。这个过程的基础是DDR，它快速的，一致的，精确的提供了设备的信息。对于改变了移动手机浏览器设置到桌面模式的用户，重定向将不会发生。

顶 翻译的不错哦!

警惕云

云服务是给网站迅速增加特性的流行方法。但是它们跨越Internet的请求对性能带来损耗。如果忽略处理时间，我们观察到由Amazon Web Service提供的云服务的数据传输有平均200毫秒的延时。

200毫秒是一个黄金秒的20%。因此，仔细考虑一下你使用的云服务在哪里，确保它们是异步调用的，以便在等待响应的过程中其他处理能继续下去。它们应该避免关键路径上的活动，例如判别请求设备的信息。

顶 翻译的不错哦!

压缩内容

紧随视频、图像之后，CSS和HTML占据了大量的Web流量。我们需要优化所有这一切的方法。视频本身就是就可以写一篇文章，所以要等以后再说。

图像
流行的解决方案是同一张图像提供三个版本，而且当浏览器渲染页面的时候，使用JavaScript或者CSS选择最适合请求设备的那张图象。这是一个好的开始，不过管理同一图像的不同版本却是很痛苦的；图像从来都不是完美优化的，而且这种方法给有限CPU和电池电量的移动设备增加了进行图像大小调整的负担。

顶 翻译的不错哦!

有一个更好的处理方法是使用图像优化器。可以通过Viusal Studio的集成开发环境把52Degrees.mobi的图像优化器增加到ASP.NET站点。下面的配置将自动增加到web.config里。

<handlers>
<add name="Image" verb="GET" path="P.axd" type="FiftyOne.Framework.Image.ImageHandler, FiftyOne.Framework" />
</handlers>
上面的处理器告诉互联网信息服务（IIS）图像处理器应该处理资源P.axd的任何GET请求。
一旦web.config里启用这项，下面的ASP.NET代码将使用图像优化器从三种可能的资源-也就是分别为240，480和640像素宽的图像中确定一个图像。

<mob:Image runat="server" ID="ImageBanner" CalculateSizeMode="ClientWidth" Style="clear: both; width: 100%">
<mob:AltImage ImageUrl="~/Images/Landscape240.png" />
<mob:AltImage ImageUrl="~/Images/Landscape480.png" />
<mob:AltImage ImageUrl="~/Images/Landscape640.png" />
</mob:Image>

顶 翻译的不错哦!

当初始化显示图像的时候，服务器将发送一个白色的1x1像素的GIF显示在图像所在位置。下面就是生成的HTML:

<img id="B" src="P.axd?i=E.gif&i=1"/>
一旦页面装载完成，JavaScript用来算出最终显示图像所需要的真正的尺寸，然后向服务器请求一个大小明确的图像。经过JavaScript处理后，上面的HTML转换为：

<img id="B" src="P.axd?i=1&w=500"/>
web.config中引用的图像处理器把i查询字符串关联到图像源，因此服务器上最适合的图像将用作调整的最初图像。w查询字符串参数指定了所请求图像的宽度。因此不需要提供多个图像；一个单独的图像几乎就可以了。 这种方法易于实现，而且结果是大小明确的图像。这样做不但减少了带宽，而且还减少了移动电话CPU的运行周期和电量。

顶 翻译的不错哦!

HTML

整个牛津大辞典包括171476个单词。如果一个电脑用独有的二进制数字代表一个单词，而不是一个字母表的字母，大概需要用18比特(向上舍入大概3字节)。这表明压缩算法是十分有效的。

然而，HTML不是很有效，因为它充满了用字符代表的元素、IDs，类，styles和Javascript,没有考虑到是否是人可读的。压缩可以减少这些，但还是要有开销的。这就是为什么流行的库都有人们读不懂的压缩版本。

顶 翻译的不错哦!

在服务器发送给浏览器之前，一些与标记相关的单词还可以最小化，而且不会丢失任何这些单词的意义。看一个上面所示的图像的例子，ASP.NET里标准的HTML的图像元素的ID属性是ImageBanner。

<mob:Image runat="server" ID="ImageBanner" CalculateSizeMode="ClientWidth" Style="clear: both; width: 100%">
然而发送给浏览器的代码只使用B。对一个单独的元素来说。这样的性能改进是微不足道的，然而，对一个含有数百个元素的复杂页面来说，传送这样的页面会更快，而且浏览器将能更快地处理这一切。

顶 翻译的不错哦!

Includes

图像例子生成的HTML看起来比较奇怪：

<img id="B" src="P.axd?i=1&w=500"/>
这段ASP.NET代码没有样式而且也没有为img元素设置CLASS属性。那么它的样式是怎么来的呢？
服务器端最小化过程中将会确定样式信息，并且为网页创建一个CSS文件，从而减小了HTML的大小。当HTML变化时，样式已经被缓存在浏览器中，不需要重新下载。CSS片段看起来就像这样：

#B{clear:both;width:100%;}

顶 翻译的不错哦!

如果许多元素共享同一个样式，那么就要给这个CSS增加ID属性，这样它们就可以共享相同的信息。

还可以通过使用服务器端样式元素实现在多个元素和页面上共享样式信息。下面的代码扩展了前面图像例子，以此来说明共享样式元素。

<mob:Style runat="server" ID="StyleBanner">
<mob:Filter Style="clear: both; width: 100%"/>
</mob:Style>

<mob:Image runat="server" ID="ImageBanner" CalculateSizeMode="ClientWidth" StyleID="StyleBanner">
根据设备的能力，还可以对这个元素进行进一步扩展，使得可以应用其他样式，并且可在多个页面上优化样式表。 这种技术总是确保只传输必须的CSS，因此在对同一个页面的后续请求方面提高了性能，尤其是对HTML内容只有稍稍不同的页面。

顶 翻译的不错哦!

为什么选择.NET？

上面所示的图像优化和动态最小化HTML和CSS内容的技术和代码例子取决于页面渲染之后且服务器传送给浏览器之前更改的内容。这样的预处理技术在诸如ASP.NET的Web表格这样的结构里实现起来相当容易。

然而在基于脚本的比如PHP这样的架构里实现它们就非常复杂。正是由于这个原因，并且为了保持一致性，这篇文章中的例子都是在.NET上的。我们已经能够把这种技术应用到其他语言上，例子代码可在朋友博客里看到。

顶 翻译的不错哦!

总结

为了真正地优化性能，我们需要考虑网站所有人的投资回报。监测设备特征的不同是根本起始点。

然后我们才能部署诸如利用分散的移动网站来分离或改变内容焦点的解决方案。我们还能通过收紧缩小图像和HTML，移除jQuery，问询何时单独使用响应式网站设计（RWD），以及其它技术等使性能达到最高。当然，现有的技术也是至关重要的，例如配置缓存路径和压缩内容。

微调我们的开发环境，来模拟真实世界的情况，这样也可以在整个开发过程中获得对性能的更好的理解。

顶 翻译的不错哦!

现在就优化

为了能让你更多地考虑性能，我已经设定好了一个寻找世界上最重型的网站的竞赛。寻找一个网页，它在移动电话上运行得很糟糕，并将其提交给竞赛。我们会比较页面的分量，以及它是否是最重型的，你将因此而赢得1000美元。与此同时，实现本文以及其他Smashing Magazine中的牛文中提到的技术，以确保你的网站在被我们掂量其性能的时候不会出现在榜单之首！

从未有过比这更能提高你的网站性能的机会了。

顶 翻译的不错哦!

原文链接：http://www.oschina.net/translate/creating-high-performance-mobile-websites （本内容由互联网资料整理而成）

[小Tip]用好浏览器的控制台 | JerryQu的小站

文章目录

- 格式化log

- 指定log的级别

- 使用断言

- log分组

- 输出表格

- 计时器

- 改变当前所在window

- 快速引用

作为一名前端开发工程师，对浏览器控制台肯定都不陌生。加上最近大家纷纷在控制台玩出花来（1、2），同学们对Console的热情又上升了不少。这里记录一下我知道的几个小技巧，欢迎大家补充。

本文所有内容我都在Chrome28自带Console和Firefox的Firebug1.12试过。Firefox也自带了一个Console，不过有点弱，用Firefox的同学还是建议装个Firebug。

格式化log

console.log除了能接受多个参数，还支持指定输出内容的格式，还可以这样混着用：

console.log("I am %s and I have:", myName, thing1, thing2, thing3);

下面是console.log支持的几种格式：

格式

说明

%s

字符串

%d, %i

整数（不支持指定位数）

%f

浮点数（不支持指定位数）

%o

对象

%c

CSS样式

其中比较有趣的是%c，可以这么玩（我很佩服想到在Console做招聘广告甚至还附带网站logo的同学）：

console.log('%c  ', 'font-size:124px;background-image:url(http://ququ.qiniudn.com/uploads/2011/07/ququ_1_1_1.jpg)');

指定log的级别

与很多其它编程语言类似，控制台的log也支持各种级别，console.debug()，console.info()，console.warn()，console.error()，不同级别的log会标上不同的icon。

合理使用不同的log级别，一方面可以方便地筛选自己关注的log，另一方面有些级别的log还会在控制台打印调用堆栈和源码位置。

使用断言

有时候，我们需要在特定情况下打印log，这时候console.assert方法就很有用：

console.assert(expression[, object, ...])

如果expression返回的不是true，控制台会打印异常信息及源码位置。

log分组

有时候需要打印在控制台的log太多，我们可以对其分组，这样可以按组来折叠log信息。具体效果是怎样的，大家试下就知道了：

console.group("分组1");console.log(1);console.group("分组1.1");console.log(1.1);console.groupEnd();console.groupEnd();console.group('分组2');console.log(2);console.groupEnd();

输出表格

console.table方法可以在控制台打印表格，这在输出复杂信息时很有用。这个方法在Chrome和Firebug下有点点不一样，大家可以自己试试：

console.table({'first' : {'name' : 'ququ', 'age' : 27}, 'second' : {'name' : 'kitty', 'age' : 100}});

console.table的高级用法，可以参考此文。

计时器

很多时候，我们用两个new Date相减获取时间差，其实也可以这样用：

> console.time('time1')
> console.timeEnd('time1');
time1: 27085.415ms

改变当前所在window

在Chrome开发工具的Console标签下，有个写着“<top frame>”的下拉框，通过它我们就可以选择所处的window环境。

Firebug中要稍微麻烦一点，需要用到cd方法，它唯一的参数就是要改变到的window，类似这样：

> cd(window.frames[0])
当前 window: Window about:blank
> cd(top)
当前 window: Window www.imququ.com

快速引用

控制台内置了一些变量，可以让我们快速引用某些内容。这里介绍两个我最常用到的：

$_，引用控制台最近一次运算结果，如：

> 1+1
2
> $_+1
3

$0，引用当前选中的元素。在控制台，我们可以方便的选中或“吸中”某个元素，然后通过$0就可以使用它，大家动手试下就明白了。更爽的是，Chrome开发工具“Elements”等标签下，可以通过esc键呼出Console，谁用谁知道。

随便提一句，clear()和console.clear()都可以清空Console已有的信息。

本文链接：http://www.imququ.com/post/about-browser-devtools-console.html

--EOF--

原文链接：http://www.imququ.com/post/about-browser-devtools-console.html?nojs=1 （本内容由互联网资料整理而成）

把握5元素！让页面文字阅读更舒适

上网在线阅读，是大多人获取信息的重要途径。用户会在网上阅读到大量的新闻及各类文章，特别是网络小说，这些页面展示的主体就是通篇大段的文字。对于这种看似简单的文字堆砌的页面，有没有人抱怨过：“这样的页面还需要出效果图啊？”作为网页设计师的你是怎样处理这样的界面的？

是习惯性的使用宋体字或者微软雅黑？
是直接设为12px或者14px的字号？
你有想过为什么要使用这种字体、字号吗？
你有考虑过文字排布与阅读效率的关系吗？
你的页面背景会影响到用户的浏览舒适度吗？

让我们从以下5个元素具体分析。

字体、字号

字体和字号这两个元素必须捏在一起说。

字体分为衬线体（serif）与非衬线体（sans serif）。简单的说，衬线体（serif）就是带有衬线的字体，笔画粗细不同并带有额外的装饰，开始和结尾有明显的笔触（如下图）。常用的英文衬线体有Times New Roman和Georgia，中文即是我们在windows下最常见的中易宋体。

与衬线体相反，无衬线装饰，笔画粗细无明显查差异的字体就是非衬线体（sans serif）。Arial、Helvetica、Verdanad都是我们熟悉的英文非衬线体，中文有微软雅黑和mac下默认的中文－华文细黑。

在过去，普遍认为有笔触装饰的衬线体，可提高辨识度和阅读效率，更适合做为阅读的字体。多用于报纸、书籍等印刷品的正文。非衬线体饱满醒目，常用做标题或者用于较短的段落。

很长一段时间里，在互联网上我们会注意这样的一个现象，国外网站大部分以非衬线体为主，而中文网站基本就是宋体。其实不难理解，衬线字体笔画有粗细之分，很小字的情况下细笔画就被弱化，受限于电脑的分辨率，10-12px的衬线字体在显示器上是相当难辨认的（见下图）；同字号的非衬线体笔画简洁而饱满，更适于做网页字体。windows下，中文宋体小字点阵的特性，12px、14px的字号显示仍然相当清晰美观，便于阅读。这也就是为什么大多网页设计制作人员容易机械性的选用特定字号或英文或中文的字体。

如今随着显示器越来越大，分辨率越来越高，经常会觉得看12px的文字有点吃力，做为设计师也会不自觉的开始大量使用14px的字体，而且越来越多的网站开始使用15、16px甚至18px以上的字号做正文文字。大字号的使用，对英文字体来讲，衬线体的高辨识度和流畅阅读的优势就体现出来了。中文宋体在显示14号以上字体时，单线条大字总是看上去有些单薄不那么协调，特别是这款点阵字在ClearType（http://baike.baidu.com/view/755822.htm） 强制平滑显示状态下尤其显得模糊不清。微软雅黑就是微软在这个字体显示问题上的解决方案。

让我们分别对比下中英文的衬线体与非衬线体在不同字号时的显示效果。

英文以Times和Verdanad为例，中文以宋体和微软雅黑为例。

行长

我们随手拿起一本书或者一份报纸，数一数每行的文字，一般情况下都不会超过40个汉字。这是因为如果每行文字过长，读者会不停的转动脖子，感到疲惫的同时也会降低阅读效率；目光从行尾移至下一行首，也很容易串行，影响读者浏览文章的节奏。这点同样适用于网页上的文章阅读。由于显示器是横向的，我们更要注意划分阅读区域。

文本宽度控制在450-700px为宜，此范围内参照字号大小；
英文每行80-100个字母（空格算一个字母）为宜；
中文每行30-40个汉字为宜。

间距

通过设置间距，我们就可以控制文字的密度。如果每行间距太小，与文字过长一样，读者浏览文章时也容易串行；如果行距过宽，阅读时就会感觉文章不够连贯。

间距没有固定的值，通常是根据字体大小来定义的。在word里我们常看到双倍行距、单倍行距和1.5倍行距的选项。网页上行距的单位常用em来表示，不管是中文网站还是英文网站，大家多喜欢用1.5em－1.8em的行距。以1.6em的行距为例，字号16px的字，行距就是25.6px。

如（图）所示，1.5-1.8em的行距的确是非常适于阅读的文字密度。

关于段距，我们要考虑一下具体实际情况。比如文章篇幅较短，就不需要很宽的段距；二文章篇幅很长，特别是那些偏学术的比较枯燥的文章，就要多利用段距把握文章的节奏，给阅读者喘息和思考的机会，且使文章更有层次和可读性。

背景

白色是全频光，全放射光对人眼睛的最有刺激，所以很多印刷品读物的纸张都选用乳白色或者淡黄色的纸张。由于显示器本身就发光，所以同是纯白色背景的文字，在电脑上比在纸上阅读会使眼睛更容易疲乏，另有研究表明：在电脑上阅读只有在纸上阅读速度的78%，阅读效率大大减低。因此，为了提高页面浏览的舒适度和效率，越来越多的页面采用浅灰色和淡黄色做为页面背景。

另外，印刷品以文字为主的出版物往往会采用低光泽度的纸张，也是因为光泽度高的纸张反射度高，导致阅读质量下降，容易使人视觉疲劳。页面背景仿造低光泽度的纸张（纸张纹理）也能否提高阅读的舒适度呢？

我曾做过这样一个小调研，同样一段文字放在不同颜色的背景上（仅考虑自然光源），其中有两个背景加了纸张纹理的效果。结果75%的人认为带有纸张纹理的背景长时间阅读舒适度最高。

结语：实际工作中，我们需要思考的更多，仅仅这5个元素是不够的。浏览环境、设备、网站特点、用户人群、个人偏好等等都需要设计师根据具体情况来把握界面文字的展示效果。差别也许是细微的，就像蝴蝶效应，多一点点的思考，就能为用户带来更好的体验感受。

转载请注明出自”百度MUX”

原文链接：http://mux.baidu.com/?p=4943 （本内容由互联网资料整理而成）

毫秒必争，前端网页性能最佳实践

你愿意为打开一个网页等待多长时间？我一秒也不愿意等。但是事实上大多数网站在响应速度方面都让人失望。现在越来越多的人开始建立自己的网站，博客，你的网页响应速度如何呢？在这篇文章中我们来介绍一下提高网页性能的最佳实践，以及相应的问题解决方案，让站长或者即将要成为站长的朋友了解如何去测试和提高网站响应速度，对自己的网站更有信心。

最佳实践我们引用的来自yahoo前端性能团队总结的35条黄金定律。原文猛击这里。下面我们分门别类将每条的关键点总结一下。

网页内容

减少http请求次数

80%的响应时间花在下载网页内容(images, stylesheets, javascripts, scripts, flash等)。减少请求次数是缩短响应时间的关键！可以通过简化页面设计来减少请求次数，但页面内容较多可以采用以下技巧。

1. 捆绑文件: 现在有很多现成的库可以帮你将多个脚本文件捆绑成一个文件，将多个样式表文件捆绑成一个文件，以此来减少文件的下载次数。例如在asp.net中可以使用ScriptManager，asp.net MVC中的Bundling。

2. CSS Sprites: 就是把多个图片拼成一副图片，然后通过CSS来控制在什么地方具体显示这整张图片的什么位置。给大家看个熟悉的Sprites实例。

豆瓣把他的图标集中在一起，然后我们看他如何控制只显示第一个图标的

.app-icon-read {
background-position
: 0 0;

}
.app-icon {
background
: url("/pics/app/app_icons_50_5.jpg") no-repeat scroll 0 0 transparent;

border-radius: 10px 10px 10px 10px;
box-shadow: 1px 1px 2px #999999;
display: inline-block;
height
: 50px;

width
: 50px;

}

3. Image Maps： 也是将多幅图拼在一起，然后通过坐标来控制显示导航。这里有个经典的例子，选中图片中的某个人就会将你带到不同的链接。

4. Inline images: 通过编码的字符串将图片内嵌到网页文本中。例如下面的inline image的显示效果为一个勾选的checkbox。

.sample-inline-png {
padding-left: 20px;
background: white url('data:image/png;base64,iVBORw0KGgoAA
AANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0l
EQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6
P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC') no-repeat scroll left top;
}

图片显示效果如左图

减少DNS查询次数

DNS查询也消耗响应时间，如果我们的网页内容来自各个不同的domain (比如嵌入了开放广告，引用了外部图片或脚本)，那么客户端首次解析这些domain也需要消耗一定的时间。DNS查询结果缓存在本地系统和浏览器中一段时间，所以DNS查询一般是对首次访问响应速度有所影响。下面是我清空本地dns后访问博客园主页dns的查询请求。看少去还不少哦。

避免页面跳转

当客户端收到服务器的跳转回复时，客户端再次根据服务器回复中的location指定的地址再次发送请求，例如以下跳转回复。

HTTP/1.1 301 Moved Permanently
Location: http://example.com/newuri
Content-Type: text/html

当客户端遇到这种回复的时候，用户只能等待客户端再次发送请求，有的网站甚至会一直跳n次，跳到他想带你去的地方…当然在这个时候用户看不到任何页面内容，只有浏览器的进度条一直在刷新。

缓存Ajax

Ajax可以帮助我们异步的下载网页内容，但是有些网页内容即使是异步的，用户还是在等待它的返回结果，例如ajax的返回是用户联系人的下拉列表。所以我们还是要注意尽量应用以下规则提高ajax的响应速度。

- 添加Expires 或 Cache-Control报文头使回复可以被客户端缓存

- 压缩回复内容

- 减少dns查询

- 精简javascript

- 避免跳转

- 配置Etags

延迟加载

这里讨论延迟加载需要我们知道我们的网页最初加载需要的最小内容集是什么。剩下的内容就可以推到延迟加载的集合中。

Javascript是典型的可以延迟加载内容。一个比较激进的做法是开发网页时先确保网页在没有Javascript的时候也可以基本工作，然后通过延迟加载脚本来完成一些高级的功能。

提前加载

与延迟加载目的相反，提前加载的是为了提前加载接下来网页中访问的资源，下面是提前加载的类型

无条件提前加载：当前网页加载完成后，马上去下载一些其他的内容。例如google会在页面加载成功之后马上去下载一个所有结果中会用到的image sprite。

有条件加载：根据用户的输入推断需要加载的内容，雅虎的示例是search.yahoo.com，

有预期的的加载：这种情况一般发生在网页重新设计时，由于用户经常访问旧网页，本地对旧的网页内容缓存充分从而显得旧网页速度很快，而新的网页内容却没有缓存，设计者可以在旧网页的内容中预先加载一些新网页中可能用到的内容，这样新的网页就会生下来一些需要下载的资源。

减少DOM元素数量

网页中元素过多对网页的加载和脚本的执行都是沉重的负担，500个元素和5000个元素在加载速度上会有很大差别。

想知道你的网页中有多少元素，通过在浏览器中的一条简单命令就可以算出，

document.getElementsByTagName('*').length

多少算是多了呢？雅虎在写这篇文章的时候号称主页只有700多元素，但现在接近多了一倍。我们的网页至少别比雅虎还多吧。。。

根据域名划分内容

浏览器一般对同一个域的下载连接数有所限制，按照域名划分下载内容可以浏览器增大并行下载连接，但是注意控制域名使用在2-4个之间，不然dns查询也是个问题。

一般网站规划会将静态资源放在类似于static.example.com，动态内容放在www.example.com上。这样做还有一个好处是可以在静态的域名上避免使用cookie。后面我们会在cookie的规则中提到。

减少iframe数量

使用iframe要注意理解iframe的优缺点

- 可以用来加载速度较慢的内容，例如广告。

- 安全沙箱保护。浏览器会对iframe中的内容进行安全控制。

- 脚本可以并行下载

- 即使iframe内容为空也消耗加载时间

- 会阻止页面加载

- 没有语义

避免404

404我们都不陌生，代表服务器没有找到资源，我们要特别要注意404的情况不要在我们提供的网页资源上，客户端发送一个请求但是服务器却返回一个无用的结果，时间浪费掉了。

更糟糕的是我们网页中需要加载一个外部脚本，结果返回一个404，不仅阻塞了其他脚本下载，下载回来的内容(404)客户端还会将其当成Javascript去解析。

服务器

使用CDN

再次强调第一条黄金定律，减少网页内容的下载时间。提高下载速度还可以通过CDN(内容分发网络)来提升。CDN通过部署在不同地区的服务器来提高客户的下载速度。如果你的网站上有大量的静态内容，世界各地的用户都在访问，我说的是youtube么？那CDN是必不可少的。事实上大多数互联网中的巨头们都有自己的CDN。我们自己的网站可以先通过免费的CDN供应商来分发网页资源。

添加Expires 或Cache-Control报文头

这条规则分为两个方面，

Gzip压缩传输文件

Gzip通常可以减少70%网页内容的大小，包括脚本、样式表、图片等文件。Gzip比deflate更高效，主流服务器都有相应的压缩支持模块。

IIS中内建了静态压缩和动态压缩模块，如何配制可以参考Enable HTTP Compression of Static Content (IIS 7)和Enable HTTP Compression of Dynamic Content (IIS 7)。

值得注意的是pdf文件可以从需要被压缩的类型中剔除，因为pdf文件本身已经压缩，gzip对其效果不大，而且会浪费CPU。

虽然标题叫配制ETags，但是这里你要根据具体情况进行一些判断。首先Etag简单来说是通过一个文件版本标识使得服务器可以轻松判断该请求的内容是否有所更新，如果没有就回复304 (not modified)，从而避免下载整个文件。

但是Etags的版本信息即使主流服务器未能很好地支持跨服务器的判断，比如你从一个服务器集群中一台得到Etags，然后发送到了另一台那么校验很有可能会失败。

如果你遇到这样的问题，IIS 7中可以通过如下方法将Etag去掉，使用URL Rewrite，然后在web.config中添加如下配制

<rewrite>
<outboundRules>
<rule name="Remove ETag">
<match serverVariable="RESPONSE_ETag" pattern=".+" />
<action type="Rewrite" value="" />
</rule>
</outboundRules>
</rewrite>

IIS8里提供了一个简单配制来直接关闭Etag，

<element name="clientCache">
<attribute name="cacheControlMode" type="enum" defaultValue="NoControl">
<enum name="NoControl" value="0" />
<enum name="DisableCache" value="1" />
<enum name="UseMaxAge" value="2" />
<enum name="UseExpires" value="3" />
</attribute>
<attribute name="cacheControlMaxAge" type="timeSpan" defaultValue="1.00:00:00" />
<attribute name="httpExpires" type="string" />
<attribute name="cacheControlCustom" type="string" />

<attribute name="setEtag" type="bool" defaultValue="false" />

</element>

尽早flush输出

网页后台程序中我们知道有个方法叫Response.Flush()，一般我们调用它都是在程序末尾，但注意这个方法可以被调用多次。目的是可以将现有的缓存中的回复内容先发给客户端，让客户端“有活干”。

那在什么时候调用这个方法比较好呢？一般情况下我们可以在对于需要加载比较多外部脚本或者样式表时可以提前调用一次，客户端收到了关于脚本或其他外部资源的链接可以并行的先发请求去下载，服务器接下来把后续的处理结果发给客户端。

使用GET Ajax请求

浏览器在实现XMLHttpRequest POST的时候分成两步，先发header，然后发送数据。而GET却可以用一个TCP报文完成请求。另外GET从语义上来讲是去服务器取数据，而POST则是向服务器发送数据，所以我们使用Ajax请求数据的时候尽量通过GET来完成。

关于GET和POST的详细对比可以查看这里。

避免空的图片src

空的图片src仍然会使浏览器发送请求到服务器，这样完全是浪费时间，而且浪费服务器的资源。尤其是你的网站每天被很多人访问的时候，这种空请求造成的伤害不容忽略。

浏览器如此实现也是根据RFC 3986 - Uniform Resource Identifiers标准，空的src被定义为当前页面。

所以注意我们的网页中是否存在这样的代码

straight HTML
<img src="">

JavaScript
var img = new Image();
img.src = "";

Cookie

减少Cookie大小

Cookie被用来做认证或个性化设置，其信息被包含在http报文头中，对于cookie我们要注意以下几点，来提高请求的响应速度，

- 去除没有必要的cookie，如果网页不需要cookie就完全禁掉

- 将cookie的大小减到最小

- 注意cookie设置的domain级别，没有必要情况下不要影响到sub-domain

- 设置合适的过期时间，比较长的过期时间可以提高响应速度。

关于asp.net中的cookie可以参考ASP.NET Cookies Overview和Configure Use Cookies Mode for Session State (IIS 7)

页面内容使用无cookie域名

大多数网站的静态资源都没必要cookie，我们可以采用不同的domain来单独存放这些静态文件，这样做不仅可以减少cookie大小从而提高响应速度，还有一个好处是有些proxy拒绝缓存带有cookie的内容，如果能将这些静态资源cookie去除，那就可以得到这些proxy的缓存支持。

常见的划分domain的方式是将静态文件放在static.example.com，动态内容放在www.example.com。

也有一些网站需要在二级域名上应用cookie，所有的子域都会继承，这种情况下一般会再购买一个专门的域名来存放cookie-free的静态资源。例如Yahoo!的yimg.com，YouTube的ytimg.com等。

CSS

将样式表置顶

经样式表(css)放在网页的HEAD中会让网页显得加载速度更快，因为这样做可以使浏览器逐步加载已将下载的网页内容。这对内容比较多的网页尤其重要，用户不用一直等待在一个白屏上，而是可以先看已经下载的内容。

如果将样式表放在底部，浏览器会拒绝渲染已经下载的网页，因为大多数浏览器在实现时都努力避免重绘，样式表中的内容是绘制网页的关键信息，没有下载下来之前只好对不起观众了。

避免CSS表达式

CSS表达式可以动态的设置CSS属性，在IE5-IE8中支持，其他浏览器中表达式会被忽略。例如下面表达式在不同时间设置不同的背景颜色。

background-color: expression( (new Date()).getHours()%2 ? "#B8D4FF" : "#F08A00" );

CSS表示的问题在于它被重新计算的次数远比我们想象的要多，不仅在网页绘制或大小改变时计算，即使我们滚动屏幕或者移动鼠标的时候也在计算，因此我们还是尽量避免使用它来防止使用不当而造成的性能损耗。

如果想达到类似的效果我们可以通过简单的脚本做到。

<html>
<head>
</head>
<body>
<script type="text/javascript">
var currentTime = new Date().getHours();
if (currentTime%2) {
if (document.body) {
document.body.style.background = "#B8D4FF";
}
}
else {
if (document.body) {
document.body.style.background = "#F08A00";
}
}
</script>
</body>
</html>

避免使用@import的原因很简单，因为它相当于将css放在网页内容底部。

避免使用Filters

AlphaImageLoad也是IE5.5 - IE8中支持，这种滤镜的使用会导致图片在下载的时候阻塞网页绘制，另外使用这种滤镜会导致内存使用量的问题。IE9中已经不再支持。

Javascript

HTTP/1.1 specification建议浏览器对同一个hostname不要超过两个并行下载连接， 所以当你从多个domain下载图片的时候可以提高并行下载连接数量。但是当脚本在下载的时候，即使是来自不同的hostname浏览器也不会下载其他资源，因为浏览器要在脚本下载之后依次解析和执行。

因此对于脚本提速，我们可以考虑以下方式，

- 把脚本置底，这样可以让网页渲染所需要的内容尽快加载显示给用户。

- 现在主流浏览器都支持defer关键字，可以指定脚本在文档加载后执行。

- HTML5中新加了async关键字，可以让脚本异步执行。

使用外部Javascirpt和CSS文件

使用外部Javascript和CSS文件可以使这些文件被浏览器缓存，从而在不同的请求内容之间重用。

同时将Javascript和CSS从inline变为external也减小了网页内容的大小。

使用外部Javascript和CSS文件的决定因素在于这些外部文件的重用率，如果用户在浏览我们的页面时会访问多次相同页面或者可以重用脚本的不同页面，那么外部文件形式可以为你带来很大的好处。但对于用户通常只会访问一次的页面，例如microsoft.com首页，那inline的javascript和css相对来说可以提供更高的效率。

精简Javascript和CSS

精简就是将Javascript或CSS中的空格和注释全去掉，

body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}

精简后版本

body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}

统计表明精简后的文件大小平均减少了21%，即使在应用Gzip的文件也会减少5%。

例如我的网站上有5个CSS，4个Javascirpt，下面是分别经过bundling和minify之后的结果。

没有任何处理之前

捆绑Javascript和CSS之后

精简Javascript和CSS之后

用来帮助我们做精简的工具很多，主要可以参考如下，

JS compressors:

CSS compressors:

与VS集成比较好的工具如下.

- YUICompressor - 编译集成，包含在NuGet.

- AjaxMin  - 编译集成

去除重复脚本

重复的脚本不仅浪费浏览器的下载时间，而且浪费解析和执行时间。一般用来避免引入重复脚本的做法是使用统一的脚本管理模块，这样不仅可以避免重复脚本引入，还可以兼顾脚本依赖管理和版本管理。

减少DOM访问

通过Javascript访问DOM元素没有我们想象中快，元素多的网页尤其慢，对于Javascript对DOM的访问我们要注意

- 缓存已经访问过的元素

- Offline更新节点然后再加回DOM Tree

- 避免通过Javascript修复layout

使用智能事件处理

这里说智能的事件处理需要开发者对事件处理有更深入的了解，通过不同的方式尽量少去触发事件，如果必要就尽早的去处理事件。

比如一个div中10个按钮都需要事件句柄，那么我们可以将事件放在div上，在事件冒泡过程中捕获该事件然后判断事件来源。

图片

优化图像

当美工完成了网站的图片设计后，我们可以在上传图片之前对其做以下优化

- 检查GIF图片中图像颜色的数量是否和调色板规格一致。如果你发现图片中只用到了4种颜色，而在调色板的中显示的256色的颜色槽，那么这张图片就还有压缩的空间。可以使用imagemagick检查：
identify -verbose image.gif

- 尝试把GIF格式转换成PNG格式，看看是否节省空间。大多数情况下是可以压缩的。下面这条简单的命令可以安全地把GIF格式转换为PNG格式：
convert image.gif image.png

- 在所有的PNG图片上运行pngcrush（或者其它PNG优化工具）。例如：
pngcrush image.png -rem alla -reduce -brute result.png

- 在所有的JPEG图片上运行jpegtran。这个工具可以对图片中的出现的锯齿等做无损操作，同时它还可以用于优化和清除图片中的注释以及其它无用信息
jpegtran -copy none -optimize -perfect src.jpg dest.jpg

优化CSS Sprite

- Spirite中水平排列图片，垂直排列会增加文件大小；

- Spirite中把颜色较近的组合在一起可以降低颜色数，理想状况是低于256色以便适用PNG8格式；

- 不要在Spirite的图像中间留有较大空隙。这虽然不大会增加文件大小,但对于用户代理来说它需要更少的内存来把图片解压为像素地图。100×100的图片为1万像素，1000×1000就是100万像素。

不要在HTML中缩放图片

不要通过图片缩放来适应页面，如果你需要小图片，就直接使用小图片吧。

使用小且可缓存的favicon.ico

网站图标文件favicon.ico，不管你服务器有还是没有，浏览器都会去尝试请求这个图标。所以我们要确保这个图标

- 存在

- 文件尽量小，最好小于1k

- 设置一个长的过期时间

移动客户端

保持单个内容小于25KB

这限制是因为iphone，他只能缓存小于25K，注意这是解压后的大小。所以单纯gzip不一定够用，精简文件工具要用上了。

打包组建成符合文档

把页面内容打包成复合文本就如同带有多附件的Email，它能够使你在一个HTTP请求中取得多个组建。当你使用这条规则时，首先要确定用户代理是否支持（iPhone不支持）。

文章有点长，能看到这里不容易，谢谢大家捧场，疏漏或补充欢迎留言讨论。

原文链接：http://www.cnblogs.com/developersupport/p/webpage-performance-best-practices.html （本内容由互联网资料整理而成）

老码农教你学英语

本文作者： 伯乐在线 - 老码农 。如需转载本文，请先参见文章末尾处的转载要求。

对于咱们这些高端大气、时刻需要和国际接轨的码农，英语的重要性自然是毋庸置疑的。尤其是那些胸怀大志，想在码农行业闯出一片天地，成为秒杀身边小弟们的潜在大牛们，熟练掌握英语更是事业成功最关键的因素之一。试想在你捧着某出版社刚刚翻译出来的《JSP 高效编程》苦苦学习JSP模板的时候，你旁边的小弟却是拿着原版的《AngularJS in Action》学习开发单页面应用，虽然你们都同样认真地学习了一个月，可做出来东西的效果能一样吗？

所以，英语好才能学到最新最炫的技术，否则只能拿着国内出的翻译版学习两三年前的老古董还把它当个宝。更何况国内的翻译书水平如何你不会不知道吧？多少坑爹的翻译啊！不提了！其实我十多年前还参加过一本Java开发指南的翻译，而当时我一直在IBM主机上做开发，压根就不会Java，所以误人子弟是肯定的了。回首往事，惭愧啊！请上帝宽恕我的罪恶，阿门。。。

好了，现在言归正传，说说码农应该如何学习英语，达到熟练掌握英语的水平。首先，我要明确一个概念：英语学习是不可能速成的。一心想速成的同学们可以不用往下看了，不然浪费了你的时间我不承担任何责任。

作为码农的习惯，自然第一个重点是要准确定义”熟练掌握英语“的概念。

我的定义如下：

- 1. 阅读：能够直接阅读英文文档，比如《MongoDB: The Definitive Guide》，并且阅读速度和理解程度都能与母语相当；

- 2. 写作：能够直接编写英文文档、邮件，达到英语母语人士能够无歧义理解的程度，学有余力的同学可以冲击逻辑严密和用词严谨；

- 3. 听说：能够顺畅地与英语母语人士进行有关技术方面的交流，达到双方沟通无歧义的程度，学有余力的同学还可以争取在对话中表现幽默感和个人品位。

肯定有人要不服了：为啥学习的起点给俺整这么高呢？因为，只有达到这个标准，你才能充分发现学好英语的好处，也才能自觉地尽可能使用英语，形成良性循环。

那么，要达到这个标准需要多少时间呢？对于CET-4或者6级水平的同学（没错，4和6都是一个水平，几乎没啥差别），我的估计是需要1万小时以上。这也是我前面说英语不可能速成的原因。1万个小时这个数据的估算方法是阅读5000小时 听2000小时 写2000小时 说1000小时，下面具体说明：

1. 阅读是提高英语水平的基础、核心、重中之重。

为什么这么说？最有效的学习方式就是模仿，而听说读写四个要素里只有听和读有模仿条件。

那为什么不是先多听呢？有两个原因。一是口语比较随意，俚语方言比较多，在表达上不如书面内容严谨；二是听一次没能理解的内容不容易查找和重复。所以大量的阅读是提高英语水平的关键性基础性的工作。

有人又要问了，难道不要先背单词吗？这个嘛，我自己是最讨厌背单词的，又怎么会把你们往火坑里推呢？而且单纯地背单词用处不大，在阅读中记住的单词才是有用的。

比如在GRE单词表开头随便找个单词avalanche，释义是n.雪崩 vi.崩塌，你可以使劲地背，把它和其他几千个单词都记得滚瓜烂熟，然后当你某天在文档中看到一句话说：”… to handle an avalanche of client requests …“，你可能都想不起来背过avalanche这个单词，即使想起来也未必能准确理解它在这段话里的含义。

这是为什么呢？因为背单词是孤立地去记一个个单词，应试也许有用，但是事倍功半。最好的方法就是在阅读中去理解它，比如还是那一句，我压根不知道avalanche这个单词，但是从前后文去蒙，an avalanche of大概是说非常多的意思，如果你不确定理解得对不对就去查一下字典，就明白了这意思是”像雪崩一样滚滚而来的大量的什么东西“。

查词最好是查英-英辞典，比如Merriam-Webster的韦氏英英辞典。还有，查一个单词的时候，不要去记那几个中文释义。某些人背单词就喜欢像念经似的念叨：“capability，才能，能力，容量，性能，生产率，capability，才能，能力，容量，性能，生产率……”，旁观者都替他觉得累得慌对吧？其实你只要模糊地知道它的意思就行了，学英语尽量不要掺进来中文。老外不知道啥叫“才能，能力，容量，性能，生产率”，也没影响人家用capability这个词不是？

另外我觉得吧，单词和人一样，也是有眼缘的，记得住记不住都不要去强求。即使这一次看完又忘了也没关系，等你阅读量上去之后，遇到次数多了自然就记住了。有人说，万一有的词遇到次数很少，还是记不住咋办？对这种问题我就无语了。遇到次数很少的单词你记它干啥？有个著名单词中文意思是什么早期银版照相术的，我就偏不记它，它能把我怎么着啊？

通过这个过程，你不但记住了单词，还掌握了它的常见用法，这对于以后的写和说都是非常好的基础。现在很多人学英语的弊病在于应试思维，就仿佛孔乙己说的茴香豆的茴有四种写法，少记住一种就可能在考试里做错题被扣分，但是现实生活中你也许只需要会一种就行了，关键是知道它的意思而且能够准确地使用它。

矮马，一下子扯到背单词跑偏了。还是接着说阅读，怎么阅读呢？我推荐一种暴力方法，不管你基础如何，先找一两本和技术相关的白话书，而且是你最感兴趣的领域，比如经典的《Man Month Myth》（人月神话） 和《Joel on Software》（大神Joel谈软件开发）原版书，这样读起来好歹不那么苦闷，有助于你坚持下来。毕竟在这个起步阶段，坚持是第一位的。不然给你找本《advanced econometrics》试试，据说95%的码农读了三天之后都疯了。

就算是你感兴趣的书，也保不齐一开始读起来觉得很费劲。也许有的人翻开第一页一看，妈呀，一半单词不认识！这咋办？没事，像我前面说的，有把握蒙的就蒙，没把握的就查，查过的把中文意思写在单词旁边，总之原则就是要全部看懂。

还有，句子太长里边有好多that….. which….. who….什么的从句？那也没关系，别人理解不了，可咱是码农啊，懂得嵌套结构的原理，不就是递归嘛！甭管他连了多少个，从最后面的那个往前一个一个处理，把从句用彩笔一个一个标出来，最后看清楚嵌套关系以后，句子也就容易看懂了。实际上，我觉得英语最妙的地方就在这里，它能在一个句子里用一个无限延展的树形结构来描述一个概念，直到把它定义得非常严谨无歧义为止。中文做不到这一点，必须拆成好多个句子才能做到通顺，但理解起来就困难多了。

在这个阶段一定不要求快，一天吭哧吭哧地只看了半页都没关系，也很正常，但一定要确保准确理解。在此我想特别提醒的是，如果你一开始不适应，一定要坚持下来。其实学习英语过程中最难的不是听说读写，而是英语思维。汉语是讲朦胧美的，所谓“道可道，非常道”，越深刻的东西往往越是“只可意会，不可言传”，文人写文章喜欢下结论而很少论证，结论的经验性主观性较大，例如“肉食者鄙，未能远谋”；而欧美文章则大多务求精确严谨，定义精确，论证充分，避免逻辑上的漏洞，下结论则往往比较谨慎，一般都是客观数据，尽量避免主观看法，例如“根据卫星云图，明天的降水概率为70%”。我年轻的时候买过《孙子兵法》和克劳塞维茨的《战争论》一起看，两者的风格差别真是泾渭分明。

此外对于人文方面也有很大差别。老外经过文艺复兴，对于人文关怀有了很好的基础，更强调个人自由和权利，所谓”风能进，雨能进，国王不能进”；我们从历史文化传承的角度看则是集体大于个人，更强调个人适应环境。再比如老外对于弱势群体的歧视嘲笑是非常忌讳的，但中国人往往习惯了小品里瘸子瞎子胖子出场摔一大跟头这样的笑料。

所以，这些思维上的差异才是学英语的最大障碍，而大量阅读有助于理解和养成这种思维习惯。特别是逻辑上的严密性和表达的客观性方面，由于文化传统和教师本身的原因，在中国现在的教育环境很难训练出来。但是通过大量阅读英语材料，就可以达到很好的洗脑效果，实为居家旅行、和平演变必备良药。

读完两三本白话书就算是热好身了，下面可以开始读一些更枯燥的东西，主要是专业教课书。如果上大学的时候学的《数据结构》、《离散数学》、《操作系统》这些都还给老师了，正好拿英文版的复习一下。找你专业相关的基础课原版教材，5本左右，开练。照着前面的要求，一样细细地看，不求快但求精，练习题也好好做一部分，不然你怎么知道自己确实理解透了？

等你看完这几本，阅读基本上就算入门了，可以开始练习写作和听力。具体做法后面再细说。

与此同时，要开始看一些行业里最新的技术文档。这些可能还没出书，也可能有一些免费的pdf，不管怎样，也找5本左右来看，什么MongoDB，Neo4j，Node.js，AngularJS之类的，细细地看，边看边做个系统练手，因为这些新技术文档还比较少，社区里的东西也不多，所以免不了要去StackOverflow问，或者去GitHub找些例子来看，别偷懒，这些都是很好的学习方式。

等你把这几本书也啃下来，估计你在网上看的英文资料也和这几本书差不多多了。好了，恭喜你，你的阅读能力肯定没问题了。如果我没猜错的话，你这时候碰到翻译版的书都懒得看，更别说花钱买和花时间读了。

另外，阅读能力达到这个水平以后，写作也基本有个基础了。所谓“熟读唐诗三百首，不会作诗也会吟”，看多了以后你会发现英语写作也有一些套路，和中文是一个道理，经过大量的阅读，很多句式其实已经在你的脑子里了，只要经常用它，自然就熟能生巧了。

2. 写作要创造环境，每天都要写1000字以上。

在互联网时代，这一点也不难。最简单直接的办法就是注册一个StackOverflow账号，起初是去问问题，把你搞不定的代码贴上去，会有大牛们帮你搞定，顺带着阅读也练了；等你技术水平涨了以后，想想这么多人帮了你你才成了大牛，总不能只进不出吧，有余力了就应该报复社会，所以就经常去StackOverflow找一些你拿手的主题，帮其他菜鸟们解答一些问题，这样也就练了写作了，一举两得多好！

等你在SO上混出了点名堂，有点江湖地位的时候，就会有人来找你，比如帮忙干点活啊咨询点问题啊之类的，自然邮件联系就少不了了，这都是练习写作的机会，就算你不想帮他干活，也可以和他扯一扯。

另外，弄个翻墙软件比如goagent，注册个Google Plus账号，上去看看热点文章，写点自己的体会之类的。实在闲得无聊也可以找Linus之类的大牛混，他每发一贴你就上去评论一番或者请教一下，先混个脸熟嘛！顺带也练习了写作。时间长了，说不定大牛还喜欢上你了，没准随便给你个肉身翻墙的机会，这就是意外之喜了。

总之，写作是大量阅读之后水到渠成的产物，不过要注意一点，写出来的每个句子甚至每个单词都要务求精准地道，不知道的不要瞎写，要么改换自己熟悉的写法，要么查清楚了再写。要是养成随意乱写的习惯，写出来的句子都是中国人能看懂，外国人都看不懂，那前面的努力就付诸东流了。

3. 听力要在阅读能力达到一定水平后再开始练习，和前面说的阅读与写作的关系相似，练听力的同时其实就在为口语打基础。

我首先要说的是，千万不要跟着美剧什么的练听力，你又不是打算偷渡去纽约皇后区卖毒品跟黑人大哥混，就算你听一耳朵就能熟练分辨出说WTF are you doing的是意大利人、爱尔兰人还是黑人，又有啥用呢？咱们做码农的一定不要忘了自己的本分，得跟着码农的大哥混才对。所以练习听力一定要多找IT圈的大牛的访谈来听。

最经典的自然是天妒英才英年早逝的乔帮主，多听听他的访谈你就知道他的成功绝非偶然。像这样一位逻辑严密，思维活跃，表达能力强，善于调动听众情绪，还很有幽默感，而且做事又非常有韧性的人，实在是百年难遇的天才人物。

比尔盖茨的访谈就差多了，他的表达能力真的是不敢恭维，有时候东拉西扯的听不明白重点，依稀有点韩乔生老师的风韵。政客系列也不推荐，像奥巴马的讲演听起来总有一种似曾相识的赶脚，细细一想，这不就是美国版的传销讲座嘛！

所以，多听乔大神的访谈绝对是没错的。为啥是访谈呢？因为都是对话，形式上和咱们的需求匹配，咱们工作中需要的英语对话就是类似于访谈式的，你想想是不是？而且他的访谈聊的都是咱们挨踢的事儿，背景知识都比较熟悉，你专心听他的表达和逻辑就好了。你要是不服，俺给你弄个生物系教授讲分子生物学课程的录音让你听，你听完还能找得到北吗？

这些访谈在网上都有，到iTunes里的podcast里大把大把的，不过别找带字幕的听，千万！

现在材料有了，怎么听呢？我先告诉你，每个访谈都要听100遍以上，头20遍就是稀里糊涂地听，能听懂多少听懂多少，听不懂的先蒙。但我敢保证，第20遍肯定比第1遍听懂的东西要多多了。到了20遍还在蒙的，基本你听到第100遍也还是在蒙，所以再蒙下去就没意义了，这时候找到文字版好好看一遍，就看一遍，然后收起来，再听20遍，这时候有些原来靠蒙的就听出来了，听完20遍再看一次，再听，如此反复，100遍之后基本应该都不用蒙了，然后再听下一个。听过100遍的，以后也要经常复习，反复听，很多句型就会自动进到你的脑子里，给你的口语打下一个很好的基础。
4. 说是最麻烦的，因为说的条件最难创造，这必须得有个大活人认真地跟你聊才行啊！

当然了，要是你能找个英语母语的老外谈对象，那就又省事又高效了。不过，等口语练好了一定要一脚将其踹掉，要记住，你是一个中国人，肥水不流外国田！

好了，大晚上的不扯淡了，练习英语口语不外乎这么几个途径：交外国朋友，进外国公司，或者花钱雇外国人跟你聊。各人工作、经济情况不同，这就自己选择吧。反正现在改革开放了，一部分人也富起来了，跟谁聊也就丰俭由人了。

不过这里要提醒一点，别去什么英语角练口语，这种地方要么是一百多人围着一个老外，跟看猴似的，要么就是一堆中国人互相来几句好肚油肚，听着就想摸摸对方的肚子看看有多好有多油，纯粹浪费时间！我认为，练口语一定要找英语是母语的人士练习，这样才能事半功倍。

另外，有机会就多去美国英国澳大利亚加拿大什么的地方转转，现场体验一下自己的学习成果。不过我这里讲的主要是码农工作相关的东西，基本没涉及到生活类的英语，比如去麦当劳买个milk shake什么的，这些主要要靠现场体验现场学习，但有了前面阳春白雪的码农高端上流社会英语作为基础，这些下里巴人的生活英语上手很快，对你肯定不在话下。

关于码农如何学好英语，我就先说这么多吧。最后再总结一下，英语学习是一个终身的事业，只有通过痛苦的起步阶段，慢慢把它变成你的一个爱好，才能持久下去。等到它真正成为你喜爱的东西了，你就会发现眼中的世界从此变得不同。它变大了，变得多样化了，也变得更美好了。这时，所有的努力和坚持都有了回报。

作者微博：@老码农的自留地 （转载请保留）

(1 个评分，平均: 5.00)

Loading ...

本文作者： 伯乐在线 - 老码农
本文链接： http://blog.jobbole.com/45296/
[ 转载必须在正文中标注并保留原文链接、译文链接和译者等信息。]

原文链接：http://blog.jobbole.com/45296/ （本内容由互联网资料整理而成）

使用渐进式JPEG 来提升用户体验

原文出处： 标点符

今天才认识到原来JPEG文件有两种保存方式，分别是Baseline JPEG（标准型）和Progressive JPEG（渐进式）。两种格式有相同尺寸以及图像数据，扩展名也是相同的，唯一的区别是二者显示的方式不同。

Baseline JPEG

这种类型的JPEG文件存储方式是按从上到下的扫描方式，把每一行顺序的保存在JPEG文件中。打开这个文件显示它的内容时，数据将按照存储时的顺序从上到下一行一行的被显示出来，直到所有的数据都被读完，就完成了整张图片的显示。如果文件较大或者网络下载速度较慢，那么就会看到图片被一行行加载的效果，这种格式的JPEG没有什么优点，因此，一般都推荐使用Progressive JPEG。

Progressive JPEG

和Baseline一遍扫描不同，Progressive JPEG文件包含多次扫描，这些扫描顺寻的存储在JPEG文件中。打开文件过程中，会先显示整个图片的模糊轮廓，随着扫描次数的增加，图片变得越来越清晰。这种格式的主要优点是在网络较慢的情况下，可以看到图片的轮廓知道正在加载的图片大概是什么。在一些网站打开较大图片时，你就会注意到这种技术。

渐进式图片带来的好处是可以让用户在没有下载完图片就可以看到最终图像的大致轮廓，一定程度上可以提升用户体验。（瀑布留的网站建议还是使用标准型的）

另外渐进式的图片的大小并不会和基本的图片大小相差很多，有时候可能会比基本图片更小。渐进式的图片的缺点就是吃用户的CPU和内存，不过对于现在的电脑来说这点图片的计算并不算什么。

说了这边多下面就改讲讲怎么讲图片保存为或者转化为Progressive JPEG了。

1、PhotoShop

在photoshop中有“存储为web所用格式”，打开后选择“连续”就是渐进式JPEG。

2、Linux

检测是否为progressive jpeg ： identify -verbose filename.jpg | grep Interlace（如果输出 None 说明不是progressive jpeg；如果输出 Plane 说明是 progressive jpeg。）

将basic jpeg转换成progressive jpeg：> convert infile.jpg -interlace Plane outfile.jpg

3、PHP

使用imageinterlace和imagejpeg函数我们可以轻松解决转换问题。

<?php
$im = imagecreatefromjpeg('pic.jpg');
imageinterlace($im, 1);
imagejpeg($im, './php_interlaced.jpg', 100);
imagedestroy($im);
?>

4、Python

import PIL
from exceptions import IOError

img = PIL.Image.open("c:\\users\\biaodianfu\\pictures\\in.jpg")
destination = "c:\\users\\biaodianfu\\pictures\\test.jpeg"
try:
img.save(destination, "JPEG", quality=80, optimize=True, progressive=True)
except IOError:
PIL.ImageFile.MAXBLOCK = img.size[0] * img.size[1]
img.save(destination, "JPEG", quality=80, optimize=True, progressive=True)

5、jpegtran

jpegtran -copy none -progressive <inputfile> <outputfile>

6、C#

using (Image source = Image.FromFile(@"D:\temp\test2.jpg")) {
ImageCodecInfo codec = ImageCodecInfo.GetImageEncoders().First(c => c.MimeType == "image/jpeg");
EncoderParameters parameters = new EncoderParameters(3);
parameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
parameters.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.ScanMethod, (int)EncoderValue.ScanMethodInterlaced);
parameters.Param[2] = new EncoderParameter(System.Drawing.Imaging.Encoder.RenderMethod, (int)EncoderValue.RenderProgressive);
source.Save(@"D:\temp\saved.jpg", codec, parameters);
}

(还没有评分)

Loading ...

原文链接：http://blog.jobbole.com/44038/ （本内容由互联网资料整理而成）

获得屏幕上的像素

转自answer-Huang的博客   [answer-Huang微博]

原文:http://www.objc.io/issue-3/moving-pixels-onto-the-screen.html#pixels

一个像素是怎么映射到屏幕上去的？有许多种方式将一些事物映射到显示屏，他们需要不同的框架、许多功能和方法的结合体。这里我们走马观花的看一下屏幕之后发生的一些事情。当你决定什么时候、怎么去查明并解决问题的时候，我希望这能帮助你理解哪一个API将能更好的工作。我们将聚焦于iOS，然而我讨论的大多数问题也同样适用于OS X。

图形堆栈

当像素映射到屏幕上的时候后台发生了很多事情。但是一旦他们显示到屏幕上，每一个像素均由三个颜色组件构成：红，绿，蓝。三个独立的颜色单元会根据给定的颜色来显示到一个像素之上。在iPhone5上的液晶显示器上有1,136x640=727,040个像素，因此有2,181,120个颜色单元。在15寸的视网膜屏的MacBook Pro上，这一数字在15.5百万以上。所有的图形堆栈一起工作来确定每一次正确的显示。当你滚动整个屏幕的时候，数以百万计的颜色单元必须以每秒60次的速度刷新。这是一个很大的工作量。

软件组成

从简单的角度来看，软件堆栈看起来有点像这样：

显示的上一层是图形处理单元GPU，GPU是一个专门为图形高迸发计算量身定做的处理单元.这也是为什么它能同时更新所有的像素，并把结果显示到显示器上。它迸发的本性能让它高效的将不同纹理合成起来。我们将有一小块内容来更详细的讨论图形合成。关键的一点是，GPU是非常专业的，因此在某些工作上非常高效。比如，GPU非常快，并且比CPU使用更少的电来完成这个工作。正常的CPU都有一个普遍的目的，可以做很多不同的事情，但是合成图像在CPU上却会比较慢。

GPU驱动是直接和GPU交流的代码块。不同的GPU是不同野兽，但是驱动使他们在下一层显示的更统一，典型的有OpenGL/OpenGL ES.

OpenGL(Open Graphics Library)是一个提供了2D和3D图形渲染的API。

GPU是一个非常特殊的硬件，OpenGL和GPU密切的工作以促进GPU的能力，并实现硬件加速渲染。对大多数人来说，OpenGL看起来非常底层，但是当它在1992年第一次发布的时候(20多年前的事了)是第一个和图形硬件(GPU)交流的标准化方式,因此这是一个重大的飞跃，因为程序员不再需要为每个GPU重写他们的应用。

OpenGL之上扩展出很多东西。在这一点上iOS几乎所有的东西都是通过Core Animation，然而在OS X上，绕过Core Animation直接使用Core Graphics的这种情况并不少见。对于一些专门的应用，尤其是游戏，程序可能直接和OpenGL/OpenGL ES交流。事情变得使人更加困惑，因为Core Animation使用Core Graphics来做一些渲染。像AVFoundation，Core Image框架，和其他一些混合的入口。

要记住一件事情，GPU是一个非常强大的图形硬件并且在显示像素方面扮演了核心作用。它连接到CPU。从硬件上讲两者之间存在某种类型的总线，并且有像OpenGL，Core Animation和Core Graphics这样的框架来在GPU和CPU之间精心安排数据的传输。为了将像素显示到屏幕上，一些处理将在CPU上进行。数据将会传送到GPU，这也将会反过来加工(个人理解为加压、解压)，最终像素显示到屏幕上。

这个过程的每一部分都有各自的挑战，并且许多时候需要做出折中的选择。

硬件参与者

正如上面一个简单的图片来显示那些挑战，GPU需要将每一个frame的纹理(位图)合成在一起(比如一秒60次)。每一个纹理会占用VRAM(video RAM)，所以需要给GPU能够同时保持多少纹理一个限制。GPU在合成方面非常高效，但是某些合成任务却比其他更复杂，并且GPU在 16.7ms(1/60s)内能做的工作有限。

下一个挑战就是将数据传输到GPU上。为了让GPU访问数据，数据将需要从RAM移动到VRAM上。这就是提及到的上传数据到GPU。这看起来貌似微不足道，但是一些大型的纹理却会非常耗时。

最终，CPU开始运行你的程序。你可能会让CPU从bundle加载一张PNG的图片并且解压它。这所有的事情都在CPU上进行。然后当你需要显示解压缩后的图片时，它需要以某种方式上传到GPU。一些平凡的比如显示文本对CPU来说是一件非常复杂的事情，这会促使Core Text和Core Graphics框架更紧密的集成来根据文本生成一个位图。一旦准备好，它将会被作为一个纹理上传到GPU并准备显示出来。当你滚动或者在屏幕上移动文本，然而，同样的纹理能够被复用，CPU只需简单的告诉GPU新的位置就行了,所以GPU就可以重用存在的纹理了。CPU并不需要重新渲染文本，并且位图也不需要重新上传到GPU。

这说明一些包含错综复杂的影响，根据这个概述的方式，我们将深入一些技术影响。

在图形世界中，合成是一个描述不同位图如何放到一起来创建你最终在屏幕上看到图像的过程。在许多方面显得显而易见，而让人忘了背后错综复杂的计算。

让我们忽略一些难懂的事例并且假定屏幕上一切事物皆纹理。一个纹理就是一个包含RGBA值的长方形，比如，每一个像素里面都包含红、绿、蓝和透明度的值。在Core Animation世界中这就相当于一个CALayer。

在这个简化的设置中，每一个layer是一个纹理，这所有的纹理都已某种方式堆叠在彼此的顶部。对于屏幕上的每一个像素，GPU需要算出怎么混合这些纹理来得到像素RGB的值。这就是合成大概的意思。

如果我们所拥有的是一个和屏幕大小一样并且和屏幕像素对齐的单一纹理，屏幕上每一个像素相当于纹理中的一个像素。纹理的最后一个像素也就是屏幕的最后一个像素。

如果我们有第二个纹理放在第一个纹理之上，然后GPU将会把第二个纹理合成到第一个纹理中。有很多种不同的合成方法，但是如果我们假定两个纹理的像素对齐，并且使用正常的混合模式，我们便可以用下面这个公式来计算每一个像素:

R = S + D * ( 1 – Sa )

结果的颜色是源色彩(顶端纹理)+目标颜色(低一层的纹理)*(1-源颜色的透明度)。在这个公式中所有的颜色都假定已经预先乘以了他们的透明度。

显然相当多的事情在这儿发生了。让我们进行第二个假定，两个纹理都完全不透明，比如alpha=1.如果目标纹理(低一层的纹理)是蓝色(RGB=0,0,1),并且源纹理(顶层的纹理)颜色是红色(RGB=1,0,0),因为Sa为1，所以结果为：

R = S

结果是源颜色的红色。这正是我们所期待的。

如果源颜色层为50%的透明，比如alpha=0.5，既然alpha组成部分需要预先乘进RGB的值中，那么S的RGB值为(0.5, 0, 0)，公式看起来便会像这样:

我们最终得到RGB值为(0.5, 0, 0.5),是一个紫色。这这是我们所期望将透明红色合成到蓝色背景上所得到的。

记住我们刚刚只是合成一个纹理单一像素到另一个纹理的像素上。当两个纹理覆盖在一起的时候，GPU需要为所有像素做这种操作。正如你所知道的一样，许多程序都有很多层，因此所有的纹理都需要合成到一起。尽管GPU是一块高度优化的硬件来做这种事情，但这还是会让它非常忙碌，

不透明 VS 透明

当源纹理是完全不透明的时候，目标像素就等于源纹理。这可以省下GPU很大的工作量，这样只需简单的拷贝源纹理而不需要合成所有的像素值。但是没有方法能告诉GPU纹理上的像素是透明还是不透明的。只有当你作为一名开发者知道你放什么到CALayer上了。这也是为什么CALayer有一个叫做opaque的属性了。如果这个属性为YES，GPU将不会做任何合成，而是简单从这个层拷贝，不需要考虑它下方的任何东西(因为都被它遮挡住了)。这节省了GPU相当大的工作量。这也正是Instruments中color blended layers选项中所涉及的。(这在模拟器中的Debug菜单中也可用).它允许你看到哪一个layers(纹理)被标注为透明的，比如GPU正在为哪一个layers做合成。合成不透明的layers因为需要更少的数学计算而更廉价。

所以如果你知道你的layer是不透明的，最好确定设置它的opaque为YES。如果你加载一个没有alpha通道的图片，并且将它显示在UIImageView上，这将会自动发生。但是要记住如果一个图片没有alpha通道和一个图片每个地方的alpha都是100%，这将会产生很大的不同。在后一种情况下，Core Animation需要假定是否存在像素的alpha值不为100%。在Finder中，你可以使用Get Info并且检查More Info部分。它将告诉你这张图片是否拥有alpha通道。

像素在一条线上和不重合在一起

到现在我们都在考虑显示像素完美的重合在一起的layers。到现在为止当所有的像素是对齐的时候我们得到相对简单的计算公式。每当GPU需要计算出屏幕上一个像素是什么颜色的时候，它只需要考虑在这个像素之上的所有layer中对应的单个像素，并把这些像素合并到一起。或者，如果最顶上的纹理是不透明的，这时候GPU就可以简单的拷贝纹理中的像素到屏幕上。

当一个layer上所有的像素和屏幕上的像素完美的对应整齐，那这个layer就是像素对齐的。主要有两个原因可能会造成不对齐。第一个便是滚动；当一个纹理上下滚动的时候，纹理的像素便不会和屏幕的像素排列对齐。另一个原因便是当纹理的起点不在一个像素的边界上。

在这两种情况下，GPU需要再做额外的计算。它需要将源纹理上多个像素混合起来，生成一个用来合成的值。当所有的像素都是对齐的时候，GPU只剩下很少的工作要做。

再次，Core Animation工具和模拟器有一个叫做color misaligned images的选项，当这些在你的CALayer实例中发生的时候，这个功能便可向你展示。

一个图层可以有一个和它相关联的蒙板，蒙板是一个拥有alpha值的位图，当像素要和它下面包含的像素合并之前都会把蒙板应用到图层的像素上去。当你要设置一个图层的圆角半径时，你可以有效的在图层上面设置一个蒙板。但是也可以指定任意一个蒙板。比如，一个字母A形状的蒙板。最终只有在蒙板中显示出来的(即图层中的部分)才会被渲染出来。

屏幕外的渲染

屏幕外的渲染可以被Core Animation自动触发，或者被应用程序强制触发。屏幕外的渲染会合并/渲染图层树的一部分到一个新的缓冲区，然后该缓冲区被渲染到屏幕上。

当屏幕外的渲染合成计算是非常昂贵的时候, 你也许希望强制这种操作。一种好的方法就是缓存合成的纹理/图层。如果你的渲染树非常复杂(所有的纹理，以及如何组合在一起)，你可以强制屏幕外的渲染缓存那些图层，然后可以用缓存作为合成的结果放到屏幕上。

如果你的程序混合了很多图层，并且想要他们一起做动画，GPU通常会为每一帧(1/60s)重复合成所有的图层。当使用屏幕外渲染时，GPU第一次会混合所有图层到一个基于新的纹理的位图缓存上，然后使用这个纹理来绘制到屏幕上。现在，当这些图层一起移动的时候，GPU便可以复用这个位图缓存，并且只需要做很少的工作。需要注意的是，只有当那些图层不改变时，这才可以用。如果那些图层改变了，GPU需要重新创建位图缓存。你可以通过设置shouldRasterize为YES来触发这个行为。

然而，这是一个权衡。第一，这可能会使事情变得更慢。创建额外的屏幕外缓冲区是GPU需要多做的一步操作，特殊情况下这个位图可能再也不需要被复用，这便是一个无用功了。然而，可以被复用的位图，GPU也有可能将它卸载了。所以你需要计算GPU的利用和帧的速率来判断这个位图是否有用。

屏幕外渲染也可能产生副作用。如果你正在直接或者间接的将蒙板应用到一个图层上，Core Animation为了应用这个蒙板，会强制进行屏幕外渲染。这会对GPU产生重负。通常情况下蒙板只能被直接渲染到帧的缓冲区中(在屏幕内)。

Instrument的Core Animation工具有一个叫做Color Offscreen-Rendered Yellow的选项，它会将已经被渲染到屏幕外缓冲区的区域标注为黄色(这个选项在模拟器中也可以用)。同时记得检查Color Hits Green and Misses Red选项。绿色代表无论何时一个屏幕外缓冲区被复用，而红色代表当缓冲区被重新创建。

一般情况下，你想要避免屏幕外渲染，因为这是很大的消耗。直接将图层合成到帧的缓冲区中(在屏幕上)是比先创建屏幕外缓冲区，然后渲染到纹理中，最后将结果渲染到帧的缓冲区中要廉价很多。因为这其中涉及两次昂贵的环境转换(转换环境到屏幕外缓冲区，然后转换环境到帧缓冲区)。

所以当你打开Color Offscreen-Rendered Yellow后看到黄色，这便是一个警告，但这不一定是坏的。如果Core Animation能够复用屏幕外渲染的结果，这便能够提升性能。

同时还要注意，光栅层的空间是有限的。苹果暗示大概有屏幕大小两倍的空间来存储光栅层/屏幕外缓冲区。

如果你使用图层的方式会通过屏幕外渲染，你最好摆脱这种方式。为图层使用蒙板或者设置圆角半径会造成屏幕外渲染，产生阴影也会如此。

至于蒙板，圆角半径(特殊的蒙板)和clipsToBounds /masksToBounds，你可以简单的创建一个包含蒙板里面已经存在的内容，比如，使用蒙板上已经应用的图片。如果你想根据图层的内容申请一个长方形的蒙板，你可以使用contentsRect来代替蒙板。

如果你最后设置了shouldRasterize为YES，那也要记住设置rasterizationScale为contentsScale。

更多的关于合成

像往常一样，维基百科上有更多关于透明合成的基础公式。当我们谈完像素后，我们将更深入一点的谈论红，绿，蓝和alpha是怎么在内存中表现的。

OS X

如果你是在OS X上工作，你将会发现大多数debugging选项在一个叫做”Quartz Debug”的独立程序中，而不是在Instruments中。Quartz Debug是Graphics Tools中的一部分，这可以在苹果的developer portal中下载到。

Core Animation & OpenGL ES

正如名字所建议的那样，Core Animation让你在屏幕上实现动画。我们将跳过动画部分，而集中在绘图上。需要注意的是，Core Animation允许你做非常高效的渲染。这也是为什么当你使用Core Animation时可以实现每秒60帧的动画。

Core Animation的核心是OpenGL ES之上的一个抽象，简而言之，它让你直接使用OpenGL ES的功能，却不需要处理OpenGL ES做的复杂的事情。当我们上面谈论合成的时候，我们把图层和纹理当做等价的，但是他们不是同一物体，可又是如此的类似。

Core Animation的图层可以有子图层，所以最终你得到的是一个图层树。Core Animation所需要做的最繁重的任务便是判断出哪些图层需要被(重新)绘制，而OpenGL ES需要做的便是将图层合并、显示到屏幕上。

举个例子，当你设置一个图层的内容为CGImageRef时，Core Animation会创建一个OpenGL纹理，并确保在这个图形中的位图被上传到对应的纹理中。以及当你重写-drawInContext方法时，Core Animation会请求分配一个纹理，同时确保Core Graphics会将你所做的(即你在drawInContext中绘制的东西)放入到纹理的位图数据中。一个图层的性质和CALayer的子类会影响到OpenGL如何渲染出来，许多低等级的OpenGL ES行为被简单易懂地封装到CALayer概念中。

Core Animation通过Core Graphics的一端和OpenGL ES的另一端，精心策划基于CPU的位图绘制。因为Core Animation处在渲染过程中的重要位置上，所以你如何使用Core Animation将会对性能产生极大的影响。

CPU限制 VS GPU限制

当你在屏幕上显示东西的时候，有许多组件参与了其中的工作。其中，CPU和GPU在硬件中扮演了重要的角色。在他们命名中P和U分别代表了”处理”和”单元”，当需要在屏幕上进行绘制时，他们都需要做处理。他们都有资源限制(即CPU和GPU的硬件资源)。

为了每秒达到60帧，你需要确定CPU和GPU不能过载。此外，即使你当前能达到60fps(frame per second),你还是要尽可能多的绘制工作交给GPU做，而让CPU尽可能的来执行应用程序。通常，GPU的渲染性能要比CPU高效很多，同时对系统的负载和消耗也更低一些。

既然绘图性能是基于CPU和GPU的，你需要找出哪一个是限制你绘图性能的。如果你用尽了GPU所有的资源，也就是说，是GPU限制了你的性能，同样的，如果你用尽了CPU，那就是CPU限制了你的性能。

要告诉你，如果是GPU限制了你的性能，你可以使用OpenGL ES Driver instrument。点击上面那个小的i按钮，配置一下，同时注意查看Device Utilization %。现在，当你运行你的app时，你可以看到你GPU的负荷。如果这个值靠近100%，那么你就需要把你工作的重心放在GPU上面了。

Core Graphics /Quartz 2D

通过Core Graphics这个框架，Quartz 2D更为广泛的被知道。

Quartz 2D拥有比我们这里谈到更多的装饰。我们这里不会过多的讨论关于PDF的创建，渲染，解析，或者打印。只需要注意的是，PDF的打印和创建是在屏幕上绘制位图，很大程度上这两种操作是大同小异的。因为他们都是基于Quartz 2D。

让我们简单的了解一下Quartz 2D主要的概念。有关详细信息可以到苹果的官方文档中了解。

放心，当Quartz 2D涉及到2D绘制的时候，它是非常强大的。有基于路径的绘制，反锯齿渲染，透明图层，分辨率，并且设备独立，可以说出很多特色。这可能会让人产生畏惧，主要因为这是一个低级并且基于C的API。

主要的概念当对简单，UIKit和AppKit都包含了Quartz 2D的一些简单API，一旦你熟练了，一些简单C的API也是很容易理解的。最终你学会了一个能实现Photoshop和Illustrator大部分功能的绘图引擎。苹果把iOS程序里面的股票应用作为讲解Quartz 2D在代码中实现动态渲染的一个例子。

当你的程序进行位图绘制时，不管这种方式或那种方式，都是基于Quartz 2D的。也就是说，CPU实现的部分绘制是通过Quartz 2D实现的。尽管Quartz可以做其它的事情，但是我们这里还是集中于位图绘制，在缓冲区(一块内存)绘制位图会包括RGBA数据。

比方说，我们要画一个八角形，我们通过UIKit能做到这一点

相对应的Core Graphics代码：

需要问的问题是:这个绘制到哪儿去了？这正好引出所谓的CGContext登场。我们传过去的ctx参数正是在那个上下文中。而这个上下文定义了我们需要绘制的地方。如果我们实现了CALayer的-drawInContext:这时已经传过来一个上下文。绘制到这个上下文中的内容将会被绘制到图层的备份区(图层的缓冲区).但是我们也可以创建我们自己的上下文，叫做基于位图的上下文，比如CGBitmapContextCreate().这个方法返回一个我们可以传给CGContext方法来绘制的上下文。

注意UIKit版本的代码为何不传入一个上下文参数到方法中？这是因为当使用UIKit或者AppKit时，上下文是唯一的。UIkit维护着一个上下文堆栈，UIKit方法总是绘制到最顶层的上下文中。你可以使用UIGraphicsGetCurrentContext()来得到最顶层的上下文。你可以使用UIGraphicsPushContext()和UIGraphicsPopContext()在UIKit的堆栈中推进或取出上下文。

最为突出的是，UIKit使用UIGraphicsBeginImageContextWithOptions()和UIGraphicsEndImageContext()方便的创建类似于CGBitmapContextCreate()的位图上下文。混合调用UIKit和Core Graphics非常简单：

或者另外一种方法:

你可以使用Core Graphics创建大量的非常酷的东西。一个很好的理由就是，苹果的文档有很多例子。我们不能得到所有的细节，但是Core Graphics有一个非常接近Adobe Illustrator和Adobe Photoshop如何工作的绘图模型，并且大多数工具的理念翻译成Core Graphics了。终究，他是起源于NeXTSTEP。(原来也是乔老爷的作品)

CGLayer

一件非常值得提起的事，便是CGLayer。它经常被忽视，并且它的名字有时会造成困惑。在Photoshop中，从图层的意义上讲，它不是一个图层。从Core Animation的意义上讲，它还不是一个图层。

把CGLayer想象成一个子上下文。它共用父上下文的所有特性。你可以独立于父上下文，在它自己的后备存储中绘制。并且因为它跟上下文紧密的联系在一起，CGLayer可以被高效的绘制到上下文中。

什么时候这将变得有用?如果你是用Core Graphics来绘制一些相当难懂的，并且部分内容需要被重新绘制的，你只需将那部分内容绘制到CGLayer一次，然后便可绘制/合成这个CGLayer到父上下文中。当这可行的时候，这是一个非常优雅的性能窍门。这和我们上文中提到的屏幕外缓冲区概念有点类似。你需要做出权衡的便是，是否需要为CGLayer的后备存储申请额外的内存，衡量这是否对你有用。

屏幕上的像素是由红，绿，蓝三种颜色组件构成的。因此，位图数据有时也被叫做RGB数据。你可能会对数据如何组织在内存中感到好奇。而事实是，有很多种不同的方式在内存中展现RGB位图数据。

稍后我们将会谈到压缩数据，这又是一个完全不同的概念。现在，我们先看一下RGB位图数据，我们可以从颜色组件:红，绿，蓝中得到一个值。而大多数情况下，我们有第四个组件:透明度。最终我们从每个像素中得到四个单独的值。

默认的像素布局

在iOS和OS X上最常见的格式就是大家所熟知的32bits-per-pixel(bpp),8bits-per-componet(bpc),透明度会首先被乘以到像素值上(就像上文中提到的那个公式一样),在内存中，像下面这样:

这个格式经常被叫做ARGB。每个像素占用4字节(32bpp),每一个颜色组件是1字节(8bpc).每个像素有一个alpha值，这个值总是最先得到的(在RGB值之前)，最终红、绿、蓝的值都会被预先乘以alpha的值。预乘的意思就是alpha值被烘烤到红、绿、蓝的组件中。如果我们有一个橙色，他们各自的8bpc就像这样:240,99,24.一个完全不透明的橙色像素拥有的ARGB值为255，240，99，24，它在内存中的布局就像上面图示那样。如果我们有一个相同颜色的像素，但是alpha值为33%，那么他的像素值便是84，80，33，8.

另一个常见的格式便是32bpp，8bpc，跳过第一个alpha值，看起来像下面这样：

这常被叫做xRGB。像素并没有任何alpha值(他们都被假定为100%不透明)，但是内存布局是一样的。你应该想知道为什么这种格式很流行，当我们每一个像素中都有一个不用字节时，我们将会省下25%的空间。事实证明，这种格式更容易被现代的CPU和绘图算法消化，因为每一个独立的像素都对齐到32-bit的边界。现代的CPU不喜欢装载(读取)不对齐的数据，特别是当将这种数据和上面没有alpha值格式的数据混合时，算法需要做很多挪动和蒙板操作。

当处理RGB数据时，Core Graphics也需要支持把alpha值放到最后(另外还要支持跳过)。有时候也分别称为RGBA和RGBx，假定是8bpc，并且预乘了alpha值。

深奥的布局

大多数时候，当处理位图数据时，我们也需要处理Core Graphics/Quartz 2D。有一个非常详细的列表列出了他支持的混合组合。但是让我们首先看一下剩下的RGB格式：

另一个选择是16bpp，5bpc，不包含alpha值。这个格式相比之前一个仅占用50%的存储大小(每个像素2字节)，但将使你存储它的RGB数据到内存或磁盘中变得困难。既然这种格式中，每个颜色组件只有5bits(原文中写的是每个像素是5bits，但根据上下文可知应该是每个组件)，这样图形(特别是平滑渐变的)会造成重叠在一起的假象。

还有一个是64bpp，16bpc，最终为128bpp，32bpc，浮点数组件(有或没有alpha值)。它们分别使用8字节和16字节，并且允许更高的精度。当然，这会造成更多的内存使用和昂贵的计算。

整件事件中，Core Graphics也支持一些像灰度模式和CMYK格式，这些格式类似于仅有alpha值的格式(蒙板)。

二维数据

当颜色组件(红、绿、蓝、alpha)混杂在一起的时候，大多数框架(包括Core Graphics)使用像素数据。正是这种情况下我们称之为二维数据，或者二维组件。这个意思是：每一个颜色组件都在它自己的内存区域，也就是说它是二维的。比如RGB数据，我们有三个独立的内存区域，一个大的区域包含了所有像素的红颜色的值，一个包含了所有绿颜色的值，一个包含了所有蓝颜色的值。

在某些情况下，一些视频框架便会使用二维数据。

YCbCr

当我们处理视频数据时，YCbCr是一种常见的格式。它也是包含了三种(Y,Cb和Cr)代表颜色数据的组件。但是简单的讲，它更类似于通过人眼看到的颜色。人眼对Cb和Cr这两种组件的色彩度不太能精确的辨认出来，但是能很准确的识别出Y的亮度。当数据使用YCbCr格式时，在同等的条件下，Cb和Cr组件比Y组件压缩的更紧密。

出于同样的原因，JPEG图像有时会将像素数据从RGB转换到YCbCr。JPEG单独的压缩每一个二维颜色。当压缩基于YCbCr的平面时，Cb和Cr能比Y压缩得更完全。

图片格式

当你在iOS或者OS X上处理图片时，他们大多数为JPEG和PNG。让我们更进一步观察。

JPEG

每个人都知道JPEG。他是相机的产物。它代表这照片如何存储在电脑上。甚至你嘛嘛都听说过JPEG。

一个很好的理由，很多人都认为JPEG文件仅是另一种像素数据的格式，就像我们刚刚谈到的RGB像素布局那样。这样理解离真像真是差十万八千里了。

将JPEG数据转换成像素数据是一个非常复杂的过程，你通过一个周末的计划都不能完成，甚至是一个非常漫长的周末(原文的意思好像就是为了表达这个过程非常复杂，不过老外的比喻总让人拎不清)。对于每一个二维颜色，JPEG使用一种基于离散余弦变换(简称DCT变换)的算法，将空间信息转变到频域.这个信息然后被量子化，排好序，并且用一种哈夫曼编码的变种来压缩。很多时候，首先数据会被从RGB转换到二维YCbCr，当解码JPEG的时候，这一切都将变得可逆。

这也是为什么当你通过JPEG文件创建一个UIImage并且绘制到屏幕上时，将会有一个延时，因为CPU这时候忙于解压这个JPEG。如果你需要为每一个tableviewcell解压JPEG，那么你的滚动当然不会平滑(原来tableviewcell里面最要不要用JPEG的图片)。

那究竟为什么我们还要用JPEG呢？答案就是JPEG可以非常非常好的压缩图片。一个通过Iphone5拍摄的，未经压缩的图片占用接近24M。但是通过默认压缩设置，你的照片通常只会在2-3M左右。JPEG压缩这么好是因为它是失真的，它去除了人眼很难察觉的信息，并且这样做可以超出像gzip这样压缩算法的限制。但这仅仅在图片上有效的，因为JPEG依赖于图片上有很多人类不能察觉出的数据。如果你从一个基本显示文本的网页上截取一张图，JPEG将不会这么高效。压缩效率将会变得低下，你甚至能看出来图片已经压缩变形了。

PNG

PNG读作”ping”。和JPEG相反，它的压缩对格式是无损的。当你将一张图片保存为PNG，并且打开它(或解压)，所有的像素数据会和最初一模一样，因为这个限制，PNG不能像JPEG一样压缩图片，但是对于像程序中的原图(如buttons，icons)，它工作的非常好。更重要的是，解码PNG数据比解码JPEG简单的多。

在现实世界中，事情从来没有那么简单，目前存在了大量不同的PNG格式。可以通过维基百科查看详情。但是简言之，PNG支持压缩带或不带alpha通道的颜色像素(RGB)，这也是为什么它在程序原图中表现良好的另一个原因。

挑选一个格式

当你在你的程序中使用图片时，你需要坚持这两种格式:JPEG或者PNG。读写这种格式文件的压缩和解压文件能表现出很高的性能，另外，还支持并行操作。同时Apple正在改进解压缩并可能出现在将来的新操作系统中，届时你将会得到持续的性能提升。如果尝试使用另一种格式，你需要注意到，这可能对你程序的性能会产生影响，同时可能会打开安全漏洞，经常，图像解压缩算法是黑客最喜欢的攻击目标。

已经写了很多关于优化PNGs，如果你想要了解更多，请到互联网上查询。非常重要的一点，注意Xcode优化PNG选项和优化其他引擎有很大的不同。

当Xcode优化一个PNG文件的时候，它将PNG文件变成一个从技术上讲不再是有效的PNG文件。但是iOS可以读取这种文件，并且这比解压缩正常的PNG文件更快。Xcode改变他们，让iOS通过一种对正常PNG不起作用的算法来对他们解压缩。值得注意的重点是，这改变了像素的布局。正如我们所提到的一样，在像素之下有很多种方式来描绘RGB数据，如果这不是iOS绘制系统所需要的格式，它需要将每一个像素的数据替换，而不需要加速来做这件事。

让我们再强调一遍，如果你可以，你需要为原图设置resizable images。你的文件将变得更小，因此你只需要从文件系统装载更少的数据。

UIKit和Pixels

每一个在UIKit中的view都有它自己的CALayer。依次，这些图层都有一个叫像素位图的后备存储，有点像一个图像。这个后备存储正是被渲染到显示器上的。

With –drawRect:

如果你的视图类实现了-drawRect:，他们将像这样工作:

当你调用-setNeedsDisplay，UIKit将会在这个视图的图层上调用-setNeedsDisplay。这为图层设置了一个标识，标记为dirty(直译是脏的意思，想不出用什么词比较贴切)，但还显示原来的内容。它实际上没做任何工作，所以在一个row中可以多次调用-setNeedsDisplay。

下面，当渲染系统准备好，它会调用视图图层的-display方法.此时，图层会装配它的后备存储。然后建立一个Core Graphics上下文(CGContextRef)，将后备存储对应内存中的数据恢复出来，绘图会进入对应的内存区域，并使用CGContextRef绘制。

当你使用UIKit的绘制方法，例如:UIRectFill()或者-[UIBezierPath fill]代替你的-drawRect:方法，他们将会使用这个上下文。使用方法是，UIKit将后备存储的CGContextRef推进他的graphics context stack，也就是说，它会将那个上下文设置为当前的。因此UIGraphicsGetCurrent()将会返回那个对应的上下文。既然UIKit使用UIGraphicsGetCurrent()绘制方法，绘图将会进入到图层的后备存储。如果你想直接使用Core Graphics方法，你可以自己调用UIGraphicsGetCurrent()得到相同的上下文，并且将这个上下文传给Core Graphics方法。

从现在开始，图层的后备存储将会被不断的渲染到屏幕上。直到下次再次调用视图的-setNeedsDisplay，将会依次将图层的后备存储更新到视图上。

不使用-drawRect:

当你用一个UIImageView时，事情略有不同，这个视图仍然有一个CALayer，但是图层却没有申请一个后备存储。取而代之的是使用一个CGImageRef作为他的内容，并且渲染服务将会把图片的数据绘制到帧的缓冲区，比如，绘制到显示屏。

在这种情况下，将不会继续重新绘制。我们只是简单的将位图数据以图片的形式传给了UIImageView，然后UIImageView传给了Core Animation，然后轮流传给渲染服务。

实现-drawRect:还是不实现-drawRect:

这听起来貌似有点低俗，但是最快的绘制就是你不要做任何绘制。

大多数时间，你可以不要合成你在其他视图(图层)上定制的视图(图层)，这正是我们推荐的，因为UIKit的视图类是非常优化的 (就是让我们不要闲着没事做,自己去合并视图或图层) 。

当你需要自定义绘图代码时，Apple在WWDC 2012’s session 506:Optimizing 2D Graphics and Animation Performance中展示了一个很好的例子:”finger painting”

另一个地方需要自定义绘图的就是iOS的股票软件。股票是直接用Core Graphics在设备上绘制的，注意，这仅仅是你需要自定义绘图，你并不需要实现-drawRect:方法。有时，通过UIGraphicsBeginImageContextWithOptions()或者CGBitmapContextCeate()创建位图会显得更有意义，从位图上面抓取图像，并设置为CALayer的内容。下面我们将给出一个例子来测试，检验。

单一颜色

如果我们看这个例子：

现在我们知道这为什么不好:我们促使Core Animation来为我们创建一个后备存储，并让它使用单一颜色填充后备存储，然后上传给GPU。

我们跟本不需要实现-drawRect:，并节省这些代码工作量，只需简单的设置这个视图图层的背景颜色。如果这个视图有一个CAGradientLayer作为图层，那么这个技术也同样适用于此（渐变图层）。

可变尺寸的图像

类似的，你可以使用可变尺寸的图像来降低绘图系统的压力。让我们假设你需要一个300x500点的按钮插图，这将是600x100=60k像素或者60kx4=240kB内存大小需要上传到GPU，并且占用VRAM。如果我们使用所谓的可变尺寸的图像，我们只需要一个54x12点的图像，这将占用低于2.6k的像素或者10kB的内存，这样就变得更快了。

Core Animation可以通过CALayer的contentsCenter属性来改变图像，大多数情况下，你可能更倾向于使用，-[UIImage resizableImageWithCapInsets:resizingMode:].

同时注意，在第一次渲染这个按钮之前，我们并不需要从文件系统读取一个60k像素的PNG并解码，解码一个小的PNG将会更快。通过这种方式，你的程序在每一步的调用中都将做更少的工作，并且你的视图将会加载的更快。

同时发生的绘图

最后一个objc.io的问题是关于同时发生绘图的讨论。正如你所知道的一样，UIKit的线程模型是非常简单的：你仅可以从主队列(比如主线程)中调用UIKit类(比如视图),那么同时绘图又是什么呢？

如果你必须实现-drawRect:，并且你必须绘制大量的东西，这将占用时间。由于你希望动画变得更平滑，除了在主队列中，你还希望在其他队列中做一些工作。同时发生的绘图是复杂的，但是除了几个警告，同时发生的绘图还是比较容易实现的。

我们除了在主队列中可以向CALayer的后备存储中绘制一些东西，其他方法都将不可行。可怕的事情将会发生。我们能做的就是向一个完全断开链接的位图上下文中进行绘制。

正如我们上面所提到的一样，在Core Graphics下，所有Core Graphics绘制方法都需要一个上下文参数来指定绘制到那个上下文中。UIKit有一个当前上下文的概念(也就是绘制到哪儿去)。这个当前的上下文就是per-thread.

为了同时绘制，我们需要做下面的操作。我们需要在另一个队列创建一个图像，一旦我们拥有了图像，我们可以切换回主队列，并且设置这个图像为UIImageView的图像。这个技术在WWDC 2012 session 211中讨论过。(异步下载图片经常用到这个)

增加一个你可以在其中绘制的新方法：

这个方法通过UIGraphicsBeginImageContextWithOptions()方法，并根据给定的大小创建一个新的CGContextRef位图。这个方法也会将这个上下文设置为当前UIKit的上下文。现在你可以在这里做你想在-drawRect:中做的事了。然后我们可以通过UIGraphicsGetImageFromCurrentImageContext(),将获得的这个上下文位图数据作为一个UIImage，最终移除这个上下文。

很重要的一点就是，你在这个方法中所做的所有绘图的代码都是线程安全的，也就是说，当你访问属性等等，他们需要线程安全。因为你是在另一个队列中调用这个方法的。如果这个方法在你的视图类中，那就需要注意一点了。另一个选择就是创建一个单独的渲染类，并设置所有需要的属性，然后通过触发来渲染图片。如果这样，你可以通过使用简单的UIImageView或者UITableViewCell。

要知道，所有UIKit的绘制API在使用另一个队列时，都是安全的。只需要确定是在同一个操作中调用他们的，这个操作需要以UIGraphicsBeginImageContextWithOptions()开始，以UIGraphicsEndIamgeContext()结束。

你需要像下面这样触发渲染代码：

要注意，我们是在主队列中调用view.image = image.这是一个非常重要的细节。你不可以在任何其他队列中调用这个代码。

像往常一样，同时绘制会伴随很多问题，你现在需要取消后台渲染。并且在渲染队列中设置合理的同时绘制的最大限度。

为了支持这一切，最简单的就是在一个NSOperation子类内部实现-renderInImageOfSize:

最终，需要指出，设置UITableViewCell上下文异步是非常困难的。单元格很有可能在完成异步渲染前已经被复用了。尽管单元格已经被其他地方复用，但你只需要设置上下文就行了。

CALayer

到现在为止，你需要知道在GPU内，一个CALayer在某种方式上和一个纹理类似。图层有一个后备存储，这便是被用来绘制到屏幕上的位图。

通常，当你使用CALayer时，你会设置它的上下文为一个图片。这到底做了什么？这样做会告诉Core Animation使用图片的位图数据作为纹理。如果这个图片(JPEG或PNG)被压缩了，Core Animation将会这个图片解压缩，然后上传像素数据到GPU。

尽管还有很多其他中图层，如果你是用一个简单的没有设置上下文的CALayer，并为这个CALayer设置一个背景颜色，Core Animation并不会上传任何数据到GPU，但却能够不用任何像素数据而在GPU上完成所有的工作，类似的，对于渐变的图层，GPU是能创建渐变的，而且不需要CPU做任何工作，并且不需要上传任何数据到GPU。

自定义绘制的图层

如果一个CALayer的子类实现了-drawInContext:或者它的代理，类似于-drawLayer:inContest:,Core Animation将会为这个图层申请一个后备存储，用来保存那些方法绘制进来的位图。那些方法内的代码将会运行在CPU上，结果将会被上传到GPU。

形状和文本图层

形状和文本图层还是有些不同的。开始时，Core Animation为这些图层申请一个后备存储来保存那些需要为上下文生成的位图数据。然后Core Animation会讲这些图形或文本绘制到后备存储上。这在概念上非常类似于，当你实现-drawInContext:方法，然后在方法内绘制形状或文本，他们的性能也很接近。

在某种程度上，当你需要改变形状或者文本图层时，这需要更新它的后备存储，Core Animation将会重新渲染后备存储。例如，当动态改变形状图层的大小时，Core Animation需要为动画中的每一帧重新绘制形状。

异步绘图

CALayer有一个叫做drawsAsynchronously的属性，这似乎是一个解决所有问题的高招。注意，尽管这可能提升性能，但也可能让事情变慢。

当你设置drawsAsynchronously为YES时，发生了什么？你的-drawRect:/-drawInContext:方法仍然会被在主线程上调用。但是所有调用Core Graphics的操作都不会被执行。取而代之的是，绘制命令被推迟，并且在后台线程中异步执行。

这种方式就是先记录绘图命令，然后在后台线程中重现。为了这个过程的顺利进行，更多的工作需要被做，更多的内存需要被申请。但是主队列中的一些工作便被移出来了(大概意思就是让我们把一些能在后台实现的工作放到后台实现，让主线程更顺畅)。

对于昂贵的绘图方法，这是最有可能提升性能的，但对于那些绘图方法来说，也不会节省太多资源。

原文链接：http://www.cocoachina.com/applenews/devnews/2013/0813/6781.html （本内容由互联网资料整理而成）

Backbone的技巧和模式

Backbone.js is a popular open-source JavaScript “MV*” framework that has gained significant traction since its first release a little over three years ago. Although Backbone.js provides structure to JavaScript applications, it leaves a lot of design patterns and decisions up to the developer, for better or worse, and developers run into many common problems when they first begin developing in Backbone.js. Therefore, in this article, we’ll explore different design patterns that you can use in your Backbone.js applications, and we’ll look at many of the common gotchas that trip up developers.

Applications, like buildings, are best built following known patterns. (Image: Matthew Rutledge)

Perform Deep Copies Of Objects

JavaScript treats all primitive-type variables as pass-by-value. So, the value of a variable is passed when the variable is referenced.

var helloWorld = “Hello World”;
var helloWorldCopy = helloWorld;

For example, the code above will set helloWorldCopy equal to the value of helloWorld. So, any modification to helloWorldCopy would not modify helloWorld, since it is a copy. JavaScript treats all non-primitive-type variables as pass-by-reference, meaning that JavaScript will pass a reference of the memory address of the variable when the variable is referenced.

var helloWorld = {
‘hello’: ‘world’
}
var helloWorldCopy = helloWorld;

For example, the code above will set helloWorldCopy equal to the reference of helloWorld, and, as you might guess, any modifications to helloWorldCopy would directly manipulate helloWorld. If you’d like to have a copy of helloWorld, you will have to create a copy of the object.

You’re probably wondering, “Why is he explaining all of this pass-by-reference and pass-by-value stuff?” Well, Backbone.js does not copy objects, which means that if you .get() an object from a model, any modifications to that object will directly manipulate the object! Let’s look at an example to illustrate where this can become an issue. Let’s say you have a Person model like the following:

var Person = Backbone.Model.extend({
defaults: {
'name': 'John Doe',
'address': {
'street': '1st Street'
'city': 'Austin',
'state': 'TX'
'zipCode': 78701
}
}
});

And let’s say you create a new person object:

var person = new Person({
'name': 'Phillip W'
});

Now, let’s manipulate some of the attributes of the new person object:

person.set('name', 'Phillip W.');

The code above successfully manipulates the name attribute of the person object. Now let’s try to manipulate the address of the person object. However, before doing so, let’s add some validation for the address.

var Person = Backbone.Model.extend({
validate: function(attributes) {

if(isNaN(attributes.address.zipCode)) return "Address ZIP code must be a number!";
},

defaults: {
'name': 'John Doe',
'address': {
'street': '1st Street'
'city': 'Austin',
'state': 'TX'
'zipCode': 78701
}
}
});

Now, let’s attempt to manipulate the address with an incorrect ZIP code.

var address = person.get('address');
address.zipCode = 'Hello World';
// Raises an error since the ZIP code is invalid
person.set('address', address);
console.log(person.get('address'));
/* Prints an object with these properties.
{
'street': '1st Street'
'city': 'Austin',
'state': 'TX'
'zipCode': 'Hello World'
}
*/

How can this be? Our validation has raised an error! Why are the attributes still changed? As mentioned, Backbone.js does not copy model attributes; it simply returns whatever you’re asking for. So, as you might guess, if you ask for an object, you will get a reference to that object, and any manipulation to the object will directly manipulate the actual object in the model. This can quickly lead you down a very dark rabbit hole that can take you hours of debugging to diagnose.

Performing a deep copy of model objects can save you from going down a rabbit hole of debugging. (Image: David Orban)

This issue catches developers who are new to Backbone.js and even seasoned JavaScript developers off guard. This issue has been heavily discussed in the GitHub issues section of Backbone.js. As Jeremy Ashkenas points out there, performing a deep copy is a very difficult problem to solve, and it can become an expensive operation for very large, deep objects.

Luckily, jQuery provides an implementation of deep copying, $.extend. As an aside, Underscore.js, a dependency of Backbone.js, provides the _.extend function, but I would avoid using it because it does not perform a deep copy. I use $.extend to perform a deep copy of any object that I .get() from a model using the following syntax. Remember to pass true, so that it performs a deep copy of the object.

var address = $.extend(true, {}, person.address);

We now have an exact copy of the address object, and we can modify it to our heart’s content without worrying about modifying the actual model. You should know that there is a small performance hit from performing a deep copy, but I have never seen this cause noticeable problems. However, if you’re deep copying massive objects, or thousands of objects all at once, you’ll likely want to do some performance profiling. This leads us directly to the next pattern.

Create Facades To Objects

In the real world, requirements change often, and so does the JavaScript Object Notation (or JSON) returned by the endpoints that your models and collections hit. This can become a really big pain point in your code base if your view is tightly coupled to the underlying data model. Therefore, I create getters and setters for all objects.

The pros of this pattern are many. If any of the underlying data structures change, then the view layers shouldn’t have to update that much; you will have one point of access to the data, so you are less likely to forget to do a deep copy, and your code will be more maintainable and much easier to debug. The downside is that this pattern can cause a bit of bloat in your models or collections.

Let’s look at an example to illustrate this pattern. Imagine we have a Hotel model that contains rooms and the currently available rooms, and that we want to be able to retrieve rooms by bed size.

var Hotel = Backbone.Model.extend({
defaults: {
"availableRooms": ["a"],
"rooms": {
"a": {
"size": 1200,
"bed": "queen"
},
"b": {
"size": 900,
"bed": "twin"
},
"c": {
"size": 1100,
"bed": "twin"
}
},

getRooms: function() {
$.extend(true, {}, this.get("rooms"));
},

getRoomsByBed: function(bed) {
return _.where(this.getRooms(), function() {
{ "bed": bed }
});
}
}
});

Now let’s imagine that tomorrow you will be releasing your code, and you find out that the endpoint developers forgot to tell you that the data structure of rooms has changed from an object to an array. Your code now looks like the following.

var Hotel = Backbone.Model.extend({
defaults: {
"availableRooms": ["a"],
"rooms": [
{
"name": "a",
"size": 1200,
"bed": "queen"
},
{
"name": "b",
"size": 900,
"bed": "twin"
},
{
"name": "c",
"size": 1100,
"bed": "twin"
}
],

getRooms: function() {
var rooms = $.extend(true, {}, this.get("rooms")),
newRooms = {};

// transform rooms from an array back into an object
_.each(rooms, function(room) {
newRooms[room.name] = {
"size": room.size,
"bed": room.bed
}
});
},

getRoomsByBed: function(bed) {
return _.where(this.getRooms(), function() {
{ "bed": bed }
});
}
}
});

We updated only one function in order to transform the Hotel structure into the structure that the rest of our application expects, and our entire app still behaves as expected. If we didn’t have a getter here, we might potentially have to update each point of access to rooms. Ideally, you would want to update all of your functions to work with the new data structure, but if you’re pressed for time and have to release, this pattern can save you.

As an aside, this pattern can be thought of as both a facade design pattern, because it hides the complexity of creating a copy of your objects, or a bridge design pattern, because it can be used to transform data into what is expected. A good rule of thumb is to use getters and setters on anything that is an object.

Store Data Not Persisted By The Server

Although Backbone.js prescribes that models and collections map to representational state transfer (or REST-ful) endpoints, you will sometimes find that you want to store data in your models or collections that is not persisted on the server. Many other articles about Backbone.js, such as “Backbone.js Tips: Lessons From the Trenches” by Prateek Dayal of SupportBee, describe this pattern. Let’s look at a quick example to help explain where this might come in handy. Suppose you have a ul list.

<ul>
<li><a href="#" data-id="1">One</a></li>
<li><a href="#" data-id="2">Two</a></li>
. . .
<li><a href="#" data-id="n">n</a></li>
</ul>

Where n is 200 and when the user clicks on one of the items, that item becomes selected and is visualized to the user as the chosen item by a selected class being added. One approach to this would be the following:

var Model = Backbone.Model.extend({
defaults: {
items: [
{
"name": "One",
"id": 1
},
{
"name": "Two",
"id": 2
},
{
"name": "Three",
"id": 3
}
]
}
});

var View = Backbone.View.extend({
template: _.template($('#list-template').html()),

events: {
"#items li a": "setSelectedItem"
},

render: function() {
$(this.el).html(this.template(this.model.toJSON()));
},

setSelectedItem: function(event) {
var selectedItem = $(event.currentTarget);
// Set all of the items to not have the selected class
$('#items li a').removeClass('selected');
selectedItem.addClass('selected');
return false;
}
});

<script id="list-template" type="template">
<ul id="items">
<% for(i = items.length - 1; i >= 0; i--) { %>
<li>
<a href="#" data-id="<%= item[i].id %>"><%= item[i].name %></a></li>
<% } %></ul>
</script>

Now let’s say we want to figure out which item has been selected. One way would be to traverse the list. But if the list is really long, this could become quite an expensive task. Therefore, let’s also store which item is selected when the user clicks on a list item.

var Model = Backbone.Model.extend({
defaults: {
selectedId: undefined,
items: [
{
"name": "One",
"id": 1
},
{
"name": "Two",
"id": 2
},
{
"name": "Three",
"id": 3
}
]
}
});

var View = Backbone.View.extend({
initialize: function(options) {
// Re-render when the model changes
this.model.on('change:items', this.render, this);
},

template: _.template($('#list-template').html()),

events: {
"#items li a": "setSelectedItem"
},

render: function() {
$(this.el).html(this.template(this.model.toJSON()));
},

setSelectedItem: function(event) {
var selectedItem = $(event.currentTarget);
// Set all of the items to not have the selected class
$('#items li a').removeClass('selected');
selectedItem.addClass('selected');
// Store a reference to what item was selected
this.model.set('selectedId', selectedItem..data('id'));
return false;
}
});

Now we can easily query our model to determine which item has been selected, and we don’t have to traverse the Document Object Model (DOM). This pattern is extremely useful for storing extraneous data that you might want to keep track of; keep in mind also that you can create models and collections that don’t necessarily have endpoints associated with them.

The downsides to this pattern are that your models or collections won’t really follow a RESTful architecture since they won’t map perfectly to a Web resource; also, this pattern can cause a bit of bloat in your models; and it can cause a bit of pain when saving your models if your endpoint strictly accepts only the JSON it expects.

Render Parts Of Views Instead Of Entire Views

When you first begin developing Backbone.js applications, your views will typically be structured something like this:

var View = Backbone.View.extend({
initialize: function(options) {
this.model.on('change', this.render, this);
},

template: _.template($(‘#template’).html()),

render: function() {
this.$el.html(template(this.model.toJSON());
$(‘#a’, this.$el).html(this.model.get(‘a’));
$(‘#b’, this.$el).html(this.model.get(‘b’));
}
});

Here, any change to your model will trigger a complete re-rendering of the view. I was a practitioner of this pattern when I first began developing with Backbone.js. But as my view code grew in size, I quickly realized that this approach was not maintainable or optimal because the view was being completely re-rendered when any attribute of the model changed.

When I ran into this problem, I did a quick Google search to see what others had done and found Ian Storm Taylor’s blog post, “Break Apart Your Backbone.js Render Methods,” in which he describes listening for individual attribute changes in the model and then re-rendering only the part of the view corresponding to the changed attribute. Taylor also describes returning a reference to the object so that the individual rendering functions can be easily chained together. The example above now transforms into something much more maintainable and performant because we are only updating portions of the view that correspond to the model attributes that have changed.

var View = Backbone.View.extend({
initialize: function(options) {
this.model.on('change:a', this.renderA, this);
this.model.on('change:b', this.renderB, this);
},

renderA: function() {
$(‘#a’, this.$el).html(this.model.get(‘a’));
return this;
},

renderB: function() {
$(‘#b’, this.$el).html(this.model.get(‘b’));
return this;
},

render: function() {
this
.renderA()
.renderB();
}
});

I should mention that many plugins, such as Backbone.StickIt and Backbone.ModelBinder, provide key-value bindings between model attributes and view elements, which can save you from a lot of boilerplate code. So, check them out if you have complex form fields.

Keep Models Agnostic To Views

As Jeremy Ashkenas points out in one of Backbone.js’ GitHub issues, Backbone.js doesn’t enforce any real separation of concerns between the data and view layer, except that models are not created with a reference to their view. Because Backbone.js doesn’t enforce a separation of concerns, should you? I and many other Backbone.js developers, such as Oz Katz and Dayal, believe the answer is overwhelmingly yes: Models and collections, the data layer, should be entirely agnostic of the views that are bound to them, keeping a clear separation of concerns. If you don’t follow a separation of concerns, your code base could quickly turn into spaghetti code, and no one likes spaghetti code.

Keeping your models agnostic to your views will help prevent spaghetti code, and no one likes spaghetti code! (Image: Sira Hanchana)

Keeping your data layer completely agnostic to the view layer allows for a more modular, reusable and maintainable code base. You can easily reuse and extend models and collections throughout your application without concern for the views that are tying into them. Following this pattern enables developers who are new to your project to quickly dive into the code base, because they will know exactly where rendering takes place and where all of the business logic of your application should live.

This pattern also enforces the single responsibility principle, which dictates that each class should have a single responsibility, and its responsibility should be encapsulated in the class, since your models and collections should handle data and your views should handle rendering.

Parameter Mapping In Routers

The best way to demonstrate how this pattern came about is through an example. Let’s say you have some sort of search page, and that the search page allows users to add two different filter types, foo and bar, each of which has a multitude of options. Therefore, your URL structure would start off looking something like this:

'search/:foo'
'search/:bar'
'search/:foo/:bar'

Now, all of these routes use the exact same view and model, so you would ideally like them all to use the same function, search(). However, if you examine Backbone.js, there isn’t any sort of parameter mapping; the parameters are just plopped into the function from left to right. So, in order for them all to use the same function, you would end up creating different functions to correctly map the parameters to search().

routes: {
'search/:foo': 'searchFoo',
'search/:bar': 'searchBar',
'search/:foo/:bar': 'search'
},

search: function(foo, bar) {
},

// I know this function will actually still map correctly, but for explanatory purposes, it's left in.
searchFoo: function(foo) {
this.search(foo, undefined);
},

searchBar: function(bar) {
this.search(undefined, bar);
},

As you can imagine, this pattern could quickly bloat your routers. When I first ran into this problem, I attempted to do some parsing of the actual function definitions with regular expressions to “magically” map the parameters, which would have worked — but only if I had followed specific constraints. So, I scrapped the idea (I might still throw this into a Backbone plugin sometime). I opened an issue on GitHub, and Ashkenas suggested mapping all of the parameters in the search function.

The code above now transforms into something much more maintainable:

routes: {
'base/:foo': 'search',
'base/:bar': 'search',
'base/:foo/:bar': 'search'
},

search: function() {
var foo, bar, i;

for(i = arguments.length - 1; i >= 0; i--) {

if(arguments[i] === 'something to determine foo') {
foo = arguments[i];
continue;
}
else if(arguments[i] === 'something to determine bar') {
bar = arguments[i];
continue;
}
}
},

This pattern can drastically reduce router bloat. However, be aware that it will not work for parameters that are not distinguishable. For example, if you had two parameters that were both IDs and followed the pattern XXXX-XXXX, you would not be able to identify which ID corresponded to which parameter.

model.fetch() Won’t Clear Your Model

This usually trips up those who are new to Backbone.js: model.fetch() will not clear out your model, but rather extends the attributes of your model. Therefore, if your model has attributes x, y and z and your fetch returns y and z, then x will still be in the model and only y and z will be updated. The following example visualizes this concept.

var Model = Backbone.Model.extend({
defaults: {
x: 1,
y: 1,
z: 1
}
});
var model = new Model();
/* model.attributes yields
{
x: 1,
y: 1,
z: 1
} */
model.fetch();
/* let’s assume that the endpoint returns this
{
y: 2,
z: 2,
} */
/* model.attributes now yields
{
x: 1,
y: 2,
z: 2
} */

PUTs Require An ID Attribute

This one also usually trips up those who are new to Backbone.js. To have your models send a HTTP PUT request when you call .save(), your model is required to have an ID attribute set. Remember that the HTTP PUT verb is designed to be an update, so it makes sense that to send a PUT request, your model is required to have an ID. In an ideal world, all of your models would have a perfect ID attribute named ID, but the JSON data that you receive from your endpoints will likely not always have perfectly named IDs.

Therefore, if you need to update a model, be sure that an ID is on the model before saving it. Backbone.js versions 0.5 and greater allow you to change the name of the ID attribute of your models using idAttribute, in case your endpoint doesn’t return IDs named id.

If are stuck using a version of Backbone.js lower than 0.5, I suggest that you modify the parse function of your collection or model to map your expected ID attribute to the attribute ID. Here’s a quick example of how you can achieve this by modifying your parse function. Let’s imagine you have a collection of cars whose IDs are carID.

parse: function(response) {

_.each(response.cars, function(car, i) {
// map the returned ID of carID to the correct attribute ID
response.cars[i].id = response.cars[i].carID;
});

return response;
},

Model Data On Page Load

Sometimes you will find that your models or collections need to be initialized with data on page load. Many articles about Backbone.js patterns, such as Rico Sta Cruz’ “Backbone Patterns” and Katz’ “Avoiding Common Backbone.js Pitfalls,” discuss this pattern. This pattern is easily achieved by inlining a script on the page and rendering out the data in either single-model attributes or JSON, using the server-side language of your choice. For example, in Rails, I use one of the following:

// a single attribute
var model = new Model({
hello:
});
// or to have json
var model = new Model();

Using this pattern could improve your search engine rankings by “instantly” rendering out your page, and it could drastically shorten the time it takes for your application to be up and running by limiting the application’s initial HTTP requests.

Handling Failed Model Attribute Validation

Very often, you will want to know what model attributes have failed validation. For example, if you have an extremely complex form field, you might want to know which model attribute has failed validation so that you can highlight the input field corresponding to the attribute. Unfortunately, alerting your views as to which model attributes have failed validation is not directly baked into Backbone.js, but you can use a few different pattern to handle this.

Return an Error Object

One pattern to notify your views of which model attributes have failed validation is to pass back an object with some sort of flag detailing which attribute has failed validation, such as the following:

// Inside your model
validate: function(attrs) {
var errors = [];

if(attrs.a < 0) {
errors.push({
'message': 'Form field a is messed up!',
'class': 'a'
});
}
if(attrs.b < 0) {
errors.push({
'message': 'Form field b is messed up!',
'class': 'b'
});
}

if(errors.length) {
return errors;
}
}
// Inside your view
this.model.on('invalid’, function(model, errors) {
_.each(errors, function(error, i) {
$(‘.’ + error.class).addClass('error');
alert(error.message);
});
});

The advantage of this pattern is that you are handling all invalid messages in one location. The disadvantage is that your invalid method could become a large switch or if statement if you deal with invalid attributes differently.

Broadcast Custom Error Event

An alternative pattern, suggested by a friend of mine, Derick Bailey, is to trigger custom errors events for each model attribute. This allows your views to bind to specific error events for individual attributes:

// Inside your model
validate: function(attrs) {

if(attrs.a < 0) {
this.trigger(‘invalid:a’, 'Form field a is messed up!', this);
}
if(attrs.b < 0) {
this.trigger(‘invalid:b’, 'Form field b is messed up!', this);
}
}
// Inside your view
this.model.on('invalid:a’, function(error) {
$(‘a’).addClass('error');
alert(error);
});
this.model.on('invalid:b’, function(error) {
$(‘b’).addClass('error');
alert(error);
});

The advantage of this pattern is that your view bindings are explicit in the type of error they are bound to, and if you have specific instructions for each type of attribute error, it can clean up your view code and make it more maintainable. The one downside of this pattern is that your views could become rather bloated if there aren’t many differences in how you handle different attribute errors.

Both patterns have their pros and cons, and you should think about which pattern is best for your use case. If you treat all failed validation errors the same way, then the first approach would probably be best; if you have specific UI changes per model attribute, then the latter approach would be better.

HTTP Status Code 200 Trigger Error

If the endpoint that your model or collection is hitting returns invalid JSON, then your model or collection will trigger an “error” event, even if the endpoint returns a 200 HTTP status code. This issue mostly comes up when developing locally against mock JSON data. So, a good rule of thumb is to throw any mock JSON files you’re developing through a JSON validator. Or get a plugin for your IDE that will catch any ill-formatted JSON.

Create A Generic Error Display

This one could save you time and create a uniform pattern for handling and visualizing error messages, which will improve your user’s overall experience. In any Backbone.js app that I develop, I create a generic view that handles alerts:

var AlertView = Backbone.View.extend({
set: function(typeOfError, message) {
var alert = $(‘.in-page-alert’).length ? $(‘.in-page-alert’): $(‘.body-alert’);
alert
.removeClass(‘error success warning’)
.html(message)
.fadeIn()
.delay(5000)
.fadeOut();
}
});

The view above first looks to see whether there is a view-specific in-page-alert div, which you would declare inside of your view’s markup. If there is no view-specific div, then it falls back to a generic body-alert div, which you would declare somewhere in the layout. This enables you to deliver a consistent error message to your users and provides a useful fallback in case you forget to specify a view-specific in-page-alert div. The pattern above streamlines how you handle error messages in your views, as follows:

this.model.on('error', function(model, error) {
alert.set('TYPE-OF-ERROR', error);
});

Update Single-Page App Document Titles

This is more of a usability concern than anything. If you’re developing a single-page application, remember to update the document title of each page! I’ve written a simple Backbone.js plugin, Backbone.js Router Title Helper, that does this in a simple and elegant format by extending the Backbone.js router. It allows you to specify a title’s object literal, whose keys map to route function names and whose values are page titles.

Backbone.Router = Backbone.Router.extend({

initialize: function(options){
var that = this;

this.on('route', function(router, route, params) {

if(that.titles) {
if(that.titles[router]) document.title = that.titles[router];
else if(that.titles.default) document.title = that.titles.default;
else throw 'Backbone.js Router Title Helper: No title found for route:' + router + ' and no default route specified.';
}
});
}
});

Cache Objects In Single-Page Applications

While we are on the topic of single-page applications, another pattern you could follow is to cache objects that will be reused! This case is fairly straightforward and simple:

// Inside a router
initialize: function() {

this.cached = {
view: undefined,
model: undefined
}
},

index: function(parameter) {
this.cached.model = this.cached.model || new Model({
parameter: parameter
});
this.cached.view = this.cached.view || new View({
model: this.cached.model
});
}

This pattern will give your application a small bump in speed because you won’t have to reinitialize your Backbone.js objects. However, it could cause your application’s memory footprint to grow rather large; so, I’ll typically only cache objects that are commonly used throughout an application. If you’ve developed Backbone.js apps in the past, then you’re likely asking yourself, “What if I want to refetch data?” You can refetch data each time the route is triggered:

// Inside a router
initialize: function() {

this.cached = {
view: undefined,
model: undefined
}
},

index: function(parameter) {
this.cached.model = this.cached.model || new Model({
parameter: parameter
});
this.cached.view = this.cached.view || new View({
model: this.cached.model
});
this.cached.model.fetch();
}

This pattern will work when your application must retrieve the latest data from the endpoint (for example, an inbox). However, if the data that you’re fetching is dependent on the state of the application (assuming that the state is maintained through your URL and parameters), then you would be refetching data even if the application’s state hasn’t changed since the user was last on the page. A better solution would be to refetch data only when the state of the application (parameter) has changed:

// Inside a router
initialize: function() {

this.cached = {
view: undefined,
model: undefined
}
},

index: function(parameter) {
this.cached.model = this.cached.model || new Model({
parameter:parameter
});
this.cached.model.set('parameter', parameter);
this.cached.view = this.cached.view || new View({
model: this.cached.model
});
}

// Inside of the model
initialize: function() {
this.on("change:parameter", this.fetchData, this);
}

JSDoc Functions And Backbone.js Classes

I’m a huge fan of documentation and JSDoc. I JSDoc all Backbone classes and functions in the following format:

var Thing = Backbone.View.extend(/** @lends Thing.prototype */{
/** @class Thing
* @author Phillip Whisenhunt
* @augments Backbone.View
* @contructs Thing object */
initialize() {},

/** Gets data by ID from the thing. If the thing doesn't have data based on the ID, an empty string is returned.
* @param {String} id The id of get data for.
* @return {String} The data. */
getDataById: function(id) {}
});

If you document your Backbone classes in the format above, then you can generate beautiful documentation that contains all of your classes and functions with parameters, return types and descriptions. Be sure to keep the initialize function as the first function declared, which helps when generating JSDoc. If you’d like to see an example of a project that uses JSDoc, check out the HomeAway Calendar Widget. There is also a Grunt.js plugin, grunt-jsdoc-plugin, to generate documentation as part of your build process.

Practice Test-Driven Development

In my opinion, if you’re writing in Backbone.js, you should be following test-driven development (TDD) for your models and collections. I follow TDD by first writing failing Jasmine.js unit tests against my models or collections. Once the unit tests are written and failing, I flush out the model or collection.

By this point, all of my Jasmine tests will have been passing, and I have confidence that my model functions all work as expected. Since I’ve been following TDD, my view layer has been super-easy to write and also extremely thin. When you begin practicing TDD, you will definitely slow down; but once you wrap your head around it, your productivity and the quality of your code will dramatically increase.

I hope these tips and patterns have helped! If you have suggestions for other patterns or you’ve found a typo or you think that one of these patterns isn’t the best approach, please leave a comment below or tweet me.

Thanks to Patrick Lewis, Addy Osmani, Derick Bailey and Ian Storm Taylor for reviewing this article.

(al) (ea)

原文链接：http://coding.smashingmagazine.com/2013/08/09/backbone-js-tips-patterns/ （本内容由互联网资料整理而成）

为什么务必在构建函数里定义实例属性？

TL,DR: 务必总是在构建函数里定义实例属性。

在小胖的PoorPhy物理库里，有很多类似这样的代码：

function WorldA () {
// ...
}

WorldA.prototype = {
gravityX: 0,
gravityY: 0,
// ...
}

相比之下，另一种写法是在构建函数里定义属性：

function WorldB () {
this.gravityX = 0
this.gravityY = 0
}

不难看出，gravityX和gravityY都应该是实例属性，也就是说每个World对象的这两个属性应该是会不一样的。把这些属性放在原型里，创建实例对象以后，实例对象本身的这两个属性其实是空的，如果在实例上不定义gravityX，则我们获得的其实是原型里面的gravityX：

var world = new WorldA()
world.gravityX // 0
world.hasOwnProperty('gravityX') // false

可能大家会觉得第一种写法没什么不好，反正实例上没定义的话还是能获得默认值啊，还省去了构建函数里的定义操作，没定义的属性还省内存。可是其实这样对性能是会有影响的。这里涉及到JavaScript引擎的内部优化。以V8和如下代码为例：

function reverseGravity (world) {
world.gravityX = -world.gravityX
world.gravityY = -world.gravityY
}

大家可能听说过hidden class的说法。在V8中，只有当两个对象本身的所有属性按顺序从名字到类型都完全相同时，他们才拥有相同的hidden class。hidden class类似于C的struct，因为明确知道内存的分布，所以V8生成的机器码在操作对象时的指令数就可以很少，效率也会很高。

在reverseGravity这个函数里，如果每次获得的world对象都在自身上具有gravityX和gravityY，那么V8在编译这个函数的时候，就会预设每次遇到的都是同一个hidden class，因此这个函数对应的机器码效率就会很高。但是！如果world对象有时候gravityX是在自己身上的，有时候是在原型上的（自身没定义），V8就需要进行一次额外的hidden class check，效率就会低一些。

到这里，性能上的损失其实还可以接受。但是！假如world时有时无的属性超过了4个（也就是可能会有4个以上的不同的hidden class），V8就会改用hash table而不是hidden class来检索对象上的属性了！（为啥是4个？源码是这么写的 -_-）hash table查找的机器码指令数比起hidden class来说，多了不是一点点。例子说话，差距有多少，看这个jsPerf，性能差距高达80%。

以上所描述的问题，不仅仅在V8中存在。现代JS引擎所使用的优化技巧都很类似，在其他JS引擎中，两种情况也有很大的性能差异，只是没有在V8下那么极端而已。类似地，为了防止hidden class的改变，慎用delete。

附：对比V8在两种情况下产生的机器码

原文链接：https://gist.github.com/yyx990803/6246816 （本内容由互联网资料整理而成）

非等宽图片列表的布局

非等宽图片列表的布局

各大搜索引擎的图片频道的搜索结果页，搜索出来的结果都是较零碎的图片，图片质量、尺寸都是参差不齐的，并限定了每一行的总宽度。这种非等宽的图片列表，在Google+、flickr也都有用到。

最近刚好对360搜索的图片搜索结果页进行了一次重构和改版，对于这种图片布局也花心思研究了一番，接下来说说我的一些处理思路。

非等宽的单个图片要排列到一个固定了宽度的容器中，那么这个等宽的容器就是最大的限制和障碍，开始怀念那种常见等宽瀑布流的布局(没有限制真好)。

先理下基本的需求：

- 1、图片的宽度是不固定的；

- 2、图片的高度是固定的；

- 3、每行图片的总宽度是固定的，所以每行能容纳的图片的数量是无法确定的；

- 4、确保图片质量不变，不能有拉伸变形导致图片失真的情况出现；

- 5、图片之间的间距是固定的；

任意选取一组图片数据，计算图片宽度的总和，当宽度刚好大于或者等于一行的固定宽度，那么这就是一组待处理尺寸的原图，处理前的情况应该是这样的。

以一行图片为单位，用一个 ul 列表来存放这一行图片，将 li 浮动。一组原图的宽度的总和通常都是大于一行的宽度，刚好相等的情况很少见，如果宽度过大，使用浮动的布局必然会让最后一张图掉下去。基本的 HTML 结构是下面这样的。

<style type="text/css">
.img_list{ /*该行的固定宽度*/ overflow:hidden }
.img_list li{ float:left; }
</style>

<ul class="img_list">
<li><a href="#"><img src="demo.jpg" alt="" /></a></li>
<li><a href="#"><img src="demo.jpg" alt="" /></a></li>
<li><a href="#"><img src="demo.jpg" alt="" /></a></li>
<li><a href="#"><img src="demo.jpg" alt="" /></a></li>
<li><a href="#"><img src="demo.jpg" alt="" /></a></li>
</ul>

为了尽量简化方法，这里先无视间距。

裁切宽度

那么每一组图片只要将多出来那部分的宽度(extraWidth)想办法解决掉就行了。为了保证图片不失真，可以使用裁切的方式，li 设置一个固定的宽度，并且设置 overflow:hidden，图片本身还是设置原图的尺寸，同时给图片设置负 marginLeft，确保图片是居中的。将多出来的那部分宽度通过对每张图片进行相同宽度的裁切，这样可以确保每张图片裁切得比较均匀。

裁切的简单公式： 图片裁切的宽度(marginLeft) = 多出的宽度(extraWidth) / 图片的数量(imgLength) / 2

下面是对单个图片的裁切。

<li style="width:100px;height:100px;overflow:hidden;">
<a href="#">
<img style="width:104px;height:100px;margin-left:-2px" src="demo.jpg" alt="" />
</a>
</li>

裁切图片虽然能确保图片不失真，并保证每一行图片的行高是一样的，但会损失掉图片一部分左右边缘的内容，裁切掉的宽度的大小依据一行图片的数量多少来变化的，对于一行图片数较多的情况下(竖图较多)，这种方法还是不错的。

除了裁切还有其他的办法吗？

等比缩放

裁切是为了减小图片所占的宽度，并确保图片不失真，缩放同样也可以实现前面说到的两点，缩放的处理要比裁切会麻烦些。

将前面计算出的extraWidth，通过对图片进行等比例缩放的方式，将缩放的宽度平均分配到每张图片上，这是对图片宽度的处理。

缩放宽度的简单公式：图片缩放后的宽度(zoomWidth) = 图片原始宽度(imgWidth) – 多出的宽度(extraWidth) / 图片的数量(imgLength)

既然是等比例缩放，对图片的宽度进行了缩放后，图片的高度也要按照相等的比例进行缩放。可以用第一张图片的高度作为基准高度。

基准高度的简单公式：基准高度(baseHeight) = 图片原始高度(imgHeight) – 多出的宽度(extraWidth) / 图片的数量(imgLength)

根据图片原始的宽度和缩放后的宽度的计算得出的缩放比例，就可以计算出每张图片应该等比例缩放的高度。这个缩放的高度和之前得到的基准高度会有一定的出入，如果缩放后的高度大于基准高度可以通过负 marginTop 的方法对图片的高度进行裁切，如果缩放后的高度小于基准的高度，可以再对图片进行等比例放大，然后再对宽度进行一定的裁切。通过这些处理，就可以保证每一行是等高的。

等比缩放对图片的裁切会很小，基本不会损失掉原图边缘的内容，但是会让每一行的行高都不一样，无法固定行高。Google+ 和 flickr 就是采用的等比缩放的方法，这种方法对于大尺寸以及横图居多的情况很合适。

不管是裁切还是缩放，一行图片在计算后都会有一定的误差值，需要对误差值进一步再做处理，这个不难，就不多说了。

如果你也有好的处理办法，欢迎与我讨论。

原文链接：http://stylechen.com/not-fixed-width-imglist-layout.html （本内容由互联网资料整理而成）

JavaScript 中的陷阱

由于 JavaScript “弱语言”的性质，使得其在使用过程中异常的宽松灵活，但也极为容易“掉进陷阱”。这些陷进往往隐藏的很深，所以要多加小心。

全局变量

JavaScript 通过函数管理作用域。在函数内部声明的变量只在这个函数内部，函数外面不可用。另一方面，全局变量就是在任何函数外面声明的或是未声明直接简单使用的。

“未声明直接简单使用”，指的是不用 var 关键字来声明变量。这个我们已经非常清楚，避免造成隐式产生全局变量的方法就是声明变量尽量用 var 关键字。

可你以为用了 var 就 ok 了？来看看这个陷进：

functionfoo(){

vara=b=0;

// body...

也许你期望得到的是两个局部变量，但 b 却是货真价实的全局变量。why? Because 赋值运算是自右往左的 ，所以这相当于：

functionfoo(){

vara=(b=0);

// body...

所以 b 是全局变量。

解决：变量声明，最好一个个来，别搞批发~_~;

变量声明

先来看陷阱：

myName="global";

functionfoo(){

alert(myName);

varmyName="local";

alert(myName);

foo();

乍看上去，我们预计期望两次 alert 的结果分别为 “global” 与 “local”，但真实的结果是 “undefined” 与 “local”。why? Because 变量在同一作用域（同一函数）中，声明都是被提至作用域顶部先进行解析的。

所以以上代码片段的执行行为可能就像这样：

functionfoo(){

varmyName;

alert(myName);// "undefined"

myName="local";

alert(myName);// "local"

用另一个陷阱来测试下你是否真的理解了预解析：

if(!("a"inwindow)){

vara=1;

alert(a);

a 变量的声明被提前到了代码顶端，此时还未赋值。接下来进入 if 语句，判断条件中 "a" in window 已成立（a 已被声明为全局变量），所以判断语句计算结果为 false，直接就跳出 if 语句了，所以 a 的值为 undefined。

vara;// "undefined"

console.log("a"inwindow);// true

if(!("a"inwindow)){

vara=1;// 不执行

alert(a);// "undefined"

解决：变量声明，最好手动置于作用域顶部，对于无法当下赋值的变量，可采取先声明后赋值的手法。

函数声明

函数声明也是被提前至作用域顶部，先于任何表达式和语句被解析和求值的

alert(typeoffoo);// "function"

functionfoo(){

// body...

可以对比一下：

alert(typeoffoo);// "undefined"

varfoo=function(){

// body...

明白了这个道理的你，是否还会掉入以下的陷阱呢？

functiontest(){

alert("1");

test();

functiontest(){

alert("2");

test();

运行以上代码片段，看到的两次弹窗显示的都是 “2”，为什么不是分别为 “1” 和 “2” 呢？很简单，test 的声明先于 test() 被解析，由于后者覆盖前者，所以两次执行的结果都是 “2”。

解决：多数情况下，我用函数表达式来代替函数声明，特别是在一些语句块中。

函数表达式

先看命名函数表达式，理所当然，就是它得有名字，例如：

varbar=functionfoo(){

// body...

要注意的是：函数名只对其函数内部可见。如以下陷阱：

varbar=functionfoo(){

foo();// 正常运行

foo();// 出错：ReferenceError

解决：尽量少用命名函数表达式（除了一些递归以及 debug 的用途），切勿将函数名使用于外部。

函数的自执行

对于函数表达式，可以通过后面加上 () 自执行，而且可在括号中传递参数，而函数声明不可以。陷阱：

// (1) 这只是一个分组操作符，不是函数调用！

// 所以这里函数未被执行，依旧是个声明

functionfoo(x){

alert(x);

}(1);

以下代码片段分别执行都弹窗显示 “1”，因为在 (1) 之前，都为函数表达式，所以这里的 ()非分组操作符，而为运算符，表示调用执行。

// 标准的匿名函数表达式

varbar=functionfoo(x){

alert(x);

}(1);

// 前面的 () 将 function 声明转化为了表达式

(functionfoo(x){

alert(x);

})(1);

// 整个 () 内为表达式

(functionfoo(x){

alert(x);

}(1));

// new 表达式

newfunctionfoo(x){

alert(x);

}(1);

// &&, ||, !, +, -, ~ 等操作符（还有逗号），在函数表达式和函数声明上消除歧义

// 所以一旦解析器知道其中一个已经是表达式了，其它的也都默认为表达式了

true&&functionfoo(x){

alert(x);

}(1);

解决：这个陷阱的关键在于，弄清楚形形色色函数表达式的实质。

循环中的闭包

以下演示的是一个常见的陷阱：

varlinks=document.getElementsByTagName("ul")[0].getElementsByTagName("a");

for(vari=0,l=links.length;i<l;i++){

links[i].onclick=function(e){

e.preventDefault();

alert("You click link #"+i);

我们预期当点击第 i 个链接时，得到此序列索引 i 的值，可实际无论点击哪个链接，得到的都是 i 在循环后的最终结果：”5”。

解释一下原因：当 alert 被调用时，for 循环内的匿名函数表达式，保持了对外部变量 i的引用（闭包），此时循环已结束，i 的值被修改为 “5”。

解决：为了得到想要的结果，需要在每次循环中创建变量 i 的拷贝。以下演示正确的做法：

varlinks=document.getElementsByTagName("ul")[0].getElementsByTagName("a");

for(vari=0,l=links.length;i<l;i++){

links[i].onclick=(function(index){

returnfunction(e){

e.preventDefault();

alert("You click link #"+index);

})(i);

可以看到，(function () { ... })() 的形式，就是上文提到的 函数的自执行 ，i 作为参数传给了 index，alert 再次执行时，它就拥有了对 index 的引用，此时这个值是不会被循环改变的。当然，明白了其原理后，你也可以这样写：

for(vari=0,l=links.length;i<l;i++){

(function(index){

links[i].onclick=function(e){

e.preventDefault();

alert("You click link #"+index);

})(i);

欢迎留言拍砖，补充！

声明: 本文采用 BY-NC-SA 协议进行授权 | WEB前端开发
转载请注明转自《JavaScript 中的陷阱》

原文链接：http://www.css88.com/archives/5347 （本内容由互联网资料整理而成）

用 Sass 实现酷炫动态文字效果

Published August 12, 2013 by Guest Author

The following post is by Hugo Giraudel, a regular contributor here. In the grand CSS-Tricks tradition, Hugo found a cool effect on the web and dug into how he could re-create it in a smart way.

A couple of days ago, I saw this pen by Hugo Darby-Brown which intented to reproduce the MetaFizzy effect by David DeSandro in pure CSS with the help of Sass. Hugo did a great job and probably the most important one: digging into the original JavaScript to understand how to do it in Sass.

He succeeded, but I thought his code could be improved. Even if it was clearly far better than a vanilla CSS version, there was still a lot of repeated code. Let's find a way to make it like super-DRY!

Important! This is really just an experiment. If you ever want to do something like this, please just use JavaScript. The CSS version takes like 500 lines long and turns out to be quite heavy for the GPU. So once again, this is a Sass experiment. Just for fun.

Where do we start?

This is a question I would not be able to answer if Hugo didn't do his demo first. Here's what we need to do:

- Give our text a long shadow, progressively fading to black

- Make the color of the shadow slowly change over time

- On hover, go crazy rainbow

- Animate this rainbow quickly

So in the end, we will need a couple of things:

- An animation for the smooth shadow

- An animation for the hover state

- A list of colors for the hover state

That's pretty much it.

The smooth shadow

The keyframes

The smooth shadow is the easier of the things we'll do. All we do is output a bunch of text-shadows of one color progressively fading them to black. Then, we need an animation which will change this color over time. A perfect job for hsl()!

Because we want our Sass code as DRY as possible, we'll call a mixin in our keyframes that will handle the crapload of text-shadows for us. First, the skeleton.

@mixin text-3d($hue) {
/* Output crazy text-shadows */
}

@keyframes text-3d-animation {
@for $i from 0 through 10 {
#{$i * 10%} {
@include text-3d($i * 36);
}
}
}

All we did was create a text-3d-animation CSS animation with 11 explicit keyframes (0%, 10%, ... 90%, 100%). In each keyframe, we call a mixin named text-3d, passing $i * 36 as $hue argument (36, 72, 108, 144, 156, ...). If you are familiar with hsl() notation, you can see where this is going.

The mixin

Now we've created the animation calling the mixin, it's time to build the mixin! All it has to do is outputing a bunch of text-shadows. I went with 50 which is pretty huge already but you can pick the number you want I guess (although you have to hardcode this, I didn't define a parameter for this). Once again, we won't write our shadows manually; Sass lists and loops are meant for this.

@mixin text-3d($hue) {
$ts: ();
@for $i from 1 through 50 {
$ts: $ts, $i*2px $i*2px hsl($hue + $i*1, 100%, 50% - $i);
}
text-shadow: $ts, 0 0 50px, 0 0 55px;
}

Don't panic yet! This is actually simple. Before entering our loop, we define an empty list called $ts (stands for text-shadow). Then we enter the loop. In each run, we append a new shadow to our list where:

- both horizontal and vertical offsets are set to $i * 2px to make the shadows bigger and bigger

- we don't define any blur, but you can set one if you like

- the color is defined in HSL with hue set to the given parameter (multiple of 36) + $i * 1, the saturation to 100% and the lightness to 50% - $i, meaning it progressively goes to black

Then once the loop is finally over, we simply output our $ts list as a value for text-shadow. We also add two shadows manually for the cool white "aura".

And we're done for the non-hover MetaFizzy effect! It should work like a charm.

The crazy rainbow

For the most part, the hover animation works the same as the non-hover animation. We will proceed the same way we did before, starting with the keyframes.

The keyframes

@keyframes crazy-rainbow-animation {
@for $i from 1 through 50 {
#{$i * 2%} {
@include crazy-rainbow($i, tomato yellow green blue purple);
}
}
}

As you can see, this is pretty much the same thing we used for the 3D text animation except we won't use 11 explicit keyframes here but 50. Actually 51 if we want to prevent a little glitch; let's add the 0% keyframe (out of the loop of course).

@keyframes crazy-rainbow-animation {
0% {
@include crazy-rainbow(50, tomato yellow green blue purple);
}
@for $i from 1 through 50 {
#{$i * 2%} {
@include crazy-rainbow($i, tomato yellow green blue purple);
}
}
}

We pass our crazy-rainbow mixin two parameters:

1. $i as a numeric value once again (we'll see the point later)

2. the list of colors we want to see moving when hovering the text (that's right, we can customize the colors!)

The mechanics

This is where things get complicated. The animation when hovered basically looks like a striped shadows (which makes no sense anymore): one color, then another one, then an other color, and so on... But there's more, the color are moving.

The idea is to have something like this:

@keyframes crazy-rainbow-animation {
0% {
text-shadow: 2px 2px   color1, 4px 4px   color1, 6px 6px   color1, 8px 8px   color1,
10px 10px color2, 12px 12px color2, 14px 14px color2, 16px 16px color2,
18px 18px color3, 20px 20px color3, 22px 22px color3, 24px 24px color3;
}

2% {
text-shadow: 2px 2px   color3, 4px 4px   color1, 6px 6px   color1, 8px 8px   color1,
10px 10px color1, 12px 12px color2, 14px 14px color2, 16px 16px color2,
18px 18px color2, 20px 20px color3, 22px 22px color3, 24px 24px color3;
}

/* And so on... */
}

At every new keyframe, colors (not offsets) have to be moved 1 index in the list. The last color of the list comes at first, and every color is pushed of one slot to the right. So in the end, we have the same number of shadows with the same offsets except their color change.

Building the array of colors

Because of this, we need a list of colors as long as the number of shadows we want to output. If we want to use 50 shadows, we need a list of 50 colors. Creating this list manually would be a pain in the butt, so we create a function for this.

The goal of this function is to turn a list of colors into a list of colors. But the returned list should match the length we want so we can turn a list of 5 colors into a list of 50. Like this:

$given-colors: tomato yellow green blue purple;
$returned-colors: create-list($colors);
/*
$returned-colors: tomato, tomato, tomato, tomato, tomato, tomato, tomato, tomato, tomato, tomato,
yellow, yellow, yellow, yellow, yellow, yellow, yellow, yellow, yellow, yellow,
green, green, green, green, green, green, green, green, green, green,
blue, blue, blue, blue, blue, blue, blue, blue, blue, blue,
purple, purple, purple, purple, purple, purple, purple, purple, purple, purple;
*/

Unfortunately, I realized 50 shadows is not always a good number for this animation. It occurred to me depending on the number of colors you want to run, the animation may not be quite well completed; sometimes the colors just "jump". This is because we need the shadows from the last keyframe to match the shadows from the first.

To sum up, we need to find a number which is:

- lesser than or equals to 50 (number of keyframes)

- a multiple of the length of the color list (to make the animation loop without any jump)

- the closest to 50 as possible (to make the animation as fluid as possible)

So let's say we have a list of 6 elements, the function should return 8 (because 9 would go over 50 since 9 * 6 = 54). A list of 7 elements should return 7 (because 7 * 7 = 49). You get the idea.

@function define-max($n) {
@for $i from 1 through 50 {
@if $i * $n > 50 {
@return $i - 1;
}
}
}

Now back to our create-list() function. We have a list of a couple of colors and want to turn it into a list of about 50 colors? Okay, great.

@function create-list($colors) {
$max: define-max( length($colors) );
$l: ();
@each $c in $colors {
@for $i from 1 through $max {
$l: append($l, $c);
}
}
@return $l;
}

The mixin

Okaaaaaay! So all we did until now was create a function to turn a list of colors into a longer list of colors. Let's dig into the mixin.

@mixin crazy-rainbow($n, $colors) {
$colors: create-list($colors);
$ts: ();

@for $i from 1 through length($colors) {
$n: if($n > length($colors) or $n == 0, 1, $n);

$ts: $ts, $i*2px $i*2px 0 nth($colors, $n);

$n: $n + 1;
}

text-shadow: $ts;
}

Same as earlier, we define a $ts empty list to store all our shadows. Then we enter the loop to add a shadow to our $ts list every time before moving the pointer of 1 index to the right ($n: $n + 1). If the index goes out of list range, we move it back to 1.

It works! Let's sum up what we did:

1. We computed the number of shadows (X) we needed to output based on the number of colors we wanted to run. This is only to make the animation loop correctly without any visual glitch.

2. We generated a big array of X colors based on the number we previously computed and on the list of colors we wanted to run.

3. At every keyframe we output X shadows starting from a different index in the array every time. This is what makes the colors move.

Improvings bits

Now that we're done with all the mechanics, we can improve things a little bit. Why not make a metafizzy mixin assigning a couple of styles to our element?

@mixin metafizzy($size, $duration: 10s) {
font-family: 'MetafizzyLogoRegular', cursive;
color: white;
line-height: .9em;
font-weight: normal;
font-size: $size;
animation: text-3d-animation $duration linear infinite;

&:hover {
animation: crazy-rainbow-animation 1s linear infinite;
animation-direction: reverse;
}
}

This mixin defines all the typography stuff, including the font family (you'll need font files though), font size, font weight, line height and so on.

Let's keep going. What about a mixin to generate our two keyframes animations? We could pass it the color list we want to use on hover.

@mixin metafizzy-animations($hover-colors) {
@keyframes text-3d {
@for $i from 0 through 10 {
#{$i*10%} {
@include text-3d($i * 36);
}
}
}

@keyframes crazy-rainbow {
@for $i from 1 through 50 {
0% {
@include crazy-rainbow(50, $hover-colors);
}
#{$i*2%} {
@include crazy-rainbow($i, $hover-colors);
}
}
}
}

Unfortunately, we can't include this mixin in the metafizzy one since the latter is included inside a selector (like h1). Actually we can do it, but this won't work; the @keyframes animations will be outputed inside the selector and not at root of document.

In Sass 3.3, we will have the @at-root directive that will make this kind of thing possible (@directive bubbling), but for now this isn't possible so we have to include it at root.

Usage & demo

@include metafizzy-animations(red orangered yellow lightgreen green deepskyblue);

h1 {
@include metafizzy(25em, 5s);
/* Other styles that please you */
}

Done. That's all I got folks, hope you liked it and thanks for reading!

See the Pen SCSS version of Meta Fizzy by Hugo Giraudel (@HugoGiraudel) on CodePen

原文链接：http://css-tricks.com/metafizzy-effect-with-sass/ （本内容由互联网资料整理而成）

CSS 布局的未来

本文由LL根据Peter Gasston的《The future of CSS layouts》站点所译，整个译文带有我们自己的理解与思想，如果译得不好或不对之处还请同行朋友指点。如需转载此译文，需注明英文出处：http://www.netmagazine.com/features/future-css-layouts，以及作者相关信息

——作者：Peter Gasston

——译者：LL

大家都知道，css有着奇妙的特性，然而让人失望的是，css对基本页面布局上的支持有所欠缺。但是这种情况正在得到改变，更多的动态页面正是由css所支持的，正如peter gasston所说的。

这是一篇关于css布局且具有前瞻性的 文章。

在这里，我能够保证在数年之后，css3必定能够引领潮流。一揽子的前端工具正在应运而生，方便为我们带来rounded corners圆角，gradients渐变，opacity透明度，transformations，transitions，animations，甚至更多效果。但是现在我们都有了fun stuff，eye candy，还有需要什么呢？

下一个我们需要面对的就是关于定位css3的布局问题，虽然现在我们在可以使用float，relative定位，负值margin进行定位，但是我们仍然很难在多栏目的布局上大展身手。

W3C组织以及浏览器制作商意识到了这个问题，也推出了一揽子解决方案。其中的主力军是IE团队。IE10就好比步入css布局新时代的先驱者，它让我们的网站变得更加丰富多彩，灵活多变，吸引大家的眼球。

在这篇文章当中，我将为大家从优秀实用到纯粹理论阶段中介绍有关css布局的工具，也许在这些阶段中你不一定用得到他们，（至少不是现在），但仍然还很有必要去了解，通过这样可以透过迷雾，看清未来。如果你需要在这个文章当中对这些工具需要更多细节的介绍，或者更多介绍css3的内容，可以向大家推荐 这本书。

多列布局（Columns）

在多栏目中分配好内容是排版的重中之重，CSS Multi-Columns module同样能够为我们在网页上实现自适应的效果。下面有两种方法实现栏目的布局，值得一提的是这两种方法都采用了不同的属性（而且他们都需要有个干爹罩着-父级容器）。第一个方法就是直接指定，也就是说你直接指明需要在文本当中设置的栏目列数就行了。下面的代码能够产生三栏的文本，其宽度大小填满了父级的宽度。

div { column-count: 3; }

第二个方法比较灵活，首先栏目的宽度是固定的，而且其栏目的数目会一直增加直到填满父级宽度为止，举个例子吧，我设置栏目的宽度为140px，如果其父级宽度为800px的时候，那么这将会产生的栏目数为5个。

div { column-width: 140px; }

在默认的情况下，每个栏目之间的间隔长度是1em，但是你可以通过column-gap这个属性来进行改动。同样，你也可以在column-rule对column进行样式上的修改，我们可以回想到border这个属性也有着同样的写法规则。下面这个代码语句生成一个宽度为2px的虚线，而且column的间隔为28px-为大家展示了另一种的规则。

div {
column-gap: 28px;
column-rule: 2px dotted #ccc;
}

如果你想看看效果如何，狠狠点击 这里吧。对了，你需要在firefox，chrome，safari，opera11.1或者ie10的平台下才能得到正常的显示；如果你没有上面所说的浏览器，那你就看看下面的效果图片吧。

在一个水平空间上分割块状内容的时候css的columns属性为你提供了非常便捷的方法。

其实围绕着column你还可以做更多事情，但是文章篇幅有限，不能为你一一说明。如果你希望看到一个实际的例子，引用维基百科的一些例子这里可以满足你，在这里使用了column-count属性把文章分成了三栏，在火狐上multi-column的使用需要添加-moz-的前缀，在Chrome和Safari上则需要使用-webkit-前缀， opera11.1+以及ie10就不用添加前缀。

扩展阅读

——大漠

伸缩性盒模型（Flexible box）

Flexible Box Layout module（flexbox）为大家提供了一个在父级容器中不用考虑具体height和width数值的自适应方法。举个简单的例子来说吧，想象你有两个子级元素，你希望他们都能够以相同的宽度一起来填满他们的父级宽度。你也许能够使用百分比来实现，但是一旦要把border，padding，margins考虑进来的时候，情况就复杂多了。Flexbox的出现便让这个问题不再成为问题：

.parent { display: flex; }
.child-one, .child-two { flex: 1; }

简单地放置两个子级元素水平排列在父级容器当中，同时使其宽度一致。flex的数值实际上就是比例的意思，在这些子级元素当中按比例地重新分配空出来的空间在他们的宽度；好吧，多说没用，上代码！

.child-one { flex: 1; }
.child-two { flex: 2; }

当这两个子级元素的宽度都在父级容器中都得到分配的时候，.child-two的宽度将会以剩余空间的三分之二得到补充，.child-one的宽度将会以剩余空间的三分之一得到补充；所以，如果他们的父级为260px宽，每个栏目首先被赋予了100px宽度，多出来的60px将会有40px的宽度分配到child-one上，而20px的宽度将被分配到child-two上。

一例胜百言，点击在 这里,你会把这个知识点看的更加清楚（你需要firefox，chrome，safari，或者ie10+）.你还可以尝试改变浏览器窗口的大小来看看有什么变化。

正如能够动态改变一个容器的尺寸大小，FlexBox对父级容器提供属性支持控制多余的空间分配，也可以对该子级元素进行定位。justify-content属性作用在子级元素的宽度（这是默认情况下的，可以改变）.然后justify-content还有个兄弟属性，align-items，他作用在高度上，下面为你展示下，他们是如何实现效果的：

.parent {
justify-content: center;
align-items: center;
display: flex;
height: 200px;
width: 100px;
}
.child {
flex: 0;
height: 100px;
width: 100px;
}

.child的flex数值为0所以它不会有什么动态的尺寸变化，他的宽度高度都是父级容器的一半，因此，该子级元素将被定位在水平，垂直方向上的中心位置。

在.net杂志中一篇叫The CSS3 Flexible Box model explained的文章当中，你能了解更多关于Flexbox的东西。加上相关的前缀（-moz-.webkit-,-ms-）你能够在相应的firefox，chrome，safari，以及ie10pp各大浏览器平台中使用flexbox属性，在JS Polyfill, Flexie这个网站当中，你还能进行关于flexbox的尝试。我们要注意的是代码也是灵活多变的；点击这里你还能了解更多版本规范。

译文中的代码已做修改，有关于更多的信息查阅原文，或者阅读下面的扩展阅读。

扩展阅读

——大漠

网格(Grid)

Brand new to IE10PP）最值得称道的地方就是Grid Layout系统，这也是我最感到兴奋的地方。这是第一次使用columns和rows来定义我们的网格。其中我们可以使用宽度数值，关键词auto，以及一个新的长度单位，fr（fracion的缩写）。多说没用，翠花，上代码！

div {
display: grid;
grid-columns: 1fr 3fr 1fr;
grid-rows: 100px auto 12em;
}

上面的代码能够为我们带来一个三列的网格，其中最中间的栏目的宽度是左右两边栏目的三倍，第一行的高度为100px，最后一行的高度是12em，中间的行高自动调整，以刚好填满整个栏目的大小。

网格确定了之后，我们就能在里面定位其中的内容了，利用一些section 标签我创建了一个简单的页面布局，下面就是代码：

header {
grid-column: 1;
grid-column-span: 3;
grid-row: 1;
}
nav {
grid-column: 1;
grid-row: 2;
}
article {
grid-column: 2;
grid-row: 2;
}
aside {
grid-column: 3;
grid-row: 2;
}
footer {
grid-column: 1;
grid-column-span: 3;
grid-row: 3;
}

看着这些代码，你肯定会发现这些都采用了columns和rows，也就是grid-column和grid-row属性；在article的元素标签中，我把他放在了网格中的第二行，第二列，这个位置也就是在3*3网格的中心地带。同样的，我在header和footer标签当中使用了grid-column-span，这个代码让他们有了横跨三列栏目的勇气和能力，（grid-column-span也有着类似的功能，只是在这里就不引用了哈。）

我做过一个关于这些属性的布局例子，点 这里看看吧，但是这需要你用IE10PP这个平台来打开他。如果你没有这个浏览器，那你就看看下面的效果图吧。

这些属性都在IE10pp得到支持，因此你可以拿来自己试验试验，在技术文档中当中还有很多属性也是得到IE10PP支持的，所以说前景非常光明，敬请期待。

扩展阅读

——大漠

模板（Template）

另一个支持网格系统的就是Template Layout module。采用了不同的语法，第一步工作就是对position属性进行赋值，这些值就是按顺序排列下去的字母。上代码！

header { position: a; }
nav { position: b; }
article { position: c; }

我们搞定了这些posiiton工作之后，我们就可以通过这一连串的字母进行布局；每一串字母就代表着一行，每个字母就代表着一列；一例胜百言，弄一个一行三列的网格，你需要这么做，上代码！

div { display: 'abc'; }

这样，在一个水平行中你就可以显示出三个空间大小一样的元素了。同样，你可以重复字母，这样你就能够进行跨列显示了，如果你在不同字符串的相同位置使用相同的字母，那么你就能够进行跨行显示了，下面的例子就是说明了nav元素跨行显示，header，article跨列显示，上关键代码！

div {
display:
'baa'
'bcc';
}

Template Layout在目前为止没有被浏览器所支持，但是有个jQuery polyfill by Alexis Deveria能够让其得到支持。我就是依靠这个捣鼓出的这个例子,该效果跟Grid Layout的一模一样，然而其中的代码方法确实一点都不同。

这个例子运用了js所以他能够被绝大多数的现代浏览器所支持，但是你还是在浏览器看不到效果的话，那么你可以上拉回去看看介绍Grid Layout所贴出来的效果图参考参考，因为这效果图是一模一样的。同样，我记得我说过Grid Layout module博大精深？好吧，下面关于Template的语句中有包含其中的代码，不多说，上代码!

header { grid-cell: a; }
article { grid-cell: b; }
div {
display: grid;
grid-template: 'a' 'b';
}

上面的代码实现了跟Template layout属性 一样的效果，而且同样也有着不被广泛支持的命运，但我们都知道，明天肯定不会一成不变。

扩展阅读

——大漠

浮动定位

当前的float属性让我们的文本相对于一个元素向左或者向右浮动显示，但是在 IE10PP内的扩展属性,带着我们走得更远，我们可以定位浮动元素在我们想要的地方，而且其兄弟元素依然能够围绕在他们身旁。这需要float中的一个新的值，以及一些定位属性，上代码：

div {
float: positioned;
left: 200px;
position: absolute;
top: 100px;
width: 250px;
}

这段代码将会产生一个250px宽度的元素，而且这个元素的位置为距离父级容器最左边的200px处，以及最顶端的100px处。在默认情况下，其兄弟元素将会浮动在其的周围，但是你可以利用wrap-type属性进行改动，就好像下面的代码控制了文本只能在元素的上方和下方显示：

div { wrap-type: top-bottom; }

同样地，你可以把Position Float和Grid Layout module结合起来，在网格中确定元素的位置之后，安排其他元素在它周围显示，上代码：

div {
float: positioned;
grid-column: 2;
grid-row: 2;
}

Position Float是被IE10PP所支持的，所以你可以马上运用上他了，下面还有个demo，不过需要在IE10的平台下正常查看，如果你没有IE10平台，那么你就看看下面的效果图聊以自慰吧。

Floats and Positioning规范所能够做的确实比我在这里展示的多，更多的属性，包括shaped floats，或者image masks的floats；虽然说，目前为止，没什么浏览器支持这些东西，但是在各大浏览器下一个版本到来的时候，你一定会看到关于这方面的更多支持。

Exclusions

Positioned Floats在标签box-shaped的帮助下能够使得文本得以流动，不过我要说的是，shaped float可以在技术文档中查阅。这个想法来源于Adobe公司的一个模块：CSS Exclusions。在这个模块中有两个关键的属性；第一个就是wrap-float，它可以使得你创建椭圆，矩形，多边形，并以这些形式来进行浮动，上代码！

div { wrap-shape: circle(50%, 50%, 100px); }

这个代码能够产生一个半径为100px的圆，并且在父级容器的中心显示出来。你同样能够利用polygn（）来创建任何你想要的形状，用空格来显示一个组的纵，横坐标，一个三角形的代码如下显示：

div { wrap-shape: polygon(0,100px 100px,100px 50px,0); }

当你得到了你的形状的时候，你能够通过使用第二个属性wrap-shape-mode来使得内敛元素围绕显示在原来的元素当中，当然了这些内联元素需要和你的定位元素有共同的父级容器。上代码！

div {
wrap-shape: circle(50%, 50%, 100px);
wrap-shape-mode: around;
}

你可以通过下载来查看这个css的拓展功能，这个网站上有完整的技术说明，以及一些让人印象深刻的实例，其中一个的效果如下图所示。

正如上面所提到的，Exclusions语法已经被Floats模块接纳，所以很有可能在以后会以一个拓展的内容来展示给我们，而不是一个独立的新内容。

扩展阅读

1. CSS Floats and Positioning Level 3

——大漠

Regions

来自Adobe的一个更加凶猛的支持：CSS Regions，在一个多文件中提供一个自适应内容。在第一次的声明就确定了，在float属性中设定一些独特的字符串作为数值，然后在content属性中利用from（）功能中指定自适应的区域，说得有点乱七八糟，上代码：

.content { flow: foo; }
.target1, .target2 { content: from(foo); }

看代码，就相当简单，类名叫.content的内容，首先是放在类名叫.target1的元素中，如果内容溢出了，那么继续放在.target2中。为了清楚显示，所以在两个target元素中不会出现重复的内容，他开始于target1，如果有需要的话在target2中得到补充。一图胜百言，上图！

随便提一下，这两个target不需要在html文档中相互紧挨在一起；如果有需要的话，他们有可能在一个页面的两端显示。

然而，Regions这个用法还没有被浏览器所普遍支持，面对这种情况，你可以从Adobe Labs中下载这个属性 ，自己来尝试一下。

扩展阅读

——大漠

结论

FlexBox和Columns这两个内容，我也不敢确定在新的布局模块中被浏览器完全支持。我还要说的是，Positioned Floats 和 Exclusions 这两个模块非常接近，很大概率被合并。在IE10中Grid Layout则把Template Layout包含起来了。Regions 已经被webkit所支持了，因此很快地已经出现在webkit浏览器中（safari，Chrome）。

因此，我感觉一些语法上即将会发生改变，在这里你所看到的效果，即将出现在将来的CSS3效果当中。如果这成为现实，我想这绝对是个好情况；这些新的方法将会是个对CSS3很好的补充和完善，在数年之后，我们即使创建一个复杂的网站都是非常简单轻松的了。

感谢Oli Studholme对这篇文章的专业评价

一篇类似的，关于css布局且具有前瞻性的　文章。

译者手语：整个翻译依照原文线路进行，并在翻译过程略加了个人对技术的理解。如果翻译有不对之处，还烦请同行朋友指点。谢谢！

如需转载烦请注明出处：

英文原文：http://www.netmagazine.com/features/future-css-layouts

中文译文：http://www.w3cplus.com/css3/future-css-layouts.html

原文链接：http://www.w3cplus.com/css3/future-css-layouts.html （本内容由互联网资料整理而成）

教你如何用 CSS 制作圆形导航

A tutorial on how to create a circular navigation using CSS transforms.

View demo Download source

In this tutorial I’m going to show you how to create circular navigations using CSS transforms. I’m going to take you through the steps for creating these styles one by one, and explain the math (yikes!) and simple logic behind them so you get a clear understanding of the technique.

Like I mentioned, there’s going to be some basic math involved, along with CSS transforms to create these styles. But don’t worry, the math is really very simple and I’ll be going through it step by step.

I also want to mention that credit for the original technique goes to Ana Tudor. I tweaked it to achieve the results that I wanted, which is also what I’m hoping you’ll be able to do by the end of this tutorial: have a deep and clear understanding of the technique so you can start fiddling around and creating your own styles.

So, without further ado, let’s get started!

The Markup

We’re going to be building a navigation, so we’ll start with a regular navigation structure. We’ll need a div to contain our unordered list of items, a button to trigger the opening and closing of the navigation, the list of items, and for the first demo we’ll need an extra overlay to cover the page when the navigation is open.

The icons we’re using in this demo are from Font Awesome.

The Math Behind the CSS Transforms

The best way to explain the math is to use a visual explanation instead of a written one. So I’ll start with the logic and we’ll apply the math as we go, and after we’re done with our explanation we’ll get to the coding part, at which point you’ll be able to know what each CSS rule exactly does.

First let’s go over what a “central angle” is. This is a visual representation followed by a simple explanation:

Suppose you want to distribute all navigation items on a semi-circle like the example we’re making here, and you have 6 items in your navigation list, then each angle would have a central angle of:

180deg / 6 = 30deg

If you want the items to take up a complete circle, and you have 6 items you want to have in this circle, the central angle for each item would be:

360deg / 6 = 60deg

And so on. So you calculate the value of the central angle you want, and from here on we’ll start applying some simple math to CSS transforms to actually create these angles.

To create an angle of value equal to our desired central angle value, we’ll have to skew the items (using the skew() CSS function) by:

90deg – x deg, where x is the value of the central angle we want.

Simple. But in this case, all the content inside the list items will also be skewed and the content will look distorted, which is not what we want, so we’ll “unskew” the anchors inside each item so that the content looks fine, but we’ll get to that in a moment.

I’ve set up a live and interactive demo that will show you how the transforms are applied to the navigation items step by step so you get a clearer understanding of what we’ll be doing in the code. (Note that the walkthrough in the demo may differ slightly in order of steps from the steps we’ll be taking in the tutorial)

You might want to play the step-by-step demo at this point to get a clearer understanding of what we’ll be doing next. I’ve also added a walkthrough to the demo which explains what’s happening in each step, so take a minute to play the demo and try to gain a better understanding of what we’ll do. You can play the demo from start to finish using the Start Demo Button, or control moving from step to step using the Next Step Button, and you can reset it anytime using the Reset Button.

View the interactive demo

Here are screenshots of the steps you’re going to see in the demo:

Initial state:

Step 1:

Step 2:

Step 3:

Step 4:

Step 5:

Step 6:

Step 7

So let’s go over this again:

- we’ll need to position the items absolutely inside their container.

- We’ll set the transform-origin for each item to be the bottom right corner.

- We’ll then translate the items up and to the left just enough so that their transform origin coincides with their container’s center.

- We’ll then skew them to get the central angle we want (using the formula we mentioned above), then rotate them clockwise into their positions using this formula:
For each item of index i we rotate it by: i * x , where x is again the value of the central angle

In our example, we have 5 items, which means 5 central angles, that we want to cover only the upper half of the circle, so according to the math we explained above, every item would have a central angle of 36deg, but in our example I’ve set the central angle value to 40deg (because it provides a bigger clickable area), so the sum of all angles will be 5 * 40 = 200deg which is greater than 180deg. In this case, all we do is just rotate the items “back” counter-clockwise by (200-180)/2 deg to ensure they are balanced on both sides.

At this point we have created our central angles and positioned them. But skewing the list items has also caused their content (anchor tags) to skew too, and thus caused their content to be distorted, so the last mathematical rule we’ll be applying here (phew!) is one that will make sure the anchor tags aren’t distorted and their content be visible. The rule is:

You unskew the anchor tags, which means you skew them by the opposite value of that you used to skew the list items, and then you unrotate the anchors by a value of:
- [90 – (x/2) ] , with x being the value of the central angle
so, for a central angle value of 40deg, we have to skew the anchors by -40deg and rotate them by:
-[ 90 – (40/2) ] = -70 deg

The anchors are positioned absolutely inside their parents, and the overflow is set to hidden on the list items, which means that part of the anchors is cut off, so in order to make sure that the text/icon content of the anchors lies within their visible part, we’ll set their text to be aligned at the center.

And that’s all the math you need to create the slices inside the navigation! Phew, finally, right?

So let’s go over this one more time, quickly:

1. Determine value x of central angle you want

2. Skew the list items by 90deg – x

3. Rotate the items into their positions

4. Rotate items in the opposite direction if/when needed to balance (this step is actually merged with the previous one, you subtract the value by which you want to rotate the angles back from the value of the angle you rotate them by)

5. “Unskew” and “unrotate” the anchors inside them (and set their text align to center”)

Of course I’ve skipped part where you move the list items so that their transform origin coincides with their container’s center (as shown in the demo). And that’s pretty much all you need to create the angles, but that’s not everything you need to create the whole navigation. A few simple steps remain, and they are pretty much just regular styling, so let’s start with the CSS and talk about these steps as we go!

The CSS

We’re first going to style the first demo.

We’re going to use Modernizr‘s classes applied to the body tag to target supporting and non-supporting browsers, and provide a very simple and basic fallback for older browsers that do not support CSS transforms.
Let’s start with the styles for the navigation wrapper. It will get a fixed position at the bottom center of the page, and will initially be scaled down, and when the open button is clicked it will open / scale up.

.csstransforms .cn-wrapper {
font-size:1em;
width: 26em;
height: 26em;
overflow: hidden;
position: fixed;
z-index: 10;
bottom: -13em;
left: 50%;
border-radius: 50%;
margin-left: -13em;
transform: scale(0.1);
transition: all .3s ease;
}
/* class applied to the container via JavaScript that will scale the navigation up */
.csstransforms .opened-nav {
border-radius: 50%;
transform: scale(1);
}

We’ll also style and position the button which will trigger the opening and closing of our navigation.

.cn-button {
border:none;
background:none;
color: white;
text-align: Center;
font-size: 1.5em;
padding-bottom: 1em;
height: 3.5em;
width: 3.5em;
background-color: #111;
position: fixed;
left: 50%;
margin-left: -1.75em;
bottom: -1.75em;
border-radius: 50%;
cursor: pointer;
z-index: 11
}
.cn-button:hover,
.cn-button:active,
.cn-button:focus{
background-color: #222;
}

When the navigation is opened, an overlay covers the page. Here are the styles for the overlay.

.cn-overlay{
width:100%
height:100%;
background-color: rgba(0,0,0,0.6);
position:fixed;
top:0;
left:0;
bottom:0;
right:0;
opacity:0;
transition: all .3s ease;
z-index:2;
pointer-events:none;
}

/* Class added to the overlay via JavaScript to show it when navigation is open */
.cn-overlay.on-overlay{
pointer-events:auto;
opacity:1;
}

Now we’ll style the navigation items and their anchors, applying the logic and math-based transformed we explained earlier.

.csstransforms .cn-wrapper li {
position: absolute;
font-size: 1.5em;
width: 10em;
height: 10em;
transform-origin: 100% 100%;
overflow: hidden;
left: 50%;
top: 50%;
margin-top: -1.3em;
margin-left: -10em;
transition: border .3s ease;
}

.csstransforms .cn-wrapper li a {
display: block;
font-size: 1.18em;
height: 14.5em;
width: 14.5em;
position: absolute;
bottom: -7.25em;
right: -7.25em;
border-radius: 50%;
text-decoration: none;
color: #fff;
padding-top: 1.8em;
text-align: center;
transform: skew(-50deg) rotate(-70deg) scale(1);
transition: opacity 0.3s, color 0.3s;
}

.csstransforms .cn-wrapper li a span {
font-size: 1.1em;
opacity: 0.7;
}
/* for a central angle x, the list items must be skewed by 90-x degrees
in our case x=40deg so skew angle is 50deg
items should be rotated by x, minus (sum of angles - 180)2s (for this demo) */

.csstransforms .cn-wrapper li:first-child {
transform: rotate(-10deg) skew(50deg);
}

.csstransforms .cn-wrapper li:nth-child(2) {
transform: rotate(30deg) skew(50deg);
}

.csstransforms .cn-wrapper li:nth-child(3) {
transform: rotate(70deg) skew(50deg)
}

.csstransforms .cn-wrapper li:nth-child(4) {
transform: rotate(110deg) skew(50deg);
}

.csstransforms .cn-wrapper li:nth-child(5) {
transform: rotate(150deg) skew(50deg);
}

.csstransforms .cn-wrapper li:nth-child(odd) a {
background-color: #a11313;
background-color: hsla(0, 88%, 63%, 1);
}

.csstransforms .cn-wrapper li:nth-child(even) a {
background-color: #a61414;
background-color: hsla(0, 88%, 65%, 1);
}

/* active style */
.csstransforms .cn-wrapper li.active a {
background-color: #b31515;
background-color: hsla(0, 88%, 70%, 1);
}

/* hover style */
.csstransforms .cn-wrapper li:not(.active) a:hover,
.csstransforms .cn-wrapper li:not(.active) a:active,
.csstransforms .cn-wrapper li:not(.active) a:focus {
background-color: #b31515;
background-color: hsla(0, 88%, 70%, 1);
}

We’ll provide a simple and basic fallback for browsers that do not support CSS transforms.

.no-csstransforms .cn-wrapper{
font-size:1em;
height:5em;
width:25.15em;
bottom:0;
margin-left: -12.5em;
overflow: hidden;
position: fixed;
z-index: 10;
left:50%;
border:1px solid #ddd;
}

.no-csstransforms .cn-button{
display:none;
}

.no-csstransforms .cn-wrapper li{
position:static;
float:left;
font-size:1em;
height:5em;
width:5em;
background-color: #eee;
text-align:center;
line-height:5em;
}

.no-csstransforms .cn-wrapper li a{
display:block;
width:100%;
height:100%;
text-decoration:none;
color:inherit;
font-size:1.3em;
border-right: 1px solid #ddd;
}

.no-csstransforms .cn-wrapper li a:last-child{
border:none;
}

.no-csstransforms .cn-wrapper li a:hover,
.no-csstransforms .cn-wrapper li a:active,
.no-csstransforms .cn-wrapper li a:focus{
background-color: white;
}

.no-csstransforms .cn-wrapper li.active a {
background-color: #6F325C;
color: #fff;
}

Of course we want our navigation to be responsive, so it will shrink to fit smaller screens. Here are the responsive styles for both the circular and the simple styles.

@media screen and (max-width:480px){
.csstransforms .cn-wrapper{
font-size:.68em;
}
.cn-button{
font-size:1em;
}
.csstransforms .cn-wrapper li {
font-size:1.52em;
}
}

@media screen and (max-width:320px){
.no-csstransforms .cn-wrapper{
width:15.15px;
margin-left: -7.5em;
}
.no-csstransforms .cn-wrapper li{
height:3em;
width:3em;
}
}

And that’s it for the first demo! Let’s move on to the next one.

The second circular style is different from the first one, but all the math and logic and transforms needed to create this style is pretty much the same as the previous one, except for three differences. So we won’t be going over the same explanation again, we’ll only cover the three different steps needed for this style.

Let’s take the above example again, and change just a small simple CSS rule, and see what difference it makes to the shape of the items.

We’ll apply a radial gradient background to the anchors with a transparent background color. The result will look like this:

You can see where we’re headed from here. Next, we’re going to add space between the items, by changing the angle of rotation for each item a little bit. We’ll also remove the background colors of the list items and the container, and the borders used, and decrease the top padding of the anchor tags to pull the icons up a little bit to center them vertically. The result obtained looks like this:

As you see the navigation is already starting to look different; we’re more than halfway through.
We still have one important thing to do. At the current state of this style, the clickable area of the anchors is still bigger than what we want. What we do want, is that only the colored part of the navigation shown in the image be clickable/hoverable. The image below shows the extra clickable area of the anchors.

When you hover over the red area shown in the image, which is the lower part of the anchor tags, the hover state of the anchors is fired, which is the normal thing to happen, but because we want it to seem like the anchors are only the purple area, we need to prevent mouse events from firing on the lower part of the anchors. For this we’re going to use a “cover” which we’ll place over at the center of the navigation container, which will be a circular shape covering the lower parts of the anchors, and therefore blocking the mouse events on these parts. We’ll be using a pseudo-element for this to avoid adding additional empty tags to our markup.

So, applying these three steps to our CSS, and changing the overall styles (colors and size) of the navigation and navigation items to look like the preview image above, we end up with the following CSS:

.csstransforms .cn-wrapper {
position: absolute;
top: 100%;
left: 50%;
z-index: 10;
margin-top: -13em;
margin-left: -13.5em;
width: 27em;
height: 27em;
border-radius: 50%;
background: transparent;
opacity: 0;
transition: all .3s ease 0.3s;
transform: scale(0.1);
pointer-events: none;
overflow: hidden;
}

/*cover to prevent extra space of anchors from being clickable*/
.csstransforms .cn-wrapper:after{
color: transparent;
content:".";
display:block;
font-size:2em;
width:6.2em;
height:6.2em;
position: absolute;
left: 50%;
margin-left: -3.1em;
top:50%;
margin-top: -3.1em;
border-radius: 50%;
z-index:10;
}

.csstransforms .cn-wrapper li {
position: absolute;
top: 50%;
left: 50%;
overflow: hidden;
margin-top: -1.3em;
margin-left: -10em;
width: 10em;
height: 10em;
font-size: 1.5em;
transition: all .3s ease;
transform: rotate(76deg) skew(60deg);
transform-origin: 100% 100%;
pointer-events: none;
}

.csstransforms .cn-wrapper li a {
position: absolute;
right: -7.25em;
bottom: -7.25em;
display: block;
width: 14.5em;
height: 14.5em;
border-radius: 50%;
background: #429a67;
background: radial-gradient(transparent 35%, #429a67 35%);
color: #fff;
text-align: center;
text-decoration: none;
font-size: 1.2em;
line-height: 2;
transition: all .3s ease;
transform: skew(-60deg) rotate(-75deg) scale(1);
pointer-events: auto;
}

.csstransforms .cn-wrapper li a span {
position: relative;
top: 1.8em;
display: block;
font-size: .5em;
font-weight: 700;
text-transform: uppercase;
}

.csstransforms .cn-wrapper li a:hover,
.csstransforms .cn-wrapper li a:active,
.csstransforms .cn-wrapper li a:focus {
background: radial-gradient(transparent 35%, #449e6a 35%);
}

We want the navigation items in the second demo to spread out in a fan-like effect when the navigation is opened.
To achieve this effect, we have positioning the items in the same place and with the same rotation/skew of rotate(76deg) skew(60deg).

Using transition delays we allow the items to spread out after we scale the wrapper up. When the navigation gets closed, we’ll wait for the items to move back in, before we scale the wrapper down again.

When the open button is clicked we’re going to spread the items out by rotating each of them to their final position on the circle.

.csstransforms .opened-nav {
border-radius: 50%;
opacity: 1;
transition: all .3s ease;
transform: scale(1);
pointer-events: auto;
}

.csstransforms .opened-nav li {
transition: all .3s ease .3s;
}

.csstransforms .opened-nav li:first-child {
transform: rotate(-20deg) skew(60deg);
}

.csstransforms .opened-nav li:nth-child(2) {
transform: rotate(12deg) skew(60deg);
}

.csstransforms .opened-nav  li:nth-child(3) {
transform: rotate(44deg) skew(60deg);
}

.csstransforms .opened-nav li:nth-child(4) {
transform: rotate(76deg) skew(60deg);
}

.csstransforms .opened-nav li:nth-child(5) {
transform: rotate(108deg) skew(60deg);
}

.csstransforms .opened-nav li:nth-child(6) {
transform: rotate(140deg) skew(60deg);
}

.csstransforms .opened-nav li:nth-child(7) {
transform: rotate(172deg) skew(60deg);
}

Of course, we’ll also provide a basic fallback for non-supporting browsers.

.no-csstransforms .cn-wrapper{
margin:10em auto;
overflow:hidden;
text-align:center;
padding:1em;
}
.no-csstransforms .cn-wrapper ul{
display:inline-block;
}
.no-csstransforms li{
font-size:1em;
width:5em;
height:5em;
float:left;
line-height:5em;
text-align:center;
background-color: #fff;
}
.no-csstransforms li a{
display:block;
height:100%;
width:100%;
text-decoration: none;
color: inherit;
}
.no-csstransforms .cn-wrapper li a:hover,
.no-csstransforms .cn-wrapper li a:active,
.no-csstransforms .cn-wrapper li a:focus{
background-color: #f8f8f8;
}
.no-csstransforms .cn-wrapper li.active a {
background-color: #6F325C;
color: #fff;
}
.no-csstransforms .cn-button{
display:none;
}

And the navigation is also going to be responsive, so we’ll shrink it on smaller screens. Here are the responsive styles for both circular and simple styles.

@media only screen and (max-width: 620px) {
.no-csstransforms li{
width:4em;
height:4em;
line-height:4em;
}
}
@media only screen and (max-width: 500px) {
.no-ccstransforms .cn-wrapper{
padding:.5em;
}
.no-csstransforms .cn-wrapper li{
font-size:.9em;
width:4em;
height:4em;
line-height:4em;
}
}
@media only screen and (max-width: 480px) {
.csstransforms .cn-wrapper{
font-size: .68em;
}
.cn-button{
font-size:1em;
}
}
@media only screen and (max-width:420px){
.no-csstransforms .cn-wrapper li{
width:100%;
height:3em;
line-height:3em;
}
}

And that’s pretty much all the CSS you need to create these styles!

The JavaScript

We won’t be using any JavaScript framework for these demos. I will be using David De Sandro’s Classie.js to add and remove classes. And finally for browsers that don’t support the addEventListener and removeEventListener we’ll use Jonathan Neal’s EventListener polyfill.

We’ll add an event handler to the button in each of the two demos. Clicking the button and/or focusing it will trigger opening/closing of the navigation.
Also, for the first demo, clicking anywhere outside the navigation when it’s open will also close it.
Let’s start with the JavaScript for the first demo.

(function(){

var button = document.getElementById('cn-button'),
wrapper = document.getElementById('cn-wrapper'),
overlay = document.getElementById('cn-overlay');

//open and close menu when the button is clicked
var open = false;
button.addEventListener('click', handler, false);
button.addEventListener('focus', handler, false);
wrapper.addEventListener('click', cnhandle, false);

function cnhandle(e){
e.stopPropagation();
}

function handler(e){
if (!e) var e = window.event;
e.stopPropagation();//so that it doesn't trigger click event on document

if(!open){
openNav();
}
else{
closeNav();
}
}
function openNav(){
open = true;
button.innerHTML = "-";
classie.add(overlay, 'on-overlay');
classie.add(wrapper, 'opened-nav');
}
function closeNav(){
open = false;
button.innerHTML = "+";
classie.remove(overlay, 'on-overlay');
classie.remove(wrapper, 'opened-nav');
}
document.addEventListener('click', closeNav);

})();

The JavaScript for the second demo is similar to the previous one, except that we customize it for this case:

(function(){

var button = document.getElementById('cn-button'),
wrapper = document.getElementById('cn-wrapper');

//open and close menu when the button is clicked
var open = false;
button.addEventListener('click', handler, false);
button.addEventListener('focus', handler, false);

function handler(){
if(!open){
this.innerHTML = "Close";
classie.add(wrapper, 'opened-nav');
}
else{
this.innerHTML = "Menu";
classie.remove(wrapper, 'opened-nav');
}
open = !open;
}
function closeWrapper(){
classie.remove(wrapper, 'opened-nav');
}

})();

And that’s pretty much it! I hope you liked this tutorial and found it useful!

View demo Download source

原文链接：http://tympanus.net/codrops/2013/08/09/building-a-circular-navigation-with-css-transforms/ （本内容由互联网资料整理而成）

让你的专题动起来

- 让你的专题动起来

In 网页重构

on 2012-12-18 17:58:01

by hairong

日常工作中会遇到很多的专题，在没有flash的支持下，如何让你的页面更生动呢？
CSS3不是新事物，CSS3动画也逐渐受到大家的关注，在机甲&洛克合作不删档放号专题中首次尝试了CSS3的简单动画制作。
专题地址http://jjxf.qq.com/act/a20120907roco/，请使用chrome浏览器或者Firefox浏览器查看。

拿到页面设计稿后，起初打算从龙星的眼睛、噜噜、枪炮武器上进行一些效果实现，最后为了突出活动抽奖，决定增加抽奖按钮的光效以及奖品的呼吸效果。最终页面呈现包括五个效果点：龙星眼睛的眨动、噜噜的飘动、抽奖按钮的光效、奖品的hover呼吸灯效果以及枪炮武器的发光效果。

思路：首先设置初始属性，背景Y轴位置为0，将背景遮住眼睛，最终属性则是将背景位置移动到-3000px的位置，通过背景在4s内沿Y轴匀速运动3000px的过程实现了眼睛眨动的效果。噜噜的飘动以及抽奖按钮的发光效果与眼睛眨动效果是同样的原理。
效果代码如下：

思路：初始属性设置背景透明度为0.6，最终属性设置背景透明度为1，通过背景透明度的平滑变化，形成一个发光的呼吸效果。
效果代码如下：

实现这样简单的动画效果，需要运用到CSS3的哪些属性呢？

CSS3有3个动画属性：

1、变形transform
transform字面上就是变形，改变的意思。在CSS3中，transform主要包括了旋转rotate、缩放scale、移动translate、扭曲skew以及矩阵变形matrix。
具体属性说明以及demo演示请使用chrome浏览器查看
http://tgideas.qq.com/demo/css3/transform.htm
洛克&机甲不删档合作放号专题上眼睛的眨动、噜噜的飘动、抽奖按钮的发光效果并不是只能用背景移动来实现，我们还可以可以运用translate对div进行位置移动来实现。
Tgideas的一些实例运用(请用chrome浏览器或firefox浏览器查看)：

piggy

jinhui

karin

2、转换transition
css的transition允许css的属性值在一定的时间区间内平滑地过渡。这种效果可以在鼠标单击、获得焦点、被点击或对元素任何改变中触发，并圆滑地以动画效果改变CSS的属性值。
transition的使用写法：

合并写法：

具体属性说明以及demo演示请使用chrome浏览器查看
http://tgideas.qq.com/demo/css3/transition.htm
Tgideas的一些实例运用(请用chrome浏览器或firefox浏览器查看)：

lam

dg

nornor

3、动画animation
animation按照字面上的意思就是“动画”的意思，但在CSS3中animation只应用在页面上已存在的DOM元素上。运用animation，结合变形transform、转换transition，可以制作出简单的动画效果。在制作动画前，先了解一下关键帧keyframes。
1）、Keyframes
我们把他称为“关键帧”，在使用transition制作简单的转换效果时，我们包括了初始属性和最终属性，一个开始执行动作时间和一个延续动作时间以及动作的变换速率，其实这些值都是一个中间值，如果我们要控制的更细一些，比如说我要第一个时间段执行什么动 作，第二个时间段执行什么动作（换到flash中说，就是第一帧我要执行什么动作，第二帧我要执行什么动作），这样我们用Transition就很难实现 了，此时我们也需要这样的一个“关键帧”来控制。
keyframe写法:

2）、动画animation
animation的写法：

合并写法：

具体属性说明以及demo演示请使用chrome浏览器查看
http://tgideas.qq.com/demo/css3/animation.htm
Tgideas的一些实例运用(请用chrome浏览器或firefox浏览器查看)：

nornor

hairong

CSS3的兼容性：

各浏览器前缀：
IE：-ms-
Firefox：-moz-
Chrome：-webkit-
Safari：-webkit-
Opera：-o-

一点总结：

CSS3效果不需要任何其他插件的支持，对于iPhone、iPad都可以正常浏览，但是对于IE，几乎不支持，国内50%左右的用户仍然在使用IE，导致很大一部分人其实看不到这个效果，但是对于FireFox、Chorme等浏览器进行CSS3效果的实现，对于高端浏览器用户来说，不失为一件令用户感到开心的事情，同时这也是一件让自己开心的事情。
初次尝试CSS3效果实现，由起初的无从下手、焦躁不安，到最后效果呈现时的开怀，其实很多事情并不难，只要自己去尝试探索，总能在其中找到各种快乐！

ps:感谢Tgideas的Must大神帮忙配图！

- 留言板

原文链接：http://tgideas.qq.com/webplat/info/news_version3/804/808/811/m579/201212/184628.shtml （本内容由互联网资料整理而成）

字符编码笔记：ASCII，Unicode和UTF-8

今天中午，我突然想搞清楚Unicode和UTF-8之间的关系，于是就开始在网上查资料。

结果，这个问题比我想象的复杂，从午饭后一直看到晚上9点，才算初步搞清楚。

下面就是我的笔记，主要用来整理自己的思路。但是，我尽量试图写得通俗易懂，希望能对其他朋友有用。毕竟，字符编码是计算机技术的基石，想要熟练使用计算机，就必须懂得一点字符编码的知识。

1. ASCII码

我们知道，在计算机内部，所有的信息最终都表示为一个二进制的字符串。每一个二进制位（bit）有0和1两种状态，因此八个二进制位就可以组合出256种状态，这被称为一个字节（byte）。也就是说，一个字节一共可以用来表示256种不同的状态，每一个状态对应一个符号，就是256个符号，从0000000到11111111。

上个世纪60年代，美国制定了一套字符编码，对英语字符与二进制位之间的关系，做了统一规定。这被称为ASCII码，一直沿用至今。

ASCII码一共规定了128个字符的编码，比如空格“SPACE”是32（二进制00100000），大写的字母A是65（二进制01000001）。这128个符号（包括32个不能打印出来的控制符号），只占用了一个字节的后面7位，最前面的1位统一规定为0。

2、非ASCII编码

英语用128个符号编码就够了，但是用来表示其他语言，128个符号是不够的。比如，在法语中，字母上方有注音符号，它就无法用ASCII码表示。于是，一些欧洲国家就决定，利用字节中闲置的最高位编入新的符号。比如，法语中的é的编码为130（二进制10000010）。这样一来，这些欧洲国家使用的编码体系，可以表示最多256个符号。

但是，这里又出现了新的问题。不同的国家有不同的字母，因此，哪怕它们都使用256个符号的编码方式，代表的字母却不一样。比如，130在法语编码中代表了é，在希伯来语编码中却代表了字母Gimel (ג)，在俄语编码中又会代表另一个符号。但是不管怎样，所有这些编码方式中，0—127表示的符号是一样的，不一样的只是128—255的这一段。

至于亚洲国家的文字，使用的符号就更多了，汉字就多达10万左右。一个字节只能表示256种符号，肯定是不够的，就必须使用多个字节表达一个符号。比如，简体中文常见的编码方式是GB2312，使用两个字节表示一个汉字，所以理论上最多可以表示256x256=65536个符号。

中文编码的问题需要专文讨论，这篇笔记不涉及。这里只指出，虽然都是用多个字节表示一个符号，但是GB类的汉字编码与后文的Unicode和UTF-8是毫无关系的。

3.Unicode

正如上一节所说，世界上存在着多种编码方式，同一个二进制数字可以被解释成不同的符号。因此，要想打开一个文本文件，就必须知道它的编码方式，否则用错误的编码方式解读，就会出现乱码。为什么电子邮件常常出现乱码？就是因为发信人和收信人使用的编码方式不一样。

可以想象，如果有一种编码，将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码，那么乱码问题就会消失。这就是Unicode，就像它的名字都表示的，这是一种所有符号的编码。

Unicode当然是一个很大的集合，现在的规模可以容纳100多万个符号。每个符号的编码都不一样，比如，U+0639表示阿拉伯字母Ain，U+0041表示英语的大写字母A，U+4E25表示汉字“严”。具体的符号对应表，可以查询unicode.org，或者专门的汉字对应表。

4. Unicode的问题

需要注意的是，Unicode只是一个符号集，它只规定了符号的二进制代码，却没有规定这个二进制代码应该如何存储。

比如，汉字“严”的unicode是十六进制数4E25，转换成二进制数足足有15位（100111000100101），也就是说这个符号的表示至少需要2个字节。表示其他更大的符号，可能需要3个字节或者4个字节，甚至更多。

这里就有两个严重的问题，第一个问题是，如何才能区别unicode和ascii？计算机怎么知道三个字节表示一个符号，而不是分别表示三个符号呢？第二个问题是，我们已经知道，英文字母只用一个字节表示就够了，如果unicode统一规定，每个符号用三个或四个字节表示，那么每个英文字母前都必然有二到三个字节是0，这对于存储来说是极大的浪费，文本文件的大小会因此大出二三倍，这是无法接受的。

它们造成的结果是：1）出现了unicode的多种存储方式，也就是说有许多种不同的二进制格式，可以用来表示unicode。2）unicode在很长一段时间内无法推广，直到互联网的出现。

5.UTF-8

互联网的普及，强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种unicode的实现方式。其他实现方式还包括UTF-16和UTF-32，不过在互联网上基本不用。重复一遍，这里的关系是，UTF-8是Unicode的实现方式之一。

UTF-8最大的一个特点，就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号，根据不同的符号而变化字节长度。

UTF-8的编码规则很简单，只有二条：

1）对于单字节的符号，字节的第一位设为0，后面7位为这个符号的unicode码。因此对于英语字母，UTF-8编码和ASCII码是相同的。

2）对于n字节的符号（n>1），第一个字节的前n位都设为1，第n+1位设为0，后面字节的前两位一律设为10。剩下的没有提及的二进制位，全部为这个符号的unicode码。

下表总结了编码规则，字母x表示可用编码的位。

Unicode符号范围 | UTF-8编码方式
(十六进制) | （二进制）
--------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

下面，还是以汉字“严”为例，演示如何实现UTF-8编码。

已知“严”的unicode是4E25（100111000100101），根据上表，可以发现4E25处在第三行的范围内（0000 0800-0000 FFFF），因此“严”的UTF-8编码需要三个字节，即格式是“1110xxxx 10xxxxxx 10xxxxxx”。然后，从“严”的最后一个二进制位开始，依次从后向前填入格式中的x，多出的位补0。这样就得到了，“严”的UTF-8编码是“11100100 10111000 10100101”，转换成十六进制就是E4B8A5。

6. Unicode与UTF-8之间的转换

通过上一节的例子，可以看到“严”的Unicode码是4E25，UTF-8编码是E4B8A5，两者是不一样的。它们之间的转换可以通过程序实现。

在Windows平台下，有一个最简单的转化方法，就是使用内置的记事本小程序Notepad.exe。打开文件后，点击“文件”菜单中的“另存为”命令，会跳出一个对话框，在最底部有一个“编码”的下拉条。

里面有四个选项：ANSI，Unicode，Unicode big endian 和 UTF-8。

1）ANSI是默认的编码方式。对于英文文件是ASCII编码，对于简体中文文件是GB2312编码（只针对Windows简体中文版，如果是繁体中文版会采用Big5码）。

2）Unicode编码指的是UCS-2编码方式，即直接用两个字节存入字符的Unicode码。这个选项用的little endian格式。

3）Unicode big endian编码与上一个选项相对应。我在下一节会解释little endian和big endian的涵义。

4）UTF-8编码，也就是上一节谈到的编码方法。

选择完”编码方式“后，点击”保存“按钮，文件的编码方式就立刻转换好了。

7. Little endian和Big endian

上一节已经提到，Unicode码可以采用UCS-2格式直接存储。以汉字”严“为例，Unicode码是4E25，需要用两个字节存储，一个字节是4E，另一个字节是25。存储的时候，4E在前，25在后，就是Big endian方式；25在前，4E在后，就是Little endian方式。

这两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中，小人国里爆发了内战，战争起因是人们争论，吃鸡蛋时究竟是从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开。为了这件事情，前后爆发了六次战争，一个皇帝送了命，另一个皇帝丢了王位。

因此，第一个字节在前，就是”大头方式“（Big endian），第二个字节在前就是”小头方式“（Little endian）。

那么很自然的，就会出现一个问题：计算机怎么知道某一个文件到底采用哪一种方式编码？

Unicode规范中定义，每一个文件的最前面分别加入一个表示编码顺序的字符，这个字符的名字叫做”零宽度非换行空格“（ZERO WIDTH NO-BREAK SPACE），用FEFF表示。这正好是两个字节，而且FF比FE大1。

如果一个文本文件的头两个字节是FE FF，就表示该文件采用大头方式；如果头两个字节是FF FE，就表示该文件采用小头方式。

8. 实例

下面，举一个实例。

打开”记事本“程序Notepad.exe，新建一个文本文件，内容就是一个”严“字，依次采用ANSI，Unicode，Unicode big endian 和 UTF-8编码方式保存。

然后，用文本编辑软件UltraEdit中的”十六进制功能“，观察该文件的内部编码方式。

1）ANSI：文件的编码就是两个字节“D1 CF”，这正是“严”的GB2312编码，这也暗示GB2312是采用大头方式存储的。

2）Unicode：编码是四个字节“FF FE 25 4E”，其中“FF FE”表明是小头方式存储，真正的编码是4E25。

3）Unicode big endian：编码是四个字节“FE FF 4E 25”，其中“FE FF”表明是大头方式存储。

4）UTF-8：编码是六个字节“EF BB BF E4 B8 A5”，前三个字节“EF BB BF”表示这是UTF-8编码，后三个“E4B8A5”就是“严”的具体编码，它的存储顺序与编码顺序是一致的。

9. 延伸阅读

* The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets（关于字符集的最基本知识）

* 谈谈Unicode编码

* RFC3629：UTF-8, a transformation format of ISO 10646（如果实现UTF-8的规定）

（完）

原文链接：http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html （本内容由互联网资料整理而成）

互联网协议入门（一）

全世界几十亿台电脑，连接在一起，两两通信。上海的某一块网卡送出信号，洛杉矶的另一块网卡居然就收到了，两者实际上根本不知道对方的物理位置，你不觉得这是很神奇的事情吗？

互联网的核心是一系列协议，总称为"互联网协议"（Internet Protocol Suite）。它们对电脑如何连接和组网，做出了详尽的规定。理解了这些协议，就理解了互联网的原理。

下面就是我的学习笔记。因为这些协议实在太复杂、太庞大，我想整理一个简洁的框架，帮助自己从总体上把握它们。为了保证简单易懂，我做了大量的简化，有些地方并不全面和精确，但是应该能够说清楚互联网的原理。

=================================================

互联网协议入门

作者：阮一峰

一、概述

1.1 五层模型

互联网的实现，分成好几层。每一层都有自己的功能，就像建筑物一样，每一层都靠下一层支持。

用户接触到的，只是最上面的一层，根本没有感觉到下面的层。要理解互联网，必须从最下层开始，自下而上理解每一层的功能。

如何分层有不同的模型，有的模型分七层，有的分四层。我觉得，把互联网分成五层，比较容易解释。

如上图所示，最底下的一层叫做"实体层"（Physical Layer），最上面的一层叫做"应用层"（Application Layer），中间的三层（自下而上）分别是"链接层"（Link Layer）、"网络层"（Network Layer）和"传输层"（Transport Layer）。越下面的层，越靠近硬件；越上面的层，越靠近用户。

它们叫什么名字，其实并不重要。只需要知道，互联网分成若干层就可以了。

1.2 层与协议

每一层都是为了完成一种功能。为了实现这些功能，就需要大家都遵守共同的规则。

大家都遵守的规则，就叫做"协议"（protocol）。

互联网的每一层，都定义了很多协议。这些协议的总称，就叫做"互联网协议"（Internet Protocol Suite）。它们是互联网的核心，下面介绍每一层的功能，主要就是介绍每一层的主要协议。

二、实体层

我们从最底下的一层开始。

电脑要组网，第一件事要干什么？当然是先把电脑连起来，可以用光缆、电缆、双绞线、无线电波等方式。

这就叫做"实体层"，它就是把电脑连接起来的物理手段。它的主要规定了网络的一些电气特性，作用是负责传送0和1的电信号。

三、链接层

3.1 定义

单纯的0和1没有任何意义，必须规定解读方式：多少个电信号算一组？每个信号位有何意义？

这就是"链接层"的功能，它在"实体层"的上方，确定了0和1的分组方式。

3.2 以太网协议

早期的时候，每家公司都有自己的电信号分组方式。逐渐地，一种叫做"以太网"（Ethernet）的协议，占据了主导地位。

以太网规定，一组电信号构成一个数据包，叫做"帧"（Frame）。每一帧分成两个部分：标头（Head）和数据（Data）。

"标头"包含数据包的一些说明项，比如发送者、接受者、数据类型等等；"数据"则是数据包的具体内容。

"标头"的长度，固定为18字节。"数据"的长度，最短为46字节，最长为1500字节。因此，整个"帧"最短为64字节，最长为1518字节。如果数据很长，就必须分割成多个帧进行发送。

3.3 MAC地址

上面提到，以太网数据包的"标头"，包含了发送者和接受者的信息。那么，发送者和接受者是如何标识呢？

以太网规定，连入网络的所有设备，都必须具有"网卡"接口。数据包必须是从一块网卡，传送到另一块网卡。网卡的地址，就是数据包的发送地址和接收地址，这叫做MAC地址。

每块网卡出厂的时候，都有一个全世界独一无二的MAC地址，长度是48个二进制位，通常用12个十六进制数表示。

前6个十六进制数是厂商编号，后6个是该厂商的网卡流水号。有了MAC地址，就可以定位网卡和数据包的路径了。

3.4 广播

定义地址只是第一步，后面还有更多的步骤。

首先，一块网卡怎么会知道另一块网卡的MAC地址？

回答是有一种ARP协议，可以解决这个问题。这个留到后面介绍，这里只需要知道，以太网数据包必须知道接收方的MAC地址，然后才能发送。

其次，就算有了MAC地址，系统怎样才能把数据包准确送到接收方？

回答是以太网采用了一种很"原始"的方式，它不是把数据包准确送到接收方，而是向本网络内所有计算机发送，让每台计算机自己判断，是否为接收方。

上图中，1号计算机向2号计算机发送一个数据包，同一个子网络的3号、4号、5号计算机都会收到这个包。它们读取这个包的"标头"，找到接收方的MAC地址，然后与自身的MAC地址相比较，如果两者相同，就接受这个包，做进一步处理，否则就丢弃这个包。这种发送方式就叫做"广播"（broadcasting）。

有了数据包的定义、网卡的MAC地址、广播的发送方式，"链接层"就可以在多台计算机之间传送数据了。

四、网络层

4.1 网络层的由来

以太网协议，依靠MAC地址发送数据。理论上，单单依靠MAC地址，上海的网卡就可以找到洛杉矶的网卡了，技术上是可以实现的。

但是，这样做有一个重大的缺点。以太网采用广播方式发送数据包，所有成员人手一"包"，不仅效率低，而且局限在发送者所在的子网络。也就是说，如果两台计算机不在同一个子网络，广播是传不过去的。这种设计是合理的，否则互联网上每一台计算机都会收到所有包，那会引起灾难。

互联网是无数子网络共同组成的一个巨型网络，很像想象上海和洛杉矶的电脑会在同一个子网络，这几乎是不可能的。

因此，必须找到一种方法，能够区分哪些MAC地址属于同一个子网络，哪些不是。如果是同一个子网络，就采用广播方式发送，否则就采用"路由"方式发送。（"路由"的意思，就是指如何向不同的子网络分发数据包，这是一个很大的主题，本文不涉及。）遗憾的是，MAC地址本身无法做到这一点。它只与厂商有关，与所处网络无关。

这就导致了"网络层"的诞生。它的作用是引进一套新的地址，使得我们能够区分不同的计算机是否属于同一个子网络。这套地址就叫做"网络地址"，简称"网址"。

于是，"网络层"出现以后，每台计算机有了两种地址，一种是MAC地址，另一种是网络地址。两种地址之间没有任何联系，MAC地址是绑定在网卡上的，网络地址则是管理员分配的，它们只是随机组合在一起。

网络地址帮助我们确定计算机所在的子网络，MAC地址则将数据包送到该子网络中的目标网卡。因此，从逻辑上可以推断，必定是先处理网络地址，然后再处理MAC地址。

4.2 IP协议

规定网络地址的协议，叫做IP协议。它所定义的地址，就被称为IP地址。

目前，广泛采用的是IP协议第四版，简称IPv4。这个版本规定，网络地址由32个二进制位组成。

习惯上，我们用分成四段的十进制数表示IP地址，从0.0.0.0一直到255.255.255.255。

互联网上的每一台计算机，都会分配到一个IP地址。这个地址分成两个部分，前一部分代表网络，后一部分代表主机。比如，IP地址172.16.254.1，这是一个32位的地址，假定它的网络部分是前24位（172.16.254），那么主机部分就是后8位（最后的那个1）。处于同一个子网络的电脑，它们IP地址的网络部分必定是相同的，也就是说172.16.254.2应该与172.16.254.1处在同一个子网络。

但是，问题在于单单从IP地址，我们无法判断网络部分。还是以172.16.254.1为例，它的网络部分，到底是前24位，还是前16位，甚至前28位，从IP地址上是看不出来的。

那么，怎样才能从IP地址，判断两台计算机是否属于同一个子网络呢？这就要用到另一个参数"子网掩码"（subnet mask）。

所谓"子网掩码"，就是表示子网络特征的一个参数。它在形式上等同于IP地址，也是一个32位二进制数字，它的网络部分全部为1，主机部分全部为0。比如，IP地址172.16.254.1，如果已知网络部分是前24位，主机部分是后8位，那么子网络掩码就是11111111.11111111.11111111.00000000，写成十进制就是255.255.255.0。

知道"子网掩码"，我们就能判断，任意两个IP地址是否处在同一个子网络。方法是将两个IP地址与子网掩码分别进行AND运算（两个数位都为1，运算结果为1，否则为0），然后比较结果是否相同，如果是的话，就表明它们在同一个子网络中，否则就不是。

比如，已知IP地址172.16.254.1和172.16.254.233的子网掩码都是255.255.255.0，请问它们是否在同一个子网络？两者与子网掩码分别进行AND运算，结果都是172.16.254.0，因此它们在同一个子网络。

总结一下，IP协议的作用主要有两个，一个是为每一台计算机分配IP地址，另一个是确定哪些地址在同一个子网络。

4.3 IP数据包

根据IP协议发送的数据，就叫做IP数据包。不难想象，其中必定包括IP地址信息。

但是前面说过，以太网数据包只包含MAC地址，并没有IP地址的栏位。那么是否需要修改数据定义，再添加一个栏位呢？

回答是不需要，我们可以把IP数据包直接放进以太网数据包的资料部分，因此完全不用修改以太网的规格。这就是互联网分层结构的好处：上层的变动完全不涉及下层的结构。

具体来说，IP数据包也分为"标头"和"数据"两个部分。

"标头"部分主要包括版本、长度、IP地址等信息，"数据"部分则是IP数据包的具体内容。它放进以太网数据包后，以太网数据包就变成了下面这样。

IP数据包的"标头"部分的长度为20到60字节，整个数据包的总长度最大为65,535字节。因此，理论上，一个IP数据包的"数据"部分，最长为65,515字节。前面说过，以太网数据包的"数据"部分，最长只有1500字节。因此，如果IP数据包超过了1500字节，它就需要分割成几个以太网数据包，分开发送了。

4.4 ARP协议

关于"网络层"，还有最后一点需要说明。

因为IP数据包是放在以太网数据包里发送的，所以我们必须同时知道两个地址，一个是对方的MAC地址，另一个是对方的IP地址。通常情况下，对方的IP地址是已知的（后文会解释），但是我们不知道它的MAC地址。

所以，我们需要一种机制，能够从IP地址得到MAC地址。

这里又可以分成两种情况。第一种情况，如果两台主机不在同一个子网络，那么事实上没有办法得到对方的MAC地址，只能把数据包传送到两个子网络连接处的"网关"（gateway），让网关去处理。

第二种情况，如果两台主机在同一个子网络，那么我们可以用ARP协议，得到对方的MAC地址。ARP协议也是发出一个数据包（包含在以太网数据包中），其中包含它所要查询主机的IP地址，在对方的MAC地址这一栏，填的是FF:FF:FF:FF:FF:FF，表示这是一个"广播"地址。它所在子网络的每一台主机，都会收到这个数据包，从中取出IP地址，与自身的IP地址进行比较。如果两者相同，都做出回复，向对方报告自己的MAC地址，否则就丢弃这个包。

总之，有了ARP协议之后，我们就可以得到同一个子网络内的主机MAC地址，可以把数据包发送到任意一台主机之上了。

五、传输层

5.1 传输层的由来

有了MAC地址和IP地址，我们已经可以在互联网上任意两台主机上建立通信。

接下来的问题是，同一台主机上有许多程序都需要用到网络，比如，你一边浏览网页，一边与朋友在线聊天。当一个数据包从互联网上发来的时候，你怎么知道，它是表示网页的内容，还是表示在线聊天的内容？

也就是说，我们还需要一个参数，表示这个数据包到底供哪个程序（进程）使用。这个参数就叫做"端口"（port），它其实是每一个使用网卡的程序的编号。每个数据包都发到主机的特定端口，所以不同的程序就能取到自己所需要的数据。

"端口"是0到65535之间的一个整数，正好16个二进制位。0到1023的端口被系统占用，用户只能选用大于1023的端口。不管是浏览网页还是在线聊天，应用程序会随机选用一个端口，然后与服务器的相应端口联系。

"传输层"的功能，就是建立"端口到端口"的通信。相比之下，"网络层"的功能是建立"主机到主机"的通信。只要确定主机和端口，我们就能实现程序之间的交流。因此，Unix系统就把主机+端口，叫做"套接字"（socket）。有了它，就可以进行网络应用程序开发了。

5.2 UDP协议

现在，我们必须在数据包中加入端口信息，这就需要新的协议。最简单的实现叫做UDP协议，它的格式几乎就是在数据前面，加上端口号。

UDP数据包，也是由"标头"和"数据"两部分组成。

"标头"部分主要定义了发出端口和接收端口，"数据"部分就是具体的内容。然后，把整个UDP数据包放入IP数据包的"数据"部分，而前面说过，IP数据包又是放在以太网数据包之中的，所以整个以太网数据包现在变成了下面这样：

UDP数据包非常简单，"标头"部分一共只有8个字节，总长度不超过65,535字节，正好放进一个IP数据包。

5.3 TCP协议

UDP协议的优点是比较简单，容易实现，但是缺点是可靠性较差，一旦数据包发出，无法知道对方是否收到。

为了解决这个问题，提高网络可靠性，TCP协议就诞生了。这个协议非常复杂，但可以近似认为，它就是有确认机制的UDP协议，每发出一个数据包都要求确认。如果有一个数据包遗失，就收不到确认，发出方就知道有必要重发这个数据包了。

因此，TCP协议能够确保数据不会遗失。它的缺点是过程复杂、实现困难、消耗较多的资源。

TCP数据包和UDP数据包一样，都是内嵌在IP数据包的"数据"部分。TCP数据包没有长度限制，理论上可以无限长，但是为了保证网络的效率，通常TCP数据包的长度不会超过IP数据包的长度，以确保单个TCP数据包不必再分割。

六、应用层

应用程序（某种互联网服务）收到"传输层"的数据，接下来就要进行解读。由于互联网是开放架构，数据来源五花八门，必须事先规定好格式，否则根本无法解读。

"应用层"的作用，就是规定应用程序的数据格式。

举例来说，TCP协议可以为各种各样的程序传递数据，比如Email、WWW、FTP等等。那么，必须有不同协议规定电子邮件、网页、FTP数据的格式，这些应用程序协议就构成了"应用层"。

这是最高的一层，直接面对用户。它的数据就放在TCP数据包的"数据"部分。因此，现在的以太网的数据包就变成下面这样。

至此，整个互联网的五层结构，自下而上全部讲完了。这是从系统的角度，解释互联网是如何构成的。下一次，我反过来，从用户的角度，自上而下看看这个结构是如何发挥作用，完成一次网络数据交换的。

（完）

原文链接：http://www.ruanyifeng.com/blog/2012/05/internet_protocol_suite_part_i.html （本内容由互联网资料整理而成）

使用JavaScript创建模块化的双人对战象棋程序

使用JavaScript创建模块化的双人对战象棋程序

1. 关于这篇文章

2004年，我花两天时间，用JavaScript和VML创建了一个单机双人象棋，并且作了简短的分析。在那个时代，没有AngularJS，没有BackBone，没有所有这些前端MV*框架。甚至没有jQuery，没有prototype，没有mootools，因此没有什么可借鉴的模块划分方式。我只好用很原始的办法，做了一种伪继承，实际是组合，来实现棋子和棋局之间的关系。

现在是2013年，9年过去了，Web的世界早已不是过去的样子，开发方式发生了翻天覆地的变化，我们有了Gmail，有了Google docs等等把Web技术应用到极致的优秀产品，有了asm.js、pdf.js等等让我们目瞪口呆的技术，更催生了各种MV*框架的兴起，我们有更多，更强大的方式去写Web程序。

前一段时间，我创建了一个简单的JavaScript框架叫做thin，实现了模块的定义、异步加载和使用，并且为它写了一个比较简短的Demo，但这个Demo实在太简单了，当一个应用更大、更复杂的时候，我们应该如何组织自己的程序呢？

为了说明我们这个简单框架的能力，我把之前写过的这个象棋在这个thin框架基础上重写一遍，并且作更深入的分析，以便使一些入门不久的读者得到帮助，同时也顺便检验我的新框架模块化是否是可用的。

另外一个方面，我们看到VML已经彻底衰落了，各种基于SVG和Canvas的绘图技术取代了它，因此在本例中，我们也与时俱进，改用SVG来绘制棋盘和棋子。RaphaelJS是一个很好的跨平台绘图库，它封装了SVG和VML，在能够使用SVG的浏览器中，它用SVG绘图，否则尝试使用VML，对于上层应用，操作绘图的API是毫无区别的，开发者不会感知到它的具体实现差别。

之前我写过一些文字，用于探讨软件开发模块的划分原则，感觉它们放在这篇文章里非常合适，所以略微修改之后，加了进来。

2. 模块划分的一些原则

2.1. 面向对象

面向对象可以算是老生常谈了，在现代软件开发中，它是个主流的选择，相对于面向过程，有一些改进。

假设我们是上帝，要创造世界，因为这个过程太过复杂，无从入手，所以先从一件简单的事情看起。现在我们要设计一个方法，用于描述狼吃羊这个事情，某只狼吃了某只羊，你可以面向过程地吃，eat(狼A, 羊A)，也可以面向对象地吃，狼A.eat(羊A)。差别在哪里？只是写法有点变化。

好，那么我们帮上帝模拟整个生物界，这里面很多东西可以吃，大鱼吃小鱼，小鱼吃虾米，吃不吃皮，吐不吐骨头，这个时候再来修改这个eat函数，复杂吗？eat里面要判断很多东西，假如上帝很勤劳，所有代码都自己设计，那没关系，没太大区别，判断就判断呗。

假设上帝没足够精力来管理整个东西了，雇了一群天使来协助设计，每个人都来修改这个eat函数，当然可以拆分，wolfEatSheep(), tigerEatWolf()，然后在eat里面判断参数来分别调用，把函数分下去让每个人做，可以。

动物不光要做吃这个事情，要能跑能跳，会说会叫，又多了一堆函数，每个里面都这么判断，相当相当的烦。怎么办？我们来面向对象一下。

现在开始按照动物拆分，100个天使，每个天使创造一种动物。创造哪种动物，就站在哪种动物的角度考虑问题，我吃的时候怎么吃，跑的时候怎么跑，都跟别人无关，这么一来，每个人就专注多了。每个动物只关注我要怎么才能活着，不必站在上帝的角度考虑问题。这个过程，是类的划分过程，也就是封装的过程。

这时候，上帝觉得自然界光有动物是不行的，还要有植物，刚才说的这些都是动物，植物的特点跟动物有很大区别。假设你是上帝，为每种生物安排衣食住行，那是相当复杂的。偷懒吧，上帝说，植物们，你们自己生长吧，动物们，你们吃喝玩乐吧，假如能达到这个效果，那很省事。

上帝用一个循环来遍历所有动物，让他们吃喝玩乐，用另外一个循环让植物欣欣向荣。动物跟植物为什么要区别对待？因为它们不是同样的东西，能做的事情不同。所有动物派生于动物这个基础类型，从动物这个种类下，又分出各种纲，各种目，各种属。狮子是哺乳动物，猴子也是，但是狮子是猫科动物，猴子是灵长动物，这就构成了一个倒着的树状体系，一层一层形成继承关系。哺乳动物会喂奶，那么所有继承自哺乳动物的，都自动拥有这个特征。整个这一切，构成了继承链。

假设有一天由于变异出现了新物种，不必劳烦上帝关照，只要鉴别一下它属于什么类型，就知道能做什么事了，它的一举一动，都必然拥有它所继承的种类的特征。

这样就能描述生物界了吗？不，还有那么一些怪胎的存在。你认为哺乳动物都不会飞，那就错了，因为蝙蝠会飞。蝙蝠会飞是它自身的特性，并非继承自哺乳动物，但是“飞”这个动作，却非蝙蝠独有。如果把“飞”定义成接口，那就很美好了，蝙蝠实现了它的飞行接口，虽然内部实现跟鸟类有所不同，而且这并不影响它的哺乳动物特性。

总之，是否面向对象只是思维方式的不同。做一个软件，面向对象也能做，不面向对象也能做。我的观点，如果关注可维护性和协作性，从目前的角度，面向对象是很好的选择，它很自然，很优雅，优雅得只要打一个“.”，你就能想起来什么事能做，什么事不能做。

2.2. 模块的职责划分

面向对象的一个基本原则是分而治之（Divide and Conquer），这种方法论提倡将程序模块化，各模块实现单独的功能，在统一的管理下协同工作，构成整个系统。

在具体实施的时候，又有两种倾向：将功能高度集中于主控制模块；将功能下放到各部件。这两种做法都有很高的可行性，也分别有大量支持者。我觉得在一些程度上，后者更贴近人类的思维方式，更适合用人性化的观念来解释。

将两种类型的程序对应到生物集群，第一种相当于一个蚁群，第二个相当于人群。蚁群的特点是，个体能够完成的事务非常有限，但是因为在一个非常强有力的统治者蚁后的控制下，它们能够协同工作，统一调度，完成不可想象的事件。人群的特点是，每个人都可以独立思考，能够理解别人的指令，并且根据这些指令做到力所能及的事情。作为人群的统治者，他的智慧不需要比其他人的高太多，只需要从宏观上来把握一些事情即可。

从系统的实现来说，第一种方式难度很高。完成单个蚂蚁（小模块）的功能并不复杂，创建大量的蚂蚁也只不过是需要的时间多一点，但是，当开始设计蚁后（总控模块）的时候，噩梦开始了，整个调度算法实在是一件令人头疼的事情。对于比较复杂一点的系统，让一个人去设计这个模块简直是不可思议，但是如果由多个人共同完成这个模块，又面临着互相理解的问题，每个人的思路都不相同，在努力协作的过程中，大量的时间被浪费在交流和意见的统一上。与此同时，制作蚂蚁的程序员日益烦躁，觉得自己的工作没有难度，无聊，士气低下……

换一种思路，从人类管理的角度来看问题。假设有一支庞大的军队（假设是一个集团军），司令官需要他的士兵列队，我们来为这个系统设计调度算法。先假设所有士兵跟蚂蚁一样笨，他们只能明白“站到司令部大门往东50米，往北100米的地方”这样的简单指令，请同情一下这位司令官，他不得不为每个士兵来指定一个位置，并且不得不研究列队的规则，他需要整天忙碌来完成这样一个庞大的任务（而且还不一定能完成）。他叹息道：哦，上帝……

让我们设法来减轻他的烦恼吧，目标是让每个人都主动参与这个事件，不再那么被动，大家都努力完成自己力所能及的工作。于是我们授权各级指挥官让他自己的士兵列队，这样一来，司令官的工作简单多了，他发布命令：各位军长请注意，我命令你们列队，按照番号顺序，分布到司令部门口的空地上（假设这个空地足够大，姑且认为能够容纳整个集团军），各军之间保持50米间隔。

接到命令以后，军长们开始忙碌，而司令官先生已经可以搬一把椅子坐到电话机旁，等待列队完毕的报告了。同样，军长要做的事情，也就是告诉属下的各位师长，让他们按照番号顺序列队，就这样，命令被传递到最下面一级。班长大喊：伙计们，按照个头排成一列，矮的在前面，高的在后面，前后间隔一米！于是，所有人站到了他应该站的位置，望着在短时间内迅速列队的整个集团军，司令官太满意了。

我们发现了什么？很显然，下放权力的方式要省事得多，更关键的是，它使得每个人都做一定的事情，但是又不成为负担。在设计者思路清晰化的同时，负责为系统每个部分编写代码的人员也更容易享受到编程的乐趣，就算是最低层的程序员也有了发挥自己才能、用自己的思路去影响系统的机会，而且，系统集成的过程将变得更加简单。

对于一名软件设计师来说，他的思想决定了他所设计出来的软件结构，将自己的灵魂注入到冰冷的代码中，这是一种艺术。然而，不同的人有不同的风格，设计者对于世界的认知方式不同，他们对于同样的需求，可能采用的设计方式也多种多样。

3. 怎么设计我们的象棋程序

3.1. 为象棋程序划分模块

做一个象棋程序，有哪些事情要做呢？

首先，我们要能够初始化一个棋局，把棋盘和棋子绘制出来，点击棋子的时候，能给出它可以走的地方，可以移动这个棋子，也可以吃掉对方的棋子。走完一步，要能够判断是否将军，如果吃掉了对方的将帅，能够判断棋局的终止。这些东西，除了绘图之外，我们都放在棋局模块里，我们有个第一个模块Game。绘图模块的职责比较单一，我们把它放在一个棋盘模块中，这是第二个模块ChessBoard。

天下之事，事事都是棋局，人在局中为名来，为利往，都是棋子。可见，与棋局相对的就是棋子了。按照我们在第二部分提到的思路，棋子应当是要承担一些职责的，那么，哪些事情适合交给棋子来做呢？

我们定义这么一个规则：做一件事，如果有多个参与者，其中某个参与者要付出的代价最大，这一步就由这个参与者来负责做。

我们把走棋的这个过程分解，这里有四个部分：

- 判断我有没有可能出现在那个位置，比如说，象不能过河，老将和卫士不能出九宫格。

- 判断目标位置有没有己方棋子，如果有，也过不去。

- 判断能否直接到达目标位置，比如说，马腿是否被挡着了？象眼是否被塞着了？

- 移动过去，如果有对方棋子，吃掉它。

从第一步来看，这个过程不依赖于其他任何东西，每个棋子都应当能够牢记自己能去什么地方，不能去什么地方，只要你给它一个棋盘坐标，它自己是可以知道能不能去的。比如象知道自己不能过河，如果你给的坐标就超过了，它可以知道自己不能去。所以，这个职责我们放给棋子。

再看第二步，这个我们怎么判断呢？假设我们是一个士兵，在平原上打仗，我想知道前面山顶有没有人，怎么办？看了很多电影的我们表示，很好办。“总部总部，请侦察对面山顶。”所以，这个过程我们可以看到，检索目标位置不是棋子自身的职责，他只是调用了某个别的东西（己方司令部），得到了结果。

下面是第三步，这里面有可能不需要依赖于其他模块，也可能要依赖，怎么解释呢？比如说卫士，他走路只看距离，如果是他的合法可达位置，并且和当前位置距离的平方为1+1=2，那就可以直接过去，不需要依赖任何外部模块。但是如果是马，要先看距离的平方是不是1+4=5，然后再找马腿的位置，再去看那个位置有没有棋子。所以这种情况下，就要依赖外部模块。

第四步看似很简单，过去的时候发个通知给司令部，我换地方了！但司令部那边要把当前所有人分别在哪都记录着，所以他要做的事情其实比棋子更复杂，所以这一步可以让他做。

于是，我们得出结论，棋子的职责应该是这些：

- 判断自己是否可能出现在某位置

- 判断自己能否到达某位置

现在我们就有了Chess模块，并且从它派生出各种棋子，同时，为这些棋子实现一个工厂模块ChessFactory，用于根据参数创建这些棋子。

现在我们来考虑，谁来提供这个查询的服务，承担司令部的这些职责，棋子的位置都保存在棋局中了，所以，很自然地，棋局承担了这个职责。使用。

简单地考虑一下，我们的棋局应当能够：

- 初始化。初始化方法做的是把棋局恢复成初始状态，每次重新开局之前，我们可以这么做一下。

- 走棋。走棋是把给出的棋子移动到指定位置，如果目标位置没有别的棋子，只做移动，否则还要把对方杀死。走棋之前有一些判断条件，我们也把它们列出来。

- 列出某棋子的可达范围。这其实是一个辅助功能，当用户点击某棋子的时候，界面上能够标示出所有该棋子的可到达位置，便于用户选择，当用户选择其中某一个的时候，把棋子移动过去。

- 判断是否终局。每一步棋走完，我们都需要看一下是否有一方获胜，如果有，本局应当终止。

3.2. 代码结构

根据上述的结论，我们建立了这么4个代码文件，用于存放不同的模块：

- game，存放棋局相关的功能

- chessboard，存放绘制棋盘和棋子相关的功能，点击操作也由它负责传递

- config，存放各种配置信息，比如棋盘大小等等

- chessman，存放各种棋子的功能和棋子生成器

注意到我在chessman.js里面，定义了多个模块，这其实就是我这个thin框架的核心理念，模块跟文件不一一对应，模块对应于Java中的class文件，而js文件对应于Java中的jar文件，是模块的集合。这么做当然也有弊端，因为无法得知模块是否有冲突，或者存在被覆盖的情况，引用也不是很方面，所以我为此还建立了一套管理和发布机制，专门用来解决这个问题。在小型项目纯手写代码的情况下，直接这样用就可以了。代码细节不一一列出，请读者自行查看。

4. 可能的改进

上面我们实现了一个可以在单机下双人对战的象棋程序，运行得还不错，但是我们想要给它一些增强，应当如何去做呢？

如果我们想要本机开多个棋局，怎么办？

在我们现有结构下，其实很简单，因为我们模块化做得还是挺好的，Game可以作为顶层模型，然后创建出对应的DOM容器，用我们上次写的Bind来扫描一遍，自动创建实例，就可以了。

另外一个很典型的增强是，既然我们都做了单机的对战了，是不是可以搞一个服务端，变成联机的对战呢？当然可以，要做这个，我们需要改动的代码是Game模块，这一步不再适合直接创建了，而是要放在新建棋局的服务端回调里面，棋局的状态也需要在服务端保存一份，然后每次下棋，把走的棋子和坐标放过去，对其他任何模块都没有改动。从这里我们也可以看到如果代码进行了合理的分层，当需要改进的时候，对原代码改动有多么容易。

再有这么一天，我们还要做棋局的撤销怎么办？虽然这个很不好，有损大丈夫的威名，但我们只从实现角度来分析一下。在设计模式中，有一种叫做命令模式，这种模式其实就很适合做undo跟redo，只要把每个事情都封装为步骤，那么，这两种操作就变成了正向和反向的两种步骤了，做起来也就非常容易。

还不满意，要添加人工智能怎么办？抛开人工智能常用的剪枝算法不谈，我们假设已有这么个算法，只需要在一方移动棋子之后，把当前局势传递给这个算法得到下一步即可。

综上所述，做大一点的Web应用，必须先做模块化，把模块按照功能划分，理清它们之间的关系，然后再用合适的框架去管理维护。作者正在编写的thin框架就是试图从模块入手，一步一步添加其他功能，把它做成一个有一定可用性的框架。

本文的Demo地址是：http://xufei.github.io/thin/demo/chess.html

本文仅用于学习和交流目的，不代表图灵社区观点。非商业转载请注明作译者、出处，并保留本文的原始链接。

原文链接：http://www.ituring.com.cn/article/53808 （本内容由互联网资料整理而成）

Chrome（Extensions）开发攻略

本文将从个人经验出发，讲述为什么需要Chrome插件，如何开发，如何调试，到哪里找资料，会遇到怎样的问题以及如何解决等，同时给出一个个人认为的比较典型的例子——获取网页内容，和服务器交互，再把信息反馈给用户。OK，准备开始吧，我尽量把文章写得好看点，以免读者打瞌睡。

简单地说，浏览器插件，可以大大的扩展你的浏览器的功能。包括但不仅限于这些功能：捕捉特定网页的内容，捕捉HTTP报文，捕捉用户浏览动作，改变浏览器地址栏/起始页/书签/Tab等界面元素的行为，与别的站点通信，修改网页内容……给你增加许多想象空间，试想想看，你可以用它来识别一些网站上的广告代码，并直接把这些代码删掉，这样你就不会受到广告的困扰了，没错，如你所愿，这样的插件别人已经开发好了，你可以直接用。不过，也要说浏览器插件的弊端，那就是：会带来一些安全隐患，也可能让你的浏览器变得缓慢甚至不稳定。

因为Chrome的插件开发起来最简单，总体上看没什么新的技术，开发语言就是javascript，web前端工程师能很快上手；而Firefox的插件开发则复杂许多，涉及到环境的搭建和一些WEB以外的技术；IE的插件开发就更复杂了，需要熟悉C++和COM技术，当然还要装微软的Visual Studio。

应该说Chrome和Opera的插件的开发都不难，但Firefox的则比较棘手，也许你要问，那为什么Firefox的插件是最丰富的？我想这有些历史原因，Chrome出来毕竟比较晚，另外几种浏览器提供的插件的功能也是不尽相同的，OK，我们还是言归正传吧。

几乎是零需求。Chrome浏览器和一个文本编辑器即可，文本编辑器最好是带语法高亮的那种。谷歌对我们做技术的人来说真是太大度了。

文章不长，照着文章去做，完成后，你就成功开发了第一个Chrome插件，这个插件会弹出一个小窗口，上面显示些阿猫阿狗的小图片。如图：

- manifest.json - 所有插件都要有这个文件，这是插件的配置文件，可看作插件的“入口”。

- icon.png - 小图标，推荐使用19*19的半透明png图片，更好的做法是同时提供一张38*38的半透明的png图片作为大图标，在我后面提供的例子中，我就是那么干的。

- popup.html - 就是你所看到的那个阿猫阿狗的弹出页面。

- popup.js - 阿猫阿狗页面所引用的javascript文件。

这里千万千万注意了，我当初没仔细看popup.html里有一小段注释，这一小段注释说：出于安全考虑，javascript必须与html分开存放。而我想嘛，一个小测试程序，没必要分开吧，直接写一起不就行了吗？结果javascript死活执行不了，我翻来覆去找不到原因，还以为弹出的小窗口不支持javascript，在网上搜索了半天又没有结果，最后才发现是这个原因，浪费了许多时间，这个事情也一定程度上说明了：细节决定成败。

manifest.json中的内容也非常显而易见，我选择其中几个属性讲一下：

{
"manifest_version": 2,

"name": "One-click Kittens",
"description": "This extension demonstrates a browser action with kittens.",
"version": "1.0",

"permissions": [
"https://secure.flickr.com/"
],
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
}
}

"manifest_version"：现在应该总是2。

"permissions"：很重要的东西，即允许插件做哪些事情，访问哪些站点，假如一个插件的"permissions"里写有“http://*.hacker.com/”，那么这个插件就被允许访hacker.com上的所有内容，包括可能会把你的一些个人信息提交给hacker.com，危险性不言而喻，查看一个插件能访问那些站点的方法是：在chrome的地址栏里输入“chrome://extensions/”（注意：这个页面我们之后要频繁用到，请收藏一下），然后点对应插件的旁边的那个“权限”，如：

"default_popup"：用来指定点击小图标后弹出的小窗口中默认显示的是哪个html，这个弹出的小窗口就叫做“popup”。

"browser_action"：这是一个浏览器级的动作，也就是说，不管你现在在访问哪个页面，那个小按钮总是显示出来，而我们的插件如果仅仅是针对某些页面的话，就不适合用这个"browser_action"了。下面我们来弄一个只有访问博客园（www.cnblogs.com）才会出现的小按钮。

Page Action

chrome-plugin-page-action-demo.7z

这个插件只有4个文件，其中两个还是图标，那就只剩下一个必须的manifest.json和一个background.js了。

mainifest.json：

{
"manifest_version": 2,
"name": "cnblogs.com viewer",
"version": "0.0.1",
"background": { "scripts": ["background.js"] },
"permissions": ["tabs"],
"page_action": {
"default_icon": {
"19": "cnblogs_19.png",
"38": "cnblogs_38.png"
},
"default_title": "cnblogs.com article information"
}
}

注意：这里是“page_action”而不是“browser_action”属性了。

“permissions”属性里的“tabs”是必须的，否则下面的js不能获取到tab里的url，而这个url是我们判断是否要把小图标show出来的依据。background是什么概念？这是一个很重要的东西，可以把它认为是chrome插件的主程序，理解这个很关键，一旦插件被启用（有些插件对所有页面都启用，有些则只对某些页面启用），chrome就给插件开辟了一个独立的javascript运行环境（又称作运行上下文），用来跑你指定的background script，在这个例子中，也就是background.js。

background.js

function getDomainFromUrl(url){
var host = "null";
if(typeof url == "undefined" || null == url)
url = window.location.href;
var regex = /.*\:\/\/([^\/]*).*/;
var match = url.match(regex);
if(typeof match != "undefined" && null != match)
host = match[1];
return host;
}

function checkForValidUrl(tabId, changeInfo, tab) {
if(getDomainFromUrl(tab.url).toLowerCase()=="www.cnblogs.com"
){
chrome.pageAction.show(tabId);
}
};
chrome.tabs.onUpdated.addListener(checkForValidUrl);

代码中，我们使用了一个正则表达式去匹配url，获取出其中的domain部分，如果domain部分是“www.cnblogs.com”的话，就把小图标show出来，效果如下：

当然了，你现在点那个小图标的话，是没有任何反应的，我没有像官方提供的那个例子那样提供了popup。OK，现在是时候描述下chrome插件的结构了。

Chrome插件结构

需要声明的是，这个结构图是我自己画的，代表我对Chrome插件的理解，可能并不全面，甚至还不是十分准确，但找来找去找不到现成的，只好自己动手，如有谬误，请不吝指出。

如图，manifest.json作为插件的配置文件，同时可以看作程序的“入口”，因为它指定了显示什么图标，background script有哪些文件，content script又有哪些文件，pop up的页面是什么，等等。

什么是popup，什么是background script，相信大家都清楚了，那什么是content script呢？content script就是我们要注入到页面中的脚本，插件允许我们往网页中注入脚本，这是一个多么让人有想象力的功能，其功能之强大无需多解释，总的来说，就是让我们全面干预页面的内容！也许你马上会想到，这可能带来很大的安全隐患，没错，有些恶意插件会窃取你的页面信息，而有些有漏洞的插件则可能让你遭受跨站脚本注入（XSS）的攻击；另一个可能你会想到的问题是：往页面中注入自己的脚本，难道不会跟页面原本的脚本发生冲突吗？能想到这点说明你真的很厉害，如果我们的注入脚本和页面原本的脚本处于同一个运行环境中，确实会发生冲突，所以，Chrome是另外开辟了一个独立的运行空间，供我们的Content Script使用的，Content Script能访问DOM的内容，但却不能访问页面原本的脚本（我是说直接访问不行），反之，页面原本的脚本也不能直接访问Content Script。在图中，浅红色的背景块代表Content Script的运行环境，而浅蓝色的背景块代表页面运行环境，另外插件的运行环境我用浅绿色表示，注意，这是三个不同的运行环境，调试的时候你会充分体会到它们的不同。

那么，Content Script会在什么时候运行呢？默认情况下，是在网页加载完了和页面脚本执行完了，页面转入空闲的情况下（Document Idle），但这个是可以改变的，详情可参考https://developer.chrome.com/extensions/content_scripts.html，查看其中的“run_at”。

由于处于不同的运行环境中，Content Script和Background Script不能直接互相访问，那它们之间如何通信？通过Message！这个之后的代码中会有。

学习资料

理解了Chrome插件结构之后，我相信你完全有能力开发一款自己的插件了，当然了，你得自己去google一些资料，这里我就分享下我的方法。

首先，官方的资料一定得看看，https://developer.chrome.com/extensions/index.html，这个上面的资料得大致浏览一下（不需要全部仔细看），这样你能够明白一些术语，知道如何去寻找你的解决方案。

再则，官方提供的例子，可以看看，https://developer.chrome.com/extensions/samples.html，我发现上面的例子有些已经不能用于新版的Chrome了，但没关系，你只要找你想要的就行了，也不用一个个尝试，就根据你的需要，挑选几个你感兴趣的看看即可。

遇到问题，怎么办？当然是用google去查找问题，但这里我最最最强烈推荐stackoverflow.com，这简直是解决问题的神器！不多解释了，用过便知。

学习过程基本上就是：看个大概，写点代码，调试调试。就可以了。哦，大前提当然是你得有javascript的基础。（你：呵呵，你在逗我吧！）

我的例子

chrome-plugin-cnblogs-article-information.zip

chrome-plugin-cnblogs-article-info-server.zip （服务器端，PHP代码）

好，轮到我的例子登场了。它的功能是这样的：当你浏览博客园的时候，它会启动并尝试获取你浏览的文章的信息（标题、作者和日期），再通过往另一个服务器发送请求的方式，记录和获取你第一次访问这篇文章的时间，把这个时间连带文章的信息，显示在popup上。听起来挺无聊的功能，但关键是为了演示嘛，如图：

这个插件一共有9个文件，新出现的文件有两个（其它相信大家都很熟悉了），一个是“content_script.js”，这就是前面提到的Content Script，获取和修改页面的内容就靠它了；另一个是“jquery-2.0.0.min.js”，大名鼎鼎的jQuery，我很喜欢用的js库，其理念是“write less，do more”，能帮我减少很多代码，这是目前最新的2.0.0版，这个版本跟以前的1.x.x的最大差别就是不再支持IE6、7和8，我个人是十分赞同这种做法的，微软的旧版浏览器都成了Web技术发展的绊脚石了，而且这次我们用的是Chrome浏览器，果断选择最新版了。

另外还有一个服务器端，为了让问题简化，这次我用了php代码，一个php文件就是整个处理了，没有太多繁杂的配置，简洁，这是php最大的优势。系统结构如图：

抓取网页的内容得依靠content_script.js，然后通过sendMessage/onMessage和background.js交换数据，background.js将url信息通过ajax（XMLHttpRequest）发送给localhost，获取此页面的第一次访问的时间，最后，用户点小图标，popup.html出现，popup.html会读取（代码在popup.js中）background.js中的articleData的数据，把它显示出来。这就是整个过程。

我抓取网页数据的方式并不能确保所有的博客园的文章都能被正常获取，这跟用户使用的博客模板有关系，但我尝试下来大多数文章还是可以抓取的，我不去适应所有的模板了，毕竟这只是个演示的demo。

另外还需说明的一点是我使用了jQuery做XMLHttpRequest，post的内容不是传统的html表单形式，而是json数据，所以在服务器端这边，就不能直接用$_REQUEST获取，而是通过读取“php://input”的内容获取。顺便谈谈个人对web api的一个看法：“统一”大于“灵活”，这是我的观点，我确定我的接口的格式是json，使用utf-8编码，于是就一直用下去，调用者不用考虑用XML还是html表单还是别的，开发者也不必多考虑，让这成为一种统一的约定，在团队协助和以后的开发中会很省事。

调试

程序开发，必定要涉及到调试，记得我刚开始做WEB开发前，问一些做了好久WEB开发的朋友，你们是怎样做javascript调试的，我发觉大多数人竟然回答：用alert一点点试吧——不是不行，是太原始，太低效了，对吧？其实Chrome直接支持javascript的调试，拥有了Chrome，就相当于拥有了一个强大的javascript调试器了。

Chrome打开开发者工具的方法是<Ctrl>+<Shift>+<I>（Windows版），大致如下：

我们这次需要关心的有“Elements”、“Sources”和“Console”这三个标签。Elements是用来做DOM分析的，功能有点类似Firebug，帮助我们分析页面的内容；而Sources，是我们用来调试javascript的；Console则是我们的Log的输出窗口，也是一个调试利器。

调试Content Script

如我提供的这个例子，可在Sources的“Copntent Scripts”下看到“content_script.js”然后设断点，执行到断点处时，Chrome会挺住，你可以观察到上面的值，如图：

太cool了，请问你还要一点点alert吗？

调试Background

由于background和content script并不在同一个运行环境中，因此上面的方法是看不到Background的javascript的。要调试Background，还需要打开插件页，也就是“chrome://extensions”。点对应的插件的“generated background page.html”，就出现了调试窗口，接下来的操作就跟前面的类似了。如图：

至于你看到ID，“aajnhhjiia……”这一长串东西，这是chrome自动安排的一个ID。

调试Popup

虽然Popup和Background是处于同一运行环境中，但在刚才的Background的调试窗口中是看不到Popup的代码的。调试Popup还需要这样：

然后……就跟前面差不多了。

一些问题

也许有时候你会发觉调试器不是很灵，至少我用下来感觉如此，比如你可能发现断点设不了，或者断点不起作用，或者看不到你自己的javascript文件。我的方法是在插件页中，把对应的插件的“已启用”这个复选框去掉，再重新勾上，然后再点一下“重新加载(Ctrl+R)”，通常能解决问题。当然了，还有些很古怪的问题，还不好重现，总体的解决思路就是重新载入一下，实在不行的话重启浏览器，或者清除浏览器缓存什么的，再试试看。

在做插件调试的时候我还遇到一个十分郁闷的问题，那就是我的Chrome使用了“Go Agent”，关于Go Agent是用来干嘛的，这个嘛，可以去google一下，我相信绝大多数程序员都会喜欢上它……可由于使用了这个东西，很可能会导致插件的XMLHttpRequest工作不正常，而且可能你会思索半天也找不到原因，好吧，暂时把Go Agent停用掉，甚至可能你需要重启下Chrome——我的经验。

总结

我还是想说，我觉得Google对我们程序员来说是个很大度的公司，在Chrome这个产品上面就可见一斑。利用Chrome插件技术，我们可以做许多有用的东西，通过本文，相信你已经知道如何去开发一款Chrome插件了，当然了，Chrome插件的功能是很强大的，我用到的仅是冰山一小角。要深入，当然还需要更加充分地利用google和stackoverflow.com了。

原文链接：http://www.lupaworld.com/article-228139-1.html （本内容由互联网资料整理而成）

程序员的困境

Recently I interviewed tens of candidates for a kernel programmer’s position. These candidates are from big, good companies, which are famous for chips or embedded OS/systems. Many of them claimed they have at least 10 years on-job experience on kernel. Their resumes look fairly shiny — all kinds of related projects, buzz words and awards…

But most of them cannot answer a really basic question: When we call the standard malloc function, what happens in kernel?

Don’t be astonished. When I ask one of the candidate to write a simple LRU cache framework based on glib hash functions, he firstly claimed he had never used glib — that’s what I expected — I showed the glib hash api page and explained the APIs to him in detail, then after almost an hour he wrote only a few lines of messy code.

I don’t know if the situation is similar in other countries, but in China, or more specifically, in Beijing, this is reality. “Senior” programmers who worked for big, famous foreign companies for years cannot justify themselves in simple, fundamental problems.

Why did this happen?

The more I think about it, the more I believe it is caused not only by themselves but also by the companies they worked for. These companies usually provide stable stack of code, which has no significant changes for years. The technologies around the code wraps up people’s skills, so that they just need to follow the existing path, rather than to be creative. If you happened to work for such kind of code for a long period and did not reach to the outer world a lot, one day you will find yourself to be in a pathetic position — they called you “EXPERT” inside the team or company, yet you cannot find an equally good job in the market unfortunately.

This is so called “Expert Trap”. From day to day, we programmers dreamed of being an expert inside the team/company; however, when that day really comes we trapped ourselves. The more we dig into existing code, the deeper we trapped into it. We gradually lose our ability to write complete projects from scratch, because the existing code is so stable (so big/so profitable). What’s the worse, if our major work is just to maintain the existing code with little feature development, after a while, no matter how much code we’ve read and studies, we will find we cannot write code — even if the problem is as simple as a graduate school assignment. This is the programmer’s dilemma: we make our living by coding, but the big companies who fed us tend to destroy our ability to make a living.

How to get away from this dilemma?

For personal —

First of all, Do your own personal projects. You need to “sharpen your saw” continuously. If the job itself cannot help you do so, pick up the problems you want to concur and concur it in your personal time. By doing so, most likely you will learn new things. If you publish your personal projects, say in github, you may get chances to know people who may pull you away from your existing position.

Do not stay in a same team for more than two years. Force yourself to move around, even if in the same organization, same company, you will face new challenges and new technologies. Try to do job interviews every 18 months. You don’t need to change your job, but you can see what does the market require and how you fit into it.

For team/company —

Give pressures and challenges to the employees. Rotate the jobs, let the “experts” have chance to broaden their skills. Start new projects, feed the warriors with battles.

Hold hackathon periodically. This will help to build a culture that embrace innovation and creation. People will be motivated by their peers — “gee, that bustard can write such a beautiful framework for 24 hours, I gotta work hard”.

原文链接：https://medium.com/i-m-h-o/231d7499a75 （本内容由互联网资料整理而成）

如何把你的图标转换成 Web 字体

本文由大漠根据Martin Gittins的《How to turn your icons into a web font》所译，整个译文带有我们自己的理解与思想，如果译得不好或不对之处还请同行朋友指点。如需转载此译文，需注明英文出处：http://www.webdesignerdepot.com/2013/04/how-to-turn-your-icons-into-a-web-font，以及作者相关信息

——作者：Martin Gittins

——译者：大漠

我已经制为网站制作了一些优雅的图标，并且在新的网站和老的iPad上进行了测试。正常尺寸下，布局看看上去一切OK，但的将页面进行放大显示，我却发现我的icon变得模糊不清，但是基于文本的标题依然清晰可见。在具有Retina显屏的iPad上，这些图标看上去都不清晰，即使在没有进行任何放大的情况之下。

我第一想法就是创建双倍大小的Sprite图，然后通过css样式，设置他们只显示二分之一尺寸。虽然这样的方法让他们在Retina屏上显示的大小是正常的，一旦你开始放大屏幕，图标又变得模糊不清。但文本还是一样的清晰。

答案很明显，我需要把我的图标变成一个字体。

在这篇教程中，我们将看看如何使用一个免费的Web应用程序IcoMoon将矢量图转换成Web字体。然后在看看如何使用生成的字体通过css应用到Web页面中。

使用字体图标的优势

字体图标除了图像清晰度之外，比位图还有哪些优势呢。

- 适用性：一个图标字体比一系列的图像（特别是在Retina屏中使用双倍大小的图像）要小。一旦图标字体加载了，你的图标就会马上渲染出来，不需要下载一个图像。

- 可扩展性：图标字体可以用过font-size属性设置其任何大小。这使您能够随时输出不同大小的图标，然而，使用位图，你必须得为每个不同大小的图像输出一个不同文件。

- 灵活性：文字效果可以很容易地应用到你的图标上，包括颜色，阴影和翻转等效果。他们还可以在任何背景下显示。

- 兼容性：网页字体支持所有现代浏览器，包括IE低版本。详细兼容性可以点击这里。

我们开始吧！

创建一个图标字体

Symbol字体可以使用一个专用的字体创建应用程序，比如说Glyphs，但是一个专业的排版工具之外的需求或要求构建一个简单的图标字体，比如说间距和粗细这样的物理关系并不是非常重要。

目前为止，最简单的方法是使用Keyamoon制作的一个Web应用程序IcoMoon，可以解决字符转换成Web字体的所有麻烦。

这个Html5应用程序解决了创建字体文件和如何使用创建的图标字体的一切麻烦。IcoMoon附带了大量的图标，你也可以通过图标库添加更多的图标，其中大部分都可以免费使用（使用时请先查看他们的许可证）。如果你正在寻找如“文件下载”和“购物车”一样的图标，那么你会发现，使用标准的图标比你自己创建的要方便得多。

一步一步

1、准备你的插图

首先，你需要能创建矢量图标的程序，并且能够找到输出SVG格式，比如“Illustrator”或者“iNkscape”。

当你设计的时候，你可以使用任何你喜欢的颜色，但是图标必须是一个纯色。确保每个图标的尺寸大小是相同的。有一个图标更高或者更宽，会很难创建一个一致的字体。在这里，我们不得不减少飞艇图标的宽度，以便它匹配其他图标。

2、清理

仔细检查每一个图标，以确保它没有缺陷——细节在小尺寸上是完全的，当你放大的时候小的缺陷就能被发现。在所示图标，我需要删除参差不齐的路径。

在Illustrator中，使用Pathfinder工具统一层叠元素，减去前面元素，比如这些图标中的星星图标。

关键的原则是确保你的图标是可读的小尺寸。心可能的简化。

3、导成svg

现在，选择一个图标，并将它复制粘贴到一个新的文档场景中（例如200px X 200px）。缩放也是符合。你可能会发现它有一个基线尺寸的设置。使用彩色的图标，比如说在白色的背景中使用黑色的图标。

现在，选择菜单“文件”中“保存”，并选择将文件保存成“SVG”格式。使用默认的SVG设置。一旦你这样做，所有的图标，你可以创建一个Web字体。

4、导入到IcoMoon

打开IcoMoon Web app。导入一个图标，点击“Imort icons”按钮，然后选择您想要添加的SVG文件——您也可以一次添加多个文件。这些图标将会出现在“Your Custom Icons”区域中。如果他们是高亮的黄色显示，表示这些图标是你将要创建的图标字体。在这个例子中，你可以看到，我不仅导出我自己创建的图标，我还在“Mini-icons”中添加了别的图标。

5、从IcoMoon中导出字体

如果你想调整图标的位置、大小或旋转，你可以点击“Edit”按钮。可以使用“Save Copy”按钮来创建图片的变化（例如，一个镜像的衅标）。添加一个有意义的图标标记，因为这将被用来生成类名。

当你都准备好了，点击屏幕底部的“Font”按钮开始生成字体。这样你就可以指定哪个图标映射到哪个字符上，例如，如果你要设置一套六个旋转的球，你可以每这六个球分别指定字符：q、w、e、r、t和y。你也可以根据你自己的爱好选择一个字体的名字。你也可以调整字体的指标，除非你要设置你息定的字体和标准文本，不然你真的不需要担心这个。

6、下载字体文件

单击“Download”下载字体包到你的电脑上。他有一个字文件夹包含了字体本身（woff,eot,ttf格式），以及一个HTML示例页面和相应的CSS。甚至还有一个javascript文件和一个解决方法，如果你需要支持IE或IE7。

将font文件夹复制到你的网站，为你的项目添加字体。你需要从style.css文件中复制CSS样式，并粘贴到你网站的CSS文件中，但是我的方法是将它重命名为font.css，并保持他作为一个单独的CSS文件。你需要的时候在把这个CSS文件引入到你的HTML中。

<link rel="stylesheet" href="fonts.css" />

在CSS文件中你可以找到@font-face，你需要将URL路径修改成你本地的相对路径，或者你可以简单地把字体文件和你的样式放在同一个文件夹。

7、调用字体

正如你看到的样本文件index.html，有两种方法可以调用，一种是通字符（unicod或名称），另一种是通过类名。第一个例子使用了HTML5的data-icon自定义属性。

<div aria-hidden="true" data-icon="&#x67;"></div>

在这里，fs1类名用于设置字体的大小。这个“aria-hidden”属性有助于确保字符能屏幕阅读器读到。

第二种方法使用一个span元素：

<span aria-hidden="true"></span>

这个方法是非常有用的，你的字符可以与文本在内联中一起显示。

如果你想让图标具有链接功能，你可以将放在一个链接中：

<a href="
http://www.yoursite.com
"  data-icon="&#x73;"></a>

在这里，我添加了一个iconlink类名，并设置了一个悬浮效果：

a.iconlink {
font-family: 'youriconfont';
text-decoration:none;
color: #666666;
}
a.iconlink:hover {
text-decoration:none;
color: #999999;
}

8、先进的想法

正如我们刚刚悬停状下改变图标颜色一样，我们可以使用的颜色属性和字体属性修改图标。你可以设置其他属，比如文本阴影和透明度，来设置图标的效果。

尝试它，我保证你永远不会再次使用位图图标。

你有试过使用字体图标吗？你找到了？如果你有好的想法请在评论中给我们留言，让我们知道。

如需转载烦请注明出处：

英文原文：http://www.webdesignerdepot.com/2013/04/how-to-turn-your-icons-into-a-web-font/

中文译文：http://www.w3cplus.com/css3/how-to-turn-your-icons-into-a-web-font.html

原文链接：http://www.w3cplus.com/css3/how-to-turn-your-icons-into-a-web-font.html （本内容由互联网资料整理而成）