詳解Nodejs的timers模塊
本模塊,屬于來模擬一些瀏覽器自帶方法的模塊,比如setTimeout,clearTimeout等方法,之所以會有該模塊,在我看來,也是為了能讓前端工程師使用起來,更簡單,使用一個單獨(dú)的模塊,來把瀏覽器上的功能來模擬出來,那么就可以直接減少學(xué)習(xí)的成本,這樣就可以花更少的時間,學(xué)習(xí)到更多的東西。
timers模塊中,使用的C++的方法
timers模塊中,調(diào)用了C++實(shí)現(xiàn)的方法,這些方法,在該模塊中,占據(jù)了很重要的位置,所以,這里我們先來看下,在C++的方法中,提供了哪些方法。
var Timer = process.binding('timer_wrap').Timer;
console.log(Timer);
運(yùn)行之后,在控制臺,就會打印出如下的內(nèi)容,它的格式如下
{
[Function: Timer]
// Timer構(gòu)造函數(shù),可以進(jìn)行實(shí)例化
kOnTimeout: 0,
// 靜態(tài)屬性,公用,更改會影響其他的調(diào)用
now: [Function: now]
// 靜態(tài)方法,獲取類似時間戳的一個數(shù)字
}
其中,Timer本身是一個構(gòu)造函數(shù),而這個構(gòu)造函數(shù)中,還包含了一個靜態(tài)屬性和一個靜態(tài)方法,關(guān)于靜態(tài)屬性和方法,基本上,這兩個只是拿來使用的,是禁止修改的,并且,其使用方法比較簡單,所以這里不多說了。
Timer既然還是一個構(gòu)造函數(shù),那么久是可以被實(shí)例化的,接下來,看下實(shí)例化之后的對象:
var Timer = process.binding('timer_wrap').Timer,
timer = new Timer(),
i = "";
console.log("obj has attribute:");
console.log(timer);
console.log("prototype method and attribute:");
for(i in timer){
console.log(i+"="+timer[i]);
}
把上面的代碼,執(zhí)行的結(jié)果如下:
obj has attribute:
{}
prototype method and attribute:
close=function close() { [native code] }
ref=function ref() { [native code] }
unref=function unref() { [native code] }
start=function start() { [native code] }
stop=function stop() { [native code] }
setRepeat=function setRepeat() { [native code] }
getRepeat=function getRepeat() { [native code] }
again=function again() { [native code] }
從上面的結(jié)果中可以看出,在Timer實(shí)例化之后,在對象本身,是沒有屬性和方法的,在原型鏈上,是有一些方法,至于這些方法,有什么用,就需要慢慢去看一下了。
timers模塊中的一個基礎(chǔ)--構(gòu)造函數(shù)Timeout
之所以這里要把這個構(gòu)造函數(shù)以單小節(jié)的形式給出,是因?yàn)樵谖铱磥?,如果想要對整個timers模塊中的邏輯有更好的認(rèn)識,那么該模塊的基礎(chǔ)一個私有的構(gòu)造函數(shù)的理解,還是很有必要的。
這里,我們首先來看一下源碼:
var Timeout = function(after) {
// 定義內(nèi)部屬性,過時時間
this._idleTimeout = after;
// 循環(huán)鏈表中的兩個屬性,可以參考前篇文章linklist私有模塊
this._idlePrev = this;
this._idleNext = this;
// 記錄開始計(jì)時時間的屬性
this._idleStart = null;
// 當(dāng)時間到了,執(zhí)行的回調(diào)函數(shù)
this._onTimeout = null;
// 該計(jì)時器,是否需要repeat,setInterval方法,該屬性為true
this._repeat = false;
};
function unrefdHandle() {
// unref方法的回調(diào)函數(shù),內(nèi)部this指向Timeout._handle屬性
// 在該屬性上,定義了owner屬性,保存Timeout的實(shí)例化后的對象
this.owner._onTimeout();
if (!this.owner._repeat)
this.owner.close();
}
Timeout.prototype.unref = function() {
// 這個方法,是用來暫停計(jì)時器的
// 添加一個新的屬性_handle用來對接C++提供的API接口
if (!this._handle) {
// 做一些初始的判斷屬性,設(shè)置初始值等
var now = Timer.now();
if (!this._idleStart) this._idleStart = now;
var delay = this._idleStart + this._idleTimeout - now;
if (delay < 0) delay = 0;
// 把this指向的計(jì)時器對象,清理掉,從計(jì)時器鏈表中清理掉
exports.unenroll(this);
// 介入C++提供的API方法
this._handle = new Timer();
// 添加一些屬性,用來保存一些信息
this._handle.owner = this;
this._handle[kOnTimeout] = unrefdHandle;
// 開始計(jì)時,在delay后執(zhí)行改方法的回調(diào)
this._handle.start(delay, 0);
this._handle.domain = this.domain;
// 調(diào)用C++提供的方法,停止計(jì)時器的執(zhí)行
this._handle.unref();
} else {
// 如果之前有_handle屬性,那么則直接停止
this._handle.unref();
}
};
Timeout.prototype.ref = function() {
// 該方法,只有在unref之后,才起作用,恢復(fù)計(jì)時器的工作
// 如果在unref中,生成了_handle屬性,那么使用該屬性
// 調(diào)用C++提供的API,ref,恢復(fù)計(jì)時器的運(yùn)行
if (this._handle)
this._handle.ref();
};
Timeout.prototype.close = function() {
// 當(dāng)要關(guān)閉計(jì)時器對象時,如果定義過接入C++餓API的方法時
// 直接使用C++的方法,關(guān)閉
// 否則,把該方法,清理出去
// 不讓它再lists鏈表中,那么當(dāng)計(jì)時器執(zhí)行到時,也不會執(zhí)行該計(jì)時器的回調(diào)函數(shù)
this._onTimeout = null;
if (this._handle) {
this._handle[kOnTimeout] = null;
// 調(diào)用C++中提供的close方法,見前面構(gòu)造函數(shù)Timer的原型鏈方法中
this._handle.close();
} else {
exports.unenroll(this);
}
};
上面的源碼,就是在timers模塊中,內(nèi)部的一個私有構(gòu)造函數(shù),在timers公開的一些方法,占據(jù)了一個很重要的位子,因?yàn)?,這個方法,是timers模塊,與C++代碼鏈接的重要部分。該部分,是沒有示例可以給出的,只有在后面使用timers模塊對外公開的API中,來看下對應(yīng)的使用效果。
這里之所以,要先把這個構(gòu)造函數(shù)放在這里,因?yàn)?,在我看來,如果能先對這個構(gòu)造函數(shù)有所了解的話,那么接下來看timers模塊中的其他方法時,就會變的簡單很多。
當(dāng)然,也有可能是,因?yàn)闆]有看其他的源代碼,而導(dǎo)致對于該構(gòu)造函數(shù)的一些方法和屬性,很沒用感覺的,那么,接下來,就繼續(xù)看下去吧。
timers模塊的源碼
timers中的源碼,可以分為兩部分,在這里,只會看下其中的一部分,還有另外一部分,是和延時執(zhí)行相反的立即執(zhí)行的回調(diào)函數(shù),這是我們不常用到的,所以這里就不在占用篇幅。
這里,依然使用源碼來開始:
'use strict';
// timer_wrap模塊,為底層C++實(shí)現(xiàn)的模塊
var Timer = process.binding('timer_wrap').Timer;
// Timer在控制臺打印出的數(shù)據(jù)如下:
// {[Function: Timer] 是一個構(gòu)造函數(shù)
// kOnTimeout: 0,
// now: [Function: now]
// }
// Nodejs模擬的雙向鏈表的操作模塊,請查看前一篇關(guān)于linklist的文章
var L = require('_linklist');
// 斷言的管理模塊中的ok方法
var assert = require('assert').ok;
var kOnTimeout = Timer.kOnTimeout | 0;
// Timeout values > TIMEOUT_MAX are set to 1.
var TIMEOUT_MAX = 2147483647; // 2^31-1
// 把timer添加到debug的模塊中,并生成一個函數(shù),命名為debug
// 在之后,直接調(diào)用,該函數(shù),即可把官員timer的錯誤信息,打印到控制臺
var util = require('util');
var debug = util.debuglog('timer');
// 注,debuglog方法,應(yīng)該是最近的版本中,新添加的,因?yàn)樵谝荒昵?,剛接觸nodejs時,
// util模塊中,還沒有該方法
// Object containing all lists, timers
// key = time in milliseconds
// value = list
var lists = {};
// the main function - creates lists on demand and the watchers associated
// with them.
// 把item存入到一個鏈表中去,并且把msecs對應(yīng)的鏈表,存入到lists對象中去
// lists的格式是這樣的:
// {
// "1000": 這里是一個循環(huán)鏈表,該鏈表內(nèi),包含了所有msecs=1000的list對象
// "2000":{}
// }
function insert(item, msecs) {
// 給item定義兩個私有屬性
// 一個記錄當(dāng)前時間
item._idleStart = Timer.now();
// 一個記錄毫秒時間,類似于過期時間
item._idleTimeout = msecs;
// 如果定義的毫秒,是負(fù)值,則直接返回,不做后面的處理
if (msecs < 0) return;
var list;
// 如果該過期時間,已經(jīng)緩存在了lists對象中,則直接找到緩存的數(shù)據(jù)
if (lists[msecs]) {
list = lists[msecs];
} else {
// 否則,執(zhí)行新建一個list數(shù)據(jù)
// 并把item和msecs的數(shù)據(jù)初始化到新創(chuàng)建的對象中去
list = new Timer();
// 下面這些,就是Timer實(shí)例化之后,包含的方法
// close
// ref
// unref
// start
// stop
// setRepeat
// getRepeat
// again
// 實(shí)例化之后,調(diào)用start方法
list.start(msecs, 0);
// 把list對象,改為一個循環(huán)鏈表
L.init(list);
// 把該list添加到lists對象中緩存
// 并設(shè)置一些屬性,這些屬性,在其他方法中被用到
lists[msecs] = list;
list.msecs = msecs;
list[kOnTimeout] = listOnTimeout;
}
// 把item插入到list的下一個節(jié)點(diǎn)去
L.append(list, item);
assert(!L.isEmpty(list)); // list is not empty
}
// 每一個list的kOnTimeout的屬性值,應(yīng)該是一個回調(diào)函數(shù)
// 所以,其內(nèi)部指向的是list本事
function listOnTimeout() {
var msecs = this.msecs;
var list = this;
debug('timeout callback %d', msecs);
// 類似一個時間戳,但是又和Date.now()的毫秒級時間戳不同,不知道是如何判斷這個的
var now = Timer.now();
debug('now: %d', now);
var diff, first, threw;
// 當(dāng)時間到了之后,把對應(yīng)該時間的鏈表中的所有元素執(zhí)行
// 如果出現(xiàn)異味,則等一會再次執(zhí)行,請看源碼中的具體注釋
while (first = L.peek(list)) {
// If the previous iteration caused a timer to be added,
// update the value of "now" so that timing computations are
// done correctly. See test/simple/test-timers-blocking-callback.js
// for more information.
// 本處的while是,把list的所有前置列表,都處理一遍,直到list所處的鏈表中,只有l(wèi)ist時結(jié)束
if (now < first._idleStart) {
// 當(dāng)first元素,當(dāng)執(zhí)行insert時,會操作_idleStart的屬性值
// 如果Timer.now的值,是一直增加的,那么這里為神馬會執(zhí)行?
// 那么又為什么要有這個判斷?只是打了一個log,難道只是為了做個通知?
now = Timer.now();
debug('now: %d', now);
}
// 求這個差值?并且與list的msecs值進(jìn)行判斷
diff = now - first._idleStart;
if (diff < msecs) {
// 執(zhí)行到這里,那邊把list繼續(xù)延時一段時間,因?yàn)楫?dāng)前的一個item沒有被執(zhí)行
// 所以重新計(jì)時,再執(zhí)行一次
list.start(msecs - diff, 0);
debug('%d list wait because diff is %d', msecs, diff);
// 并且直接return,結(jié)束本回調(diào)函數(shù),等待msecs-diff時間之后,再次執(zhí)行
return;
} else {
// 把first從它所在的鏈表中移除
L.remove(first);
// 我覺得,這里是在判斷,是否移除成功
assert(first !== L.peek(list));
// 如果當(dāng)前的first沒有回調(diào)函數(shù),那么不需要再向下執(zhí)行,繼續(xù)while循環(huán)
if (!first._onTimeout) continue;
// 接下來,就是執(zhí)行回調(diào)的處理了,處理的邏輯還行,看起來不算復(fù)雜
// 只是,有些判斷,我現(xiàn)在無法理解到,為什么要這么判斷
// v0.4 compatibility: if the timer callback throws and the
// domain or uncaughtException handler ignore the exception,
// other timers that expire on this tick should still run.
//
// https://github.com/joyent/node/issues/2631
var domain = first.domain;
if (domain && domain._disposed)
continue;
try {
if (domain)
domain.enter();
threw = true;
first._onTimeout();
if (domain)
domain.exit();
threw = false;
} finally {
if (threw) {
// We need to continue processing after domain error handling
// is complete, but not by using whatever domain was left over
// when the timeout threw its exception.
var oldDomain = process.domain;
process.domain = null;
process.nextTick(function() {
list[kOnTimeout]();
});
process.domain = oldDomain;
}
}
}
}
debug('%d list empty', msecs);
assert(L.isEmpty(list));
list.close();
delete lists[msecs];
}
var unenroll = exports.unenroll = function(item) {
L.remove(item);
// _idleTimeout中保存著msecs的值,
// 所有可以根據(jù)該屬性,直接找到該對象在lists中的緩存數(shù)據(jù)
// 不過,item的msecs中,也保存了list本身的msecs的
var list = lists[item._idleTimeout];
// if empty then stop the watcher
debug('unenroll');
if (list && L.isEmpty(list)) {
debug('unenroll: list empty');
// list調(diào)用C++的接口
list.close();
delete lists[item._idleTimeout];
}
// if active is called later, then we want to make sure not to insert again
item._idleTimeout = -1;
// 本方法,其實(shí)就是在清理一些默認(rèn)的數(shù)據(jù)了
// 屬于,當(dāng)一個方法執(zhí)行完之后,把其對應(yīng)的數(shù)據(jù),都直接清理掉
};
// Does not start the time, just sets up the members needed.
exports.enroll = function(item, msecs) {
// 給item重新設(shè)置一些屬性
// msecs的值,需要時number類型,并且有效的正整數(shù)和零
if (!util.isNumber(msecs)) {
throw new TypeError('msecs must be a number');
}
if (msecs < 0 || !isFinite(msecs)) {
throw new RangeError('msecs must be a non-negative finite number');
}
// if this item was already in a list somewhere
// then we should unenroll it from that
// 保證,item不會存在于兩個鏈表中,
// 比如,我最初把item設(shè)置為1000之后執(zhí)行,那么item在lists[1000]所在的鏈表中
// 接下來,我又把item設(shè)置為2000之后執(zhí)行,那么就要先吧item從原來的lists[1000]的鏈表中刪除
// 然后,添加到lists[2000]所指向的鏈表去
if (item._idleNext) unenroll(item);
// Ensure that msecs fits into signed int32
// 保證是在最大值之內(nèi)的,否則,設(shè)置為一個系統(tǒng)設(shè)置的最大值
if (msecs > TIMEOUT_MAX) {
msecs = TIMEOUT_MAX;
}
// 設(shè)置信息,并初始化item本身的鏈表
item._idleTimeout = msecs;
L.init(item);
};
// call this whenever the item is active (not idle)
// it will reset its timeout.
exports.active = function(item) {
// 把item插入到緩存的lists對象中,
// 或者把已經(jīng)存在的于對象中的item,進(jìn)行一次數(shù)據(jù)更新
var msecs = item._idleTimeout;
if (msecs >= 0) {
// 看上面的函數(shù),enroll可以知道,msecs是必須大于等于0的
var list = lists[msecs];
// 如果list存在于lists中,找到對應(yīng)的鏈表
if (!list || L.isEmpty(list)) {
// 如果list為空,或者list為空鏈接,則執(zhí)行insert方法,創(chuàng)建一個新的鏈表
// 并且把該鏈表,保存到lists[msecs]中去
insert(item, msecs);
} else {
// 如果有,那么更新item屬性的當(dāng)前時間,把item插入到list鏈表中去
item._idleStart = Timer.now();
L.append(list, item);
}
}
};
/*
* DOM-style timers
*/
exports.setTimeout = function(callback, after) {
// setTimeout的實(shí)現(xiàn)源代碼
// 前兩個參數(shù)必須是固定的
var timer;
// after轉(zhuǎn)化為數(shù)字,或者NaN
after *= 1; // coalesce to number or NaN
// 如果不在合法范圍之內(nèi),則把a(bǔ)fter設(shè)置為1
if (!(after >= 1 && after <= TIMEOUT_MAX)) {
after = 1; // schedule on next tick, follows browser behaviour
}
// 根據(jù)Timerout構(gòu)造函數(shù),生成一個實(shí)例,該構(gòu)造函數(shù)完成的功能
// 只是創(chuàng)建一個對象,設(shè)置了一些屬性和一些方法
timer = new Timeout(after);
// 實(shí)例化后的timer包含以下內(nèi)部屬性
// _idleTimeout = after;
// _idlePrev = this;
// _idleNext = this;
// _idleStart = null;
// _onTimeout = null;
// _repeat = false;
// 以及一下幾個原型鏈方法
// unref
// ref
// close
// 如果傳入的參數(shù),小于等于2個,說明沒有多余的默認(rèn)參數(shù)傳入
if (arguments.length <= 2) {
timer._onTimeout = callback;
} else {
// 如果有多余的默認(rèn)參數(shù)傳入,那么就要把多余的參數(shù)緩存一下
// 使用閉包,重新設(shè)置一個回調(diào)函數(shù)
var args = Array.prototype.slice.call(arguments, 2);
timer._onTimeout = function() {
callback.apply(timer, args);
}
}
// 設(shè)置timer的domain屬性為process的domain屬性
// 該屬性,暫時還不知道為什么存在
if (process.domain) timer.domain = process.domain;
// 把timer設(shè)置為啟動,并在active中,插入到等待執(zhí)行的列表中去
exports.active(timer);
// 返回Timeout的實(shí)例對象,所以,可以想象setTimeout的返回值,到底有哪些屬性和方法了吧
return timer;
};
exports.clearTimeout = function(timer) {
// 只有timer存在
// 回調(diào)存在,回調(diào)時間存在的情況下,才需要把該方法清理掉
// 至于為什么要判斷這些條件,請參考listOnTimeout方法內(nèi)部的注釋及邏輯
if (timer && (timer[kOnTimeout] || timer._onTimeout)) {
timer[kOnTimeout] = timer._onTimeout = null;
// 清除回調(diào),時間等屬性,然后把timer自lists鏈表中,去除掉
// 這樣減少在每次調(diào)用時,對lists中對象的無意義的循環(huán)
if (timer instanceof Timeout) {
timer.close(); // for after === 0
} else {
exports.unenroll(timer);
}
}
};
exports.setInterval = function(callback, repeat) {
// 前期的處理,和setTimeout方法相同,唯一不同的是,回調(diào)
// 在本方法中,回調(diào)之后,再添加另外一個計(jì)時器
// 在我看來,就像是每次去調(diào)用setTimeout方法一樣
repeat *= 1; // coalesce to number or NaN
if (!(repeat >= 1 && repeat <= TIMEOUT_MAX)) {
repeat = 1; // schedule on next tick, follows browser behaviour
}
var timer = new Timeout(repeat);
var args = Array.prototype.slice.call(arguments, 2);
timer._onTimeout = wrapper;
timer._repeat = true;
if (process.domain) timer.domain = process.domain;
exports.active(timer);
return timer;
function wrapper() {
callback.apply(this, args);
// If callback called clearInterval().
if (timer._repeat === false) return;
// If timer is unref'd (or was - it's permanently removed from the list.)
// 下面的處理,是因?yàn)樵趎et模塊中,和在本模塊中,重新啟用一個計(jì)時器的方法有區(qū)別
if (this._handle) {
// 該分支處理,應(yīng)該是為了net模塊中做的處理
// 在本模塊中,暫時是沒有提及到該屬性的
this._handle.start(repeat, 0);
} else {
// 當(dāng)前的模塊中的回調(diào)函數(shù)
timer._idleTimeout = repeat;
exports.active(timer);
}
}
};
exports.clearInterval = function(timer) {
// 基本上,就是只有timer和repeat屬性存在的情況下
// 才表示timer對象,是出于Interval方法中,
// 這個時候,才去清理掉repeat屬性,然后clearTimeout的方法
// 清理掉該計(jì)時器
if (timer && timer._repeat) {
timer._repeat = false;
clearTimeout(timer);
}
};
timers中的源碼,就是這樣了,篇幅有限,本篇到這里就結(jié)束了,接下來的一篇關(guān)于timers模塊的文章,將就本篇的源碼,結(jié)合一些示例,進(jìn)行一些說明。
總結(jié)
像這樣的一些模塊,感覺突然不知道怎么寫了,如果整篇的去放這個源碼,感覺這樣的文章,完全沒有意義的,這樣的話,還是應(yīng)該分開寫的吧。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
實(shí)例分析nodejs模塊xml2js解析xml過程中遇到的坑
這篇文章主要介紹了實(shí)例分析nodejs模塊xml2js解析xml過程中遇到的坑,涉及nodejs模塊xml2js解析xml過程中parseString方法參數(shù)使用技巧,需要的朋友可以參考下2017-03-03
在 Node.js 中使用 async 函數(shù)的方法
利用 async 函數(shù),你可以把基于 Promise 的異步代碼寫得就像同步代碼一樣。一旦你使用 async 關(guān)鍵字來定義了一個函數(shù),那你就可以在這個函數(shù)內(nèi)使用 await 關(guān)鍵字。下面通過本文給大家分享Node.js 中使用 async 函數(shù)的方法,一起看看吧2017-11-11
Node.js 使用遞歸實(shí)現(xiàn)遍歷文件夾中所有文件
這篇文章主要介紹了Node.js使用遞歸實(shí)現(xiàn)遍歷文件夾中所有文件,需要的朋友可以參考下2017-09-09
vscode安裝教程以及配置node.js環(huán)境全過程
這篇文章主要給大家介紹了關(guān)于vscode安裝教程以及配置node.js環(huán)境的相關(guān)資料,VSCode是一款由微軟開發(fā)的輕量級編輯器,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10
Visual?Studio?Code中npm腳本找不到圖文解決辦法
這篇文章主要給大家介紹了關(guān)于Visual?Studio?Code中npm腳本找不到的圖文解決辦法,做前端開發(fā)如果項(xiàng)目達(dá)到了一定的規(guī)模就離不開npm了,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07

