Czy javascript używa ciągów niezmiennych lub modyfikowalnych? Czy potrzebuję „kreatora ciągów”?
Czy javascript używa ciągów niezmiennych lub modyfikowalnych? Czy potrzebuję „kreatora ciągów”?
Odpowiedzi:
Są niezmienne. Nie możesz zmienić znaku w ciągu za pomocą czegoś takiego var myString = "abbdef"; myString[2] = 'c'
. Metody manipulacji ciągami, takie jak trim
, slice
zwracają nowe ciągi.
W ten sam sposób, jeśli masz dwa odwołania do tego samego ciągu, modyfikowanie jednego nie wpływa na drugie
let a = b = "hello";
a = a + " world";
// b is not affected
Jednak zawsze słyszałem, co Ash wspomniał w swojej odpowiedzi (że użycie Array.join jest szybsze do konkatenacji), więc chciałem przetestować różne metody konkatenacji ciągów i wyodrębnienia najszybszej drogi do StringBuilder. Napisałem kilka testów, aby sprawdzić, czy to prawda (nie jest!).
Uznałem, że to najszybszy sposób, choć ciągle myślałem, że dodanie wywołania metody może spowolnić ...
function StringBuilder() {
this._array = [];
this._index = 0;
}
StringBuilder.prototype.append = function (str) {
this._array[this._index] = str;
this._index++;
}
StringBuilder.prototype.toString = function () {
return this._array.join('');
}
Oto testy prędkości działania. Wszyscy trzej tworzą gigantyczną strunę złożoną ze "Hello diggity dog"
stu tysięcy połączeń w pustą strunę.
Stworzyłem trzy typy testów
Array.push
iArray.join
Array.push
, a następnie używanieArray.join
Potem stworzył te same trzy testy przez abstrahując je StringBuilderConcat
, StringBuilderArrayPush
a StringBuilderArrayIndex
http://jsperf.com/string-concat-without-sringbuilder/5 Proszę tam pojechać i testy zakończą więc możemy uzyskać próbkę piękny. Zauważ, że naprawiłem mały błąd, więc dane do testów zostały wyczyszczone, zaktualizuję tabelę, gdy będzie wystarczająca ilość danych o wydajności. Przejdź do http://jsperf.com/string-concat-without-sringbuilder/5, aby uzyskać starą tabelę danych.
Oto kilka liczb (najnowsza aktualizacja Ma5rch 2018), jeśli nie chcesz korzystać z linku. Liczba na każdym teście to 1000 operacji na sekundę (im wyższa, tym lepiej )
| Browser | Index | Push | Concat | SBIndex | SBPush | SBConcat |
---------------------------------------------------------------------------
| Chrome 71.0.3578 | 988 | 1006 | 2902 | 963 | 1008 | 2902 |
| Firefox 65 | 1979 | 1902 | 2197 | 1917 | 1873 | 1953 |
| Edge | 593 | 373 | 952 | 361 | 415 | 444 |
| Exploder 11 | 655 | 532 | 761 | 537 | 567 | 387 |
| Opera 58.0.3135 | 1135 | 1200 | 4357 | 1137 | 1188 | 4294 |
Wyniki
Obecnie wszystkie wiecznie zielone przeglądarki dobrze radzą sobie z konkatenacją ciągów. Array.join
pomaga tylko IE 11
Ogólnie Opera jest najszybsza, 4 razy szybsza niż Array.join
Firefox jest drugi i Array.join
jest tylko nieco wolniejszy w FF, ale znacznie wolniejszy (3x) w Chrome.
Chrome jest trzeci, ale konkat ciąg jest 3 razy szybszy niż Array.join
Tworzenie StringBuilder wydaje się nie wpływać zbytnio na wydajność.
Mam nadzieję, że ktoś inny uzna to za przydatne
Inny przypadek testowy
Ponieważ @RoyTinker myślał, że mój test jest wadliwy, stworzyłem nowy przypadek, który nie tworzy dużego łańcucha przez połączenie tego samego łańcucha, używa innego znaku dla każdej iteracji. Łączenie strun wciąż wydawało się szybsze lub równie szybkie. Uruchommy te testy.
Sugeruję, aby każdy zastanowił się nad innymi sposobami przetestowania tego i dodaj nowe linki do różnych przypadków testowych poniżej.
join
konkatenacji ciągów vs, a tym samym zbudowanie tablicy przed testem. Nie sądzę, że to oszustwo, jeśli ten cel jest zrozumiany (i join
wylicza tablicę wewnętrznie, więc nie jest też oszustwem pominięcie for
pętli w join
teście).
Array.join
W JavaScript ciągi są niezmiennymi obiektami, co oznacza, że znaki w nich zawarte nie mogą być zmieniane, a wszelkie operacje na ciągach faktycznie tworzą nowe ciągi. Ciągi są przypisywane przez odniesienie, a nie przez wartość. Zasadniczo, gdy obiekt jest przypisywany przez odniesienie, zmiana dokonana w obiekcie za pomocą jednego odwołania będzie widoczna we wszystkich innych odniesieniach do obiektu. Ponieważ ciągów nie można zmienić, możesz mieć wiele odwołań do obiektu ciągów i nie martw się, że wartość ciągu zmieni się bez Twojej wiedzy
null
undefined
number
i boolean
. Ciągi są przypisywane przez wartość, a nie przez odniesienie i są przekazywane jako takie. Zatem ciągi znaków są nie tylko niezmienne, są wartością . Zmiana ciąg "hello"
być "world"
jakby uznając, że od teraz numer 3 to numer 4 ... to nie ma sensu.
var a = "hello";var b=a;a.x=5;console.log(a.x,b.x);
String
obiekty utworzone za pomocą konstruktora ciągów są owijane wokół wartości ciągu JavaScript. Dostęp do wartości ciągu typu pudełkowego można uzyskać za pomocą .valueOf()
funkcji - dotyczy to również Number
obiektów i wartości liczbowych. Ważne jest, aby pamiętać, że String
obiekty utworzone przy użyciu new String
nie są rzeczywistymi ciągami, ale są owijkami lub ramkami wokół ciągów. Zobacz es5.github.io/#x15.5.2.1 . O tym, jak rzeczy przekształcają się w obiekty, zobacz es5.github.io/#x9.9
Wskazówka dotycząca wydajności:
Jeśli musisz połączyć duże łańcuchy, umieść części łańcucha w tablicy i użyj Array.Join()
metody, aby uzyskać cały łańcuch. Może to być wielokrotnie szybsze w przypadku łączenia dużej liczby ciągów.
W StringBuilder
JavaScript nie ma .
Aby wyjaśnić proste umysły, takie jak moje (z MDN ):
Niezmienne są obiekty, których stanu nie można zmienić po utworzeniu obiektu.
Ciąg i liczby są niezmienne.
Niezmienny oznacza, że:
Możesz sprawić, by nazwa zmiennej wskazywała na nową wartość, ale poprzednia wartość jest nadal przechowywana w pamięci. Stąd potrzeba zbierania śmieci.
var immutableString = "Hello";
// W powyższym kodzie tworzony jest nowy obiekt o wartości ciągu.
immutableString = immutableString + "World";
// Teraz dodajemy „Świat” do istniejącej wartości.
Wygląda na to, że mutujemy ciąg „immutableString”, ale tak nie jest. Zamiast:
Po dołączeniu „immutableString” z wartością ciągu występują następujące zdarzenia:
- Pobierana jest istniejąca wartość „immutableString”
- „Świat” jest dołączany do istniejącej wartości „immutableString”
- Wynikowa wartość jest następnie przydzielana do nowego bloku pamięci
- Obiekt „immutableString” wskazuje teraz na nowo utworzoną przestrzeń pamięci
- Wcześniej utworzone miejsce w pamięci jest teraz dostępne do odśmiecania.
Wartość typu ciągu jest niezmienna, ale obiekt String, który jest tworzony za pomocą konstruktora String (), jest zmienny, ponieważ jest to obiekt i można do niego dodawać nowe właściwości.
> var str = new String("test")
undefined
> str
[String: 'test']
> str.newProp = "some value"
'some value'
> str
{ [String: 'test'] newProp: 'some value' }
Tymczasem, chociaż możesz dodawać nowe właściwości, nie możesz zmieniać już istniejących właściwości
Zrzut ekranu z testu w konsoli Chrome
Podsumowując: 1. cała wartość typu łańcucha (typ pierwotny) jest niezmienna. 2. Obiekt String jest zmienny, ale zawarta w nim wartość typu łańcucha (typ pierwotny) jest niezmienna.
new String
generuje zmienne opakowanie wokół niezmiennego ciągu
String
obiektu (opakowania), co oznacza, że nie jest on niezmienny (domyślnie; jak każdy inny obiekt, do którego można go wywołać Object.freeze
, aby stał się niezmienny). Ale prymitywny typ wartości ciągu, niezależnie od tego, czy jest zawarty w String
opakowaniu obiektu, czy nie, jest zawsze niezmienny.
Jeśli chodzi o twoje pytanie (w komentarzu do odpowiedzi Asha) dotyczące StringBuilder w ASP.NET Ajax, eksperci wydają się nie zgadzać w tej kwestii.
Christian Wenz mówi w swojej książce Programowanie ASP.NET AJAX (O'Reilly), że „to podejście nie ma żadnego mierzalnego wpływu na pamięć (w rzeczywistości implementacja wydaje się być wolniejsza niż standardowe”).
Z drugiej strony Gallo i wsp. W swojej książce ASP.NET AJAX in Action (Manning) stwierdzili, że „gdy liczba ciągów do konkatenacji jest większa, konstruktor ciągów staje się niezbędnym obiektem, aby uniknąć ogromnego spadku wydajności”.
Sądzę, że musisz przeprowadzić własne testy porównawcze, a wyniki mogą się różnić w różnych przeglądarkach. Jednak nawet jeśli nie poprawi to wydajności, nadal może być uważane za „przydatne” dla programistów przyzwyczajonych do kodowania za pomocą StringBuilders w językach takich jak C # lub Java.
To późny post, ale wśród odpowiedzi nie znalazłem dobrego cytatu z książki.
Oto określony, z wyjątkiem wiarygodnej książki:
Ciągi są niezmienne w ECMAScript, co oznacza, że po ich utworzeniu ich wartości nie mogą się zmienić. Aby zmienić ciąg przechowywany przez zmienną, oryginalny ciąg musi zostać zniszczony, a zmienna wypełniona innym ciągiem zawierającym nową wartość ... —Profesjonalny JavaScript dla programistów stron internetowych, wydanie trzecie, s. 43
Odpowiedź, która cytuje fragment książki Rhino, słusznie dotyczy niezmienności łańcucha, ale błędnie mówi: „Łańcuchy są przypisywane przez odniesienie, a nie przez wartość”. (prawdopodobnie pierwotnie zamierzali umieścić słowa w odwrotny sposób).
Błędne przekonanie o „referencji / wartości” wyjaśniono w rozdziale „Profesjonalny JavaScript” w rozdziale „Podstawowe i referencyjne wartości”:
Pięć pierwotnych typów ... [są]: Niezdefiniowane, Null, Boolean, Number i String. Mówi się, że do tych zmiennych można uzyskać dostęp według wartości, ponieważ manipulujesz rzeczywistą wartością przechowywaną w zmiennej. —Profesjonalny JavaScript dla programistów stron internetowych, wydanie trzecie, s. 85
w przeciwieństwie do obiektów :
Kiedy manipulujesz obiektem, naprawdę pracujesz nad odniesieniem do tego obiektu, a nie do samego obiektu. Z tego powodu mówi się, że do takich wartości można się odwoływać. —Profesjonalny JavaScript dla programistów stron internetowych, wydanie trzecie, s. 85
Ciągi JavaScript są rzeczywiście niezmienne.
Ciągi w JavaScript są niezmienne