[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()
})
~~~
![requirejs result](https://segmentfault.com/img/remote/1460000020621570 "requirejs result")
**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 result](https://segmentfault.com/img/remote/1460000020621571 "sea.js result")
可以看到 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>
![Jaxer](https://image-static.segmentfault.com/312/044/3120446892-5d9d3d8640748_articlex)
<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算法
- 第八部分 工作代码总结
- 样式代码
- 框架代码
- 组件代码
- 功能代码
- 通用代码