我們相信 JS - 最好的學習方法是建構/編碼和教學。我創造挑戰來幫助我的朋友學習 JavaScript,作為回報,它幫助我更深入地擁抱這門語言。隨意克隆、分岔和拉取。
function a ( x ) {
x ++ ;
return function ( ) {
console . log ( ++ x ) ;
} ;
}
a ( 1 ) ( ) ;
a ( 1 ) ( ) ;
a ( 1 ) ( ) ;
let x = a ( 1 ) ;
x ( ) ;
x ( ) ;
x ( ) ;
1, 2, 3
和1, 2, 3
3, 3, 3
和3, 4, 5
3, 3, 3
和1, 2, 3
1, 2, 3
和3, 3, 3
這個問題重新審視了閉包——JavaScript 中最令人困惑的概念之一。閉包允許我們建立一個stateful function
,並且這樣的函數可以存取其範圍之外的變數。簡而言之,閉包可以存取global
變數(作用域)、 father function
作用域和its
自己的作用域。
我們這裡有唯一一個正確答案,3,3,3 和 3,4,5,因為我們首先簡單地呼叫函數a()
。它的工作原理就像一個普通的函數,我們還沒有看到任何所謂的stateful
東西。在下面的程式碼中,我們宣告了一個變數x
,它儲存函數a(1)
的值,這就是為什麼我們得到 3. 4. 5 而不是 3, 3, 3。
這個陷阱給我一種 PHP 世界中static
變數的感覺。
function Name ( a , b ) {
this . a = a ;
this . b = b ;
}
const me = Name ( "Vuong" , "Nguyen" ) ;
console . log ( ! ( a . length - window . a . length ) ) ;
undefined
NaN
true
false
我們在控制台中得到了 true。棘手的部分是當我們從建構子 Name 建立一個物件但我們不使用new
keywork 時。這使得該變數a
全域變數並獲得值“Vuong”。請記住,它實際上是全域物件window
(在瀏覽器中)或nodejs中的global
的屬性。
然後我們得到a.length
~ 5 和window.a.length
~ 5,它們回傳 0。
想像一下當我們使用new
keywork 建立實例me
時會發生什麼事。這是一個有趣的詢問!
const x = function ( ... x ) {
let k = ( typeof x ) . length ;
let y = ( ) => "freetut" . length ;
let z = { y : y } ;
return k - z . y ( ) ;
} ;
console . log ( Boolean ( x ( ) ) ) ;
true
false
擴充運算子...x
可以幫助我們以陣列的形式取得函數中的參數。然而,在 Javascript 中,typeof 陣列會傳回「物件」而不是「陣列」。如果您來自 PHP,那是完全奇怪的。
也就是說,我們現在得到了傳回 6 的字串object
的長度。
請注意,函數 x() (以function express
或anonymous function
的形式(如果您來自 PHP) 在被呼叫時傳回 -1,並且在使用Boolean(-1)
轉換為 bool 時傳回 true 而不是 false。Chiclean Boolean(0)
返回 false。
( function js ( x ) {
const y = ( j ) => j * x ;
console . log ( y ( s ( ) ) ) ;
function s ( ) {
return j ( ) ;
}
function j ( ) {
return x ** x ;
}
} ) ( 3 ) ;
undefined
js()
函數無需呼叫即可自動執行,稱為IIFE(立即呼叫函數表達式)。注意到函數js
的參數x
實際上傳遞的值為 3。
此函數的傳回值是 y(s())),這表示呼叫其他三個函數y()
、 s()
和j()
因為函數s()
傳回j()
。
j() 傳回 3^3 = 27,因此 s() 回傳 27。
y(s()) 表示 y(27),回傳 27*3 = 81。
請注意,我們可以在函數實際聲明之前呼叫declare function
,但不能使用expression function
呼叫。
var tip = 100 ;
( function ( ) {
console . log ( "I have $" + husband ( ) ) ;
function wife ( ) {
return tip * 2 ;
}
function husband ( ) {
return wife ( ) / 2 ;
}
var tip = 10 ;
} ) ( ) ;
我們這裡有一個 IIFE(立即呼叫函數表達式)。這意味著我們不必呼叫它,但聲明時它會自動執行。流程如下:丈夫()返回妻子()/2,妻子()返回小費*2。
我們可能會認為tip = 100,因為當用var
關鍵字宣告時它是一個全域變數。然而,它實際上是undefined
,因為我們在函數內部也有var tip = 10
。由於變數tip
使用預設值undefined
提升,最終結果將是D。我們知道當我們嘗試除以2或2的倍數時, undefined
會傳回NaN。
如果我們不重新聲明var tip = 10;
在函數的最後,我們一定會得到B。
JS 很有趣吧?
const js = { language : "loosely type" , label : "difficult" } ;
const edu = { ... js , level : "PhD" } ;
const newbie = edu ;
delete edu . language ;
console . log ( Object . keys ( newbie ) . length ) ;
此挑戰修改了 ES6 關於spread operator ...
展開運算子對於檢索函數中的參數、在 JavaScript 中unite
或combine
物件和陣列非常有用。 PHP也有這個功能。
在變數edu
中,我們使用...js
(此處為擴充運算子)將兩個物件合併為一個。它的工作方式與數組相同。
然後我們宣告另一個名為newbie
變數。重要提示:透過這樣宣告變量,兩個變數都指向記憶體中的相同位置。我們可能知道 PHP 中的$a = &$b
之類的東西,它讓兩個變數以相同的方式運作。我們可能已經知道這種情況下pass by reference
。
然後我們有 2,因為edu.language
被刪除。兩個物件現在都只有兩個元素。
現在是時候考慮在 JS 中處理淺物件還是深層物件了。
var candidate = {
name : "Vuong" ,
age : 30 ,
} ;
var job = {
frontend : "Vuejs or Reactjs" ,
backend : "PHP and Laravel" ,
city : "Auckland" ,
} ;
class Combine {
static get ( ) {
return Object . assign ( candidate , job ) ;
}
static count ( ) {
return Object . keys ( this . get ( ) ) . length ;
}
}
console . log ( Combine . count ( ) ) ;
內建方法Object.assign(candidate, job)
將candidate
和job
這兩個物件合併為一個物件。然後Object.keys
方法計算物件中key
的數量。
請注意, get()
和count()
兩個方法被定義為static
,因此需要使用Class.staticmethod()
語法靜態呼叫它們。然後最終物件得到 5 個元素。
var x = 1 ;
( ( ) => {
x += 1 ;
++ x ;
} ) ( ) ;
( ( y ) => {
x += y ;
x = x % y ;
} ) ( 2 ) ;
( ( ) => ( x += x ) ) ( ) ;
( ( ) => ( x *= x ) ) ( ) ;
console . log ( x ) ;
最初x
被宣告為值 1。先x
變成 2,然後變成 3。
在第二個 IIFE 函數中, x = x + y
則當前值為5%2
。
在第三個和第四個 IIFE 函數中,我們得到 2 x = x + x
,然後得到 4 x = x * x
。這不僅僅是簡單的。
$ var = 10 ;
$ f = function ( $ let ) use ( $ var ) {
return ++ $ let + $ var ;
};
$ var = 15 ;
echo $ f ( 10 );
var x = 10 ;
const f = ( l ) => ++ l + x ;
x = 15 ;
console . log ( f ( 10 ) ) ;
這個問題說明了 PHP 和 JavaScript 在處理閉包時的差異。在第一個片段中,我們使用關鍵字use
來宣告一個閉包。 PHP 中的閉包只是一個匿名函數,資料使用關鍵字use
傳遞給函數。否則,當我們不使用關鍵字use
時,它被稱為lambda
。您可以在此處查看程式碼片段的結果 https://3v4l.org/PSeMY。 PHP closure
僅在定義閉包之前接受變數的值,無論在何處呼叫。因此, $var
是 10 而不是 15。
相反,當變數傳遞給匿名函數時,JavaScript 對待變數的方式有點不同。我們不必在這裡使用關鍵字use
將變數傳遞給閉包。第二個片段中的變數x
在呼叫閉包之前更新,然後我們得到 26。
請注意,在 PHP 7.4 中,我們有了箭頭函數,因此我們不必使用關鍵字use
將變數傳遞給函數。在 PHP 中呼叫函數內部global
變數的另一種方法是使用關鍵字global
或使用內建 GLOBAL 變數 $GLOBALS。
let x = { } ;
let y = { } ;
let z = x ;
console . log ( x == y ) ;
console . log ( x === y ) ;
console . log ( x == z ) ;
console . log ( x === z ) ;
從技術上講, x
和y
具有相同的值。兩者都是空物體。但是,我們不使用該值來比較物件。
z
和x
是引用同一記憶體位置的兩個物件。在 JavaScript 中,陣列和物件是透過reference
傳遞的。因此, x
和z
在比較時傳回 true。
console . log ( "hello" ) ;
setTimeout ( ( ) => console . log ( "world" ) , 0 ) ;
console . log ( "hi" ) ;
鑑於函數setTimeout()在跳回堆疊之前會保留在task queue
中stack,
會先列印“hello”和“hi”,則A是錯誤的。答案C和D也是如此。
無論您為setTimeout()
函數設定多少秒,它都會在同步程式碼之後執行。因此,我們將首先得到“hello”,因為它首先被放入呼叫堆疊中。雖然setTimeout()
然後被放入呼叫堆疊,但它隨後會卸載到 Web API(或 Node API),然後在清除其他同步程式碼時被呼叫。這意味著我們然後得到“hi”,最後得到“world”。
所以B是正確答案。
感謝:@kaitoubg(voz)您關於timeout throttled
的建議,我決定稍微改變一下問題。它將確保讀者不會感到困惑,因為在其他瀏覽器或環境上測試時,前面的程式碼可能會產生不同的結果。問題的重點是關於使用setTimeout.
。
String . prototype . lengthy = ( ) => {
console . log ( "hello" ) ;
} ;
let x = { name : "Vuong" } ;
delete x ;
x . name . lengthy ( ) ;
String.prototype.someThing = function () {}
是為String
定義新的內建方法的常用方法。我們可以對Array
、 Object
或FunctionName
做同樣的事情,其中 FunctionName 是我們自己設計的函數。
認識到"string".lengthy()
總是返回hello
並不具有挑戰性。然而,棘手的部分在於delete object
,我們可能認為這個表達式將完全刪除該對象。情況並非如此,因為delete
僅用於刪除物件的屬性。它不會刪除該物件。然後我們得到hello
而不是ReferenceError
。
請注意,如果我們聲明 object 時沒有let, const
或var
,那麼我們就有了一個全域物件。 delete objectName
然後回傳true
。否則,它總是回傳false
。
let x = { } ;
x . __proto__ . hi = 10 ;
Object . prototype . hi = ++ x . hi ;
console . log ( x . hi + Object . keys ( x ) . length ) ;
首先我們有一個空物件x
,然後我們使用x.__proto__.hi
為 x 新增另一個屬性hi
。請注意,這相當於Object.prototype.hi = 10
,並且我們將屬性hi
加入到father
物件Object
中。這意味著每個物件都會繼承這個屬性。 hi
財產成為共享財產。假設現在我們宣告一個新對象,例如let y = {}
, y
現在有一個從father
Object
繼承的屬性hi
。簡單地說x.__proto__ === Object.prototype
回傳true
。
然後我們用x.hi
值 11 覆寫x
hi
。
更新(2021 年 7 月 27 日)。如果你寫Object.prototype.hi = 11;
而不是Object.prototype.hi = ++x.hi;
如同上面程式碼中所寫,那麼Object.keys(x)
將會傳回一個空數組,因為Object.keys(object)
只傳回物件本身的屬性,而不回傳繼承的屬性。這意味著最終結果將是 11 而不是 12。 will create a property for the object
,然後 `Object.keys(x)` 為我們提供陣列 `["hi"]`。
然而,如果您執行console.log(x.hasOwnProperty("hi"))
它仍然傳回false
。順便說一句,當您故意為 x 新增屬性(例如x.test = "testing"
時, console.log(x.hasOwnProperty("test"))
傳回true
。
const array = ( a ) => {
let length = a . length ;
delete a [ length - 1 ] ;
return a . length ;
} ;
console . log ( array ( [ 1 , 2 , 3 , 4 ] ) ) ;
const object = ( obj ) => {
let key = Object . keys ( obj ) ;
let length = key . length ;
delete obj [ key [ length - 1 ] ] ;
return Object . keys ( obj ) . length ;
} ;
console . log ( object ( { 1 : 2 , 2 : 3 , 3 : 4 , 4 : 5 } ) ) ;
const setPropNull = ( obj ) => {
let key = Object . keys ( obj ) ;
let length = key . length ;
obj [ key [ length - 1 ] ] = null ;
return Object . keys ( obj ) . length ;
} ;
console . log ( setPropNull ( { 1 : 2 , 2 : 3 , 3 : 4 , 4 : 5 } ) ) ;
本問題檢視 JavaScript 中delete
運算子的工作原理。簡而言之,當我們編寫delete someObject
或delete someArray
時,它什麼都不做。儘管如此,當編寫諸如delete someObject.someProperty
之類的內容時,它仍然會完全刪除並刪除物件的屬性。對於數組,當我們編寫delete someArray[keyNumber]
時,它只刪除index
的value
,保持index
不變,並且新value
現在設定為undefined
。因此,在第一個程式碼片段中,我們得到了原始陣列中的(長度)4 個元素,但在呼叫函數object() 時傳遞的物件中只剩下3 個屬性,如第二個片段所示。
第三個片段給了我們 4 ,因為將物件的屬性宣告為null
或undefined
並不能完全刪除該屬性。鑰匙完好無損。所以物件的長度是不可變的。
對於熟悉 PHP 的人來說,我們有unset($someArray[index])
來刪除陣列元素,包括鍵和值。當print_r
數組時,我們可能看不到未設定的鍵和值。但是,當我們推送(使用array_push($someArray, $someValue)
)該數組中的新元素時,我們可能會看到先前的鍵仍然保留,但沒有值並且沒有顯示。這是你應該注意的事情。看看 https://3v4l.org/7C3Nf
var a = [ 1 , 2 , 3 ] ;
var b = [ 1 , 2 , 3 ] ;
var c = [ 1 , 2 , 3 ] ;
var d = c ;
var e = [ 1 , 2 , 3 ] ;
var f = e . slice ( ) ;
console . log ( a === b ) ;
console . log ( c === d ) ;
console . log ( e === f ) ;
a
和b
傳回 false,因為即使值相同,它們也指向不同的記憶體位置。如果你來自 PHP 世界,那麼當我們比較 value 或 value + type 時,顯然它會回傳 true。請參閱:https://3v4l.org/IjaOs。
在 JavaScript 中,對於array
和object
值是透過參考傳遞的。因此,在第二種情況下, d
是c
的副本,但它們都指向相同的記憶體位置。 c
中的所有變化都會導致d
的變化。在 PHP 中,我們可能有$a = &$b;
,以類似的方式工作。
第三個提示我們使用slice()
方法在 JavaScript 中複製陣列。現在我們有了f
,它是e
的副本,但它們指向不同的記憶體位置,因此它們有不同的「生命」。當比較它們時,我們相應地得到false
。
var languages = {
name : [ "elixir" , "golang" , "js" , "php" , { name : "feature" } ] ,
feature : "awesome" ,
} ;
let flag = languages . hasOwnProperty ( Object . values ( languages ) [ 0 ] [ 4 ] . name ) ;
( ( ) => {
if ( flag !== false ) {
console . log (
Object . getOwnPropertyNames ( languages ) [ 0 ] . length <<
Object . keys ( languages ) [ 0 ] . length
) ;
} else {
console . log (
Object . getOwnPropertyNames ( languages ) [ 1 ] . length <<
Object . keys ( languages ) [ 1 ] . length
) ;
}
} ) ( ) ;
這個程式碼片段非常棘手,因為它有幾個不同的內建方法來處理JavaScript
中的物件。例如,儘管Object.keys
和Object.getOwnPropertyNames
非常相似,但它們都被使用,只是後者可以傳回不可枚舉的屬性。您可能想看看這個完整的書面參考https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames
Object.values
和Object.keys
分別傳回物件的屬性值和屬性名稱。這不是什麼新鮮事。 object.hasOwnProperty('propertyName')
傳回boolean
確認屬性是否存在。
我們有 true flag
,因為Object.values(languages)[0][4].name
回傳feature
,這也是屬性的名稱。
那麼我們在if-else
流程中有 4 << 4 回傳位元值,相當於4*2^4
~ 4*16
~ 64。
var player = {
name : "Ronaldo" ,
age : 34 ,
getAge : function ( ) {
return ++ this . age - this . name . length ;
} ,
} ;
function score ( greeting , year ) {
console . log (
greeting + " " + this . name + `! You were born in ${ year - this . getAge ( ) } `
) ;
}
window . window . window . score . call ( window .