Program Tip

CodeMash 2012의 'Wat'강연에서 언급 된 이러한 기괴한 JavaScript 동작에 대한 설명은 무엇입니까?

programtip 2020. 9. 29. 18:26
반응형

CodeMash 2012의 'Wat'강연에서 언급 된 이러한 기괴한 JavaScript 동작에 대한 설명은 무엇입니까?


CodeMash 2012 '와트'이야기는 기본적으로 루비와 자바 스크립트와 약간 기괴한 단점을 지적한다.

http://jsfiddle.net/fe479/9/ 에서 결과의 JSFiddle을 만들었습니다 .

(Ruby를 모르기 때문에) JavaScript와 관련된 동작은 다음과 같습니다.

JSFiddle에서 내 결과 중 일부가 비디오의 결과와 일치하지 않는다는 것을 발견했으며 그 이유를 모르겠습니다. 그러나 JavaScript가 각 경우에 백그라운드 작업을 처리하는 방법을 알고 싶습니다.

Empty Array + Empty Array
[] + []
result:
<Empty String>

+JavaScript에서 배열과 함께 사용할 때 연산자 에 대해 상당히 궁금합니다 . 이것은 비디오의 결과와 일치합니다.

Empty Array + Object
[] + {}
result:
[Object]

이것은 비디오의 결과와 일치합니다. 여기서 무슨 일이 일어나고 있습니까? 이것은 왜 객체입니다. 뭐라고합니까 +운영자는 무엇입니까?

Object + Empty Array
{} + []
result
[Object]

비디오와 일치하지 않습니다. 영상은 결과가 0 인 반면 [Object]를 얻었습니다.

Object + Object
{} + {}
result:
[Object][Object]

이것은 비디오와도 일치하지 않으며 변수를 출력하면 어떻게 두 개의 객체가 생성됩니까? 내 JSFiddle이 잘못되었을 수 있습니다.

Array(16).join("wat" - 1)
result:
NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN

wat + 1을 수행하면 wat1wat1wat1wat1...

나는 이것이 문자열에서 숫자를 빼려고 시도하면 NaN이 발생하는 단순한 행동이라고 생각합니다.


보고있는 결과에 대한 설명 목록은 다음과 같습니다. 내가 사용하는 참조는 ECMA-262 표준 에서 가져온 것 입니다.

  1. [] + []

    더하기 연산자를 사용하면 왼쪽 및 오른쪽 피연산자가 먼저 기본 형식으로 변환됩니다 ( §11.6.1 ). §9.1 원시적 복귀 유효한 가진 개체는 디폴트 값 (여기서는 배열) 객체 변환 toString()방법 호출의 결과 object.toString()( §8.12.8를 ). 배열의 경우 호출 array.join()( §15.4.4.2 ) 과 동일 합니다. 빈 배열을 결합하면 빈 문자열이 생성되므로 더하기 연산자의 # 7 단계는 빈 문자열 인 두 개의 빈 문자열 연결을 반환합니다.

  2. [] + {}

    와 유사하게 [] + []두 피연산자가 먼저 기본 형식으로 변환됩니다. "Object objects"(§15.2)의 경우 이는 object.toString()null이 아닌 정의되지 않은 개체의 경우 "[object Object]"( §15.2.4.2 ) 를 호출 한 결과입니다 .

  3. {} + []

    {}여기가 개체로 구문 분석, 대신 (빈 블록으로되지 §12.1 당신이 식을 수 그 진술을 강요하지 않는 등,하지만 더 그것에 대해 이후로 적어도). 빈 블록의 반환 값은 비어 있으므로 해당 문의 결과는 +[]. 단항 +연산자 ( §11.4.6 ) 는를 반환합니다 ToNumber(ToPrimitive(operand)). 우리가 이미 알고 있듯이, ToPrimitive([])빈 문자열, 그리고에 따라 §9.3.1 , ToNumber("")0입니다.

  4. {} + {}

    Similar to the previous case, the first {} is parsed as a block with empty return value. Again, +{} is the same as ToNumber(ToPrimitive({})), and ToPrimitive({}) is "[object Object]" (see [] + {}). So to get the result of +{}, we have to apply ToNumber on the string "[object Object]". When following the steps from §9.3.1, we get NaN as a result:

    If the grammar cannot interpret the String as an expansion of StringNumericLiteral, then the result of ToNumber is NaN.

  5. Array(16).join("wat" - 1)

    As per §15.4.1.1 and §15.4.2.2, Array(16) creates a new array with length 16. To get the value of the argument to join, §11.6.2 steps #5 and #6 show that we have to convert both operands to a number using ToNumber. ToNumber(1) is simply 1 (§9.3), whereas ToNumber("wat") again is NaN as per §9.3.1. Following step 7 of §11.6.2, §11.6.3 dictates that

    If either operand is NaN, the result is NaN.

    So the argument to Array(16).join is NaN. Following §15.4.4.5 (Array.prototype.join), we have to call ToString on the argument, which is "NaN" (§9.8.1):

    If m is NaN, return the String "NaN".

    Following step 10 of §15.4.4.5, we get 15 repetitions of the concatenation of "NaN" and the empty string, which equals the result you're seeing. When using "wat" + 1 instead of "wat" - 1 as argument, the addition operator converts 1 to a string instead of converting "wat" to a number, so it effectively calls Array(16).join("wat1").

As to why you're seeing different results for the {} + [] case: When using it as a function argument, you're forcing the statement to be an ExpressionStatement, which makes it impossible to parse {} as empty block, so it's instead parsed as an empty object literal.


This is more of a comment than an answer, but for some reason I can't comment on your question. I wanted to correct your JSFiddle code. However, I posted this on Hacker News and someone suggested that I repost it here.

The problem in the JSFiddle code is that ({}) (opening braces inside of parentheses) is not the same as {} (opening braces as the start of a line of code). So when you type out({} + []) you are forcing the {} to be something which it is not when you type {} + []. This is part of the overall 'wat'-ness of Javascript.

The basic idea was simple JavaScript wanted to allow both of these forms:

if (u)
    v;

if (x) {
    y;
    z;
}

To do so, two interpretations were made of the opening brace: 1. it is not required and 2. it can appear anywhere.

This was a wrong move. Real code doesn't have an opening brace appearing in the middle of nowhere, and real code also tends to be more fragile when it uses the first form rather than the second. (About once every other month at my last job, I'd get called to a coworker's desk when their modifications to my code weren't working, and the problem was that they'd added a line to the "if" without adding curly braces. I eventually just adopted the habit that the curly braces are always required, even when you're only writing one line.)

Fortunately in many cases eval() will replicate the full wat-ness of JavaScript. The JSFiddle code should read:

function out(code) {
    function format(x) {
        return typeof x === "string" ?
            JSON.stringify(x) : x;
    }   
    document.writeln('&gt;&gt;&gt; ' + code);
    document.writeln(format(eval(code)));
}
document.writeln("<pre>");
out('[] + []');
out('[] + {}');
out('{} + []');
out('{} + {}');
out('Array(16).join("wat" + 1)');
out('Array(16).join("wat - 1")');
out('Array(16).join("wat" - 1) + " Batman!"');
document.writeln("</pre>");

[Also that is the first time I have written document.writeln in many many many years, and I feel a little dirty writing anything involving both document.writeln() and eval().]


I second @Ventero’s solution. If you want to, you can go into more detail as to how + converts its operands.

First step (§9.1): convert both operands to primitives (primitive values are undefined, null, booleans, numbers, strings; all other values are objects, including arrays and functions). If an operand is already primitive, you are done. If not, it is an object obj and the following steps are performed:

  1. Call obj.valueOf(). If it returns a primitive, you are done. Direct instances of Object and arrays return themselves, so you are not done yet.
  2. Call obj.toString(). If it returns a primitive, you are done. {} and [] both return a string, so you are done.
  3. Otherwise, throw a TypeError.

For dates, step 1 and 2 are swapped. You can observe the conversion behavior as follows:

var obj = {
    valueOf: function () {
        console.log("valueOf");
        return {}; // not a primitive
    },
    toString: function () {
        console.log("toString");
        return {}; // not a primitive
    }
}

Interaction (Number() first converts to primitive then to number):

> Number(obj)
valueOf
toString
TypeError: Cannot convert object to primitive value

Second step (§11.6.1): If one of the operands is a string, the other operand is also converted to string and the result is produced by concatenating two strings. Otherwise, both operands are converted to numbers and the result is produced by adding them.

More detailed explanation of the conversion process: “What is {} + {} in JavaScript?


We may refer to the specification and that's great and most accurate, but most of the cases can also be explained in a more comprehensible way with the following statements:

  • + and - operators work only with primitive values. More specifically +(addition) works with either strings or numbers, and +(unary) and -(subtraction and unary) works only with numbers.
  • All native functions or operators that expect primitive value as argument, will first convert that argument to desired primitive type. It is done with valueOf or toString, which are available on any object. That's the reason why such functions or operators don't throw errors when invoked on objects.

So we may say that:

  • [] + [] is same as String([]) + String([]) which is same as '' + ''. I mentioned above that +(addition) is also valid for numbers, but there is no valid number representation of an array in JavaScript, so addition of strings is used instead.
  • [] + {} is same as String([]) + String({}) which is same as '' + '[object Object]'
  • {} + []. This one deserves more explanation (see Ventero answer). In that case, curly braces are treated not as an object but as an empty block, so it turns out to be same as +[]. Unary + works only with numbers, so the implementation tries to get a number out of []. First it tries valueOf which in the case of arrays returns the same object, so then it tries the last resort: conversion of a toString result to a number. We may write it as +Number(String([])) which is same as +Number('') which is same as +0.
  • Array(16).join("wat" - 1) subtraction - works only with numbers, so it's the same as: Array(16).join(Number("wat") - 1), as "wat" can't be converted to a valid number. We receive NaN, and any arithmetic operation on NaN results with NaN, so we have: Array(16).join(NaN).

To buttress what has been shared earlier.

The underlying cause of this behaviour is partly due to the weakly-typed nature of JavaScript. For example, the expression 1 + “2” is ambiguous since there are two possible interpretations based on the operand types (int, string) and (int int):

  • User intends to concatenate two strings, result: “12”
  • User intends to add two numbers, result: 3

Thus with varying input types,the output possibilities increase.

The addition algorithm

  1. Coerce operands to primitive values

The JavaScript primitives are string, number, null, undefined and boolean (Symbol is coming soon in ES6). Any other value is an object (e.g. arrays, functions and objects). The coercion process for converting objects into primitive values is described thus:

  • If a primitive value is returned when object.valueOf() is invoked, then return this value, otherwise continue

  • If a primitive value is returned when object.toString() is invoked, then return this value, otherwise continue

  • Throw a TypeError

Note: For date values, the order is to invoke toString before valueOf.

  1. If any operand value is a string, then do a string concatenation

  2. Otherwise, convert both operands to their numeric value and then add these values

Knowing the various coercion values of types in JavaScript does help to make the confusing outputs clearer. See the coercion table below

+-----------------+-------------------+---------------+
| Primitive Value |   String value    | Numeric value |
+-----------------+-------------------+---------------+
| null            | “null”            | 0             |
| undefined       | “undefined”       | NaN           |
| true            | “true”            | 1             |
| false           | “false”           | 0             |
| 123             | “123”             | 123           |
| []              | “”                | 0             |
| {}              | “[object Object]” | NaN           |
+-----------------+-------------------+---------------+

It is also good to know that JavaScript's + operator is left-associative as this determines what the output will be cases involving more than one + operation.

Leveraging the Thus 1 + "2" will give "12" because any addition involving a string will always default to string concatenation.

You can read more examples in this blog post (disclaimer I wrote it).

참고URL : https://stackoverflow.com/questions/9032856/what-is-the-explanation-for-these-bizarre-javascript-behaviours-mentioned-in-the

반응형

'Program Tip' 카테고리의 다른 글

  (0) 2020.09.29
Python에서 증가 및 감소 연산자의 동작  (0) 2020.09.29
Python의 mkdir -p 기능  (0) 2020.09.29
Facebook 및 새 Twitter URL의 shebang / hashbang (#!)은 무엇입니까?  (0) 2020.09.29
왜 px 대신 em?  (0) 2020.09.29