Чи рядки JavaScript незмінні? Чи потрібен мені "JavaScript builder" у JavaScript?


237

Чи використовує javascript незмінні або змінні рядки? Чи потрібен мені "конструктор струн"?


3
Так, y незмінні, і вам потрібен якийсь "конструктор струн". Читайте цей blog.codeeffects.com/Article/String-Builder-In-Java-Script або цей codeproject.com/KB/scripting/stringbuilder.aspx
Kizz

2
Цікаво, що ці приклади суперечать моїм висновкам у моїй відповіді.
Хуан Мендес

Відповіді:


294

Вони незмінні. Ви не можете змінити символ у рядку з чимось подібним var myString = "abbdef"; myString[2] = 'c'. Методи роботи з рядками , такі як trim, sliceповертають нові рядки.

Таким же чином, якщо у вас є дві посилання на один і той же рядок, зміна одного не впливає на інше

let a = b = "hello";
a = a + " world";
// b is not affected

Однак я завжди чув те, що згадував Еш у своїй відповіді (що використання Array.join швидше для конкатенації), тому я хотів перевірити різні способи об'єднання рядків і абстрагування найшвидшого шляху в StringBuilder. Я написав кілька тестів, щоб побачити, чи це правда (це не так!).

Це те, що я вважав, що це найшвидший спосіб, хоча я думав, що додавання виклику методу може зробити його повільніше ...

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('');
}

Ось тести на швидкість роботи. Усі троє створюють гігантську струну, що складається зі з’єднання "Hello diggity dog"сто тисяч разів у порожню струну.

Я створив три типи тестів

  • Використання Array.pushтаArray.join
  • Використання індексації масиву для уникнення Array.push, а потім використанняArray.join
  • Пряме з'єднання рядків

Тоді я створив ті самі три тести, абстрагувавши їх StringBuilderConcat, StringBuilderArrayPushі StringBuilderArrayIndex http://jsperf.com/string-concat-without-sringbuilder/5 Будь ласка, зайдіть туди і запустіть тести, щоб ми могли отримати хороший зразок. Зауважте, що я виправив невелику помилку, тому дані для тестів будуть видалені, я оновлю таблицю, коли буде достатньо даних про продуктивність. Перейдіть на сторінку http://jsperf.com/string-concat-without-sringbuilder/5 для старої таблиці даних.

Ось кілька номерів (останнє оновлення в Ma5rch 2018), якщо ви не хочете перейти за посиланням. Кількість на кожному тесті становить 1000 операцій / секунду (чим вище, тим краще )

| 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     | 

Знахідки

  • В наші дні всі вічнозелені браузери добре обробляють об'єднання рядків. Array.joinдопомагає лише IE 11

  • Загалом, Opera найшвидша, у 4 рази швидша за Array.join

  • Firefox займає друге місце і Array.joinлише трохи повільніше у FF, але значно повільніше (в 3 рази) у Chrome.

  • Хром третій, але строковий конмат у 3 рази швидший, ніж Array.join

  • Створення StringBuilder, здається, не надто впливає на продуктивність.

Сподіваюся, що хтось інший вважає це корисним

Різний тестовий випадок

Оскільки @RoyTinker подумав, що мій тест був помилковим, я створив новий випадок, який не створює великої рядки, об'єднуючи ту саму рядок, він використовує різний символ для кожної ітерації. З'єднання струн все ще здавалося швидшим або таким же швидким. Давайте проведемо ці тести.

Я пропоную всім думати про інші способи перевірити це, і сміливо додайте нові посилання на різні тестові справи нижче.

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


@Juan, посилання, яке ви попросили нас відвідати рядок 112 знаків 30 разів. Ось ще один тест, який може допомогти збалансувати речі - Array.join vs об'єднання рядків на 20 000 різних рядках 1-char (приєднання набагато швидше в IE / FF). jsperf.com/join-vs-str-concat-large-array
Roy Tinker

1
@RoyTinker Рой, ой Рой, ваші тести обманюють, оскільки ви створюєте масив під час налаштування тесту. Ось справжній тест з використанням різних символів jsperf.com/string-concat-without-sringbuilder/7 Не соромтеся створювати нові тестові випадки, але створення масиву є частиною самого тесту
Хуан Мендес

@JuanMendes Моєю метою було звузити тестовий випадок до суворого порівняння joinконкатенації проти рядків, отже, побудови масиву до тесту. Я не думаю, що це обман, якщо ця мета зрозуміла (і joinвнутрішньо перераховує масив, тому також не обманювати опускати forцикл із joinтесту).
Рой Тінкер

2
@RoyTinker Так, будь-який конструктор рядків вимагатиме створення масиву. Питання полягає в тому, чи потрібен конструктор струн. Якщо у вас вже є рядки в масиві, то це не є дійсним тестовим випадком для того, що ми тут обговорюємо
Хуан Мендес

1
@Baltusaj Стовпці, які говорять про "Index / Push", використовуютьArray.join
Хуан Мендес

41

з книги носорогів :

У JavaScript рядки - це незмінні об'єкти, це означає, що символи всередині них можуть не змінюватися і що будь-які операції над рядками фактично створюють нові рядки. Рядки присвоюються посиланням, а не значенням. Загалом, коли об'єкт призначається за допомогою посилання, зміна, внесена до об'єкта через одну посилання, буде видима через усі інші посилання на об'єкт. Оскільки рядки не можуть бути змінені, однак, ви можете мати кілька посилань на об'єкт рядка і не хвилюватися, що значення рядка зміниться, не знаючи про це


7
Посилання на відповідний розділ книги носорогів: books.google.com/…
baudtack

116
Цитата книги Носорога (і, отже, ця відповідь) тут неправильна . У рядках JavaScript є примітивні типи значень, а не об'єкти (spec) . Насправді, як і для ES5, вони є одним із єдиних 5 типів поряд із null undefined numberта boolean. Рядки присвоюються за значенням, а не за посиланням і передаються як такі. Таким чином, рядки не просто незмінні, вони є цінністю . Зміна рядка "hello"бути таким "world", як вирішити, що число 3 - це число 4 ... це не має сенсу.
Бенджамін Груенбаум

8
Так, як мій коментар говорить рядок є незмінною, але вони не є посилальними типами , ні вони є об'єктами - вони примітивні типів значень. Найпростішим способом зрозуміти, що вони не є, було б спробувати додати властивість до рядка та прочитати його:var a = "hello";var b=a;a.x=5;console.log(a.x,b.x);
Бенджамін Груенбаум

9
@ VidarS.Ramdal Ні, Stringоб'єкти, створені за допомогою конструктора рядків, є обгортками навколо рядкових значень JavaScript. Ви можете отримати доступ до значення рядка типу в коробці за допомогою .valueOf()функції - це також стосується Numberоб'єктів і значень чисел. Важливо зазначити, що Stringоб'єкти, створені за допомогою new String, не є власне рядками, а є обгортками або полями навколо рядків. Див. Es5.github.io/#x15.5.2.1 . Про те, як перетворюють речі на об’єкти, дивіться es5.github.io/#x9.9
Бенджамін

5
Що стосується того, чому деякі люди кажуть, що рядки є об'єктами, вони, ймовірно, походять від Python або Lisp або будь-якої іншої мови, де його специфікація використовує слово "object" для позначення будь-якого типу даних (навіть цілих чисел). Їм просто потрібно прочитати, як специфікація ECMA визначає слово: "член типу" Об'єкт ". Крім того, навіть слово "значення" може означати різні речі відповідно до специфікацій різних мов.
Jisang Yoo

21

Порада щодо продуктивності:

Якщо вам потрібно об'єднати великі рядки, покладіть частини рядка в масив і скористайтеся Array.Join()методом, щоб отримати загальний рядок. Це може бути в багато разів швидше для об'єднання великої кількості рядків.

У StringBuilderJavaScript немає .


Я знаю, що немає stringBuilder, у msAjax є такий, і я просто розмірковував над тим, чи корисно це
DevelopingChris

5
Що це стосується того, що струни незмінні чи ні?
бодотака

4
@docgnome: Оскільки рядки незмінні, конкатенація рядків вимагає створення більше об'єктів, ніж підхід Array.join
Хуан Мендес

8
Відповідно до тесту Хуана, конкатенація рядків насправді швидша як в IE, так і в Chrome, в той час як у Firefox повільніше.
Білл Ян

9
Подумайте над оновленням своєї відповіді, можливо, це було давно, але це вже не так. Дивіться jsperf.com/string-concat-without-sringbuilder/5
Хуан Мендес

8

Просто для уточнення для простих розумів, як моя (від MDN ):

Незмінними є об'єкти, стан яких неможливо змінити, коли об’єкт створений.

Рядок і цифри є незмінними.

Незмінне означає, що:

Можна вказати змінну точку імені на нове значення, але попереднє значення все ще зберігається в пам'яті. Звідси необхідність збору сміття.

var immutableString = "Hello";

// У наведеному вище коді створюється новий об'єкт зі значенням рядка.

immutableString = immutableString + "World";

// Зараз ми додаємо "Світ" до існуючого значення.

Це виглядає так, що ми мутуємо рядок 'незмінний стринг', але це не так. Замість цього:

При додаванні "незмінного строку" зі значенням рядка відбуваються такі події:

  1. Отримано існуюче значення "незмінногоString"
  2. "Світ" додається до існуючого значення "незмінногоСтринга"
  3. Потім отримане значення розподіляється на новий блок пам'яті
  4. Об'єкт "muttableString" тепер вказує на щойно створений простір пам'яті
  5. Раніше створений простір пам'яті тепер доступний для збору сміття.

4

Значення типу рядка незмінне, але об'єкт String, який створюється за допомогою конструктора String (), є змінним, оскільки це об'єкт, і ви можете додати до нього нові властивості.

> var str = new String("test")
undefined
> str
[String: 'test']
> str.newProp = "some value"
'some value'
> str
{ [String: 'test'] newProp: 'some value' }

Тим часом, хоча ви можете додати нові властивості, ви не можете змінити вже наявні властивості

Скріншот тесту на консолі Chrome

На закінчення 1. 1. усе значення типу рядка (примітивний тип) є незмінним. 2. Об'єкт String є змінним, але значення типу рядка (примітивний тип), який він містить, є незмінним.


Об'єкти Javascript String є незмінним developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures
prasun

@prasun, але на цій сторінці написано: "Усі типи, крім об'єктів, визначають незмінні значення (значення, які неможливо змінити)." String об'єкти є об'єктом. І як це непорушно, якщо ви можете додати до нього нові властивості?
Жанзян

читайте розділ "Тип рядка". Посилання String Javascript вказує як на примітив, так і на Object, а пізніше в ньому йдеться про "рядки JavaScript незмінні". Схоже, документ не зрозумілий на цю тему, оскільки він конфліктує у двох різних записках
prasun

6
new Stringстворює змінну обгортку навколо непорушної струни
tomdemuyt

1
Це дуже легко перевірити, виконавши код @ zhanziyang вище. Ви можете повністю додати нові властивості до Stringоб’єкта (обгортки), тобто не змінюється (за замовчуванням; як і будь-який інший об'єкт, ви можете зателефонувати Object.freezeна нього, щоб зробити його незмінним). Але тип примітивного рядкового значення, міститься в Stringобгортці об'єкта чи ні, завжди незмінний.
Марк Рід

3

Струни незмінні - вони не можуть змінюватися, ми можемо коли-небудь робити нові рядки.

Приклад:

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

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

1

Що стосується вашого питання (у вашому коментарі до відповіді Еша) про StringBuilder в ASP.NET Ajax, експерти, здається, не погоджуються з цього питання.

Крістіан Венц у своїй книзі « Програмування ASP.NET AJAX» (O'Reilly) каже, що «такий підхід не має жодного вимірюваного впливу на пам’ять (насправді реалізація здається тик повільнішою, ніж стандартний підхід)».

З іншого боку, Gallo та ін в своїй книзі ASP.NET AJAX в дії (Manning) говорять про те, що "Коли кількість рядків для об'єднання зростає, конструктор струн стає важливим об'єктом, щоб уникнути величезних падінь продуктивності".

Я думаю, вам знадобиться зробити власний бенчмаркінг, і результати можуть також відрізнятися між браузерами. Однак, навіть якщо це не покращує продуктивність, воно все ще може вважатися "корисним" для програмістів, які звикли кодувати StringBuilders на таких мовах, як C # або Java.


1

Це пізній пост, але серед відповідей я не знайшов гарної цитати книги.

Ось певний виняток, окрім надійної книги:

Рядки незмінні в ECMAScript, тобто, коли вони створюються, їх значення не можуть змінюватися. Щоб змінити рядок, який утримується змінною, початкову рядок потрібно знищити, а змінну заповнити іншою рядком, що містить нове значення ... —Професійний JavaScript для веб-розробників, 3-е видання, стор.43

Тепер відповідь, яка цитує уривок книги Носорога, є правильним про незмінність рядків, але неправильна фраза "Строки призначаються за посиланням, а не за значенням". (ймовірно, вони спочатку мали на меті поставити слова навпаки).

Помилка "посилання / значення" пояснюється в "Професійному JavaScript", главі під назвою "Примітивні та еталонні значення":

П'ять примітивних типів ... [є]: Undefined, Null, Boolean, Number та String. Кажуть, що до цих змінних можна отримати значення, оскільки ви маніпулюєте фактичним значенням, яке зберігається в змінній. —Професійний JavaScript для веб-розробників, 3-е видання, стор.85

що протистоїть об'єктам :

Коли ви маніпулюєте об'єктом, ви дійсно працюєте над посиланням на цей об’єкт, а не над самим власним об'єктом. З цієї причини, як кажуть, доступ до таких значень здійснюється шляхом посилання. —Професійний JavaScript для веб-розробників, 3-е видання, стор.85


FWIW: Книга Rhino, ймовірно, означає, що внутрішньо / реалізує призначення рядка зберігає / копіює покажчик (а не копіює вміст рядка). З огляду на текст після цього, це не схоже на випадковість. Але я згоден: вони неправильно використовують термін "за посиланням". Це не "за посиланням" лише тому, що реалізація проходить покажчики (для продуктивності). Вікі - стратегія оцінювання цікаво читати на цю тему.
ToolmakerSteve


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.