在Web开发的时候经常会遇到浏览器不响应事件进入假死状态,甚至弹出“脚本运行时间过长“的提示框,如果出现这种情况说明你的脚本已经失控了,必须进行优化。

为什么会出现这种情况呢,我们先来看一下浏览器的内核处理方式:

浏览器的内核是多线程的,它们在内核制控下相互配合以保持同步,一个浏览器至少实现三个常驻线程:javascript引擎线程,GUI渲染线程,浏览器事件触发线程。

JavaScript引擎是基于事件驱动单线程执行的,JS引擎一直等待着任务队列中任务的到来然后加以处理,浏览器无论再什么时候都只有一个JS线程在运行JS程序。
GUI
渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。但需要注意
GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
事件触发线程,当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeOut、也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。
了解了浏览器的内核处理方式就不难理解浏览器为什么会进入假死状态了,当一段JS脚本长时间占用着处理机就会挂起浏览器的GUI更新,而后面的事件响应也被排在队列中得不到处理,从而造成了浏览器被锁定进入假死状态。另外JS脚本中进行了DOM操作,一旦JS调用结束就会马上进行一次GUI渲染,然后才开始执行下一个任务,所以JS中大量的DOM操作也会导致事件响应缓慢甚至真正卡死浏览器,如在IE6下一次插入大量的HTML。而如果真的弹出了“脚本运行时间过长“的提示框则说明你的JS脚本肯定有死循环或者进行过深的递归操作了。

Nicholas C. Zakas认为不论什么脚本,在任何时间、任何浏览器上执行都不应该超过100毫秒,否则一定要将脚本分解成若干更小的代码段。那么我们该如何来做呢:

第一步,优化你的循环,循环体中包含太多的操作和循环的次数过多都会导致循环执行时间过长,并直接导致锁死浏览器。如果循环之后没有其他操作,每次循环只处理一个数值,而且不依赖于上一次循环的结果则可以对循环进行拆解,看下面的chunk的函数:

function chunk(array, process, context) {
  setTimeout(function() {
    var item = array.shift();
    process.call(context, item);
    if (array.length > 0) {
      setTimeout(arguments.callee, 100);

    }
  }), 100);
}
chunk()函数的用途就是将一个数组分成小块处理,它接受三个参数:要处理的数组,处理函数以及可选的上下文环境。每次函数都会将数组中第一个对象取出交给process函数处理,如果数组中还有对象没有被处理则启动下一个timer,直到数组处理完。这样可保证脚本不会长时间占用处理机,使浏览器出一个高响应的流畅状态。

其实在我看来,借助JS强大的闭包机制任何循环都是可拆分的,下面的版本增加了callback机制,使可再循环处理完毕之后进行其他的操作。

function chunk(array,process,cbfun){
  var i=0,len = array.length; //这里要注意在执行过程中数组最好是不变的
  setTimeout(function(){
    process( array[i] , i++ ); //循环体要做的操作
    if( i < len ){
      setTimeout(arguments.callee,100);
    }else{
      cbfun(); //循环结束之后要做的操作
    }
  }, 100);
}
第二步,优化你的函数,如果函数体内有太多不相干但又要一起执行的操作则可以进行拆分,考虑下面的函数:

function dosomething(){
  dosomething1();
  dosomething2();
}
dosomething1和dosomething2互不相干,执行没有先后次序,可用前面提到的chunk函数进行拆分:

function dosomething(){
  chunk([dosomething1,dosomething2],function(item){item();})
}
或者直接交给浏览器去调度

function dosome(){
setTimeout(dosomething1,0);
setTimeout(dosomething2,0);
}
第三步,优化递归操作,函数递归虽然简单直接但是过深的递归操作不但影响性能而且稍不注意就会导致浏览器弹出脚本失控对话框,必须小心处理。

看以下斐波那契数列的递归算法:

function fibonacci(n) {
  return n < 2 ? n: fibonacci(n - 1) + fibonacci(n - 2);
};
fibonacci(40)这条语句将重复调用自身331160280次,在浏览器中执行必然导致脚本失控,而采用下面的算法则只需要调用40次

fibonacci = function(n){
  var memo = {0:0,1:0}; //计算结果缓存
  var shell = function(n){
    var result = memo[n];
    if( typeof result != 'number' ) {//如果值没有被计算则进行计算
      memo[n] = shell(n-1) + shell(n -2)
    }

    return memo[n];
  }
  return shell(n);
}
这项技术被称为memoization,他的原理很简单就是同样的结果你没必要计算两次。另一种消除递归的办法就是利用迭代,递归和迭代经常会被作为互相弥补的方法。

第四步,减少DOM操作,DOM操作的代价是相当昂贵的,大多数DOM操作都会触发浏览器的回流(reflow)操作。例如添加删除节点,修改元素样式,获取需要经过计算的元素样式等。我们要做的就是尽量少的触发回流操作。

el.style.width = '300px' el.style.height = '300px' el.style.backgroundColor = 'red'
上面的操作会触发浏览器的三次回流操作,再看下面的方式:

el.className = 'newStyle'
通过设置改元素的className一次设置多个样式属性,将样式写再CSS文件中,只触发一次回流,达到了同样是效果而且效率更高。因为浏览器最擅长的就是根据class设置样式。

还有很多可以减少DOM操作的方法,在此就不多说了,但是一个基本的原则就是让浏览器去做它自己擅长的事情,例如通过class来改变元素的属性。

相信经过上面的优化的过程必定可以大大提高用户体验,不会出现浏览器被锁死和弹出脚本失控的对话框,使你的浏览器从繁重的任务中解放出来。需要指出的是上面这些优化并不是必须的,只有当一段脚本的执行时间真的影响到了用户体验才需要进行。虽然它们让用户觉得脚本的执行变快了,但其实完成同一个操作的时间可能被延长了,这些技术只是让浏览器处于一个快速响应的状态,使用户浏览更流畅。

