JavaScript'te valueOf () ile toString () karşılaştırması


115

Javascript'te her nesnenin bir valueOf () ve toString () yöntemi vardır. Bir dize dönüşümü çağrıldığında toString () yönteminin çağrıldığını düşünürdüm, ancak görünüşe göre valueOf () tarafından baskılanıyor.

Örneğin, kod

var x = {toString: function() {return "foo"; },
         valueOf: function() {return 42; }};
window.console.log ("x="+x);
window.console.log ("x="+x.toString());

yazdıracak

x=42
x=foo

Bu bana geriye doğru geliyor ... örneğin, x karmaşık bir sayı olsaydı, valueOf () 'nun bana büyüklüğünü vermesini isterdim, ama ne zaman bir dizgeye dönüştürmek istesem "a + bi" gibi bir şey isterdim. Ve bir dizeyi ima eden bağlamlarda toString () 'i açıkça çağırmak zorunda kalmak istemem.

Bu sadece böyle mi?


6
Eğer denedin window.console.log (x);ya alert (x);?
Li0liQ

5
Sırasıyla "Object" ve "foo" verirler. Eğlenceli şeyler.
brainjam

Aslında, alert (x); "foo" ve window.console.log (x) verir; Firebug'da "foo {}" ve Chrome konsolunda tüm Object'i verir.
brainjam

Firefox 33.0.2'de alert(x)görüntülenir foove window.console.log(x)görüntülenir Object { toString: x.toString(), valueOf: x.valueOf() }.
John Sonderson

Yanıtlar:


107

("X =" + x) 'in "x = tostring" değil "x = değer" vermesinin nedeni şudur. "+" Değerini değerlendirirken, javascript önce işlenenlerin ilkel değerlerini toplar ve ardından her bir ilkelin türüne bağlı olarak ekleme veya birleştirme uygulanıp uygulanmayacağına karar verir.

Yani, böyle çalıştığını düşünüyorsun

a + b:
    pa = ToPrimitive(a)
    if(pa is string)
       return concat(pa, ToString(b))
    else
       return add(pa, ToNumber(b))

ve gerçekte olan bu

a + b:
    pa = ToPrimitive(a)
    pb = ToPrimitive(b)*
    if(pa is string || pb is string)
       return concat(ToString(pa), ToString(pb))
    else
       return add(ToNumber(pa), ToNumber(pb))

Yani, toString orijinal nesnenize değil valueOf sonucuna uygulanır.

Daha fazla referans için, ECMAScript Dil Spesifikasyonundaki 11.6.1 Ekleme operatörü (+) bölümüne bakın .


* Dize bağlamında çağrıldığında, ToPrimitive yapar toString çağırmak, ancak '+' her tür içeriği zorlamaz çünkü bu, burada durum böyle değil.


3
Eğer (pa dizedir && pb dizgedir) "ise" aslında "blok okunması" ndaki koşulun olması gerekmez mi? Yani, "||" yerine "&&" ?
brainjam

3
Standart kesinlikle "veya" der (bağlantıya bakın).
user187291

2
Evet, bu tam olarak doğru - dizgilere diğer türlere göre birleştirme sırasında öncelik verilir. Eğer her iki işlenen bir dizidir şeyin tamamı bir dize olarak birleştirilmiş olacaktır. İyi cevap.
devios1

76

Cevaba ulaşmadan önce işte biraz daha ayrıntı:

var x = {
    toString: function () { return "foo"; },
    valueOf: function () { return 42; }
};

alert(x); // foo
"x=" + x; // "x=42"
x + "=x"; // "42=x"
x + "1"; // 421
x + 1; // 43
["x=", x].join(""); // "x=foo"

toStringFonksiyon olduğunu değil tarafından "uydurulmuş" valueOfgenel olarak. ECMAScript standardı aslında bu soruyu oldukça iyi yanıtlıyor. Her nesnenin [[DefaultValue]]talep üzerine hesaplanan bir özelliği vardır . Bu özelliği sorarken, yorumlayıcı ayrıca ne tür bir değer beklediğine dair bir "ipucu" sağlar. İpucu ise String, daha toStringönce kullanılır valueOf. Ancak ipucu ise Number, o valueOfzaman önce kullanılacaktır. Yalnızca biri varsa veya bir ilkel olmayan döndürürse, genellikle diğerini ikinci seçenek olarak arayacağını unutmayın.

+Operatör her zaman bir ipucu vermiş Numberilk işlenen bir dize değeri olsa bile. Olsa sorar xonun için Numberilk işlenen bir dize gelen döner beri, temsil [[DefaultValue]]onu dize birleştirme yapar.

toStringDize birleştirme için çağrıldığından emin olmak istiyorsanız , bir dizi ve .join("")yöntemi kullanın.

