🧐 По ссылке или по значению?



👋 Привет! Вообще мне казалось, что я уже писал пост на эту тему, но найти я его не смог, поэтому пишу снова. В общем в чем соль? В JS у нас есть устоявшееся утверждение, что: "все примитивные типы при передаче всегда копируются".



Но так ли это? Какие последствия это имеет? Разберемся. Примитивами называют встроенные типы: Undefined, Null, Boolean, Number, BigInt, Symbol, String. Термин "примитив" не связан с "простотой", так как BigInt или String в реализации не такие уж и простые.



Они обладают важными свойствами: неделимость и неизменяемость. Нельзя изменить символ в строке или знак числа. Поэтому вместо термина "примитив" мне больше нравится "скаляр".



Копируются ли примитивы при передаче? На самом деле это неважно для нас (как для разработчиков), так как если значение неизменяемо, то без разницы, как оно передается. Сложно? Ну да, поэтому рассмотрим подробнее.



В JS динамическая типизация, значит, все типы определяются в Runtime. Это позволяет функциям принимать любые типы аргументов, добавлять в массив элементы разных типов или менять тип переменной. Динамическая типизация невероятно гибка!



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



Указатель - это просто целое неотрицательное число, индекс ячейки памяти. Обычно размер указателя равен размеру регистра процессора или некоторому другому значению. Например, в Chrome размер указателя равняется 32 битам - это позволяет экономить память.



Все типы представляются указателями. Некоторые указатели реально ссылаются на данные в памяти, другие — "выглядят" как указатели, но на самом деле ими не являются. Т.е. сами данные "закодированы" в указателе и ходить за ними никуда уже не нужно.



Как понять кто есть кто? Для этого используется специальный "тег", который также закодирован внутри указателя (такая техника называется TaggedPointer). Т.е. внутри нашего указателя находятся как сами данные, так и часть битов отведена, для того чтобы пометить, что это не указатель, а какой-то другой тип.



Звучит сложно? Ну, есть такое. Я как-нибудь напишу про это отдельную статью. Главное, что сейчас стоит уловить, что с точки зрения реализации все наши данные выглядят как указатели. Именно поэтому у нас и получается хранить в массиве разные типы значений или передавать разные типы аргументов в функции.