回答 Soshnikov 的測驗
JavaScript 測驗最近肯定很流行。最新添加的是 Dmitry A. Soshnikov 的作品,被親切地稱為 The quiz。我必須承認,這個測驗有一些我見過的最令人費解的 JavaScript 示例。我喜歡他的測驗的一點是,每段代碼或多或少都依賴於一個 JavaScript 概念,這就是為什麼我要再花一周時間解釋另一個測驗的原因。
問題 #1
typeof typeof(null)
這可能是所有問題中最簡單的。只要你有 typeof typeof
,結果總是“字符串”。為什麼?因為 typeof
運算符總是返回一個字符串值(在這種情況下,它返回 typeof(null)
的“object” )。
問題 #2
以下檢查的算法是否完全等價?
typeof foo == 'undefined'
和
typeof foo === 'undefined'
測驗聲稱答案是“是”,儘管正如 Kangax 指出的那樣,這兩種操作的算法實際上是不同的。問這個問題的更好方法是,“這兩項檢查的最終結果是否完全等價?”答案是肯定的,因為您最終會在每次比較中比較兩個字符串。比較兩個字符串時,==操作符不做任何類型強制,所以兩個比較總是返回相同的結果。
問題 #3
結果是什麼:
100['toString']['length']
這其中有幾個很好的欺騙。數字最終被 Number
包裹 使用時輸入,Number
類型有一個 toString()
方法。但是,您實際上並沒有調用 toString()
在這種情況下,您實際上是在訪問 length
toString()
的屬性 方法。像這樣看代碼更有意義:
100.toString.length
length
函數的屬性表示需要多少個名稱參數。 toString()
方法接受一個參數,它是輸出數字的字符串表示形式的基數(例如 toString(2)
將數字的二進製表示形式輸出為字符串和 toString(16)
將數字的十六進製表示形式輸出為字符串)。
所以這個問題的答案是1。
問題 #4
結果是什麼:
var a = (1,5 - 1) * 2
這是第一個取決於您對逗號運算符如何工作的了解的問題。簡單來說:當表達式包含一個或多個逗號時,表達式的值等於最後一個值。例如 (1,2,3,4)
的值 是 4 和 ("hello", "world")
的值 是“世界”。逗號操作符的最佳用途是定義多個變量,絕對不推薦這裡的示例用法。
有了這些知識,很明顯這個問題的答案是 8。那是因為 (1,5-1)
被評估為 (1,4)
所以最終的值為 4。我相信你可以從那裡得到它。
問題 #5
結果是什麼:
var x = 10;
var foo = {
x: 20,
bar: function () {
var x = 30;
return this.x;
}
};
console.log(
foo.bar(),
(foo.bar)(),
(foo.bar = foo.bar)(),
(foo.bar, foo.bar)()
);
此代碼在控制台上輸出四個值。真正的問題是這四個值是什麼。應該很明顯,第一個值是 20,因為 foo.bar()
訪問 this.x
在 foo
,即20。下一部分,(foo.bar)()
作用與 foo.bar()
完全相同 .包裝 foo.bar
in parens 不會改變它的評估方式。這也輸出 20。
接下來是棘手的部分。賦值表達式的返回值總是右邊的表達式。將函數分配給一個位置,即使它與它來自的位置相同,也會導致整個表達式具有該函數的值。重要的信息是該函數現在沒有與之關聯的上下文對象,因此 (foo.bar = foo.bar)()
就像 foo.bar.call()
一樣執行 .當然,任何在對像上下文之外調用的函數都會在全局上下文中執行,所以 this.x
現在是 10。因此,第三部分輸出 10。
第四種變體輸出與第三種相同的結果。您再次遇到逗號運算符。請記住 foo.bar
在這部分代碼中表示一個指向函數的指針,逗號運算符在被調用之前獲取該函數的值。這輸出與上一節相同的值,原因相同:使用逗號運算符意味著函數是上下文無關的,並且在全局範圍內執行。
所以你的總體答案:20 20 10 10
.
問題 #6
結果是什麼:
function f(x, y) {
x = 10;
console.log(
arguments[0],
arguments[1]
);
}
f();
此函數有兩個命名參數,但在調用該函數時均未提供。您應該知道這些命名參數的值將是 undefined
在這種情況下,輸出 arguments[1]
顯然也應該是 undefined
.那麼,唯一的問題是 arguments[0]
的值 .這實際上測試了 Baranovskiy 的第四個問題測試的反面。在他的測試中,Barnovskiy 更改了 arguments
中的一個值 對象並且您看到相應的命名參數的值也發生了變化(有關更多信息,請參閱我的文章)。然而,反之則不然。
更改命名參數的值不會自動更改 arguments
中的相應值 .正如我前面提到的帖子中提到的,arguments
對象和命名參數不共享內存空間。當對 arguments
進行更改時 , 該值被複製 到命名參數。它不能以另一種方式工作。命名參數並不比局部變量更特殊,因此更改其值不會影響 arguments
目的。所以,arguments[0]
仍然是 undefined
代碼的輸出是 undefined undefined
.
問題 #7
結果是什麼:
var
b = 10,
c = (
20,
function (x) { return x + 100},
function () { return arguments[0]}
);
a = b + c
({x: 10}).x
要回答這個問題,您只需要了解兩個概念。首先是逗號運算符的工作原理,您現在應該是專家了。 c
的值 是函數function(){ return arguments[0];}
,它只返回傳入的第一個參數。
您需要知道的第二件事是自動分號插入的工作原理。由於代碼的格式化方式,您可能傾向於認為會在 a = b + c
之後插入分號 .請記住 c
是一個函數,下一個非空白字符是 (
.在這種情況下,空格被忽略了,所以最後一行實際上是:
a = b + c({x: 10}).x
由於 c
中包含的函數 只是簡單地把傳入的參數傳回去,這個表達式的結果在邏輯上等價於:
a = b + ({x: 10}).x
這真的只是:
a = b + 10
這使得 a
等於20,就是代碼的最終值。
問題 #8
結果是什麼:
1..z
另一個偷偷摸摸的問題。乍一看,這看起來像是一個明顯的語法錯誤。但是,由於解析此文本的方式,此處沒有語法錯誤。請記住,前面的數字最終被 Number
包裹 訪問時鍵入,這會生成一個臨時對象。 z
在這種情況下是試圖訪問一個屬性,這意味著代碼可以寫成:
(1.)["z"]
那麼什麼是1.
?它實際上是 JavaScript 中的有效浮點數。不幸的是,JavaScript 允許數字尾隨小數點,所以你可以有 1
或 1.
或 1.0
取決於您編寫代碼的感覺。尾隨小數點被認為是不好的做法,並且在通過 JSLint 運行代碼時會發出警告。
真的,這個問題是在問你屬性 z
的值 在這個代表 1.
的數字對像上 .由於 Number
上沒有這樣的屬性 對象,值為 undefined
.
問題 #9
結果是什麼:
({
x: 10,
foo: function () {
function bar() {
console.log(x);
console.log(y);
console.log(this.x);
}
with (this) {
var x = 20;
var y = 30;
bar.call(this);
}
}
}).foo();
另一個測試你對 with
理解的棘手問題 陳述。要正確回答這個問題,實際上只有一個概念需要掌握,這就是 var
with
中的語句 聲明確實如此。基本上有三種情況:
- 被聲明的變量不作為上下文對象的屬性存在(在這種情況下,
this
) 並且該變量不作為包含函數的局部變量存在 (foo()
)。在這種情況下,變量聲明為包含函數創建了一個新的局部變量。這是由於var
語句提升(在我之前的帖子中也有描述)。 - 被聲明的變量作為上下文對象的屬性存在。這裡實際上發生了兩件事。一、
var
語句被提升並定義了一個新的局部變量。但是,初始化語句保留在同一位置,因此將值分配給具有相同名稱的對象屬性。 - 被聲明的變量作為包含函數的局部變量存在。在這種情況下,只需為現有變量分配給定值。
有了這些知識,您就可以確定輸出的三個值。一、x
已聲明但從未賦值。由於 var
吊裝,with
聲明實際上與此相同:
var x;
var y;
with (this) {
x = 20;
y = 30;
bar.call(this);
}
所以 var x = 20;
被映射到 this.x = 20;
with
內部 自 x
以來的聲明 作為上下文對象的屬性存在 this
.這意味著 this.x
局部變量 x
從 10 變為 20 永遠不會被賦值。
函數 bar()
是 foo()
內部的閉包 ,因此可以訪問所有 foo()
的局部變量(x
和 y
)。當 console.log(x)
被執行,它輸出 undefined
因為變量 x
從未初始化(所有變量都被賦值為 undefined
聲明時)。
接下來,y
賦值為 30,在 foo()
中創建一個局部變量 .自 bar()
是一個閉包,它可以訪問 foo()
的所有局部變量 是的。
最後一部分,console.log(this.x);
輸出 20,因為函數是在對象的上下文中調用的。
所以這就是你的答案:undefined
, 30
, 20
.
問題 #10
結果是什麼:
foreach (k in {a: 10, b: 20})
{
// ...
}
foreach-in
之後的另一個棘手問題 在 ECMA-262 中沒有定義。有一個for-each-in
在 ECMA-357(ECMAScript for XML)中定義的語句,並且在該規範中,它用於迭代數組中的值。所以這裡的訣竅是,對 JavaScript 了解太多實際上可能會導致錯誤的答案。
由於沒有foreach-in
語句在任何地方實現,這應該會導致錯誤。你可能認為它會導致語法錯誤,但它不會因為 foreach
是一個有效的標識符(它不是關鍵字並且遵循標識符格式),因此 JavaScript 引擎會查找對 foreach
的引用 並且,找不到它,拋出一個 ReferenceError
.
這個問題的“正確”答案是一個爭論點。我認為答案應該是“總是 ReferenceError”,因為如果你在示例中只運行這段代碼,這就是你得到的。作者說答案實際上是“ReferenceError or possible no error”,因為如果 foreach()
和 k
都是之前定義的,這不會引發錯誤。由於所有其他問題都僅取決於所提供的代碼,因此我認為要求人們做這件事並不公平。但是,為了盡可能完整,我們假設代碼是這樣的:
function foreach(){
//do something
}
var k = "a";
foreach (k in {a: 10, b: 20})
{
// ...
}
使用此代碼,您將不會收到任何錯誤。為什麼?因為foreach(k in {a: 10, b: 20})
計算結果為 foreach(true)
因為屬性“a”確實存在於給定的對象字面量中。但是剩下的花括號呢?
這是自動分號插入的另一個技巧。代碼的格式看起來像大括號代表語句的主體,然而,它們實際上代表一個空的對象字面量。代碼解釋為:
function foreach(){
//do something
}
var k = "a";
foreach (k in {a: 10, b: 20});
{
// ...
};
請注意,在左大括號之前和右大括號之後插入分號。插入分號後,它們實際上是兩個獨立且不相關的語句。未分配給變量的對象字面量可能看起來很奇怪,但它仍然是有效的語句,就像以下任何一種:
"hello world";
5;
true;
語句不必執行函數或賦值,它可能只包含一個值。
結束
我真的很喜歡這個測驗,因為它的難度很高。希望現在您能更好地理解逗號運算符的工作原理以及變量聲明提升的一些語義。