js当中CommonJS 和es6的模块化引入方案以及比较

js当中CommonJS 和es6的模块化引入方案以及比较:https://blog.csdn.net/jackTesla/article/details/80796936

在es6之前,对于模块化方案主要是CommonJS和AMD两种。咱们这次说一下ES6和CommonJS的区别。

它们有两个重大差异:

CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
    第一个差异:
    CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个模块文件lib.js的例子。

// lib.js

var counter = 3;

function incCounter() {

counter++;

}

module.exports = {

counter: counter,

incCounter: incCounter,

};

上面代码输出内部变量counter和改写这个变量的内部方法incCounter。然后,在main.js里面加载这个模块。

// main.js

var mod = require(‘./lib‘);

console.log(mod.counter);  // 3

mod.incCounter();

console.log(mod.counter); // 3

上面代码说明,lib.js模块加载以后,它的内部变化就影响不到输出的mod.counter了。这是因为mod.counter是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。

// lib.js

var counter = 3;

function incCounter() {

counter++;

}

module.exports = {

get counter() {

return counter

},

incCounter: incCounter,

};

上面代码中,输出的counter属性实际上是一个取值器函数。现在再执行main.js,就可以正确读取内部变量counter的变动了。

ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

还是举上面的例子。

// lib.js

export let counter = 3;

export function incCounter() {

counter++;

}

// main.js

import { counter, incCounter } from ‘./lib‘;

console.log(counter); // 3

incCounter();

console.log(counter); // 4

上面代码说明,ES6 模块输入的变量counter是活的,完全反应其所在模块lib.js内部的变化。

再举一个出现在export一节中的例子。

// m1.js

export var foo = ‘bar‘;

setTimeout(() => foo = ‘baz‘, 500);

// m2.js

import {foo} from ‘./m1.js‘;

console.log(foo);

setTimeout(() => console.log(foo), 500);

上面代码中,m1.js的变量foo,在刚加载时等于bar,过了 500 毫秒,又变为等于baz。

让我们看看,m2.js能否正确读取这个变化。

$ babel-node m2.js

bar

baz

上面代码表明,ES6 模块不会缓存运行结果,而是动态地去被加载的模块取值,并且变量总是绑定其所在的模块。

由于 ES6 输入的模块变量,只是一个“符号连接”,所以这个变量是只读的,对它进行重新赋值会报错。

// lib.js

export let obj = {};

// main.js

import { obj } from ‘./lib‘;

obj.prop = 123; // OK

obj = {}; // TypeError

上面代码中,main.js从lib.js输入变量obj,可以对obj添加属性,但是重新赋值就会报错。因为变量obj指向的地址是只读的,不能重新赋值,这就好比main.js创造了一个名为obj的const变量。

第二个差异:
    因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

Es6模块的设计思想是尽量放入静态化,使得在编译时就能确定依赖关系,而CommonJS就只能在运行时确定这些输入和输出的变量。

// CommonJS模块

let { stat, exists, readFile } = require(‘fs‘);

// 等同于

let _fs = require(‘fs‘);

let stat = _fs.stat;

let exists = _fs.exists;

let readfile = _fs.readfile;

上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。

ES6 通过export命令显式指定输出的代码,再通过import命令输入。

// ES6模块

import { stat, exists, readFile } from ‘fs‘;

上面代码的实质是从fs模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。

在es6中,export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。

export var foo = ‘bar‘;

setTimeout(() => foo = ‘baz‘, 500);

上面代码输出变量foo,值为bar,500 毫秒之后变成baz。

这一点与 CommonJS 规范完全不同。CommonJS 模块输出的是值的缓存,不存在动态更新。

export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,import命令也是如此。这是因为处于条件代码块之中,就没法做静态优化了,违背了 ES6 模块的设计初衷。

function foo() {

export default ‘bar‘ // SyntaxError

}

foo()

上面代码中,export语句放在函数之中,结果报错。

注意,import命令具有提升效果,会提升到整个模块的头部,首先执行。

foo();

import { foo } from ‘my_module‘;

上面的代码不会报错,因为import的执行早于foo的调用。这种行为的本质是,import命令是编译阶段执行的,在代码运行之前。

