JavaScript'teki dize ilkelleri ile String nesneleri arasındaki fark nedir?


116

Alındığı MDN'yi

Dize değişmezleri (çift veya tek tırnak ile gösterilir) ve yapıcı olmayan bir bağlamda (yani, new anahtar sözcüğü kullanılmadan) String çağrılarından döndürülen dizeler ilkel dizelerdir. JavaScript, ilkelleri otomatik olarak String nesnelerine dönüştürür, böylece ilkel dizeler için String nesnesi yöntemlerini kullanmak mümkündür. Bir yöntemin ilkel bir dizede çağrılacağı veya özellik aramasının gerçekleştiği bağlamlarda, JavaScript otomatik olarak dizeyi ilkel olarak sarar ve yöntemi çağırır veya özellik araması gerçekleştirir.

Bu nedenle, dizge ilkelleri üzerindeki işlemlerin (yöntem çağrılarının) dize Nesneleri üzerindeki işlemlerden daha yavaş olması gerektiğini düşündüm çünkü herhangi bir ilkel dize method, dizeye uygulanmadan önce dizge Nesnesine (ekstra iş) dönüştürülür .

Ancak bu test durumunda sonuç tam tersidir. Kod bloğu-1 daha hızlı çalışır kod bloğu-2 , hem kod blokları aşağıda verilmiştir:

kod bloğu-1:

var s = '0123456789';
for (var i = 0; i < s.length; i++) {
  s.charAt(i);
}

kod bloğu-2:

var s = new String('0123456789');
for (var i = 0; i < s.length; i++) {
    s.charAt(i);
}

Sonuçlar tarayıcılarda değişiklik gösterir ancak kod bloğu-1 her zaman daha hızlıdır. Biri bunu açıklayabilir mi, neden blok-1 kod bloğu-2'den daha hızlıdır .


6
Kullanmak new String, başka bir şeffaf Nesne sarma katmanı sunar . typeof new String(); //"object"
Paul S.

peki ya '0123456789'.charAt(i)?
Yuriy Galanter

@YuriyGalanter, sorun değil ama neden code block-1daha hızlı diye soruyorum ?
The Alpha

2
Dize nesneleri, gerçek yaşam bağlamında görülmesi nispeten nadirdir, bu nedenle yorumlayıcıların dizgi değişmezlerini optimize etmeleri şaşırtıcı değildir. Günümüzde kodunuz basitçe yorumlanmıyor , perde arkasında gerçekleşen birçok optimizasyon katmanı var.
Fabrício Matté

2
Bu garip: revizyon 2
hjpotter92

Yanıtlar:


149

JavaScript'in iki ana tür kategorisi vardır: ilkeller ve nesneler.

var s = 'test';
var ss = new String('test');

Tek tırnak / çift tırnak modelleri, işlevsellik açısından aynıdır. Bunun yanı sıra, adlandırmaya çalıştığınız davranışa otomatik boks denir. Öyleyse gerçekte olan şey, sarmalayıcı türünün bir yöntemi çağrıldığında bir ilkelin sarmalayıcı türüne dönüştürülmesidir. Basitçe söyleyin:

var s = 'test';

İlkel bir veri türüdür. Yöntemi yoktur, ham veri belleği referansına bir göstericiden başka bir şey değildir, bu da çok daha hızlı rastgele erişim hızını açıklar.

Peki s.charAt(i)mesela bunu yaptığınızda ne olur ?

Bu yana sbir örneği değil StringJavaScript olacak otomatik kutusuna ssahip, typeof stringkendi sargı tipi Stringile, typeof objectya da daha doğrusu s.valueOf(s).prototype.toString.call = [object String].

Otomatik kutulama davranışı s, gerektiğinde sarmalayıcı türüne ileri geri döner, ancak daha basit bir veri türü ile uğraştığınız için standart işlemler inanılmaz derecede hızlıdır. Ancak otomatik boks ve Object.prototype.valueOffarklı etkilere sahiptir.

