web前端教程接下來降為大家繼續分享js中的子產品化知識
4.循環依賴
就是a依賴b,b依賴a,對于不同的規範也有不同的結果。
4.1CommonJS
對于node,每一個子產品的exports={done:false}表示一個子產品有沒有加載完畢,經過一系列的加載最後全部都會變為true。 同步,從上到下,隻輸出已經執行的那部分代碼 首先,我們寫兩個js用node跑一下:
//a.js
console.log('a.js')
var b = require('./b.js')
console.log(1)
//b.js
console.log('b.js')
var a = require('./a.js')
console.log(2)
//根據他的特點,require一個檔案的時候,馬上運作内部的代碼,是以相當于
//輸出是a.js、b.js、2、1
複制代碼
加上export的時候:
module.exports = {val:1}
console.log(b.val)
module.exports = {val:2}
b.val = 3
console.log(b)
console.log(a.val)
a.val = 3
console.log(a)
//1.在a.js暴露出去一個對象module.exports = {val:1}
//2.require了b,來到b,運作b腳本
//3.b的第一行,把{val:1}暴露出去,引入剛剛a暴露的{val:1},列印a.val的結果肯定是1
//4.重新暴露一次,是{val:2},然後做了一件多餘的事情,改a.val為3(反正是拷貝過的了怎麼改都不會影響a.js),毫無疑問列印出{ val: 3 }
//5.回到a,繼續第三行,列印b.val,因為b暴露的值是2,列印2
//6.繼續再做一件無意義的事情,列印{ val: 3 }
解決辦法:代碼合理拆分
4.2ES6子產品
ES6子產品是輸出值的引用,是動态引用,等到要用的時候才用,是以可以完美實作互相依賴,在互相依賴的a.mjs和b.mjs,執行a的時候,當發現import馬上進入b并執行b的代碼。當在b發現了a的時候,已經知道從a輸入了接口來到b的,不會回到a。但是在使用的過程中需要注意,變量的順序。
如果是單純的暴露一個基本資料類型,當然會報錯not defined。 因為函數聲明會變量提升,是以我們可以改成函數聲明(不能用函數表達式)
//a.mjs
import b from './b'
console.log(b())
function a(){return 'a'}
export default a
//b.mjs
import a from './a'
console.log(a())
function b(){return 'b'}
export default b
4.3 require
我們一般使用的時候,都是依賴注入,如果是有循環依賴,那麼可以直接利用require解決
define('a',['b'],function(b){
//dosomething
});
define('b',['a'],function(a){
//dosomething
//為了解決循環依賴,在循環依賴發生的時候,引入require:
define('a',['b','require'],function(b,require){
//dosomething
require('b')
4.4 sea
循環依賴,一般就是這樣
define(function(require, exports, module){
var b = require('./b.js');
//......
var a = require('./a.js');
//......
而實際上,并沒有問題,因為sea自己解決了這個問題: 一個子產品有幾種狀态:
'FETCHING': 子產品正在下載下傳中 'FETCHED': 子產品已下載下傳 'SAVED': 子產品資訊已儲存 'READY': 子產品的依賴項都已下載下傳,等待編譯 'COMPILING':子產品正在編譯中 'COMPILED': 子產品已編譯
步驟:
1.子產品a下載下傳并且下載下傳完成FETCHED
2.編譯a子產品(執行回調函數)
3.遇到了依賴b,b和自身沒有循環依賴,a變成SAVED
4.子產品b下載下傳并且下載下傳完成FETCHED
5.b遇到了依賴a,a是SAVED,和自身有循環依賴,b變成READY,編譯完成後變成COMPILED
6.繼續回到a,執行剩下的代碼,如果有其他依賴繼續重複上面步驟,如果所有的依賴都是READY,a變成READY
7.繼續編譯,當a回調函數部分所有的代碼運作完畢,a變成COMPILED
對于所有的子產品互相依賴的通用的辦法,将互相依賴的部分抽取出來,放在一個中間件,利用釋出訂閱模式解決
5.webpack是如何處理子產品化的
假設我們定義兩個js:app.js是主入口檔案,a.js、b.js是app依賴檔案,用的是COMMONJS規範 webpack首先會從入口子產品app.js開始,根據引入方法require把所有的子產品都讀取,然後寫在一個清單上:
var modules = {
'./b.js': generated_b,
'./a.js': generated_a,
'./app.js': generated_app
}
'generated_'+name是一個IIFE,每個子產品的源代碼都在裡面,不會暴露内部的變量。比如對于沒有依賴其他子產品的a.js一般是這樣,沒有變化:
function generated_a(module, exports, webpack_require) {
// ...a的全部代碼
對于app.js則不一樣了:
function generated_app(module, exports, webpack_require) {
var a_imported_module = __webpack_require__('./a.js');
var b_imported_module = __webpack_require__('./b.js');
a_imported_module['inc']();
b_imported_module['inc']();
webpack_require就是require、exports、import這些的具體實作,夠動态地載入子產品a、b,并且将結果傳回給app
對于webpack_require,大概是這樣的流程
var installedModules = {};//儲存已經加載完成的子產品
function webpack_require(moduleId) {
if (installedModules[moduleId]) {//如果已經加載完成直接傳回
return installedModules[moduleId].exports;
var module = installedModules[moduleId] = {//如果是第一次加載,則記錄在表上
i: moduleId,
l: false,//沒有下載下傳完成
exports: {}
};
//在子產品清單上面讀取對應的路徑所對應的檔案,将子產品函數的調用對象綁定為module.exports,并傳回
modules[moduleId].call(module.exports, module, module.exports,__webpack_require__);
module.l = true;//下載下傳完成
return module.exports;
對于webpack打包後的檔案,是一個龐大的IIFE,他的内容大概是這樣子:
(function(modules) {
var installedModules = {};
function __webpack_require__(moduleId) { /*...*/}
__webpack_require__.m = modules;//所有的檔案依賴清單
__webpack_require__.c = installedModules;//已經下載下傳完成的清單
__webpack_require__.d = function(exports, name, getter) {//定義子產品對象的getter函數
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
configurable: false,
enumerable: true,
get: getter
});
}
};
__webpack_require__.n = function(module) {//當和ES6子產品混用的時候的處理
var getter = module && module.__esModule ?//如果是ES6子產品用module.default
function getDefault() { return module['default']; } :
function getModuleExports() { return module; };//是COMMONJS則繼續用module
__webpack_require__.d(getter, 'a', getter);
return getter;
};
__webpack_require__.o = function(object, property) { //判斷是否有某種屬性(如exports)
return Object.prototype.hasOwnProperty.call(object, property);
__webpack_require__.p = "";//預設路徑為目前
return __webpack_require__(__webpack_require__.s = 0);//讀取第一個子產品
})
/**/
//IIFE第二個括号部分
([
(function(module, exports, __webpack_require__) {
var a = __webpack_require__(1);
var b = __webpack_require__(2);
//子產品app代碼
}),
//子產品a代碼
module.exports = ...
//子產品b代碼
]);
如果是ES6子產品,處理的方法也不一樣。還是假設我們定義兩個js:app.js是主入口檔案,a.js、b.js是app依賴檔案。
//前面這段是一樣的
(function(module, __webpack_exports__, __webpack_require__) {//入口子產品
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
var __WEBPACK_IMPORTED_MODULE_0__m__ = __webpack_require__(1);
var __WEBPACK_IMPORTED_MODULE_1__m__ = __webpack_require__(2);
Object(__WEBPACK_IMPORTED_MODULE_0__m__["a"])();//用object包裹着,使得其他子產品export的内容即使是基本資料類型,也要讓他變成一個引用類型
Object(__WEBPACK_IMPORTED_MODULE_1__m__["b"])();
(function(module, __webpack_exports__, __webpack_require__) {
__webpack_exports__["a"] = a;//也就是export xxx
//....
__webpack_exports__["b"] = b;