至此已完成NodeJsInputFileSysten模塊的講解,下一步就是實際實用的模塊: 掛載到compiler對象上的輸入模塊其實是帶有緩存的輸入模塊,源碼整理如下(用ES6的class重寫): 這裡的核心是利用Storage來生成一個緩存容器,緩存對應的讀操作。 有兩個需要註意的地方。 一個是 ...
至此已完成NodeJsInputFileSysten模塊的講解,下一步就是實際實用的模塊:
compiler.inputFileSystem = new CachedInputFileSystem(new NodeJsInputFileSystem(), 60000);
掛載到compiler對象上的輸入模塊其實是帶有緩存的輸入模塊,源碼整理如下(用ES6的class重寫):
class CachedInputFileSystem { constructor() { // fileSystem => NodeJsInputFileSystem => graceful-fs => fs this.fileSystem = fileSystem; // 生成緩存容器 this._statStorage = new Storage(duration); this._readdirStorage = new Storage(duration); this._readFileStorage = new Storage(duration); this._readJsonStorage = new Storage(duration); this._readlinkStorage = new Storage(duration); this._stat = this.fileSystem.stat ? this.fileSystem.stat.bind(this.fileSystem) : null; if (!this._stat) this.stat = null; // ...more // 自定義JSON讀取 if (this.fileSystem.readJson) { this._readJson = this.fileSystem.readJson.bind(this.fileSystem); } else if (this.readFile) { this._readJson = function(path, callback) { /*...*/ }.bind(this); } else { this.readJson = null; } // sync... } stat(path, callback) { this._statStorage.provide(path, this._stat, callback); }; // readdir,readFile,readJson,readlink // sync... purge(what) { this._statStorage.purge(what); this._readdirStorage.purge(what); this._readFileStorage.purge(what); this._readlinkStorage.purge(what); this._readJsonStorage.purge(what); }; } module.exports = CachedInputFileSystem;
這裡的核心是利用Storage來生成一個緩存容器,緩存對應的讀操作。
有兩個需要註意的地方。
一個是purge方法,這個是Storage的原型方法,所以暫時先放著(形參名有點意思,叫what)。
第二個是這個模塊自定義了一個方法專門用來讀取JSON文件,源碼如下:
this._readJson = function(path, callback) { // fs.readFile讀取文件 this.readFile(path, function(err, buffer) { if (err) return callback(err); try { // 先將位元組流字元轉換成utf-8格式的字元串 // 再調用JSON.parse進行解析 var data = JSON.parse(buffer.toString("utf-8")); } catch (e) { return callback(e); } // 使用回調處理數據 callback(null, data); }); }.bind(this);
只是調用JSON.parse解析字元,這個方法只能專門處理JSON格式的數據,不然會報錯。
Storage
該模塊核心在於Storage對象,下麵就看一看Storage內部實現,源碼如下:
class Storage { constructor() { // duration => 60000 this.duration = duration; this.running = new Map(); this.data = new Map(); this.levels = []; if (duration > 0) { this.levels.push(new Set(), new Set(), new Set(), new Set(), new Set(), new Set(), new Set(), new Set(), new Set()); // (duration - 8000) / 500 => 52000 / 500 => 104 for (var i = 8000; i < duration; i += 500) this.levels.push(new Set()); } this.count = 0; this.interval = null; this.needTickCheck = false; this.nextTick = null; this.passive = true; this.tick = this.tick.bind(this); } ensureTick() { /*...*/ }; finished(name, err, result) { /*...*/ }; finishedSync(name, err, result) { /*...*/ }; provide(name, provider, callback) { /*...*/ }; provideSync(name, provider) { /*...*/ }; tick() { /*...*/ }; checkTicks() { /*...*/ }; purge(what) { /*...*/ }; }
構造函數中的Set與Map均為ES6新添加的數據結構,詳情自行查閱。
其中levels數組除去本身的9個Set,根據duration的值,再次加了104個Set對象,之後看具體含義。
接下來依次講解原型函數。
ensureTick
Storage.prototype.ensureTick = function() { // 第一調用進行初始化 // this.tick為定期執行的函數 // 執行間隔為 (60000 / 113)|0 = 530 if (!this.interval && this.duration > 0 && !this.nextTick) this.interval = setInterval(this.tick, Math.floor(this.duration / this.levels.length)); };
可以看出這是一個初始化的方法,初始化一個定時器,間隔取決於傳進來的duration。
做了一個測試,檢測從8000開始到60000定時間隔的變化:
let startNum = 8000, startLen = 9, result = []; for (; startNum < 60000; startNum += 500, startLen++) { result.push((startNum / startLen | 0)); }
輸出如圖:
由於levels的長度最低為9,當傳入8000時會達到最大值,所以間隔一定小於0.888秒,且隨著duration的值增加而減少,將duration設為100萬可以發現這個間隔在500會趨於平緩,大部分暫且可以認為間隔是穩定在0.5秒~0.6秒。
checkTicks
Storage.prototype.checkTicks = function() { this.passive = false; if (this.nextTick) { // 無限執行tick直到返回true while (!this.tick()); } };
finished
Storage.prototype.finished = function(name, err, result) { // 獲取指定名字的回調事件流 var callbacks = this.running.get(name); this.running.delete(name); if (this.duration > 0) { // 設置進data this.data.set(name, [err, result]); // 獲取levels的第一個Set對象 var levelData = this.levels[0]; // 新增count才會+1 this.count -= levelData.size; levelData.add(name); this.count += levelData.size; this.ensureTick(); } // 遍歷執行回調 for (var i = 0; i < callbacks.length; i++) { callbacks[i](err, result); } };
不應用的話不知道是幹嘛用的。
finishedSync
Storage.prototype.finishedSync = function(name, err, result) { if (this.duration > 0) { // ...一模一樣 } };
provide
Storage.prototype.provide = function(name, provider, callback) { if (typeof name !== "string") { callback(new TypeError("path must be a string")); return; } var running = this.running.get(name); // 將回調函數加進runnning直接返回 if (running) { running.push(callback); return; } if (this.duration > 0) { this.checkTicks(); // 獲取data中對應的事件 非同步執行 var data = this.data.get(name); if (data) { return process.nextTick(function() { callback.apply(null, data); }); } } // 無法獲取running與data時 this.running.set(name, running = [callback]); var _this = this; provider(name, function(err, result) { _this.finished(name, err, result); }); };
該方法會先後嘗試從running與data中獲取對應的事件,無法獲取將設置到running中,並調用提供的provider方法。
tick
Storage.prototype.tick = function() { var decay = this.levels.pop(); for (var item of decay) { this.data.delete(item); } this.count -= decay.size; decay.clear(); // 清空後頭部插入 this.levels.unshift(decay); // 當沒有事件時初始化條件 if (this.count === 0) { clearInterval(this.interval); this.interval = null; this.nextTick = null; return true; } else if (this.nextTick) { this.nextTick += Math.floor(this.duration / this.levels.length); var time = new Date().getTime(); if (this.nextTick > time) { this.nextTick = null; // 初始化定時器 this.interval = setInterval(this.tick, Math.floor(this.duration / this.levels.length)); return true; } } else if (this.passive) { clearInterval(this.interval); this.interval = null; this.nextTick = new Date().getTime() + Math.floor(this.duration / this.levels.length); } else { this.passive = true; } };
這個方法在使用中再解釋吧。
purge
Storage.prototype.purge = function(what) { // 不傳參數 // 清空所有數據 if (!what) { this.count = 0; clearInterval(this.interval); this.nextTick = null; this.data.clear(); this.levels.forEach(function(level) { level.clear(); }); } // 傳字元串 // 移除data中所有以參數開頭的key else if (typeof what === "string") { for (var key of this.data.keys()) { if (key.startsWith(what)) this.data.delete(key); } } // 傳數組 // 遞歸批量移除 else { for (var i = what.length - 1; i >= 0; i--) { this.purge(what[i]); } } };
用於清空數據的方法。
總體來說,模塊內容如下:
1、levels數組 => 總數據源
2、running對象 => 存儲待運行回調事件流
3、data對象 => 存儲已完成事件流
4、count => 記錄levels數據數量
5、interval => 當前定時器的id
6、needTick,nextTick,passive均為標記
由於沒有應用,所以講起來十分僵硬,後面的源碼中會重新回來看這些方法。