Яка різниця між рядковими примітивами та об'єктами String в JavaScript?


116

Взято з MDN

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

Отже, я вважав (логічно), що операції (виклики методів) на рядкових примітивах повинні бути повільнішими, ніж операції над рядковими об'єктами, тому що будь-який рядковий примітив перетворюється на рядок "Об'єкт (додаткова робота") перед тим, methodяк застосовуватися до рядка.

Але в цьому тестовому випадку результат протилежний. У блок 1-код працює швидше , ніж код блоку-2 , обидва кодових блоків наведені нижче:

код блоку-1:

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

код код-2:

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

Результати різняться в браузерах, але код код-1 завжди швидший. Чи можете будь-хто пояснити це, чому блок коду-1 швидше, ніж блок-2 коду .


6
За допомогою new Stringвводиться ще один прозорий шар обгортання об'єктів . typeof new String(); //"object"
Пол С.

про що '0123456789'.charAt(i)?
Юрій Галантер

@YuriyGalanter, це не проблема, але я запитую, чому code block-1швидше?
Альфа

2
Струнні об'єкти порівняно рідко можна побачити в реальному контексті життя, тому для інтерпретаторів не дивно оптимізувати рядкові букви. Сьогодні ваш код не просто інтерпретується , є багато шарів оптимізації, які відбуваються за кадром.
Фабрісіо Матте

2
Це дивно: редакція 2
hjpotter92

Відповіді:


149

У JavaScript є дві основні категорії, примітиви та об'єкти.

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

Шаблони одинарних / подвійних цитат однакові за функціональністю. Окрім того, поведінка, яку ви намагаєтесь назвати, називається автобоксингом. Отже, що насправді відбувається, це те, що примітив перетворюється на тип його обгортки, коли викликається метод типу обгортки. Простіше кажучи:

var s = 'test';

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

Отже, що відбувається, наприклад, коли ви робите це s.charAt(i)?

Оскільки sце не екземпляр String, JavaScript автоматично sвстановить вікно , яке має typeof stringйого тип обгортки String, з typeof objectточністю або точніше s.valueOf(s).prototype.toString.call = [object String].

Поведінка автоматичного боксу sпри необхідності передає свій тип обгортки, але стандартні операції неймовірно швидкі, оскільки ви маєте справу з більш простим типом даних. Однак автобокс має і Object.prototype.valueOfрізні ефекти.

Якщо ви хочете змусити автобокс або викинути примітив на його тип обгортки, ви можете використовувати Object.prototype.valueOf, але поведінка відрізняється. На основі широкого спектру тестових сценаріїв автоматичний бокс застосовує лише "необхідні" методи, не змінюючи примітивний характер змінної. Ось чому ви набираєте кращу швидкість.


33

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

Рядок примітиву розбирається на v8::Stringоб'єкт. Отже, методи можуть бути використані безпосередньо на ньому, як згадує jfriend00 .

Об'єкт String, з іншого боку, розбирається на a, v8::StringObjectщо розширюється, Objectі, крім того, що є повноцінним об'єктом, служить обгорткою для v8::String.

Тепер це логічно, виклик new String('').method()повинен розпаковувати це v8::StringObject«S v8::Stringперед виконанням методи, тому він повільніше.


У багатьох інших мовах примітивні значення не мають методів.

Спосіб MDN видає, що це найпростіший спосіб пояснити, як працює автоматичний бокс примітивів (як це також було зазначено у відповіді Флава ), тобто як примітивні значення JavaScript у JavaScript можуть викликати методи.

Однак розумний двигун не буде перетворювати рядок примітиву-y в об'єкт String кожного разу, коли потрібно викликати метод. Про це також інформативно йдеться у специфікованій специфікації ES5. стосовно роздільних властивостей (і "методів" ¹) примітивних значень:

ПРИМІТКА Об'єкт, який може бути створений на кроці 1, недоступний поза вищевказаним методом. Реалізація може вибрати, щоб уникнути фактичного створення об'єкта. [...]

На дуже низькому рівні рядки найчастіше реалізуються як незмінні скалярні значення. Приклад структури обгортки:

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

Чим далі ви від примітиву, тим довше буде потрібно, щоб дістатися до нього. На практиці, Stringпримітиви набагато частіше , ніж StringObjectз, отже , це не дивно для двигунів , щоб додати методи до струн примітивам відповідних (трактуються) об'єкти класу замість перетворення і назад Stringі в StringObjectякості пояснення MDN передбачає.


¹ В JavaScript "метод" - це лише умова іменування для властивості, яка відповідає значенню функції типу.


1
Ласкаво просимо. =]Тепер мені цікаво, чи є пояснення MDN лише тому, що це здається найпростішим способом зрозуміти автоматичний бокс або чи є в ньому специфікація в ES специфікації. Читаючи по всій специфікації в даний момент, щоб перевірити, буде пам'ятати оновіть відповідь, якщо я коли-небудь знайду посилання.
Фабрісіо Матте

