- 前端js模塊化的演變發展
- 模塊化解決的問題
- 傳統模塊化、插件化
- CommonJS
- AMD/CMD
- ES6模塊化
ES6以前 沒有js引擎
- 一開始js寫在html的script標簽里
- js內容增多,抽取出index.js文件,外部引入
- js再增加,index.html對應index.js index2.html對應index2.js(模塊化概念的誕生)
- 含有可復用的代碼,提出公共的common.js
- 引入common.js的所有內容不合理 → 不能光以頁面為基準來區分程序塊、分js文件
案例一 模塊化初試
// index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><script type="text/javascript" src="js/module_a.js"></script><script type="text/javascript" src="js/module_b.js"></script><script type="text/javascript" src="js/module_c.js"></script><script type="text/javascript" src="js/index.js"></script>
</body>
</html>
// module_a.js
var a = [1, 2, 3, 4, 5].reverse()
// module_b.js
var b = a.concat([6, 7, 8, 9, 10])
// module_c.js
var c = b.join('-')
// index.js
console.log(a)
console.log(b)
console.log(c)
存在問題
- js引擎遇到script時阻塞,所以這4個js文件必須按內部的邏輯,順序加載,順序是不能變的
- 這4個文件共用了JS作用域-全局作用域
- 因此:污染全局 + if 變量重名 → 變量覆蓋
模塊化解決問題:
- 加載順序
- 污染全局
案例二 IIFE注入
歷史問題:ECMA規定語句應當以分號結尾,早前js都是運行在瀏覽器上的,但瀏覽器支持判斷當前是否是語句,是就自動加上分號。當使用多個IIFE,且不寫分號時,瀏覽器無法識別,報錯。因此約定俗成的規定,IIFE前面必須寫分號,更規范的是結尾也寫分號,即
;(function(){
})();
- 使用IIFE,解決污染全局,為了易于拓展,模塊應當返回對象
- 新的問題,若沒有拋到全局,如何在模塊之間獲得相應的abc
- 用變量接收IIFE的返回值,在需要用的的模塊傳入(注入),解決了模塊依賴
- 注意:模塊名完全獨立,不應該重復,因此在全局聲明了,而內部abc屬于數據類型的變量,不能在全局聲明
- 注意:不注入moduleABC,直接用moduleA.a訪問變量能得到正確結果,但注入意味著moduleABC被引入到局部作用域下,不再需要去全局上查找了
// module_a.js
var moduleA = (function () {var a = [1, 2, 3, 4, 5].reverse()return {a: a}
})();
// module_b.js
var moduleB = (function (moduleA) {var b = moduleA.a.concat([6, 7, 8, 9, 10])return {b: b}
})(moduleA);
// module_c.js
var moduleC = (function (moduleB) {var c = moduleB.b.join('-')return {c: c}
})(moduleB);
// index.js
; (function (moduleA, moduleB, moduleC) {console.log(moduleA.a)console.log(moduleB.b)console.log(moduleC.c)
})(moduleA, moduleB, moduleC);
存在問題
- 順序問題依然未解決
插件
- 構造函數執行init
- 構造函數掛載到window(插件)
- script里實例化
案例三 CommonJS
NodeJS誕生帶來了前所未有的模塊化體驗
require(...) 引入模塊
module.exports導出模塊
運行在node環境下
CommonJS是模塊化規范,來源于NodeJS
在服務端開發,引入模塊用require,是同步的方法
只要引用,就會創建模塊的實例
有非常強的緩存機制
一定是在Node上運行,客戶端運行不了(要借助webpack?)
require實質是IIFE,會傳入一些參數
(function(exports,require,module,__filename.__dirname){})()
- CommonJS
- 只引入index.js
// index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><script type="text/javascript" src="index.js"></script>
</body>
</html>
// module_a.js
var a = (function () {return [1, 2, 3, 4, 5].reverse()
})();
module.exports = {a
};
// module_b.js
var moduleA = require('./module_a')
var b = (function () {return moduleA.a.concat([6, 7, 8, 9, 10])
})();
module.exports = {b
}
// module_c.js
var moduleB = require('./module_b')
var c = (function () {return moduleB.b.join('-')
})();
module.exports = {c: c
}
// index.js
var moduleA = require('./js/module_a.js');
var moduleB = require('./js/module_b.js');
var moduleC = require('./js/module_c.js');
; (function () {console.log(moduleA.a)console.log(moduleB.b)console.log(moduleC.c)
})()
案例四 AMD
- 不需要借助webpack就能運行在客戶端
- 所有依賴加載完成后才會執行回調函數(前置依賴)
AMD Asynchronous Module Definition 異步模塊定義
來源于CommonJS
define(moduleName, [module], factory) 定義模塊
require([module], callback) 引入模塊
RequireJS實現AMD
- 引入require.js
- 定義+使用依賴時注入
- 使用module得先require.config配置路徑
// index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><script src="js/require.js"></script><script src="js/index.js"></script>
</body>
</html>
// module_a.js
define('moduleA', function () {var a = [[1, 2, 3, 4, 5]]return {a: a.reverse()}
})
// module_b.js
define('moduleB', ['moduleA'], function (moduleA) {return {b: moduleA.a.concat([6, 7, 8, 9, 10])}
})
// module_c.js
define('moduleC', ['moduleB'], function (moduleB) {return {c: moduleB.b.join('-')}
})
// index.js
require.config({paths: {moduleA: 'js/module_a',moduleB: 'js/module_b',moduleC: 'js/module_c'}
})
require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC) {console.log(moduleA.a)console.log(moduleB.b)console.log(moduleC.c)
});
案例五CMD
- 阿里對模塊化的貢獻
- require加載 define定義
- exports導出(return和它的效果一直) module操作
- 需要配置模塊URL
- 依賴加載完畢后執行factory
- 依賴就近 按需加載(這是和CommonJS AMD本質上的不同)
Common Mudule Definition 通用模塊定義
define(function(require,exports,module){}) 定義模塊
seajs.use([module路徑],function(moduleA,moduleB,moduleC){}) 使用模塊
// index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><script src="js/sea.js"></script><script src="js/index.js"></script>
</body>
</html>
// module_a.js
define(function (require, exports, module) {var a = [[1, 2, 3, 4, 5]]return {a: a.reverse()}
})
// module_b.js
define(function (require, exports, module) {var moduleA = require('module_a')return {b: moduleA.a.concat([6, 7, 8, 9, 10])}
})
// module_c.js
define(function (require, exports, module) {var moduleB = require('module_b')return {c: moduleB.b.join('-')}
})
// index.js
seajs.use(['module_a.js', 'module_b.js', 'module_c.js'], function (moduleA, moduleB, moduleC) {console.log(moduleA.a)console.log(moduleB.b)console.log(moduleC.c)
})
案例六 ES6模塊化規范
import module from ‘模塊路徑’ 導入模塊
export module 導出模塊Uncaught SyntaxError: Cannot use import statement outside a module
調試過程中的報錯解答
// index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><script type="module" src="./js/index.js"></script>
</body>
</html>
// module_a.js
export default {a: [1, 2, 3, 4, 5].reverse()
}
// module_b.js
import moduleA from './module_a.js'
export default {b: moduleA.a.concat([6, 7, 8, 9, 10])
}
// module_c.js
import moduleB from './module_b.js'
export default {c: moduleB.b.join('-')
}
// index.js
import moduleA from './module_a.js'
import moduleB from './module_b.js'
import moduleC from './module_c.js'; (function (moduleA, moduleB, moduleC) {console.log(moduleA.a)console.log(moduleB.b)console.log(moduleC.c)
})(moduleA, moduleB, moduleC);
案例7 CommonJS與ES6的區別
- 配置webpack
// export.js
exports.a = 0;
setTimeout(() => {console.log('來自export', ++exports.a)
}, 300);
// commonjs.js
const { a } = require('./export')
setTimeout(() => {console.log('來自commonjs', a)
}, 300);
// es6.js
import { a } from './export'
setTimeout(() => {console.log('來自es6', a) // a是只讀的
}, 300);
- commonjs輸出的是一個值的拷貝
- es6模塊輸出的是值的引用
- commonjs模塊是在運行時加載(commonjs運行在服務端,require時加載)
- es6模塊是在編譯時加載