一、让setTimeout去吧
经常使用定时器的人都会使用setTimeout,而不使用setInterval,这个很好理解,setTimeout强调的是下次,而setInterval强调的是多次。
当去医院看病的时候,医生一般会告诉你下次什么时候来复查,而不是隔多久复查一次。
所以在可读性和可靠性上setTimeout更符合。
二、请先去排队
下面是一道常见的面试题
for(var i = 0; i < 3; i++) {
setTimeout(function(){
console.log(i);
}, 0);
}
结果是输出: 3 3 3
它强调了setTimeout(fn, 0);的本质:把fn立刻添加到一个异步队列中,等主线程空闲,再排队执行。
等多久执行要看主线程什么时候释放,然后队有多长~,所谓的0只是马上去排队。
setTimeout的使用技巧一般是将复杂的运算放入队列,避免它阻塞了主线程。毫无疑问,你吃饭时间长,在只有一个餐位的情况下,人多,你就别想先吃。
三、vip通道和普通客户的区别
看下面一个常见性能问题
<button onclick="say()">点击</button>
<script type="text/javascript">
function say() {
alert('hello');
};
for (var i = 0; i < 10000; i++) {
console.log(i);
}
</script>
明明是say方法先排队,但是反复点击确弹不出hello,而是要等for循环结束后,才可以正常点击。
这是因为for循环是主线程vip通道,say方法被放到其他队列里面了,主线程长期不释放,阻塞了say方法。
这种情况我们是不希望发生的,例如:在ajax回填一个大数据的时候,会占用主线程很长时间,而这个时候用户不能操作页面按钮。这得是多大的体验问题啊~
四、ajax也有vip
当一个ajax发起的过程中,是不会占用主线程的。这个时候是浏览器给ajax单独开了一个线程。
这个vip通道不能浪费~,因为java也是多线程,如果前后交互,能同时建立多个连接。这将是一个很好的性能优化方案,具体的要等我下次介绍。
五、单线程才是javaScript的本质
在javaScript中,所谈到的多线程(除了ajax),实际上都是一个误区:我们所号称的多线程,不过是强调了排队的次序。而打饭的窗口却只有一个。
所以说:单线程才是javaScript的本质。
六、从CPU调度到GUI的调度
java多线程是CPU的调度,javaScript在CPU上是不能改变的,但对于执行javaScript的浏览器而言,浏览器GUI渲染引擎是可以调度的。
调度的方法就是setTimeout。 可以设想,如果任何一个函数都可以拆解成一个个小的执行单元片段,那么用setTimeout去调度这个执行单元片段,就可以实现js的多线程。
七、场景
如果,有一个需求:需要一个小盒子,在x方向运动300px后停止3秒,运动到500px终止。在y方向运动400px后停止4秒,运动到600px终止。
这不是一个复杂的问题,不过我们可以尝试着用多线程去解一下。
我们可以构思下面的实现
<div id="cri" style="position: absolute;">盒子</div>
<script type="text/javascript">
var i = 0;
// x方向的线程
Thread.create(function b(){
if (i === 300) {
this.sleep(3000); // 休眠3秒
}
if (i === 500) {
this.interrupt(); // 终止
}
cri.style.left = i + 'px';
i++;
});
var j = 0;
// y方向的线程
Thread.create(function a(){
if (j === 400) {
this.sleep(4000);
}
if (j === 600) {
this.interrupt();
}
cri.style.top = j + 'px';
j++;
});
// 启动线程
Thread.start();
</script>
同时创建x和y两个方向上的线程,通过sleep和interrupt来简单控制线程。
八、实现方法
实现上面简单线程不难,我们可以创建一个队列,通过setTimeout去依次执行。interrupt实际上是把当前方法移除队列,sleep可以通过比较时间戳来实现。
(function(){
// 执行队列
var _executeQueue = [];
// 定时器句柄
var _timeID = 0;
// 防止Thread被实例
function Thread(){
throw new Error("Thread cannot be instantiated");
}
// 创建线程的方法
Thread.create = function(fn){
// 添加sleep方法
fn.sleep = function(time){
fn._executeTime = new Date().getTime() + time;
};
// 添加interrupt方法
fn.interrupt = function() {
fn._interrupt = true;
};
// 记录时间戳
fn._executeTime = new Date().getTime();
// 函数放入队列
_executeQueue.push(fn);
return fn;
};
// 线程执行方法
Thread.start = function() {
if (_timeID) return; // 防止线程多次启动
(function executeNext() { // 轮询执行队列
if (_executeQueue.length > 0) {
for(var i = 0; i < _executeQueue.length; i++) {
var executeStatement = _executeQueue[i];
// 比较时间戳时间sleep
if (executeStatement._executeTime < new Date().getTime()) {
try{
// 执行队列里面的方法,改变上下文让线程内部可以使用this调用
executeStatement.call(executeStatement, null);
}catch(e){}
}
// 实现interrupt方法
if (executeStatement._interrupt === true) {
_executeQueue.splice(i, 1);
i--;
}
};
if (_timeID >= 0) {
// 轮询执行
_timeID = setTimeout(executeNext, 0);
}
}
})();
};
// stop方法,停止所有线程
Thread.stop = function(){
clearTimeout(_timeID);
_timeID = 0;
};
window.Thread = Thread;
})();