JavaScript >> Javascript 文檔 >  >> JavaScript

回答 Baranovskiy 的 JavaScript 測驗

上週,我在推特上發布了關於我在 Dmitry Baranovskiy 的博客上遇到的一個 JavaScript 測驗,題為“你認為你了解 JavaScript 嗎?”與其他此類測驗一樣,對於五個不同的示例代碼,只有一個問題需要回答:結果是什麼?示例代碼測試了 JavaScript 引擎行為的一些古怪屬性。過去我看到過類似的測驗,有時人們說他們在求職面試中將其用作測試。我認為這樣做既是對候選人的不尊重,也是毫無用處的。您不會每天都遇到這種怪癖,因此將獲得工作的最低要求與要求空姐候選人解釋噴氣推進力一樣有用。

儘管如此,我還是喜歡這篇文章中的一些示例代碼,因為它可以用來解釋關於 JavaScript 作為一種語言的一些有趣的事情。以下是對每個示例中發生的情況的深入解釋。

示例#1

if (!("a" in window)) {
    var a = 1;
}
alert(a);

這段看起來很奇怪的代碼似乎在說,“如果 window 沒有屬性‘a’,則定義一個變量‘a’並將其賦值為 1。”然後,您會期望警報顯示數字 1。實際上,警報顯示“未定義”。要了解為什麼會發生這種情況,您需要了解有關 JavaScript 的三件事。

首先,所有的全局變量都是window的屬性 .編寫var a = 1 功能上相當於寫window.a = 1 .因此,您可以使用以下命令檢查是否聲明了全局變量:

"variable-name" in window

二、所有變量聲明都是提升 到包含範圍的頂部。考慮這個更簡單的例子:

alert("a" in window);
var a;

即使變量聲明是在測試之後出現的,這種情況下的警報也會輸出“true”。這是因為 JavaScript 引擎首先掃描變量聲明並將它們移動到頂部。引擎最終執行如下代碼:

var a;
alert("a" in window);

閱讀這段代碼,就更明白為什麼警報會顯示“真”了。

為了理解這個例子,你需要理解的第三件事是,雖然變量 declarations 被提升,變量初始化 不是。這一行既是聲明也是初始化:

var a = 1;

您可以像這樣將聲明和初始化分開:

var a;    //declaration
a = 1;    //initialization

當 JavaScript 引擎遇到聲明和初始化的組合時,它會自動進行拆分,以便可以提升聲明。為什麼初始化沒有提升?因為這可能會在代碼執行期間影響變量的值並導致意外結果。

所以,了解了 JavaScript 的這三個方面,重新審視一下原來的代碼。代碼實際上被執行如下:

var a;
if (!("a" in window)) {
    a = 1;
}
alert(a);

查看此代碼應該使解決方案顯而易見。變量 a 首先聲明,然後是 if 聲明說,“如果 a 沒有聲明,然後初始化 a 值為 1。”當然,這個條件永遠不會成立,因此變量 a 保持其默認值 undefined .

示例 #2

var a = 1,
    b = function a(x) {
        x && a(--x);
    };
alert(a);

這段代碼看起來比實際複雜得多。結果是警報顯示數字 1,即 a 被初始化的值。但這是為什麼呢?再一次,這個例子依賴於 JavaScript 三個關鍵方面的知識。

第一個概念是變量聲明提升,示例 #1 也依賴於此。第二個概念是function 聲明吊裝。所有函數聲明與變量聲明一起被提升到包含範圍的頂部。為了清楚起見,函數聲明如下所示:

function functionName(arg1, arg2){
    //function body
}

這與函數表達式相反,它是一個變量賦值:

var functionName = function(arg1, arg2){
    //function body
};

需要明確的是,函數表達式不是 吊起。現在這對您來說應該是有意義的,就像變量初始化一樣,將值的賦值從代碼中的一個位置移動到另一個位置可以顯著改變執行。

你必須知道的第三個概念,既要理解又要混淆這個例子是函數聲明覆蓋變量聲明而不是變量初始化。要理解這一點,請考慮以下內容

function value(){
    return 1;
}
var value;
alert(typeof value);    //"function"

變量 value 即使變量聲明出現在函數聲明之後,它也會作為函數結束。在這種情況下,函數聲明優先。但是,拋出變量初始化,你會得到不同的結果:

function value(){
    return 1;
}
var value = 1;
alert(typeof value);    //"number"

現在變量 value 設置為1。變量初始化覆蓋函數聲明。

回到示例代碼,儘管名稱如此,該函數實際上是一個函數表達式。命名函數表達式不被視為函數聲明,因此不會被變量聲明覆蓋。但是,您會注意到包含函數表達式的變量是 b 而函數表達式的名稱是 a .瀏覽器以不同的方式處理它。 Internet Explorer 將其視為函數聲明,因此它被變量初始化覆蓋,這意味著對 a(--x) 的調用 導致錯誤。所有其他瀏覽器都允許調用 a(--x) 在函數內部,而 a 仍然是函數外部的數字。基本上,調用 b(2) 在 Internet Explorer 中引發 JavaScript 錯誤但返回 undefined 在別人身上。

