快速响应的用户界面

. 发布于 类目


ui线程

每个浏览器用于执行js代码和更新ui的进程就只有一个,这个被称为‘ui线程’,它基于一个简单的队列系统,也就是根据代码的先后循序将代码插入队列并执行。

一般来说,当用户点击一个按钮botton的时候,就会触发ui线程,他会创建两个任务并添加到队列中,第一个任务是更新ui按钮,它需要改变外观来表示它被点击了,然后再触发onclick事件,如果你对这个事件进行了调用其他函数,就会执行所调用的函数。

事实上,大多数浏览器会在js代码运行时停止把新的任务加入ui队列中去,也就是说当用户点击一个事件,这个事件所调用的函数运行时间过长,那么在运行的这段时间中用户再点击其他的事件是没有交互的,产生了一种假死的状态。

所以,一个js代码的运行它必须要快,以避免对用户体验造成不良影响。

浏览器限制

浏览器本身是有限制的,它会从js代码运行的行数和运行的时间进行限制,比如当运行超过500万条语句时浏览器会弹出警告,并停止脚本运行,甚至还有之前的栈大小限制,还有就是运行时间,当一个js文件开始运行时,浏览器会开始计时,如果这个脚本运行超过规定的时间,那么浏览器就会弹出提示,当前脚本运行时间过长,是否停止脚本运行。

但对于普通用户来说,他们可能并不知道这个脚本指的是什么,也就会处于是停止还是不停止运行的两难选择,为此,我们要从一开始就要避免出现这种问题。

多久才算久

100毫秒以内是最佳的时间

使用定时器让出时间片段

即便我们极力避免js的运行时间不超过100ms,但是难免还是会有大量的计算占用时间,所以我们可以通过定时器来一段一段的运行,每一段都有空出一些时间让其他代码运行。

定时器的两种方式:

  1. setTimeout()
  2. setInterval()

通过对定时器设置延迟时间来达到延迟调用的效果。

延迟的时间怎么计算的?

定时间被运行后开始计算,而不是他的上一级函数运行后,如:

function set() {
    one();
    setTimeout(function(){},250)

}

250ms的计算是从setTimeout运行后开始计算,而不是set()运行后。

如果你在一个事件中使用一个定时器,那么事件中运行的函数最好要快一些,如果事件函数运行超过的250ms,也就是你设置的延时时间,那么可能会在事件结束之前定时器就被运行了。

定时器的精度

事实上js的定时器并不是很精准,你设置延迟250毫秒并不意味着就一定是在250毫秒后运行,浏览器都在试图尽可能的精准,但往往会过快或者过慢几毫秒,因此,定时器不能用于测量实际的时间。

在windows系统中,定时器的步进值是15ms,也就是说,设置为10ms的延迟往往会是15ms后运行,或者低于15ms视为0,为此,最小的延迟值应该要是15或者30,起码要保证还有15ms的间隔。

注意,大部分浏览器在延迟小于10的时候,表现都不太一样。

使用定时间处理数组

常用的循环代码:

for(var i =0,len=item.length;i<len;i++) {
  process(item[i]);
}

这是一个常用的循环代码,每次循环传入数组item的一个值,然后运行process()方法,假设这里的process运行时间过长,就会造成页面的假死,如果item数组庞大,也会如此。

为此我们就可以使用延迟调用来空出一部分时间给其他代码运行。

但是使用延迟调用要明确两个因素:

  1. 处理的过程是否必须同步
  2. 数据是否要按顺序处理

如果以上两点都不需要考虑,那么就可以使用该方法,因为延迟调用是异步的,然后有可能在不断延迟中,上一个还没运行完下一个就调用了。

function set(item) {
  var arr = item.concat();
  setTimeout(function(){
      process(arr.shift());
      if(arr.length > 0) {
        setTimeout(arguments.callee,25)
      }else {
        callback();
      }
  },25)
}

然而事实上arguments.callee已经不推荐使用,因为每次递归都会重新创建arguments对象,这个代价是很昂贵的,我们可以使用非匿名函数。

function set(item) {
  var arr = item.concat();
  setTimeout(function fn(){
      process(arr.shift());
      if(arr.length > 0) {
        setTimeout(fn,25)
      }else {
        callback();
      }
  },25)
}