由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。

// 报错

import { ‘f‘ + ‘oo‘ } from ‘my_module‘;

// 报错

let module = ‘my_module‘;

import { foo } from module;

// 报错

if (x === 1) {

import { foo } from ‘module1‘;

} else {

import { foo } from ‘module2‘;

}

上面三种写法都会报错,因为它们用到了表达式、变量和if结构。在静态分析阶段,这些语法都是没法得到值的。

这样的设计,固然有利于编译器提高效率,但也导致无法在运行时加载模块。在语法上,条件加载就不可能实现。如果import命令要取代 Node 的require方法,这就形成了一个障碍。因为require是运行时加载模块,import命令无法取代require的动态加载功能。

const path = ‘./‘ + fileName;

const myModual = require(path);

上面的语句就是动态加载,require到底加载哪一个模块,只有运行时才知道。import命令做不到这一点。

因此,有一个提案,建议引入import()函数,完成动态加载。

import(specifier)

上面代码中,import函数的参数specifier,指定所要加载的模块的位置。import命令能够接受什么参数,import()函数就能接受什么参数,两者区别主要是后者为动态加载。

import()返回一个 Promise 对象。下面是一个例子。

const main = document.querySelector(‘main‘);

import(`./section-modules/${someVariable}.js`)

.then(module => {

module.loadPageInto(main);

})

.catch(err => {

main.textContent = err.message;

});

import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块。另外,import()函数与所加载的模块没有静态连接关系,这点也是与import语句不相同。import()类似于 Node 的require方法,区别主要是前者是异步加载,后者是同步加载。

目前阶段,通过 Babel 转码,CommonJS 模块的require命令和 ES6 模块的import命令,可以写在同一个模块里面,但是最好不要这样做。因为import在静态解析阶段执行,所以它是一个模块之中最早执行的。下面的代码可能不会得到预期结果。

require(‘core-js/modules/es6.symbol‘);

require(‘core-js/modules/es6.promise‘);

import React from ‘React‘;

注:
    import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。

import {a} from ‘./xxx.js‘

a = {}; // Syntax Error : ‘a‘ is read-only;

上面代码中,脚本加载了变量a,对其重新赋值就会报错,因为a是一个只读的接口。但是,如果a是一个对象,改写a的属性是允许的。

import {a} from ‘./xxx.js‘

a.foo = ‘hello‘; // 合法操作

上面代码中,a的属性可以成功改写,并且其他模块也可以读到改写后的值。不过,这种写法很难查错,建议凡是输入的变量,都当作完全只读,轻易不要改变它的属性。
————————————————
版权声明:本文为CSDN博主「jackTesla」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jackTesla/article/details/80796936

原文地址:https://www.cnblogs.com/bydzhangxiaowei/p/12238747.html

时间: 2024-07-30 22:57:07

js当中CommonJS 和es6的模块化引入方案以及比较的相关文章

一览js模块化:从CommonJS到ES6

本文由云+社区发表 模块化是指把一个复杂的系统分解到一个一个的模块. 模块化开发的优点: (1)代码复用,让我们更方便地进行代码管理.同时也便于后面代码的修改和维护. (2)一个单独的文件就是一个模块,是一个单独的作用域,只向外暴露特定的变量和函数.这样可以避免污染全局变量,减少变量命名冲突. js模块化规范有:CommonJS.AMD.CMD.ES6的模块系统.本文将依次介绍下每个规范. 0.早期:用script来引入js模块 <script type="text/javascript&

amd、cmd、CommonJS以及ES6模块化

AMD.CMD.CommonJs.ES6的对比 他们都是用于在模块化定义中使用的,AMD.CMD.CommonJs是ES5中提供的模块化编程的方案,import/export是ES6中定义新增的 什么是AMD.CMD.CommonJs? 他们之间有什么区别? 项目当中是如何使用? 1)AMD-异步模块定义 AMD是RequireJS在推广过程中对模块定义的规范化产出,它是一个概念,RequireJS是对这个概念的实现,就好比JavaScript语言是对ECMAScript规范的实现.AMD是一个

前端模块化 CommonJS AMD ES6 CMD

不使用模模块化的情况 util.js(基础函数库) getFormatDate 函数 a-util.js(业务基础函数库) aGetFormatDate 函数 使用getFormatDate a.js aGetFormatDate // util.js function getFormatDate(date, type) { // type === 1 返回 xxxx // type === 2 返回 xxx // ... } // a-util.js function aGetFormatDa

使用6to5,让今天就来写ES6的模块化开发!

http://es6rocks.com/2014/10/es6-modules-today-with-6to5/?utm_source=javascriptweekly&utm_medium=email 原文链接 我之前在Twitter上发过一个照片,表达出我有多快乐,这像是一个时光机让我们可以穿梭到未来-ES6的时代!下面让我来展示如何使用6to5让今天就可以练手ES6的模块化. 使用6to5让今天就可以练手ES6的模块化 第一步 如果你现在还不了解ES6的模块化开发,请在JSModules.

[js高手之路] es6系列教程 - 迭代器,生成器,for...of,entries,values,keys等详解

接着上文[js高手之路] es6系列教程 - 迭代器与生成器详解继续. 在es6中引入了一个新的循环结构for ....of, 主要是用来循环可迭代的对象,那么什么是可迭代的对象呢? 可迭代的对象一般都有Symbol.iterator属性,你可以在控制台中用console.dir打印数组,Map,Set,在他们的原型对象(prototype)上面就能找到.这个属性与迭代器密切相关,通过该函数可以返回一个迭代器,下文,我会举一个例子.一般来说所有的集合对象(数组,Set,Map 以及字符串)都是可

ES6(Module模块化)

模块化 ES6的模块化的基本规则或特点: 1:每一个模块只加载一次, 每一个JS只执行一次, 如果下次再去加载同目录下同文件,直接从内存中读取. 一个模块就是一个单例,或者说就是一个对象: 2:每一个模块内声明的变量都是局部变量, 不会污染全局作用域: 3:模块内部的变量或者函数可以通过export导出: 4:一个模块可以导入别的模块 一.导入和导出 1.导出 导入: 结果: 2.只取需要的 结果: 3.导出数目太多时(无需一一对应) * 代表全部内容,as 代表别名 结果: 4.推荐写法 结果

关于js当中一些糟糕的特性

首先,不可否认,js是一门具有许多优秀特性的弱类型语言,但是这门语言在设计之初就投入了工程实践,没有经历严格的实验室测试,以致力于它是如此的粗糙,在相当长的一段时间很不受开发者待见,被视为一门玩具性的语言,而且有些特性确实设计的很糟糕,今天就这些糟糕的特性我就具体谈一谈. 1.js当中首要的最糟糕的特性应该要数它对全局变量的依赖性.什么是全局变量?也就是对所有域都开放,能在任意时间.任意部分对其进行修改,这极大地降低了程序的可靠性.其次对于大型项目而言,随着业务拓展,代码量的增加,程序越来越庞大

Sea.js 提供简单、极致的模块化开发体验

http://seajs.org/docs/#intro 为什么使用 Sea.js ? Sea.js 追求简单.自然的代码书写和组织方式,具有以下核心特性: 简单友好的模块定义规范:Sea.js 遵循 CMD 规范,可以像 Node.js 一般书写模块代码. 自然直观的代码组织方式:依赖的自动加载.配置的简洁清晰,可以让我们更多地享受编码的乐趣. Sea.js 还提供常用插件,非常有助于开发调试和性能优化,并具有丰富的可扩展接口. 兼容性 Sea.js 具备完善的测试用例,兼容所有主流浏览器:

Node.js 4.0的ES6新特性

简介 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境.Node.js 使用了一个事件驱动.非阻塞式 I/O 的模型,使其轻量又高效.Node.js 的包管理器 npm,是全球最大的开源库生态系统. Node.js 4.0.0这个版本是Node和iojs合并后发布的首个稳定版本,并且为开发者带来了大量的ES6语言扩展.了解 Node.js中包括的ES6语言扩展.本课将会为你介绍如何使用这些新特性. Node.js 4.0.0 可以让您享受最尖端的技术,保持项