在javascript中,定时器是一个常常被误用且不被众人所知的特性,但若是在复杂应用程序中正确应用定时器的话,就会给开发人员带来很是多的好处。javascript
1.js运做在浏览器中,是单线程的,即js代码始终在一个线程上执行,这个线程称为js引擎线程。java
2.浏览器是多线程的,除了js引擎线程,它还有ajax
UI渲染线程
浏览器事件触发线程
http请求线程
EventLoop轮询的处理线程
复制代码
这些线程的做用:api
UI线程用于渲染页面
js线程用于执行js任务
浏览器事件触发线程用于控制交互,响应用户
http线程用于处理请求,ajax是委托给浏览器新开一个http线程
EventLoop处理线程用于轮询消息队列
复制代码
单线程的含义是js只能在一个线程上运行,也就说,js同时只能执行一个js任务,其它的任务则会排队等待执行。浏览器
js是单线程的,并不表明js引擎线程只有一个。js引擎有多个线程,一个主线程,其它的后台配合主线程。bash
多线程之间会共享运行资源,浏览器端的js会操做dom,多个线程必然会带来同步的问题,全部js核心选择了单线程来避免处理这个麻烦。js能够操做dom,影响渲染,因此js引擎线程和UI线程是互斥的。这也就解释了js执行时会阻塞页面的渲染。多线程
JavaScript运行时,除了一个运行线程,引擎还提供一个消息队列,里面是各类须要当前程序处理的消息。新的消息进入队列的时候,会自动排在队列的尾端。app
咱们说的定时器能够在JavaScript中使用,但咱们没说它是JavaScript自身的一个功能—定时器不是JavaScript的一项功能,定时器做为对象和方法的一部分,才能在浏览器中使用。dom
下面咱们经过一张图片来线程中的定时器工做原理异步
这张图有不少信息须要消化,但彻底理解之后就会对js的异步执行工做有一个更加深刻的理解。
这张图的X轴是以毫秒为单位的时间轴矩形快的大小意味着js代码的执行部分以及执行时间。下面本胖就以时刻为单位来简单明了地说清楚这张图的内涵哈。
在0ms时刻
启动一个10ms的延迟的定时器(代号吕肥肥)
启动一个10ms的间隔定时器(代号吕胖胖一代)
启动一个大约18ms执行时间的主线js代码块(代号王大熊)
复制代码
在6ms时刻
一个鼠标单击事件(代号吕小花)
复制代码
在18ms时刻
王大熊执行完毕
可是在0-18ms这18ms时间内发送了不少事情
在10ms的时候吕肥肥和吕胖胖一代都想执行。
可是呢,主线程里面王大熊还站着坑呢,
因而吕肥肥和吕胖胖只好乖乖地排队,
对了还有一个6ms想要执行的吕小花就会在第18ms后才能执行
复制代码
在20ms时刻
这时候主线程里面占坑的是吕小花,这时候吕胖胖二代又诞生了
可是呢,吕胖胖还在排着队呢,因此这个吕胖胖二代会被废弃
也就是说浏览器不会对特定(好比吕胖胖)间隔定时器的多个实例进行排队
复制代码
第28ms时刻
吕小花已经执行完毕,这时候排着队的有吕肥肥以及吕胖胖一代
因而就会执行吕肥肥
复制代码
第30ms时刻
吕胖胖三代又诞生了,可是呢这时候吕胖胖一代还在排队(好苦逼的吕胖胖)
因此这个吕胖胖三代也是要被废弃的(浏览器就是这么聪明)
复制代码
第35ms时刻
吕肥肥执行完毕,这时候主线程彻底空了,要开始执行吕胖胖一号了
复制代码
第40ms时刻
吕胖胖四代又诞生了,这时候呢没有其余吕胖胖在排队了,那么这个吕胖胖四号就会排队等待被执行
复制代码
第42ms时刻
吕胖胖一代执行完毕,这时候排队的是有吕胖胖四代,因此就会执行吕胖胖四代(吕胖胖二代,吕胖胖三代都被废弃)
复制代码
上面分析了0ms-42ms这42ms间发生的事情,能够得出以下的结论
1.js引擎是单线程的,异步事件要排队才能执行
2.没法保证设置的定时器在何时执行
3.某一时刻,相同setInterval实例只会有一个在排队
复制代码
上面这张图是定时器的api集合,这里须要强调一点
不管是window.setTimeout仍是window.setInterval,在使用函数名做为调用句柄时都不能带参数,而在许多场合必需要带参数,这就须要想方法解决。
function say(name) {
console.log(name);
}
setTimeout('say("我是放在字符串里面的传进来的吕肥肥")', 1000);
复制代码
function say(name) {
console.log(name);
}
function _say(name) {
return function() {
say(name);
}
}
复制代码
var _setTimeout = setTimeout;
window.setTimeout = function(cb, param, time) {
var args = Array.prototype.slice.call(arguments, 1);
var _cb = function() {
cb.apply(null, args);
};
_setTimeout(_cb, time);
}
window.setTimeout(say, '我是改造过setTimeout才被传进来的王大熊', 2000);
复制代码
其实吧,上面的方法都是能够不用的,由于setTimeout默认就是执行第三个参数的(这一点是本胖作分享的时候同事提出来的,很是感谢),直接想下面这样就能够传入参数
setTimeout((name) => { console.log(name) }, 1000, '吕胖胖');
复制代码
任何知识只有在用实际开发中才有存在的意义,定时器也同样。下面咱们来看看定时器有哪些用处。
上图是以前作活动的一个弹幕效果,当时用的就是定时器。
function Barrage(box) {
this.box = box;
}
Barrage.prototype = {
// 气泡动效
randomPop: function (val) {
var item = document.createElement('span'),
box = this.box,
randomLeft = this.random(0, (box.clientWidth / 2)),
randomTop = this.random(0, box.clientHeight - 15);
item.style.left = randomLeft + 'px';
item.style.top = randomTop + 'px';
item.innerText = val;
box.appendChild(item);
item.addEventListener('animationend', function() {
item.remove();
});
},
// 在min,max之间的随机数
random: function (min, max) {
return (min + Math.random() * (max - min)).toFixed(2);
}
};
var box = document.querySelector('.barrage-box');
var zimu = new Barrage(box);
var time = 0,
inter = null,
isRun = true,
assistList = [
{
nickName: '吕肥肥',
num: 100
},
{
nickName: '吕胖胖',
num: 1200
},
{
nickName: '王大熊',
num: 200
},
{
nickName: '王大虎',
num: 1000
},
{
nickName: '吕肥肥',
num: 100
},
{
nickName: '吕胖胖',
num: 1200
},
{
nickName: '王大熊',
num: 200
},
{
nickName: '王大虎',
num: 1000
},
{
nickName: '吕肥肥',
num: 100
},
{
nickName: '吕胖胖',
num: 1200
},
{
nickName: '王大熊',
num: 200
},
{
nickName: '王大虎',
num: 1000
}
];
function go() {
clearTimeout(inter);
assistList.forEach(function (item) {
time++;
inter = setTimeout(function () {
if (isRun) {
zimu.randomPop(item.nickName + '注入' + item.num + '铜板');
}
time++;
if (time === assistList.length * 2) {
time = 0;
go();
}
}, time * 2000);
});
}
document.addEventListener('visibilitychange', function () {
if (document.hidden) {
isRun = false;
} else {
isRun = true;
}
});
go();
复制代码
之因此采用这段代码来讲明定时器作动效的例子,是由于当你用定时器作动效的时候,有一点须要特别注意那就是当app被切换到后台或者浏览器tab切换后再次到动效页面,这时候间隔时间内全部定时器的实例都将同时执行,会形成下面这样的状况(这里数据少,不是很明显)
节流和防抖这对好兄弟很容易被人混淆,这里作一个说明哈。
节流
必定时间内js方法只跑一次,多数在监听页面元素滚动事件的时候会用到
多数在监听页面元素滚动事件的时候会用到
复制代码
防抖
频繁触发的状况下,只有足够的空闲时间,才执行代码一次
最多见的就是用户注册时候的手机号码验证和邮箱验证了
复制代码
下面用定时器来分别实现简单的节流和防抖。
节流
var canRun = true;
document.body.onscroll = function () {
if (!canRun) {
// 判断是否已空闲,若是在执行中,则直接return
return;
}
canRun = false;
setTimeout(function () {
console.log("函数节流");
canRun = true;
}, 300);
};
复制代码
防抖
var timer = false;
document.body.onscroll = function () {
clearTimeout(timer); // 清除未执行的代码,重置回初始化状态
timer = setTimeout(function () {
console.log("函数防抖");
}, 300);
};
复制代码
在处理一些数据量不少的操做时候(尤为是大量dom操做的时候),会发现浏览器会变的很慢,好比下面的这段代码,目的就是想页面动态插入500000个tr节点。
var tbody = document.querySelector('#table');
for (var i = 0; i < 500000; i++) {
var tr = document.createElement('tr');
tr.innerText = i;
tbody.appendChild(tr);
}
复制代码
其实咱们能够巧用定时器的做用
var num = 500000,
divideInto = 10,
chunkSize = num / divideInto,
flag = 0;
var tbody = document.querySelector('#table');
setTimeout( function add() {
var base = chunkSize * flag;
for (var i = 0; i < chunkSize; i++) {
var tr = document.createElement('tr');
tr.innerText = flag * chunkSize + i;
tbody.appendChild(tr);
}
flag++;
if (flag < divideInto) {
setTimeout(add, 0);
}
}, 0);
复制代码
上面说了这么多,从概念到原理到api最后到应用,让咱们一次又一次地被定时器这个神器的东西所叹服,其实吧定时器是个神奇的东西,有不少意想不到的功能等着咱们去探索
(本文完)