Почему строковый литерал считается примитивным типом в JavaScript? - PullRequest
7 голосов
/ 25 апреля 2020

Официальная документация, а также тонны статей в inte rnet говорят, что 'some string' является примитивным значением, то есть оно создает копию каждый раз, когда мы присваиваем ее переменной.

Однако , этот вопрос (и ответ на него) Как заставить JavaScript глубоко копировать строку? демонстрирует, что на самом деле V8 не копирует строку даже в методе substr.

Также было бы безумно копировать строки каждый раз, когда мы передаем их в функции, и это не имело бы смысла. В таких языках, как C#, Java или Python, тип данных String определенно является ссылочным типом.

Кроме того, эта ссылка показывает иерархию, и мы в конце концов видим HeapObject. https://thlorenz.com/v8-dox/build/v8-3.25.30/html/d7/da4/classv8_1_1internal_1_1_sliced_string.html enter image description here

Наконец, после проверки

let copy = someStringInitializedAbove

в Devtools ясно, что новая копия этой строки не была создана!

Так что я уверен, что строки не копируются при назначении. Но я до сих пор не понимаю, почему так много статей, таких как JS Примитивы против ссылки , говорят, что они есть.

1 Ответ

8 голосов
/ 25 апреля 2020

В основном, потому что спецификация гласит: :

строковое значение

примитивное значение, представляющее собой конечную упорядоченную последовательность нуля или более 16-разрядных целочисленных значений без знака

Спецификация также определяет наличие объектов String, в отличие от простых строк. (Аналогично, существуют типы примитивов number, boolean и symbol, а также объекты Number и Boolean и Symbol.)

Строки примитивов следуют всем правилам других примитивов. На уровне языка к ним относятся точно так же, как примитивные числа и логические значения. Для всех намерений и целей они являются примитивными значениями. Но, как вы говорите, было бы безумием a = b буквально сделать копию строки в b и поместить эту копию в a. Реализации не должны делать это, потому что значения примитивной строки неизменны (точно так же как значения примитивного числа). Вы не можете изменить любые символы в строке, вы можете только создать новую строку. Если бы строки были изменяемыми, реализация должна была бы сделать копию, когда вы сделали a = b (но если бы они были изменяемыми, spe c была бы написана по-другому).

Обратите внимание, что примитивные строки и объекты String действительно разные вещи:

const s = "hey";
const o = new String("hey");

// Here, the string `s` refers to is temporarily
// converted to a string object so we can perform an
// object operation on it (setting a property).
s.foo = "bar";
// But that temporary object is never stored anywhere,
// `s` still just contains the primitive, so getting
// the property won't find it:
console.log(s.foo); // undefined

// `o` is a String object, which means it can have properties
o.foo = "bar";
console.log(o.foo); // "bar"

Так почему же примитивные струны? Вы должны спросить Брендана Эйха (и он достаточно отзывчив в Твиттере), но я подозреваю, что это было так, что определение операторов эквивалентности (==, ===, != и !==) не было Это должно быть либо что-то, что может быть перегружено типом объекта для его собственных целей, либо специальным регистром для строк.

Так зачем нужны строковые объекты? Наличие объектов String (и объектов Number, и логических объектов, и объектов Symbol) вместе с правилами, гласящими, что при создании временной версии примитива создается возможность определять методы для примитивов. Когда вы делаете:

console.log("example".toUpperCase());

в условиях спецификации, создается объект String (с помощью операции GetValue ), а затем свойство toUpperCase ищется для этого объекта и (в выше) называется. Поэтому примитивные строки получают свои toUpperCase (и другие стандартные методы) из String.prototype и Object.prototype. Но созданный временный объект не доступен для кода, за исключением некоторых крайних случаев, движки ¹ и JavaScript могут избежать буквального создания объекта вне этих крайних случаев. Преимущество в том, что заключается в том, что к String.prototype можно добавлять новые методы и использовать их для примитивных строк.


¹ "Какие крайние случаи?" Я слышал, вы спрашиваете. Наиболее распространенная из них, о которой я могу подумать, это когда вы добавили свой собственный метод к String.prototype (или аналогичному) в свободном коде режима:

Object.defineProperty(String.prototype, "example", {
    value() {
        console.log(`typeof this: ${typeof this}`);
        console.log(`this instance of String: ${this instanceof String}`);
    },
    writable: true,
    configurable: true
});

"foo".example();
// typeof this: object
// this instance of String: true

Там движок JavaScript был вынужден создать объект String, поскольку this не может быть примитивом в свободном режиме.

Strict Режим позволяет избежать создания объекта, поскольку в строгом режиме this необязательно быть типом объекта, он может быть примитивом (в данном случае, примитивной строкой):

"use strict";
Object.defineProperty(String.prototype, "example", {
    value() {
        console.log(`typeof this: ${typeof this}`);
        console.log(`this instance of String: ${this instanceof String}`);
    },
    writable: true,
    configurable: true
});

"foo".example();
// typeof this: string
// this instanceof String: false
...