这里插个题外话

一道面试题。接受参数n=5,不用for循环输出数组【1,2,3,4,5】

function set(num) {
  var arr = [];
  var i = 0;
  return (function fn() {
      arr.unshift(num--);
      if(num > 0) {
          fn();
      }
      return arr;
  })()
  
}

我们还可以将定时器封装成一个可以多次调用的函数

function processArray(items,process,callback) {
  var todo = itmes.concat();

  setTimeoue(function fn(){
    process(todo.shift());
  },25)

  if(todo.length > 0) {
     setTimeoue(fn,25);
  }else {
    callback();
  }

}

函数支持传三个参数,依次是需要传入的参数数组集合,调用的函数,回调的函数

分割任务

如果一个函数任务里面有几个子任务,且每个子任务都是相对独立的,但是一起的话运行时间过长,于是我们可以采用上面的那种办法

原方法:

function sava(id) {
  open(id);
  write(id);
  close(id);

  //上面都完成了
  updataUI(id);
}

改为:

function sava(id) {
  var tasks = [open,write,close];

  setTimeout(function fn(){
    var task = tasks.shift();
    task(id);
    if(tasks.length > 0) {
        setTimeout(fn(),25);
    }else {
        updataUI(id);
    }
  },25)
}

记录代码的运行时间

如果我们需要每次运行的函数过多,那么无限的setTimeout反倒成为了累赘,因为是延迟调用,那么自然会造成反应的延迟,特别是数量过多的情况,为此我们可以想到之前的‘达夫设备’,一个setTimeout运行多个函数

那么怎么在极短的时间里运行多个函数呢?

一般来说,一个js代码的运行不超过100ms最佳,那么我们既然要优化,自然不能用最大值,所以我们可以采用50ms。

function timed(items,process,callback){
  var todo = items.concat();

  setTimeout(function fn() {
    var start = +new Date();
    do {
      process(todo.shift());
    }while(todo.length > 0 && (+new Date() - start > 50))

    if(todo.length > 0 ) {
      setTimeout(fn(),25);
    }else {
      callback();
    }

  },25)

}

当定时间内的函数运行时先创建一个时间,也就是当前时间,然后再do-while循环,第二次循环的时候判断todo还有没有值,并且第二次判断的时间减去刚开始的时间要不超过50ms才可以继续循环,如果不满足,重新延迟调用。

定时器的性能

定时器延迟的时间越短,且量越多,就会造成浏览器的ui线程堵塞,所以在延迟短的定时器中,尽量少的使用,但是超过1秒或以上的延迟其实不怎么会造成堵塞,所以在使用定时器的时候可以根据情况自行分配好延迟的时间。

workers

workers是新引入的一个api接口,它可以创建一个独立的线程来运行js,ie10及以上才支持,新版浏览器都支持,所以在不考虑到兼容性的问题是,workers是一个处理大量运算脚本的最佳选择。

workers创建时需要传入一个参数,这个参数就是需要运行的js文件路径,在workers中,有一个只读的navigator对象,还有一些js的对象,但是要注意,在workers中不要对ui进行操作,因为他是独立于ui线程的,所以操作可能会带来错误或者其他问题,workers本身用于独立计算那些,并且通过message事件和postMessage()相互通信。

也就是说js首先会创建一个workers环境,new Workers(‘xxx.js’);

然后创建一个onmessage事件,通过event对象的data属性获取到从workers传来的参数,页面也可以通过postMessage()向workers传参。

在workers环境中也创建一个onmessage事件用于监听页面传来的参数,也是event对象的data获取数据。

向页面传参也是postMessage()方法。

var worker = new Workers('xxx.js');

worker.onmessage = function(event) {
     collback(event.data);
} 

worker.postMessage('参数');

//workers

self.onmessage = function(event) {
    process(event.data);
}

worker.postMessage('计算好参数传给页面');

在workers中也可以再载入其他js文件,载入的后js文件在workers中运行,使用importScripts()方法

//workers中
imporScripts('xxx.js','sss.js');

微信分享

评论:

已有 3 条评论

    YIR

    YIR

    我在想这篇文章的封面是什么相机。


      YIR

      YIR

      嗷,一刷新就没了。

Friend Link

搜索
去往底部
返回顶部