参考高性能javascript
理解浏览器UI线程 用于执行javascript和更新用户界面的进程通常被称为浏览器UI线程 UI线程的工作机制可以理解为一个简单的队列系统,队列中的任务按顺序执行
<button onclick="handleClick()">click</button> <script type="text/javascript"> function handleClick() { var div = document.createElement('div'); div.innerHTML = "aaa"; document.body.appendChild(div); }
在上面的例子中当按钮被点击的时候,它触发UI线程创建的两个任务并且添加到队列中,更新被点击按钮的UI和执行javascript代码(handleClick) 假如此刻队列是空闲的(也就数存在用户操作无法添加到队列的情况) 第一个任务会立即被执行然后javascript代码被提取出来并且执行,在执行javascript代码的过程中创建了一个div,当js运行完,又触发了另一次的UI更新(显示创建的DIV)
由于js脚本运行时间的长短会导致新创建的任务无法被加入队列,所以有必要对脚本的执行时间进行限制或者在脚本执行的过程中让出一会浏览器的UI线程让新的任务添加到队列
理解定时器 参考我之前的一篇blog 定时器相关setTimeout setInterval requestAnimationFrame
通过上面的一些点 定时器可以称为长时间运行脚本的跨浏览器的解决方案
使用定时器取代循环
- 处理过程是否必须同步
- 数据是否一定要按顺序处理
如果上面的两个回答都是否,那我们就可以使用定时器来代替循环了
function processArray(items,process,callback) { var todo = items.concat(); setTimeout(function(){ process(todo.shift()); if(todo.length > 0) { setTimeout(arguments.callee,25); } else { callback(items); } },25); }
上面的函数接受一个数组项 处理数组项的函数 回调函数 通过自调用的方式能保证队列中不会有缺失的代码片段也保证了代码执行的间隔至少是25ms
上面方式的缺陷是延长了处理数组的总时长(加入了时间间隔) 但是给出了一定时间间隔留给了UI线程,能避免代码长时间运行导致浏览器锁死
改进版的定时器取代循环
function timedProcessArray(items,process,callback) { var todo = items.concat(); setTimeout(function(){ var start = +new Date(); // 加号将Date对象转换成数字 do { process(todo.shift()); } while(todo.length > 0 && (+new Date() - start) < 50) //当前处理的时间间隔短 就不进行循环的分解 if(todo.length > 0) { setTimeout(arguments.callee,25); } else { callback(); } }); }
分割任务
将一个任务划分成多个原子任务,使用定时器去执行相应的原子任务,思路跟上面的循环处理数组的思路相同
function multistep(steps,args,callback) { var tasks = steps.concat(); setTimeout(function(){ var task = tasks.shift(); task.apply(null,args || []); if(tasks.length > 0) { setTimeout(arguments.callee,25) } else { callback(); } },25) } //这里的args必须是数组 因为我们使用的apply
了解web workers 通过定义一个包含worker代码的文件 通过new Worker("js文件url")就能在网页中创建一个新的线程,它通过特定的方法与主线程进行信息的交互,并且不影响主线程的执行
主线程 var worker = new Worker("worker.js"); //这段代码会异步的下载worker.js 当worker.js下载并执行完成后就启动了这个worker worker.onmessage = function(event) { alert(event.data); } //网页代码接收数据的处理函数 worker.postMessage("hello");//网页代码向worker发送数据 worker.js self.onmessage = function(event) { self.postMessage(event.data+"world"); } //worker接收网页代码数据的处理函数
只有特定的数据可以通过postMessage传递 例如原始值(字符串 数字 布尔值 null undefined ) 也可以传递Object和Array实例 数据会被序列化出入和传出worker 然后被反序列化
用处: web workers适用于处理纯数据或者与浏览器UI无关的长时间运行的脚本
编程实践
1.避免双重求值
eval() Function() setTimeout() setInterval() 允许在程序中提取一个包含代码的字符串,然后动态的的执行它 例如eval("num1 + num2") 这段代码会先对eval进行求值 然后对字符串中的内容进行求值返回结果 应该避免使用eval 和Function() 并且在使用setTimeout 和 setInterval的时候传入函数体
2. 使用object 和 array 直接量
3 避免重复的工作 例如跨浏览器的事件绑定函数
function addListenEvent(eventTarget,eventType,handler) { if(eventTarget.addEventListener) { eventTarget.addEventListener(eventType,handler,false); } else if(eventTarget.attachEvent) { eventType = "on" + eventType; eventTarget.attachEvent(eventType,handler); } else { eventTarget["on"+eventType] = handler; } }
这个方法在每次调用的时候都进行了浏览器的检测,有重复的操作
我们可以通过延时加载和条件预加载的方式去优化这个函数
1延时加载 函数在使用之前不进行任何的操作,在调用后在决定根据情况去改变这个函数的状态 这种方式在第一次运行的时候会消耗较长的时间,适合当一个函数不会被立即调用的情况下使用 使用延时加载改写上面的函数
function addListenEvent(eventTarget,eventType,handler) { if(eventTarget.addEventListener) { addListenEvent = function(eventTarget,eventType,handler) { eventTarget.addEventListener(eventType,handler,false); } } else if(eventTarget.attachEvent) { addListenEvent = function(eventTarget,eventType,handler) { eventType = "on" + eventType; eventTarget.attachEvent(eventType,handler); } } else { addListenEvent = function(eventTarget,eventType,handler) { eventTarget["on"+eventType] = handler; } } }
2条件预加载 这种方式需要在脚本加载的时候就进行检测,适用于一个函数马上就需要被调用并且在整个页面的生命周期被频繁使用的场景
var addListenEvent = document.body.addEventListener ? function(eventTarget,eventType,handler) { eventTarget.addEventListener(eventType,handler,false); } : function(eventTarget,eventType,handler) { eventType = "on" + eventType; eventTarget.attachEvent(eventType,handler); };
使用速度快的运算方式
例如在判断奇数偶数的时候 通过与1进行按位与的来代替取余操作能提高性能
使用原生的方法
小记 ~25 = -26
25的二进制形式为 11001 取反的二进制形式为 00110 左边 右边 因为~x+1 = -x 所以右边~26 + 1 = 00110