文章首发于sau交流学习社区

一、前言

目前主流的模块规范:

1、UMD通用模块

2、CommonJs

3、es6 module

二、UMD模块(通用模块)

(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规范;

什么是UMD模块规范?就是AMD+CommonJs+全局变量的组合规范。

这段代码用来判断当前的运行环境,如果是node环境,就会使用CommonJs规范,然后判断是否是AMD环境,是的话就会使用AMD规范,最后导出全局变量

有了UMD后我们的代码可以同时运行在node和浏览器上。现在前端多数的库最后打包都是使用UMD规范。

二、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 相对路径开头

在没有指定后缀名的情况下:

1、先去寻找同级目录同级目录:src/app/

2、同级目录没有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 源码

我们可以知道:

1、模块只执行一次,之后调用获取的module.exports都是在缓存中,哪怕这个js没有执行完(因为先加入缓存后加入模块)。

2、模块导出就是return 这个变量,其实跟赋值一样,基本类型导出的是引用类型导出的是引用地址

3、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,分析一下:

1、执行 node main.js -> 第一行 require(a.js),(node 执行也可以理解为调用了require方法,我们省略require(main.js)内容);

2、进入 require(a)方法: 判断缓存(无) -> 初始化一个 module -> 将 module 加入缓存 -> 执行模块 a.js 内容,(需要注意 是先加入缓存, 后执行模块内容)

3、a.js: 第一行导出 a = 1 -> 第二行 require(b.js)(a 只执行了第一行)

4、进入 require(b) 内 同 1 -> 执行模块 b.js 内容

5、b.js: 第一行 b = 11 -> 第二行 require(a.js)

6、require(a) 此时 a.js 是第二次调用 require -> 判断缓存(有)-> cachedModule.exports -> 回到 b.js(因为js对象引用问题 此时的 cachedModule.exports = { a: 1 })

7、b.js:第三行 输出 { a: 1 } -> 第四行 修改 b = 22 -> 执行完毕回到 a.js

8、a.js:第二行 require 完毕 获取到 b -> 第三行 输出 { b: 22 } -> 第四行 导出 a = 2 -> 执行完毕回到 main.js

9、main.js:获取 a -> 第二行 输出 { a: 2 } -> 执行完毕

以上就是nodemodule模块解析和运行的大致规则

三、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规则:

1、export * from '' 或者 export {} from '',重定向导出,重定向的命名并不能在本模块使用,只是搭建一个桥梁,例如:这个a并不能在本模块内使用

2、export {}, 与变量名绑定,命名导出

3、export Declaration,声明的同时,命名导出, Declaration就是: var, let, const, function, function*, class 这一类的声明语句

4、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 规则:

1、import { } from 'module', 导入module.js的命名导出

2、import defaultExport from 'module', 导入module.js的默认导出

3、import * as name from 'module', 将module.js的的所有导出合并为name的对象,key为导出的命名,默认导出的key为default

4、import 'module',副作用,只是运行module,不为了导出内容例如 polyfill,多次调用次语句只能执行一次

5、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)

分析:

1、执行 main.js -> 导入 bar.js;

2、bar.js -> 导入 foo.js;

3、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 方法

四、CommonJs与es6 module的区别

从上面能够知道一些区别:

1、CommonJs导出的是变量的一份拷贝,ES6 Module导出的是变量的绑定(引用);

2、CommonJs是单个值导出,ES6 Module可以导出多个;

3、CommonJs是动态语法可以写在判断里,ES6 Module静态语法只能写在顶层;

4、CommonJs的 this 是当前模块,ES6 Module的 this 是 undefined。

五、易混淆点

5.1模块语法与解构

module语法解构语法很容易混淆,例如:

import { a } from 'module'

const { a } = require('module')

尽管看上去很像,但是不是同一个东西,这是两种完全不一样的语法与作用,ps:两个人撞衫了,穿一样的衣服你不能说这俩人就是同一个人
1、module 的语法: 上面有写 import/export { a } / { a, b } / { a as c} FromClause

2、解构 的语法:

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: []}) {}

他们是差别非常大的两个东西,一个是模块导入导出,一个是获取对象的语法糖