Прекрасне розуміння впровадження V8. Додам, що бокс є не просто для вирішення функції. Тут також слід перейти в цьому посиланні на метод. Тепер я не впевнений, чи пропускає V8 це для вбудованих методів, але якщо ви додасте власне розширення, щоб сказати String.prototype, ви отримаєте коробкову версію рядкового об'єкта щоразу, коли він буде викликаний.
Бен

17

У випадку рядкового літералу ми не можемо призначити властивості

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

Тоді як у випадку String Object ми можемо призначити властивості

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

1
Нарешті хтось мотивує, навіщо нам також потрібні Stringпредмети. Дякую!
Ciprian Tomoiagă

1
Чому хтось мав би це потребувати?
Адітья

11

Лінійний рядок:

Літеральні рядки незмінні, це означає, що після їх створення стан їх не може бути змінено, що також робить їх безпечними.

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

a==b Результатом буде "true" обидва рядки посилаються на один і той же об'єкт.

Об'єкт рядка:

Тут створюються два різних об'єкти, які мають різні посилання:

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

a==b Результат буде помилковим, оскільки вони мають різні посилання.


1
Чи є також рядковий об'єкт незмінним?
Ян Ван

@YangWang - це дурна мова, і обидва a& bнамагаються призначити a[0] = 'X'це буде виконано успішно, але не вийде, як ви могли очікувати
ruX

Ви написали "var a = 's'; var b = 's'; a == b результатом буде" true "обидва рядки посилаються на один і той же об'єкт." Це не правильно: a і b не посилаються ні на один і той же об’єкт, результат вірний, оскільки вони мають однакове значення. Ці значення зберігаються в різних місцях пам'яті, тому якщо змінити одне інше, це не зміниться!
SC1000

9

Якщо ви використовуєте new, ви прямо заявляєте, що хочете створити екземпляр Об'єкта . Таким чином, new Stringстворюється Об'єкт, що обгортає примітив String , що означає, що будь-яка дія на ньому передбачає додатковий шар роботи.

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

Оскільки вони різного типу, ваш інтерпретатор JavaScript може також оптимізувати їх по-різному, як зазначено в коментарях .


5

Коли ви заявляєте:

var s = '0123456789';

ви створюєте рядок примітиву. Цей рядковий примітив має методи, які дозволяють викликати методи на ньому без перетворення примітиву в об'єкт першого класу. Отже, ваше припущення, що це буде повільніше, оскільки рядок, який потрібно перетворити на об'єкт, не є правильним. Його не потрібно перетворювати на об’єкт. Сам примітив може викликати методи.

Перетворення його на повномасштабний об'єкт (що дозволяє додати до нього нові властивості) - це додатковий крок і не робить швидкіші опрацювання рядків (адже ваш тест показує, що він робить їх повільнішими).


Звідки рядок примітиву успадковує всі властивості прототипу, включаючи власні String.prototype?
Юрій Галантер

1
var s = '0123456789';це примітивне значення, як це значення можуть мати методи, я плутаюся!
Альфа

2
@SheikhHeera - примітиви вбудовані в мовну реалізацію, тому перекладач може надати їм особливі повноваження.
jfriend00

1
@SheikhHeera - я не розумію вашого останнього коментаря / питання. Примітивний рядок сам по собі не підтримує додавання до нього власних властивостей. Щоб дозволити це, у JavaScript також є об'єкт String, який має всі ті ж методи, що і рядковий примітив, але є повномасштабним об'єктом, який можна розглядати як об’єкт усіма способами. Ця подвійна форма видається дещо безладною, але я підозрюю, що це було зроблено як компроміс із продуктивністю, оскільки 99% випадків - це використання примітивів, і вони, ймовірно, можуть бути швидшими та ефективнішими в пам'яті, ніж струнні об'єкти.
jfriend00

1
@SheikhHeera "перетворений на рядок" Об'єкт "- це те, як MDN виражає його, щоб пояснити, як примітиви здатні викликати методи. Вони буквально не перетворюються на рядкові об'єкти.
Фабріціо Матте

4

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

По суті, ще одна відмінність між ними - це при використанні eval. eval ('1 + 1') дає 2, тоді як eval (новий рядок ('1 + 1')) дає '1 + 1', тож якщо певний блок коду може бути виконаний як 'нормально', так і з eval, він може призводять до дивних результатів


Дякуємо за Ваш внесок :-)
Альфа

Ого, це якась справді дивна поведінка. Ви повинні додати невелику строкову демонстрацію у коментарі, щоб показати цю поведінку - це надзвичайно відкриває очі.
EyuelDK

