《高性能javascript》一书要点和延伸(下)
第六章 快速响应的用户界面
本章开篇介绍了浏览器UI线程的概念,我也突然想到一个小例子,这是写css3动画的朋友都经常会碰到的一个问题:
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
div{width:50px; height:50px; background:yellow;}
.act{width:100px;transition:width 0.5s;}
</style>
</head>
<body>
<div class="act"></div>
<button>click me</button>
<script>
var btn = document.querySelector('button');
var div = document.querySelector('div'); btn.onclick = function(){
div.className = '';
div.className = 'act';
}
</script>
</body>
如代码所示,我们希望点击按钮的时候,div能通过移除class瞬间变回50px,然后再给其加回class来触发动画(0.5秒内,宽度由50px延伸到100px),
不过这段代码的执行效果是——没有效果(录屏软件在win10下有点兼容bug,鼠标都偏移了):

其解决方案却也简单——套上一个setTimeout即可:
btn.onclick = function(){
div.className = '';
setTimeout(function(){
div.className = 'act';
}, 0)
}
执行如下:

原理是,我们通过 setTimeout,将div的第一次UI事件得以优先执行,而非放到 div.className = 'act' 的后方执行。
在用户点击按钮(未加setTimeout时的代码)的时候其实发生了这样的事情:
⑴ UI事件——更新按钮的UI,让用户能“看到”它被点击了。同时把回调事件放入事件队列。
⑵ JS事件A——执行回调事件,先执行首行的 div.className = '' ,移除div的类名,这时候会生成一个UI事件A(重渲染div)放入事件队列中等候空闲。
⑶ JS事件B——继续执行回调事件,给div加上名为“act”的类,这时候依旧又生成了一个UI事件B(重新渲染div)并放入队列中等候。
⑷ UI事件A——鉴于浏览器的UI线程已不存在任何执行中的任务(回调已执行完毕,处空闲状态),那么事件队列中的UI事件便开始以FIFO的形式进入UI线程来被处理。
⑸ UI事件B——跟UI事件A是一样的,即根据div的当前样式来做渲染处理。

(制图的时候没记清楚,把事件A/B写为事件1/2了,大家自行脑部替换吧)
而加上 setTimeout 之后则变为:
⑴ UI事件——更新按钮的UI,让用户能“看到”它被点击了。同时把回调事件(JS事件A和B)放入事件队列。
⑵ JS事件A——执行回调事件,先执行首行的 div.className = '' ,移除div的类名,这时候会生成一个UI事件A(重渲染div)放入事件队列中等候空闲。
⑶ UI事件A——由于JS事件B带延迟特性,故先放行事件队列后方的队列成员,让UI事件A先执行。这时候div失去了类,依据当前有效样式,将其渲染为50px宽度。
⑷ JS事件B——继续执行回调事件,给div加上名为“act”的类,依旧又生成了一个UI事件B(重新渲染div)并放入队列中等候。
⑸ UI事件B——div加上了类,故根据当前的有效样式,将其渲染为100px宽度。
⑹ UI事件C——鉴于div的宽度发生了变化,故触发动画事件。


综上我们稍微了解了浏览器UI线程(主线程)的一个工作流程,但常规浏览器并不仅仅只有一个线程在运作,其主要线程可归类为:

另外我们回过头看看 setTimeout/setInterval 这两个时间机制,它们实际上只是把回调事件放入队列中以“礼让”的状态等候,若后方有事件成员则礼让给后方先出队。
这点跟 node 的 setImmediate 是一样的,不同的是 setImmediate 不受延时限制,当event loop当轮结束时则执行。
那么给 setTimeout 配置一个数值为 0 的延时,是否就实现了 setImmediate 的功能呢?答案是否定的,在书中“定时器精度”一节有提及,js的时间机制是不精准的,它受到了系统/客户端定时器分辨率(如window下为15毫秒)的影响,所以会存在毫秒级的偏差。
不过这里需要了解的事实是—— JS中的时间机制并不是一个纯粹的异步事件,它依旧走的UI单线程,只是当事件队列为空时候才“见缝插针”到UI线程中去执行,营造出了一种“异步”的假象。
顺道也在这里提一提,JS中真正走了异步的应该是下面几个事件:
1. Ajax
2. event(如监听click)
3. requestAninmationFrame
4. WebSQL、IndexDB
5. Web Worker
6. postMessage

第七章 Ajax
“动态脚本注入”一节介绍了JSONP原理——前后端约定好一个回调名,让script请求的回包数据包裹在该回调名内,客户端拉取到该回包时通过 eval 来即时触发回调函数。
除了 JSONP 我们还是能有许多跨域通信的实现,可参照我的旧文章。
本章提及的“Multipart XHR”其实是域名收敛的一种实现,比如下面的单条请求就一口气返回了对应的多个脚本资源:
不过这里提及了一个有趣的处理——若MXHR响应的出局非常多,等到全部数据返回过来才做处理有点慢,我们可以通过监听XHR的 readyState 来提前处理。
当 readyState 为3时其实表示客户端已经开始下载回包(含报头)了,这时候我们就可以通过轮询来提前处理(主要是拆开、提取回包中的合并资源):
var req = new XMLHttpRequest();
var getLatestPacketInterval, lastLength = 0;
req.open('GET', 'rollup_images.php', true);
req.onreadystatechange = readyStateHandler;
req.send(null);
function readyStateHandler{
if (req.readyState === 3 && getLatestPacketInterval === null) {
// 开始轮询
getLatestPacketInterval = window.setInterval(function() {
getLatestPacket();
}, 15);
}
if (req.readyState === 4) {
// 停止轮询
clearInterval(getLatestPacketInterval);
// 获取最后一个数据包
getLatestPacket();
}
}
function getLatestPacket() {
var length = req.responseText.length;
var packet = req.responseText.substring(lastLength, length);
processPacket(packet);
lastLength = length;
}
接着提及的 Beacons 其实是一种 image ping 技术,常规也是用来跨域通信的(主要用于统计)。不过这里提及的服务端响应处理还是值得一看:
1. 服务端返回真实的图片数据,客户端可通过判断图片宽度来了解状态;
2. 若客户端无须了解服务端状态,则返回不带消息正文的204即可。

第八章 编程实践
本章提供一些建议,让读者能避免使用一些性能上不太好的编程习惯。
1. 避免双重求值
js中提供了某些接口允许你输入字符串来编译执行,eval是其中最耳熟能详的方法了。除却eval还包括如下方法:
⑴ 以 new Function() 的形式来创建函数; ⑵ 让 setTimeout/setInterval 执行字符串。
这些方法都会让js引擎先做字符串解析,再做求值处理,导致了双重求值,性能开销会变大,所以常规不建议这么来使用。
如果不得已要解析服务端返回的大规模json字符串,可以开个 Web Worker 做异步处理。
2. 使用 Object/Array 直接量
//不推荐
var o = {};
o.a = 1;
o.b = 2; //推荐
var o = {
a: 1,
b: 2
} //不推荐
var arr = new Array();
arr[0] = 1;
arr[1] = 2; //推荐
var arr = [1, 2];
使用“推荐”的直接量处理来定义一个对象将获得更快的执行速度也有助减小文件体积。
3. 避免重复工作
大部分开发都会忽略的地方,即封装在某个方法中的功能分支判断,在每次方法被调用的时候都会重新做一次冗余判断:
function addHandler(target, eventType, handler){
if(target.addEventListener){
target.addEventListener(eventType, handler, false)
} else {
target.attachEvent('on'+eventType, handler)
}
}
如上述的事件绑定接口在每次被调用时,都需要做一次事件添加句柄判断。
解决该问题的方法是内部重写接口(延迟加载):
function addHandler(target, eventType, handler){
if(target.addEventListener){
addHandler = function(target, eventType, handler){
target.addEventListener(eventType, handler, false)
}
} else {
addHandler = function(target, eventType, handler){
target.attachEvent('on'+eventType, handler)
}
}
addHandler(target, eventType, handler); //延迟加载
}
4. 用速度最快的部分
⑴ 位操作
JS的位操作会相比其它的计算处理快得多,若妥当使用可以提升脚本执行速度。
例如常规我们会以 if(i%2) 来判断 i 是奇数或偶数,若把条件更改为 if(i & 1) 会得到一样的结果,不过速度快了50%。
本节也提及了“位掩码”的使用,是种有趣的逻辑识别处理。
打个比方,在手Q web 页面开发中,我们会通过一个“_wv”的参数来知会客户端(手Q)是否显示返回按钮、分享按钮,以及如何显示分享面板等功能。
关于这个参数有类似这样的映射:

当我们给 url 的 _wv 参数取值 21 (即 16 + 4 + 1)的时候,手Q针对该参数的值来隐藏返回按钮和底栏,并配置分享面板中不出现空间的选项。
而常规我们在写JS时,可以利用位掩码来实现相同处理。
我们依旧使用上方的映射表,不过不再使用累加处理,而是使用位处理:
var wv = 16 | 4 | 1; //识别处理
if(wv & 1){
//隐藏返回按钮
}
if(wv & 2){
//隐藏分享按钮
}
...//省略4和8的分支
if(wv & 16){
//分享面板隐藏空间分享
}
⑵ 原生方法
即多使用原生的 Math 接口来实现复杂的计算,多使用原生的选择器(如 querySelector)来选择DOM。
至于后面两章主要提及的是前端构建和检测工具,其中部分技术还是淘汰掉的东西就不赘述了。共勉~

