CodeMash 2012 के लिए 'वाट' वार्ता में उल्लिखित इन विचित्र जावास्क्रिप्ट व्यवहार के लिए स्पष्टीकरण क्या है?


753

CodeMash 2012 के लिए 'वाट' बात मूल रूप से रूबी और जावास्क्रिप्ट के साथ कुछ विचित्र quirks बताते हैं।

मैंने http://jsfiddle.net/fe479/9/ पर परिणामों का JSFiddle बनाया है ।

जावास्क्रिप्ट के लिए विशिष्ट व्यवहार (जैसा कि मुझे रूबी नहीं पता है) नीचे सूचीबद्ध हैं।

मुझे JSFiddle में पता चला कि मेरे कुछ परिणाम वीडियो में उन लोगों के साथ मेल नहीं खाते, और मुझे यकीन नहीं है कि क्यों। हालांकि, मैं यह जानने के लिए उत्सुक हूं कि जावास्क्रिप्ट प्रत्येक मामले में पर्दे के पीछे कैसे काम कर रहा है।

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

+जावास्क्रिप्ट में सरणियों के साथ उपयोग किए जाने पर मैं ऑपरेटर के बारे में काफी उत्सुक हूं । यह वीडियो के परिणाम से मेल खाता है।

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

यह वीडियो के परिणाम से मेल खाता है। यहाँ क्या चल रहा है? यह एक वस्तु क्यों है? +ऑपरेटर क्या करता है?

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

यह वीडियो से मेल नहीं खाता। वीडियो बताता है कि परिणाम 0 है, जबकि मुझे मिलता है [वस्तु]।

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

यह या तो वीडियो से मेल नहीं खाता है, और दो वस्तुओं में चर परिणाम का उत्पादन कैसे करता है? हो सकता है कि मेरी JSFiddle गलत हो।

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

वाट + 1 में परिणाम wat1wat1wat1wat1...

मुझे संदेह है कि यह सिर्फ सीधा व्यवहार है जो NaN में एक स्ट्रिंग परिणाम से एक संख्या को घटाने की कोशिश कर रहा है।


4
{} + [] एक ही मुश्किल और कार्यान्वयन पर निर्भरता है, जैसा कि मैं यहां बताता हूं , क्योंकि यह एक बयान के रूप में या एक अभिव्यक्ति के रूप में पार्स होने पर निर्भर करता है। आप किस वातावरण में परीक्षण कर रहे हैं (मुझे फ़ायरफ़ॉक्स और क्रोम में अपेक्षित 0 मिला है, लेकिन नोडजेजे में "[ऑब्जेक्ट ऑब्जेक्ट]" मिला है)?
हुगोमग

1
मैं विंडोज 7 पर फ़ायरफ़ॉक्स 9.0.1 चला रहा हूं, और JSFiddle ने इसका मूल्यांकन [ऑब्जेक्ट]
NIBlyPig

@missingno मुझे NodeJS REPL में 0 मिलता है
OrangeDog

41
Array(16).join("wat" - 1) + " Batman!"
निक जॉनसन

1
@missingno यहाँ सवाल पोस्ट किया है , लेकिन के लिए {} + {}
Ionică Bizău

जवाबों:


1479

