Czy ciągi JavaScript są niezmienne? Czy potrzebuję „kreatora ciągów” w JavaScript?


237

Czy javascript używa ciągów niezmiennych lub modyfikowalnych? Czy potrzebuję „kreatora ciągów”?


3
Tak, y są niezmienne i potrzebujesz jakiegoś „konstruktora ciągów”. Przeczytaj ten blog.codeeffects.com/Article/String-Builder-In-Java-Script lub ten codeproject.com/KB/scripting/stringbuilder.aspx
Kizz

2
Co ciekawe, przykłady te są sprzeczne z moimi ustaleniami w mojej odpowiedzi.
Juan Mendes

Odpowiedzi:


294

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, slicezwracają 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

  • Korzystanie Array.pushiArray.join
  • Używanie indeksowania tablic w celu uniknięcia Array.push, a następnie używanieArray.join
  • Łączenie strun prostych

Potem stworzył te same trzy testy przez abstrahując je StringBuilderConcat, StringBuilderArrayPusha 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.joinpomaga tylko IE 11

  • Ogólnie Opera jest najszybsza, 4 razy szybsza niż Array.join

  • Firefox jest drugi i Array.joinjest 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.

http://jsperf.com/string-concat-without-sringbuilder/7


@Juan, link, o który prosiłeś nas odwiedzić, łączy łańcuch znaków 112 znaków 30 razy. Oto kolejny test, który może pomóc zrównoważyć sytuację - Array.join vs konkatenacja łańcuchów na 20 000 różnych łańcuchów 1- znakowych (łączenie jest znacznie szybsze na IE / FF). jsperf.com/join-vs-str-concat-large-array
Roy Tinker

1
@ RoyTinker Roy, och, Roy, twoje testy oszukują, ponieważ tworzysz tablicę w ustawieniach testu. Oto prawdziwy test z użyciem różnych znaków jsperf.com/string-concat-without-sringbuilder/7 Możesz tworzyć nowe przypadki testowe, ale tworzenie tablicy jest częścią samego testu
Juan Mendes

@JuanMendes Moim celem było zawężenie przypadku testowego do ścisłego porównania joinkonkatenacji ciągów vs, a tym samym zbudowanie tablicy przed testem. Nie sądzę, że to oszustwo, jeśli ten cel jest zrozumiany (i joinwylicza tablicę wewnętrznie, więc nie jest też oszustwem pominięcie forpętli w jointeście).
Roy Tinker,

2
@RoyTinker Tak, każdy konstruktor ciągów będzie wymagał zbudowania tablicy. Pytanie dotyczy tego, czy potrzebny jest konstruktor ciągów. Jeśli masz już ciągi znaków w tablicy, to nie jest to poprawny przypadek testowy dla tego, co tutaj omawiamy
Juan Mendes

1
@Baltusaj Używają kolumn z napisem Indeks / PushArray.join
Juan Mendes

41

z książki o nosorożcach :

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


7
Link do odpowiedniej sekcji książki o nosorożcach: books.google.com/…
baudtack

116
Cytat z książki Rhino (a więc i ta odpowiedź) jest tutaj błędny . W JavaScript łańcuchy są pierwotnymi typami wartości, a nie obiektami (specyfikacjami) . W rzeczywistości, od ES5, są jednym z 5 typów wartości obok null undefined numberi 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.
Benjamin Gruenbaum,

8
Tak, podobnie jak mój komentarz mówi, że ciągi niezmienne, ale nie są ani typami referencyjnymi, ani obiektami - są pierwotnymi typami wartości. Łatwym sposobem, aby przekonać się, że nie są, jest próba dodania właściwości do ciągu, a następnie przeczytania jej:var a = "hello";var b=a;a.x=5;console.log(a.x,b.x);
Benjamin Gruenbaum,

9
@ VidarS.Ramdal Nie, Stringobiekty 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ż Numberobiektów i wartości liczbowych. Ważne jest, aby pamiętać, że Stringobiekty utworzone przy użyciu new Stringnie 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
Benjamin

5
Jeśli chodzi o to, dlaczego niektórzy twierdzą, że ciągi znaków są obiektami, prawdopodobnie pochodzą one z Pythona, Lispa lub innego języka, w którym jego specyfikacja używa słowa „obiekt” do oznaczenia dowolnego rodzaju danych (nawet liczb całkowitych). Muszą tylko przeczytać, jak specyfikacja ECMA definiuje słowo: „członek typu Object”. Również słowo „wartość” może oznaczać różne rzeczy zgodnie ze specyfikacją różnych języków.
Jisang Yoo,

21

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 StringBuilderJavaScript nie ma .


Wiem, że nie ma łańcuchaBuilder, msAjax ma taki i zastanawiałem się, czy jest on użyteczny
DevelopingChris

5
Co to ma wspólnego z ciągami niezmiennymi czy nie?
baudtack

4
@docgnome: Ponieważ ciągi są niezmienne, string konkatenacji wymaga tworzenia większej liczby obiektów niż podejście Array.join
Juan Mendes

8
Zgodnie z powyższym testem Juana konkatenacja ciągów jest w rzeczywistości szybsza zarówno w IE, jak i Chrome, a wolniej w Firefox.
Bill Yang

9
Zastanów się nad aktualizacją swojej odpowiedzi, być może dawno temu była to prawda, ale już nie jest. Zobacz jsperf.com/string-concat-without-sringbuilder/5
Juan Mendes

8

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:

  1. Pobierana jest istniejąca wartość „immutableString”
  2. „Świat” jest dołączany do istniejącej wartości „immutableString”
  3. Wynikowa wartość jest następnie przydzielana do nowego bloku pamięci
  4. Obiekt „immutableString” wskazuje teraz na nowo utworzoną przestrzeń pamięci
  5. Wcześniej utworzone miejsce w pamięci jest teraz dostępne do odśmiecania.

4

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.


Obiekty JavaScript String są niezmiennymi developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures
prasun

@prasun, ale na tej stronie jest napisane: „Wszystkie typy oprócz obiektów definiują niezmienne wartości (wartości, których nie można zmienić).” Obiekty łańcuchowe są obiektami. A jak to jest niezmienne, jeśli możesz dodać do niego nowe właściwości?
zhanziyang

przeczytaj sekcję „Typ ciągu”. Link String JavaScript wskazuje zarówno na prymitywne, jak i Object, a później mówi: „Ciągi JavaScript są niezmienne”. Wygląda na to, że dokument nie jest jasny na ten temat, ponieważ koliduje z dwiema różnymi notatkami
prasun

6
new Stringgeneruje zmienne opakowanie wokół niezmiennego ciągu
tomdemuyt

1
Bardzo łatwo przetestować, uruchamiając powyższy kod @ zhanziyang. Możesz całkowicie dodać nowe właściwości do Stringobiektu (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 Stringopakowaniu obiektu, czy nie, jest zawsze niezmienny.
Mark Reed,

3

Ciągi są niezmienne - nie mogą się zmieniać, możemy tylko tworzyć nowe.

Przykład:

var str= "Immutable value"; // it is immutable

var other= statement.slice(2, 10); // new string

1

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.


1

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


FWIW: Książka Rhino prawdopodobnie oznacza, że wewnętrznie / implementacja przypisania łańcucha zapisuje / kopiuje wskaźnik (zamiast kopiowania zawartości łańcucha). Na ich podstawie nie wygląda to na wypadek. Ale zgadzam się: niewłaściwie używają terminu „przez odniesienie”. Nie jest to „odniesienie” tylko dlatego, że implementacja przekazuje wskaźniki (dla wydajności). Wiki - strategia oceny to ciekawa lektura na ten temat.
ToolmakerSteve


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.