溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

JavaScript中常見的反模式是什么

發(fā)布時(shí)間:2021-11-06 13:42:10 來源:億速云 閱讀:136 作者:iii 欄目:web開發(fā)

本篇內(nèi)容主要講解“JavaScript中常見的反模式是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“JavaScript中常見的反模式是什么”吧!

硬編碼

硬編碼(Hard-Coding)的字符串、數(shù)字、日期…… 所有能寫死的東西都會(huì)被人寫死。 這是一個(gè)婦孺皆知的反模式,同時(shí)也是最廣泛使用的反模式。 硬編碼中最為典型的大概是 平臺(tái)相關(guān)代碼(Platform-Related), 這是指特定的機(jī)器或環(huán)境下才可以正常運(yùn)行的代碼, 可能是只在你的機(jī)器上可以運(yùn)行,也可能是只在 Windows 下可以運(yùn)行。

例如在 npm script 中寫死腳本路徑 /Users/harttle/bin/fis3, 原因可能是安裝一次非常困難,可能是為了避免重復(fù)安裝,也可能僅僅是因?yàn)檫@樣好使。 不管怎樣,這會(huì)讓所有同事來找你問“為什么我這里會(huì)報(bào)錯(cuò)”。 解決辦法就是把它放到依賴管理,如果有特定的版本要求可以使用 package-lock,如果實(shí)在搞不定可以視為外部依賴可以放到本地配置文件并從版本控制(比如 Git) 移除。

例如在 cli 工具中寫死特殊文件夾 /tmp, ~/.cache,或者路徑分隔符 \\ 或 /。 這類字符串一般可以通過 Node.js 內(nèi)置模塊(或其他運(yùn)行時(shí) API)來得到, 比如使用 os.homedir, os.tmpdir, path.sep 等。

重復(fù)代碼

重復(fù)代碼(Duplication)在業(yè)務(wù)代碼中尤為常見,初衷幾乎都是維護(hù)業(yè)務(wù)的穩(wěn)定性。 舉個(gè)例子:在頁面 A 中需要一個(gè)漂亮的搜索框,而頁面 B 中恰好有一個(gè)。 這時(shí)程序員小哥面臨一個(gè)艱難的選擇(如果直接拷貝還會(huì)有些許感到不安的話):

  • 把 B 拷貝一份,改成 A 想要的樣子。

  • 把 B 中的搜索框重構(gòu)到 C,B 和 A 引用這份代碼。

由于時(shí)間緊迫希望早點(diǎn)下班,或者由于改壞 B 需要承擔(dān)責(zé)任 (PM:讓你做 A 為啥 B 壞了?回答這個(gè)問題比較復(fù)雜,這里先跳過), 經(jīng)過一番思考后決定采取方案 2。

至此整個(gè)故事進(jìn)行地很自然也很順利,這大概就是重復(fù)代碼被廣泛使用的原因。 這個(gè)故事中有幾點(diǎn)需要質(zhì)疑:

  • B 這么容易被改壞,說明 B 的作者 并未考慮復(fù)用。這時(shí)不應(yīng)復(fù)用 B 的代碼,除非決定接手維護(hù)它。

  • B 改壞的責(zé)任不止程序員小哥:B 的作者是否有 編寫測(cè)試,測(cè)試人員是否 回歸測(cè)試 B 頁面?

  • 時(shí)間緊迫不必然導(dǎo)致反模式的出現(xiàn),不可作為說服自己的原因。短期方案也存在優(yōu)雅實(shí)現(xiàn)。

解決辦法就是:抽取 B 的代碼重新開發(fā)形成搜索框組件 C,在 A 頁面使用它。 同時(shí)提供給日后的小伙伴使用,包括敦促 B 的作者也遷移到 C 統(tǒng)一維護(hù)。

假 AMD

模塊化本意是指把軟件的各功能分離到獨(dú)立的模塊中,每個(gè)模塊包含完整的一個(gè)細(xì)分功能。 在 JavaScript 中則是特指把腳本切分為獨(dú)立上下文的,可復(fù)用的代碼單元。

由于 JavaScript 最初作為頁面腳本,存在很多引用全局作用域的語法,以及不少基于全局變量的實(shí)踐方式。 比如 jQuery 的 $, BOM 提供的 window,省略 var 來定義變量等。 AMD 是 JavaScript 社區(qū)較早的模塊化規(guī)范。這是一個(gè)君子協(xié)定,問題就出在這里。 有無數(shù)種方式寫出假的 AMD 模塊:

  • 沒有返回值。對(duì),要的就是副作用。

  • define 后直接 require。對(duì),要的就是立即執(zhí)行。

  • 產(chǎn)生副作用。修改 window 或其他共享變量,比如其他模塊的靜態(tài)屬性。

  • 并發(fā)問題。依賴關(guān)系不明容易引發(fā)并發(fā)問題。

