摘要:認識模塊作為一名編程語言,一直以來沒有模塊的概念。在之前,有主要的個模塊化方案和。這樣引入模塊和引入模塊方法差不多,其代表是。關鍵字用于規定模塊的對外接口,關鍵字用于輸入其他模塊提供的功能。
認識模塊
JS 作為一名編程語言,一直以來沒有模塊的概念。嚴重導致大型項目開發受阻,js 文件越寫越大,不方便維護。其他語言都有模塊的接口,比如 Ruby 的 require,python 的 import,C++ 天生的 #include,甚至 CSS 都有 @import。在 ES6 之前,有主要的2個模塊化方案:CommonJS 和 AMD。前者用于服務器,后者用于瀏覽器。CommonJS 這樣引入模塊:
let {stat, exists, readFile} = require("fs");
AMD 和 CommonJS 引入模塊方法差不多,其代表是 require.js。這里我們主要研究 ES6 提供的方法:
import {stat, exists, readFile} from "fs"
這個方法相比之前的方案,具有以下優點:
最大的優點就是編譯的時候完成模塊加載,稱之為"編譯時加載", 而 CommonJS 使用的是 "運行時加載"。明顯 ES6 效率更高
不再需要 UMD 模塊格式,未來服務器和瀏覽器一定都能支持這種方法
將來瀏覽器 API 可以用模塊的格式提供,不需要做成全局變量或 navigator 的屬性
不需要反復的封裝和定義命名空間,直接以模塊形式提供即可
模塊默認工作在嚴格模式,即使沒有指定"use strict", 關于嚴格模式可以看:Javascript嚴格模式特點
一個模塊就是一個文件,有效地減少了全局變量污染
export 和 import模塊功能主要由2個命令組成:export 和 import。export 關鍵字用于規定模塊的對外接口,import 關鍵字用于輸入其他模塊提供的功能。這里需要知道的是,ES6 中模塊導出的都會構成一個對象。
export 導出模塊的部分方法屬性或類
export var a = 1; export var b = 2; export var c = 3;
上面導出了3個變量,和下面的下法等價:
var a = 1; var b = 2; var c = 3; export {a, b, c}; //這種寫法更好,在文件結尾統一導出,清晰明了
當然還可以導出函數和類
//導出一個函數 add export function add(x,y){ return x + y; } //導出一個類 export default class Person{}
還可以在導出時候對參數重命名:
function foo(){} function bar(){} export {foo, bar as bar2, bar as bar3} //bar 被重命名為 bar2,bar3輸出了2次
import 導入命令可以導入其他模塊通過 export 導出的部分
// abc.js var a = 1; var b = 2; var c = 3; export {a, b, c} //main.js import {a, b, c} from "./abc"; //接受的變量用大括號表示,以解構賦值的形式獲取 console.log(a, b, c);
導入的時候也可以為變量重新取一個名字
import {a as aa, b, c}; console.log(aa, b, c)
如果想在一個模塊中先輸入后輸出同一個模塊,import語句可以和export語句寫在一起。
// 正常寫法 import {a, b, c} form "./abc"; export {a, b, c} // 使用簡寫, 可讀性不好,不建議 export {a, b, c} from "./abc"; //ES7 提議,在簡化先輸入后輸出的寫法。現在不能使用,也不建議使用,可讀性不好 export a, b, c from "./abc"
使用 import 和 export 需要注意一下幾個方面:
export 必須寫在所在模塊作用于的頂層。如果寫在了內部作用于會報錯
export 輸出的值是動態綁定的,綁定在其所在的模塊。
// foo.js export var foo = "foo"; setTimeout(function() { foo = "foo2"; }, 500); // main.js import * as m from "./foo"; console.log(m.foo); // foo setTimeout(() => console.log(m.foo), 500); //foo2 500ms 后同樣會被修改
import 具有聲明提升,而且會提升到整個文件最上面
import 獲得的變量都是只讀的,修改它們會報錯
在 export 輸出內容時,如果同時輸出多個變量,需要使用大括號{},同時 import 導入多個變量也需要大括號
import 引入模塊的默認后綴是 .js, 所以寫的時候可以忽略 js 文件擴展名
import 會執行要所加載的模塊。如下寫法僅僅執行一個模塊,不引入任何值
import "./foo"; //執行 foo.js 但不引入任何值模塊整體加載
當然模塊可以作為整體加載,使用*關鍵字,并利用 as 重命名得到一個對象,所有獲得的 export 的函數、值和類都是該對象的方法:
// abc.js export var a = 1; export var b = 2; export var c = 3; // main.js import * as abc from "./abc"; console.log(abc.a, abc.b, abc.c);
上面 main.js 中的整體加載可以用 module 關鍵字實現:
//暫時無法實現 module abc from "./abc"; console.log(abc.a, abc.b, abc.c); //1 2 3
注意,以上2種方式獲得的接口,不包括 export default 定義的默認接口。
export default為了使模塊的用戶可以不看文檔,或者少看文檔,輸出模塊的時候利用 export default 指定默認輸出的接口。使用 export defalut 輸出時,不需要大括號,而 import 輸入變量時,也不需要大括號(沒有大括號即表示獲得默認輸出)
// abc.js var a = 1, b = 2, c = 3; export {a, b}; export default c; //等價于 export default 3; // main.js import {a, b} from "./abc"; import num from "./abc"; // 不需要大括號, 而且可以直接改名(如果必須用原名不還得看手冊么?) console.log(a, b, num) // 1 2 3
本質上,export default輸出的是一個叫做default的變量或方法,輸入這個default變量時不需要大括號。
// abc.js var a = 20; export {a as default}; // main.js import a from "./abc"; // 這樣也是可以的 console.log(a); // 20 // 這樣也是可以的 import {default as aa} from "./abc"; console.log(aa); // 20
如果需要同時輸入默認方法和其他變量可以這樣寫 import:
import customNameAsDefaultExport, {otherMethod}, from "./export-default";
這里需要注意:一個模塊只能有一個默認輸出,所以 export default 只能用一次
模塊的繼承所謂模塊的繼承,就是一個模塊 B 輸出了模塊 A 全部的接口,就仿佛是 B 繼承了 A。利用 export * 實現:
// circleplus.js export * from "circle"; //當然,這里也可以選擇只繼承其部分接口,甚至可以對接口改名 export var e = 2.71828182846; export default function(x){ //重新定義了默認輸出,如果不想重新定義可以:export customNameAsDefaultExport from "circle"; return Math.exp(x); } //main.js import * from "circleplus"; //加載全部接口 import exp from "circleplus"; //加載默認接口 //...use module here
上面這個例子 circleplus 繼承了 circle。值得一提的是,export * 不會再次輸出 circle 中的默認輸出(export default)。
在使用和定義模塊時,希望可以做到以下幾個建議:
Module 語法是 JavaScript 模塊的標準寫法,堅持使用這種寫法。使用 import 取代 require, 使用 export 取代module.exports
如果模塊只有一個輸出值,就使用 export default,如果模塊有多個輸出值,就不使用 export default
盡量不要 export default 與普通的 export 同時使用
不要在模塊輸入中使用通配符。因為這樣可以確保你的模塊之中,有一個默認輸出(export default)
如果模塊默認輸出一個函數,函數名的首字母應該小寫;如果模塊默認輸出一個對象,對象名的首字母應該大寫
ES6 模塊加載的實質ES6 模塊加載的機制是值的應用,而 CommonJS 是值的拷貝。這意味著, ES6 模塊內的值的變換會影響模塊外對應的值,而 CommonJS 不會。 ES6 遇到 import 時不會立刻執行這個模塊,只生成一個動態引用,需要用的時候再去里面找值。有點像 Unix 中的符號鏈接。所以說 ES6的模塊是動態引用,不會緩存值。之前的這個例子就可以說明問題:
// foo.js export let counter = 3; export function inc(){ counter++; } // main.js import {counter, inc} from "./foo"; console.log(counter); //3 inc(); console.log(counter); //4
我們看一個 CommonJS 的情況
// foo.js let counter = 3; function inc(){ counter++; } module.exports = { counter: counter, inc: inc } // main.js let foo = require("./foo") let counter = foo.counter; let inc = foo.inc; console.log(counter); //3 inc(); console.log(counter); //3循環加載
不知道你們只不知道循環引用,在 內存管理與垃圾回收中提到過:如果 A 對象的一個屬性值是 B 對象,而 B 對象的一個屬性值是 A 對象,就會形成循環引用,無法釋放他們的內存。而模塊中也會出現循環加載的情況:如果 A 模塊的執行依賴 B 模塊,而 B 模塊的執行依賴 A 模塊,就形成了一個循環加載,結果程序不能工作,或者死機。然而,這樣的關系很難避免,因為開發者眾多,誰都會在開發自己的模塊時使用別人的幾個模塊,久而久之,就行互聯網一樣,這樣的依賴也織成了一個網。
ES6 和 CommonJS 處理循環加載又不一樣,從 CommonJS 開始研究
CommonJS
CommonJS 每次執行完一個模塊對應的 js 文件后在內存中就生成一個對象:
{ id: "...", //表示屬性的模塊名 exports: {...}; //模塊輸出的各個接口 loaded: true, //表示是否加載完畢 //...內容很多,不一一列舉了 }
之后使用這個模塊,即使在寫一遍 requrie,都不會再執行對應 js 文件了,會直接在這個對象中取值。
CommonJS 如果遇到循環加載,就輸出已執行的部分,之后的不再執行,執行順序以注釋序號為準(從0開始):
// a.js exports.done = false; //1. 先輸出 done var b = require("./b.js"); //2. 進入 b.js 執行 b.js //5. 發現 a.js 沒執行完,那就重復不執行 a.js,返回已經執行的 exports console.log(`In a.js, b.done = ${b.done}`); //10. 第2步的 b.js 執行完了,繼續執行 a.js 得到控制臺輸出:"In a.js, b.done = true" exports.done = true; //11 console.log("a.js executed"); //12. 得到控制臺輸出:"a.js executed" // b.js exports.done = false; //3. 先輸出 done var a = require("./a.js"); //4. 執行到這里發生循環加載,去 a.js 執行 a.js //6. 只得到了 a.js 中的 done 為 false console.log(`In b.js, a.done = ${a.done}`); //7. 得到控制臺輸出:"In b.js, a.done = false" exports.done = true; //8. 輸出 done, 覆蓋了第3步的輸出 console.log("b.js executed"); //9. 得到控制臺輸出:"b.js executed" //main.js var a = require("./a.js"); //0. 去 a.js 執行 a.js var b = require("./b.js"); //13. b.js 已經執行過了,直接去內存中的對象取值 console.log(`In main,a.done = ${a.done}, b.done = ${b.done}`) //得到控制臺輸出:"In main,a.done = true, b.done = true"
ES6
由于 ES6 使用的是動態引用,遇到 import 時不會執行模塊。所以和 CommonJS 有本質的區別。同樣我們看個例子:
// a.js import {bar} from "./b.js"; export function foo(){ bar(); console.log("finished") } // b.js import {foo} from "./a.js"; export function bar(){ foo(); } //main.js import * from "./a.js"; import * from "./b.js"; //...
上面這段代碼寫成 CommonJS 形式是無法執行的,應為 a 輸出到 b 的接口為空(null), 所以在 b 中調用 foo() 要報錯的。但是 ES6 可以執行,得到控制臺輸出"finished"
另一個例子是這樣的。執行順序以注釋序號為準(從0開始):
// even.js import {odd} from "./odd"; //2. 得到 odd.js 動態引用,但不執行 export var counter = 0; //3. 輸出 counter 的引用 export function even(n){ //4. 輸出 even 函數的引用 counter++; //6 return n === 0 || odd(n - 1); //7. n 不是 0, 去 odd.js 找 odd() 函數 //10. 執行 odd 函數,傳入9 } // odd.js import {even} from "./even"; //8. 得到 even.js 動態引用,但不執行 export function odd(n){ //9. 輸出 odd 函數 return n !== 0 && even(n - 1); //11. 回到第2步,找到 even 函數,回來執行,傳入8,直到 n 為 0 結束 } // main.js import * as m from "./even"; //0. 得到 even.js 動態引用,但不執行 console.log(m.even(10)); //1. 去 even.js 找 even 函數。 //5. 執行函數,傳入10 //最終得到控制臺輸出:true console.log(m.counter); //由于 ES6 模塊傳值是動態綁定的(下同),所以得到控制臺輸出:6 console.log(m.even(20)); //分析同上,得到控制臺輸出:true console.log(m.counter); //得到控制臺輸出:17
上面寫了11步,之后是一個循環,沒有繼續寫。但不難看出 ES6 根本不怕循環引用,只要模塊文件的動態引用在,就可以計算完成。不過,別看這個過程比 CommonJS 復雜,每次都有重新運行模塊文件,而不直接讀取緩存,但 ES6 的這些工作在編譯期間就完成了,比 CommonJS 在運行時間處理模塊要效率更高,體驗更好。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97437.html
摘要:是制定的的規范,代表發表的新版本,等以此類推。持續優化更新變量聲明用于聲明常量,變量建議使用命令,建議不再使用命令,聲明的是全局變量,而則為局部變量。聲明的是全局變量也是頂層對象的屬性,則沒有綁定到頂層對象。 ECMAScript6 是 ECMA 制定的 JavaScript 的規范,ES6代表2016發表的新版本,ES7、ES5等以此類推。 (持續優化更新) 變量聲明 cons...
摘要:和塊級作用域塊級作用于對于強類型語言經驗的人應該非常好理解一言以蔽之對變量作用于分隔使用了函數詞法作用域而使用花括號塊作用域。但是花括號對于沒有聲明直接定義以及用聲明的變量沒有影響這些變量依然遵守詞法作用域規則。 let、const和塊級作用域 塊級作用于對于強類型語言經驗的人應該非常好理解, 一言以蔽之:ES5對變量作用于分隔使用了函數(詞法作用域), 而ES6使用花括號(塊作用域)...
摘要:筆記和和是塊作用域的,是聲明常量用的。一個對象如果要有可被循環調用的接口,就必須在的屬性上部署遍歷器生成方法原型鏈上的對象具有該方法也可。這種方式會訪問注冊表,其中存儲了已經存在的一系列。這種方式與通過定義的獨立不同,注冊表中的是共享的。 ECMAScript6 筆記 let 和 const let和const是塊作用域的 ,const是聲明常量用的。 {let a = 10;} a ...
摘要:所以,打包工具就出現了,它可以幫助做這些繁瑣的工作。打包工具介紹僅介紹款主流的打包工具,,,,以發布時間為順序。它定位是模塊打包器,而屬于構建工具。而且在其他的打包工具在處理非網頁文件比如等基本還是需要借助它來實現。 本文當時寫在本地,發現換電腦很不是方便,在這里記錄下。 前端的打包工具 打包工具可以更好的管理html,css,javascript,使用可以錦上添花,不使用也沒關系...
摘要:就是一個用于搭建類似于網頁版知乎這種表單項繁多,且內容需要根據用戶的操作進行修改的網頁版應用。單頁應用程序顧名思義,單頁應用一般指的就是一個頁面就是應用,當然也可以是一個子應用,比如說知乎的一個頁面就可以視為一個子應用。 最近在逛各大網站,論壇,以及像SegmentFault等編程問答社區,發現Vue.js異常火爆,重復性的提問和內容也很多,樓主自己也趁著這個大前端的熱潮,著手學習了一...
閱讀 1388·2021-11-24 09:38
閱讀 2089·2021-09-22 15:17
閱讀 2385·2021-09-04 16:41
閱讀 3477·2019-08-30 15:56
閱讀 3516·2019-08-29 17:19
閱讀 1975·2019-08-28 18:09
閱讀 1253·2019-08-26 13:35
閱讀 1715·2019-08-23 17:52