यहां उन परिणामों के स्पष्टीकरण की सूची दी गई है जिन्हें आप देख रहे हैं (और देखने वाले हैं)। मेरे द्वारा उपयोग किए जा रहे संदर्भ ECMA-262 मानक से हैं

  1. [] + []

    अतिरिक्त ऑपरेटर का उपयोग करते समय, बाएं और दाएं दोनों ऑपरेंड को पहले प्राइमेटिव में बदला जाता है ( addition11.6.1 )। This9.1 के अनुसार , एक वस्तु (इस मामले में एक सरणी) को एक आदिम में परिवर्तित करने से इसका डिफ़ॉल्ट मान वापस आ जाता है, जो कि एक वैध toString()विधि वाली वस्तुओं के लिए कॉलिंग का परिणाम है object.toString()( §8.12.8 )। सरणियों के लिए यह कॉलिंग array.join()( ays15.4.4.2 ) के समान है। खाली सरणी में शामिल होने से रिक्त स्ट्रिंग में परिणाम होता है, इसलिए जोड़ ऑपरेटर का # 7 चरण दो खाली स्ट्रिंग का संघटन लौटाता है, जो रिक्त स्ट्रिंग है।

  2. [] + {}

    इसी के लिए [] + [], दोनों ऑपरेंड पहले पुरातन करने के लिए बदल रहे हैं। "ऑब्जेक्ट ऑब्जेक्ट्स" (.215.2) के लिए, यह फिर से कॉलिंग का परिणाम है object.toString(), जो गैर-अशक्त, गैर-अपरिभाषित वस्तुओं के लिए है "[object Object]"( .215.2.4.2 )।

  3. {} + []

    {}यहाँ एक वस्तु के रूप में पार्स नहीं कर रहा है, लेकिन इसके बजाय (एक खाली ब्लॉक के रूप में §12.1 तक कि आप उस बयान के लिए मजबूर नहीं कर रहे हैं के रूप में एक अभिव्यक्ति हो सकता है, लेकिन यह है कि के बारे में अधिक बाद में के रूप में, कम से कम)। खाली ब्लॉकों का रिटर्न मान खाली है, इसलिए उस विवरण का परिणाम उसी के समान है +[]। एकल +ऑपरेटर ( §11.4.6 ) रिटर्न ToNumber(ToPrimitive(operand))। जैसा कि हम पहले से ही जानते हैं, ToPrimitive([])खाली स्ट्रिंग है, और .19.3.1 के अनुसार , ToNumber("")0 है।

  4. {} + {}

    पिछले मामले के समान, पहले {}को खाली रिटर्न मान वाले ब्लॉक के रूप में पार्स किया गया है। फिर से, के +{}रूप में ही है ToNumber(ToPrimitive({})), और ToPrimitive({})है "[object Object]"(देखें [] + {})। इसलिए परिणाम प्राप्त करने के लिए +{}, हमें ToNumberस्ट्रिंग पर आवेदन करना होगा "[object Object]".39.3.1 से चरणों का पालन करते समय , हमें NaNपरिणाम मिलता है :

    यदि व्याकरण StringNumericLiteral के विस्तार के रूप में स्ट्रिंग की व्याख्या नहीं कर सकता है , तो ToNumber का परिणाम NaN है

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

    .215.4.1.1 और §15.4.2.2 के अनुसार , Array(16)लंबाई 16 के साथ एक नया सरणी बनाता है। शामिल होने के लिए तर्क का मूल्य प्राप्त करने के लिए, .611.6.2 चरण # 5 और # 6 दिखाते हैं कि हमें दोनों ऑपरेंड को एक में बदलना है। नंबर का उपयोग कर ToNumberToNumber(1)केवल 1 ( simply9.3 ) है, जबकि ToNumber("wat")फिर से .19.3.1 केNaN अनुसार है । Step11.6.2 के चरण 7 के बाद , .311.6.3 यह निर्धारित करता है

    यदि कोई ऑपरेंड NaN है , तो परिणाम NaN है

    तो तर्क Array(16).joinहै NaN। To15.4.4.5 ( Array.prototype.join) के बाद , हमें ToStringतर्क पर कॉल करना होगा, जो "NaN"( .89.8.1 ) है:

    यदि मीटर है NaN , स्ट्रिंग वापसी "NaN"

    .415.4.4.5 के चरण 10 के बाद , हमें "NaN"उस रिक्त स्थान के 15 पुनरावृत्ति और खाली स्ट्रिंग मिलते हैं , जो आपके द्वारा देखे जाने वाले परिणाम के बराबर है। जब तर्क के "wat" + 1बजाय उपयोग किया "wat" - 1जाता है, तो अतिरिक्त ऑपरेटर एक संख्या 1में परिवर्तित "wat"होने के बजाय एक स्ट्रिंग में परिवर्तित हो जाता है, इसलिए यह प्रभावी रूप से कॉल करता है Array(16).join("wat1")

जैसा कि आप {} + []मामले के लिए अलग-अलग परिणाम देख रहे हैं : इसे फ़ंक्शन तर्क के रूप में उपयोग करते समय, आप स्टेटमेंट को एक्सप्रेशनस्टैटेमेंट होने के लिए मजबूर कर रहे हैं , जिससे {}खाली ब्लॉक के रूप में पार्स करना असंभव हो जाता है, इसलिए इसे खाली ऑब्जेक्ट के रूप में पार्स किया जाता है शाब्दिक।


2
तो क्यों [] +1 => "1" और [] -1 => -1 है?
रॉब एल्सनर

4
@RobElsner []+1बहुत ही तर्क का अनुसरण करता है []+[], जैसे 1.toString()rhs ऑपरैंड के साथ । के लिए []-1की व्याख्या देखने के "wat"-1बिंदु में 5. कि याद रखें ToNumber(ToPrimitive([]))0 (बिंदु 3) है।
वेंटरो

4
यह स्पष्टीकरण गायब है / बहुत सारे विवरण छोड़ देता है। उदाहरण के लिए "एक वस्तु (इस मामले में एक सरणी) को एक आदिम में परिवर्तित करने से उसका डिफ़ॉल्ट मान वापस आ जाता है, जो कि एक वैध toString () विधि के साथ ऑब्जेक्ट को कॉल करने का परिणाम है ।toString ()" [] के मान को पूरी तरह से गायब है। पहले कहा जाता है, लेकिन क्योंकि वापसी मूल्य एक आदिम नहीं है (यह एक सरणी है), [] की स्टर्लिंग को इसके बजाय प्रयोग किया जाता है। मैं इसके बजाय वास्तविक indepth स्पष्टीकरण 2ality.com/2012/01/object-plus-object.html
jahav

30

यह एक उत्तर की तुलना में अधिक टिप्पणी है, लेकिन किसी कारण से मैं आपके प्रश्न पर टिप्पणी नहीं कर सकता। मैं आपका JSFiddle कोड सही करना चाहता था। हालाँकि, मैंने इसे हैकर न्यूज़ पर पोस्ट किया था और किसी ने सुझाव दिया था कि मैं इसे यहाँ प्रस्तुत करता हूं।

JSFiddle कोड में समस्या यह है कि ({})(कोष्ठक के अंदर ब्रेसिज़ खोलना) समान नहीं है {}(कोड की एक पंक्ति की शुरुआत के रूप में ब्रेसिज़ खोलना)। इसलिए जब आप टाइप out({} + [])करते हैं तो आप {}कुछ ऐसा होने के लिए मजबूर कर रहे हैं जो कि जब आप टाइप करते हैं तो ऐसा नहीं होता {} + []। यह जावास्क्रिप्ट के समग्र 'वाट-नेस' का हिस्सा है।

मूल विचार सरल था जावास्क्रिप्ट इन दोनों रूपों की अनुमति देना चाहता था:

if (u)
    v;

if (x) {
    y;
    z;
}

ऐसा करने के लिए, दो व्याख्याएं उद्घाटन ब्रेस से बनी थीं: 1. इसकी आवश्यकता नहीं है और 2. यह कहीं भी दिखाई दे सकती है

यह एक गलत कदम था। असली कोड में कहीं भी बीच में एक शुरुआती ब्रेस दिखाई नहीं देता है, और वास्तविक कोड भी अधिक नाजुक हो जाता है जब यह दूसरे के बजाय पहले फॉर्म का उपयोग करता है। (अपनी अंतिम नौकरी के हर महीने में लगभग एक बार, मुझे एक सहकर्मी के डेस्क पर बुलाया जाएगा, जब मेरे कोड में उनके संशोधन काम नहीं कर रहे थे, और समस्या यह थी कि वे घुंघराले जोड़े बिना "अगर" के लिए एक पंक्ति जोड़ दिया था। ब्रेसिज़। मैंने अंततः बस इस आदत को अपनाया कि घुंघराले ब्रेसों की हमेशा आवश्यकता होती है, तब भी जब आप केवल एक पंक्ति लिख रहे हों।)

सौभाग्य से कई मामलों में eval () जावास्क्रिप्ट के पूर्ण वाट-नेस को दोहराएगा। JSFiddle कोड को पढ़ना चाहिए:

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>");

