乐趣区

关于javascript:commonJsAMDUMDes6模块化的区别这篇就够了

前言:置信大家对前端模块化的了解比拟含糊,上面我总结了相干知识点,废话不多说,间接进入主题

一、模块化的思维

模块化就是把逻辑代码拆分成独立的块,各自封装,相互独立,每个块自行决定对外裸露什么,同时自行决定引入执行哪些内部代码。

二、commonJs

Node 利用由模块组成,采纳 CommonJS 模块标准。也就是说 CommonJs 是利用在 node 服务器端的,如果浏览器想应用 CommonJs 标准的话须要用 browserify 库 来进行转化。(前面会有例子)
CommonJs 分为两局部:moudle 对象和 requeire 命令

1、moudle 对象

Node 外部提供一个 Module 构建函数。所有模块都是 Module 的实例

function Module(id, parent) {
  this.id = id;
  this.exports = {};
  this.parent = parent;
  // ...
  }

每个模块外部,都有一个 module 对象,代表以后模块。它有以下属性。

  • module.id 模块的辨认符,通常是带有绝对路径的模块文件名。
  • module.filename 模块的文件名,带有绝对路径。
  • module.loaded 返回一个布尔值,示意模块是否曾经实现加载。
  • module.parent 返回一个对象,示意调用该模块的模块。
  • module.children 返回一个数组,示意该模块要用到的其余模块。
  • module.exports 示意模块对外输入的值。

module.exports属性示意以后模块对外输入的接口,其余文件加载该模块,实际上就是读取 module.exports 变量。

//a.js
module.exports = {
  name: 'ysl',
  age: '27'
}
let obj = require('./a.js')
console.log(obj)  // {name: 'ysl', age: 27}

除了应用 module.exports 的形式导出值之外,还能够应用 exports 导出

//a.js
export.name = 'ysl'
export.age = 27
// 留神不能 export = {name:'ysl',age:27}, 这种形式是有效的

下面两种形式导出的后果是统一的

2、require 命令

下面咱们曾经将内容导出,当初是怎么把加载导出的内容,node 内置的 require 命令用于加载模块文件

//a.js
module.exports = {name:'ysl',age:27}
//b.js
let obj = require(./a.js)
console.log(obj) // {name:'ysl',age:27}

须要留神点一:require 第一次加载某个模块时,Node 会缓存该模块。当前再加载该模块,就间接从缓存取出该模块的 module.exports 属性。

//a.js
module.exports = {
  name: 'ysl',
  age: 27
}
//b.js
let obj1 = require('./a.js')
console.log(obj) // {name:'ysl',age:27}
obj1.name = 'abc'
let obj2 = require('./test')
console.log(obj2) // {name:'abc',age:27}
console.log(obj1) // {name:'abc',age:27}
// 从这个例子能够看出 node 会对模块进行缓存
delete require.cache[require.resolve('./a.js')]
let obj3 = require('./test')
console.log(obj3) // {name:'ysl',age:27}
// 能够应用 delete require.cache[moduleName]来删除缓存

须要留神点二:CommonJS 模块的加载机制是,输出的是被输入的值的拷贝。也就是说,一旦输入一个值,模块外部的变动就影响不到这个值。

// lib.js
var counter = 3;
function incCounter() {counter++;}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};
// main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;
console.log(counter);  // 3
incCounter();
console.log(counter); // 3
// 下面代码阐明,`counter` 输入当前,`lib.js` 模块外部的变动就影响不到 `counter` 了。

三、AMD

因为 commonJs 加载是同步加载的,在浏览器中如果某个模块加载工夫很长,整个利用就会停在那里等,页面会呈现卡死的景象。(上面的例子)

  <script src="1.js"></script>
  <script src="2.js"></script>
  <script src="3.js"></script>
  <script src="4.js"></script>
  <script src="5.js"></script>
  <script src="6.js"></script>
  // 首先,加载的时候,浏览器会进行网页渲染,加载文件越多,网页失去响应的工夫就会越长;其次,因为 js 文件之间存在依赖关系,因而必须严格保障加载程序(比方上例的 1.js 要在 2.js 的后面),依赖性最大的模块肯定要放到最初加载,当依赖关系很简单的时候,代码的编写和保护都会变得艰难

AMD 采纳异步形式加载模块,模块的加载不影响它前面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载实现之后,这个回调函数才会运行。
require.js 类库 实现了 AMD 标准,上面讲讲其根本用法:
1、下载 require.js: npm install requirejs
2、援用 require.js

<script src='../node_modules/requirejs/require.js' data-main='./index'></script>
// data-main 属性的作用是,指定网页程序的主模块。在上例中,就是 js 目录上面的 index.js,这个文件会第一个被 require.js 加载。因为 require.js 默认的文件后缀名是 js,所以能够把 index

3、编写 index.js 模块代码,主模块是依赖其余模块的入口,这时就要应用 AMD 标准定义的的 require()函数加载依赖模块

 // index.js
 require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
    // some code here
       // 能够在这里编写模块加载后的代码
 });
 // require()函数承受两个参数:
 // 第一个参数是一个数组,示意所依赖的模块['moduleA', 'moduleB', 'moduleC']
 // 第二个参数是一个回调函数,当后面指定的模块都加载胜利后,它将被调用
 // 留神 ['moduleA', 'moduleB', 'moduleC'] 这外面的三个模块与 index.js 在同一个目录

4、模块的写法,AMD 提供 define 办法,调用 define 并传入一个函数

// moduleA.js
  define(function (){var add = function (x,y){return x+y;};
    return {add: add};
  });
// index.js
  require(['moduleA'], function (moduleA){console.log(moduleA)
       //moduleA 就是 moduleA.js 模块传入的函数执行后返回的对象{add:function}
 });

以上就是合乎 AMD 标准的 require.js 根本用法

三、UMD 标准

通过对 CommonJs、CMD、AMD 进一步解决,它没有本人专有的标准,是集结了 CommonJs、CMD、AMD 的标准于一身。
它能够通过运行时或者编译时让同一个代码模块在应用 CommonJs、CMD 甚至是 AMD 的我的项目中运行。
将来同一个 JavaScript 包运行在浏览器端、服务区端都只须要恪守同一个写法就行了。

// UMD 简略实现
((global, factory) => {
    // 如果 以后的上下文有 define 函数,并且 AMD  阐明处于 AMD 环境下
    if (typeof define === 'function' && define.amd) {define(["moduleA"], factory);
    }
    else if (typeof exports === 'object') {//commonjs
        let moduleA = require("moduleA")
        modules.exports = factory(moduleA)
    }
    else {global.moduleA = factory(global.moduleA) // 间接挂载成 windows 全局变量 
    }
})(this, (moduleA) => {
    // 本模块的定义
    return {}})

四、ES6 模块

历史上,JavaScript 始终没有模块(module)体系,无奈将一个大程序拆分成相互依赖的小文件,再用简略的办法拼装起来。ES6 在语言规范的层面上,实现了模块性能,而且实现得相当简略,齐全能够取代 CommonJS 和 AMD 标准,成为浏览器和服务器通用的模块解决方案。不再须要 UMD 模块格局了,未来服务器和浏览器都会反对 ES6 模块格局。
es6 模块的语法分为两局部:export 模块导出、import 模块导入

1、export 语法
// profile.js 
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};

下面的例子导出一个对象,蕴含 firstName, lastName, year 三个属性。

留神:export语句输入的接口,与其对应的值是动静绑定关系,即通过该接口,能够取到模块外部实时的值。
// profile.js
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
// 下面代码输入变量 `foo`,值为 `bar`,500 毫秒之后变成 `baz`
2、import 语法
// main.js 
import {firstName, lastName, year} from './profile.js';
function setName(element) {element.textContent = firstName + ' ' + lastName;}

