Русский
Русский
English
Статистика
Реклама

Mol_strict Как же меня object Object этот ваш undefined NaN

Здравствуйте, меня зовут Дмитрий Карловский и я не прощаю ошибок. Как только вижу оную тут же бросаю что-нибудь исключительно тяжёлое. И как же тяжела работа программиста на JS...


class Foo extends Object {}const foo = new Foo`Здравствуйте, ${ foo }!`// "Здравствуйте [object Object]!"`В этом месяце вы заработали ${ foo / 1000 } тысяч рублей.`// "В этом месяце вы заработали NaN тысяч рублей."`Ваша цель "${ 'foo'[4] }" наконец-то достигнута.`// "Ваша цель "undefined" наконец-то достигнута."`Осталось ещё ${ foo.length - 1 } целей и вы достигните успеха.`// "Осталось ещё NaN целей и вы достигните успеха."

Облегчить его страдания можно разными путями..


  1. Прикрыться тайпскриптом. Но в рантайме ноги всё равно остаются босыми, и на них кто-нибудь вечно наступает.
  2. Обложиться проверками. Но чуть замешкаешься и рантайм грабли тут же бьют по голове.
  3. Исправить JS. Даже не надейтесь.
  4. Исправить JS рантайм. Ну, давайте подумаем..

Проблемы с динамической типизацией JS возникают по 2 основным причинам:


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

Сначала разберёмся с первой проблемой. JS устроен так, что приведение примитивных типов мы никак исправить не сможем. А вот за приведением объектов у нас есть полный контроль. Поэтому давайте пропатчим глобальный прототип всех объектов, чтобы никакие объекты по умолчанию не допускали приведения типов:


Object.prototype[ Symbol.toPrimitive ] = function() {    throw new TypeError( `Field Symbol(Symbol.toPrimitive) is not defined` )}

Теперь, чтобы разрешить приведение типа, нужно переопределить метод Symbol.toPrimitive у нужного объекта.


Ладно, с первой проблемой разобрались. Но как-то она далась нам подозрительно легко Что-то тут не так! Не похоже это на Веб Ну да ладно, пошли к следующей.


Тут нам нужно как-то перехватывать обращения к произвольным полям объекта, даже если они ещё никак не были объявлены. В JS для этого есть только один механизм прокси. Что ж, напишем такой прокси который при обращении к любому полю громко ругается квазицензурными словами:


export let $mol_strict_object = new Proxy( {}, {    get( obj: object, field: PropertyKey, proxy: object ) {        const name = JSON.stringify( String( field ) )        throw new TypeError( `Field ${ name } is not defined` )    },})

К сожалению, поменять prototype у Object, как мы это сделали ранее, браузер нам уже не даст. Как и поменять прототип у Object.prototype он всегда будет null. Зато мы можем менять прототипы у почти всех остальных стандартных классов унаследованных от Object:



Поэтому пройдёмся по всем глобальным переменным:


for( const name of Reflect.ownKeys( $ ) ) {    // ...}

Отсеим те из них, кто не является классами:


const func = Reflect.getOwnPropertyDescriptor( globalThis, name )!.valueif( typeof func !== 'function' ) continueif(!( 'prototype' in func )) continue

Обратите внимание, что мы не используем globalThis[name] для получения значения, чтобы не триггерить ненужные варнинги.


Теперь оставляем лишь те классы, что непосредственно унаследованы от Object:


const proto = func.prototypeif( Reflect.getPrototypeOf( proto ) !== Object.prototype ) continue

И, наконец, подменяем прототип прототипа с Object.prototype на наш строгий вариант:


Reflect.setPrototypeOf( proto, $mol_strict_object )

Теперь почти все стандартные объекты будут смотреть на вас с укоризной, при обращении к свойству, которому не задано значение. Ведь если значение задано, то браузер не пойдёт по цепочке прототипов и не дойдёт до нашего прокси.


К сожалению, есть и исключения, такие как сам Object, все объектные литералы и всё, что унаследовано от EventTarget, который тоже не дают менять.


И тут CSSStyleDeclaration делает нам подножку: если подменить его прототип на прокси (даже прозрачный, не кидающий исключений), то, внезапно, в Хроме 89 он перестаёт синхронизироваться с атрибутом style dom-элемента:


( <div style={{ color: 'red' }} /> ).outerHTML // <div></div>

Поэтому его пока что приходится вносить в исключения.


Есть и ещё одна беда Если объявлять прикладные классы так:


class Foo {}

То объекты этих классов не будут строгими. Но если объявить их так:


class Foo extends Object {}

То ничего особо не изменится. Но вот если подменить глобальный объект Object на свой строгий подкласс:


globalThis.Object = function $mol_strict_object( this: object, arg: any ) {    let res = Object_orig.call( this, arg )    return this instanceof $mol_strict_object ? this : res}Reflect.setPrototypeOf( Object, Reflect.getPrototypeOf({}) )Reflect.setPrototypeOf( Object.prototype, $mol_strict_object )

То прикладные классы, явно унаследованные от Object, станут строгими.


Итак, что у нас получилось...


class Foo extends Object {}const foo = new Foo`Здравствуйте, ${ foo }!`// TypeError: Field "Symbol(Symbol.toPrimitive)" is not defined`В этом месяце вы заработали ${ foo / 1000 } тысяч рублей.`// TypeError: Field "Symbol(Symbol.toPrimitive)" is not defined`Ваша цель "${ 'foo'[4] }" наконец-то достигнута.`// TypeError: Field "4" is not defined`Осталось ещё ${ foo.length - 1 } целей и вы достигните успеха.`// TypeError: Field "length" is not defined

На случай, если это будут читать дети, подчеркну:


Не меняйте поведение рантайма в библиотеках, ибо таким образом вы можете сломать кому-нибудь приложение. А вот для запуска тестов лучше использовать как раз максимально строгий рантайм мало ли использовать вашу библиотеку будут именно в нём.


Если хотите лучше понять как всё это работает, то гляньте эту статью: Насколько JavaScript сильный?.


Полные исходники можно найти тут: $mol_strict.


Для подключения к своему NPM проекту достаточно прописать где-нибудь в начале точки входа:


import "mol_strict"

Или:


require("mol_strict")

Другие независимые сборки микробиблиотек из $mol можно найти тут: $mol: Usage from NPM ecosystem.


А если хотите обсудить подноготную JS рантайма, то присоединяйтесь к этим чатам:


  • У браузера под юбкой (Обсуждаем разработку браузерных движков. Парсинг, рендеринг, архитектура, вот это всё.)
  • UfoStation Chat (ФП Фронтенд и Программирование.)

Наконец, в твиттере _jinnin можно обнаружить много свежих мыслей на тему фронтенда, JS, UX и прочей дичи.

Источник: habr.com
К списку статей
Опубликовано: 06.04.2021 18:11:06
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Ненормальное программирование

Javascript

$mol

$mol_strict

Js

Runtime

Strict mode

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru