浏览器UI线程

用于执行JavaScript代码和更新界面的进程被称为 “浏览器UI线程” 。

UI线程的工作基于一个简单的队列系统,任务会被保存到队列中直到线程空闲,一旦空闲队列就被重新提取出来运行。这些任务要么是运行JavaScript代码,要么是执行UI更新,包括重绘和重排。浏览器每一次执行JavaScript代码或者响应用户事件,都可能会导致一个或多个任务加入队列。

function handleClick(){
  //创建div元素
  var div = document.createElement("div");
  //div元素添加内部文本
  div.innerHTML = "Clicked!";
  //将div元素append到body中
  document.body.appendChild(div);
}

handleClick()时,会触发UI线程创建两个任务并添加到队列中:

* 第一个任务就是更新按钮的UI,浏览器需要改变按钮外观表明它被点击了;

* 第二个任务就是执行`handleClick()`方法中的代码。但是在运行过程中,创建了一个新的 `<div>` 元素并把它追加在 `<body>` 元素的末尾,这实际上引发了另一次UI变化,一个新的UI更新任务呗添加到队列中。当方法运行完毕后,UI还会在更新一次。

- 所有的UI线程任务执行完毕,线程进入空闲状态是最理想的。因为用户所有的交互都会立即出发UI更新,而事实上,大多数浏览器在JavaScript运行时,会停止把新任务添加到UI线程的队列中,也就是说JavaScript任务必须尽快结束,以避免对用户体验造成不良影响。

浏览器限制

浏览器对JavaScript任务的运行时间进行了限制,这很有必要,它能确保了不让恶意代码通过密集的操作锁住用户的浏览器。

此类限制分为两种:`调用栈大小`以及`长时间运行(long-running)脚本限制`。

长时间脚本限制也称为“长时间运行脚本定时器”或“失控脚本定时器”,顾名思义,它的原理就是浏览器记录脚本的运行时间,当达到一定限度时终止它并弹出提示框,这是可用性的问题。

不同浏览器检测长时间运行脚本的方法也各不相同:

* IE注册表中,设置默认限制500万条语句

(HKEY_CURRENT_USER\Software\Microsoft\InternetExplorer\StylesMaxScriptStatements)

* Firfox的默认限制为10秒(dom.max_script_run_time)

* Safari的默认限制为5秒(Disable Runnaway JavaScript Timer)

* Opera由于自身的架构,没有长运行脚本限制

* Chrome也没有单独的长运行脚本限制,而是依赖其通过崩溃检测系统来处理此类问题

多久才算“太久”

即便主流浏览器有长运行脚本限制的机制,但并不意味着你也允许它这样做。

用JavaScript 的创造者 Brendan Eich 的话,如果你的js运行了整整几秒钟,那么很可能你做错了什么。

那么多久才算合适呢?

Jakob Nielsen` 在其著作《可用性工程》(Morgan Kaufrnann,1944)中指出,如果界面超过100毫秒未响应,用户会感觉自己与界面失去联系。

当脚本执行时,UI不随用户交互而更新。也就是说,当运行JavaScript代码的这段时间内,用户交互行为引发的UI更新会被浏览器自动跳过。因此,在JavaScript脚本运行期间,用户点击一个按钮,可能无法看到它已经被按下的样式,界面呈现“挂起”或“假死”的状态。

使用定时器让出时间片段

在大型应用的Web界面的实现中,总会有一些复杂的JavaScript任务不能在100毫秒或更短的时间内完成。此时,最理想的方法是停止执行JavaScript脚本,让出UI线程的控制权,使得UI可以更新,然后再继续执行JavaScript。

在JavaScript中通过`setTimeout()`和`setInterval()`方法创建定时器。

function saySomething(){
  oneMethod();
  setTimeout(function(){
    console.info("hello");
  },250);
  anotherMethod();
}

而定时器是创建(调用`setTime()`)后当即开始计时,那么定时器代码有可能在`saySomething()`方法处理完成之前执行完成。

如案例所示,如果`anotherMethod()`方法的执行时间超过250毫秒,那么定时器代码会抢先进入UI执行队列。

定时器的精度

JavaScript 定时器延迟通常不太准确,相差大约几毫秒,仅仅因为你指定延迟为250毫秒,并不能意味着settimeout调用之后过250毫秒时精确地加入队列。因此,所以我们应该避免定时器用于测量实际时间。

其中,Windows系统中定时器分辨率为15毫秒,这意味着, __在Windows系统中,设置定时器的时间间隔不能小于15毫秒,否则会导致浏览器锁定。建议最少25

使用定时器处理数组

一种造成长时间运行脚本的起因是耗时过长的循环,通过把循环的工作分解到一系列定时器中执行,是常见的优化方法。

var items = [], //数组
len = items.length; //数组的长度
//遍历数组中的元素作为参数,执行proccess方法
for (var i=0;i<len;i++) {
  process(item[i]);
}

这类循环执行时间过长的原因,不外乎是`process()`方法的处理太过复杂,或者是items数组的长度太长

如果items数组不需要按顺序处理,并且`process()`方法的处理过程不需要同步,则可以通过定时器来分解任务,即异步代码模式:

var items = []; //原数组
var todo = items.concat(); //克隆原数组到todo
setTimeout(function(){
//获取todo数组的第一个元素,并返回删除第一个元素后的数组
procces(todo.shift());
if(todo.length > 0){
//arguments.callee表示当前执行的匿名函数
setTimeout(arguments.callee,25);
}else{
return items;
}
},25);

可以进一步封装,以便多出重用

var items = []; //原数组
function efficiencyLoopFn(items,process,callback){
var todo = items.concat(); //克隆原数组到todo
setTimeout(function(){
//获取todo数组的第一个元素,并返回删除第一个元素后的数组
process(todo.shift());
if(todo.length > 0){
//arguments.callee表示当前执行的函数
setTimeout(arguments.callee,25);
}else{
callback(items);
}
},25);
}

还有一个分割任务,如果一个函数运行时间太长,可以将其分成一系列更小的步骤,把每个独立的方法放在定时器中调用。

function saveDocument(id){
openDocument(id); //进入
writeText(id); //写入
closeDocument(id); //关闭i
updateUI(id); //更新界面
}
// 改为
function saveDocument(id){
var tasks = [openDocument,writeText,closeDocument,updateUI];
setTimeout(function(){
//执行下一个任务
var task = tasks.shift();
task(id);
//检查是否还有其他任务
if(tasks.length >0){
setTimeout(arguments.callee,25);
}else{
callback();
}
},25);
}

定时器与性能

虽然可以通过定时器提升JavaScript代码的性能,但过度使用也会对性能造成负面影响。等上一个定时器结束后再创建新的定时器不会导致性能问题,但同时有多个重复的定时器创建,则会发生性能问题。因为只有一个UI线程,而所有的定时器都在争夺运行时间。

Web Workers

在Web Workers出现之前,没有办法在浏览器UI线程之外运行代码,而Web Workers API能使代码运行且不占用浏览器UI线程的时间。这意味着,每个新的Workers都在自己的线程中运行代码,不会影响其他Worker的运行代码,并且不会影响浏览器UI。

Web Workers API已经作为HTML5标准的规范被Firefox、Chrome、Safari浏览器支持。

Worker运行环境(Woker  Environment)

* navigator`对象,包括四个属性appName、appVersion、user Agent和platform;

