浏览器加载
传统方法
默认情况下,浏览器同步加载javascript脚本,即渲染引擎遇到<script>标签就会停下来,等到脚本执行完毕再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。如果脚本体积很大,下载和执行时间就会很长,因此造成浏览器堵塞,用户会感到浏览器卡死了,体验很不好,所以浏览器允许脚本异步加载,下面是两种异步加载语法:
<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>
defer与async的区别是,前者要等到整个页面正常渲染结束才会执行,而后者一旦下载完成,渲染与引擎就会中断渲染,执行这个脚本以后再继续渲染。用一句话来说,defer是"渲染完再执行",async是"下载完就执行"。另外如果有多个defer脚本,则会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。
ES6模块与CommonJS模块的差异
ES模块与CommonJS模块有两大差异:
- CommonJS模块输出的是一个值的复制,ES6模块输出的是值的引用。
- CommonJS模块是运行时加载,ES6模块是编译时输出接口。
- CommonJS模块的顶层this指向当前模块,ES6模块的顶层this指向undefined。
第二个差异是因为CommonJS加载的是一个对象(即module.exports属性),该对象只有在脚本运行结束时才会生成。而ES6模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
第一个差异是因为CommonJS模块输出的是值的复制,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。ES6模块的运行机制与CommonJS不一样。JS引擎对脚本静态分析的时候,遇到模块加载命令import就会生成一个只读引用。原始值变了这个引用也会改变,给这个引用添加属性值,原始值也会改变,但是不能对这个引用重新赋值,否则会报错。
总结:CommonJS模块特性有点像基本数据类型,在用require或module.exports引用进来的时候,新值和原始值互不影响;ES6模块特性有点像引用数据类型,在用export或export default引用进来的时候,改变新值或原始值,另外一个值也会改变,但是新值不能重新赋值。
import命令加载CommonJS模块
Node采用CommonJS模块格式,模块的输出都定义在module.exports属性上面。在Node环境中,使用import命令加载CommonJS模块,Node会自动将module.exports属性当作模块的默认输出,即等同于export default,并且引用进来新值和原始值互不影响保持原来的特性,但是写法是ES6写法后面不能跟大括号。
require命令加载ES6模块
采用require命令加载ES6模块时,ES6模块的所有输出接口都会成为输入对象的属性。
// es.js
export let foo = {bar: 'my-default'}
export function f() {}
// cjs.js
const es = require('./es.js')
console.log(es)
{
get foo () {return foo}
get f () {return f}
}
require和import都可以用来加载CommonJS和ES6模块,只不过写法和返回结果有一点不同。
总结:export加接口输出的模块用import后加上{}大括号引入,export default不加接口输出的模块用import后不加{}大括号引入,module.exports加等于号输出的模块用import可以加上大括号也可以不加大括号引入,加大括号表示解构赋值,不加大括号表示整体引入。
网友评论