平時寫代碼的時候,知道如何導出變量,如何引入變量。可見模塊化就在咱們的身邊,但是爲何前端會引入模塊化的概念,以及爲何有同步加載和異步加載呢?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的互相引用,你們能夠動手實踐一下,會受益不淺。
參考: