2024年,你需要掌握的 JavaScript 面試問(wèn)題和答案
面試 JavaScript 職位?沒(méi)問(wèn)題!今天,我要和大家分享一些關(guān)于 JavaScript 的面試題及其答案,幫助你在 2024 年的技術(shù)面試中脫穎而出。
JavaScript 不僅是前端開發(fā)的核心,還在許多后端應(yīng)用中扮演著重要角色。無(wú)論你是資深開發(fā)者還是技術(shù)新手,了解這些問(wèn)題對(duì)你都是非常有幫助的。
1、JavaScript的單線程特性及異步處理機(jī)制
JavaScript確實(shí)是一種單線程編程語(yǔ)言。這意味著它只有一個(gè)調(diào)用棧和一個(gè)內(nèi)存堆。在任何時(shí)候,只能執(zhí)行一組指令。
同步和阻塞的本質(zhì)
JavaScript本質(zhì)上是同步和阻塞的。這意味著代碼會(huì)按行執(zhí)行,一個(gè)任務(wù)必須完成后才能開始下一個(gè)任務(wù)。這種特性在處理復(fù)雜或耗時(shí)的操作時(shí)可能導(dǎo)致用戶界面的響應(yīng)緩慢或凍結(jié)。
JavaScript的異步能力
盡管JavaScript是單線程的,但它也具有異步處理能力。這允許某些操作獨(dú)立于主執(zhí)行線程進(jìn)行。這通常通過(guò)回調(diào)函數(shù)、Promise、async/await和事件監(jiān)聽器等機(jī)制實(shí)現(xiàn)。這些異步特性使JavaScript能夠處理諸如數(shù)據(jù)獲取、用戶輸入處理和I/O操作等任務(wù),而不會(huì)阻塞主線程。這對(duì)于構(gòu)建響應(yīng)性強(qiáng)和交互性強(qiáng)的Web應(yīng)用程序非常重要。
回調(diào)函數(shù)
回調(diào)函數(shù)是異步編程中最基本的方法。它是在某個(gè)任務(wù)完成后才被調(diào)用的函數(shù)。例如:
// 異步操作:讀取文件
fs.readFile('example.txt', 'utf-8', function(err, data) {
if (err) {
throw err;
}
console.log(data); // 文件讀取完成后輸出內(nèi)容
});
Promise
Promise是處理異步操作的一種更優(yōu)雅的方式。
// 創(chuàng)建一個(gè)Promise
let promise = new Promise(function(resolve, reject) {
// 異步操作
setTimeout(function() {
resolve('操作成功完成');
}, 1000);
});
// 使用Promise
promise.then(function(value) {
console.log(value); // 1秒后輸出“操作成功完成”
});
async/await
async/await是基于Promise的一種更簡(jiǎn)潔的異步處理方式。它讓異步代碼看起來(lái)更像同步代碼。
// 定義一個(gè)異步函數(shù)
async function fetchData() {
let response = await fetch('https://api.example.com/data');
let data = await response.json();
return data;
}
// 調(diào)用異步函數(shù)
fetchData().then(data => console.log(data));
JavaScript雖然是單線程且同步的,但其強(qiáng)大的異步處理能力使其成為構(gòu)建現(xiàn)代Web應(yīng)用的理想選擇。通過(guò)理解和合理運(yùn)用JavaScript的異步機(jī)制,我們可以打造出既高效又用戶友好的應(yīng)用程序。
2、現(xiàn)代瀏覽器中JavaScript引擎的運(yùn)作機(jī)制
在探索網(wǎng)頁(yè)和網(wǎng)絡(luò)應(yīng)用的世界時(shí),JavaScript引擎扮演著不可或缺的角色。
當(dāng)你在瀏覽器中輸入一個(gè)網(wǎng)址,背后其實(shí)發(fā)生了一連串復(fù)雜的過(guò)程。這其中,JavaScript代碼從輸入到執(zhí)行,經(jīng)歷了以下幾個(gè)階段:
- 解析階段(Parser): 瀏覽器首先將JavaScript代碼讀入,并轉(zhuǎn)換成一個(gè)稱為“抽象語(yǔ)法樹(AST)”的結(jié)構(gòu),這個(gè)過(guò)程就像是將句子分解成詞匯和語(yǔ)法結(jié)構(gòu)。
- 解釋執(zhí)行(Interpreter): 有了AST,解釋器開始工作,將其轉(zhuǎn)換成計(jì)算機(jī)能理解的字節(jié)碼。這個(gè)過(guò)程有點(diǎn)像翻譯工作,將一種語(yǔ)言轉(zhuǎn)換為另一種。
- 性能分析(Profiler): 在代碼執(zhí)行的同時(shí),性能分析器監(jiān)視著哪些部分被頻繁使用,以便進(jìn)行優(yōu)化。
- 優(yōu)化編譯(Optimizing Compiler): 通過(guò)“即時(shí)編譯(JIT)”技術(shù),根據(jù)分析數(shù)據(jù)對(duì)代碼進(jìn)行優(yōu)化,使其運(yùn)行更快。
- 去優(yōu)化(Deoptimization): 如果優(yōu)化假設(shè)錯(cuò)誤,系統(tǒng)將撤銷該優(yōu)化,返回到未優(yōu)化的狀態(tài),雖然這會(huì)造成一定的性能損耗,但可以確保代碼正確執(zhí)行。
- 熱函數(shù)和內(nèi)聯(lián)緩存: 引擎會(huì)對(duì)“熱函數(shù)”即頻繁執(zhí)行的函數(shù)進(jìn)行優(yōu)化,并使用內(nèi)聯(lián)緩存技術(shù)來(lái)提升性能。
- 內(nèi)存管理: 調(diào)用棧負(fù)責(zé)跟蹤當(dāng)前執(zhí)行的函數(shù),而內(nèi)存堆用于分配內(nèi)存。最后,垃圾回收器負(fù)責(zé)清理不再使用的對(duì)象,釋放內(nèi)存空間。
谷歌Chrome的V8引擎
在谷歌Chrome瀏覽器中,它使用的JavaScript引擎名為V8,具有一些特殊的組件:
- “Ignition”:解釋器的名字。
- “TurboFan”:優(yōu)化編譯器的名字。
- 在解析器之外,還有一個(gè)“預(yù)解析器”,用于檢查語(yǔ)法和符號(hào)。
- 引入了“Sparkplug”,位于“Ignition”和“TurboFan”之間,它是一個(gè)快速編譯器,可以加快代碼執(zhí)行。
通過(guò)這些組件的協(xié)同工作,V8能夠在瀏覽器中快速、高效地執(zhí)行JavaScript代碼。
JavaScript引擎的運(yùn)作是現(xiàn)代網(wǎng)絡(luò)體驗(yàn)的核心。它確保了我們?yōu)g覽的網(wǎng)頁(yè)不僅僅是靜態(tài)的文檔,而是充滿了互動(dòng)性和動(dòng)態(tài)內(nèi)容的生動(dòng)世界。在這個(gè)過(guò)程中,從解析器到優(yōu)化編譯器的每一個(gè)環(huán)節(jié)都至關(guān)重要。它們合作確保了代碼不僅能夠被執(zhí)行,而且能以最優(yōu)化的方式執(zhí)行,使得用戶體驗(yàn)流暢且高效。無(wú)論是初學(xué)者還是資深開發(fā)者,理解這些過(guò)程都是掌握前端技術(shù)的重要一環(huán)。
3、JavaScript中的事件循環(huán)機(jī)制
事件循環(huán)(Event Loop)是JavaScript運(yùn)行時(shí)環(huán)境中的核心組件。在介紹這個(gè)概念之前,我們需要了解JavaScript是單線程執(zhí)行的,這意味著它一次只能執(zhí)行一個(gè)任務(wù)。然而,這并不意味著它不能執(zhí)行異步操作——這正是事件循環(huán)發(fā)揮作用的地方。
(1)事件循環(huán)的角色
事件循環(huán)的主要職責(zé)是監(jiān)控調(diào)用棧和隊(duì)列,并安排異步任務(wù)的執(zhí)行。它確保主線程上的代碼執(zhí)行流暢,同時(shí)也能處理那些需要一些時(shí)間才能完成的任務(wù)。
(2)事件循環(huán)的工作流程
事件循環(huán)的工作流程可以分為以下幾個(gè)步驟:
- 調(diào)用棧(Call Stack): 這是一個(gè)后進(jìn)先出(LIFO)的數(shù)據(jù)結(jié)構(gòu),用來(lái)存儲(chǔ)當(dāng)前正在執(zhí)行的函數(shù)。一旦一個(gè)函數(shù)執(zhí)行完成,它就會(huì)被從棧中彈出。
- Web API: 當(dāng)執(zhí)行到異步操作(如setTimeout、fetch請(qǐng)求、Promise)時(shí),這些操作會(huì)被移至Web API環(huán)境中,并且在那里等待操作完成。完成后,回調(diào)函數(shù)會(huì)被推入任務(wù)隊(duì)列中,等待執(zhí)行。
- 任務(wù)隊(duì)列(Task Queue/Macrotasks): 這是一個(gè)先進(jìn)先出(FIFO)的結(jié)構(gòu),用來(lái)存儲(chǔ)準(zhǔn)備好執(zhí)行的回調(diào)函數(shù),比如setTimeout和setInterval的回調(diào)。
- 微任務(wù)隊(duì)列(Job Queue/Microtasks): 與任務(wù)隊(duì)列類似,這也是一個(gè)FIFO結(jié)構(gòu),但它專門用于處理如Promise的resolve或reject回調(diào)、async/await等微任務(wù)。
- 事件循環(huán)(Event Loop): 當(dāng)調(diào)用棧為空時(shí),事件循環(huán)會(huì)首先檢查微任務(wù)隊(duì)列。如果微任務(wù)隊(duì)列中有任務(wù),它會(huì)優(yōu)先執(zhí)行這些任務(wù)。只有當(dāng)微任務(wù)隊(duì)列為空時(shí),事件循環(huán)才會(huì)檢查任務(wù)隊(duì)列。任務(wù)隊(duì)列中的任務(wù)會(huì)一個(gè)接一個(gè)地被執(zhí)行,但在每個(gè)宏任務(wù)之間,事件循環(huán)都會(huì)再次檢查微任務(wù)隊(duì)列,以確保新的微任務(wù)可以被及時(shí)處理。
(3)執(zhí)行順序的重要性
在JavaScript中,微任務(wù)總是優(yōu)先于宏任務(wù)執(zhí)行。這意味著Promise的回調(diào)會(huì)在setTimeout的回調(diào)之前執(zhí)行。理解這一點(diǎn)對(duì)于編寫高效且無(wú)錯(cuò)誤的異步代碼至關(guān)重要。
(4)示例
想象下面的情況:
console.log('1');
setTimeout(function() {
console.log('2');
}, 0);
Promise.resolve().then(function() {
console.log('3');
});
console.log('4');
輸出的順序會(huì)是:
1
4
3
2
這是因?yàn)榧词箂etTimeout的延遲時(shí)間設(shè)置為0,它的回調(diào)也會(huì)被放入任務(wù)隊(duì)列中,而`Promise.then` 的回調(diào)則會(huì)被放入微任務(wù)隊(duì)列中,而且微任務(wù)隊(duì)列的執(zhí)行總是在當(dāng)前宏任務(wù)(包括調(diào)用棧中所有的同步任務(wù))執(zhí)行完畢后,下一個(gè)宏任務(wù)開始之前。
事件循環(huán)機(jī)制是理解JavaScript異步編程的核心。它不僅確保了同步代碼的順利執(zhí)行,還管理著異步操作的調(diào)度,這使得JavaScript能夠處理復(fù)雜的場(chǎng)景,如用戶交互、腳本加載、網(wǎng)絡(luò)請(qǐng)求等,而不會(huì)造成界面的凍結(jié)。
掌握事件循環(huán)的工作原理,對(duì)于編寫高性能的JavaScript代碼是至關(guān)重要的。這不僅能幫助你避免常見的陷阱,比如“阻塞主線程”的問(wèn)題,還能讓你更好地利用JavaScript的異步特性,編寫出響應(yīng)迅速、用戶體驗(yàn)良好的網(wǎng)頁(yè)應(yīng)用。
4、理解 var, let, 和 const 區(qū)別
(1)var
作用域: var聲明的變量擁有函數(shù)作用域,如果在函數(shù)外部聲明,它將具有全局作用域。在全局作用域下使用var聲明的變量會(huì)被附加到window對(duì)象上。
- 變量提升: var聲明的變量會(huì)發(fā)生變量提升(hoisting),意味著無(wú)論在函數(shù)的哪個(gè)部分聲明,它們都會(huì)被移動(dòng)到函數(shù)的頂部。
- 重復(fù)聲明: 使用var可以重復(fù)聲明同一個(gè)變量。
- 重新賦值: 使用var聲明的變量可以被重新賦值。
(2) let
- 作用域: let聲明的變量具有塊級(jí)作用域(block scope),僅在聲明它的代碼塊內(nèi)有效。
- 變量提升: let聲明的變量也會(huì)提升,但它們不會(huì)被初始化。在代碼執(zhí)行到聲明之前,它們是不可訪問(wèn)的,這個(gè)區(qū)間被稱為“暫時(shí)性死區(qū)”(Temporal Dead Zone, TDZ)。
- 重復(fù)聲明: 在同一個(gè)作用域中,let不允許重新聲明已經(jīng)存在的變量。
- 重新賦值: 使用let聲明的變量可以被重新賦值,但不能重復(fù)聲明。
(3) const
- 作用域: 與let相同,const聲明的變量也具有塊級(jí)作用域。
- 變量提升: const同樣會(huì)提升到塊的頂部,但是在聲明語(yǔ)句之前它們也是不可訪問(wèn)的,存在于“暫時(shí)性死區(qū)”中。
- 重復(fù)聲明: const不允許在相同作用域內(nèi)重復(fù)聲明變量。
- 重新賦值: const聲明的變量不能被重新賦值,它們必須在聲明時(shí)初始化,并且聲明后值是固定的。但是,如果const變量指向的是一個(gè)對(duì)象或數(shù)組,那么對(duì)象或數(shù)組的內(nèi)容是可以被修改的。
附加在window對(duì)象上
在瀏覽器環(huán)境中,全局作用域下使用var聲明的變量會(huì)成為window對(duì)象的屬性。這意味著,如果你聲明了var dog = 'bowser',實(shí)際上你添加了一個(gè)新的全局變量dog到window對(duì)象上,你可以通過(guò)window.dog訪問(wèn)到它,并且會(huì)得到'bowser'這個(gè)值。
相比之下,let和const聲明的變量則不會(huì)被添加到window對(duì)象。這有助于避免全局命名空間的污染,也讓變量的控制范圍更加嚴(yán)格。
5、JavaScript中有哪些不同的數(shù)據(jù)類型?
JavaScript中的數(shù)據(jù)類型主要分為兩大類:原始數(shù)據(jù)類型(Primitive Data Types)和引用數(shù)據(jù)類型(Reference Data Types)。每種類型有其特定的特性和用途,理解它們對(duì)于編寫高質(zhì)量的代碼至關(guān)重要。
原始數(shù)據(jù)類型
原始數(shù)據(jù)類型是基礎(chǔ)的數(shù)據(jù)類型,直接存儲(chǔ)值,它們是不可變的。JavaScript提供了以下幾種原始數(shù)據(jù)類型:
- 數(shù)值(Numbers):用于表示整數(shù)和浮點(diǎn)數(shù)。例如:42、3.14。
- 字符串(Strings):由字符組成,用單引號(hào)、雙引號(hào)或模板字面量包圍。例如:'hello'、"world"、`hello world`。
- 布爾值(Booleans):只有兩個(gè)值true和false,用于邏輯判斷。
- 空值(Null):表示一個(gè)明確的空值。
- 未定義(Undefined):變量已聲明但未初始化時(shí)的狀態(tài)。
- 符號(hào)(Symbols):ES6中新增,每個(gè)符號(hào)值都是唯一不變的,常用作對(duì)象屬性的鍵。
引用數(shù)據(jù)類型
引用數(shù)據(jù)類型可以包含多個(gè)值或復(fù)雜的實(shí)體,它們存儲(chǔ)的是對(duì)數(shù)據(jù)的引用,而非數(shù)據(jù)本身。在JavaScript中,引用數(shù)據(jù)類型主要包括:
- 對(duì)象(Objects):鍵值對(duì)的集合,值可以是任何類型,包括其他對(duì)象或函數(shù)。
- 數(shù)組(Arrays):有序的數(shù)據(jù)集合,數(shù)組中的每個(gè)元素都可以是不同的數(shù)據(jù)類型。
特殊的原始數(shù)據(jù)類型
在許多討論中,null和undefined通常被特別對(duì)待,有時(shí)被視為特殊的原始類型:
- Null:在邏輯上表示“無(wú)值”,通常用來(lái)表示一個(gè)變量應(yīng)該有值,但不是任何其他數(shù)據(jù)類型。
- Undefined:表示變量已聲明,但尚未賦值。
Symbol的獨(dú)特性
- 唯一性:每個(gè)Symbol的值都是全局唯一的,即便創(chuàng)建多個(gè)相同描述的Symbol,它們也代表不同的值。
- 使用場(chǎng)景:主要用于對(duì)象屬性名,以保證屬性名的唯一性,防止屬性名的沖突。
- 屬性隱藏:Symbol作為屬性鍵的對(duì)象屬性不會(huì)出現(xiàn)在傳統(tǒng)的遍歷中,如for...in循環(huán)。
新增原始數(shù)據(jù)類型
- BigInt:ES2020中新增的原始數(shù)據(jù)類型,用于表示大于2^53 - 1的整數(shù)。
數(shù)據(jù)類型的選擇
選擇適合的數(shù)據(jù)類型對(duì)于性能和內(nèi)存管理至關(guān)重要。原始類型通常占用較少內(nèi)存,并且它們的操作速度更快。引用類型則允許構(gòu)建更復(fù)雜的數(shù)據(jù)結(jié)構(gòu),但需要更多的內(nèi)存,并且在處理時(shí)可能會(huì)更慢。
數(shù)據(jù)類型轉(zhuǎn)換
JavaScript是一種動(dòng)態(tài)類型語(yǔ)言,這意味著變量的數(shù)據(jù)類型不是固定的。在運(yùn)算過(guò)程中,變量的數(shù)據(jù)類型可能會(huì)自動(dòng)轉(zhuǎn)換,這稱為類型轉(zhuǎn)換(Type Coercion)。
6、什么是回調(diào)函數(shù)和回調(diào)地獄?
在JavaScript中,回調(diào)函數(shù)是異步操作中常用的概念。一個(gè)回調(diào)函數(shù)是傳遞給另一個(gè)函數(shù)的函數(shù),通常在特定任務(wù)完成后或在預(yù)定時(shí)間執(zhí)行。
回調(diào)函數(shù)的例子
function fetchData(url, callback) {
// 模擬從服務(wù)器獲取數(shù)據(jù)
setTimeout(() => {
const data = 'Some data from the server';
callback(data);
}, 1000);
}
function processData(data) {
console.log('Processing data:', data);
}
fetchData('https://example.com/data', processData);
在這個(gè)例子中,fetchData函數(shù)接受一個(gè)URL和一個(gè)回調(diào)函數(shù)作為參數(shù)。在模擬獲取服務(wù)器數(shù)據(jù)之后(使用setTimeout),它調(diào)用回調(diào)函數(shù)并傳遞檢索到的數(shù)據(jù)。
回調(diào)地獄(Callback Hell)
回調(diào)地獄,也稱為“厄運(yùn)金字塔”(Pyramid of Doom),是JavaScript編程中用來(lái)描述多個(gè)嵌套回調(diào)函數(shù)在異步函數(shù)中使用的情況。
回調(diào)地獄的例子:
fs.readFile('file1.txt', 'utf8', function (err, data) {
if (err) {
console.error(err);
} else {
fs.readFile('file2.txt', 'utf8', function (err, data) {
if (err) {
console.error(err);
} else {
fs.readFile('file3.txt', 'utf8', function (err, data) {
if (err) {
console.error(err);
} else {
// 繼續(xù)更多的嵌套回調(diào)...
}
});
}
});
}
});
在這個(gè)例子中,我們使用`fs.readFile`函數(shù)順序讀取三個(gè)文件,每個(gè)文件讀取操作都是異步的。結(jié)果是,我們不得不將回調(diào)函數(shù)嵌套在彼此之內(nèi),創(chuàng)建了一個(gè)回調(diào)函數(shù)的金字塔結(jié)構(gòu)。
避免回調(diào)地獄
為了避免回調(diào)地獄,現(xiàn)代JavaScript提供了如Promise和async/await等替代方案。下面是使用Promise重寫上述代碼的例子:
const readFile = (file) => {
return new Promise((resolve, reject) => {
fs.readFile(file, 'utf8', (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
};
readFile('file1.txt')
.then((data1) => {
console.log('Read file1.txt successfully');
return readFile('file2.txt');
})
.then((data2) => {
console.log('Read file2.txt successfully');
return readFile('file3.txt');
})
.then((data3) => {
console.log('Read file3.txt successfully');
// 繼續(xù)使用基于Promise的代碼...
})
.catch((err) => {
console.error(err);
});
在這個(gè)改進(jìn)后的例子中,我們通過(guò)鏈?zhǔn)秸{(diào)用.then()方法來(lái)順序處理異步讀取文件的操作,并通過(guò).catch()方法捕獲任何可能發(fā)生的錯(cuò)誤。這樣的代碼結(jié)構(gòu)更加清晰,也更容易理解和維護(hù)。
7、JavaScript中的Promise及其鏈?zhǔn)秸{(diào)用
Promise簡(jiǎn)介
在JavaScript異步編程中,Promise是一個(gè)非常關(guān)鍵的概念。它代表了一個(gè)異步操作的最終完成(或失?。┘捌浣Y(jié)果值。
Promise的狀態(tài)
一個(gè)Promise對(duì)象有以下三種狀態(tài):
- Pending(等待):這是Promise的初始狀態(tài),意味著異步操作尚未完成。
- Fulfilled(已解決):當(dāng)異步操作成功完成,Promise被解決,并且有一個(gè)可用的最終結(jié)果值時(shí)的狀態(tài)。
- Rejected(已拒絕):當(dāng)異步操作失敗或Promise被拒絕,沒(méi)有可用的結(jié)果值時(shí)的狀態(tài)。
Promise構(gòu)造器
Promise構(gòu)造器接受一個(gè)執(zhí)行器函數(shù)作為參數(shù),這個(gè)函數(shù)有兩個(gè)參數(shù):resolve和reject,它們都是函數(shù)。
- resolve:當(dāng)異步操作成功時(shí),將調(diào)用此函數(shù),并傳遞結(jié)果值。
- reject:當(dāng)異步操作失敗時(shí),將調(diào)用此函數(shù),并傳遞錯(cuò)誤或拒絕的原因。
使用Promise
我們可以通過(guò).then()方法來(lái)訪問(wèn)Promise的結(jié)果,通過(guò).catch()方法來(lái)捕獲可能出現(xiàn)的錯(cuò)誤。
// 創(chuàng)建一個(gè)Promise
const fetchData = new Promise((resolve, reject) => {
// 模擬從服務(wù)器獲取數(shù)據(jù)
setTimeout(() => {
const data = 'Some data from the server';
// 使用獲取的數(shù)據(jù)解決Promise
resolve(data);
// 也可以用一個(gè)錯(cuò)誤拒絕Promise
// reject(new Error('Failed to fetch data'));
}, 1000);
});
// 消費(fèi)Promise
fetchData
.then((data) => {
console.log('Data fetched:', data);
})
.catch((error) => {
console.error('Error fetching data:', error);
});
Promise鏈?zhǔn)秸{(diào)用
當(dāng)我們需要按順序執(zhí)行一系列異步任務(wù)時(shí),可以使用Promise鏈?zhǔn)秸{(diào)用。這涉及到將多個(gè).then()方法鏈接到一個(gè)Promise上,以便按特定順序執(zhí)行一系列任務(wù)。
new Promise(function (resolve, reject) {
setTimeout(() => resolve(1), 1000);
})
.then(function (result) {
console.log(result); // 1
return result * 2;
})
.then(function (result) {
console.log(result); // 2
return result * 3;
})
.then(function (result) {
console.log(result); // 6
return result * 4;
});
在這個(gè)鏈?zhǔn)秸{(diào)用中,每個(gè)`.then()`處理函數(shù)都會(huì)順序執(zhí)行,并將其結(jié)果傳遞給下一個(gè)`.then()`。如果任何一個(gè)`.then()`中發(fā)生異?;蚍祷匾粋€(gè)拒絕的`Promise`,鏈?zhǔn)秸{(diào)用將會(huì)中斷,并跳到最近的`.catch()`處理程序。
鏈?zhǔn)秸{(diào)用的優(yōu)勢(shì):使用Promise鏈?zhǔn)秸{(diào)用的優(yōu)勢(shì)在于能夠提供清晰的異步代碼結(jié)構(gòu),相比傳統(tǒng)的回調(diào)函數(shù)(callback hell),它能夠更加直觀地表達(dá)異步操作之間的依賴關(guān)系,并且能夠更簡(jiǎn)單地處理錯(cuò)誤。
8、如何理解Async/Await
Async/Await 的本質(zhì)
async/await 是一種編寫異步代碼的新方式,它建立在Promise之上,但提供了一種更直觀和更符合同步編程模式的語(yǔ)法。async/await 使得異步代碼的編寫、閱讀和調(diào)試變得和同步代碼一樣簡(jiǎn)單。
使用 Async/Await
- async 關(guān)鍵字:用于聲明一個(gè)異步函數(shù),這個(gè)函數(shù)會(huì)隱式地返回一個(gè)Promise對(duì)象。
- await 關(guān)鍵字:只能在async函數(shù)中使用,它會(huì)暫停async函數(shù)的執(zhí)行,等待Promise解決(或拒絕),然后繼續(xù)執(zhí)行async函數(shù)并返回解決的結(jié)果。
// 聲明一個(gè) async 函數(shù)
async function fetchData() {
try {
// 等待fetch請(qǐng)求完成,并獲取響應(yīng)
const response = await fetch('https://example.com/data');
// 等待將響應(yīng)解析為JSON,并獲取數(shù)據(jù)
const data = await response.json();
// 返回獲取到的數(shù)據(jù)
return data;
} catch (error) {
// 如果有錯(cuò)誤,拋出異常
throw error;
}
}
// 使用 async 函數(shù)
fetchData()
.then((jsonData) => {
// 處理獲取到的數(shù)據(jù)
console.log(jsonData);
})
.catch((error) => {
// 處理錯(cuò)誤
console.error("An error occurred:", error);
});
在上面的例子中,`fetchData` 函數(shù)被聲明為 `async` 函數(shù),它使用了 `await` 關(guān)鍵字來(lái)暫停函數(shù)的執(zhí)行,并等待 `fetch` 請(qǐng)求和 `.json()` 方法的 Promise 解決。這樣做可以使我們像編寫同步代碼一樣處理異步操作。 #### 錯(cuò)誤處理 在 `async` 函數(shù)中,可以使用 `try...catch` 結(jié)構(gòu)來(lái)捕獲并處理函數(shù)執(zhí)行過(guò)程中的錯(cuò)誤。這與同步代碼中使用 `try...catch` 的方式相同。
Async/Await 的優(yōu)點(diǎn)
- 可讀性:代碼更直觀,看起來(lái)就像是同步代碼。
- 錯(cuò)誤處理:傳統(tǒng)的 `.then().catch()` 能夠處理錯(cuò)誤,但 `async/await` 允許使用更熟悉的 `try...catch` 語(yǔ)法。
- 避免回調(diào)地獄:`async/await` 讓代碼避免了深層次的嵌套。
注意事項(xiàng)
盡管 `async/await` 提供了許多便利,但是它不會(huì)改變JavaScript事件循環(huán)的工作方式。`await` 關(guān)鍵字會(huì)導(dǎo)致 `async` 函數(shù)的執(zhí)行暫停,但不會(huì)阻塞其他代碼的執(zhí)行,因?yàn)樵诘讓樱鼈冞€是基于非阻塞的Promises工作。
9、== 與 === 有啥區(qū)別
在JavaScript中,==(寬松相等)和===(嚴(yán)格相等)是用于比較兩個(gè)值的運(yùn)算符,但它們?cè)诒容^時(shí)的行為和結(jié)果可能會(huì)非常不同。
寬松相等 ==
- 類型轉(zhuǎn)換:在比較前,==會(huì)將操作數(shù)轉(zhuǎn)換為相同的類型。這個(gè)過(guò)程被稱為類型強(qiáng)制轉(zhuǎn)換(type coercion)。
- 值比較:只要操作數(shù)的值相等,即返回true。
- 比較例子:0 == false 返回 true,因?yàn)?0 被強(qiáng)制轉(zhuǎn)換為 false。1 == "1" 返回 true,因?yàn)樽址?"1" 被強(qiáng)制轉(zhuǎn)換為數(shù)字 1。null == undefined 返回 true,這是語(yǔ)言規(guī)范中定義的特殊情況。
嚴(yán)格相等 ===
- 無(wú)類型轉(zhuǎn)換:===在比較時(shí)不會(huì)進(jìn)行類型轉(zhuǎn)換,如果操作數(shù)的類型不同,則直接返回false。
- 值和類型比較:操作數(shù)必須在值和類型上都相等,才返回true。
- 比較例子:0 === false 返回 false,因?yàn)樗鼈兊念愋筒煌阂粋€(gè)是 number,另一個(gè)是 boolean。1 === "1" 返回 false,盡管它們的值相似,但類型不同。null === undefined 返回 false,因?yàn)?null 和 undefined 是不同的類型。
執(zhí)行速度
- 執(zhí)行速度:通常認(rèn)為 === 會(huì)比 == 快,因?yàn)?=== 不需要進(jìn)行額外的類型轉(zhuǎn)換。但在現(xiàn)代JavaScript引擎中,這種差異通??梢院雎圆挥?jì)。
對(duì)象的比較
- 對(duì)象內(nèi)存引用:無(wú)論是 == 還是 ===,對(duì)象比較時(shí)都是基于它們是否引用同一個(gè)內(nèi)存地址,而不是基于它們的結(jié)構(gòu)或內(nèi)容。[] == [] 或 `[]=== []返回false`,每個(gè)空數(shù)組都是一個(gè)不同的對(duì)象實(shí)例,它們?cè)趦?nèi)存中有不同的引用。
- {} == {} 或 {} === {} 也返回 false,原因同上。
0 == false // true
0 === false // false
1 == "1" // true
1 === "1" // false
null == undefined // true
null === undefined // false
'0' == false // true
'0' === false // false
[]==[] or []===[] //false, refer different objects in memory
{}=={} or {}==={} //false, refer different objects in memory
在JavaScript編程中,推薦使用 === 來(lái)進(jìn)行比較,因?yàn)樗梢员苊庖蝾愋娃D(zhuǎn)換導(dǎo)致的意外結(jié)果,使代碼的邏輯更加清晰和可預(yù)測(cè)。在需要明確考慮類型的場(chǎng)景下,使用 === 是最佳實(shí)踐。當(dāng)你確實(shí)需要類型強(qiáng)制轉(zhuǎn)換時(shí),才使用 ==,但這通常應(yīng)當(dāng)盡量避免。
10、有哪些創(chuàng)建JavaScript對(duì)象的方法
在JavaScript中創(chuàng)建對(duì)象有多種方法,每種方法都適用于不同的場(chǎng)景:
對(duì)象字面量
這是創(chuàng)建對(duì)象最直接的方式,通過(guò)在花括號(hào)中直接定義屬性和方法。
let person = {
firstName: 'John',
lastName: 'Doe',
greet: function() {
return 'Hello, ' + this.firstName + ' ' + this.lastName;
}
};
構(gòu)造函數(shù)
使用構(gòu)造函數(shù)創(chuàng)建對(duì)象允許你實(shí)例化多個(gè)對(duì)象。使用new關(guān)鍵字調(diào)用構(gòu)造函數(shù)。
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.greet = function() {
return 'Hello, ' + this.firstName + ' ' + this.lastName;
};
}
let person1 = new Person('John', 'Doe');
let person2 = new Person('Jane', 'Smith');
Object.create()
Object.create()方法允許你指定一個(gè)原型對(duì)象來(lái)創(chuàng)建一個(gè)新對(duì)象。
let personProto = {
greet: function() {
return 'Hello, ' + this.firstName + ' ' + this.lastName;
}
};
let person = Object.create(personProto);
person.firstName = 'John';
person.lastName = 'Doe';
類語(yǔ)法(ES6)
ES6引入了類的概念,使用`class`關(guān)鍵字來(lái)定義對(duì)象的構(gòu)造函數(shù)和方法。
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
greet() {
return 'Hello, ' + this.firstName + ' ' + this.lastName;
}
}
let person = new Person('John', 'Doe');
工廠函數(shù)
工廠函數(shù)是返回一個(gè)對(duì)象的函數(shù)。這種方法允許您封裝對(duì)象的創(chuàng)建過(guò)程,并輕松創(chuàng)建具有自定義屬性的多個(gè)實(shí)例。
function createPerson(firstName, lastName) {
return {
firstName: firstName,
lastName: lastName,
greet: function() {
return 'Hello, ' + this.firstName + ' ' + this.lastName;
}
};
}
let person1 = createPerson('John', 'Doe');
let person2 = createPerson('Jane', 'Smith');
Object.setPrototypeOf()
Object.setPrototypeOf()方法用于在對(duì)象創(chuàng)建后設(shè)置其原型。
let personProto = {
greet: function() {
return 'Hello, ' + this.firstName + ' ' + this.lastName;
}
};
let person = { firstName: 'John', lastName: 'Doe' };
Object.setPrototypeOf(person, personProto);
Object.assign()
Object.assign()方法用于將一個(gè)或多個(gè)源對(duì)象的可枚舉屬性復(fù)制到目標(biāo)對(duì)象,常用于對(duì)象的合并或創(chuàng)建淺副本。
let target = { a: 1, b: 2 };
let source = { b: 3, c: 4 };
let mergedObject = Object.assign({}, target, source);
原型繼承
JavaScript采用原型繼承模式,可以通過(guò)設(shè)置原型鏈來(lái)使對(duì)象繼承其他對(duì)象的屬性和方法。
function Animal(name) {
this.name = name;
}
Animal.prototype.greet = function() {
return 'Hello, I am ' + this.name;
};
function Dog(name, breed) {
Animal.call(this, name); // 繼承屬性
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
let myDog = new Dog('Max', 'Poodle');
單例模式
單例模式用于創(chuàng)建一個(gè)類的唯一實(shí)例,通過(guò)閉包和自執(zhí)行函數(shù)實(shí)現(xiàn)。
let singleton = (() => {
let instance;
function createInstance() {
return {
// 屬性和方法
};
}
return {
getInstance: () => {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
11、什么是 Rest運(yùn)算符 和 Spread運(yùn)算符 ?
Rest運(yùn)算符
Rest運(yùn)算符(...)使得函數(shù)能夠接受不定數(shù)量的參數(shù)作為數(shù)組。這種方式允許我們?cè)谡{(diào)用函數(shù)時(shí)傳遞任意數(shù)量的參數(shù),而不需要事先定義具名參數(shù)。
Rest運(yùn)算符的例子:
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 輸出 10
在這個(gè)例子中,sum函數(shù)使用Rest運(yùn)算符...numbers來(lái)收集所有傳入的參數(shù),并將它們作為數(shù)組處理。然后使用reduce方法來(lái)計(jì)算所有參數(shù)的總和。
Spread運(yùn)算符
Spread運(yùn)算符同樣由三個(gè)點(diǎn)(...)表示,它用于將數(shù)組或?qū)ο蟮脑卣归_到另一個(gè)數(shù)組或?qū)ο笾?。Spread運(yùn)算符可以輕松實(shí)現(xiàn)數(shù)組的克隆、數(shù)組的合并以及對(duì)象的合并。
Spread運(yùn)算符的例子:
// 數(shù)組合并
const array1 = [1, 2, 3];
const array2 = [4, 5, 6];
const mergedArray = [...array1, ...array2];
// mergedArray 現(xiàn)在是 [1, 2, 3, 4, 5, 6]
// 對(duì)象合并
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const mergedObject = { ...obj1, ...obj2 };
// mergedObject 現(xiàn)在是 { a: 1, b: 3, c: 4 }
在合并對(duì)象的例子中,`obj2`的屬性會(huì)覆蓋`obj1`中的同名屬性。在這里,`b: 2` 被 `b: 3` 所覆蓋。
Rest運(yùn)算符和Spread運(yùn)算符雖然使用相同的符號(hào)(...),但用途完全相反:
- Rest運(yùn)算符:用于將傳遞給函數(shù)的多個(gè)參數(shù)組合成一個(gè)數(shù)組。
- Spread運(yùn)算符:用于將一個(gè)數(shù)組或?qū)ο蟮乃性?屬性展開到另一個(gè)數(shù)組或?qū)ο笾小?/li>
這兩個(gè)運(yùn)算符極大地增強(qiáng)了JavaScript在處理數(shù)組和對(duì)象時(shí)的靈活性,簡(jiǎn)化了很多原本需要通過(guò)循環(huán)或庫(kù)函數(shù)來(lái)實(shí)現(xiàn)的操作。
12、什么是高階函數(shù)?
在JavaScript中,高階函數(shù)(Higher-order function)是指可以接收函數(shù)作為參數(shù)或?qū)⒑瘮?shù)作為返回值的函數(shù)。簡(jiǎn)而言之,它可以對(duì)函數(shù)進(jìn)行操作,包括將函數(shù)作為參數(shù)接收,返回一個(gè)函數(shù),或者兩者都有。
高階函數(shù)的例子
// 這個(gè)高階函數(shù)接收一個(gè)數(shù)組和一個(gè)操作函數(shù)作為參數(shù)
function operationOnArray(arr, operation) {
let result = [];
for (let element of arr) {
result.push(operation(element));
}
return result;
}
// 這是一個(gè)簡(jiǎn)單的函數(shù),將會(huì)被用作高階函數(shù)的參數(shù)
function double(x) {
return x * 2;
}
// 使用高階函數(shù)
let numbers = [1, 2, 3, 4];
let doubledNumbers = operationOnArray(numbers, double);
console.log(doubledNumbers); // 輸出: [2, 4, 6, 8]
在這個(gè)例子中,operationOnArray 是一個(gè)高階函數(shù),它接受一個(gè)數(shù)組和一個(gè)函數(shù) double 作為參數(shù)。double 函數(shù)將傳入的每個(gè)元素翻倍,并將結(jié)果返回給 operationOnArray 函數(shù),后者使用這個(gè)結(jié)果來(lái)構(gòu)造一個(gè)新數(shù)組。
高階函數(shù)的應(yīng)用
高階函數(shù)在JavaScript中有許多應(yīng)用,比如:
- 數(shù)組方法:map, filter, reduce 等數(shù)組方法都是高階函數(shù)的例子,它們接收一個(gè)函數(shù)作為參數(shù)。
- 函數(shù)組合:可以將多個(gè)函數(shù)組合成一個(gè)新的函數(shù)。
- 柯里化:一個(gè)函數(shù)接收少于其聲明的參數(shù)數(shù)量,返回一個(gè)接收剩余參數(shù)的新函數(shù)。
- 異步操作:比如setTimeout或addEventListener,這些函數(shù)接收一個(gè)將在將來(lái)某個(gè)時(shí)刻執(zhí)行的回調(diào)函數(shù)。
一元函數(shù)(單參數(shù)函數(shù))
一元函數(shù)是只接受一個(gè)參數(shù)的函數(shù)。在函數(shù)式編程中,一元函數(shù)因其簡(jiǎn)單性而受到青睞,因?yàn)樗鼈円子阪準(zhǔn)秸{(diào)用和組合。
高階函數(shù)與一元函數(shù)的關(guān)系
高階函數(shù)可以返回一元函數(shù),或者接收一元函數(shù)作為參數(shù),這使得在函數(shù)式編程中,高階函數(shù)和一元函數(shù)經(jīng)常一起使用,以創(chuàng)建簡(jiǎn)潔且模塊化的代碼。
結(jié)束
在現(xiàn)代Web開發(fā)中,JavaScript的重要性不言而喻。對(duì)于前端開發(fā)者來(lái)說(shuō),掌握J(rèn)avaScript的核心概念至關(guān)重要。以上是基于常見面試題的JavaScript核心概念總結(jié),幫助你為面試做好準(zhǔn)備。
掌握這些核心概念不僅對(duì)于面試非常重要,也是成為一名優(yōu)秀的JavaScript開發(fā)者的基礎(chǔ)。無(wú)論是理解語(yǔ)言的基本結(jié)構(gòu),還是掌握高級(jí)的函數(shù)式編程技巧,JavaScript都提供了豐富的特性和靈活性,使其成為世界上最受歡迎的編程語(yǔ)言之一。通過(guò)深入了解和實(shí)踐這些概念,你將能夠編寫更高效、更可維護(hù)、更強(qiáng)大的JavaScript代碼。