setTimeout是在系統啟動的時候掛載的全局函數。代碼在timer.js。
function setupGlobalTimeouts() {const timers = NativeModule.require('timers');global.clearImmediate = timers.clearImmediate;global.clearInterval = timers.clearInterval;global.clearTimeout = timers.clearTimeout;global.setImmediate = timers.setImmediate;global.setInterval = timers.setInterval;global.setTimeout = timers.setTimeout;}
我們先看一下setTimeout函數的代碼。
function setTimeout(callback, after, arg1, arg2, arg3) {if (typeof callback !== 'function') {throw new errors.TypeError('ERR_INVALID_CALLBACK');}var i, args;switch (arguments.length) {// fast casescase 1:case 2:break;case 3:args = [arg1];break;case 4:args = [arg1, arg2];break;default:args = [arg1, arg2, arg3];for (i = 5; i < arguments.length; i++) {// extend array dynamically, makes .apply run much faster in v6.0.0args[i - 2] = arguments[i];}break;}// 新建一個對象,保存回調,超時時間等數據,是超時哈希隊列的節點const timeout = new Timeout(callback, after, args, false, false);// 啟動超時器active(timeout);// 返回一個對象return timeout;
}
其中Timeout函數在lib/internal/timer.js里定義。
function Timeout(callback, after, args, isRepeat, isUnrefed) {after *= 1; // coalesce to number or NaNthis._called = false;this._idleTimeout = after;this._idlePrev = this;this._idleNext = this;this._idleStart = null;this._onTimeout = null;this._onTimeout = callback;this._timerArgs = args;this._repeat = isRepeat ? after : null;this._destroyed = false;this[unrefedSymbol] = isUnrefed;this[async_id_symbol] = ++async_id_fields[kAsyncIdCounter];this[trigger_async_id_symbol] = getDefaultTriggerAsyncId();if (async_hook_fields[kInit] > 0) {emitInit(this[async_id_symbol],'Timeout',this[trigger_async_id_symbol],this);}
}
由代碼可知,首先創建一個保存相關信息的對象,然后執行active函數。
const active = exports.active = function(item) {// 插入一個超時對象到超時隊列insert(item, false);
}
function insert(item, unrefed, start) {// 超時時間const msecs = item._idleTimeout;if (msecs < 0 || msecs === undefined) return;// 如果傳了start則計算是否超時時以start為起點,否則取當前的時間if (typeof start === 'number') {item._idleStart = start;} else {item._idleStart = TimerWrap.now();}// 哈希隊列const lists = unrefed === true ? unrefedLists : refedLists;var list = lists[msecs];// 沒有則新建一個隊列if (list === undefined) {debug('no %d list was found in insert, creating a new one', msecs);lists[msecs] = list = new TimersList(msecs, unrefed);}...// 把超時節點插入超時隊列L.append(list, item);assert(!L.isEmpty(list)); // list is not empty
}
從上面的代碼可知,active一個定時器實際上是把新建的timeout對象掛載到一個哈希隊列里。我們看一下這時候的內存視圖。

當我們創建一個timerList的是時候,就會關聯一個底層的定時器,執行setTimeout時傳進來的時間是一樣的,都會在一條隊列中進行管理,該隊列對應一個定時器,當定時器超時的時候,就會在該隊列中找出超時節點。下面我們看一下new TimeWraper的時候發生了什么。
TimerWrap(Environment* env, Local<Object> object) : HandleWrap(env, object,reinterpret_cast<uv_handle_t*>(&handle_),AsyncWrap::PROVIDER_TIMERWRAP) {int r = uv_timer_init(env->event_loop(), &handle_);CHECK_EQ(r, 0);}
其實就是初始化了一個libuv的uv_timer_t結構體。然后接著start函數做了什么操作。
static void Start(const FunctionCallbackInfo<Value>& args) {TimerWrap* wrap = Unwrap<TimerWrap>(args.Holder());CHECK(HandleWrap::IsAlive(wrap));int64_t timeout = args[0]->IntegerValue();int err = uv_timer_start(&wrap->handle_, OnTimeout, timeout, 0);args.GetReturnValue().Set(err);}
就是啟動了剛才初始化的定時器。并且設置了超時回調函數是OnTimeout。這時候,就等定時器超時,然后執行OnTimeout函數。所以我們繼續看該函數的代碼。
const uint32_t kOnTimeout = 0;static void OnTimeout(uv_timer_t* handle) {TimerWrap* wrap = static_cast<TimerWrap*>(handle->data);Environment* env = wrap->env();HandleScope handle_scope(env->isolate());Context::Scope context_scope(env->context());wrap->MakeCallback(kOnTimeout, 0, nullptr);}
OnTimeout函數繼續調kOnTimeout,但是該變量在time_wrapper.c中是一個整形,這是怎么回事呢?這時候需要回lib/timer.js里找答案。
const kOnTimeout = TimerWrap.kOnTimeout | 0;
// adds listOnTimeout to the C++ object prototype, as
// V8 would not inline it otherwise.
// 在TimerWrap中是0,給TimerWrap對象掛一個超時回調,每次的超時都會執行該回調
TimerWrap.prototype[kOnTimeout] = function listOnTimeout() {// 拿到該底層定時器關聯的超時隊列,看TimersListvar list = this._list;var msecs = list.msecs;//if (list.nextTick) {list.nextTick = false;process.nextTick(listOnTimeoutNT, list);return;}debug('timeout callback %d', msecs);var now = TimerWrap.now();debug('now: %d', now);var diff, timer;// 取出隊列的尾節點,即最先插入的節點,最可能超時的,TimeOut對象while (timer = L.peek(list)) {diff = now - timer._idleStart;// Check if this loop iteration is too early for the next timer.// This happens if there are more timers scheduled for later in the list.// 最早的節點的消逝時間小于設置的時間,說明還沒超時,并且全部節點都沒超時,直接返回if (diff < msecs) {// 算出最快超時的節點還需要多長時間超時var timeRemaining = msecs - (TimerWrap.now() - timer._idleStart);if (timeRemaining < 0) {timeRemaining = 1;}// 重新設置超時時間this.start(timeRemaining);debug('%d list wait because diff is %d', msecs, diff);return;}// The actual logic for when a timeout happens.// 當前節點已經超時 L.remove(timer);assert(timer !== L.peek(list));if (!timer._onTimeout) {if (async_hook_fields[kDestroy] > 0 && !timer._destroyed &&typeof timer[async_id_symbol] === 'number') {emitDestroy(timer[async_id_symbol]);timer._destroyed = true;}continue;}// 執行超時處理tryOnTimeout(timer, list);}
由上可知,TimeWrapper.c里的kOnTimeout字段已經被改寫成一個函數,所以底層的定時器超時時會執行上面的代碼,即從定時器隊列中找到超時節點執行,直到遇到第一個未超時的節點,然后重新設置超時時間。再次啟動定時器。