全局副作用的影響完全等同于全局變量,幾乎有全局變量的所有缺點(diǎn): 執(zhí)行邏輯不容易理解;隱式的耦合關(guān)系;編寫測(cè)試?yán)щy。下面來一個(gè)具體的例子:

  // file: login.js

  define('login', function () {

  fetch('/account/login').then(x => {

  window.login = true

  })

  })

  require(['login'])

這個(gè) AMD 模塊與直接寫在一個(gè) <script> 并無區(qū)別,準(zhǔn)確地說是更不可控(requirejs 實(shí)現(xiàn)是異步的)。 也無法被其他模塊使用(比如要實(shí)現(xiàn)注銷后再次登錄),因?yàn)樗鼪]返回任何接口。 此外這個(gè)模塊存在并發(fā)問題(Race Condition):使用 window.login 判斷是否登錄不靠譜。

解決辦法就是把它抽象為模塊,由外部來控制它的執(zhí)行并獲得登錄結(jié)果。 在一個(gè)模塊化良好的項(xiàng)目中,所有狀態(tài)最終由 APP 入口產(chǎn)生, 模塊間共享的狀態(tài)都被抽取到最近的公共上級(jí)。

  define(function () {

  return fetch('/account/login')

  .then(() => true)

  .catch(e => {

  console.error(e)

  return false

  }

  })

注釋膨脹

注釋的初衷是讓讀者更好的理解代碼意圖,但實(shí)踐中可能恰好相反。直接舉一個(gè)生活中的例子:

  // 判斷手機(jī)百度版本大于 15

  if (navigator.userAgent.match(/Chrome:(\d+))[1] < 15) {

  // ...

  }

哈哈當(dāng)你讀到這一段時(shí),相信上述注釋已經(jīng)成功地消耗了你的時(shí)間。 如果你第一次看到這樣的注釋可能會(huì)不可思議,但真實(shí)的項(xiàng)目中多數(shù)注釋都是這個(gè)狀態(tài)。 因?yàn)榫S護(hù)代碼不一定總是記得維護(hù)注釋,況且維護(hù)代碼的通常不止一人。 C 語言課程的后遺癥不止變量命名,“常寫注釋”也是一個(gè)很壞的教導(dǎo)。

解決辦法就是用清晰的邏輯來代替注釋,上述例子重新編寫后的代碼如下:

  if (isHttpsSupported()) {

  // 通過函數(shù)抽取 + 命名,避免了添加注釋

  }

  function isHttpsSupported() {

  return navigator.userAgent.match(/Chrome:(\d+))[1] < 15

  }

函數(shù)體膨脹

“通?!闭J(rèn)為函數(shù)體膨脹和全局變量都是算法課的后遺癥。 但復(fù)雜的業(yè)務(wù)和算法的場(chǎng)景確實(shí)不同,前者有更多的概念和操作需要解釋和整理。 整理業(yè)務(wù)邏輯最有效的手段莫過于變量命名和方法抽?。ó?dāng)然,還要有相應(yīng)的閉包或?qū)ο螅?/p>

但在真實(shí)的業(yè)務(wù)維護(hù)中,保持理性并不容易。 當(dāng)你幾十次進(jìn)入同一個(gè)文件添加業(yè)務(wù)邏輯后,你的函數(shù)一定會(huì)像懶婆娘的裹腳布一樣又臭又長(zhǎng):

  function submitForm() {

  var username = $('form input#username').val()

  if (username === 'harttle') {

  username = 'God'

  } else {

  username = 'Mortal'

  if ($('form input#words').val().indexOf('harttle')) {

  username = 'prophet'

  }

  }

  $('form input#username').val(username)

  $('form').submit()

  }

這只是用來示例,十幾行還遠(yuǎn)遠(yuǎn)沒有達(dá)到“又臭又長(zhǎng)”的地步。 但已經(jīng)可以看到各種目的的修改讓 submitForm() 的職責(zé)遠(yuǎn)不止提交一個(gè)表單。 一個(gè)可能的重構(gòu)方案是這樣的:

  function submitForm() {

  normalize()

  $('form').submit()

  }

  function normalize() {

  var username = parseUsername(

  $('form input#username').val(),

  $('form input#words').val()

  )

  $('form input#username').val(username)

  }

  function parseUsername(username, words)

  if (username === 'harttle') {

  return 'God'

  }

  return words.indexOf('harttle') ? 'prophet' : 'Mortal'

  }

在重構(gòu)后的版本中,我們把原始輸入解析、數(shù)據(jù)歸一化等操作分離到了不同的函數(shù), 這些抽離不僅讓 submitForm() 更容易理解,也讓進(jìn)一步擴(kuò)展業(yè)務(wù)更為方便。 比如在 normalize() 方法中對(duì) input#password 字段也進(jìn)行檢查, 比如新增一個(gè) parseWords() 方法對(duì) input#words 字段進(jìn)行解析等等。

到此,相信大家對(duì)“JavaScript中常見的反模式是什么”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI