[TOC]
# 模塊化
使用一個技術肯定是有原因的,那么使用模塊化可以給我們帶來以下好處
* 解決命名沖突
* 提供復用性
* 提高代碼可維護性
## 立即執行函數
在早期,使用立即執行函數實現模塊化是常見的手段,通過函數作用域解決了命名沖突、污染全局作用域的問題
~~~
(function(globalVariable){
globalVariable.test = function() {}
// ... 聲明各種變量、函數都不會污染全局作用域
})(globalVariable)
~~~
<br>
<br>
## AMD
RequireJS 是 AMD 規范的代表之作,它之所以能代表 AMD 規范,是因為 RequireJS 的作者 (James Burke) 就是 AMD 規范的提出者。同時作者還開發了[`amdefine`](https://github.com/jrburke/amdefine),一個讓你在 node 中也可以使用 AMD 規范的庫。
<br>
AMD 規范由 CommonJS 的 Modules/Transport/C 提案發展而來,毫無疑問,Modules/Transport/C 提案的發起者就是 James Burke。
<br>
James Burke 指出了 CommonJS 規范在瀏覽器上的一些不足:
1. 缺少模塊封裝的能力:CommonJS 規范中的每個模塊都是一個文件。這意味著每個文件只有一個模塊。這在服務器上是可行的,但是在瀏覽器中就不是很友好,瀏覽器中需要做到盡可能少的發起請求。
2. 使用同步的方式加載依賴:雖然同步的方法進行加載可以讓代碼更容易理解,但是在瀏覽器中使用同步加載會導致長時間白屏,影響用戶體驗。
3. CommonJS 規范使用一個名為`export`的對象來暴露模塊,將需要導出變量附加到`export`上,但是不能直接給該對象進行賦值。如果需要導出一個構造函數,則需要使用`module.export`,這會讓人感到很疑惑。
<br>
AMD 規范定義了一個`define`全局方法用來定義和加載模塊,當然 RequireJS 后期也擴展了`require`全局方法用來加載模塊 。通過該方法解決了在瀏覽器使用 CommonJS 規范的不足。
~~~
define(id?, dependencies?, factory);
~~~
<br>
1. 使用匿名函數來封裝模塊,并通過函數返回值來定義模塊,這更加符合 JavaScript 的語法,這樣做既避免了對`exports`變量的依賴,又避免了一個文件只能暴露一個模塊的問題。
2. 提前列出依賴項并進行異步加載,這在瀏覽器中,這能讓模塊開箱即用。
~~~
define("foo", ["logger"], function (logger) {
logger.debug("starting foo's definition")
return {
name: "foo"
}
})
~~~
3. 為模塊指定一個模塊 ID (名稱) 用來唯一標識定義中模塊。此外,AMD的模塊名規范是 CommonJS 模塊名規范的超集。
~~~
define("foo", function () {
return {
name: 'foo'
}
})
~~~
### RequireJS 原理
在討論原理之前,我們可以先看下 RequireJS 的基本使用方式。
* 模塊信息配置:
~~~
require.config({
paths: {
jquery: 'https://code.jquery.com/jquery-3.4.1.js'
}
})
~~~
* 依賴模塊加載與調用:
~~~
require(['jquery'], function ($){
$('#app').html('loaded')
})
~~~
* 模塊定義:
~~~
if ( typeof define === "function" && define.amd ) {
define( "jquery", [], function() {
return jQuery;
} );
}
~~~
我們首先使用`config`方法進行了 jquery 模塊的路徑配置,然后調用`require`方法加載 jquery 模塊,之后在回調中調用已加載完成的`$`對象。在這個過程中,jquery 會使用`define`方法暴露出我們所需要的`$`對象。
在了解了基本的使用過程后,我們就繼續深入 RequireJS 的原理。
#### 模塊信息配置
模塊信息的配置,其實很簡單,只用幾行代碼就能實現。定義一個全局對象,然后使用`Object.assign`進行對象擴展。
~~~
// 配置信息
const cfg = { paths: {} }
// 全局 require 方法
req = require = () => {}
// 擴展配置
req.config = config => {
Object.assign(cfg, config)
}
~~~
#### 依賴模塊加載與調用
`require`方法的邏輯很簡單,進行簡單的參數校驗后,調用`getModule`方法對`Module`進行了實例化,getModule 會對已經實例化的模塊進行緩存。因為 require 方法進行模塊實例的時候,并沒有模塊名,所以這里產生的是一個匿名模塊。Module 類,我們可以理解為一個模塊加載器,主要作用是進行依賴的加載,并在依賴加載完畢后,調用回調函數,同時將依賴的模塊逐一作為參數回傳到回調函數中。
~~~
// 全局 require 方法
req = require = (deps, callback) => {
if (!deps && !callback) {
return
}
if (!deps) {
deps = []
}
if (typeof deps === 'function') {
callback = deps
deps = []
}
const mod = getModule()
mod.init(deps, callback)
}
let reqCounter = 0
const registry = {} // 已注冊的模塊
// 模塊加載器的工廠方法
const getModule = name => {
if (!name) {
// 如果模塊名不存在,表示為匿名模塊,自動構造模塊名
name = `@mod_${++reqCounter}`
}
let mod = registry[name]
if (!mod) {
mod = registry[name] = new Module(name)
}
return mod
}
~~~
模塊加載器是是整個模塊加載的核心,主要包括`enable`方法和`check`方法。
模塊加載器在完成實例化之后,會首先調用`init`方法進行初始化,初始化的時候傳入模塊的依賴以及回調。
~~~
// 模塊加載器
class Module {
constructor(name) {
this.name = name
this.depCount = 0
this.depMaps = []
this.depExports = []
this.definedFn = () => {}
}
init(deps, callback) {
this.deps = deps
this.callback = callback
// 判斷是否存在依賴
if (deps.length === 0) {
this.check()
} else {
this.enable()
}
}
}
~~~
`enable`方法主要用于模塊的依賴加載,該方法的主要邏輯如下:
1. 遍歷所有的依賴模塊;
2. 記錄已加載模塊數 (`this.depCount++`),該變量用于判斷依賴模塊是否全部加載完畢;
3. 實例化依賴模塊的模塊加載器,并綁定`definedFn`方法;
> `definedFn`方法會在依賴模塊加載完畢后調用,主要作用是獲取依賴模塊的內容,并將`depCount`減 1,最后調用`check`方法 (該方法會判斷`depCount`是否已經小于 1,以此來界定依賴全部加載完畢);
4. 最后通過依賴模塊名,在配置中獲取依賴模塊的路徑,進行模塊加載。
~~~
class Module {
...
// 啟用模塊,進行依賴加載
enable() {
// 遍歷依賴
this.deps.forEach((name, i) => {
// 記錄已加載的模塊數
this.depCount++
// 實例化依賴模塊的模塊加載器,綁定模塊加載完畢的回調
const mod = getModule(name)
mod.definedFn = exports => {
this.depCount--
this.depExports[i] = exports
this.check()
}
// 在配置中獲取依賴模塊的路徑,進行模塊加載
const url = cfg.paths[name]
loadModule(name, url)
});
}
...
}
~~~
`loadModule`的主要作用就是通過 url 去加載一個 js 文件,并綁定一個 onload 事件。onload 會重新獲取依賴模塊已經實例化的模塊加載器,并調用`init`方法。
~~~
// 緩存加載的模塊
const defMap = {}
// 依賴的加載
const loadModule = (name, url) => {
const head = document.getElementsByTagName('head')[0]
const node = document.createElement('script')
node.type = 'text/javascript'
node.async = true
// 設置一個 data 屬性,便于依賴加載完畢后拿到模塊名
node.setAttribute('data-module', name)
node.addEventListener('load', onScriptLoad, false)
node.src = url
head.appendChild(node)
return node
}
// 節點綁定的 onload 事件函數
const onScriptLoad = evt => {
const node = evt.currentTarget
node.removeEventListener('load', onScriptLoad, false)
// 獲取模塊名
const name = node.getAttribute('data-module')
const mod = getModule(name)
const def = defMap[name]
mod.init(def.deps, def.callback)
}
~~~
看到之前的案例,因為只有一個依賴 (jQuery),并且 jQuery 模塊并沒有其他依賴,所以`init`方法會直接調用`check`方法。這里也可以思考一下,如果是一個有依賴項的模塊后續的流程是怎么樣的呢?
~~~
define( "jquery", [] /* 無其他依賴 */, function() {
return jQuery;
} );
~~~
`check`方法主要用于依賴檢測,以及調用依賴加載完畢后的回調。
~~~
// 模塊加載器
class Module {
...
// 檢查依賴是否加載完畢
check() {
let exports = this.exports
//如果依賴數小于1,表示依賴已經全部加載完畢
if (this.depCount < 1) {
// 調用回調,并獲取該模塊的內容
exports = this.callback.apply(null, this.depExports)
this.exports = exports
//激活 defined 回調
this.definedFn(exports)
}
}
...
}
~~~
最終通過`definedFn`重新回到被依賴模塊,也就是最初調用`require`方法實例化的匿名模塊加載器中,將依賴模塊暴露的內容存入`depExports`中,然后調用匿名模塊加載器的`check`方法,調用回調。
~~~
mod.definedFn = exports => {
this.depCount--
this.depExports[i] = exports
this.check()
}
~~~
#### 模塊定義
還有一個疑問就是,在依賴模塊加載完畢的回調中,怎么拿到的依賴模塊的依賴和回調呢?
~~~
const def = defMap[name]
mod.init(def.deps, def.callback)
~~~
答案就是通過全局定義的`define`方法,該方法會將模塊的依賴項還有回調存儲到一個全局變量,后面只要按需獲取即可。
~~~
const defMap = {} // 緩存加載的模塊
define = (name, deps, callback) => {
defMap[name] = { name, deps, callback }
}
~~~
#### RequireJS 原理總結
最后可以發現,RequireJS 的核心就在于模塊加載器的實現,不管是通過`require`進行依賴加載,還是使用`define`定義模塊,都離不開模塊加載器。
<br>
<br>
## CMD
CMD 規范由國內的開發者玉伯提出,盡管在國際上的知名度遠不如 AMD ,但是在國內也算和 AMD 齊頭并進。相比于 AMD 的異步加載,CMD 更加傾向于懶加載,而且 CMD 的規范與 CommonJS 更貼近,只需要在 CommonJS 外增加一個函數調用的包裝即可。
~~~
define(function(require, exports, module) {
require("./a").doSomething()
require("./b").doSomething()
})
~~~
作為 CMD 規范的實現 sea.js 也實現了類似于 RequireJS 的 api:
~~~
seajs.use('main', function (main) {
main.doSomething()
})
~~~
sea.js 在模塊加載的方式上與 RequireJS 一致,都是通過在 head 標簽插入 script 標簽進行加載的,但是在加載順序上有一定的區別。要講清楚這兩者之間的差別,我們還是直接來看一段代碼:
**RequireJS**:
~~~
// RequireJS
define('a', function () {
console.log('a load')
return {
run: function () { console.log('a run') }
}
})
define('b', function () {
console.log('b load')
return {
run: function () { console.log('b run') }
}
})
require(['a', 'b'], function (a, b) {
console.log('main run')
a.run()
b.run()
})
~~~

**sea.js**:
~~~
// sea.js
define('a', function (require, exports, module) {
console.log('a load')
exports.run = function () { console.log('a run') }
})
define('b', function (require, exports, module) {
console.log('b load')
exports.run = function () { console.log('b run') }
})
define('main', function (require, exports, module) {
console.log('main run')
var a = require('a')
a.run()
var b = require('b')
b.run()
})
seajs.use('main')
~~~

可以看到 sea.js 的模塊屬于懶加載,只有在 require 的地方,才會真正運行模塊。而 RequireJS,會先運行所有的依賴,得到所有依賴暴露的結果后再執行回調。
正是因為懶加載的機制,所以 sea.js 提供了`seajs.use`的方法,來運行已經定義的模塊。所有 define 的回調函數都不會立即執行,而是將所有的回調函數進行緩存,只有 use 之后,以及被 require 的模塊回調才會進行執行。
### sea.js 原理
下面簡單講解一下 sea.js 的懶加載邏輯。在調用 define 方法的時候,只是將 模塊放入到一個全局對象進行緩存。
~~~
const seajs = {}
const cache = seajs.cache = {}
define = (id, factory) => {
const uri = id2uri(id)
const deps = parseDependencies(factory.toString())
const mod = cache[uri] || (cache[uri] = new Module(uri))
mod.deps = deps
mod.factory = factory
}
class Module {
constructor(uri, deps) {
this.status = 0
this.uri = uri
this.deps = deps
}
}
~~~
這里的 Module,是一個與 RequireJS 類似的模塊加載器。后面運行的 seajs.use 就會從緩存取出對應的模塊進行加載。
> 注意:這一部分代碼只是簡單介紹 use 方法的邏輯,并不能直接運行。
~~~
let cid = 0
seajs.use = (ids, callback) => {
const deps = isArray(ids) ? ids : [ids]
deps.forEach(async (dep, i) => {
const mod = cache[dep]
mod.load()
})
}
~~~
另外 sea.js 的依賴都是在 factory 中聲明的,在模塊被調用的時候,sea.js 會將 factory 轉成字符串,然后匹配出所有的`require('xxx')`中的`xxx`,來進行依賴的存儲。前面代碼中的`parseDependencies`方法就是做這件事情的。
早期 sea.js 是直接通過正則的方式進行匹配的:
~~~
const parseDependencies = (code) => {
const REQUIRE_RE = /"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\/\*[\S\s]*?\*\/|\/(?:\\\/|[^/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*require|(?:^|[^$])\brequire\s*\(\s*(["'])(.+?)\1\s*\)/g
const SLASH_RE = /\\\\/g
const ret = []
code
.replace(SLASH_RE, '')
.replace(REQUIRE_RE, function(_, __, id) {
if (id) {
ret.push(id)
}
})
return ret
}
~~~
但是后來發現正則有各種各樣的 bug,并且過長的正則也不利于維護,所以 sea.js 后期舍棄了這種方式,轉而使用狀態機進行詞法分析的方式獲取 require 依賴。
詳細代碼可以查看 sea.js 相關的子項目:[crequire](https://github.com/seajs/crequire)。
#### sea.js 原理總結
其實 sea.js 的代碼邏輯大體上與 RequireJS 類似,都是通過創建 script 標簽進行模塊加載,并且都有實現一個模塊記載器,用于管理依賴。
主要差異在于,sea.js 的懶加載機制,并且在使用方式上,sea.js 的所有依賴都不是提前聲明的,而是 sea.js 內部通過正則或詞法分析的方式將依賴手動進行提取的。
<br>
## CommonJS
十年前的前端沒有像現在這么火熱,模塊化也只是使用閉包簡單的實現一個命名空間。2009 年對 JavaScript 無疑是重要的一年,新的 JavaScript 引擎 (v8) ,并且有成熟的庫 (jQuery、YUI、Dojo),ES5 也在提案中,然而 JavaScript 依然只能出現在瀏覽器當中。早在2007年,AppJet 就提供了一項服務,創建和托管服務端的 JavaScript 應用。后來 Aptana 也提供了一個能夠在服務端運行 Javascript 的環境,叫做 Jaxer。網上還能搜到關于 AppJet、Jaxer 的博客,甚至 Jaxer 項目還在[github](https://github.com/aptana/Jaxer)上。
<br>

<br>
但是這些東西都沒有發展起來,Javascript 并不能替代傳統的服務端腳本語言 (PHP、Python、Ruby) 。盡管它有很多的缺點,但是不妨礙有很多人使用它。后來就有人開始思考 JavaScript 要在服務端運行還需要些什么?于是在 2009 年 1 月,Mozilla 的工程師[Kevin Dangoor](http://www.kevindangoor.com/)發起了 CommonJS 的提案,呼吁 JavaScript 愛好者聯合起來,編寫 JavaScript 運行在服務端的相關規范,一周之后,就有了 224 個參與者。
<br>
> "\[This\] is not a technical problem,It's a matter of people getting together and making a decision to step forward and start building up something bigger and cooler together."
<br>
CommonJS 標準囊括了 JavaScript 需要在服務端運行所必備的基礎能力,比如:模塊化、IO 操作、二進制字符串、進程管理、Web網關接口 (JSGI) 。但是影響最深遠的還是 CommonJS 的模塊化方案,CommonJS 的模塊化方案是JavaScript社區第一次在模塊系統上取得的成果,不僅支持依賴管理,而且還支持作用域隔離和模塊標識。再后來 node.js 出世,他直接采用了`CommonJS`的模塊化規范,同時還帶來了npm (Node Package Manager,現在已經是全球最大模塊倉庫了) 。
<br>
CommonJS 在服務端表現良好,很多人就想將 CommonJS 移植到客戶端 (也就是我們說的瀏覽器) 進行實現。由于CommonJS 的模塊加載是同步的,而服務端直接從磁盤或內存中讀取,耗時基本可忽略,但是在瀏覽器端如果還是同步加載,對用戶體驗極其不友好,模塊加載過程中勢必會向服務器請求其他模塊代碼,網絡請求過程中會造成長時間白屏。所以從 CommonJS 中逐漸分裂出來了一些派別,在這些派別的發展過程中,出現了一些業界較為熟悉方案 AMD、CMD、打包工具(Component/Browserify/Webpack)。
~~~
// a.js
module.exports = {
a: 1
}
// or
exports.a = 1
// b.js
var module = require('./a.js')
module.a // -> log 1
~~~
因為 CommonJS 還是會使用到的,所以這里會對一些疑難點進行解析
<br>
先說`require`吧
~~~
var module = require('./a.js')
module.a
// 這里其實就是包裝了一層立即執行函數,這樣就不會污染全局變量了,
// 重要的是 module 這里,module 是 Node 獨有的一個變量
module.exports = {
a: 1
}
// module 基本實現
var module = {
id: 'xxxx', // 我總得知道怎么去找到他吧
exports: {} // exports 就是個空對象
}
// 這個是為什么 exports 和 module.exports 用法相似的原因
var exports = module.exports
var load = function (module) {
// 導出的東西
var a = 1
module.exports = a
return module.exports
};
// 然后當我 require 的時候去找到獨特的
// id,然后將要使用的東西用立即執行函數包裝下,over
~~~
另外雖然`exports`和`module.exports`用法相似,但是不能對`exports`直接賦值。因為`var exports = module.exports`這句代碼表明了`exports`和`module.exports`享有相同地址,通過改變對象的屬性值會對兩者都起效,但是如果直接對`exports`賦值就會導致兩者不再指向同一個內存地址,修改并不會對`module.exports`起效。
<br>
<br>
# 參考資料
* 前端面試之道 - 掘金小冊
* [前端模塊化的前世](https://segmentfault.com/a/1190000020621564)
- 第一部分 HTML
- meta
- meta標簽
- HTML5
- 2.1 語義
- 2.2 通信
- 2.3 離線&存儲
- 2.4 多媒體
- 2.5 3D,圖像&效果
- 2.6 性能&集成
- 2.7 設備訪問
- SEO
- Canvas
- 壓縮圖片
- 制作圓角矩形
- 全局屬性
- 第二部分 CSS
- CSS原理
- 層疊上下文(stacking context)
- 外邊距合并
- 塊狀格式化上下文(BFC)
- 盒模型
- important
- 樣式繼承
- 層疊
- 屬性值處理流程
- 分辨率
- 視口
- CSS API
- grid(未完成)
- flex
- 選擇器
- 3D
- Matrix
- AT規則
- line-height 和 vertical-align
- CSS技術
- 居中
- 響應式布局
- 兼容性
- 移動端適配方案
- CSS應用
- CSS Modules(未完成)
- 分層
- 面向對象CSS(未完成)
- 布局
- 三列布局
- 單列等寬,其他多列自適應均勻
- 多列等高
- 圣杯布局
- 雙飛翼布局
- 瀑布流
- 1px問題
- 適配iPhoneX
- 橫屏適配
- 圖片模糊問題
- stylelint
- 第三部分 JavaScript
- JavaScript原理
- 內存空間
- 作用域
- 執行上下文棧
- 變量對象
- 作用域鏈
- this
- 類型轉換
- 閉包(未完成)
- 原型、面向對象
- class和extend
- 繼承
- new
- DOM
- Event Loop
- 垃圾回收機制
- 內存泄漏
- 數值存儲
- 連等賦值
- 基本類型
- 堆棧溢出
- JavaScriptAPI
- document.referrer
- Promise(未完成)
- Object.create
- 遍歷對象屬性
- 寬度、高度
- performance
- 位運算
- tostring( ) 與 valueOf( )方法
- JavaScript技術
- 錯誤
- 異常處理
- 存儲
- Cookie與Session
- ES6(未完成)
- Babel轉碼
- let和const命令
- 變量的解構賦值
- 字符串的擴展
- 正則的擴展
- 數值的擴展
- 數組的擴展
- 函數的擴展
- 對象的擴展
- Symbol
- Set 和 Map 數據結構
- proxy
- Reflect
- module
- AJAX
- ES5
- 嚴格模式
- JSON
- 數組方法
- 對象方法
- 函數方法
- 服務端推送(未完成)
- JavaScript應用
- 復雜判斷
- 3D 全景圖
- 重載
- 上傳(未完成)
- 上傳方式
- 文件格式
- 渲染大量數據
- 圖片裁剪
- 斐波那契數列
- 編碼
- 數組去重
- 淺拷貝、深拷貝
- instanceof
- 模擬 new
- 防抖
- 節流
- 數組扁平化
- sleep函數
- 模擬bind
- 柯里化
- 零碎知識點
- 第四部分 進階
- 計算機原理
- 數據結構(未完成)
- 算法(未完成)
- 排序算法
- 冒泡排序
- 選擇排序
- 插入排序
- 快速排序
- 搜索算法
- 動態規劃
- 二叉樹
- 瀏覽器
- 瀏覽器結構
- 瀏覽器工作原理
- HTML解析
- CSS解析
- 渲染樹構建
- 布局(Layout)
- 渲染
- 瀏覽器輸入 URL 后發生了什么
- 跨域
- 緩存機制
- reflow(回流)和repaint(重繪)
- 渲染層合并
- 編譯(未完成)
- Babel
- 設計模式(未完成)
- 函數式編程(未完成)
- 正則表達式(未完成)
- 性能
- 性能分析
- 性能指標
- 首屏加載
- 優化
- 瀏覽器層面
- HTTP層面
- 代碼層面
- 構建層面
- 移動端首屏優化
- 服務器層面
- bigpipe
- 構建工具
- Gulp
- webpack
- Webpack概念
- Webpack工具
- Webpack優化
- Webpack原理
- 實現loader
- 實現plugin
- tapable
- Webpack打包后代碼
- rollup.js
- parcel
- 模塊化
- ESM
- 安全
- XSS
- CSRF
- 點擊劫持
- 中間人攻擊
- 密碼存儲
- 測試(未完成)
- 單元測試
- E2E測試
- 框架測試
- 樣式回歸測試
- 異步測試
- 自動化測試
- PWA
- PWA官網
- web app manifest
- service worker
- app install banners
- 調試PWA
- PWA教程
- 框架
- MVVM原理
- Vue
- Vue 餓了么整理
- 樣式
- 技巧
- Vue音樂播放器
- Vue源碼
- Virtual Dom
- computed原理
- 數組綁定原理
- 雙向綁定
- nextTick
- keep-alive
- 導航守衛
- 組件通信
- React
- Diff 算法
- Fiber 原理
- batchUpdate
- React 生命周期
- Redux
- 動畫(未完成)
- 異常監控、收集(未完成)
- 數據采集
- Sentry
- 貝塞爾曲線
- 視頻
- 服務端渲染
- 服務端渲染的利與弊
- Vue SSR
- React SSR
- 客戶端
- 離線包
- 第五部分 網絡
- 五層協議
- TCP
- UDP
- HTTP
- 方法
- 首部
- 狀態碼
- 持久連接
- TLS
- content-type
- Redirect
- CSP
- 請求流程
- HTTP/2 及 HTTP/3
- CDN
- DNS
- HTTPDNS
- 第六部分 服務端
- Linux
- Linux命令
- 權限
- XAMPP
- Node.js
- 安裝
- Node模塊化
- 設置環境變量
- Node的event loop
- 進程
- 全局對象
- 異步IO與事件驅動
- 文件系統
- Node錯誤處理
- koa
- koa-compose
- koa-router
- Nginx
- Nginx配置文件
- 代理服務
- 負載均衡
- 獲取用戶IP
- 解決跨域
- 適配PC與移動環境
- 簡單的訪問限制
- 頁面內容修改
- 圖片處理
- 合并請求
- PM2
- MongoDB
- MySQL
- 常用MySql命令
- 自動化(未完成)
- docker
- 創建CLI
- 持續集成
- 持續交付
- 持續部署
- Jenkins
- 部署與發布
- 遠程登錄服務器
- 增強服務器安全等級
- 搭建 Nodejs 生產環境
- 配置 Nginx 實現反向代理
- 管理域名解析
- 配置 PM2 一鍵部署
- 發布上線
- 部署HTTPS
- Node 應用
- 爬蟲(未完成)
- 例子
- 反爬蟲
- 中間件
- body-parser
- connect-redis
- cookie-parser
- cors
- csurf
- express-session
- helmet
- ioredis
- log4js(未完成)
- uuid
- errorhandler
- nodeclub源碼
- app.js
- config.js
- 消息隊列
- RPC
- 性能優化
- 第七部分 總結
- Web服務器
- 目錄結構
- 依賴
- 功能
- 代碼片段
- 整理
- 知識清單、博客
- 項目、組件、庫
- Node代碼
- 面試必考
- 91算法
- 第八部分 工作代碼總結
- 樣式代碼
- 框架代碼
- 組件代碼
- 功能代碼
- 通用代碼