优化js脚本设计,防止浏览器假死的更多相关文章

  1. (转)优化js脚本设计,防止浏览器假死

    在Web开发的时候经常会遇到浏览器不响应事件进入假死状态,甚至弹出“脚本运行时间过长“的提示框,如果出现这种情况说明你的脚本已经失控了,必须进行优化. 为什么会出现这种情况呢,我们先来看一下浏览器的内 ...

  2. setTimeout 导致的浏览器假死

    问题   前几天,同事遇到一个浏览器假死的问题.就是浏览器在响应一个请求的时候,就突然不响应时间,进入假死状态,Cup也飙升到100%. 但是这个问题只出现在IE浏览器,chrome和Firefox等 ...

  3. async:false同步请求,浏览器假死

    // 异步请求导致数据错乱 // function get_num(){ // $("input[name='monitor']").eq(1).attr('checked',tr ...

  4. vs2017切换设计、拆分假死的解决

    今天安装了vs2017版把vs2015卸载了,原因就是vs2015运行webform后ajaxpro总是会出现time out现象.太难受了,所以决定体验一下新版本. 安装了vs2017后ajaxpr ...

  5. html5 WebWorkers 防止浏览器假死

    在Web开发的时候经常会遇到浏览器不响应事件进入假死状态,甚至弹出“脚本运行时间过长“的提示框,如果出现这种情况说明你的脚本已经失控了. 一个浏览器至少存在三个线程:js引擎线程(处理js).GUI渲 ...

  6. 2款JS脚本判断手机浏览器跳转WAP手机网站

    随着移动设备的普及,企业的网络宣传已经不能局限在PC端,而需要同时在移动端有所建树.对于公司网站来说,以前都是做的PC端的,当然手机等移动端也可以访问,但是用户体验肯定不如完全适合的手机端来的方便.我 ...

  7. js ajax同步请求造成浏览器假死的问题

    一.问题的起因 今天做一个需求遇到了这么个情况,就是用户个人中心有个功能,点击按钮,可以刷新用户当前的积分,这个肯定需要使用到ajax的同步请求了,当时喀喀喀三下五除二写玩了,大概代码如下: /** ...

  8. jQuery Ajax同步参数导致浏览器假死怎么办

    俗话说不作死就不会死,今天作死了一回,写了一个比较二逼的函数,遇到了同步Ajax引起的UI线程阻塞问题,在此记录一下.   事情起因是这样的,因为页面上有多个相似的异步请求动作,本着提高代码可重用性的 ...

  9. jQuery Ajax async=>false异步改为同步时,导致浏览器假死的处理方法

    今天做一个需求遇到了这么个情况,就是用户个人中心有个功能,点击按钮,可以刷新用户当前的积分,这个肯定需要使用到ajax的同步请求了,当时喀喀喀三下五除二写玩了,大概代码如下: /** * 异步当前用户 ...

随机推荐

  1. 五、 创建连接串连接本地数据库(ASP.NET MVC5 系列)

    1. 创建连接串连接本地SQLServer数据库 上节讲到MovieDBContext类,这个类的作用是连接数据库并将Movie对象迁移到数据库记录中.不过你会问一个问题:如何知道这个对象将连接哪个数 ...

  2. Lambda类库篇 —— Streams API, Collector和并行

    本文是深入理解Java 8 Lambda系列的第二篇,主要介绍Java 8针对新增语言特性而新增的类库(例如Streams API.Collectors和并行). 本文是对 Brian Goetz 的 ...

  3. Unsupervised Learning and Text Mining of Emotion Terms Using R

    Unsupervised learning refers to data science approaches that involve learning without a prior knowle ...

  4. 利刃 MVVMLight

    已经很久没有写系列文章了,上一次是2012年写的HTLM5系列,想想我们应该是较早一批使用HTML5做项目的人. 相比我当时动不动100+的粉丝增长和两天3000+的阅读量,MVVM Light只能算 ...

  5. spring 事务无效解决方法

    (原) spring 事务目前有二种,注解式和声明式,以前都是以公司里的框架写好的,没有学习的机会,今天抽空好好试了下,结果遇到好多问题. 1.注解式 最开始是这么玩的,发现数据进数据库了,没有起作用 ...

  6. iphone手机中对于html和css的一些特殊处理

    1.iphone safari iso系统不兼容:hover的解决办法: 方法一: a:hover设置的样式在IOS系统的浏览器内显示不出来,看来是IOS系统的移动设备中,需要在按钮元素或者是body ...

  7. 第39篇 免费博客github Pages绑定域名

    原文地址:http://blog.laofu.online/2017/06/02/how-bind-domain/ 网站已经有了,需要对网站来绑定一个自己的个性域名,本人是买了一个阿里云的域名,也就是 ...

  8. Java常用类之【字符串相关类型】

    一.字符相关类型 分类: 1.不可变的字符序列: String类 2.可变的字符序列: StringBuilder类--->线程不安全的 执行效率相对较高 StringBuffer类---> ...

  9. 从deque到std::stack,std::queue,再到iOS 中NSArray(CFArray)

    从deque到std::stack,std::queue,再到iOS 中NSArray(CFArray) deque deque双端队列,分段连续空间数据结构,由中控的map(与其说map,不如说是数 ...

  10. 解决jmeter请求不成功或者报403错误

    有同学遇到这种情况,jmeter请求一个网站,各项参数填写正确,可是响应是403,同样的请求放在浏览器执行就没有问题: 这是因为被请求的网站做了请求来源过滤,来源不明的请求拒绝访问,我们需要在jmet ...