Pochodzą z MDN
Literały łańcuchowe (oznaczone podwójnymi lub pojedynczymi cudzysłowami) i ciągi zwracane przez wywołania String w kontekście innym niż konstruktor (tj. Bez użycia słowa kluczowego new) są ciągami pierwotnymi. JavaScript automatycznie konwertuje prymitywy na obiekty typu String, dzięki czemu możliwe jest użycie metod obiektów typu String dla łańcuchów pierwotnych. W kontekstach, w których metoda ma być wywoływana na łańcuchu pierwotnym lub następuje wyszukiwanie właściwości, JavaScript automatycznie zawinie łańcuch pierwotny i wywoła metodę lub przeprowadzi wyszukiwanie właściwości.
Pomyślałem więc, że (logicznie) operacje (wywołania metod) na prymitywach łańcuchowych powinny być wolniejsze niż operacje na obiektach łańcuchowych, ponieważ każdy prymityw ciągu jest konwertowany na ciąg Object (dodatkowa praca) przed method
zastosowaniem na łańcuchu.
Ale w tym przypadku testowym wynik jest odwrotny. W polu kodu-1 działa szybciej niż kod blokowy-2 , oba bloki kodu podano poniżej:
blok kodu 1:
var s = '0123456789';
for (var i = 0; i < s.length; i++) {
s.charAt(i);
}
blok kodu 2:
var s = new String('0123456789');
for (var i = 0; i < s.length; i++) {
s.charAt(i);
}
Wyniki różnią się w przeglądarkach, ale blok kodu 1 jest zawsze szybszy. Czy ktoś może to wyjaśnić, dlaczego blok kodu 1 jest szybszy niż blok kodu 2 .
źródło
new String
wprowadza kolejną przezroczystą warstwę zawijania obiektów .typeof new String(); //"object"
'0123456789'.charAt(i)
?code block-1
jest szybszy?Odpowiedzi:
JavaScript ma dwie główne kategorie typów, prymitywne i obiekty.
Wzorce pojedynczego cudzysłowu / podwójnego cudzysłowu są identyczne pod względem funkcjonalności. Poza tym zachowanie, które próbujesz nazwać, nazywa się automatycznym boksowaniem. Tak więc w rzeczywistości dzieje się tak, że prymityw jest konwertowany na swój typ opakowania, gdy wywoływana jest metoda typu opakowania. Mówiąc prosto:
Jest prymitywnym typem danych. Nie ma żadnych metod, jest niczym innym jak wskaźnikiem do surowego odniesienia do pamięci danych, co wyjaśnia znacznie większą prędkość dostępu swobodnego.
Więc co się dzieje, kiedy
s.charAt(i)
na przykład to robisz ?Ponieważ
s
nie jest to instancjaString
, JavaScript będzie automatycznie-boxs
, który matypeof string
swój typ opakowaniaString
, ztypeof object
lub dokładniejs.valueOf(s).prototype.toString.call = [object String]
.Zachowanie auto-boxingu rzutuje w
s
tę iz powrotem do swojego typu opakowania w razie potrzeby, ale standardowe operacje są niewiarygodnie szybkie, ponieważ masz do czynienia z prostszym typem danych. Jednak auto-boxing iObject.prototype.valueOf
mają różne efekty.Jeśli chcesz wymusić automatyczne boksowanie lub rzutowanie prymitywu na jego typ opakowania, możesz użyć
Object.prototype.valueOf
, ale zachowanie jest inne. Bazując na szerokiej gamie scenariuszy testowych, auto-boxing stosuje tylko „wymagane” metody, bez zmiany pierwotnego charakteru zmiennej. Dlatego uzyskujesz lepszą prędkość.źródło
Jest to raczej zależne od implementacji, ale zrobię zdjęcie. Podam przykład z V8, ale zakładam, że inne silniki stosują podobne podejście.
Prymityw łańcuchowy jest przetwarzany na
v8::String
obiekt. W związku z tym metody mogą być wywoływane bezpośrednio na nim, jak wspomniano w jfriend00 .Z drugiej strony obiekt String jest analizowany do postaci
v8::StringObject
rozszerzającej sięObject
i oprócz tego, że jest pełnoprawnym obiektem, służy jako opakowanie dlav8::String
.Teraz jest to tylko logiczne, wywołanie
new String('').method()
musi unbox tov8::StringObject
„sv8::String
przed wykonaniem metody, więc jest wolniejszy.W wielu innych językach wartości pierwotne nie mają metod.
Sposób, w jaki MDN to ujmuje, wydaje się najprostszym sposobem wyjaśnienia, jak działa auto-boksowanie prymitywów (jak również wspomniano w odpowiedzi flava ), czyli w jaki sposób prymitywne wartości -y JavaScript mogą wywoływać metody.
Jednak inteligentny silnik nie konwertuje łańcucha znaków pierwotnych-y na obiekt String za każdym razem, gdy trzeba wywołać metodę. Jest to również informacyjnie wspomniane w opisanej specyfikacji ES5. w odniesieniu do rozwiązywania właściwości (i „metod” ¹) wartości pierwotnych:
Na bardzo niskim poziomie Ciągi są najczęściej implementowane jako niezmienne wartości skalarne. Przykładowa struktura opakowania:
Im dalej jesteś od prymitywu, tym dłużej zajmie ci dotarcie do niego. W praktyce
String
prymitywy występują znacznie częściej niżStringObject
s, dlatego nie jest zaskoczeniem, że silniki dodają metody do klasy odpowiadających (zinterpretowanych) obiektów prymitywów typu String zamiast konwertowania w tę iz powrotem międzyString
iStringObject
jak sugeruje wyjaśnienie MDN.¹ W JavaScript „metoda” jest po prostu konwencją nazewnictwa właściwości, która jest tłumaczona na wartość typu function.
źródło
=]
Teraz zastanawiam się, czy wyjaśnienie MDN jest tam tylko dlatego, że wydaje się być najłatwiejszym sposobem zrozumienia auto-boksu, czy też jest jakieś odniesienie do niego w specyfikacji ES .. Czytając całą specyfikację w tym momencie, aby sprawdzić, pamiętam, aby zaktualizuj odpowiedź, jeśli kiedykolwiek znajdę odniesienie.W przypadku literału tekstowego nie możemy przypisać właściwości
Natomiast w przypadku obiektu typu String możemy przypisać właściwości
źródło
String
przedmiotów. Dziękuję Ci!Literał ciągu:
Literały łańcuchowe są niezmienne, co oznacza, że po utworzeniu ich stan nie może zostać zmieniony, co sprawia, że są one również bezpieczne dla wątków.
a==b
wynikiem będzie „prawda”, oba ciągi odwołują się do tego samego obiektu.Obiekt łańcuchowy:
Tutaj tworzone są dwa różne obiekty, które mają różne odniesienia:
a==b
wynik będzie fałszywy, ponieważ mają różne odniesienia.źródło
a
ib
spróbuj go przypisaća[0] = 'X'
, zostanie wykonany pomyślnie, ale nie zadziała tak, jak można się spodziewaćJeśli używasz
new
, wyraźnie oświadczasz, że chcesz utworzyć wystąpienie Object . Dlategonew String
tworzy obiekt opakowujący prymityw String , co oznacza, że każda akcja na nim wymaga dodatkowej warstwy pracy.Ponieważ są one różnych typów, Twój interpreter JavaScript może również je optymalizować w inny sposób, jak wspomniano w komentarzach .
źródło
Kiedy deklarujesz:
tworzysz łańcuch pierwotny. Ten prymityw łańcuchowy ma metody, które umożliwiają wywoływanie metod bez konwertowania prymitywu na obiekt pierwszej klasy. Więc twoje przypuszczenie, że będzie to wolniejsze, ponieważ łańcuch musi zostać przekonwertowany na obiekt, nie jest poprawne. Nie trzeba go konwertować na obiekt. Sam prymityw może wywołać metody.
Przekształcenie go w pełnowymiarowy obiekt (co pozwala na dodanie do niego nowych właściwości) jest dodatkowym krokiem i nie przyspiesza powstawania ciągu (w rzeczywistości twój test pokazuje, że spowalnia go).
źródło
String.prototype
?var s = '0123456789';
jest prymitywną wartością, jak ta wartość może mieć metody, jestem zdezorientowany!Widzę, że to pytanie zostało rozwiązane dawno temu, istnieje jeszcze jedno subtelne rozróżnienie między literałami strunowymi a obiektami strunowymi, ponieważ nikt nie zdawał się tego dotykać, pomyślałem, że napiszę to tylko dla kompletności.
Zasadniczo inną różnicą między nimi jest użycie eval. eval ('1 + 1') daje 2, podczas gdy eval (new String ('1 + 1')) daje '1 + 1', więc jeśli pewien blok kodu może być wykonany zarówno 'normalnie', jak i z eval, może prowadzą do dziwnych wyników
źródło
new String("")
zwraca obiekt, a eval ocenia tylko ciąg znaków, et zwraca wszystko inne takie, jakie jestIstnienie obiektu ma niewiele wspólnego z rzeczywistym zachowaniem String w silnikach ECMAScript / JavaScript, ponieważ zakres główny będzie zawierał po prostu obiekty funkcji do tego celu. Zatem funkcja charAt (int) w przypadku literału łańcuchowego zostanie przeszukana i wykonana.
W przypadku prawdziwego obiektu dodajesz jeszcze jedną warstwę, w której metoda charAt (int) jest również przeszukiwana w samym obiekcie, zanim zacznie się standardowe zachowanie (tak samo jak powyżej). Najwyraźniej w tym przypadku wykonano zaskakująco dużo pracy.
Swoją drogą, nie sądzę, aby prymitywy były faktycznie konwertowane na obiekty, ale silnik skryptów po prostu oznaczy tę zmienną jako typ łańcuchowy i dlatego może znaleźć wszystkie dostarczone dla niej funkcje, więc wygląda na to, że wywołujesz obiekt. Nie zapominaj, że jest to środowisko wykonawcze skryptu, które działa na innych zasadach niż środowisko wykonawcze OO.
źródło
Największą różnicą między obiektem typu string a obiektem typu string jest to, że obiekty muszą przestrzegać tej reguły dla
==
operatora :Tak więc, podczas gdy prymitywy łańcuchowe mają wygodny sposób
==
porównywania wartości, nie masz szczęścia, jeśli chodzi o sprawianie, że jakikolwiek inny niezmienny typ obiektu (w tym obiekt ciągu) zachowuje się jak typ wartości.(Inni zauważyli, że obiekt typu string jest technicznie zmienny, ponieważ można do niego dodawać właściwości. Ale nie jest jasne, do czego jest to przydatne; sama wartość ciągu nie jest zmienna).
źródło
Kod jest optymalizowany przed uruchomieniem przez silnik javascript. Ogólnie rzecz biorąc, mikro testy porównawcze mogą wprowadzać w błąd, ponieważ kompilatory i interpretery przestawiają, modyfikują, usuwają i wykonują inne sztuczki na częściach kodu, aby działał szybciej. Innymi słowy, napisany kod mówi, jaki jest cel, ale kompilator i / lub środowisko wykonawcze zadecydują, jak go osiągnąć.
Blok 1 jest szybszy głównie z powodu: var s = '0123456789'; jest zawsze szybszy niż var s = new String ('0123456789'); ze względu na koszty tworzenia obiektów.
Część pętli nie jest tą, która powoduje spowolnienie, ponieważ funkcja chartAt () może być wstawiana przez interpreter. Spróbuj usunąć pętlę i ponownie uruchom test, zobaczysz, że współczynnik prędkości będzie taki sam, jak gdyby pętla nie została usunięta. Innymi słowy, w przypadku tych testów bloki pętli w czasie wykonywania mają dokładnie ten sam kod bajtowy / kod maszynowy.
W przypadku tego typu mikro-benchmarków, spojrzenie na kod bajtowy lub kod maszynowy zapewni jaśniejszy obraz.
źródło
W JavaScript prymitywne typy danych, takie jak ciąg, są niezłożonymi blokami konstrukcyjnymi. Oznacza to, że są to tylko wartości, nic więcej:
let a = "string value";
domyślnie nie ma wbudowanych metod, takich jak toUpperCase, toLowerCase itp ...Ale jeśli spróbujesz napisać:
Nie spowoduje to żadnego błędu, zamiast tego będą działać tak, jak powinny.
Co się stało ? Cóż, kiedy próbujesz uzyskać dostęp do właściwości łańcucha,
a
JavaScript wymusza ciąg znaków na obiekt za pomocąnew String(a);
znanego jako obiekt opakowania .Ten proces jest powiązany z koncepcją zwaną konstruktorami funkcji w Javascript, gdzie funkcje są używane do tworzenia nowych obiektów.
Kiedy wpiszesz
new String('String value');
tutaj Ciąg jest konstruktorem funkcji, który przyjmuje argument i tworzy pusty obiekt w zakresie funkcji, ten pusty obiekt jest przypisywany do this aw tym przypadku String dostarcza wszystkie znane wbudowane funkcje, o których wspomnieliśmy wcześniej. a gdy tylko operacja zostanie zakończona, na przykład operacja wielkimi literami, obiekt opakowania jest odrzucany.Aby to udowodnić, zróbmy to:
Tutaj wynik będzie niezdefiniowany. Czemu ? W tym przypadku JavaScript tworzy opakowujący obiekt String, ustawia nową właściwość addNewProperty i natychmiast odrzuca opakowujący obiekt. dlatego stajesz się niezdefiniowany. Pseudo kod wyglądałby tak:
źródło
możemy zdefiniować String na 3 sposoby
// możemy również tworzyć używając 4. var d = a + '';
Sprawdź typ ciągów utworzonych za pomocą operatora typeof
kiedy porównujesz a i b var
a==b ( // yes)
kiedy porównujesz obiekt String
źródło