Otomatik kutulamayı zorlamak veya sarmalayıcı türüne bir ilkel dönüştürmek istiyorsanız, kullanabilirsiniz Object.prototype.valueOf, ancak davranış farklıdır. Çok çeşitli test senaryolarına dayalı olarak otomatik kutulama, değişkenin ilkel doğasını değiştirmeden yalnızca 'gerekli' yöntemleri uygular. Bu yüzden daha iyi hız elde edersiniz.


33

Bu daha çok uygulamaya bağlıdır, ancak bir şans vereceğim. V8 ile örnekleyeceğim ama diğer motorların da benzer yaklaşımlar kullandığını varsayıyorum.

Bir ilkel dizge bir v8::Stringnesneye çözümlenir . Bu nedenle, jfriend00 tarafından belirtildiği gibi yöntemler doğrudan onun üzerinde çağrılabilir .

Bir String nesnesi, diğer yandan, bir ile ayrıştırılır v8::StringObjectolan uzanır Objectve ayrı bir tam teşekküllü bir nesne olmaktan, bir sargı olarak hizmet v8::String.

Şimdi bu bir çağrı, sadece mantıklı new String('').method()bu Unbox zorundadır v8::StringObject's v8::Stringyöntemi yürütmeden önce dolayısıyla yavaştır.


Diğer birçok dilde, ilkel değerlerin yöntemleri yoktur.

MDN'nin bunu ifade etme şekli, ilkellerin otomatik kutulamanın nasıl çalıştığını ( flav'ın yanıtında da bahsedildiği gibi ), yani JavaScript'in ilkel-y değerlerinin yöntemleri nasıl çağırabileceğini açıklamanın en basit yolu gibi görünüyor .

Ancak, akıllı bir motor, bir yöntemi her çağırmanız gerektiğinde, ilkel-y dizesini String nesnesine dönüştürmez . Bu ayrıca Açıklamalı ES5 spesifikasyonunda bilgilendirici olarak belirtilmiştir . ilkel değerlerin özelliklerini (ve "yöntemlerini" ¹) çözmekle ilgili olarak:

NOT 1. adımda oluşturulabilen nesneye yukarıdaki yöntemin dışında erişilemez. Bir uygulama, nesnenin fiilen yaratılmasını engellemeyi seçebilir. [...]

Çok düşük düzeyde, Dizeler çoğunlukla değişmez skaler değerler olarak uygulanır. Örnek sarmalayıcı yapısı:

StringObject > String (> ...) > char[]

İlkelden ne kadar uzak olursanız, ona ulaşmak o kadar uzun sürer. Pratikte, Stringilkeller StringObjects'den çok daha sıktır , bu nedenle motorların MDN'nin açıklamasının önerdiği gibi aralarında Stringve ileri geri dönüşüm yapmak yerine String ilkellerinin karşılık gelen (yorumlanan) nesnelerin Sınıfına yöntemler eklemesi şaşırtıcı değildir StringObject.


¹ JavaScript'te "yöntem", işlev türünün bir değerine çözümlenen bir özellik için yalnızca bir adlandırma kuralıdır.


1
Rica ederim. =]Şimdi merak ediyorum, MDN'nin açıklamasının sadece otomatik kutuyu anlamanın en kolay yolu olduğu için mi yoksa ES spesifikasyonunda buna herhangi bir referans olup olmadığı için mi orada olduğunu merak ediyorum. Bir referans bulursam cevabı güncelleyin.
Fabrício Matté

V8'in uygulamasına ilişkin harika bilgiler. Boksun sadece işlevi çözmek için orada olmadığını da eklemeliyim. Bu referansı yönteme geçirmek için de var. Şimdi, V8'in yerleşik yöntemler için bunu atlayıp atlamadığından emin değilim, ancak String.prototype demek için kendi uzantınızı eklerseniz, her çağrıldığında string nesnesinin kutulu bir versiyonunu alacaksınız.
Ben

17

Dize değişmezi durumunda, özellikleri atayamayız

