成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

八個針對高級職位的高級 JavaScript 面試題

開發 前端
在今天這篇文章中,我們將會看到一些重要的 JavaScript 面試問題的深入解釋。我的目標是徹底解釋這些面試問題,以便我們能夠理解基本概念,并希望在面試中解決其他類似問題。

JavaScript 是一種功能強大的語言,是網絡的主要構建塊之一。這種強大的語言也有一些怪癖。例如,您是否知道 0 === -0 的計算結果為 true,或者 Number("") 的結果為 0?

問題是,有時這些怪癖會讓你摸不著頭腦,甚至質疑 Brendon Eich 發明 JavaScript 的那一天。好吧,重點不在于 JavaScript 是一種糟糕的編程語言,或者像它的批評者所說的那樣它是邪惡的。所有編程語言都有某種與之相關的奇怪之處,JavaScript 也不例外。

因此,在今天這篇文章中,我們將會看到一些重要的 JavaScript 面試問題的深入解釋。我的目標是徹底解釋這些面試問題,以便我們能夠理解基本概念,并希望在面試中解決其他類似問題。

1、仔細觀察 + 和 - 運算符

console.log(1 + '1' - 1);

您能猜出 JavaScript 的 + 和 - 運算符在上述情況下的行為嗎?

當 JavaScript 遇到 1 + '1' 時,它會使用 + 運算符處理表達式。+ 運算符的一個有趣的屬性是,當操作數之一是字符串時,它更喜歡字符串連接。在我們的例子中,“1”是一個字符串,因此 JavaScript 隱式地將數值 1 強制轉換為字符串。因此,1 + '1' 變為 '1' + '1',結果是字符串 '11'。

現在,我們的等式是 '11' - 1。- 運算符的行為恰恰相反。無論操作數的類型如何,它都會優先考慮數字減法。當操作數不是數字類型時,JavaScript 會執行隱式強制轉換,將其轉換為數字。在本例中,“11”被轉換為數值 11,并且表達式簡化為 11 - 1。

把它們放在一起:

'11' - 1 = 11 - 1 = 10

2、復制數組元素

考慮以下 JavaScript 代碼并嘗試查找此代碼中的任何問題:

function duplicate(array) {
  for (var i = 0; i < array.length; i++) {
    array.push(array[i]);
  }
  return array;
}


const arr = [1, 2, 3];
const newArr = duplicate(arr);
console.log(newArr);

在此代碼片段中,我們需要創建一個包含輸入數組的重復元素的新數組。初步檢查后,代碼似乎通過復制原始數組 arr 中的每個元素來創建一個新數組 newArr。然而,重復函數本身出現了一個關鍵問題。

重復函數使用循環來遍歷給定數組中的每個項目。但在循環內部,它使用 push() 方法在數組末尾添加一個新元素。這使得數組每次都變得更長,從而產生循環永遠不會停止的問題。循環條件 (i < array.length) 始終保持為 true,因為數組不斷變大。這使得循環永遠持續下去,導致程序卡住。

為了解決數組長度不斷增長導致無限循環的問題,可以在進入循環之前將數組的初始長度存儲在變量中。

然后,您可以使用該初始長度作為循環迭代的限制。這樣,循環將僅針對數組中的原始元素運行,并且不會因添加重復項而受到數組增長的影響。這是代碼的修改版本:

function duplicate(array) {
  var initialLength = array.length; // Store the initial length
  for (var i = 0; i < initialLength; i++) {
    array.push(array[i]); // Push a duplicate of each element
  }
  return array;
}


const arr = [1, 2, 3];
const newArr = duplicate(arr);
console.log(newArr);

輸出將顯示數組末尾的重復元素,并且循環不會導致無限循環:

[1, 2, 3, 1, 2, 3]

3、原型和__proto__之間的區別

原型屬性是與 JavaScript 中的構造函數相關的屬性。構造函數用于在 JavaScript 中創建對象。定義構造函數時,還可以將屬性和方法附加到其原型屬性。

然后,從該構造函數創建的對象的所有實例都可以訪問這些屬性和方法。因此,prototype 屬性充當在實例之間共享的方法和屬性的公共存儲庫。

考慮以下代碼片段:

// Constructor function
function Person(name) {
  this.name = name;
}


// Adding a method to the prototype
Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name}.`);
};


// Creating instances
const person1 = new Person("Haider Wain");
const person2 = new Person("Omer Asif");


// Calling the shared method
person1.sayHello();  // Output: Hello, my name is Haider Wain.
person2.sayHello();  // Output: Hello, my name is Omer Asif.

在此示例中,我們有一個名為 Person 的構造函數。通過使用 sayHello 之類的方法擴展 Person.prototype,我們將此方法添加到所有 Person 實例的原型鏈中。這允許 Person 的每個實例訪問和利用共享方法。而不是每個實例都有自己的方法副本。

另一方面, __proto__ 屬性(通常發音為“dunder proto”)存在于每個 JavaScript 對象中。在 JavaScript 中,除了原始類型之外,所有東西都可以被視為對象。這些對象中的每一個都有一個原型,用作對另一個對象的引用。__proto__ 屬性只是對此原型對象的引用。當原始對象不具備屬性和方法時,原型對象用作屬性和方法的后備源。默認情況下,當您創建對象時,其原型設置為 Object.prototype。

當您嘗試訪問對象的屬性或方法時,JavaScript 會遵循查找過程來查找它。這個過程涉及兩個主要步驟:

對象自己的屬性:JavaScript 首先檢查對象本身是否直接擁有所需的屬性或方法。如果在對象中找到該屬性,則直接訪問和使用它。

原型鏈查找:如果在對象本身中找不到該屬性,JavaScript 將查看該對象的原型(由 __proto__ 屬性引用)并在那里搜索該屬性。此過程在原型鏈上遞歸地繼續,直到找到屬性或查找到達 Object.prototype。

如果即使在 Object.prototype 中也找不到該屬性,JavaScript 將返回 undefined,表明該屬性不存在。

4、范圍

編寫 JavaScript 代碼時,理解作用域的概念很重要。范圍是指代碼不同部分中變量的可訪問性或可見性。在繼續該示例之前,如果您不熟悉提升以及 JavaScript 代碼的執行方式,可以從此鏈接了解它。這將幫助您更詳細地了解 JavaScript 代碼的工作原理。

讓我們仔細看看代碼片段:

function foo() {
    console.log(a);
}


function bar() {
    var a = 3;
    foo();
}


var a = 5;
bar();

該代碼定義了 2 個函數 foo() 和 bar() 以及一個值為 5 的變量 a。所有這些聲明都發生在全局范圍內。在 bar() 函數內部,聲明了一個變量 a 并賦值為 3。那么當調用 thebar() 函數時,你認為它會打印 a 的值是多少?

當 JavaScript 引擎執行此代碼時,聲明全局變量 a 并為其賦值 5。然后,調用 bar() 函數。在 bar() 函數內部,聲明了一個局部變量 a 并賦值為 3。該局部變量 a 與全局變量 a 不同。之后,從 bar() 函數內部調用 foo() 函數。

在 foo() 函數內部,console.log(a) 語句嘗試記錄 a 的值。由于 foo() 函數的作用域內沒有定義局部變量 a,JavaScript 會查找作用域鏈以找到最近的名為 a 的變量。作用域鏈是指函數在嘗試查找和使用變量時可以訪問的所有不同作用域。

現在,我們來解決 JavaScript 將在哪里搜索變量 a 的問題。它會在 bar 函數的范圍內查找,還是會探索全局范圍?事實證明,JavaScript 將在全局范圍內進行搜索,而這種行為是由稱為詞法范圍的概念驅動的。

詞法作用域是指函數或變量在代碼中編寫時的作用域。當我們定義 foo 函數時,它被授予訪問其自己的本地作用域和全局作用域的權限。無論我們在哪里調用 foo 函數,無論是在 bar 函數內部還是將其導出到另一個模塊并在那里運行,這個特征都保持一致。詞法范圍不是由我們調用函數的位置決定的。

這樣做的結果是輸出始終相同:在全局范圍內找到的 a 值,在本例中為 5。

但是,如果我們在 bar 函數中定義了 foo 函數,則會出現不同的情況:

function bar() {
  var a = 3;


  function foo() {
    console.log(a);
  }


  foo();
}


var a = 5;
bar();

在這種情況下, foo 的詞法作用域將包含三個不同的作用域:它自己的局部作用域、 bar 函數的作用域和全局作用域。詞法范圍由編譯時將代碼放置在源代碼中的位置決定。

當此代碼運行時,foo 位于 bar 函數內。這種安排改變了范圍動態。現在,當 foo 嘗試訪問變量 a 時,它將首先在其自己的本地范圍內進行搜索。由于它在那里找不到 a,因此它將搜索范圍擴大到 bar 函數的范圍。你瞧,a 存在,其值為 3。因此,控制臺語句將打印 3。

5、對象強制

const obj = {
  valueOf: () => 42,
  toString: () => 27
};
console.log(obj + '');

值得探索的一個有趣的方面是 JavaScript 如何處理對象到原始值(例如字符串、數字或布爾值)的轉換。這是一個有趣的問題,測試您是否知道強制轉換如何與對象一起使用。

在字符串連接或算術運算等場景中處理對象時,這種轉換至關重要。為了實現這一點,JavaScript 依賴于兩個特殊的方法:valueOf 和 toString。

valueOf 方法是 JavaScript 對象轉換機制的基本部分。當在需要原始值的上下文中使用對象時,JavaScript 首先在對象中查找 valueOf 方法。

如果 valueOf 方法不存在或未返回適當的原始值,JavaScript 將回退到 toString 方法。該方法負責提供對象的字符串表示形式。

回到我們原來的代碼片段:

const obj = {
  valueOf: () => 42,
  toString: () => 27
};


console.log(obj + '');

當我們運行此代碼時,對象 obj 被轉換為原始值。在本例中,valueOf 方法返回 42,然后,由于與空字符串連接而隱式轉換為字符串。因此,代碼的輸出將為 42。

但是,如果 valueOf 方法不存在或未返回適當的原始值,JavaScript 將回退到 toString 方法。讓我們修改一下之前的例子:

const obj = {
  toString: () => 27
};


console.log(obj + '');

這里,我們刪除了 valueOf 方法,只留下 toString 方法,該方法返回數字 27。在這種情況下,JavaScript 將訴諸 toString 方法進行對象轉換。

6、理解對象鍵

在 JavaScript 中使用對象時,了解如何在其他對象的上下文中處理和分配鍵非常重要。考慮以下代碼片段并花一些時間猜測輸出:

let a = {};
let b = { key: 'test' };
let c = { key: 'test' };


a[b] = '123';
a[c] = '456';


console.log(a);

乍一看,這段代碼似乎應該生成一個具有兩個不同鍵值對的對象 a。然而,由于 JavaScript 對對象鍵的處理方式,結果完全不同。

JavaScript 使用默認的 toString() 方法將對象鍵轉換為字符串。但為什么?在 JavaScript 中,對象鍵始終是字符串(或符號),或者它們通過隱式強制轉換自動轉換為字符串。當您使用字符串以外的任何值(例如數字、對象或符號)作為對象中的鍵時,JavaScript 會在將該值用作鍵之前在內部將該值轉換為其字符串表示形式。

因此,當我們使用對象 b 和 c 作為對象 a 中的鍵時,兩者都會轉換為相同的字符串表示形式:[object Object]。由于這種行為,第二個賦值 a[b] = '123'; 將覆蓋第一個賦值 a[c] = '456';。

現在,讓我們逐步分解代碼:

  • let a = {};:初始化一個空對象a。
  • let b = { key: 'test' };: 創建一個對象 b,其屬性鍵值為 'test'。
  • let c = { key: 'test' };: 定義另一個與 b 結構相同的對象 c。
  • a[b] = '123';:將對象a中鍵為[object Object]的屬性設置為值'123'。

a[c] = '456';:將對象 a 中鍵 [object Object] 相同屬性的值更新為 '456',替換之前的值。

兩個分配都使用相同的鍵字符串 [object Object]。結果,第二個賦值會覆蓋第一個賦值設置的值。

當我們記錄對象 a 時,我們觀察到以下輸出:

{ '[object Object]': '456' }

7、==運算符

console.log([] == ![]);

這個有點復雜。那么,您認為輸出會是什么?讓我們逐步評估一下。讓我們首先看看兩個操作數的類型:

typeof([]) // "object"
typeof(![]) // "boolean"

對于[]來說它是一個對象,這是可以理解的。JavaScript 中的一切都是對象,包括數組和函數。但是操作數![]如何具有布爾類型呢?讓我們試著理解這一點。當你使用 ! 對于原始值,會發生以下轉換:

假值:如果原始值是假值(例如 false、0、null、undefined、NaN 或空字符串 ''),則應用 ! 會將其轉換為 true。

真值:如果原始值是真值(任何非假值),則應用!會將其轉換為 false。

在我們的例子中,[] 是一個空數組,它是 JavaScript 中的真值。由于 [] 為真,所以 ![] 變為假。所以,我們的表達式就變成了:

[] == ![]
[] == false

現在讓我們繼續了解 == 運算符。每當使用 == 運算符比較 2 個值時,JavaScript 就會執行抽象相等比較算法。 

該算法有以下步驟:

正如您所看到的,該算法考慮了比較值的類型并執行必要的轉換。

對于我們的例子,我們將 x 表示為 [],將 y 表示為 ![]。我們檢查了 x 和 y 的類型,發現 x 是對象,y 是布爾值。由于 y 是布爾值,x 是對象,因此應用抽象相等比較算法中的條件 7:

如果 Type(y) 為 Boolean,則返回 x == ToNumber(y) 的比較結果。

這意味著如果其中一種類型是布爾值,我們需要在比較之前將其轉換為數字。ToNumber(y) 的值是多少?正如我們所看到的,[] 是一個真值,否定則使其為假。結果,Number(false)為0。

[] == false
[] == Number(false)
[] == 0

現在我們有了比較 [] == 0,這次條件 8 開始發揮作用:

如果 Type(x) 是 String 或 Number 并且 Type(y) 是 Object,則返回比較結果 x == ToPrimitive(y)。

基于這個條件,如果其中一個操作數是對象,我們必須將其轉換為原始值。這就是 ToPrimitive 算法發揮作用的地方。我們需要將 [] x 轉換為原始值。數組是 JavaScript 中的對象。正如我們之前所看到的,當將對象轉換為基元時,valueOf 和 toString 方法就會發揮作用。 

在這種情況下, valueOf 返回數組本身,它不是有效的原始值。因此,我們轉向 toString 進行輸出。將 toString 方法應用于空數組會得到一個空字符串,這是一個有效的原語:

[] == 0
[].toString() == 0
"" == 0

將空數組轉換為字符串會得到一個空字符串“”,現在我們面臨比較:“”== 0。

現在,其中一個操作數是字符串類型,另一個操作數是數字類型,則條件 5 成立:

如果 Type(x) 是 String 并且 Type(y) 是 Number,則返回比較結果 ToNumber(x) == y。

因此,我們需要將空字符串“”轉換為數字,即為 0。

"" == 0
ToNumber("") == 0
0 == 0

最后,兩個操作數具有相同的類型并且條件 1 成立。由于兩者具有相同的值,因此,最終輸出為:

0 == 0 // true

到目前為止,我們在探索的最后幾個問題中使用了強制轉換,這是掌握 JavaScript 和在面試中解決此類問題的重要概念,這些問題往往會被問到很多。 

8、閉包

這是與閉包相關的最著名的面試問題之一:

const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
  setTimeout(function() {
    console.log('Index: ' + i + ', element: ' + arr[i]);
  }, 3000);
}

如果您知道輸出,那就好了。那么,讓我們嘗試理解這個片段。從表面上看,這段代碼片段將為我們提供以下輸出:

Index: 0, element: 10
Index: 1, element: 12
Index: 2, element: 15
Index: 3, element: 21

但這里的情況并非如此。由于閉包的概念以及 JavaScript 處理變量作用域的方式,實際的輸出會有所不同。當延遲 3000 毫秒后執行 setTimeout 回調時,它們都將引用同一個變量 i,循環完成后該變量的最終值為 4。結果,代碼的輸出將是:

Index: 4, element: undefined
Index: 4, element: undefined
Index: 4, element: undefined
Index: 4, element: undefined

出現此行為的原因是 var 關鍵字沒有塊作用域,并且 setTimeout 回調捕獲對同一 i 變量的引用。當回調執行時,它們都會看到 i 的最終值,即 4,并嘗試訪問未定義的 arr[4]。

為了實現所需的輸出,您可以使用 let 關鍵字為循環的每次迭代創建一個新范圍,確保每個回調捕獲 i 的正確值:

const arr = [10, 12, 15, 21];
for (let i = 0; i < arr.length; i++) {
  setTimeout(function() {
    console.log('Index: ' + i + ', element: ' + arr[i]);
  }, 3000);
}

通過此修改,您將獲得預期的輸出:

Index: 0, element: 10
Index: 1, element: 12
Index: 2, element: 15
Index: 3, element: 21

使用 let 在每次迭代中為 i 創建一個新的綁定,確保每個回調引用正確的值。

通常,開發人員已經熟悉涉及 let 關鍵字的解決方案。然而,面試有時會更進一步,挑戰你在不使用 let 的情況下解決問題。在這種情況下,另一種方法是通過立即調用循環內的函數(IIFE)來創建閉包。這樣,每個函數調用都有自己的 i 副本。您可以這樣做:

const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
  (function(index) {
    setTimeout(function() {
      console.log('Index: ' + index + ', element: ' + arr[index]);
    }, 3000);
  })(i);
}

在此代碼中,立即調用的函數 (function(index) { ... })(i); 為每次迭代創建一個新范圍,捕獲 i 的當前值并將其作為索引參數傳遞。這確保每個回調函數都有自己單獨的索引值,防止與閉包相關的問題并為您提供預期的輸出:

Index: 0, element: 10
Index: 1, element: 12
Index: 2, element: 15
Index: 3, element: 21

最后總結

以上就是我今天這篇文章想與您分享的8個關于JS的前端面試題, 我希望這篇文章對您的面試準備之旅有所幫助。 

如果您還有任何疑問,請在留言區給我們留言,我們一起交流學習進步。

責任編輯:華軒 來源: web前端開發
相關推薦

2023-09-20 08:03:32

JavaScript編程語言

2023-05-18 15:34:52

JavaScript開發前端

2017-08-29 14:12:16

Java面試題

2024-03-06 13:56:00

項目awaitpromise

2025-02-07 15:01:49

Promise數組前端

2024-01-02 16:16:34

Promise前端

2023-09-22 12:14:33

2015-07-31 09:34:44

Java面試題

2015-08-27 09:27:34

JavaScript面試題

2024-07-02 09:03:48

2023-09-21 14:55:24

Web 開發TypeScript

2024-06-04 14:52:28

2013-01-05 14:51:34

JavaScriptjQuery面試

2025-03-26 05:00:00

前端開發者DOM

2010-05-10 13:40:22

CCIE思科認證

2024-11-28 08:33:16

JavaScrip事件循環this

2025-01-09 12:00:00

JavaScript前端數組

2023-11-27 16:01:59

JavaScrip技巧

2022-06-17 09:47:04

Linux命令

2021-09-13 13:29:06

數據驅動大數據SaaS
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲一区中文字幕在线观看 | 日韩国产在线 | 97久久久久久 | 日韩和的一区二在线 | www.色53色.com| 韩国电影久久 | 亚洲高清av | 三级免费网 | 欧美日韩一区二区三区四区 | 18av在线播放 | 毛片一级网站 | 理论片87福利理论电影 | 日韩欧美三区 | 91精品午夜窝窝看片 | 欧美性生活一区二区三区 | 丝袜美腿一区二区三区 | 欧美综合一区 | 亚洲国产精品久久 | 一区二区在线免费观看视频 | 国产精品日日摸夜夜添夜夜av | 岛国毛片在线观看 | 免费成人在线网站 | 欧美日韩视频在线 | 成人中文网 | 久久草在线视频 | 成人黄在线观看 | 伊人网站在线观看 | 亚洲国产成人久久综合一区,久久久国产99 | 成人福利电影 | 日韩在线视频网址 | 久草在线影 | 可以在线观看av的网站 | 亚洲精品国产成人 | 成年人的视频免费观看 | 国产偷自视频区视频 | 在线观看国产www | 亚洲一区二区三区免费观看 | 国产你懂的在线观看 | 久久久久久亚洲欧洲 | 99精品国产一区二区三区 | 亚洲综合二区 |