це нормально, якщо задуматися. new String("")повернути об’єкт, а eval лише оцінити рядок, і повернути все інше, як є
Фелікс Брунет,

3

Існування об'єкта мало пов'язане з реальною поведінкою String в двигунах ECMAScript / JavaScript, оскільки коренева область буде просто містити функціональні об'єкти для цього. Тож функція charAt (int) у випадку рядкового літералу буде шукана та виконується.

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

BTW Я не думаю, що примітиви насправді перетворюються на об'єкти, але механізм скриптів просто позначить цю змінну як тип рядка, і тому він може знайти всі надані для неї функції, так що схоже на те, що ви викликаєте об'єкт. Не забувайте, що це сценарій виконання сценарію, який працює на інших принципах, ніж час виконання OO.


3

Найбільша відмінність між примітивним рядком і рядковим об'єктом полягає в тому, що об'єкти повинні дотримуватися цього правила для ==оператора :

Вираз, що порівнює об'єкти, справедливий лише в тому випадку, якщо операнди посилаються на той самий Об'єкт.

Отже, тоді як рядкові примітиви мають зручне ==порівняння значення, вам не пощастило, коли справа доходить до того, щоб будь-який інший непорушний тип об'єкта (включаючи об’єкт рядка) поводився як тип значення.

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

(Інші відзначили, що об'єкт рядка технічно змінюється, оскільки ви можете додати до нього властивості. Але не ясно, для чого це корисно; саме значення рядка не змінюється.)


Дякуємо, що додали цінність питання через досить тривалий час :-)
Альфа

1

Код оптимізовано перед запуском двигуном javascript. Загалом, мікро-орієнтири можуть вводити в оману, оскільки компілятори та інтерпретатори переставляють, змінюють, видаляють та виконують інші хитрощі на частинах вашого коду, щоб зробити його швидшим. Іншими словами, написаний код вказує, що є ціллю, але компілятор та / або час виконання вирішуватимуть, як досягти цієї мети.

Блок 1 швидше в основному через: var s = '0123456789'; завжди швидше, ніж var s = new String ('0123456789'); через накладні витрати на створення об’єктів.

Частина циклу не є причиною уповільнення, оскільки інтерпретатор може бути накреслений chartAt (). Спробуйте вийняти цикл і повторіть тест, ви побачите, що коефіцієнт швидкості буде таким самим, як якщо б цикл не був знятий. Іншими словами, для цих тестів блоки циклів під час виконання мають точно такий же байт-код / ​​машинний код.

Для цих типів мікро-орієнтирів перегляд байт-коду або машинного коду дозволить отримати більш чітку картину.


1
Дякую за вашу відповідь.
Альфа

0

У Javascript примітивні типи даних, такі як рядок, - це некомпозитний будівельний блок. Це означає, що вони просто цінності, нічого більше: let a = "string value"; за замовчуванням немає вбудованих методів, таких як toUpperCase, toLowerCase тощо ...

Але, якщо ви спробуєте написати:

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

Це не призведе до помилок, натомість вони працюватимуть як слід.

Що трапилось ? Добре, коли ви намагаєтеся отримати доступ до властивості рядка aJavascript примушує рядок до об'єкта, new String(a);відомий як об’єкт обгортки .

Цей процес пов'язаний з концепцією, що називається конструкторами функцій у Javascript, де функції використовуються для створення нових об'єктів.

Коли ви вводите new String('String value');тут String - це конструктор функцій, який бере аргумент і створює порожній об'єкт всередині функції функції, цей порожній об'єкт присвоюється цьому і в цьому випадку String надає всі ті відомі вбудовані функції, про які ми згадували раніше. і як тільки операція завершена, наприклад, виконайте великі регістри, об’єкт обгортки викидається.

Щоб довести це, давайте зробимо це:

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

Тут вихід не буде визначеним. Чому? У цьому випадку Javascript створює обгортковий об'єкт String, встановлює нову властивість addNewProperty та негайно відкидає об’єкт обгортки. саме тому ви не визначаєтесь. Псевдо-код буде виглядати приблизно так:

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

0

ми можемо визначити String трьома способами

  1. var a = "перший спосіб";
  2. var b = String ("другий шлях");
  3. var c = new String ("третій шлях");

// також ми можемо створити, використовуючи 4. var d = a + '';

Перевірте тип рядків, створених за допомогою оператора typeof

  • typeof // "string"
  • typeof b // "рядок"
  • typeof c // "об'єкт"


при порівнянні a і b var a==b ( // yes)


при порівнянні об'єкта String

var StringObj = new String("third way")
var StringObj2 = new String("third way")
StringObj  == StringObj2 // no result will be false, because they have different references
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.