工程化实战内功修炼测试题(二)
ES6模块化
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
/** 定义模块 math.js /
var basicNum = 0;
var add = function (a, b) {return a + b;
};
export { basicNum, add };
/ 引用模块 **/
import { basicNum, add } from './math';
function test(ele) {ele.textContent = add(99 + basicNum);
}
如上例所示,使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。
// export-default.js
export default function () {console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'
模块默认输出, 其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。
ES6 模块与 CommonJS 模块的差异
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用;
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口;
CommonJS 模块是运行时加载:
CommonJS 模块就像是你把文件放到箱子里,打开箱子时它才会运行。只有当你实际使用 require() 时,模块才会加载并执行。因此,模块的 exports 只有在模块执行时才会创建和输出。
通俗例子:你拿到一本书,翻开它时才开始阅读内容,内容是等你打开后才展现的。
ES6 模块是编译时输出接口:
ES6 模块的情况则不同,它是静态的,在代码解析阶段就已经知道你在导出什么。模块之间的依赖和接口会在编译时就决定,甚至可以进行一些优化(如 tree shaking)。你导出的每个值都事先确定好了,不需要等到运行时。
通俗例子:就像你提前就知道一本书的目录,甚至可以提前知道每章的内容,不需要等到你开始阅读时再去解析。
下面重点解释第一个差异,我们还是举上面那个CommonJS模块的加载机制例子:
// 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 模块的运行机制与 CommonJS 不一样。ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
ES6实现
简单来说就一句话:使用Babel将ES6编译为ES5代码,使用Browserify编译打包js。
- 定义package.json文件
{"name" : "es6-babel-browserify","version" : "1.0.0"}
- 安装babel-cli, babel-preset-es2015和browserify
npm install babel-cli browserify -g
npm install babel-preset-es2015 --save-dev
- 定义.babelrc文件
{"presets": ["es2015"]}
- 定义模块代码
//module1.js文件
// 分别暴露
export function foo() {console.log('foo() module1')
}
export function bar() {console.log('bar() module1')
}
//module2.js文件
// 统一暴露
function fun1() {console.log('fun1() module2')
}
function fun2() {console.log('fun2() module2')
}
export { fun1, fun2 }//module3.js文件
// 默认暴露 可以暴露任意数据类项,暴露什么数据,接收到就是什么数据
export default () => {console.log('默认暴露')
}// app.js文件
import { foo, bar } from './module1'
import { fun1, fun2 } from './module2'
import module3 from './module3'
foo()
bar()
fun1()
fun2()
module3()
- 编译并在index.html中引入
- 使用Babel将ES6编译为ES5代码(但包含CommonJS语法) : babel js/src -d js/lib
- 使用Browserify编译js : browserify js/lib/app.js -o js/lib/bundle.js
然后在index.html文件中引入
<script type="text/javascript" src="js/lib/bundle.js"></script>
最后得到如下结果:
foo() module1
bar() module1
fun1() module2
fun2() module2
默认暴露
- 引入第三方库
首先安装依赖npm install jquery@1,然后在app.js文件中引入
//app.js文件
import { foo, bar } from './module1'
import { fun1, fun2 } from './module2'
import module3 from './module3'
import $ from 'jquery'foo()
bar()
fun1()
fun2()
module3()
$('body').css('background', 'green')
UMD(Universal Module Definition)
是一种 javascript 通用模块定义规范,让你的模块能在javascript所有运行环境中发挥作用。
意味着要同时满足CommonJS, AMD, CMD的标准,以下为实现:
(function(root, factory) {if (typeof module === 'object' && typeof module.exports === 'object') {console.log('是commonjs模块规范,nodejs环境')module.exports = factory();} else if (typeof define === 'function' && define.amd) {console.log('是AMD模块规范,如require.js')define(factory)} else if (typeof define === 'function' && define.cmd) {console.log('是CMD模块规范,如sea.js')define(function(require, exports, module) {module.exports = factory()})} else {console.log('没有模块环境,直接挂载在全局对象上')root.umdModule = factory();}
}(this, function() {return {name: '我是一个umd模块'}
}))
- CommonJS规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的,因此有了AMD CMD解决方案;
- AMD规范在浏览器环境中异步加载模块,而且可以并行加载多个模块。不过,AMD规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅;
- CMD规范与AMD规范很相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在Node.js中运行;
- ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案;
- UMD为同时满足CommonJS, AMD, CMD标准的实现;