Back to articles

Тайная жизнь примитивов JavaScript

javascriptprimitivesbasics

📌 Note: эта статья исследует, как примитивы ведут себя «под капотом» и когда JavaScript преобразует их в объекты.

Устин Васькин Ustin M. Vaskin

Аннотация- Данная обучающая статья посвящена языку программирования JS, а именно описывает работу примитивов. В материале рассматривается, как работают примитивные типы в Java Script и то, что происходит под прикрытием. С использованием методики принуждения примитивов к объектам был выполнен анализ работы JS Engine и свойств примитивов. Автором была изучена способность назначать свойства объектов перед их примитивными аналогами.

На основании полученных данных было выявлено, что способность назначать свойства - почти единственное преимущество объектов. Если JavaScript обнаруживает попытку назначить свойство примитиву, он действительно приведет к примитиву объекта. Но, как и в предыдущих примерах, этот новый объект не имеет ссылок и сразу станет кормом для сборки мусора. Хорошее понимание примитивов и того, что происходит под прикрытием, когда работаете с ними, является важным шагом на пути к более глубокому изучению языка.

Возможно, вы этого не знаете, но в JavaScript, когда вы взаимодействуете со cтроковыми, числовыми или логическими примитивами, вы попадаете в скрытый мир Зазеркалья. В JavaScript есть 6 примитивных типов:

undefined неопределенный, null нулевой, boolean логический, string строковый, number числовой и symbol символ.

Все остальное является объектом. Примитивные типы boolean, string и number могут быть обернуты их объектными аналогами. Эти объекты являются экземплярами Boolean, String

и N u m b e r к о н с т р у к т о р о в соответственно. Использование конструктора new:

typeof true; //"boolean"
typeof
Boolean(true); //"boolean"
typeof new
Boolean(true); //"object"
typeof (new
Boolean(true)).valueOf(); //
"boolean"
typeof "abc"; //"string"
typeof
String("abc"); //"string"

Computing in Science and Engineering 1

typeof new
String("abc"); //"object"
typeof (new
String("abc")).valueOf(); //
"string"
typeof 123; //"number"
typeof
Number(123); //"number"
typeof new
Number(123); //"object"
typeof (new
Number(123)).valueOf(); //"n
umber"

Если примитивы не имеют свойств, почему «abc» .length возвращает значение?

П о т о м у ч т о J a v a S c r i p t б у д е т принуждать примитив стать объектом. В этом случае строковое значение приводится к строковому объекту для доступа к свойствам, например (длина) length. Строковый объект используется только в течение доли секунды, после чего он приносится в жертву богам сборки мусора.

String.prototype.returnMe=
function() {
return this;
}
var a = "abc";
var b = a.returnMe();
a; //"abc"
typeof a; //"string" (still a
primitive)
b; //"abc"
typeof b; //"object"
... 

И, как и во многих научных исследованиях, имеющих большое значение, мы теперь вмешивались в естественное развитие вещей и не позволяли объекту собирать мусор, пока у вас есть b. (Обратите внимание, что в строгом режиме неуловимое существо уходит) Вот еще один пример, который проверяет тип объекта, не влияя на сборку мусора:

Number.prototype.toString =
function() {
return typeof this;
}
(123).toString(); //“object"

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

JavaScript и valueOf

В JavaScript valueOf и toString являются дочерними методами, унаследованными каждым объектом. Один из этих методов будет вызываться всякий раз, когда выражение встречает сложный объект, где ожидалось примитивное значение. Например :-

alert(myCat);
var result = 2 + myCat;

В общих чертах, если выражение намекает на необходимость строки, вызывается toString, в противном случае его valueOf. Если один из методов возвращает не примитив, другой метод получает попытку. В приведенных вышепримерах ожидается, что myHamster будет строкой и числом соответственно, поэтому они будут оцениваться как:

alert(myCat.toString()); //
интерпретатор ожидал строку
var result = 2 +
myCat.valueOf(); //ожидал число

[См. ECMA 5 главу 8.12.8 для полного а л г о р и т м а . П о м н и т е , ч т о , hint и ToPrimitive являются внутренними конструкциями] Основываясь на этих правилах, обычно о ж и д а е т с я , ч т о v a l u e O f б у д е т в о з в р а щ а т ь с о д е р ж а т е л ь н о е н е строковое представление объекта. Мы можем воспользоваться значением valueOf, чтобы создать сокращенный с и н т а к с и с д л я т е к у щ е й д а т ы, выраженной в миллисекундах:

(new Date()).valueOf(); //
1588962243724 (date in ms)
+ new Date();//15889622488
+ 95(ожидается нестроковый
примитив после +)
+new Date; //1588962256511
(то же самое, но даже более
короткий синтаксис)

Это полезно, если вам часто нужно н а к а т ы в а т ь с в о и с о б с т в е н н ы е показатели профилирования. Большинство других реализаций valueOf по умолчанию не очень интересны:

Boolean(true).valueOf(); //
true
Number('123').valueOf(); //
123
"aaa".valueOf(); //"aaa"

Что более интересно (и вы знали, что это произойдет), это определить свои собственные значения реализаций:

