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];
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];
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];
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就解決上述問題:
-
實現js檔案的非同步載入,避免網頁失去響應;
-
管理模組之間的依賴性,便於程式碼的編寫和維護。
require.js的AMD格式規範:
// main.js
define(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
// some code here
});
define()函式接受的引數。
-
第一個引數是一個數組,表示所依賴的模組,上例就是['moduleA', 'moduleB', 'moduleC'],即主模組依賴這三個模組;
-
第二個引數是一個回撥函式,當前面指定的模組都載入成功後,它將被呼叫。載入的模組會以引數形式傳入該函式,從而在回撥函式內部就可以使用這些模組。
-
模組必須採用特定的define()函式來定義。如果一個模組不依賴其他模組,那麼可以直接定義在define()函式之中
-
如果這個模組還依賴其他模組,那麼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];
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;
})
分析上述程式碼的執行流程:
- 先載入index.html以及require.js檔案,找到模組的主入口(main.js)
- 載入main.js,由於main中依賴sum.js
- 再載入sum.js,sum中依賴add.js以及reduce.js
- 再載入add.js以及reduce.js
- 最後全部依賴執行完成後,回撥得到的結果
執行效果以及檔案載入順序的觀察:
模組演變5:
基於commonJS規範以及browserify瀏覽器端的模組演變
什麼是browserify:
browserify是專注於解決按照CommonJS規範書寫的模組能夠在瀏覽器中使用問題的構建工具。
CommonJS規範:
- 每個檔案就是一個模組,有自己的作用域。在一個檔案裡面定義的變數、函式、類,都是私有的,對其他檔案不可見
- 每個模組內部,
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];
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中
效果圖:
執行檢視載入資訊:
可以與require.js的載入方式做對比,理解兩者的不同以及相同的地方。
擴充套件:
require.js VS browserify
- require.js是模組載入器;browserify是預編譯工具
- require.js遵循的是AMD規範;browserify遵循的是CommonJS規範
- require.js是在瀏覽器端執行期分析依賴;browserify在伺服器端編譯期就進行了依賴分析,通過每個模組的外部介面獲取資訊
結束:
如果你看到這裡或者正好對你有所幫助,希望能點個關注或者推薦,感謝;
有錯誤的地方,歡迎在評論指出,作者看到會進行修改。