《高性能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 ...
随机推荐
- Visual Studio Code 代理设置
Visual Studio Code (简称 VS Code)是由微软研发的一款免费.开源的跨平台文本(代码)编辑器,在十多年的编程经历中,我使用过非常多的的代码编辑器(包括 IDE),例如 Fron ...
- setAttribute()
●节点分为不同的类型:元素节点.属性节点和文本节点等. ●getElementById()方法将返回一个对象,该对象对应着文档里的一个特定的元素节点. ●getElementsByTagNam ...
- salesforce 零基础学习(六十二)获取sObject中类型为Picklist的field values(含record type)
本篇引用以下三个链接: http://www.tgerm.com/2012/01/recordtype-specific-picklist-values.html?m=1 https://github ...
- 04.LoT.UI 前后台通用框架分解系列之——轻巧的弹出框
LOT.UI分解系列汇总:http://www.cnblogs.com/dunitian/p/4822808.html#lotui LoT.UI开源地址如下:https://github.com/du ...
- x:bind不支持样式文件 或 此Xaml文件必须又代码隐藏类才能使用{x:Bind} 解决办法
这两天学习UWP开发,发现一个很有趣的问题,就是我题目中的描述的. 我习惯了在ResourceDictionary中写样式文件,但是发现用x:Bind时会有问题 如果是写在Style里,则提示 “x: ...
- nginx源码分析之模块初始化
在nginx启动过程中,模块的初始化是整个启动过程中的重要部分,而且了解了模块初始化的过程对应后面具体分析各个模块会有事半功倍的效果.在我看来,分析源码来了解模块的初始化是最直接不过的了,所以下面主要 ...
- WebGIS中等值线前端生成绘制简析
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景 等值线是GIS制图中常见的功能,一般有两种思路:一种是先进行插 ...
- 消息队列性能对比——ActiveMQ、RabbitMQ与ZeroMQ(译文)
Dissecting Message Queues 概述: 我花了一些时间解剖各种库执行分布式消息.在这个分析中,我看了几个不同的方面,包括API特性,易于部署和维护,以及性能质量..消息队列已经被分 ...
- 解决使用IE8打开ADFS 3.0登录页面
系统上线前一天,发现客户竟然有XP系统和2003系统,这些系统都不能访问外网.测试时,客户端是IE8,打开我们系统ADFS的登录页面,一直在Loading,无法打开,也不报错.后来通过fiddler跟 ...
- SQL Server中SELECT会真的阻塞SELECT吗?
在SQL Server中,我们知道一个SELECT语句执行过程中只会申请一些意向共享锁(IS) 与共享锁(S), 例如我使用SQL Profile跟踪会话86执行SELECT * FROM dbo.T ...