var toDollarRate = {
pounds: 1.5,
euros: 1.1
}
var Drink = function(name,
cost, currency) {
this.name = name;
this.cost = cost;
this.currency =
currency;
}
Drink.prototype.costInDollar
s = function() {
return this.cost *
(toDollarRate[this.currency]
|| 1);
}
var boddingtons = new
Drink("Boddingtons", 2.50,
'pounds');
var peroni = new
Drink("Peroni", 3.50,
'euros');
var anchorSteam = new
Drink("Anchor Steam", 3.50,
'dollars');
Drink.prototype.valueOf =
Drink.prototype.costInDollar
s;
'$' + (boddingtons + peroni +
anchorSteam).toFixed(2); //
$11.10

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

var SystemRequest =
function(name) {
this.name = name;
}
SystemRequest.prototype.run
= function() {
//симуляция результатов
теста
this.success =
Math.random(1)>0.5;
return this;
}
SystemRequest.prototype.valu
eOf = function() {
return this.success;
}
var request1 = new
SystemRequest('request1');
var request2 = new
SystemRequest('request2');
var request3 = new
SystemRequest('request3');
request1.run() +
request2.run() +
request3.run(); //2
request1.run() +
request2.run() +
request3.run(); //1
request1.run() +
request2.run() +
request3.run(); //3 (all
passed!)

Здесь valueOf возвращает логическое з н а ч е н и е , н о в и н с т р у к ц и я х о к о н ч а т е л ь н о г о в ы п о л н е н и я используется конкатенация для преобразования логических значений в числа (1 для передачи, 0 для отказа). В п р а в и л ь н о й с и т у а ц и и переопределение valueOf может быть полезным инструментом. Но даже если вы никогда не используете его таким образом, знание того, как и почему JavaScript вы б и р а ет м ет о ды п о умолчанию toString и valueOf, поможет вам лучше узнать ваш код.

Могут л и эти объекты быть приведены к значениям? Да. Главным образом. Объекты этого типа являются просто обертками, их значение - это примитив, который они обертывают, и они обычно приводят к э т о м у з н а ч е н и ю п о м е р е необходимости.

//объект приведен к примитиву
var Twelve = new Number(12);
var fifteen = Twelve + 3;
fifteen; //15
typeof
fifteen; //"number" (примитив
у)
typeof Twelve; //"object";
(все еще объект)
//другой объект приведен к
примитиву
new String("hippo") +
"potamus"; //"hippopotamus"
//объект не приведен (потому
что оператор typeof может
работать с объектами)
typeof new String("hippo") +
"potamus"; //"objectpotamus"

К сожалению, Boolean объекты не приводятся так легко. И, чтобы подсыпать соль на рану, Boolean объект оценивается как true, если его значение не равно null или undefined. Попробуй это:

if (new Boolean(false)) {

alert("true???");
}

Обычно вы должны явно запрашивать логические объекты для их значения. Следующее может быть полезно для определения, является ли значение «truthy» или «falsey»….

var a = "";
new Boolean(a).valueOf(); //
false
... но на практике это легче сделать ...
var a = Boolean("");
a; //false
... или даже это ...
var a = "";
!!a; //false

Позволяет ли принуждение присваивать значения примитивам? Нет

var primitive = "september";
primitive.vowels = 3;
primitive.vowels; //
undefined;

Если JavaScript обнаруживает попытку назначить свойство примитиву, он действительно приведет к примитиву объекта. Но, как и в предыдущих примерах, этот новый объект не имеет ссылок и сразу станет кормом для сборки мусора. Вот псевдокодовое представление того же примера, чтобы проиллюстрировать, что на самом деле происходит

var primitive = "september";
primitive.vowels = 3;
// новый объект, созданный для
установки свойства(new
String("september")).vowels
= 3;
primitive.vowels;
// еще один новый объект,
созданный для получения
свойства (new
String("september")).vowels;
//undefined

Так что, как видите, это не только б е с п о л е з н о , н о и д о в о л ь н о расточительно. Заключение О к а з ы в а е т с я , ч т о с п о с о б н о с т ьназначать свойства - это почти единственное преимущество объектов перед их примитивными аналогами. Строки, логические значения и числа и м е ю т к о н к р е т н ы е и ч е т к о определенные цели. Более того, поскольку примитивы являются неизменяемыми: ES3

stringObjectInstance.length Имеет DontEnum, DontDelete, ReadOnly attributes в ES3

Хотя ES5: ( [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false в ES5), таким образом

var me = new
String("Angus");
me.length = 2; //(ощибка в
strict mode)
me.length; //5
me.valueOf(); “Angus”

Тем не менее хорошее понимание примитивов и того, что происходит под прикрытием, когда вы взаимодействуете с ними, является важным шагом на пути к более глубокому изучению языка. Бибилио гр афи ч е с кий список:

[См. ECMA 5 главу 8.12.8 для полного а л г о р и т м а . П о м н и т е , ч т о [[DefaultValue]], hint и ToPrimitive являются внутренними конструкциями]

И н ф о р м а ц я о б авторах: Васькин Устин Михалович Ustin M. Vaskin И н ж е н е р П р о г р а м м и с т ИМ Т О, специалист по науке о данных., Oslo.