摘要:來(lái)源編程精解中文第三版翻譯項(xiàng)目原文譯者飛龍協(xié)議自豪地采用谷歌翻譯編寫(xiě)易于刪除,而不是易于擴(kuò)展的代碼。模塊之間的關(guān)系稱為依賴關(guān)系。用于連接模塊的最廣泛的方法稱為模塊。模塊的主要概念是稱為的函數(shù)。
來(lái)源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Modules
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
自豪地采用谷歌翻譯
編寫(xiě)易于刪除,而不是易于擴(kuò)展的代碼。
Tef,《Programming is Terrible》
理想的程序擁有清晰的結(jié)構(gòu)。 它的工作方式很容易解釋,每個(gè)部分都起到明確的作用。
典型的真實(shí)程序會(huì)有機(jī)地增長(zhǎng)。 新功能隨著新需求的出現(xiàn)而增加。 構(gòu)建和維護(hù)結(jié)構(gòu)是額外的工作,只有在下一次有人參與該計(jì)劃時(shí),才會(huì)得到回報(bào)。 所以它易于忽視,并讓程序的各個(gè)部分變得深深地糾纏在一起。
這導(dǎo)致了兩個(gè)實(shí)際問(wèn)題。 首先,這樣的系統(tǒng)難以理解。 如果一切都可以接觸到一切其它東西,那么很難多帶帶觀察任何給定的片段。 你不得不全面理解整個(gè)東西。 其次,如果你想在另一個(gè)場(chǎng)景中,使用這種程序的任何功能,比起試圖從它的上下文中將它分離出來(lái),重寫(xiě)它可能要容易。
術(shù)語(yǔ)“大泥球”通常用于這種大型,無(wú)結(jié)構(gòu)的程序。 一切都粘在一起,當(dāng)你試圖挑選出一段代碼時(shí),整個(gè)東西就會(huì)分崩離析,你的手會(huì)變臟。
模塊模塊試圖避免這些問(wèn)題。 模塊是一個(gè)程序片段,規(guī)定了它依賴的其他部分,以及它為其他模塊提供的功能(它的接口)。
模塊接口與對(duì)象接口有許多共同之處,我們?cè)诘?6 章中看到。它們向外部世界提供模塊的一部分,并使其余部分保持私有。 通過(guò)限制模塊彼此交互的方式,系統(tǒng)變得更像積木,其中的組件通過(guò)明確定義的連接器進(jìn)行交互,而不像泥漿一樣,一切都混在一起。
模塊之間的關(guān)系稱為依賴關(guān)系。 當(dāng)一個(gè)模塊需要另一個(gè)模塊的片段時(shí),就說(shuō)它依賴于這個(gè)模塊。 當(dāng)模塊中明確規(guī)定了這個(gè)事實(shí)時(shí),它可以用于確定,需要哪些其他模塊才能使用給定的模塊,并自動(dòng)加載依賴關(guān)系。
為了以這種方式分離模塊,每個(gè)模塊需要它自己的私有作用域。
將你的 JavaScript 代碼放入不同的文件,不能滿足這些要求。 這些文件仍然共享相同的全局命名空間。 他們可以有意或無(wú)意干擾彼此的綁定。 依賴性結(jié)構(gòu)仍不清楚。 我們將在本章后面看到,我們可以做得更好。
合適的模塊結(jié)構(gòu)可能難以為程序設(shè)計(jì)。 在你還在探索這個(gè)問(wèn)題的階段,嘗試不同的事情來(lái)看看什么是可行的,你可能不想過(guò)多擔(dān)心它,因?yàn)檫@可能讓你分心。 一旦你有一些感覺(jué)可靠的東西,現(xiàn)在是后退一步并組織它的好時(shí)機(jī)。
包從多帶帶的片段中構(gòu)建一個(gè)程序,并實(shí)際上能夠獨(dú)立運(yùn)行這些片段的一個(gè)優(yōu)點(diǎn)是,你可能能夠在不同的程序中應(yīng)用相同的部分。
但如何實(shí)現(xiàn)呢? 假設(shè)我想在另一個(gè)程序中使用第 9 章中的parseINI函數(shù)。 如果清楚該函數(shù)依賴什么(在這種情況下什么都沒(méi)有),我可以將所有必要的代碼復(fù)制到我的新項(xiàng)目中并使用它。 但是,如果我在代碼中發(fā)現(xiàn)錯(cuò)誤,我可能會(huì)在當(dāng)時(shí)正在使用的任何程序中將其修復(fù),并忘記在其他程序中修復(fù)它。
一旦你開(kāi)始復(fù)制代碼,你很快就會(huì)發(fā)現(xiàn),自己在浪費(fèi)時(shí)間和精力來(lái)到處復(fù)制并使他們保持最新。
這就是包的登場(chǎng)時(shí)機(jī)。包是可分發(fā)(復(fù)制和安裝)的一大塊代碼。 它可能包含一個(gè)或多個(gè)模塊,并且具有關(guān)于它依賴于哪些其他包的信息。 一個(gè)包通常還附帶說(shuō)明它做什么的文檔,以便那些不編寫(xiě)它的人仍然可以使用它。
在包中發(fā)現(xiàn)問(wèn)題或添加新功能時(shí),會(huì)將包更新。 現(xiàn)在依賴它的程序(也可能是包)可以升級(jí)到新版本。
以這種方式工作需要基礎(chǔ)設(shè)施。 我們需要一個(gè)地方來(lái)存儲(chǔ)和查找包,以及一個(gè)便利方式來(lái)安裝和升級(jí)它們。 在 JavaScript 世界中,這個(gè)基礎(chǔ)結(jié)構(gòu)由 NPM 提供。
NPM 是兩個(gè)東西:可下載(和上傳)包的在線服務(wù),以及可幫助你安裝和管理它們的程序(與 Node.js 捆綁在一起)。
在撰寫(xiě)本文時(shí),NPM 上有超過(guò) 50 萬(wàn)個(gè)不同的包。 其中很大一部分是垃圾,我應(yīng)該提一下,但幾乎所有有用的公開(kāi)包都可以在那里找到。 例如,一個(gè) INI 文件解析器,類似于我們?cè)诘?9 章中構(gòu)建的那個(gè),可以在包名稱ini下找到。
第 20 章將介紹如何使用npm命令行程序在局部安裝這些包。
使優(yōu)質(zhì)的包可供下載是非常有價(jià)值的。 這意味著我們通常可以避免重新創(chuàng)建一百人之前寫(xiě)過(guò)的程序,并在按下幾個(gè)鍵時(shí)得到一個(gè)可靠,充分測(cè)試的實(shí)現(xiàn)。
軟件的復(fù)制很便宜,所以一旦有人編寫(xiě)它,分發(fā)給其他人是一個(gè)高效的過(guò)程。但首先把它寫(xiě)出來(lái)是工作量,回應(yīng)在代碼中發(fā)現(xiàn)問(wèn)題的人,或者想要提出新功能的人,是更大的工作量。
默認(rèn)情況下,你擁有你編寫(xiě)的代碼的版權(quán),其他人只有經(jīng)過(guò)你的許可才能使用它。但是因?yàn)橛行┤瞬诲e(cuò),而且由于發(fā)布好的軟件可以使你在程序員中出名,所以許多包都會(huì)在許可證下發(fā)布,明確允許其他人使用它。
NPM 上的大多數(shù)代碼都以這種方式授權(quán)。某些許可證要求你還要在相同許可證下發(fā)布基于那個(gè)包構(gòu)建的代碼。其他要求不高,只是要求在分發(fā)代碼時(shí)保留許可證。 JavaScript 社區(qū)主要使用后一種許可證。使用其他人的包時(shí),請(qǐng)確保你留意了他們的許可證。
即興的模塊2015 年之前,JavaScript 語(yǔ)言沒(méi)有內(nèi)置的模塊系統(tǒng)。 然而,盡管人們已經(jīng)用 JavaScript 構(gòu)建了十多年的大型系統(tǒng),他們需要模塊。
所以他們?cè)谡Z(yǔ)言之上設(shè)計(jì)了自己的模塊系統(tǒng)。 你可以使用 JavaScript 函數(shù)創(chuàng)建局部作用域,并使用對(duì)象來(lái)表示模塊接口。
這是一個(gè)模塊,用于日期名稱和數(shù)字之間的轉(zhuǎn)換(由Date的getDay方法返回)。 它的接口由weekDay.name和weekDay.number組成,它將局部綁定名稱隱藏在立即調(diào)用的函數(shù)表達(dá)式的作用域內(nèi)。
const weekDay = function() { const names = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; return { name(number) { return names[number]; }, number(name) { return names.indexOf(name); } }; }(); console.log(weekDay.name(weekDay.number("Sunday"))); // → Sunday
這種風(fēng)格的模塊在一定程度上提供了隔離,但它不聲明依賴關(guān)系。 相反,它只是將其接口放入全局范圍,并希望它的依賴關(guān)系(如果有的話)也這樣做。 很長(zhǎng)時(shí)間以來(lái),這是 Web 編程中使用的主要方法,但現(xiàn)在它幾乎已經(jīng)過(guò)時(shí)。
如果我們想讓依賴關(guān)系成為代碼的一部分,我們必須控制依賴關(guān)系的加載。 實(shí)現(xiàn)它需要能夠?qū)⒆址畧?zhí)行為代碼。 JavaScript 可以做到這一點(diǎn)。
將數(shù)據(jù)執(zhí)行為代碼有幾種方法可以將數(shù)據(jù)(代碼的字符串)作為當(dāng)前程序的一部分運(yùn)行。
最明顯的方法是特殊運(yùn)算符eval,它將在當(dāng)前作用域內(nèi)執(zhí)行一個(gè)字符串。 這通常是一個(gè)壞主意,因?yàn)樗茐牧俗饔糜蛲ǔ碛械囊恍傩裕热缫子陬A(yù)測(cè)給定名稱所引用的綁定。
const x = 1; function evalAndReturnX(code) { eval(code); return x; } console.log(evalAndReturnX("var x = 2")); // → 2 console.log(x); // → 1
將數(shù)據(jù)解釋為代碼的不太可怕的方法,是使用Function構(gòu)造器。 它有兩個(gè)參數(shù):一個(gè)包含逗號(hào)分隔的參數(shù)名稱列表的字符串,和一個(gè)包含函數(shù)體的字符串。 它將代碼封裝在一個(gè)函數(shù)值中,以便它獲得自己的作用域,并且不會(huì)對(duì)其他作用域做出奇怪的事情。
let plusOne = Function("n", "return n + 1;"); console.log(plusOne(4)); // → 5
這正是我們需要的模塊系統(tǒng)。 我們可以將模塊的代碼包裝在一個(gè)函數(shù)中,并將該函數(shù)的作用域用作模塊作用域。
CommonJS用于連接 JavaScript 模塊的最廣泛的方法稱為 CommonJS 模塊。 Node.js 使用它,并且是 NPM 上大多數(shù)包使用的系統(tǒng)。
CommonJS 模塊的主要概念是稱為require的函數(shù)。 當(dāng)你使用依賴項(xiàng)的模塊名稱調(diào)用這個(gè)函數(shù)時(shí),它會(huì)確保該模塊已加載并返回其接口。
由于加載器將模塊代碼封裝在一個(gè)函數(shù)中,模塊自動(dòng)得到它們自己的局部作用域。 他們所要做的就是,調(diào)用require來(lái)訪問(wèn)它們的依賴關(guān)系,并將它們的接口放在綁定到exports的對(duì)象中。
此示例模塊提供了日期格式化功能。 它使用 NPM的兩個(gè)包,ordinal用于將數(shù)字轉(zhuǎn)換為字符串,如"1st"和"2nd",以及date-names用于獲取星期和月份的英文名稱。 它導(dǎo)出函數(shù)formatDate,它接受一個(gè)Date對(duì)象和一個(gè)模板字符串。
模板字符串可包含指明格式的代碼,如YYYY用于全年,Do用于每月的序數(shù)日。 你可以給它一個(gè)像"MMMM Do YYYY"這樣的字符串,來(lái)獲得像"November 22nd 2017"這樣的輸出。
const ordinal = require("ordinal"); const {days, months} = require("date-names"); exports.formatDate = function(date, format) { return format.replace(/YYYY|M(MMM)?|Do?|ffffdd/g, tag => { if (tag == "YYYY") return date.getFullYear(); if (tag == "M") return date.getMonth(); if (tag == "MMMM") return months[date.getMonth()]; if (tag == "D") return date.getDate(); if (tag == "Do") return ordinal(date.getDate()); if (tag == "ffffdd") return days[date.getDay()]; }); };
ordinal的接口是單個(gè)函數(shù),而date-names導(dǎo)出包含多個(gè)東西的對(duì)象 - days和months是名稱數(shù)組。 為導(dǎo)入的接口創(chuàng)建綁定時(shí),解構(gòu)是非常方便的。
該模塊將其接口函數(shù)添加到exports,以便依賴它的模塊可以訪問(wèn)它。 我們可以像這樣使用模塊:
const {formatDate} = require("./format-date"); console.log(formatDate(new Date(2017, 9, 13), "ffffdd the Do")); // → Friday the 13th
我們可以用最簡(jiǎn)單的形式定義require,如下所示:
require.cache = Object.create(null); function require(name) { if (!(name in require.cache)) { let code = readFile(name); let module = {exports: {}}; require.cache[name] = module; let wrapper = Function("require, exports, module", code); wrapper(require, module.exports, module); } return require.cache[name].exports; }
在這段代碼中,readFile是一個(gè)構(gòu)造函數(shù),它讀取一個(gè)文件并將其內(nèi)容作為字符串返回。標(biāo)準(zhǔn)的 JavaScript 沒(méi)有提供這樣的功能,但是不同的 JavaScript 環(huán)境(如瀏覽器和 Node.js)提供了自己的訪問(wèn)文件的方式。這個(gè)例子只是假設(shè)readFile存在。
為了避免多次加載相同的模塊,require需要保存(緩存)已經(jīng)加載的模塊。被調(diào)用時(shí),它首先檢查所請(qǐng)求的模塊是否已加載,如果沒(méi)有,則加載它。這涉及到讀取模塊的代碼,將其包裝在一個(gè)函數(shù)中,然后調(diào)用它。
我們之前看到的ordinal包的接口不是一個(gè)對(duì)象,而是一個(gè)函數(shù)。 CommonJS 模塊的特點(diǎn)是,盡管模塊系統(tǒng)會(huì)為你創(chuàng)建一個(gè)空的接口對(duì)象(綁定到exports),但你可以通過(guò)覆蓋module.exports來(lái)替換它。許多模塊都這么做,以便導(dǎo)出單個(gè)值而不是接口對(duì)象。
通過(guò)將require,exports和module定義為生成的包裝函數(shù)的參數(shù)(并在調(diào)用它時(shí)傳遞適當(dāng)?shù)闹担?,加載器確保這些綁定在模塊的作用域中可用。
提供給require的字符串翻譯為實(shí)際的文件名或網(wǎng)址的方式,在不同系統(tǒng)有所不同。 當(dāng)它以"./"或"../"開(kāi)頭時(shí),它通常被解釋為相對(duì)于當(dāng)前模塊的文件名。 所以"./format-date"就是在同一個(gè)目錄中,名為format-date.js的文件。
當(dāng)名稱不是相對(duì)的時(shí),Node.js 將按照該名稱查找已安裝的包。 在本章的示例代碼中,我們將把這些名稱解釋為 NPM 包的引用。 我們將在第 20 章詳細(xì)介紹如何安裝和使用 NPM 模塊。
現(xiàn)在,我們不用編寫(xiě)自己的 INI 文件解析器,而是使用 NPM 中的某個(gè):
const {parse} = require("ini"); console.log(parse("x = 10 y = 20")); // → {x: "10", y: "20"}ECMAScript 模塊
CommonJS 模塊很好用,并且與 NPM 一起,使 JavaScript 社區(qū)開(kāi)始大規(guī)模共享代碼。
但他們?nèi)匀皇莻€(gè)簡(jiǎn)單粗暴的黑魔法。 例如,表示法有點(diǎn)笨拙 - 添加到exports的內(nèi)容在局部作用域中不可用。 而且因?yàn)?b>require是一個(gè)正常的函數(shù)調(diào)用,接受任何類型的參數(shù),而不僅僅是字符串字面值,所以在不運(yùn)行代碼就很難確定模塊的依賴關(guān)系。
這就是 2015 年的 JavaScript 標(biāo)準(zhǔn)引入了自己的不同模塊系統(tǒng)的原因。 它通常被稱為 ES 模塊,其中 ES 代表 ECMAScript。 依賴和接口的主要概念保持不變,但細(xì)節(jié)不同。 首先,表示法現(xiàn)在已整合到該語(yǔ)言中。 你不用調(diào)用函數(shù)來(lái)訪問(wèn)依賴關(guān)系,而是使用特殊的import關(guān)鍵字。
import ordinal from "ordinal"; import {days, months} from "date-names"; export function formatDate(date, format) { /* ... */ }
同樣,export關(guān)鍵字用于導(dǎo)出東西。 它可以出現(xiàn)在函數(shù),類或綁定定義(let,const或var)的前面。
ES 模塊的接口不是單個(gè)值,而是一組命名綁定。 前面的模塊將formatDate綁定到一個(gè)函數(shù)。 從另一個(gè)模塊導(dǎo)入時(shí),導(dǎo)入綁定而不是值,這意味著導(dǎo)出模塊可以隨時(shí)更改綁定的值,導(dǎo)入它的模塊將看到其新值。
當(dāng)有一個(gè)名為default的綁定時(shí),它將被視為模塊的主要導(dǎo)出值。 如果你在示例中導(dǎo)入了一個(gè)類似于ordinal的模塊,而沒(méi)有綁定名稱周圍的大括號(hào),則會(huì)獲得其默認(rèn)綁定。 除了默認(rèn)綁定之外,這些模塊仍然可以以不同名稱導(dǎo)出其他綁定。
為了創(chuàng)建默認(rèn)導(dǎo)出,可以在表達(dá)式,函數(shù)聲明或類聲明之前編寫(xiě)export default。
export default ["Winter", "Spring", "Summer", "Autumn"];
可以使用單詞as重命名導(dǎo)入的綁定。
import {days as dayNames} from "date-names"; console.log(dayNames.length); // → 7
另一個(gè)重要的區(qū)別是,ES 模塊的導(dǎo)入發(fā)生在模塊的腳本開(kāi)始運(yùn)行之前。 這意味著import聲明可能不會(huì)出現(xiàn)在函數(shù)或塊中,并且依賴項(xiàng)的名稱只能是帶引號(hào)的字符串,而不是任意的表達(dá)式。
在撰寫(xiě)本文時(shí),JavaScript 社區(qū)正在采用這種模塊風(fēng)格。 但這是一個(gè)緩慢的過(guò)程。 在規(guī)定格式之后,花了幾年的時(shí)間,瀏覽器和 Node.js 才開(kāi)始支持它。 雖然他們現(xiàn)在幾乎都支持它,但這種支持仍然存在問(wèn)題,這些模塊如何通過(guò) NPM 分發(fā)的討論仍在進(jìn)行中。
許多項(xiàng)目使用 ES 模塊編寫(xiě),然后在發(fā)布時(shí)自動(dòng)轉(zhuǎn)換為其他格式。 我們正處于并行使用兩個(gè)不同模塊系統(tǒng)的過(guò)渡時(shí)期,并且能夠讀寫(xiě)任何一種之中的代碼都很有用。
構(gòu)建和打包事實(shí)上,從技術(shù)上來(lái)說(shuō),許多 JavaScript 項(xiàng)目都不是用 JavaScript 編寫(xiě)的。有一些擴(kuò)展被廣泛使用,例如第 8 章中提到的類型檢查方言。很久以前,在語(yǔ)言的某個(gè)計(jì)劃性擴(kuò)展添加到實(shí)際運(yùn)行 JavaScript 的平臺(tái)之前,人們就開(kāi)始使用它了。
為此,他們編譯他們的代碼,將其從他們選擇的 JavaScript 方言翻譯成普通的舊式 JavaScript,甚至是過(guò)去的 JavaScript 版本,以便舊版瀏覽器可以運(yùn)行它。
在網(wǎng)頁(yè)中包含由 200 個(gè)不同文件組成的模塊化程序,會(huì)產(chǎn)生它自己的問(wèn)題。如果通過(guò)網(wǎng)絡(luò)獲取單個(gè)文件需要 50 毫秒,則加載整個(gè)程序需要 10 秒,或者如果可以同時(shí)加載多個(gè)文件,則可能需要一半。這浪費(fèi)了很多時(shí)間。因?yàn)樽ト∫粋€(gè)大文件往往比抓取很多小文件要快,所以 Web 程序員已經(jīng)開(kāi)始使用工具,將它們發(fā)布到 Web 之前,將他們(費(fèi)力分割成模塊)的程序回滾成單個(gè)大文件。這些工具被稱為打包器。
我們可以再深入一點(diǎn)。 除了文件的數(shù)量之外,文件的大小也決定了它們可以通過(guò)網(wǎng)絡(luò)傳輸?shù)乃俣取?因此,JavaScript 社區(qū)發(fā)明了壓縮器。 通過(guò)自動(dòng)刪除注釋和空白,重命名綁定以及用占用更少空間的等效代碼替換代碼段,這些工具使 JavaScript 程序變得更小。
因此,你在 NPM 包中找到的代碼,或運(yùn)行在網(wǎng)頁(yè)上的代碼,經(jīng)歷了多個(gè)轉(zhuǎn)換階段 - 從現(xiàn)代 JavaScript 轉(zhuǎn)換為歷史 JavaScript,從 ES 模塊格式轉(zhuǎn)換為 CommonJS,打包并壓縮。 我們不會(huì)在本書(shū)中詳細(xì)介紹這些工具,因?yàn)樗鼈兺軣o(wú)聊,并且變化很快。 請(qǐng)注意,你運(yùn)行的 JavaScript 代碼通常不是編寫(xiě)的代碼。
模塊設(shè)計(jì)使程序結(jié)構(gòu)化是編程的一個(gè)微妙的方面。 任何有價(jià)值的功能都可以用各種方式建模。
良好的程序設(shè)計(jì)是主觀的 - 涉及到權(quán)衡和品味問(wèn)題。 了解結(jié)構(gòu)良好的設(shè)計(jì)的價(jià)值的最好方法,是閱讀或處理大量程序,并注意哪些是有效的,哪些不是。 不要認(rèn)為一個(gè)痛苦的混亂就是“它本來(lái)的方式”。 通過(guò)多加思考,你可以改善幾乎所有事物的結(jié)構(gòu)。
模塊設(shè)計(jì)的一個(gè)方面是易用性。 如果你正在設(shè)計(jì)一些旨在由多人使用,或者甚至是你自己的東西,在三個(gè)月之內(nèi),當(dāng)你記不住你所做的細(xì)節(jié)時(shí),如果你的接口簡(jiǎn)單且可預(yù)測(cè),這會(huì)有所幫助。
這可能意味著遵循現(xiàn)有的慣例。 ini包是一個(gè)很好的例子。 此模塊模仿標(biāo)準(zhǔn) JSON 對(duì)象,通過(guò)提供parse和stringify(用于編寫(xiě) INI 文件)函數(shù),就像 JSON 一樣,在字符串和普通對(duì)象之間進(jìn)行轉(zhuǎn)換。 所以接口很小且很熟悉,在你使用過(guò)一次后,你可能會(huì)記得如何使用它。
即使沒(méi)有能模仿的標(biāo)準(zhǔn)函數(shù)或廣泛使用的包,你也可以通過(guò)使用簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu),并執(zhí)行單一的重點(diǎn)事項(xiàng),來(lái)保持模塊的可預(yù)測(cè)性。 例如,NPM 上的許多 INI 文件解析模塊,提供了直接從硬盤(pán)讀取文件并解析它的功能。 這使得在瀏覽器中不可能使用這些模塊,因?yàn)槲覀儧](méi)有文件系統(tǒng)的直接訪問(wèn)權(quán),并且增加了復(fù)雜性,通過(guò)組合模塊與某些文件讀取功能,可以更好地解決它。
這指向了模塊設(shè)計(jì)的另一個(gè)有用的方面 - 一些代碼可以輕易與其他代碼組合。比起執(zhí)行帶有副作用的復(fù)雜操作的更大的模塊,計(jì)算值的核心模塊適用于范圍更廣的程序。堅(jiān)持從磁盤(pán)讀取文件的 INI 文件讀取器, 在文件內(nèi)容來(lái)自其他來(lái)源的場(chǎng)景中是無(wú)用的。
與之相關(guān),有狀態(tài)的對(duì)象有時(shí)甚至是有用的,但是如果某件事可以用一個(gè)函數(shù)完成,就用一個(gè)函數(shù)。 NPM 上的幾個(gè) INI?? 文件讀取器提供了一種接口風(fēng)格,需要你先創(chuàng)建一個(gè)對(duì)象,然后將該文件加載到對(duì)象中,最后使用特定方法來(lái)獲取結(jié)果。這種類型的東西在面向?qū)ο蟮膫鹘y(tǒng)中很常見(jiàn),而且很糟糕。你不能調(diào)用單個(gè)函數(shù)來(lái)完成,你必須執(zhí)行儀式,在各種狀態(tài)中移動(dòng)對(duì)象。而且由于數(shù)據(jù)現(xiàn)在封裝在一個(gè)特定的對(duì)象類型中,與它交互的所有代碼都必須知道該類型,從而產(chǎn)生不必要的相互依賴關(guān)系。
通常,定義新的數(shù)據(jù)結(jié)構(gòu)是不可避免的 - 只有少數(shù)非?;镜臄?shù)據(jù)結(jié)構(gòu)由語(yǔ)言標(biāo)準(zhǔn)提供,并且許多類型的數(shù)據(jù)一定比數(shù)組或映射更復(fù)雜。 但是當(dāng)數(shù)組足夠時(shí),使用數(shù)組。
一個(gè)稍微復(fù)雜的數(shù)據(jù)結(jié)構(gòu)的示例是第 7 章的圖。JavaScript 中沒(méi)有一種明顯的表示圖的方式。 在那一章中,我們使用了一個(gè)對(duì)象,其屬性保存了字符串?dāng)?shù)組 - 可以從某個(gè)節(jié)點(diǎn)到達(dá)的其他節(jié)點(diǎn)。
NPM 上有幾種不同的尋路包,但他們都沒(méi)有使用這種圖的格式。 它們通常允許圖的邊帶有權(quán)重,它是與其相關(guān)的成本或距離,這在我們的表示中是不可能的。
例如,存在dijkstrajs包。 一種著名的尋路方法,與我們的findRoute函數(shù)非常相似,它被稱為迪科斯特拉(Dijkstra)算法,以首先編寫(xiě)它的艾茲格爾·迪科斯特拉(Edsger Dijkstra)命名。 js后綴通常會(huì)添加到包名稱中,以表明它們用 JavaScript 編寫(xiě)。 這個(gè)dijkstrajs包使用類似于我們的圖的格式,但是它不使用數(shù)組,而是使用對(duì)象,它的屬性值是數(shù)字 - 邊的權(quán)重。
所以如果我們想要使用這個(gè)包,我們必須確保我們的圖以它期望的格式存儲(chǔ)。 所有邊的權(quán)重都相同,因?yàn)槲覀兊暮?jiǎn)化模型將每條道路視為具有相同的成本(一個(gè)回合)。
const {find_path} = require("dijkstrajs"); let graph = {}; for (let node of Object.keys(roadGraph)) { let edges = graph[node] = {}; for (let dest of roadGraph[node]) { edges[dest] = 1; } } console.log(find_path(graph, "Post Office", "Cabin")); // → ["Post Office", "Alice"s House", "Cabin"]
這可能是組合的障礙 - 當(dāng)各種包使用不同的數(shù)據(jù)結(jié)構(gòu)來(lái)描述類似的事情時(shí),將它們組合起來(lái)很困難。 因此,如果你想要設(shè)計(jì)可組合性,請(qǐng)查找其他人使用的數(shù)據(jù)結(jié)構(gòu),并在可能的情況下遵循他們的示例。
總結(jié)通過(guò)將代碼分離成具有清晰接口和依賴關(guān)系的塊,模塊是更大的程序結(jié)構(gòu)。 接口是模塊中可以從其他模塊看到的部分,依賴關(guān)系是它使用的其他模塊。
由于 JavaScript 歷史上并沒(méi)有提供模塊系統(tǒng),因此 CommonJS 系統(tǒng)建立在它之上。 然后在某個(gè)時(shí)候,它確實(shí)有了一個(gè)內(nèi)置系統(tǒng),它現(xiàn)在與 CommonJS 系統(tǒng)不兼容。
包是可以自行分發(fā)的一段代碼。 NPM 是 JavaScript 包的倉(cāng)庫(kù)。 你可以從上面下載各種有用的(和無(wú)用的)包。
練習(xí) 模塊化機(jī)器人這些是第 7 章的項(xiàng)目所創(chuàng)建的約束:
roads buildGraph roadGraph VillageState runRobot randomPick randomRobot mailRoute routeRobot findRoute goalOrientedRobot
如果你要將該項(xiàng)目編寫(xiě)為模塊化程序,你會(huì)創(chuàng)建哪些模塊? 哪個(gè)模塊依賴于哪個(gè)模塊,以及它們的接口是什么樣的?
哪些片段可能在 NPM 上找到? 你愿意使用 NPM 包還是自己編寫(xiě)?
roads模塊根據(jù)第 7 章中的示例編寫(xiě) CommonJS 模塊,該模塊包含道路數(shù)組,并將表示它們的圖數(shù)據(jù)結(jié)構(gòu)導(dǎo)出為roadGraph。 它應(yīng)該依賴于一個(gè)模塊./graph,它導(dǎo)出一個(gè)函數(shù)buildGraph,用于構(gòu)建圖。 該函數(shù)接受包含兩個(gè)元素的數(shù)組(道路的起點(diǎn)和終點(diǎn))。
// Add dependencies and exports const roads = [ "Alice"s House-Bob"s House", "Alice"s House-Cabin", "Alice"s House-Post Office", "Bob"s House-Town Hall", "Daria"s House-Ernie"s House", "Daria"s House-Town Hall", "Ernie"s House-Grete"s House", "Grete"s House-Farm", "Grete"s House-Shop", "Marketplace-Farm", "Marketplace-Post Office", "Marketplace-Shop", "Marketplace-Town Hall", "Shop-Town Hall" ];循環(huán)依賴
循環(huán)依賴是一種情況,其中模塊 A 依賴于 B,并且 B 也直接或間接依賴于 A。許多模塊系統(tǒng)完全禁止這種情況,因?yàn)闊o(wú)論你選擇何種順序來(lái)加載此類模塊,都無(wú)法確保每個(gè)模塊的依賴關(guān)系在它運(yùn)行之前加載。
CommonJS 模塊允許有限形式的循環(huán)依賴。 只要這些模塊不會(huì)替換它們的默認(rèn)exports對(duì)象,并且在完成加載之后才能訪問(wèn)對(duì)方的接口,循環(huán)依賴就沒(méi)有問(wèn)題。
本章前面給出的require函數(shù)支持這種類型的循環(huán)依賴。 你能看到它如何處理循環(huán)嗎? 當(dāng)一個(gè)循環(huán)中的某個(gè)模塊替代其默認(rèn)exports對(duì)象時(shí),會(huì)出現(xiàn)什么問(wèn)題?
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/105019.html
摘要:在本例中,使用屬性指定鏈接的目標(biāo),其中表示超文本鏈接。您應(yīng)該認(rèn)為和元數(shù)據(jù)隱式出現(xiàn)在示例中,即使它們沒(méi)有實(shí)際顯示在文本中。 來(lái)源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:JavaScript and the Browser 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 自豪地采用谷歌翻譯 部分參考了《JavaScript 編程精解(第 2 版)》 ...
摘要:來(lái)源編程精解中文第三版翻譯項(xiàng)目原文譯者飛龍協(xié)議自豪地采用谷歌翻譯部分參考了編程精解第版,這是一本關(guān)于指導(dǎo)電腦的書(shū)。在可控的范圍內(nèi)編寫(xiě)程序是編程過(guò)程中首要解決的問(wèn)題。我們可以用中文來(lái)描述這些指令將數(shù)字存儲(chǔ)在內(nèi)存地址中的位置。 來(lái)源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Introduction 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 自豪地...
摘要:相反,當(dāng)響應(yīng)指針事件時(shí),它會(huì)調(diào)用創(chuàng)建它的代碼提供的回調(diào)函數(shù),該函數(shù)將處理應(yīng)用的特定部分?;卣{(diào)函數(shù)可能會(huì)返回另一個(gè)回調(diào)函數(shù),以便在按下按鈕并且將指針移動(dòng)到另一個(gè)像素時(shí)得到通知。它們?yōu)榻M件構(gòu)造器的數(shù)組而提供。 來(lái)源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Project: A Pixel Art Editor 譯者:飛龍 協(xié)議:CC BY-NC-SA 4...
摘要:為了運(yùn)行包裹的程序,可以將這些值應(yīng)用于它們。在瀏覽器中,輸出出現(xiàn)在控制臺(tái)中。在英文版頁(yè)面上運(yùn)行示例或自己的代碼時(shí),會(huì)在示例之后顯示輸出,而不是在瀏覽器的控制臺(tái)中顯示。這被稱為條件執(zhí)行。 來(lái)源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Program Structure 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 自豪地采用谷歌翻譯 部分參考了《J...
摘要:來(lái)源編程精解中文第三版翻譯項(xiàng)目原文譯者飛龍協(xié)議自豪地采用谷歌翻譯部分參考了編程精解第版在機(jī)器的表面之下,程序在運(yùn)轉(zhuǎn)。本章將會(huì)介紹程序當(dāng)中的基本元素,包括簡(jiǎn)單的值類型以及值運(yùn)算符。示例中的乘法運(yùn)算符優(yōu)先級(jí)高于加法。 來(lái)源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Values, Types, and Operators 譯者:飛龍 協(xié)議:CC BY-NC...
閱讀 3653·2023-04-25 21:43
閱讀 3164·2019-08-29 17:04
閱讀 866·2019-08-29 16:32
閱讀 1597·2019-08-29 15:16
閱讀 2210·2019-08-29 14:09
閱讀 2803·2019-08-29 13:07
閱讀 1678·2019-08-26 13:32
閱讀 1374·2019-08-26 12:00