思维JavaScript

    技术2022-07-11  132

    I was teaching a JavaScript workshop the other day and one of the attendees asked me a JS brain teaser during the lunch break that really got me thinking. His claim was that he ran across it accidentally, but I'm a bit skeptical; it might just have been an intentional WTF trick!

    前几天我在教一个JavaScript讲习班,一个与会者在午休时间问我一个JS脑筋急转弯,这确实让我思考。 他的说法是他不小心碰到了它,但是我有点怀疑。 这可能只是故意的WTF技巧!

    Anyway, I got it wrong the first couple of times I was trying to analyze it, and I had to run the code through a parser then consult the spec (and a JS guru!) to figure out what was going on. Since I learned some things in the process, I figured I'd share it with you.

    无论如何,在尝试分析它的前几次我都错了,我不得不通过解析器运行代码,然后查阅规范 (和JS专家!)以了解发生了什么。 由于我在此过程中学到了一些东西,因此我想与您分享。

    Not that I expect you'll ever intentionally write (or read, hopefully!) code like this, but being able to think more like JavaScript does always help you write better code.

    并不是说我希望您会像这样有意编写(或希望阅读!)代码,但是能够像JavaScript那样思考,确实可以帮助您编写更好的代码。

    设置 (The Setup)

    The question posed to me was this: why does this first line "work" (compiles/runs) but the second line gives an error?

    给我的问题是:为什么第一行“工作”(编译/运行),但第二行却给出错误?

    [[]][0]++; []++;

    The thinking behind this riddle is that [[]][0] should be the same as [], so either both should work or both should fail.

    这个谜语背后的想法是[[]][0]应该与[]相同,所以要么两者都应该起作用,要么都应该失败。

    My first answer, after thinking about it for a few moments, was that these should both fail, but for different reasons. I was incorrect on several accounts. Indeed, the first one is valid (even if quite silly).

    在考虑了片刻之后,我的第一个答案是,它们都应该失败,但是出于不同的原因。 我在几个帐户上都不正确。 确实,第一个有效(即使很傻)。

    I was wrong despite the fact that I was trying to think like JavaScript does. Unfortunately, my thinking was messed up. No matter how much you know, you can still easily realize you don't know stuff.

    尽管我试图像JavaScript一样思考,但我还是错了。 不幸的是,我的想法搞砸了。 不管您知道多少,您仍然可以轻松地意识到自己不了解东西。

    That's exactly why I challenge people to admit: "You Don't Know JS"; none of us ever fully knows something as complex as a programming language like JS. We learn some parts, and then learn more, and keep on learning. It's a forever process, not a destination.

    这就是为什么我要挑战人们承认的原因: “您不懂JS” ; 我们没有人完全了解像JS这样的编程语言那么复杂的东西。 我们学习了一些部分,然后学习更多,并继续学习。 这是一个永远的过程,而不是目的地。

    我的错 (My Mistakes)

    First, I saw the two ++ operator usages, and my instinct was that these will both fail because the unary postfix ++, like in x++, is mostly equivalent to x = x + 1, which means the x (whatever it is) has to be valid as something that can show up on the left-hand side of an = assignment.

    首先,我看到了两种++运算符的用法,我的直觉是这两种用法都会失败,因为一元后缀++就像x++中的大多数等同于x = x + 1 ,这意味着x (无论是什么)必须有效,因为它可以显示在=赋值的左侧。

    Actually, that last part is true, I was right about that, but for the wrong reasons.

    实际上,最后一部分是正确的,我对此是正确的,但出于错误的原因。

    What I wrongly thought was that x++ is sorta like x = x + 1; in that thinking, []++ being [] = [] + 1 would be invalid. While that definitely looks weird, it's actually quite OK. In ES6, the [] = .. part is valid array destructuring.

    我错误地认为x++有点像x = x + 1 ; 按照这种想法, []++为[] = [] + 1将无效。 尽管这看起来确实很奇怪,但实际上还可以。 在ES6中, [] = ..部分是有效的数组解构。

    Thinking of x++ as x = x + 1 is misguided, lazy thinking, and I shouldn't be surprised that it led me astray.

    将x++视为x = x + 1是一种误导,懒惰的思考,它使我误入歧途,我应该不会感到惊讶。

    Moreover, I was thinking of the first line all wrong, as well. What I thought was, the [[]] is making an array (the outer [ ]), and then the inner [] is trying to be a property access, which means it gets stringified (to ""), so it's like [""]. This is nonsense. I dunno why my brain was messed up here.

    而且,我也想到第一行也是错误的。 我想的是, [[]]正在创建一个数组(外部[ ] ),然后内部[]试图成为属性访问,这意味着它已被字符串化(为"" ),所以就像[""] 。 这是无稽之谈。 我不知道为什么我的大脑在这里搞砸了。

    Of course, for the outer [ ] to be an array being accessed, it'd need to be like x[[]] where x is the thing being accessed, not just [[]] by itself. Anyway, thinking all wrong. Silly me.

    当然,要使外部[ ]成为要访问的数组,就需要像x[[]] ,其中x是要访问的东西,而不仅仅是[[]]本身。 无论如何,都想错了。 傻我

    纠正思维 (Corrected Thinking)

    Let's start with the easiest correction to thinking. Why is []++ invalid?

    让我们从最简单的纠正思维开始。 为什么是 []++无效?

    To get the real answer, we should go to the official source of authority on such topics, the spec!

    要获得真正的答案,我们应该去找有关此类主题的官方权威资料, 即规范 !

    In spec-speak, the ++ in x++ is a type of "Update Expression" called the "Postfix Increment Operator". It requires the x part to be a valid "Left-Hand Side Expression" – in a loose sense, an expression that is valid on the left-hand side of an =. Actually, the more accurate way to think of it is not left-hand side of an =, but rather, valid target of an assignment.

    在SPEC-说话, ++在x++是一种类型的“更新式”称为“后缀递增运算符” 。 它需要x部分是一个有效的“左手侧表达式” -在一个宽泛的意义,也就是上的左侧有效的表达式= 。 实际上,更准确地思考它的方法不是=左侧,而是赋值的有效目标。

    Looking at the list of valid expressions that can be targets of an assignment, we see things like "Primary Expression" and "Member Expression", among others.

    查看可以作为分配目标的有效表达式的列表,我们看到诸如“主表达式”和“成员表达式”之类的东西。

    If you look into Primary Expression, you find that an "Array Literal" (like our []!) is valid, at least from a syntax perspective.

    如果查看Primary Expression ,至少从语法角度来看,您会发现“ Array Literal” (如我们的[] !)是有效的。

    So, wait! [] can be a left-hand side expression, and is thus valid to show up next to a ++. Hmmm. Why then does []++ give an error?

    所以,等等! [] 可以是左侧表达式,因此可以有效地显示在++旁边。 嗯 为什么[]++给出错误?

    What you might miss, which I did, is: it's not a SyntaxError at all! It's a runtime error called ReferenceError.

    我可能会错过的是:根本不是SyntaxError ! 这是一个称为ReferenceError的运行时错误。

    Occassionally, I have people ask me about another perplexing – and totally related! – result in JS, that this code is valid syntax (but still fails at runtime):

    有时候,我有人问我另一个困惑-完全相关! –导致JS,此代码是有效语法(但在运行时仍然失败):

    2 = 3;

    Obviously, a number literal shouldn't be something we can assign to. That makes no sense.

    显然,数字文字不应该是我们可以分配的东西。 这是没有意义的。

    But it's not invalid syntax. It's just invalid runtime logic.

    但这不是无效的语法。 这只是无效的运行时逻辑。

    So what part of the spec makes 2 = 3 fail? The same reason that 2 = 3 fails is the same reason that []++ fails.

    那么规范的哪一部分会使2 = 3失效? 2 = 3失败的原因与[]++失败的原因相同。

    Both of these operations use an abstract algorithm in the spec called "PutValue". Step 3 of this algorithm says:

    在规范中,这两个操作都使用一种称为“ PutValue”的抽象算法。 此算法的第3步说:

    If Type(V) is not Reference, throw a ReferenceError exception. 如果Type(V)不是Reference,则抛出ReferenceError异常。

    Reference is a special specification type that refers to any kind of expression that represents an area in memory where some value could be assigned. In other words, to be a valid target, you have to be a Reference.

    Reference是一种特殊的规范类型 ,它引用表示可以在其中分配某些值的内存区域的任何类型的表达式。 换句话说,要成为有效的目标,您必须是Reference 。

    Clearly, 2 and [] are not References, so that's why at runtime, you get a ReferenceError; they are not valid assignment targets.

    显然, 2和[]不是Reference ,所以这就是为什么在运行时会出现ReferenceError ; 它们不是有效的分配目标。

    但是关于...? (But What About...?)

    Don't worry, I haven't forgotten the first line of the snippet, which works. Remember, my thinking was all wrong about it, so I've got some correcting to do.

    不用担心,我没有忘记代码段的第一行,该行有效。 记住,我的想法全都错了,所以我需要做一些纠正。

    [[]] by itself is not an array access at all. It's just an array value that happens to contain another array value as its only contents. Think of it like this:

    [[]]本身根本不是数组访问。 它只是一个数组值,恰好包含另一个数组值作为其唯一内容。 这样想:

    var a = []; var b = [a]; b; // [[]]

    See?

    看到?

    So now, [[]][0], what is that all about? Again, let's break it down with some temporary variables.

    所以现在[[]][0]到底是什么? 同样,让我们​​用一些临时变量来分解它。

    var a = []; var b = [a]; var c = b[0]; c; // [] -- aka, `a`!

    So the original setup premise is correct. [[]][0] is kinda the same as just [] itself.

    因此,原始设置前提是正确的。 [[]][0]有点像[]本身。

    Back to that original question: why then does line 1 work but line 2 doesn't?

    回到原来的问题:为什么第1行起作用,而第2行却不起作用?

    As we observed earlier, the "Update Expression" requires a "LeftHandSideExpression". One of the valid types of those expressions is "Member Expression", like [0] in x[0] – that's a member expression!

    正如我们前面观察到的, “更新表达式”需要一个“ LeftHandSideExpression” 。 一个有效类型的表达式是“会员表达”一样, [0]在x[0] -这是一个成员的表达!

    Look familiar? [[]][0] is a member expression.

    看起来熟悉? [[]][0]是成员表达式。

    So, we're good on syntax. [[]][0]++ is valid.

    因此,我们擅长语法。 [[]][0]++有效。

    But, wait! Wait! Wait!

    可是等等! 等待! 等待!

    If [] is not a Reference, how could [[]][0] – which results in just [], remember! – possibly be considered a Reference so that PutValue(..) (described above) doesn't throw an error?

    如果[]不是Reference , [[]][0]怎么可能–仅导致[] ,请记住! –可能被认为是一个Reference以便PutValue(..) (如上所述)不会引发错误?

    This is where things get just a teeny bit tricky. Hat tip to my friend Allen-Wirfs Brock, former editor of the JS spec, for helping me connect the dots.

    在这里,事情变得有些棘手。 向我的朋友Allen-Wirfs Brock致谢 ,他是JS规范的前编辑,以帮助我联系各个方面。

    The result of a member expression is not the value itself ([]), but rather a Reference to that value – see Step 8 here. So in fact, the [0] access is giving us a reference to the 0th position of that outer array, rather than giving us the actual value in that position.

    成员表达式的结果不是值本身( [] ),而是对该值的Reference –请参见此处的步骤8 。 因此,实际上, [0]访问为我们提供了对该外部数组的第0个位置的引用 ,而不是为我们提供了该位置的实际值。

    And that's why it's valid to use [[]][0] as a left hand side expression: it actually is a Reference after all!

    这就是为什么将[[]][0]用作左侧表达式是有效的:毕竟它实际上是一个Reference !

    As a matter of fact, the ++ does actually update the value, as we can see if we were to capture these values and inspect them later:

    实际上, ++确实会更新该值,因为我们可以看到是否要捕获这些值并在以后检查它们:

    var a = [[]]; a[0]++; a; // [1]

    The a[0] member expression gives us the [] array, and the ++ as a mathematical expression will coerce it to a primitive number, which is first "" and then 0. The ++ then increments that to 1 and assigns it to a[0]. It's as if a[0]++ was actually a[0] = a[0] + 1.

    a[0]成员表达式为我们提供了[]数组,而作为数学表达式的++会将其强制为一个原始数字,该数字为第一个"" ,然后为0 。 然后, ++将其递增为1并将其分配给a[0] 。 好像a[0]++实际上是a[0] = a[0] + 1 。

    A little side note: if you run [[]][0]++ in the console of your browser, it will report 0, not 1 or [1]. Why?

    注意:如果在浏览器的控制台中运行[[]][0]++ ,它将报告0 ,而不是1或[1] 。 为什么?

    Because ++ returns the "original" value (well, after coercion, anyway – see step 2 & 5 here), not the updated value. So the 0 comes back, and the 1 gets put into the array via that Reference.

    因为++返回的是“原始”值(无论如何,在强制转换之后,请参见此处的步骤2和5 ),而不是更新后的值。 所以0返回,而1通过该Reference放入数组。

    Of course, if you don't keep the outer array in a variable like we did, that update is moot since the value itself goes away. But it was updated, nonetheless. Reference. Cool!

    当然,如果您不像我们一样将外部数组保留在变量中,则由于值本身会消失,因此该更新是没有意义的。 但是尽管如此,它还是被更新了。 Reference 。 凉!

    后校正 (Post-Corrected)

    I don't know if you appreciate JS or feel frustrated by all these nuances. But this endeavor makes me respect the language more, and renews my vigor to keep learning it even deeper. I think any programming language will have its nooks and crannies, some of which we like and some which drive us mad!

    我不知道您是否喜欢JS或对所有这些细微差别感到沮丧。 但是,这种努力使我更加尊重该语言,并重新振作了继续学习该语言的动力。 我认为任何编程语言都会有它的缺点和缝隙,我们喜欢其中一些,有些使我们发疯!

    No matter your persuasion, there should be no disagreement that whatever your tool of choice, thinking more like the tool makes you better at using that tool. Happy JavaScript thinking!

    无论您的说服力如何,都应该同意,无论您选择哪种工具,都像工具一样思考,可以使您更好地使用该工具。 快乐JavaScript思考!

    翻译自: https://davidwalsh.name/thinking-javascript

    相关资源:javascript思维导图(全)
    Processed: 0.016, SQL: 9