文章首发于sau交流学习社区javascript
1、前言html
目前主流的模块规范:前端
一、UMD通用模块java
二、CommonJsnode
三、es6 modulereact
2、UMD模块(通用模块)git
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.libName = factory()); }(this, (function () { 'use strict';})));
若是你在js文件的头部看到这样的代码,这个js文件使用的规范就是UMD规范;es6
什么是UMD模块规范?就是AMD+CommonJs+全局变量的组合规范。github
这段代码用来判断当前的运行环境,若是是node环境,就会使用CommonJs规范,而后判断是不是AMD环境,是的话就会使用AMD规范,最后导出全局变量express
有了UMD后咱们的代码能够同时运行在node和浏览器上。如今前端多数的库最后打包都是使用UMD规范。
2、CommonJs
nodejs的运行环境使用的模块系统就是基于CommonJs规范实现的,咱们如今所说的ComonJs规范大可能是指的node的模块系统。
2.1模块导出
关键字:module.exports,exports
// foo.js //一个一个 导出 module.exports.age = 1 module.exports.foo = function(){} exports.a = 'hello' //总体导出 module.exports = { age: 1, a: 'hello', foo:function(){} } //总体导出不能用`exports` 用exports不能在导入的时候使用 exports = { age: 1, a: 'hello', foo:function(){} }
注意:使用exports导出不能被赋值,由于赋值以后,exports失去了对module.exports的引用,成伟一个模块内的局部变量。
2.2模块导入
关键字:require
const foo = require('./foo.js'); console.log(foo.age); //1
2.2.1模块导入规则
假设在目录src/app/index.js的文件,调用require()。
./moduleA 相对路径开头
在没有指定后缀名的状况下:
一、先去寻找同级目录同级目录:src/app/
二、同级目录没有moduleA文件会去找同级的moduleA目录:src/app/moduleA
结束
/module/moduleA绝对路径开头
直接在/module/moduleA目录中寻找,规则同上
注意:react没有路径开头
没有路径开头则视为导入一个包,会首先判断moduleA是不是一个核心模块,例如path,http,优先导入核心模块,不是核心模块,会从当前文件的同级目录下的node_modules寻找。
2.3require wrapper
node的模块,实际上能够理解为代码被包裹在一个函数包装器内
function wrapper (script) { return '(function (exports, require, module, __filename, __dirname) {' + script + '\n})' } function require(id) { var cachedModule = Module._cache[id]; if(cachedModule){ return cachedModule.exports; } const module = { exports: {} } // 这里先将引用加入缓存 后面循环引用会说到 Module._cache[id] = module //固然不是eval这么简单 eval(wrapper('module.exports = "123"'))(module.exports, require, module, 'filename', 'dirname') return module.exports }
也能够查看:node module 源码
咱们能够知道:
一、模块只执行一次,以后调用获取的module.exports都是在缓存中,哪怕这个js没有执行完(由于先加入缓存后加入模块)。
二、模块导出就是return 这个变量,其实跟赋值同样,基本类型导出的是值,引用类型导出的是引用地址。
三、exports和module.exports持有相同的引用,由于最后导出的是module.exports,因此对于exports进行赋值会致使exports操做的而再也不是module.exports的引用。
2.4循环引用
// a.js module.exports.a = 1 var b = require('./b') console.log(b) module.exports.a = 2
// b.js module.exports.b = 11 var a = require('./a') console.log(a) module.exports.b = 22
//main.js var a = require('./a') console.log(a)
运行此段代码结合上面的require demo,分析一下:
一、执行 node main.js -> 第一行 require(a.js),(node 执行也能够理解为调用了require方法,咱们省略require(main.js)内容);
二、进入 require(a)方法: 判断缓存(无) -> 初始化一个 module -> 将 module 加入缓存 -> 执行模块 a.js 内容,(须要注意 是先加入缓存, 后执行模块内容)
三、a.js: 第一行导出 a = 1 -> 第二行 require(b.js)(a 只执行了第一行)
四、进入 require(b) 内 同 1 -> 执行模块 b.js 内容
五、b.js: 第一行 b = 11 -> 第二行 require(a.js)
六、require(a) 此时 a.js 是第二次调用 require -> 判断缓存(有)-> cachedModule.exports -> 回到 b.js(由于js对象引用问题 此时的 cachedModule.exports = { a: 1 })
七、b.js:第三行 输出 { a: 1 } -> 第四行 修改 b = 22 -> 执行完毕回到 a.js
八、a.js:第二行 require 完毕 获取到 b -> 第三行 输出 { b: 22 } -> 第四行 导出 a = 2 -> 执行完毕回到 main.js
九、main.js:获取 a -> 第二行 输出 { a: 2 } -> 执行完毕
以上就是node
的module
模块解析和运行的大体规则
3、es6 module
ES6 以前 javascript 一直没有属于本身的模块规范,因此社区制定了 CommonJs规范, Node 从 Commonjs 规范中借鉴了思想因而有了 Node 的 module,而 AMD 异步模块 也一样脱胎于 Commonjs 规范,以后有了运行在浏览器上的 require.js
es6 module 基本语法:
3.1 export
export * from 'module'; //重定向导出 不包括 module内的default export { name1, name2, ..., nameN } from 'module'; // 重定向命名导出 export { import1 as name1, import2 as name2, ..., nameN } from 'module'; // 重定向重命名导出 export { name1, name2, …, nameN }; // 与以前声明的变量名绑定 命名导出 export { variable1 as name1, variable2 as name2, …, nameN }; // 重命名导出 export let name1 = 'name1'; // 声明命名导出 或者 var, const,function, function*, class export default expression; // 默认导出 export default function () { ... } // 或者 function*, class export default function name1() { ... } // 或者 function*, class export { name1 as default, ... }; // 重命名为默认导出
export规则:
一、export * from '' 或者 export {} from '',重定向导出,重定向的命名并不能在本模块使用,只是搭建一个桥梁,例如:这个a并不能在本模块内使用
二、export {}, 与变量名绑定,命名导出
三、export Declaration,声明的同时,命名导出, Declaration就是: var, let, const, function, function*, class 这一类的声明语句
四、export default AssignmentExpression,默认导出, AssignmentExpression的 范围很广,能够大体理解 为除了声明Declaration(其实二者是有交叉的),a=2,i++,i/4,a===b,obj[name],name in obj,func(),new P(),[1,2,3],function(){}等等不少
3.2 import
// 命名导出 module.js let a = 1,b = 2 export { a, b } export let c = 3 // 命名导入 main.js import { a, b, c } from 'module'; // a: 1 b: 2 c: 3 import { a as newA, b, c as newC } from 'module'; // newA: 1 b: 2 newC: 3 // 默认导出 module.js export default 1 // 默认导入 main.js import defaultExport from 'module'; // defaultExport: 1 // 混合导出 module.js let a = 1 export { a } const b = 2 export { b } export let c = 3 export default [1, 2, 3] // 混合导入 main.js import defaultExport, { a, b, c as newC} from 'module'; //defaultExport: [1, 2, 3] a: 1 b: 2 newC: 3 import defaultExport, * as name from 'module'; //defaultExport: [1, 2, 3] name: { a: 1, b: 2, c: 3 } import * as name from 'module'; // name: { a: 1, b: 2, c: 3, default: [1, 2, 3] } // module.js Array.prototype.remove = function(){} //反作用 只运行一个模块 import 'module'; // 执行module 不导出值 屡次调用module.js只运行一次 //动态导入(异步导入) var promise = import('module');
import 规则:
一、import { } from 'module', 导入module.js的命名导出
二、import defaultExport from 'module', 导入module.js的默认导出
三、import * as name from 'module', 将module.js的的全部导出合并为name的对象,key为导出的命名,默认导出的key为default
四、import 'module',反作用,只是运行module,不为了导出内容例如 polyfill,屡次调用次语句只能执行一次
五、import('module'),动态导入返回一个 Promise,TC39的stage-3阶段被提出 tc39 import
3.3 es6 module 特色
3.3.1 es6 module语法是静态的
import
会自动提高到代码的顶层。
export
和 import
只能出如今代码的顶层,下面这段语法是错误的。
//if for while 等都没法使用 { export let a = 1 import defaultExport from 'module' } true || export let a = 1
import
的导入名不能为字符串或在判断语句,下面代码是错误的:
import 'defaultExport' from 'module' let name = 'Export' import 'default' + name from 'module'
静态的语法意味着能够在编译时肯定导入和导出,更加快速的查找依赖,可使用lint
工具对模块依赖进行检查,能够对导入导出加上类型信息进行静态的类型检查
3.3.2 es6 module的导出是绑定的
使用 import
被导入的模块运行在严格模式下。
使用 import
被导入的变量是只读的,能够理解默认为 const
装饰,没法被赋值。
使用 import
被导入的变量是与原变量绑定/引用的,能够理解为 import
导入的变量不管是否为基本类型都是引用传递。
// js中 基础类型是值传递 let a = 1 let b = a b = 2 console.log(a,b) //1 2 // js中 引用类型是引用传递 let obj = {name:'obj'} let obj2 = obj obj2.name = 'obj2' console.log(obj.name, obj2.name) // obj2 obj2 // es6 module 中基本类型也按引用传递 // foo.js export let a = 1 export function count(){ a++ } // main.js import { a, count } from './foo' console.log(a) //1 count() console.log(a) //2
上面这段代码就是 CommonJs
导出变量 和 ES6
导出变量的区别
3.4 es6 module 循环引用
// bar.js import { foo } from './foo' console.log(foo); export let bar = 'bar' // foo.js import { bar } from './bar' console.log(bar); export let foo = 'foo' // main.js import { bar } from './bar' console.log(bar)
分析:
一、执行 main.js -> 导入 bar.js;
二、bar.js -> 导入 foo.js;
三、foo.js -> 导入 bar.js -> bar.js 已经执行过直接返回 -> 输出 bar -> bar is not defined, bar 未定义报错
咱们可使用function
的方式解决:
// bar.js import { foo } from './foo' console.log(foo()); export function bar(){ return 'bar' } // foo.js import { bar } from './bar' console.log(bar()); export function foo(){ return 'foo' } // main.js import { bar } from './bar' console.log(bar)
由于函数声明会提示到文件顶部,因此就能够直接在 foo.js
调用还没执行完毕的bar.js
的 bar
方法
4、CommonJs与es6 module的区别
从上面可以知道一些区别:
一、CommonJs导出的是变量的一份拷贝,ES6 Module导出的是变量的绑定(引用);
二、CommonJs是单个值导出,ES6 Module能够导出多个;
三、CommonJs是动态语法能够写在判断里,ES6 Module静态语法只能写在顶层;
四、CommonJs的 this 是当前模块,ES6 Module的 this 是 undefined。
5、易混淆点
5.1模块语法与解构
module语法
与解构语法
很容易混淆,例如:
import { a } from 'module' const { a } = require('module')
尽管看上去很像,可是不是同一个东西,这是两种彻底不同的语法与做用,ps:两我的撞衫了,穿同样的衣服你不能说这俩人就是同一我的一、module
的语法: 上面有写 import/export { a } / { a, b } / { a as c} FromClause
二、解构
的语法:
let { a } = { a: 1 } let { a = 2 } = { } let { a: b } = { a: 1 } let { a: b = 2, ...res } = { name:'a' } let { a: b, obj: { name } } = { a: 1, obj: { name: '1' } } function foo({a: []}) {}
他们是差异很是大的两个东西,一个是模块导入导出,一个是获取对象的语法糖
原文出处:https://www.cnblogs.com/chengxs/p/10733889.html