綜上所述,更正確和更容易理解的代碼版本是:

var a = 1,
    b = function(x) {
        x && b(--x);
    };
alert(a);

看這段代碼,應該清楚a 永遠是 1。

示例#3

function a(x) {
    return x * 2;
}
var a;
alert(a);

如果您能夠理解前面的示例,那麼這個示例應該非常簡單。您唯一需要了解的是函數聲明勝過變量聲明,除非有初始化。這裡沒有初始化,所以alert會顯示函數的源代碼。

示例#4

function b(x, y, a) {
    arguments[2] = 10;
    alert(a);
}
b(1, 2, 3);

這段代碼更容易理解,因為您必須回答的唯一真正問題是警報顯示 3 還是 10。答案在所有瀏覽器中都是 10。要弄清楚這段代碼,你只需要知道一個概念。 ECMA-262,第 3 版,第 10.1.8 節介紹了 arguments 對象:

簡而言之,arguments 中的每個條目 object 是每個命名參數的副本。請注意,這些值是共享的,但不是內存空間。這兩個內存空間由 JavaScript 引擎保持同步,這意味著 arguments[2]a 始終包含相同的值。該值最終為 10。

示例#5

function a() {
    alert(this);
}
a.call(null);

實際上,我認為這是本測驗中五個示例中最簡單的一個。它依賴於對兩個 JavaScript 概念的理解。

首先,你要了解this的值是怎麼來的 對象確定。在對像上調用方法時,this 指向方法所在的對象。示例:

var object = {
    method: function() {
        alert(this === object);    //true
    }
}
object.method(); 

在此代碼中,this 最終指向 objectobject.method() 叫做。在全局範圍內,this 相當於 window (在瀏覽器中,在非瀏覽器環境中是 global 對像等效),所以 this 也等於 window 在不是對象屬性的函數內部。示例:

function method() {
    alert(this === window);    //true
}
method(); 

這裡,this 最終指向全局對象 window .

有了這些知識,您現在可以處理第二個重要概念:call() 是什麼 做。 call() 方法執行一個函數,就好像它是另一個對象的方法一樣。第一個參數變為 this 在方法內部,每個後續參數都作為參數傳遞給函數。考慮以下幾點:

function method() {
    alert(this === window);
}
method();    //true
method.call(document);   //false

這裡,method() 函數被調用使得 this 將是 document .因此,警報顯示為“false”。

ECMA-262 第 3 版的一個有趣部分描述了當 null 時應該發生什麼 作為 call() 的第一個參數傳入 :

所以每當 null 傳遞給 call() (或其兄弟,apply() ),默認為全局對象,即window .鑑於此,示例代碼可以以更易於理解的方式重寫為:

function a() {
    alert(this);
}
a.call(window);

此代碼清楚地表明警報將顯示與 window 等效的字符串 對象。

結論

Dmitry 整理了一個有趣的測驗,您可以從中實際學習 JavaScript 的一些奇怪的怪癖。我希望這篇文章能幫助每個人理解必要的細節,以弄清楚每段代碼在做什麼,更重要的是,為什麼要這樣做。同樣,我警告不要在工作面試中使用這類測驗,因為我認為它們在該領域沒有任何實際用途(如果你想知道我對面試前端工程師的看法,請參閱我以前的帖子)。


Tutorial JavaScript 教程
  1. 將數據傳遞給引導模式

  2. 帶有標誌的 Html 國家/地區列表

  3. HTML 中的 JavaScript 聯繫表單驗證 |示例代碼

  4. 新年。新挑戰 - #31Days31Videos

  5. Javascript - 轉儲所有全局變量

  6. 未捕獲的 DOMException:無法在“文檔”上執行“createElement”:提供的標籤名稱

  7. 一個時代的結束⌛

  1. 在你的 React 應用程序中添加下載的字體

  2. 介紹:用於 TypeScript 和 SWR 的 GraphQL Codegen 插件!

  3. 新的武士道帶反應樣式庫更新!

  4. 將 JavaScript 日期初始化為午夜的最佳方法是什麼?

  5. 使用 Stripe、Node 和 Express 進行支付處理

  6. 使用 Geolocation 和 Google Maps API

  7. 按值對對象屬性進行排序

  1. Web 開發項目——如何使用 HTML、CSS 和 JavaScript 製作登陸頁面

  2. 像我五歲一樣解釋 JavaScript 單元測試

  3. 如何將聯繫表格添加到靜態網站

  4. JavaScript 面試問題 #47:Object.setPrototypeOf 在 JS 中的工作原理