平时写代码的时候,知道如何导出变量,如何引入变量。可见模块化就在咱们的身边,但是为何前端会引入模块化的概念,以及为何有同步加载和异步加载呢?javascript
在以前的项目中,若是没有模块化的概念,不少变量都有重名或者不当心从新赋值的危险。并且用 script 有可能阻塞 HTML 的下载或者渲染,影响用户体验。css
在平时编码中,咱们都习惯把一些通用的方法提出来放在一个文件里,哪一个地方须要用到就引用,这样可以很好的梳理页面的逻辑,维护代码的成本也下降了很多。因此模块化给咱们带来的好处是显而易见的。html
现有的一些模块化方案有如下几种:前端
下面我就自身的理解对这几种方案作一个对比和总结:java
ES6
模块遇到 import
命令时,不会去执行模块,而是生成一个引用,等用到的时候,才去模块中取值。由于是动态引用,因此不存在缓存的问题。能够看一下下面的例子:node
// util.js
export let env = 'qa';
setTimeout(() => env = 'local', 1000);
// main.js
import {env} from './util';
console.log('env:', env);
setTimeout(() => console.log('new env:', env), 1500);复制代码
执行 main.js
,会输出下面的结果:react
// env: qa
// new env: local复制代码
能够看出 ES6
模块是动态的取值,不会缓存运行的结果。es6
目前浏览器还没有支持 ES6
模块 ,因此须要使用 babel 转换,你们能够在 Babel 提供的 REPL 在线编译器 中查看编译后的结果。浏览器
// es 6
import {add} from './config';
// es 5
'use strict';
var _config = require('./config');复制代码
能够看出,最后转换成 require
的方式了。ES6
模块在浏览器和服务器端均可以用,ES6
模块的设计思想是尽可能的静态化,使得编译时就能肯定模块的依赖关系,以及输入和输出的变量。缓存
import
命令具备提高效果,会提高到整个模块的头部,首先执行,因此不能把 import
写在表达式里面。这和 ES 6
模块的概念不符合。
node
的模块遵循 CommonJS
规范。在服务器端,依赖是保存在本地硬盘的,因此读取的速度很是快,使用同步加载不会有什么影响。
看一下 CommonJS
的语法:
// header.js
module.exports = {
title: '我是柚子'
};
// main.js
var header = require('./header');复制代码
这里的 module 表明的是当前模块,它是一个对象,把它打印出来是下面的结果:
{
Module {
id: '/Users/yanmeng/2017FE/css-animation/js/b.js',
exports: { item: 'item' },
parent:
Module {
id: '.',
exports: {},
parent: null,
filename: '/Users/yanmeng/2017FE/css-animation/js/main.js',
loaded: false,
children: [ [Circular] ],
paths:
[ '/Users/yanmeng/2017FE/css-animation/js/node_modules',
'/Users/yanmeng/2017FE/css-animation/node_modules',
'/Users/yanmeng/2017FE/node_modules',
'/Users/yanmeng/node_modules',
'/Users/node_modules',
'/node_modules' ] },
filename: '/Users/yanmeng/2017FE/css-animation/js/b.js',
loaded: false,
children: [],
paths:
[ '/Users/yanmeng/2017FE/css-animation/js/node_modules',
'/Users/yanmeng/2017FE/css-animation/node_modules',
'/Users/yanmeng/2017FE/node_modules',
'/Users/yanmeng/node_modules',
'/Users/node_modules',
'/node_modules'
]
}复制代码
以后调用这个模块的时候,就会从 exports 中取值,即便再执行,也不会再执行改模块,而是从缓存中取值,返回的是第一次运行的结果,除非手动清除缓存。
// 删除指定模块的缓存
delete require.cache[moduleName];
// 删除全部模块的缓存
Object.keys(require.cache).forEach(function(key) {
delete require.cache[key];
})复制代码
缓存是根据绝对路径识别模块的,若是同一个模块放在不一样的路径下,仍是会从新加载这个模块。
require 命令第一次执行的时候,会加载并执行整个脚本,而后在内存中生成此脚本返回的 exports 对象。
ES6
模块是动态引用,而且不会缓存值。
ES6
模块在对脚本静态分析的时候,遇到 import
就会生成一个只读引用,等到脚本真正执行的时候,再根据这个只读引用,到被加载的那个模块里取值,因此说 ES6
模块是动态引用。
从依赖中引入的模块变量是一个地址引用,是只读的,能够为它新增属性,但是不能从新赋值。
// lib.js
export let obj = {};
// main.js
import { obj } from './lib';
obj.prop = 123; // OK
obj = {}; // TypeError复制代码
又称异步加载模块(Asynchronous Module Definition)
- 依赖前置
- 比较适合浏览器环境
- 实现
js
文件的异步加载,避免网页失去响应- 管理模块之间的依赖性,便于代码的编写和维护
- 表明库: RequireJS
若是在浏览器环境,就须要在服务端加载模块,那么采用同步加载的方法就会影响用户体验,因此浏览器端通常采用 AMD
规范。
它采用异步方式加载模块,模块的加载不影响它后面语句的运行。全部依赖这个模块的语句,都定义在一个回调函数中,等到加载完成以后,这个回调函数才会运行。
// lib.js
define('[./util.js]', function(util){
function bar() {
util.log('it is sunshine');
};
return {
bar: bar
};
});
// main.js
require(['./lib.js'], function(lib){
console.log(lib.bar());
})复制代码
Sea.js
实现了这个规范,Sea.js
遇到依赖后只会去下载 JS
文件,并不会执行,而是等到全部被依赖的 JS
脚本都下载完之后,才从头开始执行主逻辑。所以被依赖模块的执行顺序和书写顺序彻底一致。
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// ...
var b = require('./b')
b.doSomething()
// ...
})复制代码
本文只是浅显的介绍了一些模块的概念和用法,关于 ES6 模块、 CommonJs 的循环加载和 ES 6 模块和 CommonJs的互相引用,你们能够动手实践一下,会受益不浅。
参考: