JavaScript模塊化的演變


前情回顧:

自執行函數(IIFE):

作用:馬上執行這個函數,自執行函數(IIFE),不易讀

(function(x){console.log(x);})(3);

易讀版本:

(function(x){
    return x *x;
})(3);

閉包引申:

回顧:

function create_counter(initial){
    var x = initial || 0;  //如果initial沒有值,那么采用后面的0
    return {
        //對象
        inc:function(){
            x+=1;
            return x;
        }
    }
}
var c1 = create_counter();
console.log(c1.inc());
console.log(c1.inc());
console.log(c1.inc());


var c2 = create_counter(10);
console.log(c2.inc());
console.log(c2.inc());
console.log(c2.inc()); */

箭頭函數:

function (x){
    return x * x;
}

上述代碼等價於下面:

x => x*x;

箭頭函數的無參、單參、雙參、多參的格式:

//無參
()=>3;
//單參
x =>{
    if(x > 0){
        return x*x;
    }else{
        return -x*x;
    }
}
//雙參
(x,y) => x*x+y*y;
//多參
(x,y,...rest)=>{

}

this指向的引入以及發展:

this的指向在有無use strict會不同的,我們通過幾段不同的代碼段引入this以及this指向的發展。

'use strict';
//1版-->正常使用
var xiaoming = {
    name:'小明',
    birth:2000,
    age:function(){
        var y = new Date().getFullYear();
        return y - this.birth;
    }
};
console.log(xiaoming.age()); 
//2版:
'use strict';
/* 
  嚴格模式:
  xiaoming.age() 可以得到結果,
  getAge()顯示報錯--> Cannot read property 'birth' of undefined
*/
function getAge(){
    var y = new Date().getFullYear();
    return y - this.birth;
}
var xiaoming = {
    name:'小明',
    birth:2000,
    age:getAge
};
console.log(xiaoming.age());
console.log(getAge()); 

前兩個版本暫時還是沒有引出this以及指向,下面看第三個版本,這個版本會有較大的改變,在不在strict中的結果也會不一樣。

'use strict';
/* 
  嚴格模式下:會報錯,Cannot read property 'birth' of undefined
  非嚴格模式下:NaN  not a number,瀏覽器不會爆紅,可能指向window
 */
var obj = {
    birth:2000,
    getAge:function(){
        function GetAgeFormBirth(){
            var y = new Date().getFullYear();
            return y - this.birth;
        }
        return GetAgeFormBirth();
    }
};
console.log(obj.getAge()); 

這是js的遺留問題,為了解決這個問題,我們可以使用變量保存this的指向,如版本四:

'use strict';
var obj = {
    birth:2000,
    getAge:function(){
        //保存thisz指向
        var that = this;
        function GetAgeFormBirth(){
            var y = new Date().getFullYear();
            return y - that.birth;
        }
        return GetAgeFormBirth();
    }
};
console.log(obj.getAge());

但是這種表述方式比較麻煩,代碼量也有些多,我們可以采用箭頭函數來實現:

//這是個對象
'use strict';
var obj = {
    birth:2000,
    getAge:function(){
        var b = this.birth;
        var fn = ()=> new Date().getFullYear()-this.birth;
        return fn();
    }
};
console.log(obj.getAge()); 

模塊化演變:

模塊化演變是為了初學者一步一步實現和解決代碼的冗余、復用性、命名污染問題。

模塊演變1:

缺點已在代碼塊中標明

<!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>
    <!-- html --》"" -->
    <!-- js --》'' -->
    <!-- 使用內嵌腳本 -->
    <!-- 
        缺點:
        復用性 很低 
        1.缺乏依賴解析
        2.全局命名空間污染的問題
    -->
    <h1>
        the answer is <span id="answer"></span>
    </h1>
    <script>
        function add(a,b){
            return a+b;
        }
        function reduce(arr,iteratee){
            var index=0,length = arr.length,memo = arr[index];
	index+=1;
            for(index =0;index < length;index++){
                memo = iteratee(memo,arr[index]);
            }
            return memo;
        }
        function sum(arr){
            return reduce(arr,add);
        }
        var values = [1,2,3,4,5,6,7,8,9];
        var answer = sum(values);
        document.getElementById("answer").innerHTML = answer;
    </script>