* location`对象(与window.location相同,只不过所有属性是只读的)

* `self`对象,指向全局的worker对象;

* `importScript()`方法,用来加载Worker所用到的外部JavaScript文件;

* 以及所有的ECMAScript对象(诸如:Object、Array、Date等);

* `XMLHttpRequest构造器`;

* `setTimeout()`和`setInterval()`方法;

* 停止WebWorker运行的`close()`方法;

由于Web Workers拥有独立的运行环境,因此我们需要在一个完全独立的JavaScript文件中编写Worker中运行的代码,然后创建Woker线程中引用它。

var worker = new Worker("code.js");

当以上代码执行时,会创建一个新的线程和一个新的Worker运行环境,`code.js`文件会被异步下载,等文件下载并执行完成后,启动此Woker

与Worker通信(Worker Communication)

页面代码通过事件接口与Woker进行通信,通过`postMessage()`方法给Worker传递数据,通过`onmessage`事件接收信息。

var worker = new Worker("code.js");
  worker.onmessage = function(event){
  console.info(event.data);
};
worker.postMessage("hello");

在Woker代码中,同样也是通过`onmessage`事件接收信息,通过`postMessage()`方法发送信息。

self.onmessage = function(event){
  console.info(event.data);
};
self.postMessage("hello too!");

上述是网页和Worker通信的唯一途径,并且只有特定类型的数据能通过`postMessage()`传递:原始值(字符串、数字、boolean、null和undefined)和Object和Array的实例。数据传入传出Worker时,会经历序列化和反序列化

加载外部文件(Loading External Files)

Worker通过`importScript()`方法加载外部一个或多个JavaScript文件。

`inportScript()`的调用过程是阻塞式的,直到素有文件加载并执行完成后,Worker中的脚本才会继续运行。但由于Worker在UI线程之外运行,所以不用担心这种阻塞会影响UI响应。

importScript('code1.js','code2.js');
  self.onmessage = function(event){
  console.info(event.data);
};

实际应用(Practical Uses)

Web Worker适合处理纯数据的、与浏览器UI无关的长时间运行(超过100毫秒)的脚本,比如:

编码/解码大字符串;

图片或视频处理时的复杂数学运算;

大数据排序

小结:

JavaScript 和用户界面更新在同一个进程中进行,因此一次只能处理意见事情,这意味着当JavaScript 代码正在运行时,用户界面不能响应输入,反之亦然,所以需要搞笑的管理UI线程,

任何JavaScript 任务都不应当超过 100 毫秒,过长的运行时间会导致 UI 更新出现明显的延迟,从而对用户体验产生负面影响。

定时器可以安排代码延迟执行,可以分解长时间运行的脚本。

web workers 是 新版浏览器支持的特性,允许你在UI线程之外执行JavaScript代码,从而避免锁定UI

高性能JavaScript(快速响应的用户界面)的更多相关文章

  1. 高性能javascript学习笔记系列(5) -快速响应的用户界面和编程实践

    参考高性能javascript 理解浏览器UI线程  用于执行javascript和更新用户界面的进程通常被称为浏览器UI线程  UI线程的工作机制可以理解为一个简单的队列系统,队列中的任务按顺序执行 ...

  2. 高性能JavaScript笔记二(算法和流程控制、快速响应用户界面、Ajax)

    循环 在javaScript中的四种循环中(for.for-in.while.do-while),只有for-in循环比其它几种明显要慢,另外三种速度区别不大 有一点需要注意的是,javascript ...

  3. 《高性能javascript》学习总结

    本文是学习<高性能javascript>(Nichols C. Zakes著)的一些总结,虽然书比较过时,里面的知识点也有很多用不上了,但是毕竟是前人一步步探索过来的,记录着javascr ...

  4. 高性能javascript笔记

    ----------------------------------------------------------- 第一章 加载和执行 ------------------------------ ...

  5. 高性能JavaScript读书笔记

    零.组织结构 根据引言,作者将全书划分为四个部分: 一.页面加载js的最佳方式(开发前准备) 二.改善js代码的编程技巧(开发中) 三.构建与部署(发布) 四.发布后性能检测与问题追踪(线上问题优化) ...

  6. 《高性能JavaScript》 实用指南

    By XFE-堪玉 阅读<高性能javascript>后,对其内容的一个整理和精简 加载与执行 将script标签放在body结尾标签上面 控制script标签数量(每一次script解析 ...

  7. 《高性能javascript》阅读摘要

    最近在阅读这本Nicholas C.Zakas(javascript高级程序设计作者)写的最佳实践.性能优化类的书.记录下主要知识. 加载和执行 脚本位置 放在<head>中的javasc ...

  8. 高性能javascript学习笔记系列(1) -js的加载和执行

    这篇笔记的内容主要涉及js的脚本位置,如何加载js脚本和脚本文件执行的问题,按照自己的理解结合高性能JavaScript整理出来的 javascript是解释性代码,解释性代码需要经历转化成计算机指令 ...

  9. 【读书笔记】读《高性能JavaScript》

    这本<高性能JavaScript>讲述了有关JavaScript性能优化的方方面面,主要围绕以下几个方面: 1> 加载顺序 2> 数据访问(如怎样的数据类型访问最快,怎样的作用 ...

  10. 高性能javascript学习总结(2)--DOM编程

    我们知道,对DOM的操作都是非常的耗性能的,那么为什么会耗性能呢?      文档对象模型(DOM)是一个独立于语言的,使用 XML和 HTML 文档操作的应用程序接口(API).在浏览器中,主要与 ...

随机推荐

  1. python3.6使用f-string来格式化字符串

    这里的f-string指的是以f或F修饰的字符串,在字符串中使用{}来替换变量,表达式和支持各种格式的输出.详细的格式化定义可以看官方文档 >>> a, b = 30, 20 > ...

  2. [Leetcode]495.提莫攻击

    题目: 在<英雄联盟>的世界中,有一个叫 "提莫" 的英雄,他的攻击可以让敌方英雄艾希(编者注:寒冰射手)进入中毒状态.现在,给出提莫对艾希的攻击时间序列和提莫攻击的中 ...

  3. 【liferay】4、liferay的权限体系

    liferay中有几个概念 1.user_ 表存放liferay的用户 2.usergroup 用户组 3.角色 4.组织,组织可以是站点的成员 5.站点 6.团队 liferay中所有的东西都被视为 ...

  4. POJ 2771

    #include <iostream> #include <string> #define MAXN 505 using namespace std; int _m[MAXN] ...

  5. Alpha冲刺(3/10)——追光的人

    1.队友信息 队员学号 队员博客 221600219 小墨 https://www.cnblogs.com/hengyumo/ 221600240 真·大能猫 https://www.cnblogs. ...

  6. 跨站脚本攻击(xss)理解

    一  概念 攻击者不直接攻击受害者,而是利用受害者登陆的网站中的漏洞,对受害者进行攻击. 二  危害 由于js本身的限制,并不能直接对用户的电脑造成侵害,但是可以: 1. 获取用户的storage,c ...

  7. GPS/轨迹追踪、轨迹回放、围栏控制

    折腾一个多月终于弄完了这个项目,起初都未曾接触GPS/轨迹追踪.轨迹回放.圈划围栏...等一些在百度地图或者Googel地图操作的一些业务,后端的业务相对来说简单点 cas单点登录,mongdb灵活的 ...

  8. ASP.NET Core 与 .NET Core 演变与基础概述

    https://github.com/dotnet/corehttps://github.com/aspnet/home 今天看到 .NET Core 的改名计划,感觉跨平台的时代快要来了,从之前的 ...

  9. java源码--HashMap扩容机制学习

    待完成 Java中hash算法细述 https://blog.csdn.net/majinggogogo/article/details/80260400 java HashMap源码分析(JDK8) ...

  10. curl 详解【转】

    原文:https://blog.csdn.net/lansesl2008/article/details/14523303 用途说明 curl命令是一个功能强大的网络工具,它能够通过http.ftp等 ...