(Ancak ActionScript 3.0, davranışını biraz değiştirir +. İşlenenlerden biri a ise String, bunu bir dize birleştirme operatörü olarak değerlendirecek ve Stringçağrıldığında ipucunu kullanacaktır [[DefaultValue]]. Dolayısıyla, AS3'te bu örnek "foo, x = foo, foo" sonucunu verir. = x, foo1, 43, x = foo ".)


1
Ayrıca , ilkel olmayanlar if valueOfveya toStringreturn, bunların yok sayılacağını unutmayın. Hiçbiri yoksa veya hiçbiri bir ilkel döndürmezse, o zaman a TypeErroratılır.
bcherry

1
Teşekkürler bcherry, umduğum yanıtın kalibresi bu. Ancak x + "x =" olmamalıdır; "42x =" verir? Ve x + "1"; verim 421? Ayrıca, ECMAScript standardının ilgili kısmı için bir URL'niz var mı?
brainjam

2
Aslında, '+' ipucu kullanmaz (bkz. 11.6.1 $), bu nedenle ToPrimitive [[DefaultValue]](no-hint), eşdeğeri olan [[DefaultValue]](number).
user187291

9
Yerleşik Date sınıfı için durum böyle görünmüyor. ("" + new Date(0)) === new Date(0).toString(). Bir Date nesnesi toString(), bir şeye eklendiğinde her zaman değerini döndürüyor gibi görünür .
kpozin

7
+1 & Thx! Bu cevabı detaylandırdığınız ve buraya bağlamak / paylaşmak istediğiniz blog yazınızı buldum . Bu yanıta gerçekten yardımcı oldu (Dmitry A. Soshnikov'un yorumu dahil).
GitaarLAB

1

TLDR

Tür zorlama veya örtük tür dönüştürme, zayıf yazmayı etkinleştirir ve JavaScript genelinde kullanılır. Çoğu (sıkı eşitlik işlemleri önemli hariç operatörler ===ve !==) ve değer kontrol işlemleri (örn. if(value)...Bu değerlerin türleri operasyonla hemen uyumlu değilse), kendilerine verilen değerleri zorlamak olacaktır.

Bir değeri zorlamak için kullanılan kesin mekanizma, değerlendirilen ifadeye bağlıdır. Soruda toplama operatörü kullanılıyor.

Toplama operatörü ilk önce her iki işlenenin de ilkel olmasını sağlayacaktır, bu durumda bu durumda valueOfyöntemi çağırmayı içerir . toStringGeçersiz kılınan için yöntem olup, bu durumda adı verilir valueOfnesne üzerinde bir yöntem xtemel bir değeri verir.

Ardından, sorudaki işlenenlerden biri bir dizge olduğundan, her iki işlenen de dizelere dönüştürülür. Bu süreç soyut, dahili işlemi ToString(not: büyük harfle yazılmış) kullanır ve toStringnesnedeki (veya prototip zincirindeki) yöntemden farklıdır .

Son olarak, ortaya çıkan dizeler birleştirilir.

ayrıntılar

JavaScript'teki her dil türüne karşılık gelen (yani Number, BigInt, String, Boolean, Symbol ve Object) her yapıcı işlev nesnesinin prototipinde iki yöntem vardır: valueOfve toString.

Amacı, valueOfbir nesneyle ilişkili ilkel değeri (eğer varsa) geri getirmektir. Bir nesnenin altında yatan bir ilkel değer yoksa, o zaman nesne basitçe döndürülür.

Bir valueOfilkele karşı çağrılırsa, ilkel normal şekilde otomatik olarak kutuya alınır ve temel ilkel değer döndürülür. Dizeler için temel ilkel değerin (yani tarafından döndürülen değer valueOf) dize temsilinin kendisi olduğuna dikkat edin.

Aşağıdaki kod, valueOfyöntemin temel ilkel değeri bir sarmalayıcı nesnesinden döndürdüğünü ve ilkellere karşılık gelmeyen, değiştirilmemiş nesne örneklerinin döndürülecek ilkel bir değere sahip olmadığını, bu nedenle yalnızca kendilerini döndürdüklerini gösterir.

console.log(typeof new Boolean(true)) // 'object'
console.log(typeof new Boolean(true).valueOf()) // 'boolean'
console.log(({}).valueOf()) // {} (no primitive value to return)

Amacı toString, diğer taraftan, bir nesnenin dize gösterimini dönmek olduğunu.

Örneğin:

console.log({}.toString()) // '[object Object]'
console.log(new Number(1).toString()) // '1'

Çoğu işlem için JavaScript sessizce bir veya daha fazla işleneni gerekli türe dönüştürmeye çalışır. Bu davranış, JavaScript'in kullanımını kolaylaştırmak için seçildi. JavaScript başlangıçta istisnalara sahip değildi ve bu da bu tasarım kararında bir rol oynamış olabilir. Bu tür örtük tür dönüştürmeye tür zorlama denir ve JavaScript'in gevşek (zayıf) tür sisteminin temelini oluşturur. Bu davranışın arkasındaki karmaşık kuralların amacı, yazımın karmaşıklığını dilin kendisine ve kodunuzun dışına taşımaktır.

Zorlayıcı süreç sırasında, meydana gelebilecek iki dönüşüm modu vardır:

  1. Bir nesnenin bir ilkele dönüştürülmesi (bu, bir tür dönüşümünün kendisini içerebilir) ve
  2. İlkel türlerinden biri, bir yapıcı işlevi nesnesini kullanarak belirli bir tür, örneğin doğrudan dönüştürme, (yani. Number(), Boolean(), String()Vs.)

Bir İlkele Dönüşüm

İlkel olmayan türleri, üzerinde çalışılacak ilkel türlere dönüştürmeye çalışırken, soyut işlem ToPrimitive, isteğe bağlı bir "sayı" veya "dizge" "ipucu" ile çağrılır. İpucu atlanırsa, varsayılan ipucu 'sayı'dır ( @@toPrimitiveyöntem geçersiz kılınmadıkça). İpucu 'dizge' ise, o zaman toStringönce denenir ve valueOfikinci eğer toStringbir ilkel döndürmezse. Aksi takdirde, tersi. İpucu, dönüştürmeyi talep eden işleme bağlıdır.

Toplama operatörü hiçbir ipucu vermez, bu nedenle valueOfönce denenir. Çıkarma operatörü bir 'sayı' ipucu verir, bu nedenle valueOfilk denenir. İpucunun 'dize' olduğu spesifikasyonda bulabildiğim tek durumlar şunlardır:

  1. Object#toString
  2. ToPropertyKeyBir bağımsız değişkeni, özellik anahtarı olarak kullanılabilecek bir değere dönüştüren soyut işlem

Doğrudan Tür Dönüştürme

Her operatörün işlemlerini tamamlamak için kendi kuralları vardır. Toplama operatörü ilk olarak ToPrimitiveher işlenenin bir ilkel olmasını sağlamak için kullanacaktır ; daha sonra, işlenenlerden biri bir dizge ise, ToStringdizelerle beklediğimiz dizge birleştirme davranışını sunmak için kasıtlı olarak her işlenen üzerinde soyut işlemi başlatır. ToPrimitiveAdımdan sonra her iki işlenen de dize değilse, aritmetik toplama gerçekleştirilir.

Toplamadan farklı olarak, çıkarma operatörü aşırı yüklenmiş davranışa sahip değildir ve bu nedenle, toNumericbunları kullanarak ilk önce onları ilkellere dönüştüren her işleneni çağıracaktır ToPrimitive.

Yani:

 1  +  1   //  2                 
'1' +  1   // '11'   Both already primitives, RHS converted to string, '1' + '1',   '11'
 1  + [2]  // '12'   [2].valueOf() returns an object, so `toString` fallback is used, 1 + String([2]), '1' + '2', 12
 1  + {}   // '1[object Object]'    {}.valueOf() is not a primitive, so toString fallback used, String(1) + String({}), '1' + '[object Object]', '1[object Object]'
 2  - {}   // NaN    {}.valueOf() is not a primitive, so toString fallback used => 2 - Number('[object Object]'), NaN
+'a'       // NaN    `ToPrimitive` passed 'number' hint), Number('a'), NaN
+''        // 0      `ToPrimitive` passed 'number' hint), Number(''), 0
+'-1'      // -1     `ToPrimitive` passed 'number' hint), Number('-1'), -1
+{}        // NaN    `ToPrimitive` passed 'number' hint', `valueOf` returns an object, so falls back to `toString`, Number('[Object object]'), NaN
 1 + 'a'   // '1a'    Both are primitives, one is a string, String(1) + 'a'
 1 + {}    // '1[object Object]'    One primitive, one object, `ToPrimitive` passed no hint, meaning conversion to string will occur, one of the operands is now a string, String(1) + String({}), `1[object Object]`
[] + []    // ''     Two objects, `ToPrimitive` passed no hint, String([]) + String([]), '' (empty string)
 1 - 'a'   // NaN    Both are primitives, one is a string, `ToPrimitive` passed 'number' hint, 1-Number('a'), 1-NaN, NaN
 1 - {}    // NaN    One primitive, one is an object, `ToPrimitive` passed 'number' hint, `valueOf` returns object, so falls back to `toString`, 1-Number([object Object]), 1-NaN, NaN
[] - []    // 0      Two objects, `ToPrimitive` passed 'number' hint => `valueOf` returns array instance, so falls back to `toString`, Number('')-Number(''), 0-0, 0

O Not Datevarsayılan geçersiz kılmak için yalnızca içsel olması ile içsel nesne, benzersiz @@toPrimitivevarsayılan ipucu (daha doğrusu 'numarası' den) 'dizge' olduğu tahmin edildiği yöntem. Buna sahip olmanın nedeni Date, programcının rahatlığı için örneklerin sayısal değerleri yerine varsayılan olarak okunabilir dizelere çevrilmesidir. @@toPrimitiveKullanarak kendi nesnelerinizde geçersiz kılabilirsiniz Symbol.toPrimitive.

Aşağıdaki ızgara, soyut eşitlik operatörü ( ==) ( kaynak ) için zorlama sonuçlarını gösterir :

görüntü açıklamasını buraya girin

Bkz ayrıca .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.