摘要:如果想讓模塊再次執行,必須清楚緩存同步加載模塊只有加載完成之后,才能執行后面的操作運行時加載中的實現對象中提供了一個構造函數,每個模塊都是構造函數的實例。
什么是模塊化 1、模塊化
模塊化是自頂向下逐層將系統劃分成若干更好的可管理模塊的方式,用來分割、組織和打包軟件,達到高度解耦
2、模塊模塊是可組合、分解、更換的單元;
每個模塊完成一個特定子功能,模塊間通過某種方式組裝起來,成為一個整體
模塊間高度解耦,模塊功能單一,可高度復用
1、消除全局變量,減少命名沖突
2、更好地代碼組織結構和開發協作:通過文件拆分,更易于管理復雜代碼庫,更易于多人協作開發,降低文件合并時候沖突的發生概率,方便編寫單元測試
3、依賴管理、按需加載:不再需要手動管理腳本加載順序
4、優化:
(1)代碼打包:合并小模塊,抽取公共模塊,在資源請求數和瀏覽器緩存利用方面進行合適的取舍
(2)代碼分割:按需加載代碼(分路由、異步組件),解決單頁面應用首屏加載緩慢的問題
(3)Tree Shaking :利用ES6模塊的靜態化特性。在構建過程中分析出代碼庫中未使用的代碼,從最終的bundle中 去除,從而減少JS Bundle的大小
(4)Scope Hoisting:ES6模塊內容導入導出綁定是活動的,可以將多個小模塊合并到一個函數當中去,對于重復變量名進行核實的重命名,從而減少Bundle的尺寸和提升加載速度。
1.1語法
在 標記之間添加js代碼 ,把不同的函數等簡單放在一起,就算是一個模塊
function fn1(){....} ? function fn2(){....}
1.2不足
代碼無重用性:其他頁面需要該script標簽中一些代碼時,需要復制粘貼
全局命名空間污染:所有變量、方法等都定義在全局作用域中,也容易命名沖突
2.1語法
將js代碼分成多個片段分別放入s文件中,使用 ? ? ?
2.2不足
缺乏依賴管理:文件之間講究先后順序,互相之間存在依賴關系
全局命名空間污染:所有變量、方法等都定義在全局作用域中
一個對象就是一個模塊,所有模塊成員都在其中
3.1語法
var obj = new Object({ fn1 : function (){}, fn2 : function (){} ..... });
3.2不足
暴露了內部成員:所以內部成員都被暴露,在外不可以輕易被修改
缺乏依賴管理:一個模塊一個文件,文件順序還需要手動控制
全局命名空間污染:仍然需要暴露一個全局變量
4.1 語法
將每個文件都封裝成IIFE,內部定義的變量和方法只在IIFE作用域內生效,不會污染全局。并且通過將這些方法變量賦值給某個全局對象來公開 , 不暴露私有成員;
var module = (function(obj){ let a =1; obj.fn1=function (){} return obj })(module || {});
4.2 應用
Jquery庫,公開一個全局對象$, 它中包含所以方法與屬性
4.3 不足
缺乏依賴管理:文件順序還需要手動控制,例如使用jQuery的方法前,必須保證jQuery已經加載完
全局命名空間污染:仍然需要暴露一個全局變量
5、模塊化規范的出現
(1) js引入服務器端后,出現的 CommonJS規范
(2)CommonJS的同步性限制了前端的使用,出現了 AMD
(3)UMD規范的統一
(4)ES6模塊的定義
CommonJS是除瀏覽器之外 構建js生態系統為目標而產生的規范,比如服務器和桌面環境等。最早 由Mozilla的工程師Kevin Dangoor在2009年1月創建。
2013年5月,Node.js 的包管理器 NPM 的作者 Isaac Z. Schlueter 說 CommonJS 已經過時,Node.js 的內核開發者已經廢棄了該規范。
1、定義每個文件是一個模塊,有自己的作用域。在一個文件里定義的變量、函數等都是私有的,對其他文件不可見。
在每個模塊內部,module變量代表當前模塊,它的exports屬性是對外的接口,加載某個模塊(require)時,其實加載的是該模塊的 exports屬性
var x = 5; var addX = function (value) { return value + x; }; module.exports.x = x; module.exports.addX = addX;2、語法
CommonJS包含主要包含三部分:模塊導入(加載),模塊定義、模塊標識
2.1 模塊導入:require() ——返回該模塊的exports屬性
var module1 = require("./module1.js");
2.2 模塊定義 :module.exports
//module1.js module.exports.fn1 = function (){}
2.3 模塊標識:require()方法的參數
必須是字符串
可以是以./ ../開頭的相對路徑
可以是絕對路徑
可以省略后綴名
自動依賴管理:模塊加載的順序 依賴于其在代碼中出現的順序
不污染全局作用域:模塊內部代碼運行在自己的私有作用域
可多次加載,但只執行一次:模塊可以多次加載,但是只會在第一次加載時運行一次,然后運行結果被緩存,以后再加載,直接讀取緩存結果。如果想讓模塊再次執行,必須清楚緩存
同步加載模塊:只有加載完成之后,才能執行后面的操作
運行時加載
4.1 module對象
node中提供了一個Module構造函數,每個模塊都是構造函數的實例。每個模塊內部,都要一個module對象,代表當前模塊
//Module構造函數 function Module(id,parent){ this.id=id;//模塊的標識符,通常為帶有絕對路徑的模塊文件名 this.exports ={};//模塊暴露出去的方法或者變量 this.parent=parent;//返回一個對象,父級模塊,調用該模塊的模塊 if(parent && parent.children){ parent.children.push(this); } ? this.filename =null;//模塊文件名,帶有絕對路徑 this.loaded=false;//返回一個布爾值,該模塊是否加載完成(因為是運行時加載,所以代表是否已經執行完畢) this.chilren =[];//返回數組,該模塊要用到的其他模塊 } ? //實例化一個模塊 var module1 =new Module(filename,parent)
4.2 module.exports屬性
module.exports屬性表示當前模塊對外輸出的接口,其他文件加載該模塊,實際上就是讀取module.exports變量。
4.3 exports變量
node為每個模塊提供了exoprts變量,指向module.exports。等同于在每個模塊頭部,有一行代碼
var exports = module.exports;
在對外輸出時,可以向exports對象添加方法
exports.fn1 =function(){}
不能直接將exports指向一個值,這樣會切斷exports與module.exports的聯系
exports = function(x) {console.log(x)};
如果一個模塊的module.exports是一個單一的值,不能使用exports輸出,只能使用module.exports輸出
//hello函數是無法對外輸出的,因為module.exports被重新賦值了。 exports.hello = function() { return "hello"; }; ? module.exports = "Hello world";
?
4.4 node中的模塊分類
node中模塊分為兩類:一類為mode提供的核心模塊,另一類為 用戶編寫的文件模塊
4.4.1 核心模塊
即node提供的內置模塊如 http模塊、url模塊、fs模塊等
核心模塊在node源代碼的編譯過程中被編譯進了二進制文件,在node進程啟動的時候,會被直接加載進內存,因此引用這些模塊的時候,文件定位和編譯執行這兩步會被省略。
在路徑分析中會優先判斷核心模塊,加載速度最快。
4.4.2 文件模塊
即外部引入的模塊 如node_modules中的模塊,項目中自己編寫的js文件等
在運行時動態加載,需要完整的路徑分析,文件定位,編譯執行這三部,加載速度比核心模塊慢
4.5 路徑分析、文件定位、編譯執行
4.5.1路徑分析
不論核心模塊還是文件模塊都需要經歷路徑分析這一步,Node支持如下幾種形式的模塊標識符,來引入模塊:
//核心模塊 require("http") ---------------------------- //文件模塊 ? //以.開頭的相對路徑,(可以不帶擴展名) require("./a.js") //以..開頭的相對路徑,(可以不帶擴展名) require("../b.js") ? //以/開始的絕對路徑,(可以不帶擴展名) require("/c.js") ? //外部模塊名稱 require("express") ? //外部模塊某一個文件 require("codemirror/addon/merge/merge.js");
● Node 會優先去內存中查找匹配核心模塊,如果匹配成功便不會再繼續查找
(1)比如require http 模塊的時候,會優先從核心模塊里去成功匹配
● 如果核心模塊沒有匹配成功,便歸類為文件模塊
(2) 以.、..和/開頭的標識符,require都會根據當前文件路徑將這個相對路徑或者絕對路徑轉化為真實路徑,也就是我們平時最常見的一種路徑解析
(3)非路徑形式的文件模塊 如上面的"express" 和"codemirror/addon/merge/merge.js",這種模塊是一種特殊的文件模塊,一般稱為自定義模塊。
4.5.1.1 模塊路徑
自定義模塊的查找最費時,因為對于自定義模塊有一個模塊路徑,Node會根據這個模塊路徑依次遞歸查找。
模塊路徑——Node的模塊路徑是一個數組,模塊路徑存放在module.paths屬性上。
我們可以找一個基于npm或者yarn管理項目,在根目錄下創建一個test.js文件,內容為console.log(module.paths),如下:
//test.js
console.log(module.paths);
然后在根目錄下用Node執行
node test.js
可以看到我們已經將模塊路徑打印出來。
可以看到模塊路徑的生成規則如下:
● 當前路文件下的node_modules目錄
● 父目錄下的node_modules目錄
● 父目錄的父目錄下的node_modules目錄
● 沿路徑向上逐級遞歸,直到根目錄下的node_modules目錄
對于自定義文件比如express,就會根據模塊路徑依次遞歸查找。
在查找同時并進行文件定位。
4.5.2文件定位
● 擴展名分析
我們在使用require的時候有時候會省略擴展名,那么Node怎么定位到具體的文件呢?
這種情況下,Node會依次按照.js、.json、.node的次序一次匹配。(.node是C++擴展文件編譯之后生成的文件)
若擴展名匹配失敗,則會將其當成一個包來處理,我這里直接理解為npm包
● 包處理
對于包Node會首先在當前包目錄下查找package.json(CommonJS包規范)通過JSON.parse( )解析出包描述對象,根據main屬性指定的入口文件名進行下一步定位。
如果文件缺少擴展名,將根據擴展名分析規則定位。
若main指定文件名錯誤或者壓根沒有package.json,Node會將包目錄下的index當做默認文件名。
再依次匹配index.js、index.json、index.node。
若以上步驟都沒有定位成功將,進入下一個模塊路徑——父目錄下的node_modules目錄下查找,直到查找到根目錄下的node_modules,若都沒有定位到,將拋出查找失敗的異常。
4.5.3模塊編譯
● .js文件——通過fs模塊同步讀取文件后編譯執行
● .node文件——用C/C++編寫的擴展文件,通過dlopen( )方法加載最后編譯生成的文件。
● .json——通過fs模塊同步讀取文件后,用JSON.parse( ) 解析返回結果。
● 其余擴展名文件。它們都是被當做.js文件載入。
每一個編譯成功的文件都會將其文件路徑作為索引緩存在Module._cache對象上,以提高二次引入的性能。
這里我們只講解一下JavaScript模塊的編譯過程,以解答前面所說的CommonJS模塊中的require、exports、module變量的來源。
我們還知道Node的每個模塊中都有filename、dirname 這兩個變量,是怎么來的的呢?
其實JavaScript模塊在編譯過程中,整個所要加載的腳本內容,被放到一個新的函數之中,這樣可以避免污染全局環境。該函數的參數包括require、module、exports,以及其他一些參數。
Node對獲取的JavaScript文件內容進行了頭部和尾部的包裝。在頭部添加了(function (exports, require, module,filename, dirname){n,而在尾部添加了n}); 。
因此一個JS模塊經過編譯之后會被包裝成下面的樣子:
(function(exports, require, module, __filename, __dirname){ var express = require("express") ; exports.method = function (params){ ... }; });
4.6 模塊加載機制
整體加載執行,導入的是被輸出的值得拷貝,即 一旦輸出一個值,模塊內部的變化就影響不到這個值
// lib.js var counter = 3; function incCounter() { counter++; } module.exports = { counter: counter, incCounter: incCounter, }; ? ? ? ? // main.js var counter = require("./lib").counter; var incCounter = require("./lib").incCounter; ? console.log(counter); // 3 incCounter(); console.log(counter); // 3 //counter輸出以后,lib.js模塊內部的變化就影響不到counter了。
4.7 require 的內部處理流程
require不是一個全局命令,而是指向當前模塊的module.require命令,module.require又調用node內部命令Module._load
require —>module.require——>Module._load
MOdule._load =function(require,parent,isMain){ 1.檢查緩存Module._cache ,是否有指定模塊 2.如果緩存中沒有,就創建一個新的MOdule實例 3.將實例保存到緩存 4.使用Module,load()加載指定的模塊文件 5.讀取文件內容后,使用module.compile()執行文件代碼 6.如果加載/解析過程報錯,就從緩存中刪除該模塊 7.返回該模塊的module.exports }
Module.compile方法是同步執行的,所有Module.load 要等它執行完成,才會向用戶返回 module.exports的值
AMD 與 requireJs由于node主要用戶服務端編程,模塊文件一般都已經存在于本地硬盤,所以加載起來比較快,不用考慮非同步加載的方式,因此CommonJS規范比較適用。但是如果是瀏覽器環境,要從服務器端加載資源,這時就必須采用非同步模式。
1、模塊定義define(id? dependencies?,factory)
id為string類型,表示模塊標識
dependencies:為Array類型,表示需要依賴的模塊
factory:為function或者Object,表示要進行的回調
1.1 獨立模塊(不需要依賴模塊)
define({ fn1:function(){} }) ? define(function(){ return { fn1:function(){}, } })
1.2 非獨立模塊(有依賴其他模塊)
define(["module1","module2"],function(){}) // 依賴必須一開始就寫好2、模塊導入
require(["a","b"],function(a,b){})
3、特點依賴管理:被依賴的文件早于主邏輯被加載執行 ;
運行時加載;
異步加載模塊:在模塊的加載過程中即使require的模塊還沒有獲取到,也不會影響后面代碼的執行,不會阻塞頁面渲染
AMD規范是RequireJS在推廣過程中對模塊定義的規范化產出
CMD 與 seajs 1、模塊定義在依賴示例部分,CMD支持動態引入,require、exports和module通過形參傳遞給模塊,在需要依賴模塊時,隨時調用require( )引入即可,示例如下:
define(factory)
1.1 factory 三個參數
function(require,exports,module)
require用于導入其他模塊接口
exports 用于導出接口
module存儲了與當前模塊相關聯的一些屬性與方法
1.2 例子
define(function(require ,exports,module) { //調用依賴模塊increment的increment方法 var inc = require("increment").increment; // 依賴可以就近書寫 var a = 1; inc(a); module.id == "program"; });2、模塊導入
require("路徑")3、特點
依賴就近書寫:一般不再define的參數中寫依賴,就近書寫
延遲執行
兼容CommonJS、AMD 、CMD、全局引用
寫法:
(function(global,factory){ typeof exports === "object"&& typeof module!=="undefined" ?module.exports =factory() //CommonJS :typeof define ==="fucntion" && define.amd ?define(factory) //AMD CMD :(global.returnExports = factory()) //掛載到全局 }(this,function(){ //////暴露的方法 return fn1 }))es6 module 1、模塊導出 export
export 輸入變量、函數、類等 與模塊內部的變量建立一一對應關系
//寫法一 export var a=1; //寫法二 var a=1; export {a} //寫法三 as進行重命名 var b=1; export {b as a} //寫法四 var a=1 export default a
export語句輸出的接口,與其對應的值是動態綁定關系,即通過該接口,可以取到模塊內部實時的值。
export var foo = "bar"; setTimeout(() => foo = "baz", 500);
上面代碼輸出變量foo,值為bar,500 毫秒之后變成baz。
2、模塊輸入 import2.1 寫法一
import命令接受一對大括號,里面指定要加載指定模塊,并從中輸入變量
import {firstName, lastName, year} from "./profile.js"; import { lastName as surname } from "./profile.js";
大括號里面的變量名,必須與被導入模塊(profile.js)對外接口的名稱相同。
可以使用as關鍵字,將輸入的變量重命名。
2.2 寫法二
import 后面寫模塊路徑--------執行所加載的模塊,但不輸入任何值
import "lodash";
上面代碼僅僅執行lodash模塊,但是不輸入任何值。
如果多次重復執行同一句import語句,那么只會執行一次,而不會執行多次。
2.3 寫法三
用星號(*)指定一個對象,整體加載所有對象到這個對象上 ——整體模塊加載
import * as circle from "./circle";
2.4 export default 與 import
export default 實際導出的為一個叫做 default 的變量,所以其后面不能跟變量聲明語句
使用export default命令時,import是不需要加{}的
不使用export default時,import是必須加{}
//person.js export function getName() { ... } //my_module import {getName} from "./person.js"; ? ? ? //person.js export default function getName(){ ... } //my_module import getName from "./person.js"; ? ? //person.js export name = "dingman"; export default function getName(){ ... } ? //my_module import getName, { name } from "./person.js";3、特點
編譯時加載:編譯的時候就可以確定模塊的依賴關系,已經輸入與輸出的變量
各規范總結 1、 CommonJS環境:服務器環境
特點:(1)同步加載;(2)運行時加載 (3)多次加載,只第一次執行,以后直接讀取緩存
應用: Nodejs
語法:
導入 : require() 導出:module.exports 或者 exports2、AMD
環境:瀏覽器
特點:(1)異步加載 (2)運行時加載(3)管理依賴,依賴前置書寫 (4)依賴提前執行(加載完立即執行)
應用:RequireJS
語法:
導入:require(["依賴模塊"],fucntion(依賴模塊變量引用){回調函數}) 導出(定義):define(id?def?factory(){return ///})3、CMD
環境:瀏覽器
特點:(1)異步加載 (2)運行時加載 (3)管理依賴,依賴就近書寫(4)依賴延遲執行 (require的時候才執行)
應用:SeaJS
語法:
導入:require() 導出: define(function(require,exports,module){})4、UMD
環境:瀏覽器或服務器
特點:(1)兼容CommonJS AMD UMD 全局應用
語法:無導入導出,只是一種兼容寫法
環境:瀏覽器或服務器
特點:(1)編譯時加載(2)按需加載 (3)動態更新
應用:es6最新語法
語法:
導入 :import 導出:export、 export default各規范的區別提煉 1、CommonJS與ES6
1.1 是否動態更新
es6 :輸出的值是動態綁定,會實時動態更新。
CommonJS :輸出的是值的緩存,不存在動態更新
1.2 加載時機
//ES6模塊 import { basename, dirname, parse } from "path"; ? //CommonJS模塊 let { basename, dirname, parse } = require("path");
es6 :
編譯時加載,ES6可以在編譯時就完成模塊加載;
按需加載,ES6會從path模塊只加載3個方法,其他不會加載。
動態引用,實時更新,當ES6遇到import時,不會像CommonJS一樣去執行模塊,而是生成一個動態的只讀引用,當真正需要的時候再到模塊里去取值,所以ES6模塊是動態引用,并且不會緩存值。
CommonJS:
運行時加載:
加載整個對象:require path模塊時,其實 CommonJS會將path模塊運行一遍,并返回一個對象,并將這個對象緩存起來,這個對象包含path這個模塊的所有API。
使用緩存值,不會實時更新:以后無論多少次加載這個模塊都是取第一次運行的結果,除非手動清除。因為CommonJS模塊輸出的是值的拷貝,所以當模塊內值變化時,不會影響到輸出的值
2.1 cmd依賴就近,AMD依賴前置
// CMD define(function(require, exports, module) { var a = require("./a") a.doSomething() var b = require("./b") // 依賴可以就近書寫 b.doSomething() // ... }) // AMD define(["./a", "./b"], function(a, b) {// 依賴必須一開始就寫好 a.doSomething() b.doSomething() ... })
2.2 CMD延遲執行,AMD提前執行
AMD
在加載模塊完成后就立即執行該模塊,
當所有模塊都加載執行完成后 才會進入require的回調函數,執行主邏輯
(會出現 那個依賴模塊先下載完,哪個就先執行,與執行順序書寫順序不一致)
AMD RequireJS 從 2.0 開始,也改成可以延遲執行(根據寫法不同,處理方式不同)
CMD
加載完某個依賴模塊后 并不執行, 當所有依賴模塊加載完成后進入主邏輯,遇到require語句時才**執行對應依賴模塊**。 (保證了**執行順序與書寫順序的一致**)
參考:
http://es6.ruanyifeng.com/#do...
https://zhuanlan.zhihu.com/p/...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97413.html
摘要:我是這一期的主持人黃子毅本期精讀的文章是。模塊化需要保證全局變量盡量干凈,目前為止的模塊化方案都沒有很好的做到這一點。精讀本次提出獨到觀點的同學有流形,黃子毅,蘇里約,,楊森,淡蒼,留影,精讀由此歸納。 這次是前端精讀期刊與大家第一次正式碰面,我們每周會精讀并分析若干篇精品好文,試圖討論出結論性觀點。沒錯,我們試圖通過觀點的碰撞,爭做無主觀精品好文的意見領袖。 我是這一期的主持人 ——...
摘要:前端工程化的演化。前端較為流行的單元測試,等自動化測試自動化測試是軟件通過模擬瀏覽器,對頁面進行操作,判斷是否產生預想的效果。 前端工程化 ??前端工程化的概念在近些年來逐漸成為主流構建大型web應用不可或缺的一部分,在此我通過以下這三方面總結一下自己的理解。 為什么需要前端工程化。 前端工程化的演化。 怎么實現前端工程化。 為什么需要工程化 ??隨著近些年來前端技術的不斷發展,越...
摘要:二模塊化誤區加快加載和執行的速度,一直是前端優化的一個熱點。結果文件減少,也達到了預期的效果。避免不必要的延遲。最后再根據文件的功能類型,來決定是放在頁面的頭部還是尾部。 注:本文是純技術探討文,無圖無笑點,希望您喜歡 一.前言 軟件行業極其缺乏前端人才這是圈內的共識了,某種程度上講,同等水平前端的工資都要比后端高上不少,而圈內的另一項共識則是——網頁是公司的臉面! 幾年前,谷歌的一項...
摘要:特意對前端學習資源做一個匯總,方便自己學習查閱參考,和好友們共同進步。 特意對前端學習資源做一個匯總,方便自己學習查閱參考,和好友們共同進步。 本以為自己收藏的站點多,可以很快搞定,沒想到一入匯總深似海。還有很多不足&遺漏的地方,歡迎補充。有錯誤的地方,還請斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應和斧正,會及時更新,平時業務工作時也會不定期更...
閱讀 2169·2023-04-25 15:00
閱讀 2343·2021-11-18 13:14
閱讀 1154·2021-11-15 11:37
閱讀 3083·2021-09-24 13:55
閱讀 1221·2019-08-30 15:52
閱讀 2644·2019-08-29 12:35
閱讀 3359·2019-08-29 11:04
閱讀 1209·2019-08-26 12:13