var x = "hello" ;
x.y = "world";
console.log(x.y); // this will print undefined

String Object durumunda ise özellikleri atayabiliriz

var x = new String("hello");
x.y = "world";
console.log(x.y); // this will print world

1
Sonunda biri neden Stringnesnelere ihtiyacımız olduğunu motive ediyor . Teşekkür ederim!
Ciprian Tomoiagă

1
Neden buna ihtiyaç duyulsun ki?
Aditya

11

Dize Değişmez Değeri:

Dize değişmezleri değişmezdir, yani bir kez oluşturulduklarında durumları değiştirilemez, bu da onları iş parçacığı güvenli kılar.

var a = 's';
var b = 's';

a==b sonuç 'true' olur her iki dizge de aynı nesneye başvurur.

Dize Nesnesi:

Burada iki farklı nesne oluşturulur ve bunların farklı referansları vardır:

var a = new String("s");
var b = new String("s");

a==b farklı referanslara sahip oldukları için sonuç yanlış olacaktır.


1
Dize nesnesi de değişmez mi?
Yang Wang

Her ikisi için de, saçma bir dildir @YangWang a& batamak için denemek a[0] = 'X'o olacak başarıyla yürütülebilir ancak iş Tahmin edebileceğiniz gibi
Rux

"Var a = 's' yazdınız; var b = 's'; a == b sonucu 'true' olacak, her iki dizge de aynı nesneye başvuruyor." Bu doğru değil: a ve b aynı nesneye atıfta bulunmaz, sonuç doğrudur çünkü aynı değere sahiptirler. Bu değerler farklı bellek konumlarında saklanır, bu yüzden birini değiştirirseniz diğeri değişmez!
SC1000

9

Eğer kullanırsanız new, açıkça bir örneğini oluşturmak istediğinizi belirten ediyoruz Nesne . Bu nedenle, String ilkelini saran new Stringbir Nesne üretiyor , bu da üzerindeki herhangi bir eylemin fazladan bir çalışma katmanı içerdiği anlamına geliyor.

typeof new String(); // "object"
typeof '';           // "string"

Farklı türlerde olduklarından, JavaScript yorumlayıcınız da yorumlarda belirtildiği gibi bunları farklı şekilde optimize edebilir .


5

Beyan ettiğinizde:

var s = '0123456789';

ilkel bir dizge yaratırsınız. Bu ilkel dize, ilkeli birinci sınıf nesneye dönüştürmeden üzerinde yöntemler çağırmanıza izin veren yöntemlere sahiptir. Öyleyse, dizenin bir nesneye dönüştürülmesi gerektiği için bunun daha yavaş olacağı varsayımınız doğru değil. Bir nesneye dönüştürülmesi gerekmez. İlkelin kendisi yöntemleri çağırabilir.

Onu tam gelişmiş bir nesneye dönüştürmek (bu, ona yeni özellikler eklemenize izin verir) fazladan bir adımdır ve dizi işlemlerini daha hızlı yapmaz (aslında testiniz onları daha yavaş yaptığını gösterir).


İlkel dize, özel String.prototypeolanlar dahil tüm prototip özelliklerini nasıl devralır ?
Yuriy Galanter

1
var s = '0123456789';ilkel bir değer, bu değerin nasıl yöntemleri olabilir, kafam karıştı!
The Alpha

2
@SheikhHeera - tercümanın onlara özel yetkiler verebilmesi için ilkeller dil uygulamasına yerleştirilmiştir.
jfriend00

1
@SheikhHeera - Son yorumunuzu / sorunuzu anlamadım. Bir ilkel dize kendi başına kendi özelliklerinizi eklemenizi desteklemez. Buna izin vermek için, javascript ayrıca bir string ilkeliyle aynı yöntemlere sahip, ancak her yönden bir nesne gibi davranabileceğiniz tam gelişmiş bir nesne olan bir String nesnesine de sahiptir. Bu ikili biçim biraz dağınık görünmektedir, ancak% 99 vakası ilkellerin kullanımı olduğundan ve muhtemelen dizgi nesnelerinden daha hızlı ve bellek açısından daha verimli olabileceğinden, bunun bir performans uzlaşması olarak yapıldığından şüpheleniyorum.
jfriend00

1
@SheikhHeera "Nesne dizesine dönüştürüldü", MDN'nin ilkellerin yöntemleri nasıl çağırabildiğini açıklamak için bunu ifade etme şeklidir. Kelimenin tam anlamıyla string Nesnelerine dönüştürülmezler.
Fabrício Matté

4

Bu sorunun uzun zaman önce çözüldüğünü görebiliyorum, dizgi değişmezleri ile dizgi nesneleri arasında başka bir ince ayrım var, kimse ona dokunmamış gibi görünüyor, sadece bütünlük için yazmayı düşündüm.

Temel olarak ikisi arasındaki diğer bir ayrım, eval kullanılırken. eval ('1 + 1') 2 verir, oysa eval (new String ('1 + 1')) '1 + 1' verir, bu nedenle belirli bir kod bloğu hem 'normal olarak' hem de eval ile yürütülebilirse, tuhaf sonuçlara yol açmak


Katkılarınız için teşekkürler :-)
Alpha

Vay canına, bu gerçekten tuhaf bir davranış. Bu davranışı göstermek için yorumunuza küçük bir satır içi demo eklemelisiniz - son derece göz açıcı.
EyuelDK

Eğer düşünürseniz bu normaldir. new String("")bir nesneyi döndür ve yalnızca dizeyi değerlendir, ve diğer her şeyi olduğu gibi
Félix Brunet

3

Bir nesnenin varlığının, ECMAScript / JavaScript motorlarındaki bir String'in gerçek davranışıyla çok az ilgisi vardır, çünkü kök kapsamı basitçe bunun için işlev nesneleri içerecektir. Dolayısıyla, değişmez bir dizge olması durumunda charAt (int) işlevi aranacak ve çalıştırılacaktır.

Gerçek bir nesne ile, standart davranış devreye girmeden önce (yukarıdakiyle aynı) charAt (int) yönteminin nesnenin kendisinde de arandığı bir katman daha eklersiniz. Görünüşe göre bu durumda şaşırtıcı derecede büyük miktarda iş yapılmış.

BTW İlkellerin aslında Nesnelere dönüştürüldüğünü düşünmüyorum, ancak komut dosyası motoru bu değişkeni dizge türü olarak işaretleyecek ve bu nedenle onun için sağlanan tüm işlevleri bulabilir, böylece bir nesneyi çağırıyormuşsunuz gibi görünür. Unutmayın, bu bir OO çalışma zamanından farklı ilkeler üzerinde çalışan bir betik çalışma zamanıdır.


3

Bir dize ilkeli ile bir dize nesnesi arasındaki en büyük fark, nesnelerin operatör için bu kurala== uyması gerektiğidir :

Nesneleri karşılaştıran bir ifade yalnızca işlenenler aynı Nesneye başvuruyorsa doğrudur.

Öyleyse, dizge ilkelleri ==değeri karşılaştıran kullanışlılığa sahipken , başka herhangi bir değişmez nesne türünün (bir dize nesnesi dahil) bir değer türü gibi davranmasını sağlama konusunda şansınız kalmaz.

"hello" == "hello"
-> true
new String("hello") == new String("hello") // beware!
-> false

(Diğerleri, bir dize nesnesinin teknik olarak değiştirilebilir olduğunu, çünkü ona özellikler ekleyebileceğinizi belirtmişlerdir. Ancak bunun ne için yararlı olduğu açık değildir; dize değerinin kendisi değişebilir değildir.)


Uzun bir süre sonra soruya değer kattığınız için teşekkürler :-)
The Alpha

1

