В чем разница между строковыми примитивами и строковыми объектами в JavaScript?

Взято из MDN

Строковые литералы (обозначаемые двойными или одинарными кавычками) и строки, возвращаемые из вызовов String в контексте не-конструктора (т.е. без использования ключевого слова new), являются примитивными строками. 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.

Ответ 1

В 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, но поведение отличается. На основе широкого спектра тестовых сценариев автоматическое боксирование применяет только "необходимые" методы, не изменяя примитивный характер переменной. Вот почему вы получаете лучшую скорость.

Ответ 2

Это скорее зависит от реализации, но я сделаю снимок. Я приведу пример с V8, но я предполагаю, что другие двигатели используют аналогичные подходы.

Строковый примитив анализируется на объект v8::String. Следовательно, методы могут быть вызваны непосредственно на него, как упомянуто jfriend00.

Объект String, с другой стороны, анализируется на v8::StringObject, который расширяет Object и, кроме того, что он является полноценным объектом, служит обертка для v8::String.

Теперь это логично, вызов new String('').method() должен отменить этот v8::StringObject v8::String перед выполнением метода, следовательно, он медленнее.


Во многих других языках примитивные значения не имеют методов.

Способ MDN, по-видимому, является самым простым способом объяснить, как работает авто-бокс примитивов (как упоминается в ответе flav), то есть, как значения JavaScript-примитива-y могут вызывать методы.

Однако интеллектуальный движок не будет преобразовывать примитив строки-y в объект String каждый раз, когда вам нужно вызвать метод. Это также информативно упоминается в аннотированной спецификации ES5 в отношении разрешения свойств (и "методов" №) примитивных значений:

ПРИМЕЧАНИЕ Объект, который может быть создан на шаге 1, недоступен вне указанного выше метода. Реализация может решить избежать фактического создания объекта. [...]

На очень низком уровне строки чаще всего реализуются как неизменные скалярные значения. Пример структуры обертки:

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

Чем глубже вы из примитива, тем дольше это займет, чтобы добраться до него. На практике примитивы String встречаются гораздо чаще, чем StringObject s, поэтому для разработчиков не удивительно добавлять методы к классу соответствующих (интерпретируемых) объектов примитивов String вместо того, чтобы преобразовывать назад и вперед между String и StringObject, как объясняет MDN.


¹ В JavaScript "метод" - это просто соглашение об именах для свойства, которое разрешает значение функции типа.

Ответ 3

В случае строкового литерала мы не можем назначать свойства

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

Ответ 4

Если вы используете new, вы явно заявляете, что хотите создать экземпляр объекта. Поэтому new String создает объект, обертывающий примитив String, что означает, что любое действие на нем включает дополнительный уровень работы.

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

Как и другие типы, ваш интерпретатор JavaScript также может оптимизировать их по-разному как указано в комментариях.

Ответ 5

Строковый литерал:

Строковые литералы неизменяемы, что означает, что после их создания их состояние не может быть изменено, что также делает их потокобезопасными.

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

a==b результат будет "истинным", и строка ссылается на тот же объект.

Объект String:

Здесь создаются два разных объекта, и они имеют разные ссылки:

var a = new String("s");
var b = new String("s");
Результат

a==b будет ложным, потому что у них разные ссылки.

Ответ 6

Когда вы заявляете:

var s = '0123456789';

вы создаете примитив строки. Этот примитив строки имеет методы, которые позволяют вам называть методы на нем, не преобразовывая примитив в объект первого класса. Поэтому ваше предположение, что это будет медленнее, потому что строка должна быть преобразована в объект, неверна. Он не должен быть преобразован в объект. Сам примитив может вызывать методы.

Преобразование его в полномасштабный объект (который позволяет вам добавлять к нему новые свойства) является дополнительным шагом и не ускоряет создание строк (на самом деле ваш тест показывает, что он делает их медленнее).

Ответ 7

Существование объекта имеет мало общего с фактическим поведением String в механизмах ECMAScript/JavaScript, поскольку корневая область будет просто содержать объекты функции для этого. Таким образом, функция charAt (int) в случае строкового литерала будет выполняться и выполняться.

С помощью реального объекта вы добавляете еще один слой, в котором метод charAt (int) также выполняется поиск по самому объекту до того, как будет выполняться стандартное поведение (так же, как указано выше). По-видимому, в этом случае наблюдается удивительно большая работа.

Кстати, я не думаю, что примитивы фактически преобразуются в объекты, но движок script просто помечает эту переменную как строковый тип, и поэтому он может найти все предоставленные функции для нее, поэтому похоже, что вы вызываете объект. Не забывайте, что это среда выполнения script, которая работает на разных принципах, чем среда выполнения OO.

Ответ 8

Я вижу, что этот вопрос был решен давно, есть еще одно тонкое различие между строковыми литералами и строковыми объектами, так как никто, кажется, не коснулся этого, я подумал, что просто напишу его для полноты.

По сути, другое различие между ними заключается в использовании eval. eval ('1 + 1') дает 2, тогда как eval (new String ('1 + 1')) дает '1 + 1', поэтому, если определенный блок кода может быть выполнен как 'нормально', так и с eval, он может привести к странным результатам

Ответ 9

Самое большое различие между строковым примитивом и строковым объектом состоит в том, что объекты должны следовать этому правилу для оператора ==:

Выражение, сравнивающее Объекты, истинно, только если операнды ссылаются на один и тот же Объект.

Таким образом, в то время как строковые примитивы имеют удобный == который сравнивает значение, вам не повезло, когда вы заставляете любой другой тип неизменяемого объекта (включая строковый объект) вести себя как тип значения.

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

(Другие отметили, что строковый объект является технически изменяемым, потому что вы можете добавить к нему свойства. Но не ясно, для чего это полезно; само строковое значение не является изменяемым.)