</body>
</html>

模塊演變2:

add.js

function add(a,b){
    return a+b;
}

reduce.js

function reduce(arr,iteratee){
    var index=0,length = arr.length,memo = arr[index];
    index+=1;
    for(index =0;index < length;index++){
        memo = iteratee(memo,arr[index]);
    }
    return memo;
}

sum.js

function sum(arr){
    return reduce(arr,add);
}

main.js

var values = [1,2,3,4,5,6,7,8,9];
var answer = sum(values);
document.getElementById("answer").innerHTML = answer;

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>
    <h1>
        the answer is <span id="answer"></span>
    </h1>
    <!-- script標簽引入JavaScript -->
    <!-- 缺點:
        必須保證js的引用順序正確
        同樣缺乏依賴解析
        同樣有命名沖突的問題
    -->
    <script src = "./add.js"></script>
    <script src = "./reduce.js"></script>
    <script src="./sum.js"></script>
    <script src = "./main.js"></script>
</body>
</html>

模塊演變3:

myApp.js

// 空對象
var myApp = {
};

add.js

// 立即執行函數
(function (){
    // 將add放入myApp中
    myApp.add = function(a,b){
        return  a+b;
    }
})();

reduce.js

// 立即執行函數IIFE
(function (){
    // 將reduce放入myApp中
    myApp.reduce = function(arr,iteratee){
        var index=0,length = arr.length,memo = arr[index];
	index+=1;
        for(;index < length;index++){
            memo = iteratee(memo,arr[index]);
        }
    return memo;
    }
})();

sum.js

// 立即執行函數
(function (){
    // 將sum放入myApp中
    myApp.sum = function(arr){
        return  myApp.reduce(arr,myApp.add);
    }
})();

main.js

/**
 * @description: 
 * @param {*}
 * @return {*}
 */
/* (function(){
    var values = [1,2,3,4,5,6,7,8,9];
    var answer = myApp.sum(values);
    document.getElementById("answer").innerHTML = answer;
}); */

(function(app){
    var values = [1,2,3,4,5,6,7,8,9];
    var answer = myApp.sum(values);
    document.getElementById("answer").innerHTML = answer;
})(myApp);

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>
    <h1>
        the answer is <span id="answer"></span>
    </h1>

    <script src="./myapp.js"></script>
    <script src="./reducec.js"></script>
    <script src="./sum.js"></script>
    <script src="./add.js"></script>
    <script src="./main.js"></script>
    
</body>
</html>

模塊演變4:

采用require.js來實現。

為什么要使用require.js來實現:

在正式開發的項目中,隨着js的外部引用文件越來越多,網頁失去響應的時間就會越長;其次,由於js文件之間存在依賴關系,因此必須嚴格保證加載順序,依賴性最大的模塊一定要放到最后加載,當依賴關系很復雜的時候,代碼的編寫和維護都會變得困難。

在開發中,有一個網頁三秒原則

網頁三秒原則:

當用戶的在請求一個網站的時候,響應時間超過三秒鍾,大部分用戶將關閉或者重新刷新網頁,用戶體驗很不爽。

所以require.js就解決上述問題:

  1. 實現js文件的異步加載,避免網頁失去響應;

  2. 管理模塊之間的依賴性,便於代碼的編寫和維護。

require.js的AMD格式規范:

// main.js

define(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){

    // some code here

});

define()函數接受的參數。

  1. 第一個參數是一個數組,表示所依賴的模塊,上例就是['moduleA', 'moduleB', 'moduleC'],即主模塊依賴這三個模塊;

  2. 第二個參數是一個回調函數,當前面指定的模塊都加載成功后,它將被調用。加載的模塊會以參數形式傳入該函數,從而在回調函數內部就可以使用這些模塊。

  3. 模塊必須采用特定的define()函數來定義。如果一個模塊不依賴其他模塊,那么可以直接定義在define()函數之中

  4. 如果這個模塊還依賴其他模塊,那么define()函數的第一個參數,必須是一個數組,且指明該模塊的依賴性。