Kod, javascript motoru tarafından çalıştırılmadan önce optimize edilmiştir. Genel olarak, mikro ölçütler yanıltıcı olabilir çünkü derleyiciler ve yorumlayıcılar kodunuzun daha hızlı çalışmasını sağlamak için kodunuzun bölümlerini yeniden düzenler, değiştirir, kaldırır ve başka hileler uygular. Başka bir deyişle, yazılı kod hedefin ne olduğunu söyler, ancak derleyici ve / veya çalışma zamanı bu hedefe nasıl ulaşılacağına karar verir.

Blok 1 daha hızlıdır çünkü şunlardan biridir: var s = '0123456789'; her zaman var s = new String ('0123456789') değerinden daha hızlıdır; nesne yaratmanın ek yükü nedeniyle.

Döngü kısmı yavaşlamaya neden olan kısım değildir çünkü chartAt () yorumlayıcı tarafından satır içine alınabilir. Döngüyü kaldırmayı deneyin ve testi tekrar çalıştırın, hız oranının döngü kaldırılmamış gibi olacağını göreceksiniz. Diğer bir deyişle, bu testler için, yürütme sırasındaki döngü blokları tam olarak aynı bayt kodu / makine koduna sahiptir.

Bu tür mikro karşılaştırmalar için, bayt koduna veya makine koduna bakmak daha net bir resim sağlayacaktır.


1
Cevabınız için teşekkürler.
The Alpha

0

Javascript'te, string gibi ilkel veri türleri, bileşik olmayan bir yapı taşıdır. Bu, bunların sadece değerler olduğu anlamına gelir, başka bir şey değildir: let a = "string value"; Varsayılan olarak, toUpperCase, toLowerCase vb. Gibi yerleşik yöntemler yoktur ...

Ama yazmaya çalışırsan:

console.log( a.toUpperCase() ); or console.log( a.toLowerCase() );

Bu herhangi bir hata vermeyecek, bunun yerine olması gerektiği gibi çalışacak.

Ne oldu ? Bir dizgenin bir özelliğine erişmeye çalıştığınızda, aJavascript dizgeyi sarmalayıcı nesnenew String(a); olarak bilinen bir nesneye zorlar .

Bu süreç, işlevlerin yeni nesneler oluşturmak için kullanıldığı Javascript'teki işlev yapıcıları olarak adlandırılan kavramla bağlantılıdır .

Eğer yazdığınızda new String('String value');burada Dize bu boş nesne atanır, bir argüman alır ve fonksiyon kapsamı içine boş bir nesne oluşturur fonksiyon yapıcısı olduğu bu ve bu durumda, dize Daha önce de belirttiğimiz fonksiyonları inşa bilinen tüm bu besler. ve işlem tamamlanır tamamlanmaz, örneğin büyük harf işlemi yaparsanız, sarıcı nesne atılır.

Bunu kanıtlamak için şunu yapalım:

let justString = 'Hello From String Value';
justString.addNewProperty = 'Added New Property';
console.log( justString );

Burada çıktı tanımsız olacaktır. Neden ? Bu durumda, Javascript sarmalayıcı String nesnesi oluşturur, addNewProperty yeni özelliğini ayarlar ve sarmalayıcı nesnesini hemen atar. bu yüzden tanımlanamazsın. Sözde kod şöyle görünecektir:

let justString = 'Hello From String Value';
let wrapperObject = new String( justString );
wrapperObject.addNewProperty = 'Added New Property'; //Do operation and discard

0

String'i 3 yolla tanımlayabiliriz

  1. var a = "birinci yol";
  2. var b = String ("ikinci yol");
  3. var c = new String ("üçüncü yol");

// 4. kullanarak da oluşturabiliriz. var d = a + '';

Typeof operatörü kullanılarak oluşturulan dizelerin türünü kontrol edin

  • typeof a // "dizge"
  • typeof b // "dizge"
  • typeof c // "nesne"


a ve b var'ı karşılaştırdığınızda a==b ( // yes)


String nesnesini karşılaştırdığınızda

var StringObj = new String("third way")
var StringObj2 = new String("third way")
StringObj  == StringObj2 // no result will be false, because they have different references
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.