天天看點

js中的子產品化二

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;

繼續閱讀