深入javascript的主流的模块规范的更多相关文章

  1. Javascript模块规范(CommonJS规范&&AMD规范)

    Javascript模块化编程(AMD&CommonJS) 前端模块化开发的价值:https://github.com/seajs/seajs/issues/547 模块的写法 查看 AMD规 ...

  2. JavaScript模块化开发&&模块规范

    在做项目的过程中通常会有一些可复用的通用性功能,之前的做法是把这个功能抽取出来独立为一个函数统一放到commonFunctions.js里面(捂脸),实现类似于snippets的代码片段收集. fun ...

  3. Javascript模块化编程(二)AMD规范(规范使用模块)

    这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块,先想一想,为什么模块很重要?接下来为您详细介绍,感兴趣的朋友可以了解下啊.今天介绍如何规范地使用模块. 七.模块 ...

  4. Javascript模块规范

    因为有了模块,就可以更方便地使用别人的代码,想要什么功能,就加载什么模块.但是有一个前提,就是大家必须以同样的方式编写模块. 目前,通行的Javascript模块规范共有两种:CommonJS和AMD ...

  5. 理解JS中的模块规范(CommonJS,AMD,CMD)

    随着互联网的飞速发展,前端开发越来越复杂.本文将从实际项目中遇到的问题出发,讲述模块化能解决哪些问题,以及如何使用 Sea.js 进行前端的模块化开发. 恼人的命名冲突 我们从一个简单的习惯出发.我做 ...

  6. JavaSript模块规范 - AMD规范与CMD规范介绍

    JavaSript模块化   在了解AMD,CMD规范前,还是需要先来简单地了解下什么是模块化,模块化开发?       模块化是指在解决某一个复杂问题或者一系列的杂糅问题时,依照一种分类的思维把问题 ...

  7. JavaSript模块规范 - AMD规范与CMD规范介绍(转)

    JavaSript模块规范 - AMD规范与CMD规范介绍 JavaSript模块化 在了解AMD,CMD规范前,还是需要先来简单地了解下什么是模块化,模块化开发? 模块化是指在解决某一个复杂问题或者 ...

  8. JavaSript模块规范 - AMD规范与CMD规范介绍 (转载lovenyf.blog.chinaunix.net)

    JavaSript模块化   在了解AMD,CMD规范前,还是需要先来简单地了解下什么是模块化,模块化开发?       模块化是指在解决某一个复杂问题或者一系列的杂糅问题时,依照一种分类的思维把问题 ...

  9. JavaScript设计模式-单例模式、模块模式(转载 学习中。。。。)

    (转载地址:http://technicolor.iteye.com/blog/1409656) 之前在<JavaScript小特性-面向对象>里面介绍过JavaScript面向对象的特性 ...

随机推荐

  1. JAVAEE——BOS物流项目13:Quartz概述、创建定时任务、使用JavaMail发送邮件、HighCharts概述、实现区域分区分布图

    1 学习计划 1.Quartz概述 n Quartz介绍和下载 n 入门案例 n Quartz执行流程 n cron表达式 2.在BOS项目中使用Quartz创建定时任务 3.在BOS项目中使用Jav ...

  2. java集合的contains(obj)方法的实现

    在实际项目中我们通常会有一个需求就是:想知道在一个列表中是否包含某一个对象 这里ArrayList表.HashSet表和HashMap表都提供了一个contains(obj)方法, 下面说一下两个列表 ...

  3. kmeans算法思想及其python实现

    第十章 利用k-均值聚类算法对未标注的数据进行分组 一.导语 聚类算法可以看做是一种无监督的分类方法,之所以这么说的原因是它和分类方法的结果相同,区别它的类别没有预先的定义.簇识别是聚类算法中经常使用 ...

  4. KNN算法思想与实现

    第二章 k近邻 2.1 算法描述 (1)采用测量不同特征值之间的距离进行分类 优点:对异常点不敏感,精度高,无数据输入设定 缺点:空间,计算复杂度高 适合数据:标称与数值 (2)算法的工作原理: 基于 ...

  5. node传统读取文件和promise,async await,

    先上传统文件加载方式代码,传统方式在处理多层嵌套时代码比较混乱 const fs = require('fs') //引入文件系统 function readFile (cb) { fs.readFi ...

  6. oracle数据库-错误编码大全

    ORA-00001: 违反唯一约束条件 (.) ORA-00017: 请求会话以设置跟踪事件   ORA-00018: 超出最大会话数   ORA-00019: 超出最大会话许可数   ORA-000 ...

  7. c# 将一个窗体显示在主窗体中

    Form2 form = new Form2(); //实例化要添加的窗体 form.Show();//显示 form.TopLevel = false; //要将这个顶级窗口设置false pane ...

  8. 软件配置管理及SVN的使用

    一.配置管理   1. 管理整个软件生命周期中的配置项    配置项:软件生命周期中产出的各种输出成果,如需求文档.设计文档.代码.测试相关文档   2.管理配置项的变化(核心)   3.使用配置管理 ...

  9. 1. Java面向对象之泛型-认识泛型

    package generic; class Point{ private  Object x; private  Object y; public Object getX() { return x; ...

  10. ZooKeeper 安装、配置

    http://blog.csdn.net/franklysun/article/details/6424582 如题本文介绍的是ZooKeeper 的安装和配置过程,此过程非常简单,关键是如何应用(将 ...