define()異步加載moduleA,moduleB和moduleC,瀏覽器不會失去響應;它指定的回調函數,只有前面的模塊都加載成功后,才會運行,解決了依賴性的問題。

以下是模塊演變4的實現代碼:

index.js

<!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>
    <h1>
        the answer is <span id="answer"></span>
    </h1>
	<!-- 入口點是main -->
	<!-- require加載了main.js的依賴,也會加載它依賴的依賴 -->
	<!-- 帶來的問題:
		1. 降低一些性能
	 -->
    <script data-main="main" src = "require.js"></script>
</body>
</html>

data-main屬性的作用是,指定網頁程序的主模塊

main.js

define(['sum'],function(sum){
		var value = [1,2,3,4,5,6,7,8,9];
		var answer = sum(value);
		document.getElementById('answer').innerHTML = answer;
})

sum.js

define([
    'add',
    'reduce'
], function(add, reduce) {
    var sum = function(arr){
        return reduce(arr,add);
    }
    return sum;
    
});

reduce.js

define([], function(arr,iteratee){
    var reduce = function(arr,iteratee){
        var index=0,length = arr.length,memo = arr[index];
	index+=1;
        for(;index < length;index++){
            memo = iteratee(memo,arr[index]);
        }
        return memo;
    }
    return reduce;
});

add.js

define([],function(){
    var add = function(a,b){
        return a+b;
    }
	return add;
})

分析上述代碼的執行流程:

  1. 先加載index.html以及require.js文件,找到模塊的主入口(main.js)
  2. 加載main.js,由於main中依賴sum.js
  3. 再加載sum.js,sum中依賴add.js以及reduce.js
  4. 再加載add.js以及reduce.js
  5. 最后全部依賴執行完成后,回調得到的結果

執行效果以及文件加載順序的觀察:

image-20210712142033851

模塊演變5:

基於commonJS規范以及browserify瀏覽器端的模塊演變

什么是browserify:

browserify是專注於解決按照CommonJS規范書寫的模塊能夠在瀏覽器中使用問題的構建工具。

CommonJS規范:

  1. 每個文件就是一個模塊,有自己的作用域。在一個文件里面定義的變量、函數、類,都是私有的,對其他文件不可見
  2. 每個模塊內部,module變量代表當前模塊。這個變量是一個對象,它的exports屬性(即module.exports)是對外的接口。加載某個模塊,其實是加載該模塊的module.exports屬性。

下載方式:

npm install -g browserify

代碼如下:

index.js

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<h1>
			The Answer is <span id="answer"></span>
		</h1>
		<!--  browserify .\main.js -o bundle.js   打包-->
		<script src="bundle.js"></script>
	</body>
</html>

注意點:引用的js的文件是與我們打包命令時 -o后面名字對應的。

main.js

var sum = require('./sum');
var values = [1,2,3,4,5,6,7,8,9];
var answer = sum(values);
document.getElementById("answer").innerHTML = answer;

sum.js

var reduce = require('./reduce');
var add = require('./add');

module.exports = function(arr){
	return reduce(arr,add);
};

reduce.js

module.exports = function reduce(arr,iteratee){
        var index=0,length = arr.length,memo = arr[index];
	index+=1;
        for(;index < length;index++){
            memo = iteratee(memo,arr[index]);
        }
        return memo;
    };

add.js

module.exports = function add(a,b){
	return a+b;
};

打包方式:

browserify .\main.js -o bundle.js   //window中

效果圖:

image-20210712125510595

運行查看加載信息:

image-20210712144048436

可以與require.js的加載方式做對比,理解兩者的不同以及相同的地方。

擴展:

require.js VS browserify

  • require.js是模塊加載器;browserify是預編譯工具
  • require.js遵循的是AMD規范;browserify遵循的是CommonJS規范
  • require.js是在瀏覽器端運行期分析依賴;browserify在服務器端編譯期就進行了依賴分析,通過每個模塊的外部接口獲取信息

結束:

如果你看到這里或者正好對你有所幫助,希望能點個關注或者推薦,感謝;

有錯誤的地方,歡迎在評論指出,作者看到會進行修改。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM