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
왓 + 1을하면 wat1wat1wat1wat1
…
나는 이것이 문자열에서 숫자를 빼려고하면 NaN이되는 간단한 행동이라고 생각합니다.
답변
다음은 현재보고있는 결과에 대한 설명 목록입니다. 내가 사용하는 참조는 ECMA-262 표준에서 온 것 입니다.
-
[] + []
더하기 연산자를 사용할 때 왼쪽과 오른쪽 피연산자가 모두 기본 요소로 먼저 변환됩니다 ( §11.6.1 ). 당 §9.1 원시적 복귀 유효한 가진 개체는 디폴트 값 (여기서는 배열) 객체 변환
toString()
방법 호출의 결과object.toString()
( §8.12.8를 ). 배열의 경우 이는 호출array.join()
( §15.4.4.2 ) 과 동일 합니다. 빈 배열을 조인하면 빈 문자열이 생성되므로 더하기 연산자의 7 단계는 빈 문자열 인 두 개의 빈 문자열의 연결을 반환합니다. -
[] + {}
와 마찬가지로
[] + []
두 피연산자가 모두 기본 요소로 먼저 변환됩니다. “객체 객체”(§15.2)의 경우, 이는 다시 호출의 결과이며object.toString()
, null이 아닌 정의되지 않은 객체의 경우"[object Object]"
( §15.2.4.2 )입니다. -
{} + []
{}
여기가 개체로 구문 분석, 대신 (빈 블록으로되지 §12.1 당신이 식을 수 그 진술을 강요하지 않는 등,하지만 더 그것에 대해 이후로 적어도). 빈 블록의 반환 값이 비어 있으므로 해당 문의 결과는와 동일합니다+[]
. 단항+
연산자 ( §11.4.6 )는 다음을 반환합니다ToNumber(ToPrimitive(operand))
. 우리가 이미 알고 있듯이,ToPrimitive([])
빈 문자열, 그리고에 따라 §9.3.1 ,ToNumber("")
0입니다. -
{} + {}
앞의 경우와 마찬가지로 첫 번째
{}
는 빈 반환 값을 가진 블록으로 구문 분석됩니다. 다시+{}
와 동일ToNumber(ToPrimitive({}))
하고ToPrimitive({})
있다"[object Object]"
(참조[] + {}
). 따라서 결과를 얻으려면 문자열+{}
에 적용ToNumber
해야합니다"[object Object]"
. 의 단계를 수행 할 때 §9.3.1을 , 우리가 얻을NaN
결과 :문법이 문자열을 StringNumericLiteral 의 확장으로 해석 할 수없는 경우 ToNumber 의 결과 는 NaN 입니다.
-
Array(16).join("wat" - 1)
당 §15.4.1.1 및 §15.4.2.2 ,
Array(16)
가입 인수의 값을 얻으려면 길이 (16)로 새로운 배열을 생성, §11.6.2 우리가에 두 피연산자를 변환해야한다는 # 5, # 6은 단계 를 사용하여 번호를 지정하십시오ToNumber
.ToNumber(1)
단순히 1 ( §9.3 ) 반면,ToNumber("wat")
다시 인NaN
당 §9.3.1 . 의 7 단계에 따라 §11.6.2 , §11.6.3 지시하는피연산자 중 하나가 NaN 이면 결과는 NaN 입니다.
따라서의 주장은
Array(16).join
입니다NaN
. §15.4.4.5 (Array.prototype.join
)에 이어 우리ToString
는"NaN"
( §9.8.1 ) 인수 를 호출해야합니다 .경우 m가 있다 NaN를 , 문자열을 반환합니다
"NaN"
.§15.4.4.5의 10 단계 에 따라 연결
"NaN"
및 빈 문자열 이 15 회 반복되어 나타납니다 . 이는 결과와 같습니다. 사용시"wat" + 1
대신"wat" - 1
인수, 가산 연산자 변환1
대신 변환하는 문자열"wat"
다수 효과적으로 통화 그것은 그렇게Array(16).join("wat1")
.
{} + []
케이스에 대해 다른 결과가 나타나는 이유에 관해서는 : 함수 인수로 사용할 때 명령문을 ExpressionStatement 로 강제 실행하여 {}
빈 블록 으로 구문 분석 할 수 없으므로 대신 빈 오브젝트로 구문 분석됩니다 오자.
답변
이것은 답변보다 더 많은 의견이지만, 어떤 이유로 든 귀하의 질문에 대해서는 언급 할 수 없습니다. JSFiddle 코드를 수정하고 싶었습니다. 그러나 나는 이것을 Hacker News에 게시했으며 누군가 내가 그것을 다시 게시 할 것을 제안했습니다.
JSFiddle 코드의 문제점은 ({})
(괄호 안의 중괄호 열기)가 {}
(코드 줄의 시작과 같이 중괄호 열기) 와 같지 않다는 것 입니다. 따라서 입력 out({} + [])
할 때 입력 할 {}
때가 아닌 다른 것을 강제합니다 {} + []
. 이것은 자바 스크립트의 전반적인 ‘왓’의 일부입니다.
기본 아이디어는 간단한 JavaScript가 다음 두 형식을 모두 허용하고자한다는 것입니다.
if (u)
v;
if (x) {
y;
z;
}
이 1. : 이렇게하려면, 두 가지 해석이 여는 중괄호로 만들어진 필요하지 2.이 나타날 수 있습니다 어디서나 .
이것은 잘못된 움직임이었습니다. 실제 코드에는 중간에 여는 괄호가 없으며 실제 코드는 두 번째가 아닌 첫 번째 형식을 사용할 때 더 취약합니다. (마지막 직장에서 2 개월마다 한 번씩, 내 코드 수정이 작동하지 않을 때 동료의 책상에 전화를 받았는데 문제는 곱슬 머리를 추가하지 않고 “if”에 줄을 추가했다는 것이 었습니다 나는 단지 한 줄만 쓸 때에도 항상 중괄호가 필요한 습관을 채택했습니다.)
다행스럽게도 eval ()은 JavaScript를 최대한 활용합니다. JSFiddle 코드는 다음과 같아야합니다.
function out(code) {
function format(x) {
return typeof x === "string" ?
JSON.stringify(x) : x;
}
document.writeln('>>> ' + 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>");
[또한 수년 동안 document.writeln을 처음으로 작성했으며 document.writeln () 및 eval ()과 관련된 내용을 쓰는 것이 약간 더럽습니다.]
답변
@Ventero의 솔루션에 이어 두 번째입니다. 원하는 경우 +
피연산자를 변환 하는 방법에 대해 자세히 설명 할 수 있습니다 .
첫 번째 단계 (§9.1) (기본 값은 프리미티브 두 피연산자 변환 undefined
, null
불리언, 숫자, 캐릭터, 다른 모든 값들은 배열 및 기능을 포함하여, 개체). 피연산자가 이미 기본 인 경우 완료된 것입니다. 그렇지 않은 경우 이는 오브젝트 obj
이며 다음 단계가 수행됩니다.
- 전화하십시오
obj.valueOf()
. 프리미티브를 리턴하면 완료됩니다. 직접 인스턴스Object
및 배열이 반환되므로 아직 완료되지 않았습니다. - 전화하십시오
obj.toString()
. 프리미티브를 리턴하면 완료됩니다.{}
그리고[]
당신이 수행되도록 모두 문자열을 반환합니다. - 그렇지 않으면를 던지십시오
TypeError
.
날짜의 경우 1 단계와 2 단계가 바뀝니다. 다음과 같이 변환 동작을 관찰 할 수 있습니다.
var obj = {
valueOf: function () {
console.log("valueOf");
return {}; // not a primitive
},
toString: function () {
console.log("toString");
return {}; // not a primitive
}
}
상호 작용 ( Number()
먼저 기본 형식으로 변환 한 다음 숫자로 변환) :
> Number(obj)
valueOf
toString
TypeError: Cannot convert object to primitive value
두 번째 단계 (§11.6.1) : 피연산자 중 하나가 문자열이면 다른 피연산자도 문자열로 변환되고 결과는 두 문자열을 연결하여 생성됩니다. 그렇지 않으면 두 피연산자가 모두 숫자로 변환되고 결과를 추가하여 생성됩니다.
변환 프로세스에 대한 자세한 설명 :“ JavaScript에서 {} + {} 란 무엇입니까? “
답변
우리는 사양을 참조 할 수 있으며 그것이 가장 정확하고 정확하지만 대부분의 경우는 다음 진술을 통해 더 이해하기 쉽게 설명 될 수 있습니다.
+
및-
연산자는 기본 값으로 만 작동합니다. 보다 구체적으로+
(더하기)는 문자열 또는 숫자로 작동하며+
(단항) 및-
(빼기 및 단항)은 숫자로만 작동합니다.- 프리미티브 값을 인수로 예상하는 모든 기본 함수 또는 연산자는 먼저 해당 인수를 원하는 프리미티브 유형으로 변환합니다. 모든 객체에서 사용 가능한
valueOf
또는 로 수행됩니다toString
. 이것이 그러한 함수 나 연산자가 객체를 호출 할 때 오류를 발생시키지 않는 이유입니다.
따라서 다음과 같이 말할 수 있습니다.
[] + []
와String([]) + String([])
동일합니다'' + ''
. 위에서 언급했듯이+
(추가)는 숫자에도 유효하지만 JavaScript에는 배열의 유효한 숫자 표현이 없으므로 문자열 추가가 대신 사용됩니다.[] + {}
와String([]) + String({})
동일하다'' + '[object Object]'
{} + []
. 이것에 대한 자세한 설명이 필요합니다 (Ventero 답변 참조). 이 경우 중괄호는 객체가 아니라 빈 블록으로 취급되므로와 같습니다+[]
. 단항+
은 숫자로만 작동하므로 구현은에서 숫자를 얻으려고 시도합니다[]
. 먼저valueOf
배열의 경우 동일한 객체를 반환하는 것을 시도하므로 마지막 수단 :toString
결과를 숫자로 변환 합니다. 우리는로 쓸 수 있습니다+Number(String([]))
동일하게되는+Number('')
동일하다+0
.Array(16).join("wat" - 1)
빼기-
: 그래서 같은의 숫자 만 작동Array(16).join(Number("wat") - 1)
으로,"wat"
유효한 숫자로 변환 할 수 없습니다. 의 결과NaN
에 대한 산술 연산을 받으 므로 다음과 같이 됩니다.NaN
NaN
Array(16).join(NaN)
답변
이전에 공유 한 내용을 뒷받침합니다.
이 동작의 근본 원인은 부분적으로 JavaScript의 형식이 약하기 때문입니다. 예를 들어 피연산자 유형 (int, string) 및 (int int)에 따라 두 가지 가능한 해석이 있으므로 1 + “2”라는 표현은 모호합니다.
- 사용자는 두 문자열을 연결하려고합니다. 결과 : “12”
- 사용자는 두 개의 숫자를 추가하려고합니다. 결과 : 3
따라서 다양한 입력 유형으로 출력 가능성이 증가합니다.
추가 알고리즘
- 피연산자를 기본 값으로 강제 변환
JavaScript 프리미티브는 string, number, null, undefined 및 boolean입니다 (심볼은 ES6에서 곧 제공 될 예정 임). 다른 값은 객체 (예 : 배열, 함수 및 객체)입니다. 객체를 원시 값으로 변환하는 강제 프로세스는 다음과 같이 설명됩니다.
-
object.valueOf ()가 호출 될 때 기본 값이 리턴되면이 값을 리턴하고 그렇지 않으면 계속하십시오.
-
object.toString ()이 호출 될 때 기본 값이 리턴되면이 값을 리턴하고 그렇지 않으면 계속하십시오.
-
TypeError 던지기
참고 : 날짜 값의 경우 valueOf 이전에 toString을 호출하는 순서입니다.
-
피연산자 값이 문자열이면 문자열 연결을 수행하십시오.
-
그렇지 않으면 두 피연산자를 모두 숫자 값으로 변환 한 다음이 값을 추가하십시오
JavaScript에서 다양한 유형의 강제 값을 알면 혼란스러운 출력을 더 명확하게하는 데 도움이됩니다. 아래의 강제 표를 참조하십시오
+-----------------+-------------------+---------------+
| 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 |
+-----------------+-------------------+---------------+
JavaScript의 + 연산자는 둘 이상의 + 연산과 관련된 경우의 결과를 결정하므로 왼쪽 + 연산자는 왼쪽 연관이라는 것을 아는 것도 좋습니다.
따라서 문자열을 포함하는 추가는 항상 기본적으로 문자열 연결로 설정되므로 1 + “2”를 활용하면 “12”가 제공됩니다.
이 블로그 게시물 에서 더 많은 예제를 읽을 수 있습니다 (면책 조항).