[यह भी कि पहली बार मैंने कई वर्षों में document.writeln लिखा है, और मुझे लगता है कि दोनों document.writeln (और eval) () को शामिल करते हुए कुछ भी गंदा लेखन है।


15
This was a wrong move. Real code doesn't have an opening brace appearing in the middle of nowhere- मैं असहमत (तरह का): मेरे पास अक्सर पिछले इस्तेमाल किए गए ब्लॉक हैं जैसे सी में वेरिएबल को स्कोप करना । इस आदत को थोड़ी देर के लिए उठाया गया था जब एम्बेडेड सी कर रहे थे जहां स्टैक पर चर जगह लेते हैं, इसलिए यदि उन्हें अब ज़रूरत नहीं है, तो हम चाहते हैं कि अंतरिक्ष ब्लॉक के अंत में मुक्त हो जाए। हालाँकि, ECMAScript केवल फ़ंक्शन () {} ब्लॉक के भीतर स्कोप है। इसलिए, जबकि मैं असहमत हूं कि अवधारणा गलत है, मैं सहमत हूं कि जेएस में कार्यान्वयन संभवतः ( गलत) है।
जेस टेलफ़ोर्ड

4
@JessTelford ES6 में, आप letब्लॉक-स्कोप किए गए चर घोषित करने के लिए उपयोग कर सकते हैं ।
ओरिएंल

19

I दूसरा @ वेंटरो का समाधान। यदि आप चाहते हैं, तो आप अधिक विस्तार में जा सकते हैं कि कैसे +अपने ऑपरेंड को परिवर्तित किया जाए।

पहला कदम (§9.1): पुरातन करने के लिए दोनों ऑपरेंड परिवर्तित (आदिम मान हैं undefined, null, बूलियन्स, संख्या, तार, अन्य सभी मान अरै, और फ़ंक्शन सहित वस्तुओं रहे हैं)। यदि एक ऑपरेंड पहले से ही आदिम है, तो आप कर रहे हैं। यदि नहीं, तो यह एक वस्तु है objऔर निम्न चरणों का पालन किया जाता है:

  1. पुकारते हैं obj.valueOf()। यदि यह एक आदिम देता है, तो आप कर रहे हैं। के प्रत्यक्ष उदाहरण Objectऔर सरणियाँ स्वयं वापस आती हैं, इसलिए आपको अभी तक नहीं किया गया है।
  2. पुकारते हैं obj.toString()। यदि यह एक आदिम देता है, तो आप कर रहे हैं। {}और []दोनों एक स्ट्रिंग लौटाते हैं, इसलिए आपको किया जाता है।
  3. नहीं तो फेंक दो 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

दूसरा चरण (step11.6.1): यदि ऑपरेंड में से एक स्ट्रिंग है, तो दूसरा ऑपरेंड भी स्ट्रिंग में परिवर्तित हो जाता है और परिणाम दो स्ट्रिंग्स को समेट कर उत्पन्न होता है। अन्यथा, दोनों ऑपरेंड संख्या में परिवर्तित हो जाते हैं और परिणाम उन्हें जोड़कर उत्पन्न होता है।

रूपांतरण प्रक्रिया का अधिक विस्तृत विवरण: " जावास्क्रिप्ट में {} + {} क्या है? "


13

हम विनिर्देश का उल्लेख कर सकते हैं और यह महान और सबसे सटीक है, लेकिन अधिकांश मामलों को निम्नलिखित कथनों के साथ अधिक समझदार तरीके से समझाया जा सकता है:

  • +और -ऑपरेटर केवल आदिम मूल्यों के साथ काम करते हैं। अधिक विशेष रूप से +(जोड़) स्ट्रिंग्स या संख्याओं के साथ काम करता है, और +(यूरीरी) और -(घटाव और एकारी) केवल संख्याओं के साथ काम करता है।
  • सभी मूल कार्य या ऑपरेटर जो तर्क के रूप में आदिम मूल्य की अपेक्षा करते हैं, पहले उस तर्क को वांछित आदिम प्रकार में बदल देंगे। यह valueOfया के साथ किया जाता है toString, जो किसी भी वस्तु पर उपलब्ध हैं। यही कारण है कि वस्तुओं पर आह्वान करने पर ऐसे कार्य या ऑपरेटर त्रुटियों को नहीं फेंकते हैं।

तो हम कह सकते हैं कि:

  • [] + []रूप में ही है String([]) + String([])जो रूप में ही है '' + ''। मैंने ऊपर उल्लेख किया है कि +(जोड़) भी संख्याओं के लिए मान्य है, लेकिन जावास्क्रिप्ट में किसी सरणी का कोई मान्य संख्या प्रतिनिधित्व नहीं है, इसलिए इसके अलावा तार का उपयोग किया जाता है।
  • [] + {}रूप में ही है String([]) + String({})जो रूप में ही है'' + '[object Object]'
  • {} + []। यह एक अधिक स्पष्टीकरण के योग्य है (वेंटरो उत्तर देखें)। उस स्थिति में, घुंघराले ब्रेसिज़ को एक वस्तु के रूप में नहीं बल्कि एक खाली ब्लॉक के रूप में माना जाता है, इसलिए यह समान रूप से निकला +[]। यूनरी +केवल संख्याओं के साथ काम करता है, इसलिए कार्यान्वयन एक संख्या से बाहर निकलने की कोशिश करता है []। पहले यह कोशिश करता है 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):।

0

जो पहले साझा किया गया है उसे दबाने के लिए।

इस व्यवहार का अंतर्निहित कारण आंशिक रूप से जावास्क्रिप्ट की कमजोर प्रकृति के कारण है। उदाहरण के लिए, अभिव्यक्ति 1 + "2" अस्पष्ट है क्योंकि ऑपरेंड प्रकार (इंट, स्ट्रिंग) और (इंट इंट) के आधार पर दो संभावित व्याख्याएं हैं:

  • उपयोगकर्ता का इरादा दो तार जोड़ने का है, परिणाम: "12"
  • उपयोगकर्ता दो नंबर जोड़ने का इरादा रखता है, परिणाम: 3

इस प्रकार अलग-अलग इनपुट प्रकारों के साथ, आउटपुट संभावनाएं बढ़ती हैं।

इसके अलावा एल्गोरिथ्म

  1. मोटे तौर पर आदिम मूल्यों को संचालित करता है

जावास्क्रिप्ट आदिम स्ट्रिंग, संख्या, अशक्त, अपरिभाषित और बूलियन हैं (प्रतीक जल्द ही ES6 में आ रहा है)। कोई अन्य मूल्य एक वस्तु है (उदाहरण के लिए सरणियाँ, फ़ंक्शन और ऑब्जेक्ट)। वस्तुओं को आदिम मूल्यों में परिवर्तित करने के लिए ज़बरदस्त प्रक्रिया इस प्रकार वर्णित है:

  • यदि एक आदिम मान वापस किया जाता है जब object.valueOf () लागू किया जाता है, तो इस मान को वापस करें, अन्यथा जारी रखें

  • यदि ऑब्जेक्ट के दौरान एक आदिम मान लौटाया जाता है

  • एक TypeError फेंक दें

नोट: दिनांक मानों के लिए, ऑर्डर को valueOf से पहले toString इनवोक करना है।

  1. यदि कोई भी ऑपरेंड वैल्यू एक स्ट्रिंग है, तो एक स्ट्रिंग कॉन्सेटेशन करें

  2. अन्यथा, दोनों ऑपरेंड को उनके संख्यात्मक मान में परिवर्तित करें और फिर इन मानों को जोड़ें

जावास्क्रिप्ट में प्रकारों के विभिन्न ज़बरदस्त मूल्यों को जानने से भ्रमित आउटपुट को स्पष्ट करने में मदद मिलती है। नीचे ज़बरदस्ती तालिका देखें

+-----------------+-------------------+---------------+
| 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           |
+-----------------+-------------------+---------------+

यह जानना भी अच्छा है कि जावास्क्रिप्ट + संचालक बाएं-सहयोगी है क्योंकि यह निर्धारित करता है कि आउटपुट एक से अधिक ऑपरेशन वाले मामलों में क्या होगा।

इस प्रकार 1 + "2" का उत्तोलन करने से "12" मिलेगा क्योंकि किसी भी तार को जोड़ने से स्ट्रिंग स्ट्रिंग के लिए हमेशा डिफ़ॉल्ट होगा।

आप इस ब्लॉग पोस्ट में अधिक उदाहरण पढ़ सकते हैं (अस्वीकरण मैंने इसे लिखा था)।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.