在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最後到應用,讓咱們一次又一次地被定時器這個神器的東西所歎服,其實吧定時器是個神奇的東西,有不少意想不到的功能等着咱們去探索
(本文完)