Прежде всего, нужно отметить, что материал Выше говорит не о языке JavaScript, но о том, как его видит автор этого материала.
Все, что написано выше - это авторская интерпретация, которая во многих ее частях противоречит современной спецификации языка ECMAScript и представляет из себя набор "привычных сказок о языке", сформированных в 90-тых годах прошлого тысячелетия.
Кроме очень вольного обращения с тем, как описывается язык JS - автор себе позволяет не меньших вольностей и в отношении академически устоявшихся терминов. Например таких как замыкания.
Судите сами:
Замыкания - невозможны без обсуждения вопроса фунарг проблемы. По причине того, что в зависимости от языка, один и тот же термин может обозначать принципиально противоположный способ решения задачи. Где в случае языка JS выбрано решение проблемы восходящего фунарг. За исключением поведения идентификатора this, где поведение строго противоположное.
То есть, не до конца корректно настаивать на термине "замыкание" в плоскости языка JS, не обозначив с самого начала что в принципе этот термин значит, и каким именно образом и почему в языке JS выбрано именно то решение которое в нем существует.
В противном случае, у человека, который изучает язык, может сложиться впечатление, что, описанное поведение и есть замыканием. Вместо верного - в JS выбрана вот такая стратегия решение проблемы, в других языках может быть иначе.
Тех, кого интересует фундаментальное понимание проблемы замыканий вообще и в JS в частности, рекомендую забыть о материале выше и обратиться к статье Дмитрия Сошникова
Отдельно хочу обратить внимание, на очень "вольное" употребление автором материала терминов подобных лексическому окружению и т.д. В современном JavaScript термин Lexical Environment не до конца совпадает с термином Лексического окружения из академической теории.
Было бы очень хорошо, если бы автор материала, указал на эти расхождения и, может быть, пояснил, зачем ему было нужно формулировать материал именно таким, сильно отдаленным от реальности, способом.
Вы меня пожалуйста простите, но у меня совершенно другая информация.
Object.create никакого отношения к решению проблемы с цепочкой прототипов не имеет.
Object.create разрешал и разрешает только одну проблему - создание Object на основе шаблона содержащего Property Descriptors.
В былинные времена, создав Object, вы в момент создания прибивали гвоздями к нему другой Object называемый прототипом. Назначить прототип, к созданному Object, никаким иным образом было нельзя. Object.create - это ровно тоже самое вид сбоку. Вы создаете Object не используя конструктор, но используя шаблон Property Descriptor, с прибитым к нему гвоздями прототипом.
Создали ли Вы обьект без new? Да создали. Сделали ли Вы работу, которая отличается от того, что можно повторить с использованием new? Нет не сделали. Вы повторили тоже самое, только другими шагами. И это если выносить за скобки тот факт, что любые Object созданные с использованием Property Descriptor - ни сейчас ни тогда нормально не оптимизировались.
Object.setPrototypeOf - это метод, который дал возможность в любой момент времени гибко управлять цепочкой прототипов. Так как это хочется программисту. Чего new или Object.create не дает в принципе в силу их функционала: обьект создается как задано, после чего пользуйтесь им так как есть.
Ваши рассуждения о "медленности Object.setPrototypeOf" очень далеки от реальности. Потому как операция setPrototypeOf влияет только на цепочку скрытых классов (map/shape/hidden class etc...). И как следствие, влияет на производительность только в одном случае - когда код уже был оптимизирован для целевого обьекта, а мы изменили к нему(целевому обьекту) прототип, УЖЕ ПОСЛЕ оптимизации доступа к его проперти. То есть изменили его цепочку скрытых классов. То есть привели к процессу деоптимизации для случаев Inline Cache оптимизаций.
Что безусловно возможно, если мы будем писать код, меняя все время цепочку прототипов под каждый вызов. И при этом, совершенно не будет иметь никакого значения, если мы назначим цепочку единожды, после создания обьекта, так как нам надо и забыли об этом.
Далее о private fields и ваши рассуждения на этот счет.
да, через замыкания можно было делать приватные свойства с 1998 года, но это делало методы привязанными к конкретному экземпляру, что убивало оптимизации движков.
Никаких оптимизация это не убивало. Напротив, делала возможность доступа к таким проперти эффективнее чем что-бы то ни было при условии, когда проперти из родительского контекста оказывалось в ImmutableCurrentContextSlot. (Мы же с Вам о V8 говорим да?)
Доступ же к mutable проперти, из внешнего контекста, было всегда не медленнее доступа к проперти любого Object. Поскольку по своей сути было одним и тем же.
Приватные поля в классах — это про производительность и жесткую инкапсуляцию на уровне движка, а не только про синтаксис.
Я не знаю о чем Вы тут говорите, но на уровне спецификации ECMA, приватные поля, это теже var/let/const declaration только в своем дополнительном Environment. То есть с точки зрения спецификации, скорость доступа к этим проперти ничем не отличается от доступа к проперти из любого внешнего окружения.
Если же взять живу реализацию - например V8, то доступ к private property ничем не отличается по производительности от доступа к любой другой проперти Object связанного с this.
Что с точки зрения производительности, практически не отличается от доступа к var/let/const declaration из внешнего окружения. И проигрывает в случае, если те самые declaration во внешнем окружении оказываются Immutable.
Я вам бы рекомендовал сравнить свои заявления, с тем, какой код генерирует V8 для классов с Private Fields и для Object с доступом к property из, как вы выразились, замыканий. Как для не оптимизированного случая (байт кода) так и для случая оптимизаций на уровне TurboFan.
Это сильно поможет Вам обьяснить мне что Вы имели ввиду, проведя аналогию с реальной работой V8.
Например чем отличается GetKeyedProperty <this>, [0] для private property и любого другого property того же обьекта. Это если на уровне байт кода.
А если на уровне машинного кода, то было бы интересно узнать как вы отличаете Guard код для Private Filed от обычного Property. Я вот внешне в них не вижу никакой разницы.
А самое веселое во всем этом то, что new это всего лишь способ вызвать Ordinary Function с привязкой this к только что созданному пустому обьекту, не забыв связать его протопит с property prototype той самой функции.
Это было круто в 1998 году. Только вот...
Необходимость в использовании new исчезла с появлением в JS возможности назначать прототип к обьекту при помощи стороннего метода, где-то там с момента Object.setPrototype
И жили все долго и счастливо, пока авторам спецификации показалось, что это оскорбление для new и придумали они синтаксис Class-ов. Которые без new ну никак.
Правда люди над ними посмеялись и показали им, как без new с классами на перевес, сделать все тоже самое...
Авторы спецификации оказались людьми не из робкого десятка, и решили добавить в спецификацию Class-ов private property, которые теперь без new никак не повторить.
Впрочем, ровно тоже самое можно было делать и раньше, еще с 1998 года, без исключительного слова private property... Хотя кому это важно в эпоху Class ООП.
Автору спасибо за то, что проделал большую работу, которой не хватает важного дополнения - вся эта работа, не имеет прямого отношения к современному JavaScript и является попыткой автора провести аналогии, рассказать как он(автор видит), но не то как есть на самом деле.
Аналогии не вполне удачные о чем свидетельствует его заявления о магическом this и сложности его понимания.
На просторах youtube есть несколько русскоязычных видео о this, где вплоть до блок схем показывают насколько все просто, если смотреть на язык глазами его спецификации. Самая простая аналогия это воспринимать this как обычный идентификатор, который устанавливается в зависимости от способы вызова функции.
Использование кодировки отличной от UTF8, в случае хромиум подобных браузеров и v8, имеет серьезный недостаток - потеря возможности кеширования данных связанных с интерпретацией и оптимизацией javascript кода.
Иными словами, при каждой новой загрузке такого файла, весь процесс проходит так как в первый раз.
У Автора материала в голове каша относительно терминологии.
У нас есть: терминология официальной спецификации ECMA, где:
такие вещи как: браузер, nodejs и так далее называют термином - HOST.
такие вещи как V8, CoreJS, SpiderMonkey называют термином - Implementation.
После чего идут такие термины как Agent, Realm, Job. Которые описывают специфику поведения частей языка JavaScript и должны быть реализованы Implementation.
И есть сленг. Например такой термин как RunTime - в отношении языка JavaScript используют все как им захочется и может менять свой смысл в зависимости от контекста разговора.
Далее. Никакого EventLoop или CallStack в языке JavaScript - нет. Это особенности реализации.
Спецификация языка ECMA рассказывает нам о существовании Jobs, которые работают в зависимости от обстоятельств. И которые напрямую использует спецификация HTML5 для описания своего механизма EventLoop как часть реализации работы HOST среды,
Спецификация языка ECMA рассказывает нам о Execution context и о Execution context stack, которые сейчас ошибочно связывают с CallStack. Даже не смотря на то, что в спецификации прямо сказано:
Transition of the running execution context status among execution contexts usually occurs in stack-like last-in/first-out manner. However, some ECMAScript features require non-LIFO transitions of the running execution context.
Мне кажется, что если бы материал бы назван - как зная особенности работы RunTime V8 можно сделать свой JavaScript код быстрее, то было бы точнее. Более того, подобное поведение характерно для всех популярных игроков на рынке и отличается только в деталях.
И тем не менее, стоит по критиковать автора за очень поверхностный подход к рассказу о том что такое Map или Shape или Hidden Classes. Потому как выбранная форма повествования, скорее сбивает с толку, чем дает ответ на вопрос почему и зачем что-то вообще нужно делать.
return из конструктора был в прошлом необходим только потому, что не существовало ни одного механизма, который бы позволял как-либо модифицировать цепочку прототипов, после создания обьекта. От того, возможность прямо управлять этим внутри конструктора, была "задуманной фичей".
С появлением методов setPrototypeOf или Reflect не существует ничего, что мог бы сделать только тот самый return в конструкторе. Впрочем как и сам конструктор как таковой. Теперь это вопрос вкуса и оптимизаций.
Отчасти, можно за уши притянуть классы с их private fields. Которые действительно нельзя повторить так же, пуля в пулю, без использования самих классов. Но при этом, их функционал можно повторить другими возможностями того же самого JavaScript.
Все єто совершенно бесполезная игрушка, смысла в которой не больше джаст фор фан, по причине того, что любое из "приложений" разрушит работу системы без какой либо возможности системы воспрепятствовать єтому.
Одновременно с єтим, переход на архитектуру 386 меняет ситуацию, и одновременно с єтим делает не менее бесполезным заявленным код, только уже по другой причине - в архитектуре которая предоставляет подход, к решению фундаментальных проблем многозадачности, даже на ее уровне кооперативной, нужно применять другие машинерии, опирающиеся на архитектуру того же самого 386
JavaScript больше страдает не от своих особенностей архитектуры, которых полно и в других языках, но вот от подобных материалов, где вместо того, чтобы развеять сложившиеся мифы, создаются новые.
Ниже я разберу тезисы материала, покажу что почти каждый из них несостоятелен и говорит скорее о том, о чем сказал Дуглас Крокфорд еще в далеком 2000 году:
JavaScript:The World's Most Misunderstood Programming Language
Судите сами:
"JavaScript отстой, потому что '0' == 0
JavaScript это скриптовый язык программирования. Для этой группы зяыков, подобное поведение является нормой и продиктовано оно теми целями ради которых язык был предназначен. Выше на это уже обратили внимание. Как и на то, что язык развивается и предоставляет альтернативные механизмы решения подобных задач.
eval хуже, чем вы думаете:
Это прекрасная иллюстрация того, как не знание норм языка заставляет делать ошибочные выводы.
Для того, чтобы понять поведение eval нужно понимать следующие его(JS) нормы:
Это динамический интерпретируемый язык, что за исключением правил Static Semantics диктует правила интерпретации любого его statement именно в момент его интерпретации. То есть Агент исполняющий код - понятия не имеет до начала интерпретации конкретного statement что с ним делать. То есть для интерпретатора все что перед вами это набор идентефикаторов поведение которых связывается с определенным алгоритмом именно в момент интерпретации.
Для любого Агента - не существует никакой функции eval math console и так далее. Это все идентификаторы которые ссылаются на что-то. И что делать с этим что-то, можно понять только в конкретный момент интерпретации.
eval это всего лишь property от Global Object. Который может быть связан с чем угодно.
конструкция вида identifier() интерпретируется агентом как Callable Expression. Агент ничего не знает о идентификаторе. Как следствие код вида: eval(';'); ничего для еганта не означает кроме того, что перед ним выражение которое нужно разбить на части, получить результаты выполнения каждой из них и подставить их в связанный алгоритм.
До появления "strict mode", eval был связан с поведением, которое описывалось как стандартное выполнение кода в глобальном окружении. С появлением "strict mode", super statement в спецификацию добавили поведение в описания алгоритма работы функционального обьекта, где в случае использования eval для инициализации окружения заданного кода - требуется брать не глобальное окружение, но окружение где напрямую eval был задекларирован.
А теперь собираем все в кучу:
eval - это обычный идентификатор который для агента, сам по себе, ничего не означает. Просто набор символов который интерпретируется как Reference Expression. Даже без какой либо проверки на то существует ли вообще такой Reference.
С появлением strict mode и super поведение Regular Function было изменено, что потребовало изменения ИМЕННО АЛГОРИТМА самого Callable Expression и как следствие внесение правок в поведение eval.
Сравнить просто ссылку на функциональный обьект описывающий eval с ссылкой внутри любого другого идентификатора (то что автор назвал переименованием) просто так нельзя. По причине существование super() который НЕ является вызовом функции, но является особым специфическим поведением, в отличии от eval который является типичным expression.
Добавте к этому тот факт, что способов вызвать функциональный обьект в JS больше 14 разных штук. И каждый из них должен быть учтен в связи с новым поведением strict super etc, что в случае с eval смерти подобно, так как это фундаментальная часть языка.
Я постарался очень сжато описать функционал того чем является eval. Деталей и ньюансов его реализации масса. Как следствие это не eval странный - это автор не понимает как он работает.
Циклы в JS делают вид, что их переменные захватываются по значению
В JavaScript ничего не делается по значению если в спецификации не указано явным образом обратное.
Algorithm steps may declare named aliases for any value using the form “Let x be someValue”. These aliases are reference-like in that both x and someValue refer to the same underlying data and modifications to either are visible to both. Algorithm steps that want to avoid this reference-like behaviour should explicitly make a copy of the right-hand side: “Let x be a copy of someValue” creates a shallow copy of someValue.
Как только это становится очевидным, то и eval уже не такой странный и for statement оказывается работает более чем логично.
Тот самый "ложный" объект
Общепринято считать, что в JavaScript есть 8 ложных (falsy) значений: false, +0, -0, NaN, "", null, undefined и 0n
Это общепринятое заблуждение. Потому, что в JS нет универсальных правил разрешения типов. И то, как именно происходит разрешение конкретного типа, зависит от связанного с текущим интерпретируемым выражением с которым связан алгоритм спецификации.
То что Вы привели как falsy - это поведение абстрактной операции toBolean, которая не обязательна для вызова при любых типах сравнения.
Далее, ваша претензия [[IsHTMLDDA]] не верна по сути, потому, что она описана в спецификации HTML5 и второе - использовать этот механизм может любой External Object а не только document.all.
Ну и наконец вот Вам пара примеров для размышления
Это не falsy значения, или странные обьекты. Это не знание фундаментального принципа заложенного в JS с момента еге первой спецификации. Очень рекомендую к ознакомлению главы о том что такое Object в JS и что такое Exotic Object
Графемы и перебор строк
Тут вообще сплошная ваша. Разложим по полочкам:
Человек оценивая длину строки, ожидает увидеть количество ОТОБРАЖАЕМЫХ символов
Эволюция произвела десятки разных способов формирования такого символа ( латиница, иероглифиы, арабская вязь и т.д.) где способы формирования такого символа не укаладываются в схему: таблица где каждому коду соотвествует один знак
unicode это стандарт, который попытался унифицировать эту работу. Обьем документации по этому стандарту больше чем весь JS вместе взятый
Есть стандарт unicode (описывающий способы формирования символа) а есть способ кодирования этого способа. UTF8, UTF16, UTF32 - это способы кодирования где 8,16,32 обозначатает количество используемых бит на реализацию нашей задачи.
В зависимости от того, какое кодирование мы используем, один и тот же символ может кодироваться разной последовательностью байт. То есть один и тот же символ может быть закодирован как одним байтом, двумя, тремя и так далее. И все это может происходит в рамках одной и той же системы кодирования. Например: один и тот же символ представлен разной последовательностью кодирования
`Á`.length; // 1
`A\u0301`; // 2
и оба эти символа с точки зрения Unicode идентичны. Это А с ударением.
Стандарт Unicode пытается описать все возможные комбинации подобных поведений. Кодирование - пытается закодировать все это в рамках выданных бит. В результате чего представление символа может занимать последовательность байт.
JS реализует систему кодирования UTF16.
Возможности языка JS позволяют как адресовать конкретный байт в строке, так и разбирать его по два байта на один управляющий символ. Потому
Так как Exotic Object String - для метода length реализует алгоритм, где подсчитывается простое колличества байт на кодирование UTF16 то для '😀' мы видим ответ 2; В то же создавая Exotic Object Array мы используем spread, который вызывает Iterator у Object String который в свою очередь использует не побайтовую адресацию, но преобразование CodePointAt который итерируетсся уже по UTF16 кодированным символам (парам или нет - не важно). При этом разбирая строку именно по CodePoint кодированию стандарта. Но не по представлению символа. То есть если у нас будет представление которое потребует больше 16 бит, итератор разобьет это на два проперти.
Теперь мы должны понимать, что привычные операции существующие в JS считают либо сырые байты, либо CodePoint. Чего совершенно недостаточно для того чтобы получить ожидаемый результат для человека - сколько вижу столько и считаю.
Выход из этой ситуации - это либо самостоятельная реалиация станадрта Unicode со всеми его 900 страниц спецификации, либо использование регулярных выражений с ключем /u для максимальной поддержки старых браузеров или ключем /v для того чтобы получить максимальный комфорт с работой со строками с поддержкой ВСЕХ символьных классов стандарта Unicode.
Разрежённые массивы
В JS то что вы создается при помощи литерала [] или конструктора Array называется Exotic Object Array. Это не Array в том смысле как это может быть в других языках. Это обьект с особым поведением.
Только типизированные массивы ведут себя именно как привычные массивы.
Что такое Exotic Object я давал ссылку раньше.
Вместо ИГОГО
О точке с запятой. Правило - ставте точку с запятой всегда написано кровью. Тут действительно можно ругать язык за то, что в нем существует алгоритм автоматической простановки.
Всё, что связано с == и !=
Ничего странного в них нет, если читать спецификацию, а не прибывать в иллюзиях вида: приведение типов и так далее
Всё, что связано с this
Реализуется простой блок схемой. Если максимально упростить - то все что нужно знать так это, что все в JS это ссылочныее типы (Reference Like термин спецификации) и что this это просто скрытый параметр для вызова Regular Function. Установка которого зависит от формы callable expression
NaN не равен ничему, даже самому себе
+0 против -0
Ошибки из-за плавающей точности (IEEE 754)
Это все функционирует в рамках стандарта IEE754 и абсолютно идентично другим языкам программирования которые реализуют этот стандарт. Например C Pascal Fort и так далее. То есть утверждение автора ложно. Никаких особенностей там нет. Есть не знание языка или стандарта.
typeof null - это "object"
Это абсолютно нормальное поведение описанное в первой же версии спецификации. Никакой ошибки тут нет, о чем говорил как сам Эйк так и многие другие. Была архитектурная ошибка допустившая существование null и undefined в языке где с самого начала в этом необходимости не было. Тем не менее главное - это описанное спецификацией с его первой версии поведение. Для меня логичное.
Особенности работы без строгого режима и использование var
Возврат примитивных значений из конструкторов
и т.д. и т.п.
это не знание фундаментальных особенностей языка, которые являются его архитектурой.
Очень плохо когда материал такого рода, вместо того чтобы заниматься ликбезом, множит невежество.
Статья не имеет никакого отношения к внутреннему устройству языка JavaScript. Статья описывает одну из возможностей его реализации.
Например, для работы JavaScript согласно его спецификации не нужен call stack. Он там вообще никаким образом не описывается. Работа и вызов функций происходит без необходимости что-то класть в стек.
Или цикл событий, который является одним ИЗ способов реализации работы с агентом выполняющим JavaScript код. Опять же спецификации языка глубоко плевать на то, каким образом вы будете помещать в Job Queue задачи на выполнения.
Представьте на секунду, что Agent исполняющий JS код, автоматически научился распределять нагрузку на все доступные потоки.
Код Мартина будет оптимизирован автоматически. Менять ничего не нужно будет. Вычисление простого числа воспользуется всеми доступными ресурсами многопроцессорной архитектуры.
Код на 16 строк, все так же будет работать в одном потоке. И оптимизировать его сможет только человек.
Если бы я попросил кандидата решить подобную задачу и в ответ получил бы такие шестьдесят строк, то кандидат не прошёл бы собеседование.
Если бы Вы проходили у меня собеседование, то Ваш код на 16 строчек, был бы завернут на первой же строке. const primes: number[] = [];
Сравните эту же строку с кодом Мартина. И попробуйте пояснить почему Вас бы сразу завернули.
Потому, что он говорит о Вашем не знании того как работает JS изнутри. В то же время код Мартина, заставил бы меня задать несколько уточняющих вопросов и продолжить собеседование.
Мне кажется, что Вы не поняли главное о чем писал дядя Боб. Он не давал Вам рецептов. Он прививал культуру мышления. Ваш код в 16 строчек - выполняет свою задачу. Он прекрасно может быть использован для решения на коленке. Но он безобразен. Безобразен хотя бы потому, что не оставляет никаких шансов современным оптимизаторам что-то с этим кодом сделать.
В тоже время код от Мартина такие возможности дает. И если инструмент способен делать подобные оптимизации - он с кодом Мартина может сделать много чего полезного и интересного. А с кодом в 16 строк - почти ничего.
При холодром старте, подготовка js кода может занимать больше 100% времени используемошо на его исполнение. Специфика языка.
Как следствие - кеширование как минимум, а способность управлять тем что и как кешируется/компилируется - как максимум, один из золотых граалей старта приложения.
Мне показалось, что Вы не поняли автора. Автор говорил о том, что программисты JS кода, до конца не понимают как использовать инструмент, которы! они используют.
Инструмент, потенциал которого, в силу общей слабости JS комьюнити, до сих пор не раскрыт и имеет большой потенциал. С чем лично я, более чем согласен.
abortController - никакого отношения к JS не имеет. Єто api которое может предоставить host среда. А может и не предоставить, в отличии от того что она обязана предоставить для исполнения js кода согласно спецификации.
Прежде всего, нужно отметить, что материал Выше говорит не о языке JavaScript, но о том, как его видит автор этого материала.
Все, что написано выше - это авторская интерпретация, которая во многих ее частях противоречит современной спецификации языка ECMAScript и представляет из себя набор "привычных сказок о языке", сформированных в 90-тых годах прошлого тысячелетия.
Кроме очень вольного обращения с тем, как описывается язык JS - автор себе позволяет не меньших вольностей и в отношении академически устоявшихся терминов. Например таких как замыкания.
Судите сами:
Замыкания - невозможны без обсуждения вопроса фунарг проблемы. По причине того, что в зависимости от языка, один и тот же термин может обозначать принципиально противоположный способ решения задачи. Где в случае языка JS выбрано решение проблемы восходящего фунарг. За исключением поведения идентификатора this, где поведение строго противоположное.
То есть, не до конца корректно настаивать на термине "замыкание" в плоскости языка JS, не обозначив с самого начала что в принципе этот термин значит, и каким именно образом и почему в языке JS выбрано именно то решение которое в нем существует.
В противном случае, у человека, который изучает язык, может сложиться впечатление, что, описанное поведение и есть замыканием. Вместо верного - в JS выбрана вот такая стратегия решение проблемы, в других языках может быть иначе.
Тех, кого интересует фундаментальное понимание проблемы замыканий вообще и в JS в частности, рекомендую забыть о материале выше и обратиться к статье Дмитрия Сошникова
Отдельно хочу обратить внимание, на очень "вольное" употребление автором материала терминов подобных лексическому окружению и т.д. В современном JavaScript термин Lexical Environment не до конца совпадает с термином Лексического окружения из академической теории.
Было бы очень хорошо, если бы автор материала, указал на эти расхождения и, может быть, пояснил, зачем ему было нужно формулировать материал именно таким, сильно отдаленным от реальности, способом.
Вы меня пожалуйста простите, но у меня совершенно другая информация.
Object.create никакого отношения к решению проблемы с цепочкой прототипов не имеет.
Object.create разрешал и разрешает только одну проблему - создание Object на основе шаблона содержащего Property Descriptors.
В былинные времена, создав Object, вы в момент создания прибивали гвоздями к нему другой Object называемый прототипом. Назначить прототип, к созданному Object, никаким иным образом было нельзя. Object.create - это ровно тоже самое вид сбоку. Вы создаете Object не используя конструктор, но используя шаблон Property Descriptor, с прибитым к нему гвоздями прототипом.
Создали ли Вы обьект без new? Да создали. Сделали ли Вы работу, которая отличается от того, что можно повторить с использованием new? Нет не сделали. Вы повторили тоже самое, только другими шагами. И это если выносить за скобки тот факт, что любые Object созданные с использованием Property Descriptor - ни сейчас ни тогда нормально не оптимизировались.
Object.setPrototypeOf - это метод, который дал возможность в любой момент времени гибко управлять цепочкой прототипов. Так как это хочется программисту. Чего new или Object.create не дает в принципе в силу их функционала: обьект создается как задано, после чего пользуйтесь им так как есть.
Ваши рассуждения о "медленности Object.setPrototypeOf" очень далеки от реальности. Потому как операция setPrototypeOf влияет только на цепочку скрытых классов (map/shape/hidden class etc...). И как следствие, влияет на производительность только в одном случае - когда код уже был оптимизирован для целевого обьекта, а мы изменили к нему(целевому обьекту) прототип, УЖЕ ПОСЛЕ оптимизации доступа к его проперти. То есть изменили его цепочку скрытых классов. То есть привели к процессу деоптимизации для случаев Inline Cache оптимизаций.
Что безусловно возможно, если мы будем писать код, меняя все время цепочку прототипов под каждый вызов. И при этом, совершенно не будет иметь никакого значения, если мы назначим цепочку единожды, после создания обьекта, так как нам надо и забыли об этом.
Далее о private fields и ваши рассуждения на этот счет.
Никаких оптимизация это не убивало. Напротив, делала возможность доступа к таким проперти эффективнее чем что-бы то ни было при условии, когда проперти из родительского контекста оказывалось в ImmutableCurrentContextSlot. (Мы же с Вам о V8 говорим да?)
Доступ же к mutable проперти, из внешнего контекста, было всегда не медленнее доступа к проперти любого Object. Поскольку по своей сути было одним и тем же.
Я не знаю о чем Вы тут говорите, но на уровне спецификации ECMA, приватные поля, это теже var/let/const declaration только в своем дополнительном Environment. То есть с точки зрения спецификации, скорость доступа к этим проперти ничем не отличается от доступа к проперти из любого внешнего окружения.
Если же взять живу реализацию - например V8, то доступ к private property ничем не отличается по производительности от доступа к любой другой проперти Object связанного с this.
Что с точки зрения производительности, практически не отличается от доступа к var/let/const declaration из внешнего окружения. И проигрывает в случае, если те самые declaration во внешнем окружении оказываются Immutable.
Я вам бы рекомендовал сравнить свои заявления, с тем, какой код генерирует V8 для классов с Private Fields и для Object с доступом к property из, как вы выразились, замыканий. Как для не оптимизированного случая (байт кода) так и для случая оптимизаций на уровне TurboFan.
Это сильно поможет Вам обьяснить мне что Вы имели ввиду, проведя аналогию с реальной работой V8.
Например чем отличается GetKeyedProperty <this>, [0] для private property и любого другого property того же обьекта. Это если на уровне байт кода.
А если на уровне машинного кода, то было бы интересно узнать как вы отличаете Guard код для Private Filed от обычного Property. Я вот внешне в них не вижу никакой разницы.
А самое веселое во всем этом то, что new это всего лишь способ вызвать Ordinary Function с привязкой this к только что созданному пустому обьекту, не забыв связать его протопит с property prototype той самой функции.
Это было круто в 1998 году. Только вот...
Необходимость в использовании new исчезла с появлением в JS возможности назначать прототип к обьекту при помощи стороннего метода, где-то там с момента Object.setPrototype
И жили все долго и счастливо, пока авторам спецификации показалось, что это оскорбление для new и придумали они синтаксис Class-ов. Которые без new ну никак.
Правда люди над ними посмеялись и показали им, как без new с классами на перевес, сделать все тоже самое...
Авторы спецификации оказались людьми не из робкого десятка, и решили добавить в спецификацию Class-ов private property, которые теперь без new никак не повторить.
Впрочем, ровно тоже самое можно было делать и раньше, еще с 1998 года, без исключительного слова private property... Хотя кому это важно в эпоху Class ООП.
Такой вот круговорот фигни в природе.
Когда-то, в былинные времена, были конкурсы - кто больше придумает способов обнулить регистр ax.
Все ораторы выше забыли про способ связанный с in
Автору спасибо за то, что проделал большую работу, которой не хватает важного дополнения - вся эта работа, не имеет прямого отношения к современному JavaScript и является попыткой автора провести аналогии, рассказать как он(автор видит), но не то как есть на самом деле.
Аналогии не вполне удачные о чем свидетельствует его заявления о магическом this и сложности его понимания.
На просторах youtube есть несколько русскоязычных видео о this, где вплоть до блок схем показывают насколько все просто, если смотреть на язык глазами его спецификации.
Самая простая аналогия это воспринимать this как обычный идентификатор, который устанавливается в зависимости от способы вызова функции.
И т.д. и т.п.
Использование кодировки отличной от UTF8, в случае хромиум подобных браузеров и v8, имеет серьезный недостаток - потеря возможности кеширования данных связанных с интерпретацией и оптимизацией javascript кода.
Иными словами, при каждой новой загрузке такого файла, весь процесс проходит так как в первый раз.
У Автора материала в голове каша относительно терминологии.
У нас есть: терминология официальной спецификации ECMA, где:
такие вещи как: браузер, nodejs и так далее называют термином - HOST.
такие вещи как V8, CoreJS, SpiderMonkey называют термином - Implementation.
После чего идут такие термины как Agent, Realm, Job. Которые описывают специфику поведения частей языка JavaScript и должны быть реализованы Implementation.
И есть сленг. Например такой термин как RunTime - в отношении языка JavaScript используют все как им захочется и может менять свой смысл в зависимости от контекста разговора.
Далее. Никакого EventLoop или CallStack в языке JavaScript - нет. Это особенности реализации.
Спецификация языка ECMA рассказывает нам о существовании Jobs, которые работают в зависимости от обстоятельств. И которые напрямую использует спецификация HTML5 для описания своего механизма EventLoop как часть реализации работы HOST среды,
Спецификация языка ECMA рассказывает нам о Execution context и о Execution context stack, которые сейчас ошибочно связывают с CallStack. Даже не смотря на то, что в спецификации прямо сказано:
И так далее и тому подобное.
Мне кажется, что если бы материал бы назван - как зная особенности работы RunTime V8 можно сделать свой JavaScript код быстрее, то было бы точнее. Более того, подобное поведение характерно для всех популярных игроков на рынке и отличается только в деталях.
И тем не менее, стоит по критиковать автора за очень поверхностный подход к рассказу о том что такое Map или Shape или Hidden Classes. Потому как выбранная форма повествования, скорее сбивает с толку, чем дает ответ на вопрос почему и зачем что-то вообще нужно делать.
Я бы рекомендовал, начать с видео на youtube Андрей Мелихов — V8 под капотом или если не пугает английский язык Franziska Hinkelmann: JavaScript engines - how do they even? | JSConf EU
return из конструктора был в прошлом необходим только потому, что не существовало ни одного механизма, который бы позволял как-либо модифицировать цепочку прототипов, после создания обьекта. От того, возможность прямо управлять этим внутри конструктора, была "задуманной фичей".
С появлением методов setPrototypeOf или Reflect не существует ничего, что мог бы сделать только тот самый return в конструкторе. Впрочем как и сам конструктор как таковой. Теперь это вопрос вкуса и оптимизаций.
Отчасти, можно за уши притянуть классы с их private fields. Которые действительно нельзя повторить так же, пуля в пулю, без использования самих классов. Но при этом, их функционал можно повторить другими возможностями того же самого JavaScript.
Все єто совершенно бесполезная игрушка, смысла в которой не больше джаст фор фан, по причине того, что любое из "приложений" разрушит работу системы без какой либо возможности системы воспрепятствовать єтому.
Одновременно с єтим, переход на архитектуру 386 меняет ситуацию, и одновременно с єтим делает не менее бесполезным заявленным код, только уже по другой причине - в архитектуре которая предоставляет подход, к решению фундаментальных проблем многозадачности, даже на ее уровне кооперативной, нужно применять другие машинерии, опирающиеся на архитектуру того же самого 386
JavaScript больше страдает не от своих особенностей архитектуры, которых полно и в других языках, но вот от подобных материалов, где вместо того, чтобы развеять сложившиеся мифы, создаются новые.
Ниже я разберу тезисы материала, покажу что почти каждый из них несостоятелен и говорит скорее о том, о чем сказал Дуглас Крокфорд еще в далеком 2000 году:
Судите сами:
JavaScript это скриптовый язык программирования. Для этой группы зяыков, подобное поведение является нормой и продиктовано оно теми целями ради которых язык был предназначен. Выше на это уже обратили внимание. Как и на то, что язык развивается и предоставляет альтернативные механизмы решения подобных задач.
eval хуже, чем вы думаете:
Это прекрасная иллюстрация того, как не знание норм языка заставляет делать ошибочные выводы.
Для того, чтобы понять поведение eval нужно понимать следующие его(JS) нормы:
Это динамический интерпретируемый язык, что за исключением правил Static Semantics диктует правила интерпретации любого его statement именно в момент его интерпретации. То есть Агент исполняющий код - понятия не имеет до начала интерпретации конкретного statement что с ним делать. То есть для интерпретатора все что перед вами это набор идентефикаторов поведение которых связывается с определенным алгоритмом именно в момент интерпретации.
Для любого Агента - не существует никакой функции eval math console и так далее. Это все идентификаторы которые ссылаются на что-то. И что делать с этим что-то, можно понять только в конкретный момент интерпретации.
eval это всего лишь property от Global Object. Который может быть связан с чем угодно.
конструкция вида identifier() интерпретируется агентом как Callable Expression. Агент ничего не знает о идентификаторе. Как следствие код вида:
eval(';'); ничего для еганта не означает кроме того, что перед ним выражение которое нужно разбить на части, получить результаты выполнения каждой из них и подставить их в связанный алгоритм.До появления "strict mode", eval был связан с поведением, которое описывалось как стандартное выполнение кода в глобальном окружении. С появлением "strict mode", super statement в спецификацию добавили поведение в описания алгоритма работы функционального обьекта, где в случае использования eval для инициализации окружения заданного кода - требуется брать не глобальное окружение, но окружение где напрямую eval был задекларирован.
А теперь собираем все в кучу:
eval - это обычный идентификатор который для агента, сам по себе, ничего не означает. Просто набор символов который интерпретируется как Reference Expression. Даже без какой либо проверки на то существует ли вообще такой Reference.
С появлением strict mode и super поведение Regular Function было изменено, что потребовало изменения ИМЕННО АЛГОРИТМА самого Callable Expression и как следствие внесение правок в поведение eval.
Сравнить просто ссылку на функциональный обьект описывающий eval с ссылкой внутри любого другого идентификатора (то что автор назвал переименованием) просто так нельзя. По причине существование super() который НЕ является вызовом функции, но является особым специфическим поведением, в отличии от eval который является типичным expression.
Добавте к этому тот факт, что способов вызвать функциональный обьект в JS больше 14 разных штук. И каждый из них должен быть учтен в связи с новым поведением strict super etc, что в случае с eval смерти подобно, так как это фундаментальная часть языка.
Я постарался очень сжато описать функционал того чем является eval. Деталей и ньюансов его реализации масса. Как следствие это не eval странный - это автор не понимает как он работает.
Циклы в JS делают вид, что их переменные захватываются по значению
В JavaScript ничего не делается по значению если в спецификации не указано явным образом обратное.
Как только это становится очевидным, то и eval уже не такой странный и for statement оказывается работает более чем логично.
Тот самый "ложный" объект
Это общепринятое заблуждение. Потому, что в JS нет универсальных правил разрешения типов. И то, как именно происходит разрешение конкретного типа, зависит от связанного с текущим интерпретируемым выражением с которым связан алгоритм спецификации.
То что Вы привели как falsy - это поведение абстрактной операции toBolean, которая не обязательна для вызова при любых типах сравнения.
Далее, ваша претензия [[IsHTMLDDA]] не верна по сути, потому, что она описана в спецификации HTML5 и второе - использовать этот механизм может любой External Object а не только document.all.
Ну и наконец вот Вам пара примеров для размышления
Это не falsy значения, или странные обьекты. Это не знание фундаментального принципа заложенного в JS с момента еге первой спецификации. Очень рекомендую к ознакомлению главы о том что такое Object в JS и что такое Exotic Object
Графемы и перебор строк
Тут вообще сплошная ваша. Разложим по полочкам:
Человек оценивая длину строки, ожидает увидеть количество ОТОБРАЖАЕМЫХ символов
Эволюция произвела десятки разных способов формирования такого символа ( латиница, иероглифиы, арабская вязь и т.д.) где способы формирования такого символа не укаладываются в схему: таблица где каждому коду соотвествует один знак
unicode это стандарт, который попытался унифицировать эту работу. Обьем документации по этому стандарту больше чем весь JS вместе взятый
Есть стандарт unicode (описывающий способы формирования символа) а есть способ кодирования этого способа. UTF8, UTF16, UTF32 - это способы кодирования где 8,16,32 обозначатает количество используемых бит на реализацию нашей задачи.
В зависимости от того, какое кодирование мы используем, один и тот же символ может кодироваться разной последовательностью байт. То есть один и тот же символ может быть закодирован как одним байтом, двумя, тремя и так далее. И все это может происходит в рамках одной и той же системы кодирования. Например: один и тот же символ представлен разной последовательностью кодирования
и оба эти символа с точки зрения Unicode идентичны. Это А с ударением.
Стандарт Unicode пытается описать все возможные комбинации подобных поведений. Кодирование - пытается закодировать все это в рамках выданных бит. В результате чего представление символа может занимать последовательность байт.
JS реализует систему кодирования UTF16.
Возможности языка JS позволяют как адресовать конкретный байт в строке, так и разбирать его по два байта на один управляющий символ.
Потому
Так как Exotic Object String - для метода length реализует алгоритм, где подсчитывается простое колличества байт на кодирование UTF16 то для '😀' мы видим ответ 2;
В то же создавая Exotic Object Array мы используем spread, который вызывает Iterator у Object String который в свою очередь использует не побайтовую адресацию, но преобразование CodePointAt который итерируетсся уже по UTF16 кодированным символам (парам или нет - не важно).
При этом разбирая строку именно по CodePoint кодированию стандарта. Но не по представлению символа. То есть если у нас будет представление которое потребует больше 16 бит, итератор разобьет это на два проперти.
Теперь мы должны понимать, что привычные операции существующие в JS считают либо сырые байты, либо CodePoint. Чего совершенно недостаточно для того чтобы получить ожидаемый результат для человека - сколько вижу столько и считаю.
Выход из этой ситуации - это либо самостоятельная реалиация станадрта Unicode со всеми его 900 страниц спецификации, либо использование регулярных выражений с ключем /u для максимальной поддержки старых браузеров или ключем /v для того чтобы получить максимальный комфорт с работой со строками с поддержкой ВСЕХ символьных классов стандарта Unicode.
Разрежённые массивы
В JS то что вы создается при помощи литерала [] или конструктора Array называется Exotic Object Array. Это не Array в том смысле как это может быть в других языках. Это обьект с особым поведением.
Только типизированные массивы ведут себя именно как привычные массивы.
Что такое Exotic Object я давал ссылку раньше.
Вместо ИГОГО
О точке с запятой. Правило - ставте точку с запятой всегда написано кровью. Тут действительно можно ругать язык за то, что в нем существует алгоритм автоматической простановки.
Ничего странного в них нет, если читать спецификацию, а не прибывать в иллюзиях вида: приведение типов и так далее
Реализуется простой блок схемой. Если максимально упростить - то все что нужно знать так это, что все в JS это ссылочныее типы (Reference Like термин спецификации) и что this это просто скрытый параметр для вызова Regular Function. Установка которого зависит от формы callable expression
Это все функционирует в рамках стандарта IEE754 и абсолютно идентично другим языкам программирования которые реализуют этот стандарт. Например C Pascal Fort и так далее.
То есть утверждение автора ложно. Никаких особенностей там нет. Есть не знание языка или стандарта.
Это абсолютно нормальное поведение описанное в первой же версии спецификации. Никакой ошибки тут нет, о чем говорил как сам Эйк так и многие другие. Была архитектурная ошибка допустившая существование null и undefined в языке где с самого начала в этом необходимости не было.
Тем не менее главное - это описанное спецификацией с его первой версии поведение. Для меня логичное.
это не знание фундаментальных особенностей языка, которые являются его архитектурой.
Очень плохо когда материал такого рода, вместо того чтобы заниматься ликбезом, множит невежество.
Если код, способен модифицировать /etc то єто означает что у него есть рут.
После монимания єтого момента чтения материала теряет всяческий смысл. Даже если настроен SELinux.
Код описаный в статье - єто детская подделка на коленке, развитость которой на уровне 1998 года.
Статья не имеет никакого отношения к внутреннему устройству языка JavaScript.
Статья описывает одну из возможностей его реализации.
Например, для работы JavaScript согласно его спецификации не нужен call stack. Он там вообще никаким образом не описывается. Работа и вызов функций происходит без необходимости что-то класть в стек.
Или цикл событий, который является одним ИЗ способов реализации работы с агентом выполняющим JavaScript код. Опять же спецификации языка глубоко плевать на то, каким образом вы будете помещать в Job Queue задачи на выполнения.
Представьте на секунду, что Agent исполняющий JS код, автоматически научился распределять нагрузку на все доступные потоки.
Код Мартина будет оптимизирован автоматически. Менять ничего не нужно будет. Вычисление простого числа воспользуется всеми доступными ресурсами многопроцессорной архитектуры.
Код на 16 строк, все так же будет работать в одном потоке. И оптимизировать его сможет только человек.
Это Вам как самый показательный пример.
Если бы Вы проходили у меня собеседование, то Ваш код на 16 строчек, был бы завернут на первой же строке.
const primes: number[] = [];Сравните эту же строку с кодом Мартина. И попробуйте пояснить почему Вас бы сразу завернули.
Потому, что он говорит о Вашем не знании того как работает JS изнутри.
В то же время код Мартина, заставил бы меня задать несколько уточняющих вопросов и продолжить собеседование.
Мне кажется, что Вы не поняли главное о чем писал дядя Боб. Он не давал Вам рецептов. Он прививал культуру мышления. Ваш код в 16 строчек - выполняет свою задачу. Он прекрасно может быть использован для решения на коленке. Но он безобразен. Безобразен хотя бы потому, что не оставляет никаких шансов современным оптимизаторам что-то с этим кодом сделать.
В тоже время код от Мартина такие возможности дает. И если инструмент способен делать подобные оптимизации - он с кодом Мартина может сделать много чего полезного и интересного. А с кодом в 16 строк - почти ничего.
Єто ошибка потому, что выбрав оптимальную архитектуру, вы, как человек который не знает систему, так и не поймете почему она тормозит.
А тормозить она будет из-за невозможности оптимизировать ваш код Агентом, который пришел в восхищение от вашей архитектуры и читаемости кода.
При холодром старте, подготовка js кода может занимать больше 100% времени используемошо на его исполнение. Специфика языка.
Как следствие - кеширование как минимум, а способность управлять тем что и как кешируется/компилируется - как максимум, один из золотых граалей старта приложения.
Мне показалось, что Вы не поняли автора. Автор говорил о том, что программисты JS кода, до конца не понимают как использовать инструмент, которы! они используют.
Инструмент, потенциал которого, в силу общей слабости JS комьюнити, до сих пор не раскрыт и имеет большой потенциал. С чем лично я, более чем согласен.
Лекции Константина Анохина, действующего нейробиолога, о том что мы сейчас знаем про память. Минимум 10 летней давности.
Рассказывают намного подробнее. В том числе утверждается о существовании єксперемнтальных препаратов радикально меняющих процесс запоминания.
abortController - никакого отношения к JS не имеет. Єто api которое может предоставить host среда. А может и не предоставить, в отличии от того что она обязана предоставить для исполнения js кода согласно спецификации.