js当中CommonJS 和es6的模块化引入方案以及比较
2021-04-23 19:28
标签:mon 属性 babel 动态加载 语法 函数的参数 ref 头部 返回 js当中CommonJS 和es6的模块化引入方案以及比较:https://blog.csdn.net/jackTesla/article/details/80796936 在es6之前,对于模块化方案主要是CommonJS和AMD两种。咱们这次说一下ES6和CommonJS的区别。 它们有两个重大差异: CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。 // 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变量。 第二个差异: 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 {a} from ‘./xxx.js‘ a = {}; // Syntax Error : ‘a‘ is read-only; 上面代码中,脚本加载了变量a,对其重新赋值就会报错,因为a是一个只读的接口。但是,如果a是一个对象,改写a的属性是允许的。 import {a} from ‘./xxx.js‘ a.foo = ‘hello‘; // 合法操作 上面代码中,a的属性可以成功改写,并且其他模块也可以读到改写后的值。不过,这种写法很难查错,建议凡是输入的变量,都当作完全只读,轻易不要改变它的属性。 js当中CommonJS 和es6的模块化引入方案以及比较 标签:mon 属性 babel 动态加载 语法 函数的参数 ref 头部 返回 原文地址:https://www.cnblogs.com/bydzhangxiaowei/p/12238747.html
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
第一个差异:
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个模块文件lib.js的例子。
因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。
————————————————
版权声明:本文为CSDN博主「jackTesla」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jackTesla/article/details/80796936
上一篇:揭秘webpack loader
下一篇:01 反js的根本原因
文章标题:js当中CommonJS 和es6的模块化引入方案以及比较
文章链接:http://soscw.com/index.php/essay/78626.html