關(guān)于在Typescript中做錯(cuò)誤處理的方式詳解
錯(cuò)誤處理是軟件工程重要的一部分。如果處理得當(dāng),它可以為你節(jié)省數(shù)小時(shí)的調(diào)試和故障排除時(shí)間。我發(fā)現(xiàn)了與錯(cuò)誤處理相關(guān)的三大疑難雜癥:
- TypeScript 的錯(cuò)誤類(lèi)型
- 變量范圍
- 嵌套
讓我們逐一深入了解它們帶來(lái)的撓頭問(wèn)題。
疑難雜癥一:Typescript 錯(cuò)誤類(lèi)型
在 JavaScript 中最常見(jiàn)的錯(cuò)誤處理方式與大多數(shù)編程語(yǔ)言相同:
try { throw new Error('oh no!') } catch (error) { console.dir(error) }
最終會(huì)拋出這樣一個(gè)對(duì)象:
{ message: 'oh no!' stack: 'Error: oh no!\n at <anonymous>:2:8' }
這看起來(lái)非常簡(jiǎn)單明了,那么 Typescript 又是怎樣的呢? 首先你能看到的是在 Typescript 中使用 try/catch
并檢查錯(cuò)誤類(lèi)型是,得到的是 unknow
。 對(duì)于剛接觸 Typescript 的人來(lái)說(shuō)遇到這種問(wèn)題是非常撓頭的。解決這一問(wèn)題的常用方法是簡(jiǎn)單地將錯(cuò)誤轉(zhuǎn)為其他類(lèi)型,如下所示:
try { throw new Error('oh no!') } catch (error) { console.log((error as Error).message) }
這種方法可能適用于 99.9% 的捕獲錯(cuò)誤。但為什么 TypeScript 的錯(cuò)誤處理看起來(lái)很麻煩呢?原因在于無(wú)法推斷出 "error" 的類(lèi)型,因?yàn)?try/catch
并不只捕獲錯(cuò)誤,它還捕獲任何拋出的錯(cuò)誤。在 JavaScript(和 TypeScript)中,幾乎可以?huà)伋鋈魏螙|西,如下所示:
try { throw undefined } catch (error) { console.log((error as Error).message) }
執(zhí)行這段代碼將導(dǎo)致在 "catch "代碼塊中拋出新的錯(cuò)誤,這就沒(méi)有達(dá)到使用 try/catch 的目的:
Uncaught TypeError: Cannot read properties of undefined (reading 'message') at <anonymous>:4:20
問(wèn)題產(chǎn)生的原因是 undefined 中不存在 message 屬性,從而導(dǎo)致在 catch 代碼塊中出現(xiàn) TypeError。在 JavaScript 中,只有兩個(gè)值會(huì)導(dǎo)致這個(gè)問(wèn)題:undefined 和 null。
現(xiàn)在可能有人會(huì)問(wèn),有人拋出 undefined 或 null 的可能性有多大。雖然這種情況可能很少發(fā)生,但如果真的發(fā)生了,就會(huì)在代碼中引入意想不到的行為。此外,考慮到在 TypeScript 項(xiàng)目中通常會(huì)使用大量第三方包,如果其中一個(gè)包無(wú)意中拋出了一個(gè)不正確的值,也不足為奇。
這就是 TypeScript 將可拋類(lèi)型設(shè)置為 unknow
的唯一原因嗎?乍一看,這可能只是一個(gè)罕見(jiàn)的邊緣情況,使用類(lèi)型轉(zhuǎn)換是一個(gè)比較靠譜的解決方式。然而,事情并非如此簡(jiǎn)單。雖然 undefined 和 null 是最具破壞性的情況,因?yàn)樗鼈兛赡軐?dǎo)致應(yīng)用程序崩潰,但其他值也可能被拋出。例如:
try { throw false } catch (error) { console.log((error as Error).message) }
這里的主要區(qū)別在于,它不會(huì)拋出 TypeError
,而是直接返回 undefined
。雖然這不會(huì)直接導(dǎo)致應(yīng)用程序崩潰,因此破壞性較小,但也會(huì)帶來(lái)其他問(wèn)題,例如在日志中顯示未定義。此外,根據(jù)使用undefined
值的方式,它還可能間接導(dǎo)致應(yīng)用程序崩潰。請(qǐng)看下面的示例:
try { throw false } catch (error) { console.log((error as Error).message.trim()) }
在這里,調(diào)用 undefined
上的 .trim()
將觸發(fā) TypeError
,可能導(dǎo)致應(yīng)用程序崩潰。
從本質(zhì)上講,TypeScript 的目的是通過(guò)將 catchables
的類(lèi)型指定為 unknow
來(lái)保護(hù)我們。這種方法讓開(kāi)發(fā)人員有責(zé)任確定拋出值的正確類(lèi)型,有助于防止出現(xiàn)運(yùn)行時(shí)問(wèn)題。
如下所示,您可以使用可選的鏈?zhǔn)讲僮鞣?(?.) 來(lái)保護(hù)您的代碼:
try { throw undefined } catch (error) { console.log((error as Error)?.message?.trim?.()) }
雖然這種方法可以保護(hù)你的代碼,但它使用了兩個(gè)會(huì)使代碼維護(hù)復(fù)雜化的 TypeScript 特性:
- 類(lèi)型轉(zhuǎn)換破壞了 TypeScript 的保障措施,即確保變量遵循其指定的類(lèi)型。
- 在非可選類(lèi)型上使用可選的鏈?zhǔn)讲僮鞣?,在?lèi)型不匹配的情況下,如果有人遺漏了這些操作符,也不會(huì)引發(fā)任何錯(cuò)誤。
更好的方法是利用 TypeScript 的類(lèi)型保護(hù)。類(lèi)型保護(hù)本質(zhì)上是一種函數(shù),它能確保特定值與給定類(lèi)型相匹配,并確認(rèn)可以安全地按預(yù)期使用。下面是一個(gè)類(lèi)型保護(hù)的示例,用于驗(yàn)證捕獲的變量是否屬于 Error
類(lèi)型:
export const isError = (value: unknown): value is Error => !!value && typeof value === 'object' && 'message' in value && typeof value.message === 'string' && 'stack' in value && typeof value.stack === 'string'
這種類(lèi)型防護(hù)簡(jiǎn)單明了。它首先確保值不是假的,這意味著它不會(huì)是 undefined
或 null
。然后,它會(huì)檢查它是否是一個(gè)具有預(yù)期屬性的對(duì)象。
這種類(lèi)型保護(hù)可以在代碼的任何地方重復(fù)使用,以驗(yàn)證對(duì)象是否是 Error
。下面是一個(gè)應(yīng)用示例:
const logError = (message: string, error: unknown): void => { if (isError(error)) { console.log(message, error.stack) } else { try { console.log( new Error( `Unexpected value thrown: ${ typeof error === 'object' ? JSON.stringify(error) : String(error) }` ).stack ) } catch { console.log( message, new Error(`Unexpected value thrown: non-stringifiable object`).stack ) } } } try { const circularObject = { self: {} } circularObject.self = circularObject throw circularObject } catch (error) { logError('Error while throwing a circular object:', error) }
通過(guò)創(chuàng)建一個(gè)利用 isError
類(lèi)型防護(hù)的 logError
函數(shù),我們可以安全地記錄標(biāo)準(zhǔn)錯(cuò)誤以及任何其他拋出的值。這對(duì)于排除意外問(wèn)題特別有用。不過(guò),我們需要謹(jǐn)慎,因?yàn)?JSON.stringify
也會(huì)拋出錯(cuò)誤。通過(guò)將其封裝在自己的 try/catch
塊中,可以為對(duì)象提供更詳細(xì)的信息,而不僅僅是記錄其字符串表示 [object Object]
。
此外,我們還可以檢索新 Error
對(duì)象實(shí)例化之前的堆棧跟蹤。這將包括拋出原始值的位置。雖然該方法不能直接提供拋出值的堆棧跟蹤,但它提供了拋出后的跟蹤,足以追溯到問(wèn)題的源頭。
疑難雜癥二:變量范圍
范圍界定可能是錯(cuò)誤處理中最常見(jiàn)的疑難雜癥,適用于 JavaScript 和 TypeScript。請(qǐng)看下面這個(gè)例子:
try { const fileContent = fs.readFileSync(filePath, 'utf8') } catch { console.error(`Unable to load file`) return } console.log(fileContent)
在本例中,由于 fileContent
是在 try 代碼塊內(nèi)定義的,因此在該代碼塊外無(wú)法訪問(wèn)。為了解決這個(gè)問(wèn)題,你可能會(huì)想在 try
代碼塊之外定義變量:
let fileContent try { fileContent = fs.readFileSync(filePath, 'utf8') } catch { console.error(`Unable to load file`) return } console.log(fileContent)
這種方法并不理想。使用 let
而不是 const
,就意味著變量是可變的,這會(huì)帶來(lái)潛在的錯(cuò)誤。此外,它還會(huì)增加代碼的閱讀難度。
規(guī)避這一問(wèn)題的方法之一是將 try/catch
代碼塊封裝在一個(gè)函數(shù)中:
const fileContent = (() => { try { return fs.readFileSync(filePath, 'utf8') } catch { console.error(`Unable to load file`) return } })() if (!fileContent) { return } console.log(fileContent)
雖然這種方法解決了可變性問(wèn)題,但卻使代碼變得更加復(fù)雜。我們可以通過(guò)創(chuàng)建自己的可重用封裝函數(shù)來(lái)解決這個(gè)問(wèn)題。
疑難雜癥三:嵌套
下面的示例演示了如何在可能出現(xiàn)多個(gè)錯(cuò)誤的情況下使用新的 logError
函數(shù):
export const doStuff = async (): Promise<void> => { try { const fetchDataResponse = await fetch('https://api.example.com/fetchData') const fetchDataText = await fetchDataResponse.text() if (!fetchDataResponse.ok) { throw new Error( `Unexpected response while fetching data. Status: ${fetchDataResponse.status} | Status text: ${fetchDataResponse.statusText} | Body: ${fetchDataText}` ) } let fetchData try { fetchData = JSON.parse(fetchDataText) as unknown } catch { throw new Error(`Failed to parse fetched data response as JSON: ${fetchDataText}`) } if ( !fetchData || typeof fetchData !== 'object' || !('data' in fetchData) || !fetchData.data ) { throw new Error( `Fetched data is not in the expected format. Body: ${fetchDataText}` ) } const storeDataResponse = await fetch('https://api.example.com/storeData', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(fetchData), }) const storeDataText = await storeDataResponse.text() if (!storeDataResponse.ok) { throw new Error( `Unexpected response while storing data. Status: ${storeDataResponse.status} | Status text: ${storeDataResponse.statusText} | Body: ${storeDataText}` ) } } catch (error) { logError('An error occurred:', error) } }
你會(huì)發(fā)現(xiàn)調(diào)用的是 .text()
API,而不是 .json()
。因?yàn)?fetch
能調(diào)用這兩種方法中的一種。由于我們的目標(biāo)是在 JSON
轉(zhuǎn)換失敗時(shí)顯示正文內(nèi)容,因此首先調(diào)用 .text()
,然后手動(dòng)還原為 JSON
,確保在此過(guò)程中捕捉到任何錯(cuò)誤。為避免出現(xiàn)以下隱含錯(cuò)誤:
Uncaught SyntaxError: Expected property name or '}' in JSON at position 42
雖然錯(cuò)誤提供的細(xì)節(jié)會(huì)使代碼更容易調(diào)試,但其有限的可讀性會(huì)給代碼維護(hù)帶來(lái)挑戰(zhàn)。try/catch 塊引起的嵌套增加了閱讀函數(shù)時(shí)的認(rèn)知負(fù)擔(dān)。不過(guò),有一種方法可以簡(jiǎn)化代碼,如下所示:
export const doStuffV2 = async (): Promise<void> => { try { const fetchDataResponse = await fetch('https://api.example.com/fetchData') const fetchData = (await fetchDataResponse.json()) as unknown if ( !fetchData || typeof fetchData !== 'object' || !('data' in fetchData) || !fetchData.data ) { throw new Error('Fetched data is not in the expected format.') } const storeDataResponse = await fetch('https://api.example.com/storeData', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(fetchData), }) if (!storeDataResponse.ok) { throw new Error(`Error storing data: ${storeDataResponse.statusText}`) } } catch (error) { logError('An error occurred:', error) } }
這次重構(gòu)解決了嵌套問(wèn)題,但也帶來(lái)了一個(gè)新問(wèn)題:錯(cuò)誤報(bào)告的粒度不夠。通過(guò)刪除檢查,變得更加依賴(lài)錯(cuò)誤信息本身來(lái)理解問(wèn)題。正如我們從一些 JSON.parse
錯(cuò)誤中看到的那樣,這并不總能提供最好的顆粒度。
考慮到我們討論的所有的疑難雜癥,是否存在有效處理錯(cuò)誤的最佳方法?
解決方案
應(yīng)該尋求一種比傳統(tǒng)的 try/catch 塊更優(yōu)越的錯(cuò)誤處理方法。通過(guò)利用 TypeScript 的功能,我們可以毫不費(fèi)力地為此制作一個(gè)封裝函數(shù)。
第一步是確定希望如何規(guī)范化錯(cuò)誤。下面是一種方法:
export class NormalizedError extends Error { stack: string = '' /** The original value that was thrown. */ originalValue: unknown /** * Initializes a new instance of the `NormalizedError` class. * * @param error - An `Error` object. * @param originalValue - The original value that was thrown. */ constructor(error: Error, originalValue?: unknown) { super(error.message) this.stack = error.stack ?? this.message this.originalValue = originalValue ?? error Object.setPrototypeOf(this, NormalizedError.prototype) } }
擴(kuò)展 Error
對(duì)象的主要優(yōu)點(diǎn)是它的行為與標(biāo)準(zhǔn)錯(cuò)誤類(lèi)似。從頭開(kāi)始創(chuàng)建一個(gè)自定義錯(cuò)誤對(duì)象可能會(huì)導(dǎo)致復(fù)雜問(wèn)題,尤其是在使用 instanceof
操作符檢查其類(lèi)型時(shí)。這就是為什么要顯式地設(shè)置原型,以確保 instanceof
能正確工作,尤其是當(dāng)代碼被移植到 ES5
時(shí)。
此外,Error
的所有原型函數(shù)在 NormalizedError
對(duì)象上都可用。構(gòu)造函數(shù)的設(shè)計(jì)還簡(jiǎn)化了創(chuàng)建新 NormalizedError
對(duì)象的過(guò)程,因?yàn)樗蟮谝粋€(gè)參數(shù)必須是一個(gè)實(shí)際的 Error
。以下是 NormalizedError
的優(yōu)點(diǎn):
- 由于構(gòu)造函數(shù)要求第一個(gè)參數(shù)必須是
Error
,因此它始終是一個(gè)有效的錯(cuò)誤。 - 添加了一個(gè)新屬性
originalValue
。這可以檢索拋出的原始值,這對(duì)于從錯(cuò)誤中提取附加信息或在調(diào)試過(guò)程中非常有用。 - 堆棧永遠(yuǎn)不會(huì)是未定義的。在許多情況下,記錄堆棧屬性比記錄消息屬性更有用,因?yàn)樗嘈畔ⅰH欢?,TypeScript 將其類(lèi)型定義為
string | undefined
,這主要是出于跨環(huán)境兼容性的考慮(在傳統(tǒng)環(huán)境中經(jīng)常出現(xiàn))。通過(guò)重寫(xiě)類(lèi)型并保證其始終為字符串,可以簡(jiǎn)化其使用。
既然已經(jīng)定義了標(biāo)準(zhǔn)化錯(cuò)誤的表示方法,就需要一個(gè)函數(shù)將 unknow
的拋出值轉(zhuǎn)換為標(biāo)準(zhǔn)化錯(cuò)誤:
export const toNormalizedError = <E>( value: E extends NormalizedError ? never : E ): NormalizedError => { if (isError(value)) { return new NormalizedError(value) } else { try { return new NormalizedError( new Error( `Unexpected value thrown: ${ typeof value === 'object' ? JSON.stringify(value) : String(value) }` ), value ) } catch { return new NormalizedError( new Error(`Unexpected value thrown: non-stringifiable object`), value ) } } }
使用這種方法,不再需要處理 unknow
類(lèi)型的錯(cuò)誤。所有錯(cuò)誤都將是合適的 Error
對(duì)象,從而為我們提供盡可能多的信息,并消除出現(xiàn)意外錯(cuò)誤值的風(fēng)險(xiǎn)。
為了安全地使用 NormalizedError
對(duì)象,我們還需要一個(gè)類(lèi)型保護(hù)函數(shù):
export const isNormalizedError = (value: unknown): value is NormalizedError => isError(value) && 'originalValue' in value && value.stack !== undefined
現(xiàn)在,我們需要設(shè)計(jì)一個(gè)函數(shù),幫助我們避免使用 try/catch
。另一個(gè)需要考慮的關(guān)鍵問(wèn)題是錯(cuò)誤的發(fā)生,它可以是同步的,也可以是異步的。理想情況下,我們需要一個(gè)能同時(shí)處理這兩種情況的函數(shù)。首先,讓我們創(chuàng)建一個(gè)類(lèi)型保護(hù)來(lái)識(shí)別 Promise
:
export const isPromise = (result: unknown): result is Promise<unknown> => !!result && typeof result === 'object' && 'then' in result && typeof result.then === 'function' && 'catch' in result && typeof result.catch === 'function'
有了安全識(shí)別 Promise
的能力,就可以繼續(xù)實(shí)現(xiàn)新的 noThrow
函數(shù)了:
type NoThrowResult<A> = A extends Promise<infer U> ? Promise<U | NormalizedError> : A | NormalizedError export const noThrow = <A>(action: () => A): NoThrowResult<A> => { try { const result = action() if (isPromise(result)) { return result.catch(toNormalizedError) as NoThrowResult<A> } return result as NoThrowResult<A> } catch (error) { return toNormalizedError(error) as NoThrowResult<A> } }
通過(guò)利用 TypeScript 的功能,我們可以動(dòng)態(tài)支持異步和同步函數(shù)調(diào)用,同時(shí)保持準(zhǔn)確的類(lèi)型。這樣,我們就可以使用單個(gè)實(shí)用程序函數(shù)來(lái)管理所有錯(cuò)誤。
此外,如前所述,這對(duì)解決范圍問(wèn)題特別有用??梢院?jiǎn)單地使用 noThrow
,而不用將 try/catch
封裝在自己的匿名自調(diào)用函數(shù)中,這樣代碼的可讀性就大大提高了。
下面是一個(gè)重構(gòu)版本:
export const doStuffV3 = async (): Promise<void> => { const fetchDataResponse = await fetch('https://api.example.com/fetchData').catch(toNormalizedError) if (isNormalizedError(fetchDataResponse)) { return console.log('Error fetching data:', fetchDataResponse.stack) } const fetchDataText = await fetchDataResponse.text() if (!fetchDataResponse.ok) { return console.log( `Unexpected response while fetching data. Status: ${fetchDataResponse.status} | Status text: ${fetchDataResponse.statusText} | Body: ${fetchDataText}` ) } const fetchData = noThrow(() => JSON.parse(fetchDataText) as unknown) if (isNormalizedError(fetchData)) { return console.log( `Failed to parse fetched data response as JSON: ${fetchDataText}`, fetchData.stack ) } if ( !fetchData || typeof fetchData !== 'object' || !('data' in fetchData) || !fetchData.data ) { return console.log( `Fetched data is not in the expected format. Body: ${fetchDataText}`, toNormalizedError(new Error('Invalid data format')).stack ) } const storeDataResponse = await fetch('https://api.example.com/storeData', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(fetchData), }).catch(toNormalizedError) if (isNormalizedError(storeDataResponse)) { return console.log('Error storing data:', storeDataResponse.stack) } const storeDataText = await storeDataResponse.text() if (!storeDataResponse.ok) { return console.log( `Unexpected response while storing data. Status: ${storeDataResponse.status} | Status text: ${storeDataResponse.statusText} | Body: ${storeDataText}` ) } }
這樣就解決了所有的疑難雜癥:
- 類(lèi)型現(xiàn)在可以安全使用,因此不再需要
logError
,可以直接使用console.log
來(lái)記錄錯(cuò)誤。 - 使用
noThrow
可以控制范圍,在定義const fetchData
時(shí)就證明了這一點(diǎn),以前必須使用let fetchData
。 - 嵌套已減少到單層,使代碼更易于維護(hù)。
你可能還注意到,我們?cè)?fetch
時(shí)沒(méi)有使用 noThrow
。相反,使用了 toNormalizedError
,其效果與 noThrow
差不多,但嵌套更少。由于我們構(gòu)建 noThrow
函數(shù)的方式,你可以在獲取時(shí)使用它,就像我們?cè)谕胶瘮?shù)中使用它一樣:
const fetchDataResponse = await noThrow(() => fetch('https://api.example.com/fetchData') )
總結(jié)
在不斷變化的軟件開(kāi)發(fā)環(huán)境中,錯(cuò)誤處理仍然是穩(wěn)健應(yīng)用程序設(shè)計(jì)的基石。正如我們?cè)诒疚闹兴接懙模?code>try/catch 等傳統(tǒng)方法雖然有效,但有時(shí)會(huì)導(dǎo)致代碼結(jié)構(gòu)復(fù)雜,尤其是在結(jié)合 JavaScript 和 TypeScript 的動(dòng)態(tài)特性時(shí)。通過(guò)使用 TypeScript 的功能,展示了一種精簡(jiǎn)的錯(cuò)誤處理方法,它不僅簡(jiǎn)化了我們的代碼,還增強(qiáng)了代碼的可讀性和可維護(hù)性。
NormalizedError
類(lèi)和 noThrow
實(shí)用功能的引入展示了現(xiàn)代編程范式的強(qiáng)大功能。這些工具允許開(kāi)發(fā)人員從容地處理同步和異步錯(cuò)誤,確保應(yīng)用程序在面對(duì)突發(fā)問(wèn)題時(shí)仍能保持彈性。
以上就是關(guān)于在Typescript中做錯(cuò)誤處理的方案詳解的詳細(xì)內(nèi)容,更多關(guān)于Typescript錯(cuò)誤處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
原生JavaScript實(shí)現(xiàn)幻燈片效果
這篇文章主要為大家詳細(xì)介紹了原生JavaScript實(shí)現(xiàn)幻燈片效果,文中安裝步驟介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-02-02JavaScript中如何跳出forEach循環(huán)代碼示例
循環(huán)遍歷一個(gè)元素是開(kāi)發(fā)中最常見(jiàn)的需求之一,下面這篇文章主要給大家介紹了關(guān)于JavaScript中如何跳出forEach循環(huán)的相關(guān)資料,文章通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-06-06使用javascript實(shí)現(xiàn)判斷當(dāng)前瀏覽器
這篇文章主要介紹了使用javascript實(shí)現(xiàn)判斷當(dāng)前瀏覽器的類(lèi)型及版本,雖然不是很全面,但是還是推薦給大家,簡(jiǎn)單學(xué)下方法和思路。2015-04-04JavaScript進(jìn)階(四)原型與原型鏈用法實(shí)例分析
這篇文章主要介紹了JavaScript原型與原型鏈,結(jié)合實(shí)例形式分析了JavaScript原型與原型鏈基本概念、原理、用法及操作注意事項(xiàng),需要的朋友可以參考下2020-05-05在TypeScript項(xiàng)目中進(jìn)行BDD測(cè)試
這篇文章主要介紹了在TypeScript項(xiàng)目中進(jìn)行BDD測(cè)試,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04JS在IE和FF下attachEvent,addEventListener學(xué)習(xí)筆記
今天小弄了一下JS事件,主要說(shuō)一下FF和IE兼容的問(wèn)題2009-11-11JavaScript 中的 `==` 和 `===` 操作符詳解
在 JavaScript 中,== 和 === 是兩個(gè)常用的比較操作符,分別用于 寬松相等(類(lèi)型轉(zhuǎn)換相等) 和 嚴(yán)格相等(類(lèi)型和值必須相等) 的比較,理解它們的區(qū)別以及具體的比較規(guī)則對(duì)于編寫(xiě)準(zhǔn)確和高效的代碼至關(guān)重要,需要的朋友可以參考下2024-09-09基于bootstrop常用類(lèi)總結(jié)(推薦)
下面小編就為大家?guī)?lái)一篇基于bootstrop常用類(lèi)總結(jié)(推薦)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09JavaScript手寫(xiě)數(shù)組的常用函數(shù)總結(jié)
這篇文章主要給大家介紹了關(guān)于JavaScript手寫(xiě)數(shù)組常用函數(shù)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11es6數(shù)組includes()用法實(shí)例分析
這篇文章主要介紹了es6數(shù)組includes()用法,結(jié)合實(shí)例形式分析了es6數(shù)組includes()針對(duì)給定值判斷的相關(guān)操作技巧與使用注意事項(xiàng),需要的朋友可以參考下2020-04-04