整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          JS合并拼接字符串的5種方法

          JS合并拼接字符串的5種方法

          在前端開發中,JavaScript是不可或缺的一部分,它為網頁帶來了動態交互能力。而字符串拼接作為日常開發中的基礎操作之一,其效率直接影響到用戶體驗和頁面性能。本文旨在探討JavaScript中幾種常見的字符串拼接方法,并通過實例來比較它們的優缺點。


          技術概述

          定義與特性

          字符串拼接是指將兩個或多個字符串連接成一個新的字符串。JavaScript提供了多種方式進行字符串拼接,包括但不限于使用加號 (+), 模板字符串 (${}$), String.prototype.concat(), Array.prototype.join() 以及第三方庫如 Lodash 的 _.join() 方法。

          核心特性與優勢

          • 簡單性: 使用加號或模板字符串進行拼接非常直觀易懂。
          • 靈活性: 模板字符串支持嵌入變量和表達式,可以更靈活地構建動態文本。
          • 性能: 在處理大量字符串時,join() 和第三方庫可能提供更好的性能。

          示例代碼

          // 使用加號
          let name="John";
          let greeting="Hello, " + name + "!";
          
          // 使用模板字符串
          greeting=`Hello, ${name}!`;
          
          // 使用 concat()
          greeting="Hello, ".concat(name, "!");
          
          // 使用 join()
          let parts=["Hello, ", name, "!"];
          greeting=parts.join("");
          
          // 使用 lodash
          greeting=_.join(["Hello, ", name, "!"], "");
          

          技術細節

          每種方法在內部實現上有所不同:

          • 加號 (+): 實現簡單但可能會創建多個臨時字符串對象,當拼接次數較多時性能較差。
          • 模板字符串 (`${}$): 支持嵌入表達式,但在處理大量數據時性能不如 join()
          • concat(): 可以接受多個參數,但在現代JavaScript中使用較少。
          • join(): 接受數組和分隔符,對于處理大量數據非常高效。
          • lodash的 join(): 提供了額外的功能和優化,適用于復雜項目。

          實戰應用

          假設我們需要生成一個包含用戶信息的歡迎消息,其中包含了用戶名、年齡等信息:

          function generateWelcomeMessage(user) {
            return `Welcome, ${user.name} (${user.age} years old)!`;
          }
          
          const user={ name: "Alice", age: 25 };
          console.log(generateWelcomeMessage(user));
          

          在這個例子中,模板字符串提供了最簡潔且易于理解的解決方案。

          優化與改進

          對于頻繁或大量的字符串拼接操作,推薦使用 join() 方法,因為它避免了創建中間字符串對象,從而提高了性能。

          function generateLongMessage(items) {
            return items.map(item=> `${item.name}: ${item.value}`).join(", ");
          }
          

          常見問題

          問題1: 性能問題

          在循環中使用加號進行拼接會導致性能下降。解決方法是使用 join() 或者數組的 reduce() 方法。

          問題2: 特殊字符處理

          直接拼接可能會導致HTML特殊字符未被轉義的問題。解決方法是在拼接前對特殊字符進行轉義。

          總結與展望

          字符串拼接是JavaScript中的一項基本操作,選擇合適的拼接方式可以顯著提高代碼的可讀性和性能。隨著ES6及更高版本標準的引入,模板字符串已經成為一種優雅的選擇。未來,我們可以期待更多高效的字符串處理工具和技術的發展。

          通過本文的學習,我們不僅掌握了如何有效地拼接字符串,還了解了不同方法背后的原理及其適用場景。這將幫助我們在實際開發中做出更合理的選擇。

          邊形合并特效效果圖

          由于上傳圖片大小有限制,所以只能制作這樣質量的動圖,實際效果比這流暢很多!

          對于web前端學習不懂的,或者不知道怎么學習的可以來我的前端群589651705,每天都會分享一些企業級的知識和特效源碼分享,源碼已上傳群文件,小伙伴們可以進群下載。

          140行原生javascript代碼制作正方形合并特效源碼

          html代碼:

          css代碼:

          javascript代碼:

          幾年涌現了很多分而治之的代碼管理方案。長久以來,我們一直使用所謂的模塊模式,即將代碼包裝到自調用的函數表達式中。我們必須自己管理腳本的次序,以確保每個模塊都在自己的依賴之后加載。

          后來,RequireJS 庫出現了。它提供了以編程方式定義每個模塊依賴的機制,有了依賴圖后,我們就不必再操心腳本的加載次序了。RequireJS 接受一個字符串數組,以明確依賴,同時將模塊包裝為一個函數調用,而這個函數接受前面的依賴作為參數。其實很多其他庫都支持類似的功能,只不過API 不大一樣。

          還有其他的復雜依賴管理機制,如AngularJS 中的依賴注入機制。該機制需要我們用函數定義命名組件,并相應地指定其他命名組件依賴。AngularJS 會替我們管理依賴注入的加載,我們要做的只是命名組件和指定依賴。

          取代RequireJS 的是CommonJS,隨著Node.js 的火爆,CommonJS 迅速走紅。本文將先介紹CommonJS,畢竟它在今天的應用還相當普遍。接下來還將介紹ES6 為JavaScript 引入的原生模塊系統。最后,我們將探討CommonJS 與原生JavaScript 模塊(即人們常說的ECMAScript 模塊)的互用性。

          CommonJS

          CommonJS 與其他模塊化方案的不同之處是,它將每個文件都看成一個模塊,其他方案則以編程方式聲明模塊。CommonJS 模塊擁有隱含的局部作用域,全局作用域必須通過global 顯式地被訪問。CommonJS 模塊導入依賴及導出供外部調用的接口都是動態的,導入依賴是通過require 函數調用實現的。這個函數調用是同步的,會返回所請求模塊暴露的接口。

          不看代碼很難說清一種模塊的定義語法。以下代碼展示了一個可重用的CommonJS 模塊文件。其中的has 和union 函數都位于模塊的局部作用域內。在此之上,我們再將union 賦給module.exports,它就成了這個模塊的公共API。

          function has(list, item) {
           return list.includes(item)
          }
          function union(list, item) {
           if (has(list, item)) {
           return list
           }
           return [...list, item]
          }
          module.exports=union
          

          假設將以上文件保存為union.js,那我們就可以在另一個CommonJS 模塊中調用它。比如,另一個文件是app.js,為了調用union.js,需要給require 傳入一個相對路徑。

          const union=require('./union.js')
          console.log(union([1, 2], 3))
          // <- [1, 2, 3]
          console.log(union([1, 2], 2))
          // <- [1, 2]
          

          如果擴展名是.js 或.json,則可以省略,但不鼓勵這么做。

          雖然擴展名對require 語句來說是可選的,在使用node CLI 時最好還是養成添加擴展名的習慣。瀏覽器對ESM 的實現不允許省略擴展名,否則就會導致多一次到服務器的往返才能找到作為HTTP 資源的JavaScript 模塊的正確端點。

          在Node.js 環境下,我們可以通過CLI 運行app.js,如下所示。

          ? node app.js
          # [1, 2, 3]
          # [1, 2]
          

          安裝Node.js 后,就可以在終端命令行中使用node 程序。

          正如其他JavaScript 函數那樣,CommonJS 中的require 函數也可以被動態調用。我們可以利用這一特性實現通過一個接口動態獲取不同的模塊。舉個例子,假設有一個模板目錄,其中包含幾個視圖模板文件,每個文件都導出一個函數。這些函數都接受一個模型參數,然后返回一個HTML 字符串。

          通過讀取model 對象的屬性,以下代碼中展示的模板函數構造并返回購物車中的一個商品。

          // views/item.js
          module.exports=model=> `<li>
           <span>${ model.amount }</span>
           <span>x </span>
           <span>${ model.name }</span>
          </li>`
          

          應用模塊可以基于這個item.js 提供的視圖模板打印一個<li> 元素。

          // app.js
          const renderItem=require('./views/item.js')
          const html=renderItem({
           name: 'Banana bread',
           amount: 3
          })
          console.log(html)
          

          圖1 展示了這個小應用的執行結果。

          圖1:將模型渲染為HTML 就是向模板字面量中插值而已

          接下來的這個模板用于渲染購物車中的所有商品。它接受一個商品數組,并重用前面的item.js 模板來渲染每件商品。

          // views/list.js
          const renderItem=require('./item.js')
          module.exports=model=> `<ul>
           ${ model.map(renderItem).join('\n') }
          </ul>`
          

          我們可以像前面那樣使用list.js 模板。但要注意,傳給它的模型必須是一個商品數組,而非單個商品對象。

          // app.js
          const renderList=require('./views/list.js')
          const html=renderList([{
           name: 'Banana bread',
           amount: 3
          }, {
           name: 'Chocolate chip muffin',
           amount: 2
          }])
          console.log(html)
          

          圖2 展示了小應用的當前狀況。

          圖2:基于模板字面量的復合模板同樣是信手拈來

          到目前為止,這個示例只寫了幾個小模塊,每個模塊只基于傳入的模型對象和視圖模板生成一種HTML 視圖。簡單的API 便于重用,因此我們才能輕松地將模型映射到item.js 模板函數,以渲染出多個商品,最后再通過換行符將它們連接起來。

          既然兩個視圖的API 相似,都是接受一個模型并返回一段HTML 字符串,那么就可以抽象一下。如果想要一個render 函數能夠渲染任何模板,那么可以借助require 的動態特性來輕松實現。以下示例的核心是構建指向模板模塊的路徑。與前面代碼的重要不同點是,require 調用并沒有出現在模塊代碼的頂部。對require 的調用可以出現在任何地方,甚至可以嵌套在其他函數中。

          // render.js
          module.exports=function render(template, model) {
           return require(`./views/${ template }`.js)(model)
          }
          

          有了這樣的API 后,就不用操心調用require 時傳入的視圖模板路徑是否正確了,因為render.js 模塊會正確拼接出路徑。要想渲染模板,只要傳入模板的名字和該模板所需要的模型即可,如以下代碼和圖3 所示。

          // app.js
          const render=require('./render.js')
          console.log(render('item', {
           name: 'Banana bread',
           amount: 1
          }))
          console.log(render('list', [{
           name: 'Apple pie',
           amount: 2
          }, {
           name: 'Roasted almond',
           amount: 25
          }]))
          

          圖3:模板字面量使得創建HTML 渲染應用變得易如反掌

          接下來,我們會看到ES6 模塊從某種程度上受到了CommonJS 的影響。接下來我們會討論export 和import 語句,以及ESM 與CommonJS 有哪些相通之處。

          JavaScript模塊

          在前面介紹CommonJS 模塊時,我們已經知道其API 簡單卻非常靈活、強大。ES6 模塊的API 甚至比它還要簡單,雖然靈活性稍微差了點,但幾乎是一樣強大的。

          嚴格模式

          在ES6 模塊系統中,嚴格模式默認是打開的。嚴格模式是一個特性,用于拒絕JavaScript中那些不好的特性,并讓很多靜默錯誤變成異常,從而被拋出。在拒絕這些特性的基礎上,編譯器可以啟用優化策略,讓JavaScript 運行時更快、更安全。

          ? 變量必須被聲明。

          ? 函數參數的名字必須是唯一的。

          ? 禁止使用with 語句。

          ? 為只讀屬性賦值會導致拋出錯誤。

          ? 00740 這樣的八進制數是語法錯誤。

          ? 用delete 刪除不可刪除的屬性會拋出錯誤。

          ? delete prop 是語法錯誤,delete global.prop 才是正確的。

          ? eval 不會為周圍的作用域引入新變量。

          ? eval 和arguments 不能被綁定或賦值。

          ? arguments 不會神奇地同步方法參數的變化。

          ? 不再支持arguments.callee,訪問它會拋出TypeError。

          ? 不再支持arguments.caller,訪問它會拋出TypeError。

          ? 方法調用中作為this 傳遞的上下文不會被“裝箱”為Object。

          ? 不能再使用fn.caller 和fn.arguments 來訪問JavaScript 棧。

          ? 保留字(如protected、static、interface 等)不能被綁定。

          接下來我們將深入探討一下export 語句。

          export語句

          在CommonJS 模塊中,要導出的值必須賦給module.exports。可以導出的內容包括任意類型的值、對象、數組、函數,如下所示。

          module.exports='hello'
          module.exports={ hello: 'world' }
          module.exports=['hello', 'world']
          module.exports=function hello() {}
          

          作為文件,ES6 模塊是通過export 語句暴露API 的。ESM 中的聲明只在局部作用域中有效,這一點與CommonJS 相同。模塊中聲明的任何變量都必須作為該模塊的API 導入,并且在想要使用它們的模塊中導入才能訪問。

          1. 導出默認綁定

          將前面CommonJS 代碼中的module.exports=替換成export default 即可在ESM 中實現相同的效果。

          export default 'hello'
          export default { hello: 'world' }
          export default ['hello', 'world']
          export default function hello() {}
          

          在CommonJS 中,我們可以為module.exports 動態賦值。

          function initialize() {
           module.exports='hello!'
          }
          initialize()
          

          相較于CommonJS,ESM 中的export 語句只能出現在模塊頂級。export 語句“只能出現在頂級”是一個很好的限制,因為根據方法調用來動態定義并暴露API 并不是非常必要。這一限制還有助于編譯器及靜態分析工具解析ES6 模塊。

          function initialize() {
           export default 'hello!' // SyntaxError
          }
          initialize()
          

          除了export default 語句,ESM 還支持其他暴露API 的方式。

          2. 命名導出

          在CommonJS 中,如果想要暴露多個值,不一定需要導出一個包含這些值的對象。我們可以將這些值賦給隱含的module.exports 對象。這樣導出的仍然只是一個綁定,其中包含module.exports 對象最終持有的所有屬性。也就是說,雖然以下示例看似導出了兩個值,但實際上它們都是最終導出對象的屬性。

          module.exports.counter=0
          module.exports.count=()=> module.exports.counter++
          

          在ESM 中,我們可以通過命名導出語法復現這一行為。相較于CommonJS 為隱含的module.exports 對象添加屬性,ES6 是直接聲明要導出的綁定,如下所示。

          export let counter=0
          export const count=()=> counter++
          

          注意,前面的代碼不能將變量聲明提取為獨立的語句,然后再作為命名導出傳給export,否則會導致語法錯誤。

          let counter=0
          const count=()=> counter++
          export counter // SyntaxError
          export count
          

          ESM 這樣嚴格限制模塊中聲明的語法是為了方便靜態分析,但代價是損失一些靈活性。要想提高靈活性,就必然會提高復雜性,這也是ESM 不提供靈活接口的正當理由。

          3. 導出列表

          ES6 模塊支持導出頂級命名成員的列表,如下所示。這種導出列表的語法很容易解析,同時也就前面提出的問題給出了一個解決方案。

          let counter=0
          const count=()=> counter++
          export { counter, count }
          

          要想重命名導出的綁定,可以使用別名語法export { count as increment}。這樣我們就可以將局部作用域中的綁定count 以別名increment 提供給外部,如下所示。

          let counter=0
          const count=()=> counter++
          export { counter, count as increment }
          

          最后,使用命名成員列表語法時還可以指定默認導出。以下代碼使用as default 在導出多個命名成員的同時定義了模塊的默認導出。

          let counter=0
          const count=()=> counter++
          export { counter as default, count as increment }
          

          雖然以下代碼長了點,但功能與上段代碼相同。

          let counter=0
          const count=()=> counter++
          export default counter
          export { count as increment }
          

          需要特別注意的是,我們導出的是綁定,而不只是值。

          4. 綁定,而不是值

          ES6 模塊導出綁定,而不是值或引用。這意味著以下示例中的模塊導出的fungible 會綁定到這個模塊的fungible 變量,其值會隨fungible 變量的變化而變化。被其他模塊引用后再改變公共接口可能會導致困惑,但這一機制在某些情況下確實也很有用。

          在以下代碼中,模塊導出的fungible 一開始綁定的是一個對象,5 秒后又改成了一個數組。

          export let fungible={ name: 'bound' }
          setTimeout(()=> fungible=[0, 1, 2], 5000)
          

          使用這個API 的模塊在5 秒后也能看到fungible 值的變化。以下示例每隔2 秒會打印一次引入的綁定。

          import { fungible } from './fungible.js'
          console.log(fungible) // <- { name: 'bound' }
          setInterval(()=> console.log(fungible), 2000)
          // <- { name: 'bound' }
          // <- { name: 'bound' }
          // <- [0, 1, 2]
          // <- [0, 1, 2]
          // <- [0, 1, 2]
          

          這種行為特別適合計數器和標記,但除非用途明確,最好不要使用。畢竟從使用者的角度來看,API 接口不確定很難理解。

          JavaScript 的模塊系統還提供了一個export..from 語法,用于暴露其他模塊的接口。

          5. 導出另一個模塊

          向export 添加一個from 子句就可以導出另一個模塊的命名導出。此時綁定不會導入到當前模塊的作用域。換句話說,當前模塊只是傳遞另一個模塊的綁定,并不能直接訪問該綁定。

          export { increment } from './counter.js'
          increment()
          // ReferenceError: increment is not defined
          

          在傳遞綁定時,我們可以為命名導出起個別名。如果以下示例中的模塊被命名為aliased,那么調用者可以通過import { add } from 'aliased.js' 取得counter 模塊中的increment的綁定。

          export { increment as add } from './counter.js'
          

          ESM 也支持用通配符導出另一個模塊中的所有命名導出,如下所示。但要注意,此時不會導出counter 模塊中的默認綁定。

          export * from './counter.js'
          

          要想導出另一個模塊的default 綁定,必須使用導出列表語法來添加別名。

          export { default as counter } from './counter.js'
          

          我們將ES6 模塊暴露API 的所有語法都過了一遍。接下來我們將探討一下import 語句,看看如何通過它使用其他模塊。

          import語句

          我們可以用import 語句在一個模塊中加載另一個模塊。加載模塊的語法因實現而不同,也就是說,規范并未就此給出描述。如今,我們可以編寫兼容ES6 規范的代碼,而一些聰明的人已經找到了在瀏覽器中處理模塊加載的辦法。

          Babel 這樣的編譯器可以基于CommonJS 等模塊系統拼接模塊。這意味著Babel 中的import 語句與CommonJS 中的require 具有相同的語義。

          假設模塊./counter.js 包含以下代碼。

          let counter=0
          const increment=()=> counter++
          const decrement=()=> counter--
          export { counter as default, increment, decrement }
          

          以下這行代碼可以將counter 模塊加載到我們的app 模塊中。這行代碼不會在app 模塊的作用域中創建任何變量。但是這會導致counter 模塊中的所有頂級代碼執行,包括該模塊自己的import 語句。

          import './counter.js'
          

          與export 語句類似,import 語句也只允許出現在模塊代碼的頂級。這一限制有助于編譯器簡化自己的模塊加載邏輯,同時有助于其他靜態分析工具解析你的代碼。

          1. 導入默認導出

          CommonJS 模塊通過require 語句導入其他模塊。如果需要引入某個模塊的默認導出,只要將該語句的結果賦給一個變量即可。

          const counter=require('./counter.js')
          

          要想導入ES6 模塊導出的默認綁定,就必須給它起個名字。但語法和語義與聲明變量時有些不同,因為這里是導入綁定,而不只是將值賦給一個變量。這個區別有助于靜態分析工具和編譯器更輕松地解析我們的代碼。

          import counter from './counter.js'
          console.log(counter)
          // <- 0
          

          除了默認導出,我們也可以導入命名導出并給它們起別名。

          2. 導入命名導出

          以下代碼展示了如何從counter 模塊導入increment 方法。導入命名導出的語法是一對花括號,這讓我們聯想到了解構賦值。

          import { increment } from './counter.js'
          

          為了導入多個綁定,綁定之間以逗號分隔。

          import { increment, decrement } from './counter.js'
          

          這里的語法和語義與解構有些不同。比如,解構通過冒號創建別名,而import 語句則使用as關鍵字,照搬了export 語句的語法。以下代碼在導入increment 方法時將其重命名為add。

          import { increment as add } from './counter.js'
          

          以逗號作為分隔符,我們可以同時導入默認導出和命名導出。

          import counter, { increment } from './counter.js'
          

          我們也可以給default 綁定命名,此時需要一個別名。

          import { default as counter, increment } from './counter.js'
          

          以下的代碼示例展示了ESM 與CommonJS 導入在語義上的區別。記住,ESM 中導出和導入的是綁定,而不是引用。為方便理解,你可以將以下代碼中的綁定counter 想象為一個屬性的獲取方法(getter),它可以訪問counter 模塊內部并返回其局部變量counter 的值。

          import counter, { increment } from './counter.js'
          console.log(counter) // <- 0
          increment()
          console.log(counter) // <- 1
          increment()
          console.log(counter) // <- 2
          

          最后,我們將探討命名空間導入。

          3. 通配符導入語句

          我們可以用通配符導入一個模塊的命名空間對象。相較于導入命名導出或默認導出,這樣可以一次性導入所有導出。注意,星號* 后面必須緊跟別名,導入的所有綁定都在這個別名名下。如果存在default 導出,那么它也會被放到這個命名空間綁定之下。

          import * as counter from './counter.js'
          counter.increment()
          counter.increment()
          console.log(counter.default) // <- 2
          

          動態import()

          有人提出過有關動態import() 表達式的提案(階段3)。與import 語句的靜態分析和鏈接不同,import() 在運行時加載模塊,并在獲取、解析并運行請求的模塊及其所有依賴后,返回一個包含該模塊命名空間對象的Promise。

          與import 語句類似,此時的模塊說明符可以是任意字符串。但與import 只允許靜態的字符串字面量作為模塊說明符不同,import() 的模塊說明符可以是模板字面量或任何能生成模塊說明符字符串的有效JavaScript 表達式。

          假設我們正在國際化某個應用,需要根據用戶代理的語言偏好加載相應的語言包。我們可以先導入localizationService,然后再通過import() 及根據插入navigator.language 的模板字面量構造的模塊說明符來實現本地化數據的動態加載,如下所示。

          import localizationService from './localizationService.js'
          import(`./localizations/${ navigator.language }.json`)
           .then(module=> localizationService.use(module))
          

          注意,通常并不建議將代碼寫成以上那樣,原因如下。

          ? 對靜態分析不友好,因為靜態分析是在構建時執行的,所以幾乎不可能推斷出${ navigator.language } 這樣插值的結果。

          ? JavaScript 打包工具也很難對其進行打包,結果很可能是應用加載完成后再異步加載這個模塊。

          ? Rollup 等工具很難對其進行搖樹優化,難以消除并未導入(因而永遠不會用到)的模塊代碼,因此也就難以縮小包并提升性能。

          ? 不利于輔助檢查模塊導入語句中要導入的文件是否缺失的工具(如eslint-plugin-import)發揮作用。

          與import 語句類似,規范也沒有說明import() 獲取模塊的方式,因此就要看宿主環境了。

          但提案說明了模塊被成功解決后,Promise 應該獲取解析后的命名空間對象。同時該提案指出,如果發生錯誤導致模塊加載失敗,那么Promise 應該被拒絕。

          對于不那么重要的模塊,我們可以在不阻塞頁面加載的情況下進行異步加載,同時還可以在模塊加載失敗時恰當地處理,如下所示。

          import('./vendor/jquery.js')
           .then($=> {
           // 使用jQuery
          })
          .catch(()=> {
           // 加載jQuery失敗
          })
          

          我們可以用Promise.all 異步加載多個模塊。以下示例同時導入了3 個模塊,然后在.then子句中直接用解構獲取了對它們的引用。

          const specifiers=[
           './vendor/jquery.js',
           './vendor/backbone.js',
           './lib/util.js'
          ]
          Promise
           .all(specifiers.map(specifier=> import(specifier)))
           .then(([$, backbone, util])=> {
           // 使用模塊
           })
          

          同樣,我們可以用同步循環或async/await 來加載模塊,如下所示。

          async function load() {
           const { map }=await import('./vendor/jquery.js')
           const $=await import('./vendor/jquery.js')
           const response=await fetch('/cats')
           const cats=await response.json()
           $('<div>')
           .addClass('container cats')
           .html(map(cats, cat=> cat.htmlSnippet))
           .appendTo(document.body)
          }
          load()
          

          await import() 讓動態導入模塊看起來像靜態的import 語句。我們自己心里必須明白,這里其實是一個接一個地異步加載多個模塊。

          注意,雖然import() 有點像函數,但語義與常規函數不同。這里import 并非函數定義,不能進行擴展、不能給它添加屬性,也不能對它使用解構語法。從這個意義上說,import()更像是類構造器中的super() 調用。

          ES模塊的實踐考量

          無論使用什么模塊系統,我們都可以做到公開API 并同時隱藏信息。這種完美的信息隱藏正是以前的開發者夢寐以求的特性。那時候,要想實現同樣的功能,必須非常熟悉JavaScript 的作用域規則,否則就得盲目地循環某種模式,如下所示。這個示例使用局部作用域的calc 函數創建了一個random 模塊,該函數負責生成一個區間為[0, n) 的隨機數,而公共API 中包含range 方法,該方法可以計算一個[min, max] 范圍內的隨機數。

          const random=(function() {
           const calc=n=> Math.floor(Math.random() * n)
           const range=(max=1, min=0)=> calc(max + 1 - min) + min
           return { range }
          })()
          

          比較以上代碼與以下名為random 的ESM 模塊中的代碼。你會發現,立即調用函數表達式(IIFE,immediately invoked function expression)的包裝不見了,模塊的名字也不見了。這里模塊的名字已經變成了文件名。我們又回到了以前在HTML 的<script> 標簽內編寫原始JavaScript 代碼的日子。

          const calc=n=> Math.floor(Math.random() * n)
          const range=(max=1, min=0)=> calc(max + 1 - min) + min
          export { range }
          

          雖然沒有用IIFE 來包裝代碼的問題了,但如何定義、測試、說明和使用模塊仍然需要認真思考。

          決定模塊中包含什么內容并不容易。需要考慮的因素非常多,以下列舉了其中一部分。

          ? 過于復雜嗎?

          ? 過大了嗎?

          ? API 有沒有明確的含義?

          ? API 有沒有完善的文檔?

          ? 為這個模塊編寫測試是否簡單?

          ? 為這個模塊增加新特性難不難?

          ? 刪除模塊中的特性困難嗎?

          相較于模塊長度,復雜性是需要考量的首要指標。一個模塊可能有幾千行代碼但很簡單,比如將文件說明符映射為特定語言字符串的字典;模塊也可能只有幾十行代碼卻非常難以理解,比如涉及域名驗證和其他業務邏輯規則的數據模型。我們可以將代碼拆分成更小的模塊以降低復雜性,每個模塊只專注于解決問題的某一方面。只要不是過于復雜,大模塊也不是什么大問題。

          明確定義的API 同時配有完善的文檔也是優秀模塊化設計的關鍵。模塊的API 應該聚焦,遵循信息隱藏原理。換句話說,只對模塊使用者暴露必要的東西。通過隱藏模塊的內部實現,即使模塊代碼缺乏注釋和文檔,或者將來再被修改,我們仍然可以從整體上保持接口簡單,避免意外的調用出現。通過給公開的API 編寫完善的文檔,即使這些文檔是寫在代碼中的注釋,抑或代碼本身就可以自解釋,我們可以降低模塊使用者的認知門檻。

          測試應該只針對模塊的公開接口來編寫,模塊的內部實現應該看作無關緊要的實現細節。測試要覆蓋模塊公開接口的不同方面,只要API 的輸入和輸出不變,對內部實現的修改就不應該影響測試覆蓋率。

          同樣,為模塊增加或減少特性的容易性也是需要考量的一個因素。

          ? 添加一個新特性有多難?

          ? 為實現某個邏輯是不是必須修改幾個不同的模塊?

          ? 這個流程是不是重復了很多次?或許我們可以將那些變化抽象到一個高層模塊,以隱藏復雜性,也許這樣做很大程度上只是引入了一個中間層,雖然有一些好處或改進,卻導致代碼更難理解了。

          ? 從另一方面看,這個API 有多么不容易改動?

          ? 刪除模塊的一部分、完全刪除,或用其他邏輯代替它是不是很容易?

          ? 如果模塊之間的依賴度很高,那代碼年代越久遠,改版次數越多,代碼量越大,修改就越困難。

          瀏覽器實現的功能只是原生JavaScript 模塊的一點皮毛。現在,有的瀏覽器已經實現了import 和export 語句。有的瀏覽器已經實現了<script type='module'>,通過指定module腳本類型來使用模塊。模塊加載器規范還未最終制定完成,其最新進展參見https://github.com/whatwg/loader#implementation-status。

          在此期間,Node.js 發布的新版本還沒有包含JavaScript 模塊系統的實現。考慮到JavaScript生態系統中的工具都依賴Node,到底能實現多大程度的兼容還說不清楚。實現遲遲不能推出的原因是,目前還無法確定一個文件是CommonJS 模塊還是ESM 模塊。根據文件中至少存在一個import 或export 語句來判斷它是否為ESM 模塊的提案最終被廢棄了。

          目前的做法是準備為ESM 模塊專門引入一個新文件擴展名。鑒于運行Node.js 的平臺及使用場景具有多樣性,這里要考慮的細節非常龐雜。得到一個優雅、完美、正確的方案是非常難的。

          ——本文內容選自《深入理解JavaScript特性》。一本由JavaScript之父作序推薦,360資深前端精心翻譯的著作!

          它旨在讓你輕松學習JavaScript的新進展,包括ES6及后續更新。

          書中提供了大量實用示例,以循序漸進的方式講解了異步函數、對象解構、動態導入及異步生成器等內容,并從實踐角度提供了許多建議,既能幫助廣大前端開發者建立一個完整的知識體系,也能助其在工作中如虎添翼,開發出更好的Web應用。

          書中不僅介紹了箭頭函數、解構、模板字面量以及其他語法方面的新元素,還全面展示了ES6引入的流程控制機制,以及如何高效地簡化自己的代碼。本書的討論還涉及ES6內置的新集合類型、使用代理控制屬性訪問、ES6中內置API的改進、CommonJS與ECMAScript模塊的互用性等方面。

          “尼古拉斯寫的東西特別實用……建議你好好讀讀,從中發現對自己有用的東西,進而真正擁抱JavaScript,致力于為所有人開發更好的Web應用。”——Brendan Eich,JavaScript之父

          “本書全面介紹了ES6新特性的語法和語義,有助于你大幅度提升代碼的表達能力。作者把這些特性融入簡單易懂的示例中,幫你快速上手。”——Kent C. Dodds,PayPal前端工程師,TC39成員

          ——

          【圖靈教育】

          閱讀改變世界,閱讀塑造人生

          讓我們站在巨人的肩膀上,解鎖更多IT技能!


          主站蜘蛛池模板: 成人午夜视频精品一区| www一区二区www免费| 一区二区精品视频| 蜜桃视频一区二区三区在线观看 | 色婷婷亚洲一区二区三区| 国精无码欧精品亚洲一区| 日韩精品国产一区| 亚洲一区二区三区高清不卡 | 日韩一区二区三区免费体验| 亚洲乱色熟女一区二区三区丝袜| 91秒拍国产福利一区| 天堂不卡一区二区视频在线观看| 亚洲av无码一区二区三区天堂古代| 中文字幕一区二区人妻| 国产精品主播一区二区| 性色AV 一区二区三区| 国产主播福利精品一区二区| 日本一区二区三区四区视频| 精品国产福利在线观看一区| 手机福利视频一区二区| 正在播放国产一区| 日本精品一区二区三区视频| 亚洲av一综合av一区| 国产在线精品一区二区三区不卡 | 亚洲午夜精品一区二区麻豆| 中文字幕一区在线观看视频| 精品成人一区二区三区免费视频| 一区二区三区四区在线观看视频 | 一区二区三区免费高清视频| 精品少妇人妻AV一区二区三区| 亚洲日韩国产一区二区三区在线 | 91精品乱码一区二区三区| 麻豆亚洲av熟女国产一区二 | 成人区精品人妻一区二区不卡 | 精品综合一区二区三区| 无码人妻一区二区三区精品视频| 日本在线视频一区| 一区二区乱子伦在线播放| 91在线一区二区| 午夜爽爽性刺激一区二区视频| 天天爽夜夜爽人人爽一区二区 |