网站服务器在哪,建设赚钱的网站,股东分红如何合理节税,广西建设网桂建云官网commonjs
1 commonjs 实现原理
commonjs每个模块文件上存在 module#xff0c;exports#xff0c;require三个变量,然而这三个变量是没有被定义的#xff0c;但是我们可以在 Commonjs 规范下每一个 js 模块上直接使用它们。在 nodejs 中还存在 __filename 和 __dirname 变…commonjs
1 commonjs 实现原理
commonjs每个模块文件上存在 moduleexportsrequire三个变量,然而这三个变量是没有被定义的但是我们可以在 Commonjs 规范下每一个 js 模块上直接使用它们。在 nodejs 中还存在 __filename 和 __dirname 变量。
三个变量分别表示
module 记录当前模块信息。require 引入模块的方法。exports 当前模块导出的属性
在编译的过程中实际 Commonjs 对 js 的代码块进行了首尾包装 它被包装之后的样子如下
(function(exports,require,module,__filename,__dirname){const xxx require(./xxx.js)module.exports function A(){return {name:xxx(),author:我不是外星人}}
})包装函数
function wrapper (script) {return (function (exports, require, module, __filename, __dirname) { script \n})
}2 require 文件加载流程
require 加载标识符原则
nodejs中对标识符的处理原则:
首先像 fs http path 等标识符会被作为 nodejs 的核心模块。./ 和 ../ 作为相对路径的文件模块 / 作为绝对路径的文件模块。非路径形式也非核心模块的模块将作为自定义模块。
核心模块的处理
核心模块的优先级仅次于缓存加载在 Node 源码编译中已被编译成二进制代码所以加载核心模块加载过程中速度最快。
路径形式的文件模块处理
已 ./ ../ 和 / 开始的标识符会被当作文件模块处理。require() 方法会将路径转换成真实路径并以真实路径作为索引将编译后的结果缓存起来第二次加载的时候会更快。
自定义模块处理
自定义模块一般指的是非核心的模块它可能是一个文件或者一个包它的查找会遵循以下原则
在当前目录下的 node_modules 目录查找。如果没有在父级目录的 node_modules 查找如果没有在父级目录的父级目录的 node_modules 中查找。沿着路径向上递归直到根目录下的 node_modules 目录。在查找过程中会找 package.json 下 main 属性指向的文件如果没有 package.json 在 node 环境下会以此查找 index.js index.json index.nod
3 require 模块引入与处理
直接上例子
a.js文件
const getMes require(./b)
console.log(我是 a 文件)
exports.say function(){const message getMes()console.log(message)
}b.js文件
const say require(./a)
const object {name:《React进阶实践指南》,author:我不是外星人
}
console.log(我是 b 文件)
module.exports function(){return object
}主文件main.js
const a require(./a)
const b require(./b)console.log(node 入口文件)输出
我是b文件
我是a文件
node入口文件
main.js 和 a.js 模块都引用了 b.js 模块但是 b.js 模块只执行了一次。a.js 模块 和 b.js 模块互相引用但是没有造成循环引用的情况。执行顺序是父 - 子 - 父
require 加载原理
首先为了弄清楚上述两个问题。我们要明白两个感念那就是 module 和 Module。
module 在 Node 中每一个 js 文件都是一个 module module 上保存了 exports 等信息之外还有一个 loaded 表示该模块是否被加载。
为 false 表示还没有加载为 true 表示已经加载
Module 以 nodejs 为例整个系统运行之后会用 Module 缓存每一个模块加载的信息。
require 的源码大致长如下的样子 // id 为路径标识符
function require(id) {/* 查找 Module 上有没有已经加载的 js 对象*/const cachedModule Module._cache[id]/* 如果已经加载了那么直接取走缓存的 exports 对象 */if(cachedModule){return cachedModule.exports}/* 创建当前模块的 module */const module { exports: {} ,loaded: false , ...}/* 将 module 缓存到 Module 的缓存属性中路径标识符作为 id */ Module._cache[id] module/* 加载文件 */runInThisContext(wrapper(module.exports 123))(module.exports, require, module, __filename, __dirname)/* 加载完成 *//module.loaded true /* 返回值 */return module.exports
}从上面我们总结出一次 require 大致流程是这样的 require 会接收一个参数——文件标识符然后分析定位文件分析过程我们上述已经讲到了加下来会从 Module 上查找有没有缓存如果有缓存那么直接返回缓存的内容。 如果没有缓存会创建一个 module 对象缓存到 Module 上然后执行文件加载完文件将 loaded 属性设置为 true 然后返回 module.exports 对象。借此完成模块加载流程。 模块导出就是 return 这个变量的其实跟 a b 赋值一样 基本类型导出的是值 引用类型导出的是引用地址。 exports 和 module.exports 持有相同引用因为最后导出的是 module.exports 所以对 exports 进行赋值会导致 exports 操作的不再是 module.exports 的引用。
require 避免重复加载
从上面我们可以直接得出require 如何避免重复加载的首先加载之后的文件的 module 会被缓存到 Module 上比如一个模块已经 require 引入了 a 模块如果另外一个模块再次引用 a 那么会直接读取缓存值 module 所以无需再次执行模块。
对应 demo 片段中首先 main.js 引用了 a.js a.js 中 require 了 b.js 此时 b.js 的 module 放入缓存 Module 中接下来 main.js 再次引用 b.js 那么直接走的缓存逻辑。所以 b.js 只会执行一次也就是在 a.js 引入的时候。
require 避免循环引用
那么接下来这个循环引用问题也就很容易解决了。为了让大家更清晰明白那么我们接下来一起分析整个流程。
① 首先执行 node main.js 那么开始执行第一行 require(a.js)② 那么首先判断 a.js 有没有缓存因为没有缓存先加入缓存然后执行文件 a.js 需要注意 是先加入缓存 后执行模块内容;③ a.js 中执行第一行引用 b.js。④ 那么判断 b.js 有没有缓存因为没有缓存所以加入缓存然后执行 b.js 文件。⑤ b.js 执行第一行再一次循环引用 require(a.js) 此时的 a.js 已经加入缓存直接读取值。接下来打印 console.log(我是 b 文件)导出方法。⑥ b.js 执行完毕回到 a.js 文件打印 console.log(我是 a 文件)导出方法。⑦ 最后回到 main.js打印 console.log(node 入口文件) 完成这个流程。
不过这里我们要注意问题
如上第 ⑤ 的时候当执行 b.js 模块的时候因为 a.js 还没有导出 say 方法所以 b.js 同步上下文中获取不到 say。
验证
const say require(./a)
const object {name:《React进阶实践指南》,author:我不是外星人
}
console.log(我是 b 文件)
console.log(打印 a 模块 , say)setTimeout((){console.log(异步打印 a 模块 , say)
},0)module.exports function(){return object
}我是 b 文件
打印 a 模块 {}
我是 a 文件
node 入口文件
异步打印 a 模块 {say :[Function]}
那么如何获取到 say 呢有两种办法
一是用动态加载 a.js 的方法马上就会讲到。二个就是如上放在异步中加载。
4 require 动态加载
require 可以在任意的上下文动态加载模块。我对上述 a.js 修改。a.js
console.log(我是 a 文件)
exports.say function(){const getMes require(./b)const message getMes()console.log(message)
}main.js
const a require(./a)
a.say()这样在b.js中就能获取到a.js的say方法
5 exports 和 module.exports
exports 使用
第一种方式exports a.js
exports.name 《React进阶实践指南》
exports.author 我不是外星人
exports.say function (){console.log(666)
}引用
const a require(./a)
console.log(a){name:《React进阶实践指南》,author :我不是外星人,say}
问题为什么 exports{} 直接赋值一个对象就不可以呢 比如我们将如上 a.js 修改一下
exports{name:《React进阶实践指南》,author:我不是外星人,say(){console.log(666)}
}//{}
理想情况下是通过 exports {} 直接赋值不需要在 exports.a xxx 每一个属性但是如上我们看到了这种方式是无效的。为什么会这样实际这个是 js 本身的特性决定的。
通过上述讲解都知道 exports module 和 require 作为形参的方式传入到 js 模块中。我们直接 exports {} 修改 exports 等于重新赋值了形参那么会重新赋值一份但是不会在引用原来的形参。举一个简单的例子
function wrap (myExports){myExports{name:我不是外星人}
}let myExports {name:alien
}
wrap(myExports)
console.log(myExports)//{name:alien}
当我们把 myExports 对象传进去但是直接赋值 myExports { name:我不是外星人 } 没有任何作用相等于内部重新声明一份 myExports 而和外界的 myExports 断绝了关系。所以解释了为什么不能 exports{...} 直接赋值。
module.exports 使用
module.exports 本质上就是 exports 我们用 module.exports 来实现如上的导出。
module.exports {name:《React进阶实践指南》,author:我不是外星人,say(){console.log(666)}
}module.exports 也可以单独导出一个函数或者一个类。比如如下
module.exports function (){// ...
}从上述 require 原理实现中我们知道了 exports 和 module.exports 持有相同引用因为最后导出的是 module.exports 。那么这就说明在一个文件中我们最好选择 exports 和 module.exports 两者之一如果两者同时存在很可能会造成覆盖的情况发生。比如如下情况
exports.name alien // 此时 exports.name 是无效的
module.exports {name:《React进阶实践指南》,author:我不是外星人,say(){console.log(666)}
}Es Module
导出 export 和导入 import
所有通过 export 导出的属性在 import 中可以通过结构的方式解构出来。
export 正常导出import 导入
导出模块a.js
const name 《React进阶实践指南》
const author 我不是外星人
export { name, author }
export const say function (){console.log(hello , world)
}导入模块main.js
// name , author , say 对应 a.js 中的 name , author , say
import { name , author , say } from ./a.js默认导出 export default
导出模块a.js
const name 《React进阶实践指南》
const author 我不是外星人
const say function (){console.log(hello , world)
}
export default {name,author,say
} 导入模块main.js
import mes from ./a.js
console.log(mes) //{ name: 《React进阶实践指南》,author:我不是外星人, say:Function }ES6 module 特性
1 静态语法
ES6 module 的引入和导出是静态的import 会自动提升到代码的顶层 import , export 不能放在块级作用域或条件语句中。
错误写法一
function say(){import name from ./a.js export const author 我不是外星人
}错误写法二
isexport export const name 《React进阶实践指南》import 的导入名不能为字符串或在判断语句下面代码是错误的
错误写法三
import defaultExport from modulelet name Export
import default name from module2 执行特性
ES6 module 和 Common.js 一样对于相同的 js 文件会保存静态属性。
但是与 Common.js 不同的是 CommonJS 模块同步加载并执行模块文件ES6 模块提前加载并执行模块文件ES6 模块在预处理阶段分析模块依赖在执行阶段执行模块两个阶段都采用深度优先遍历执行顺序是子 - 父。
main.js
console.log(main.js开始执行)
import say from ./a
import say1 from ./b
console.log(main.js执行完毕)a.js
import b from ./b
console.log(a模块加载)
export default function say (){console.log(hello , world)
}b.js
console.log(b模块加载)
export default function sayhello(){console.log(hello,world)
}3 导出绑定
export let num 1
export const addNumber (){num
}import { num , addNumber } from ./a
num 2想要修改导入的变量只能这么修改
import { num , addNumber } from ./aconsole.log(num) // num 1
addNumber()
console.log(num) // num 2接下来对 import 属性作出总结
使用 import 被导入的模块运行在严格模式下。使用 import 被导入的变量是只读的可以理解默认为 const 装饰无法被赋值使用 import 被导入的变量是与原变量绑定/引用的可以理解为 import 导入的变量无论是否为基本类型都是引用传递。
import() 可以做一些什么
动态加载
首先 import() 动态加载一些内容可以放在条件语句或者函数执行上下文中。
if(isRequire){const result import(./b)
}懒加载
import() 可以实现懒加载举个例子 vue 中的路由懒加载
[{path: home,name: 首页,component: () import(./home) ,},
]tree shaking
Tree Shaking 在 Webpack 中的实现是用来尽可能的删除没有被使用过的代码一些被 import 了但其实没有被使用的代码。
如果引入的文件中有的方法没有被引用那么构建打包的时候是不会被打包进来的
Commonjs 和 Es Module 总结
commonjs的特性如下
CommonJS 模块由 JS 运行时实现。CommonJs 是单个值导出本质上导出的就是 exports 属性。CommonJS 是可以动态加载的对每一个加载都存在缓存可以有效的解决循环引用问题。CommonJS 模块同步加载并执行模块文件。
Es module 的特性如下
ES6 Module 静态的不能放在块级作用域内代码发生在编译时。ES6 Module 的值是动态绑定的可以通过导出方法修改可以直接访问修改结果。ES6 Module 可以导出多个属性和方法可以单个导入导出混合导入导出。ES6 模块提前加载并执行模块文件ES6 Module 导入模块在严格模式下。ES6 Module 的特性可以很容易实现 Tree Shaking 和 Code Splitting。