下面代码的 import 命令,用于加载 profile.js 文件,并从中输出变量。import命令承受一对大括号,外面指定要从其余模块导入的变量名。大括号外面的变量名,必须与被导入模块(profile.js)对外接口的名称雷同

除了指定加载某个输入值,还能够应用整体加载,即用星号(*)指定一个对象,所有输入值都加载在这个对象下面。

// main.js 
import * as total from './profile.js';
function setName(element) {element.textContent = total.firstName + ' ' + total.lastName;}
3、export default 语法

从后面的例子能够看出,应用 import 命令的时候,用户须要晓得所要加载的变量名或函数名,否则无奈加载。然而,用户必定心愿疾速上手,未必违心浏览文档,去理解模块有哪些属性和办法。
为了给用户提供方便,让他们不必浏览文档就能加载模块,就要用到 export default 命令,为模块指定默认输入。

// profile.js 
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export default {firstName, lastName, year};
// main.js 
import total from './profile.js';
function setName(element) {element.textContent = total.firstName + ' ' + total.lastName;}

留神:export default命令用于指定模块的默认输入。显然,一个模块只能有一个默认输入,因而 export default 命令只能应用一次。

// 正是因为 `export default` 命令其实只是输入一个叫做 `default` 的变量,所以它前面不能跟变量申明语句。export default var a = 1; // 会报错

4、export 与 import 复用

如果在一个模块之中,先输出后输入同一个模块,import语句能够与 export 语句写在一起。

export {foo, bar} from 'my_module';
// 能够简略了解为 
import {foo, bar} from 'my_module';
export {foo, bar};

5、import()应用场景

import()返回一个 Promise 对象
(1)按需加载,路由按需加载就是调用此办法

button.addEventListener('click', event => {import('./dialogBox.js')
  .then(dialogBox => {dialogBox.open();
  })
  .catch(error => {/* Error handling */})
});

(2)条件加载

if (condition) {import('moduleA').then(...);
} else {import('moduleB').then(...);
}
CommonsJs 与 ES6 之间的差异
  • CommonJS 模块输入的是一个值的拷贝,ES6 模块输入的是值的援用
  • CommonJS 模块是运行时加载,ES6 模块是编译时输入接口。
  • CommonJS 模块的 require() 是同步加载模块,ES6 模块的 import 命令是异步加载,有一个独立的模块依赖的解析阶段。

五、node 和浏览器别离加载 es6 与 commonJs 的形式

node 加载 es6 模块的形式

1、ES6 模块采纳 .mjs 后缀文件名。
2、我的项目的 package.json 文件中,指定 type 字段为 module。(留神:如果没有type 字段,或者 type 字段为 commonjs,则.js 脚本会被解释成 CommonJS 模块。)
总结为一句话:.mjs文件总是以 ES6 模块加载,.cjs文件总是以 CommonJS 模块加载,.js文件的加载取决于 package.json 外面 type 字段的设置。

node 默认采纳 commonJs 模块模块的形式
// a.js
module.exports = {name:'ysl}
// main.js
let obj = require('./a.js')
// node 环境能够间接应用
浏览器加载 es6 模块形式

浏览器加载 ES6 模块,也应用 <script> 标签,然而要退出 type="module" 属性。

<script type="module" src="./foo.js"></script>
// 浏览器对于带有 `type="module"` 的 `<script>`,都是异步加载,不会造成梗塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于关上了 `<script>` 标签的 `defer` 属性。<script type="module" src="./foo.js" defer></script>
// 成果统一

通过 <script> 标签加载,可能会呈现跨域的景象,能够应用 express 解决

浏览器加载 CommonJs

浏览器间接加载 CommonJs 会报错,因为浏览器不存在 module、exports、require 这些环境变量,能够应用 Browserify,对模块进行转换

下面就是我总结进去的知识点,如果感觉有用能够点个关注加珍藏,前面会总结更多前端根底知识点,也能够关注被人的微信公众号“跟我一起学前端”。

退出移动版