摘要:現(xiàn)在,我們可以開始探討介面的設(shè)計(jì)模式了。匯出命名空間一個(gè)簡(jiǎn)單且常用的設(shè)計(jì)模式就是匯出一個(gè)包含數(shù)個(gè)屬性的物件,這些屬性具體的內(nèi)容主要是函式,但並不限於函式。如此,我們就能夠透過匯入該模組來取得這個(gè)命名空間下一系列相關(guān)的功能。
前言
這篇文章試著要整理,翻譯Export This: Interface Design Patterns for Node.js Modules這篇非常值得一讀的文章。
但因?yàn)檫@篇文章有些時(shí)日了,部分範(fàn)例已經(jīng)不符合現(xiàn)況。故這是一篇加上小弟收集彙整而成的更新翻譯。
當(dāng)你在 Node 中載入一個(gè)模組,我們到底會(huì)取回什麼?當(dāng)我們撰寫一個(gè)模組時(shí)我們又有哪些選擇可以用來設(shè)計(jì)程式的介面?
在我第一次學(xué)習(xí) Node 的時(shí)候,發(fā)現(xiàn)在 Node 中有太多的方式處理這個(gè)問題,由於 Javascript 本身非常彈性,加上在社群中的開發(fā)者們各自都有不同的實(shí)作風(fēng)格,這讓當(dāng)時(shí)的我感到有點(diǎn)挫折。
在原文作者的學(xué)習(xí)旅程中曾持續(xù)的觀察尋找好的方式以應(yīng)用在其的工作上,在這篇文章中將會(huì)分享觀察到的 Node 模組設(shè)計(jì)方式。
大略總結(jié)了 7 種設(shè)計(jì)模式(pattern)
匯出命名空間 Namespace
匯出函式 Function
匯出高階函式 High-Order Function
匯出建構(gòu)子/構(gòu)建函式 Constructor
匯出單一實(shí)例物件 Singleton
擴(kuò)展全域物件 Extend Global Object
套用猴子補(bǔ)丁 Monkey Patch
require, exports 和 module.exports首先我們需要先聊點(diǎn)基礎(chǔ)的知識(shí)
在 Node 官方文件中定義了匯入一個(gè)檔案就是匯入一個(gè)模組。
In Node.js, files and modules are in one-to-one correspondence. - Node 文件
每個(gè)模組內(nèi)都有一個(gè)隱式(implicit)的 module 物件,這個(gè)物件身上有一個(gè)屬性 exports (即 module.exports)。當(dāng)我們使用 require() 時(shí)它所傳回的,即是這個(gè)模組的 module.exports 所指向的東西。
每個(gè)模組的 module.exports 預(yù)設(shè)都是一個(gè)空物件 {},而每個(gè)模組內(nèi)帶有一個(gè) exports 捷徑變數(shù),預(yù)設(shè)指向 module.exports。
在預(yù)設(shè)情況下,module.exports 是一個(gè)物件,而 exports 又指向該物件,所以在預(yù)設(shè)情況下,以下兩個(gè)操作視為等效:
exports.foo = "hello"; // 等同於 module.exports.foo = "hello";
如果,你重新指定了某東西給 module.exports,當(dāng)然 module.exports 跟預(yù)設(shè)物件的關(guān)係就因此被打斷,而此時(shí) exports 捷徑變數(shù)仍然是指向預(yù)設(shè)物件的。前面有提到,當(dāng)我們使用 require() 時(shí),模組是以 module.exports 所指向的東西而揭露給外部,因此模組的介面是由 module.exports 所決定,與 exports 捷徑變數(shù)無關(guān)。
因此,一旦 module.exports 被指定新值後,使用捷徑變數(shù) exports 所掛入預(yù)設(shè)物件的內(nèi)容都將無法被揭露,因而可視為無效。
為了讓您更好理解關(guān)於 exports 與 module.exports 下面的範(fàn)例提供了較詳細(xì)的說明
var a = { id: 1 } var b = a console.log(a) // {id: 1} console.log(b) // {id: 1} // b 參考指向 a,意味著修改 b 的屬性 a 會(huì)跟著變動(dòng) b.id = 2 console.log(a) // {id: 2} console.log(b) // {id: 2} // 但如果將一個(gè)全新的物件賦予 b 那麼參考的關(guān)係將會(huì)中斷 b = { id: 3 } console.log(a) // {id: 2} console.log(b) // {id: 3}
另外比較具體的範(fàn)例
// 模組預(yù)設(shè): module.exports = {}, 而 exports = module.exports, 等同於指向預(yù)設(shè)的空物件 /* person.js */ exports.name = function () { console.log("My name is andyyou.") } ... /* main.js */ var person = require("./person.js") person.name()
/* person.js */ module.exports = "Hey, andyyou" // module.exports 不再是預(yù)設(shè)物件 exports.name = function () { // 透過 exports 捷徑掛進(jìn)預(yù)設(shè)物件的內(nèi)容, 將無法被揭露 console.log("My name is andyyou") } /* main.js */ var person = require("./person.js") // exports 的屬性被忽略了 person.name() // TypeError: Object Hey, andyyou has no method "name"
exports 只是指向 module.exports 的參考(Reference)
module.exports 初始值為 {} 空物件,於是 exports 也會(huì)取得該空物件
require() 回傳的是 module.exports 而不是 exports
所以您可以使用 exports.property_name = something 而不會(huì)使用 exports = something
一旦使用 exports = something 參考關(guān)係便會(huì)停止,也就是 exports 的資料都會(huì)被忽略。
本質(zhì)上我們可以理解為所有模組都隱含實(shí)作了下面這行程式碼 (實(shí)際上 module 與 exports 是由 node 模組系統(tǒng)傳入給我們的模組的)
var exports = module.exports = {}
現(xiàn)在我們知道了,當(dāng)我們要匯出一個(gè) function 時(shí)我們得使用 module.exports。
如果令捷徑 exports 變數(shù)指向該 function,僅僅是捷徑 exports 對(duì) module.exports 的關(guān)係被打斷而已,真正揭露的還是 module.exports。
另外,我們?cè)谠S多專案看到下面的這行程式碼
exports = module.exports = something
這行程式碼作用就是確保 exports 在 module.exports 被我們覆寫之後,仍可以指向相同的參考。
接著我們就可以透過 module.exports 來定義並匯出一個(gè) function
/* function.js */ module.exports = function () { return { name: "andyyou" } }
使用的方式則是
var func = require("./function")
關(guān)於 require 一個(gè)很重要的行為就是它會(huì)快取(Cache) module.exports 的值,未來每一次 require 被調(diào)用時(shí)都會(huì)回傳相同的值。
它會(huì)根據(jù)匯入檔案的絕對(duì)路徑來快取,所以當(dāng)我們想要模組能夠回傳不同得值時(shí),我們就需要匯出 function,如此一來每次執(zhí)行函式時(shí)就會(huì)回傳一個(gè)新值。
下面在 Node REPL 中簡(jiǎn)易的示範(fàn)
$ node > f1 = require("/Users/andyyou/Projects/export_this/function") [Function] > f2 = require("./function") // 相同路徑 [Function] > f1 === f2 true > f1() === f2() false
您可以觀察到 require 回傳了同樣的函式物件實(shí)例,但每一次調(diào)用函式回傳的物件是不同的。
更詳細(xì)的介紹可以參考官方文件,值得一讀。
現(xiàn)在,我們可以開始探討介面的設(shè)計(jì)模式(pattern)了。
匯出命名空間一個(gè)簡(jiǎn)單且常用的設(shè)計(jì)模式就是匯出一個(gè)包含數(shù)個(gè)屬性的物件,這些屬性具體的內(nèi)容主要是函式,但並不限於函式。
如此,我們就能夠透過匯入該模組來取得這個(gè)命名空間下一系列相關(guān)的功能。
當(dāng)您匯入一個(gè)命名空間類型的模組時(shí),我們通常會(huì)將模組指定到某一個(gè)變數(shù),然後透過它的成員(物件屬性)來存取使用這些功能。
甚至我們也可以將這些變數(shù)成員直接指定到區(qū)域變數(shù)。
var fs = require("fs") var readFile = fs.readFile var ReadStream = fs.ReadStream readFile("./file.txt", function (err, data) { console.log("readFile contents: %s", data) })
這便是fs 核心模組的做法
var fs = exports
首先用一個(gè)新的區(qū)域變數(shù) fs,令其為捷徑 exports,因此預(yù)設(shè)情況下 fs 就指向了 module.exports 身上的預(yù)設(shè)物件。上面這一行我們可以說,只是將捷徑變數(shù)換個(gè)名稱成為 fs 如此而已。
接下來,我們就可以使用新的捷徑 fs 了,例如: fs.Stats = binding.Stats。
fs.readFile = function (path, options, callback_) { // ... }
其他東西也是一樣的作法,例如匯出建構(gòu)子
fs.ReadStream = ReadStream function ReadStream(path, options) { // ... } ReadStream.prototype.open = function () { // ... }
當(dāng)匯出命名空間時(shí),您可以指定屬性到 exports ,就像 fs 的作法,又或者可以建立一個(gè)新的物件指派給 module.exports
/* exports 作法 */ exports.verstion = "1.0" /* 或者 module.exports 作法 */ module.exports = { version: "1.0", doYourTasks: function () { // ... } }
一個(gè)常見的作法就是透過一個(gè)根模組(root)來彙整並匯出其他模組,如此一來只需要一個(gè) require 便可以使用所有的模組。
原文作者在Good Eggs工作時(shí),會(huì)將資料模型(Model)拆分成個(gè)別的模組,並使用匯出建構(gòu)子的方式匯出(請(qǐng)參考下文介紹),然後透過一個(gè) index 檔案 來集合該目錄下所有的資料模型並一起匯出,如此一來在 models 命名空間下的所有資料模型都可以使用
var models = require("./models") var User = models.User var Product = models.Product
在 ES2015 和 CoffeeScript 中我們甚至還可以使用解構(gòu)指派來匯入我們需要的功能
/* CoffeeScript */ {User, Product} = require "./models" /* ES2015 */ import {User, Product} from "./models"
而剛剛提到的 index.js 大概就如下
exports.User = require("./User") exports.Person = require("./person")
實(shí)際上這樣分開的寫法還有更精簡(jiǎn)的寫法,我們可以透過一個(gè)小小的函式庫(kù)來匯入在同一階層中所有檔案並搭配 CamelCase 的命名規(guī)則匯出。
於是在我們的 index.js 中看起來就會(huì)如下
module.exports = require("../lib/require_siblings")(__filename)匯出函式
另外一個(gè)設(shè)計(jì)模式是匯出函式當(dāng)作該模組的介面。常見的作法是匯出一個(gè)工廠函式(Factory function),然後呼叫並回傳一個(gè)物件。
在使用 Express.js 的時(shí)候便是這種作法
var express = require("express") var app = express() // 實(shí)際上 express() 傳回的 app 是一個(gè) function,在 JS 中函式也是物件 app.get("/hello", function (req, res, next) { res.send("Hi there! We are using Express v" + express.version) })
Express 匯出該函式,讓我們可以用來建立一個(gè)新的 express 應(yīng)用程式。
在使用這種模式時(shí),通常我們會(huì)使用 factory function 搭配參數(shù)讓我們可以設(shè)定並回傳初始化後的物件。
想要匯出 function,我們就一定要使用 module.exports ,Express 便是這麼做
exports = module.exports = createApplication ... function createApplication () { ... }
上面指派了 createApplication 函式到 module.exports 然後再指給 exports 確保參考一致。
同時(shí) Express 也使用下面這種方式將導(dǎo)出函式當(dāng)作命名空間的作法使用。
exports.version = "3.1.1"
這邊要大略解釋一下由於 Javascript 原生並沒有支援命名空間的機(jī)制,於是大部分在 JS 中提到的 namespace 指的就是透過物件封裝的方式來達(dá)到 namespace 的效果,也就是第一種設(shè)計(jì)模式。
注意!並沒有任何方式可以阻止我們將匯出的函式作為命名空間物件使用,我們可以用其來引用其他的 function,建構(gòu)子,物件。
Express 3.3.2 / 2013-07-03 之後已經(jīng)將 exports.version 移除了
另外在匯出函式的時(shí)候最好為其命名,如此一來當(dāng)出錯(cuò)的時(shí)候我們比較容易從錯(cuò)誤堆疊資訊中找到問題點(diǎn)。
下面是兩個(gè)簡(jiǎn)單的例子:
/* bomb1.js */ module.exports = function () { throw new Error("boom") }
module.exports = function bomb() { throw new Error("boom") }
$ node > bomb = require("./bomb1"); [Function] > bomb() Error: boom at module.exports (/Users/andyyou/Projects/export_this/bomb1.js:2:9) at repl:1:2 ... > bomb = require("./bomb2"); [Function: bomb] > bomb() Error: boom at bomb (/Users/andyyou/Projects/export_this/bomb2.js:2:9) at repl:1:2 ...
匯出函式還有些比較特別的案例,值得用另外的名稱以區(qū)分它們的不同。
匯出高階函式一個(gè)高階函式或 functor 基本上就是一個(gè)函式可以接受一個(gè)或多個(gè)函式為其輸入或輸出。而這邊我們要談?wù)摰尼嵴?- 一個(gè)函式回傳函式
當(dāng)我們想要模組能夠根據(jù)輸入控制回傳函式的行為時(shí),匯出一個(gè)高階函式就是一種非常實(shí)用的設(shè)計(jì)模式。
補(bǔ)充:functor & monad
舉例來說 Connect 就提供了許多可掛載的功能給網(wǎng)頁框架。
這裡的 middleware 我們先理解成一個(gè)有三個(gè)參數(shù) (req, res, next) 的 function。
Express 從 v4.x 版之後不再相依於 connect
connect middleware 慣例就是匯出的 function 執(zhí)行後,要回傳一個(gè) middleware function。
在處理 request 的過程中這個(gè)回傳的 middleware function 就可以接手使用剛剛提到的三個(gè)參數(shù),用來在過程中做一些處理或設(shè)定。
同時(shí)因?yàn)殚]包的特性這些設(shè)定在整個(gè)中介軟體的處理流程中都是有效的。
舉例來說,compression 這個(gè) middleware 就可以在處理 responsive 過程中協(xié)助壓縮
var connect = require("connect") var app = connect() // gzip outgoing responses var compression = require("compression") app.use(compression())
而它的原始碼看起來就如下
module.exports = compression ... function compression (options) { ... return function compression (req, res, next) { ... next() } }
於是每一個(gè) request 都會(huì)經(jīng)過 compression middleware 處理,而代入的 options 也因?yàn)殚]包的關(guān)係會(huì)被保留下來
這是一種極具彈性的模組作法,也可能在您的開發(fā)項(xiàng)目上幫上許多忙。
匯出建構(gòu)子middleware 在這裡您可以大略想成串連執(zhí)行一系列的 function,自然其 Function Signature 要一致
在一般物件導(dǎo)向語言中,constructor 建構(gòu)子指的是協(xié)助我們從類別 Class建立一個(gè)該類別物件實(shí)例的一段程式碼。
// C# class Car { // c# 建構(gòu)子 // constructor 即 class 中用來初始化物件的 method。 public Car(name) { name = name; } } var car = new Car("BMW");
由於在 ES2015 之前 Javascript 並不支援類別,某種程度上在 Javascript 之中我們可以把任何一個(gè) function 當(dāng)作類別,或者說一個(gè) function 可以當(dāng)作 function 執(zhí)行或者搭配 new 關(guān)鍵字當(dāng)作 constructor 來使用。如果想知道更詳細(xì)的介紹可以閱讀MDN 教學(xué)。
欲匯出建構(gòu)子,我們需要透過構(gòu)造函式來定義類別,然後透過 new 來建立物件實(shí)例。
function Person (name) { this.name = name } Person.prototype.greet = function () { return "Hi, I am " + this.name } var person = new Person("andyyou") console.log(person.greet()) // Hi, I am andyyou
在這種設(shè)計(jì)模式底下,我們通常會(huì)將每個(gè)檔案設(shè)計(jì)成一個(gè)類別,然後匯出建構(gòu)子。這使得我們的專案架構(gòu)更加清楚。
var Person = require("./person") var person = new Person()
整個(gè)檔案看起來會(huì)如下
/* person.js */ function Person(name) { this.name = name } Person.prototype.greet = function () { return "Hi, I am " + this.name } exports = module.exports = Person匯出單一物件實(shí)例 Signleton
當(dāng)我們需要所有的模組使用者共享物件的狀態(tài)與行為時(shí),就需要匯出單一物件實(shí)例。
Mongoose是一個(gè) ODM(Object-Document Mapper)函式庫(kù),讓我們可以使用程式中的 Model 物件去操作 MongoDB。
var mongoose = require("mongoose") mongoose.connect("mongodb://localhost/test") var Cat = mongoose.model("Cat", {name: String}) var kitty = new Cat({name: "Zildjian"}) kitty.save(function (err) { if (err) throw Error("save failed") console.log("meow") })
那我們 require 取得的 mongoose 物件是什麼東西呢?事實(shí)上 mongoose 模組的內(nèi)部是這麼處理的
function Mongoose() { ... } module.exports = exports = new Mongoose()
因?yàn)?require 的快取了 module.exports 的值,於是所有 reqire("mongoose") 將會(huì)回傳相同的物件實(shí)例,之後在整個(gè)應(yīng)用程式之中使用的都會(huì)是同一個(gè)物件。
Mongoose 使用物件導(dǎo)向的設(shè)計(jì)模式來封裝,解耦(分離功能之間的相依性),維護(hù)狀態(tài)使整體具備可讀性,同時(shí)透過匯出一個(gè) Mongoose Class 的物件給使用者,讓我們可以簡(jiǎn)單的存取使用。
如果我們有需要,它也可以建立其他的物件實(shí)例來作為命名空間使用。實(shí)際上 Mongoose 內(nèi)部提供了存取建構(gòu)子的方法
Mongoose.prototype.Mongoose = Mongoose
因此我們可以這麼做
var mongoose = require("mongoose") var Mongoose = mongoose.Mongoose var anotherMongoose = new Mongoose() anotherMongoose.connect("mongodb://localhost/test")擴(kuò)展全域物件
一個(gè)被匯入的模組不只限於單純?nèi)〉闷鋮R出的資料。它也可以用來修改全域物件或回傳全域物件,自然也能定義新的全域物件。而在這邊的全域物件(Global objects)或稱為標(biāo)準(zhǔn)內(nèi)建物件像是 Object, Function, Array 指的是在全域能存取到的物件們,而不是當(dāng) Javascript 開始執(zhí)行時(shí)所產(chǎn)生代表 global scope 的 global object。
當(dāng)我們需要擴(kuò)增或修改全域物件預(yù)設(shè)行為時(shí)就需要使用這種設(shè)計(jì)模式。當(dāng)然這樣的方式是有爭(zhēng)議,您必須謹(jǐn)慎使用,特別是在開放原始碼的專案上。
例如:Should.js是一個(gè)常被用在單元測(cè)試中用來判斷分析 值 是否正確的函式庫(kù)。
require("should") var user = { name: "andyyou" } user.name.should.equal("andyyou")
這樣您是否比較清楚了,should.js 增加了底層的 Object 的功能,加入了一個(gè)非列舉型的屬性 should ,讓我們可以用簡(jiǎn)潔的語法來撰寫單元測(cè)試。
而在內(nèi)部 should.js 做了這樣的事情
var should = function (obj) { return new Assertion(util.isWrapperType(obj) ? obj.valueOf(): obj) } ... exports = module.exports = should Object.defineProperty(Object.prototype, "should", { set: function(){}, get: function(){ return should(this); }, configurable: true });
就算看到這邊您肯定跟我一樣有滿滿的疑惑,全域物件擴(kuò)展定義跟 exports 有啥關(guān)聯(lián)呢?
事實(shí)上
/* whoami.js */ exports = module.exports = { name: "andyyou" } Object.defineProperty(Object.prototype, "whoami", { set: function () {}, get: function () { return "I am " + this.name } }) /* app.js */ var whoami = require("whoami") console.log(whoami) // { name: "andyyou" } var obj = { name: "lena" } console.log(obj.whoami) // I am lena
現(xiàn)在我們明白了上面說的修改全域物件的意思了。should.js 匯出了一個(gè) should 函式但是它主要的使用方式則是把 should 加到 Object 屬性上,透過物件本身來呼叫。
套用猴子補(bǔ)丁(Monkey Patch)在這邊所謂的猴子補(bǔ)丁特別指的是在執(zhí)行時(shí)期動(dòng)態(tài)修改一個(gè)類別、模組或物件(也稱 object augmentation),通常會(huì)這麼做是希望補(bǔ)強(qiáng)某的第三方套件的 bug 或功能。
假設(shè)某個(gè)模組沒有提供您客製化功能的介面,而您又需要這個(gè)功能的時(shí)候,我們就會(huì)實(shí)作一個(gè)模組來補(bǔ)強(qiáng)既有的模組。
這個(gè)設(shè)計(jì)模式有點(diǎn)類似擴(kuò)展全域物件,但並非修改全域物件,而是依靠 Node 模組系統(tǒng)的快取機(jī)制,當(dāng)其他程式碼匯入該模組時(shí)去補(bǔ)強(qiáng)該模組的實(shí)例物件。
預(yù)設(shè)來說 Mongoose 會(huì)使用小寫以及複數(shù)的慣例替資料模型命名。例如一個(gè)資料模型叫做 CreditCard 最終我們會(huì)得到 collection 的名稱是 creditcards 。假如我們希望可以換成 credit_cards 並且其他地方也遵循一樣的用法。
下面是我們?cè)囍褂煤镒友a(bǔ)丁的方式來替既有的模組增加功能
var pluralize = require("pluralize") // 處理複數(shù)單字的函式庫(kù) var mongoose = require("mongoose") var Mongoose = mongoose.Mongoose mongoose.Promise = global.Promise // v4.1+ http://mongoosejs.com/docs/promises.html var model = Mongoose.prototype.model // 補(bǔ)丁 var fn = function(name, schema, collection, skipInit) { collection = collection || pluralize.plural(name.replace(/([a-zd])([A-Z])/g, "$1_$2").toLowerCase()) return model.call(this, name, schema, collection, skipInit) } Mongoose.prototype.model = fn /* 實(shí)際測(cè)試 */ mongoose.connect("mongodb://localhost/test") var CreditCardSchema = new mongoose.Schema({number: String}) var CreditCardModel = mongoose.model("CreditCard", CreditCardSchema); var card = new CreditCardModel({number: "5555444433332222"}); card.save(function (err) { if (err) { console.log(err) } console.log("success") })
您不該輕易使用上面這種方式補(bǔ)丁,這邊只是為了說明猴子補(bǔ)丁這種方式,mongoose 已經(jīng)有提供官方的方式設(shè)定名稱
var schema = new Schema({..}, { collection: "your_collection_name" })
當(dāng)這個(gè)模組第一次被匯入的時(shí)候便會(huì)讓 mongoose 重新定義 Mongoose.prototype.model 並將其設(shè)回原本的 model 的實(shí)作。
如此一來所有 Mongoose 的實(shí)例物件都具備新的行為了。注意到這邊並沒有修改 exports 所以當(dāng)我們 require 的時(shí)候得到的是預(yù)設(shè)的物件
另外如果您想使用上面這種補(bǔ)丁的方式時(shí),記得閱讀原始碼並注意是否產(chǎn)生衝突。
請(qǐng)善用匯出的功能Node模組系統(tǒng)提供了一個(gè)簡(jiǎn)單的機(jī)制來封裝功能,使我們能夠建立了清楚的介面。希望掌握這七種設(shè)計(jì)模式提供不同的優(yōu)缺點(diǎn)能對(duì)您有所幫助。
在這邊作者並沒有徹底的調(diào)查所有的方式,一定有其他選項(xiàng)可供選擇,這邊只有描述幾個(gè)最常見且不錯(cuò)的方法。
小結(jié)
namespace: 匯出一個(gè)物件包含需要的功能
root module 的方式,使用一個(gè)根模組匯出其他模組
function: 直接將 module.exports 設(shè)為 function
Function 物件也可以拿來當(dāng)作命名空間使用
為其命名方便偵錯(cuò)
exports = module.exports = something 的作法是為了確保參考(Reference)一致
high-order function: 可以透過代入?yún)?shù)控制並回傳 function 。
可協(xié)助實(shí)作 middleware 的設(shè)計(jì)模式
換句話說 middleware 即一系列相同 signature 的 function 串連。一個(gè)接一個(gè)執(zhí)行
constructor: 匯出類別(function),使用時(shí)再 new,具備 OOP 的優(yōu)點(diǎn)
singleton: 匯出單一物件實(shí)例,重點(diǎn)在各個(gè)檔案可以共享物件狀態(tài)
global objects: 在全域物件作的修改也會(huì)一起被匯出
monkey patch: 執(zhí)行時(shí)期,利用 Node 快取機(jī)制在 instance 加上補(bǔ)丁
筆記一個(gè) javascript 檔案可視為一個(gè)模組
解決特定問題或需求,功能完整由單一或多個(gè)模組組合而成的整體稱為套件(package)
require 匯入的模組具有自己的 scope
exports 只是 module.exports 的參考,exports 會(huì)記錄收集屬性如果 module.exports 沒有任何屬性就把其資料交給 module.exports ,但如果 module.exports 已經(jīng)具備屬性的話,那麼exports 的所有資料都會(huì)被忽略。
就算 exports 置於後方仍會(huì)被忽略
Node 初始化的順序
Native Module -> Module
StartNodeInstance() -> CreateEnvironment() -> LoadEnvironment() -> Cached
Native Module 載入機(jī)制
檢查是否有快取
-> 有; 直接回傳 this.exports
-> 沒有; new 一個(gè)模組物件
cache()
compile() -> NativeModule.wrap() 將原始碼包進(jìn) function 字串 -> runInThisContext() 建立函式
return NativeModule.exports
Node 的 require 會(huì) cache ,也就是說:如果希望模組產(chǎn)生不同的 instance 時(shí)應(yīng)使用 function
資源官方 module 文件
理解 module exports
Export This: Interface Design Patterns for Node.js Modules
module.exports v.s exports
從 node.js 原始碼看 exports 與 module.exports
Export This 中文
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/80417.html
摘要:不過到底是怎麼保留的另外為什麼一個(gè)閉包可以一直使用區(qū)域變數(shù),即便這些變數(shù)在該內(nèi)已經(jīng)不存在了為了解開閉包的神秘面紗,我們將要假裝沒有閉包這東西而且也不能夠用嵌套來重新實(shí)作閉包。 原文出處: 連結(jié) 話說網(wǎng)路上有很多文章在探討閉包(Closures)時(shí)大多都是簡(jiǎn)單的帶過。大多的都將閉包的定義濃縮成一句簡(jiǎn)單的解釋,那就是一個(gè)閉包是一個(gè)函數(shù)能夠保留其建立時(shí)的執(zhí)行環(huán)境。不過到底是怎麼保留的? 另外...
摘要:的架構(gòu)設(shè)計(jì)促使第三方開發(fā)者讓核心發(fā)揮出無限的潛力。當(dāng)然建置比起開發(fā)是較進(jìn)階的議題,因?yàn)槲覀儽仨氁斫鈨?nèi)部的一些事件。這個(gè)編譯結(jié)果包含的訊息包含模組的狀態(tài),編譯後的資源檔,發(fā)生異動(dòng)的檔案,被觀察的相依套件等。 本文將對(duì) webpack 周邊的 middleware 與 plugin 套件等作些介紹,若您對(duì)於 webpack 還不了解可以參考這篇彙整的翻譯。 webpack dev ser...
摘要:不過這個(gè)效果感覺上就像是閃一下就切換到該位置。為了使用體驗(yàn)上的感覺有時(shí)候網(wǎng)站會(huì)設(shè)計(jì)一種平滑捲動(dòng)到該位置的效果。的方式非常簡(jiǎn)單,只要在該元素設(shè)定注意是而不是這個(gè)方式非常方便不過目前只有支援,查閱。 眾所皆知 HTML 錨點(diǎn)(anchor link)透過給定標(biāo)籤 id 屬性跳到頁面上特定位置的功能。不過這個(gè)效果感覺上就像是閃一下就切換到該位置。為了使用體驗(yàn)上的感覺有時(shí)候網(wǎng)站會(huì)設(shè)計(jì)一種平滑捲...
摘要:不過這個(gè)效果感覺上就像是閃一下就切換到該位置。為了使用體驗(yàn)上的感覺有時(shí)候網(wǎng)站會(huì)設(shè)計(jì)一種平滑捲動(dòng)到該位置的效果。的方式非常簡(jiǎn)單,只要在該元素設(shè)定注意是而不是這個(gè)方式非常方便不過目前只有支援,查閱。 眾所皆知 HTML 錨點(diǎn)(anchor link)透過給定標(biāo)籤 id 屬性跳到頁面上特定位置的功能。不過這個(gè)效果感覺上就像是閃一下就切換到該位置。為了使用體驗(yàn)上的感覺有時(shí)候網(wǎng)站會(huì)設(shè)計(jì)一種平滑捲...
摘要:目錄許多開發(fā)者會(huì)把的目錄命名為但這並不強(qiáng)迫。所有的檔案都會(huì)使用從被編譯成。同時(shí)有個(gè)小小的重點(diǎn)那就是我們可已觀察編譯後的檔案大小。在專案目錄下執(zhí)行可以觀察截至目前為止的結(jié)果。我們的目標(biāo)是要把編譯封裝到我們的中。 在今時(shí)今日,webpack 已經(jīng)成為前端開發(fā)非常重要的工具之一。本質(zhì)上它是一個(gè) Javascript 模組封裝工具,但透過 loaders 和 plugins 它也可以轉(zhuǎn)換封裝其...
閱讀 727·2021-11-22 13:52
閱讀 1537·2021-09-27 13:36
閱讀 2840·2021-09-24 09:47
閱讀 2199·2021-09-22 15:48
閱讀 3612·2021-09-22 15:39
閱讀 1478·2019-08-30 12:43
閱讀 2930·2019-08-29 18:39
閱讀 3201·2019-08-29 12:51