《高性能javascript》一书要点和延伸(下)的更多相关文章
- 《高性能javascript》一书要点和延伸(上)
前些天收到了HTML5中国送来的<高性能javascript>一书,便打算将其做为假期消遣,顺便也写篇文章记录下书中一些要点. 个人觉得本书很值得中低级别的前端朋友阅读,会有很多意想不到的 ...
- 《高性能javascript》 领悟随笔之-------DOM编程篇(二)
<高性能javascript> 领悟随笔之-------DOM编程篇二 序:在javaSctipt中,ECMASCRIPT规定了它的语法,BOM实现了页面与浏览器的交互,而DOM则承载着整 ...
- 高性能JavaScript 编程实践
前言 最近在翻<高性能JavaScript>这本书(2010年版 丁琛译),感觉可能是因为浏览器引擎的改进或是其他原因,书中有些原本能提高性能的代码在最新的浏览器中已经失效.但是有些章节的 ...
- 高性能javascript学习笔记系列(4) -算法和流程控制
参考高性能javascript for in 循环 使用它可以遍历对象的属性名,但是每次的操作都会搜索实例或者原型的属性 导致使用for in 进行遍历会产生更多的开销 书中提到不要使用for in ...
- 高性能JavaScript 达夫设备
前言 在<高性能JavaScript>一书的第四章算法和流程控制中,提到了减少迭代次数加速程序的策略—达夫设备(Duff's device).达夫设备本身很好理解,但是其效果是否真的像书中 ...
- 高性能JavaScript(您值得一看)
众所周知浏览器是使用单进程处理UI更新和JavaScript运行等多个任务的,而同一时间只能有一个任务被执行,如此说来,JavaScript运行了多长时间就意味着用户得等待浏览器响应需要花多久时间. ...
- 【JavaScript】【译】编写高性能JavaScript
英文链接:Writing Fast, Memory-Efficient JavaScript 很多JavaScript引擎,如Google的V8引擎(被Chrome和Node所用),是专门为需要快速执 ...
- 编写高性能JavaScript【转】
英文链接:Writing Fast, Memory-Efficient JavaScript 很多JavaScript引擎,如Google的V8引擎(被Chrome和Node所用),是专门为需要快速执 ...
- 《高性能javascript》学习总结
本文是学习<高性能javascript>(Nichols C. Zakes著)的一些总结,虽然书比较过时,里面的知识点也有很多用不上了,但是毕竟是前人一步步探索过来的,记录着javascr ...
随机推荐
- 关于Unity3D自定义编辑器的学习
被人物编辑器折腾了一个月,最终还是交了点成品上去(还要很多优化都还么做). 刚接手这项工作时觉得没概念,没想法,不知道.后来就去看<<Unity5.X从入门到精通>>中有关于 ...
- Xshell 连接CentOS服务器解密
平台之大势何人能挡? 带着你的Net飞奔吧!http://www.cnblogs.com/dunitian/p/4822808.html Xshell生成密钥key(用于Linux 免密码登录)htt ...
- InnoDB关键特性学习笔记
插入缓存 Insert Buffer Insert Buffer是InnoDB存储引擎关键特性中最令人激动与兴奋的一个功能.不过这个名字可能会让人认为插入缓冲是缓冲池中的一个组成部分.其实不然,Inn ...
- 【NLP】前戏:一起走进条件随机场(一)
前戏:一起走进条件随机场 作者:白宁超 2016年8月2日13:59:46 [摘要]:条件随机场用于序列标注,数据分割等自然语言处理中,表现出很好的效果.在中文分词.中文人名识别和歧义消解等任务中都有 ...
- Nested Loops join时显示no join predicate原因分析以及解决办法
本文出处:http://www.cnblogs.com/wy123/p/6238844.html 最近遇到一个存储过程在某些特殊的情况下,效率极其低效, 至于底下到什么程度我现在都没有一个确切的数据, ...
- 解决使用IE8打开ADFS 3.0登录页面
系统上线前一天,发现客户竟然有XP系统和2003系统,这些系统都不能访问外网.测试时,客户端是IE8,打开我们系统ADFS的登录页面,一直在Loading,无法打开,也不报错.后来通过fiddler跟 ...
- H3 BPM社区:流程开发者的学习交流平台
企业上市有上市流程,融资扩充有融资流程,项目招投标有招投标流程,部门领导选拔有晋升流程,员工请假休假有请假流程,早起上班梳洗有符合自己习惯的流程--生活处处是流程,流程无处不在.但从信息化建设来说,企 ...
- [Android]使用Dagger 2进行依赖注入 - Producers(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/6234811.html 使用Dagger 2进行依赖注入 - P ...
- SVN版本冲突,导致出现Files 的值“ < < < < < < < .mine”无效
只要根据错误提示,找到相应文件夹下的\obj\Debug文件夹下的 相应名字.csproj.FileListAbsolute.txt, 打开并删除含有'<<<<<< ...
- [转]Java常用工具类集合
转自:http://blog.csdn.net/justdb/article/details/8653166 数据库连接工具类——仅仅获得连接对象 ConnDB.java package com.ut ...