我们在写一个网页的时候,可能会遇到这种情况,当数据过多的时候,可能会阻塞UI的渲染,导致给我们一种视觉感受,页面空白时间太长,这种情况怎么处理呢?
浏览器UI线程
用于执行javascript和更新用户界面的进程通常被称为“浏览器UI线程”。UI线程基于队列,任务会保存在队列中,直到队列空闲。一旦空闲,队列中的下一个任务就会被提出来执行,这些任务要么是js代码,要么是UI更新,然而我们每输入一个字符都可能会导致一个或多个任务加入队列。
// html// jsfunction handle(){ let div = document.createElement('div'); div.innerHTML = '你好啊'; document.body.appendChild(div)}复制代码
在点击按钮的时候,会触发UI线程来创建两个任务添加到对列中:更新按钮的UI,表示按钮被点击了;执行js。
会在UI线程空闲的时候将第一个任务取出来更新UI,然后,js任务被提取出来执行,在js这段代码中,创建了元素,并把他添加到界面中,这就表明页面需要更新,所以会在队列中添加一个任务,更新UI,在js运行完成后,更新UI
这就意味着我们要等待js运行完成才可以更新UI,如果js要运行很长时间,那么页面就会出现空白状态
解决办法----善于使用定时器
- 先来看一个例子
let arr = new Array(1000);for(let i = 0; i < arr.length; i++) { let p = document.createElement('p'); p.innerHTML = i + 1; document.body.appendChild(p)}复制代码
在这个例子中,创建一个长度为1000的数组,循环数组,在循环的时候我们创建元素并添加到页面中,但浏览器并不是按顺序执行的,他会先循环1000次,循环完成以后,才会添加元素到页面上(如果不信可以在浏览器中跑一下,在控制台看一下元素什么时候会被添加到页面上,是不是一次性添加到页面上)
- 再看另一种方法
let todo = arr.concat(); //克隆数组function renderArray() { // 从数组中拿出第一个 let task = todo.shift() setTimeout(function() { let p = document.createElement('p'); p.innerHTML = todo.length; document.body.appendChild(p) if(todo.length > 0) { setTimeout(arguments.callee, 25) } },25)}renderArray()复制代码
虽然写的代码比上边的多,但是你可以在浏览器中看出来,并不是在最后一次行渲染出来的,而是一条一条的渲染出来的
基本思路
- 创建一个数组的克隆,并将它作为数组队列来处理
- 第一次调用setTimeout创建一个定时器处理数组中的第一个条目,处理完成后判断还有没有条目需要处理,如果有的话就重新启动一个定时器
- 因为下一个定时器要运行同样的代码,所以第一个参数为arguments.callee,只想当前正在运行的函数。
为什么要用定时器
- 定时器可以用来将耗时较长的脚本拆分成较短的片段
- 用定时器会告诉javascript引擎先等待一段时间,然后添加一个javascript任务到UI队列中
- 第二个参数表示什么时候被添加到队列中,而不是在这个时候立即执行,他会在队列中其他任务执行完毕才会执行
你们担心的问题在这里
定时器与性能
- 定时器过度使用可能会对性能产生影响,BUT: 在我们写的这个定时器中,同一时间只有一个定时器存在,当这个定时器结束时才会重新创建一个,所以不会到值性能问题