我们相信 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。!0 返回 true。
想象一下当我们使用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
的长度。zy() 仅返回字符串“freetut”的长度 (7)。
请注意,函数 x()(以function express
或anonymous function
的形式(如果您来自 PHP)在被调用时返回 -1,并且在使用Boolean(-1)
转换为 bool 时返回 true 而不是 false。 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。在第一个 IIFE 函数中,有两个操作。首先x
变为 2,然后变为 3。
在第二个 IIFE 函数中, x = x + y
则当前值为 5。在第二个操作中,它仅返回 1,因为它经历了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
。
然后我们用新值 11 覆盖属性hi
。最后我们有 11 + 1 = x
有一个属性, x.hi
返回 11。
更新(2021 年 7 月 27 日)。如果你写Object.prototype.hi = 11;
而不是Object.prototype.hi = ++x.hi;
正如上面代码中所写,那么Object.keys(x)
将返回一个空数组,因为Object.keys(object)
只返回对象本身的属性,而不返回继承的属性。这意味着最终结果将是 11 而不是 12。出于某种原因,代码``Object.prototype.hi = ++x.hi; 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 .