[Ненормальное программирование, JavaScript] $mol_strict: Как же меня [object Object] этот ваш undefined NaN‼
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Здравствуйте, меня зовут Дмитрий Карловский и я… не прощаю ошибок. Как только вижу оную — тут же бросаю что-нибудь исключительно тяжёлое. И как же тяжела работа программиста на JS...
class Foo extends Object {}
const foo = new Foo
`Здравствуйте, ${ foo }!`
// "Здравствуйте [object Object]!"
`В этом месяце вы заработали ${ foo / 1000 } тысяч рублей.`
// "В этом месяце вы заработали NaN тысяч рублей."
`Ваша цель "${ 'foo'[4] }" наконец-то достигнута.`
// "Ваша цель "undefined" наконец-то достигнута."
`Осталось ещё ${ foo.length - 1 } целей и вы достигните успеха.`
// "Осталось ещё NaN целей и вы достигните успеха."
Облегчить его страдания можно разными путями..
- Прикрыться тайпскриптом. Но в рантайме ноги всё равно остаются босыми, и на них кто-нибудь вечно наступает.
- Обложиться проверками. Но чуть замешкаешься и рантайм грабли тут же бьют по голове.
- Исправить JS. Даже не надейтесь.
- Исправить 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 )!.value
if( typeof func !== 'function' ) continue
if(!( 'prototype' in func )) continue
Обратите внимание, что мы не используем globalThis[name] для получения значения, чтобы не триггерить ненужные варнинги.
Теперь оставляем лишь те классы, что непосредственно унаследованы от Object:
const proto = func.prototype
if( 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
===========
Похожие новости:
- [Разработка веб-сайтов, .NET, ASP, C#, Микросервисы] Учим ASP.NET Core новым трюкам на примере Json Rpc 2.0
- [Ненормальное программирование, Программирование, Go] Go Quiz
- [JavaScript, Научно-популярное, Космонавтика] Для чего космическому кораблю крылья?
- [Разработка веб-сайтов, JavaScript, Программирование, TypeScript] Совет #1 по ознакомлению с новыми кодовыми базами JavaScript (перевод)
- [Ненормальное программирование, Forth] Численный FORTH
- [JavaScript] Для чего нам нужно Moment.js и Day.js
- [Ненормальное программирование, Я пиарюсь, C++, ООП] CDD — Cli Driven Development
- [Python, JavaScript, Программирование, HTML] Python & EEL. Делаем просто на Python’е и красиво на JS
- [Информационная безопасность, JavaScript, Криптовалюты] Трой Хант разместил на доменах Coinhive предупреждения о взломанных сайтах
- [JavaScript, Accessibility, VueJS, TypeScript] Программа HolyJS: нюансы DevTools, минусы GraphQL, инструменты a11y
Теги для поиска: #_nenormalnoe_programmirovanie (Ненормальное программирование), #_javascript, #_$mol, #_$mol_strict, #_js, #_runtime, #_strict_mode, #_nenormalnoe_programmirovanie (
Ненормальное программирование
), #_javascript
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 00:40
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Здравствуйте, меня зовут Дмитрий Карловский и я… не прощаю ошибок. Как только вижу оную — тут же бросаю что-нибудь исключительно тяжёлое. И как же тяжела работа программиста на JS... class Foo extends Object {}
const foo = new Foo `Здравствуйте, ${ foo }!` // "Здравствуйте [object Object]!" `В этом месяце вы заработали ${ foo / 1000 } тысяч рублей.` // "В этом месяце вы заработали NaN тысяч рублей." `Ваша цель "${ 'foo'[4] }" наконец-то достигнута.` // "Ваша цель "undefined" наконец-то достигнута." `Осталось ещё ${ foo.length - 1 } целей и вы достигните успеха.` // "Осталось ещё NaN целей и вы достигните успеха." Облегчить его страдания можно разными путями..
Проблемы с динамической типизацией JS возникают по 2 основным причинам:
Сначала разберёмся с первой проблемой. 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 )!.value
if( typeof func !== 'function' ) continue if(!( 'prototype' in func )) continue Обратите внимание, что мы не используем globalThis[name] для получения значения, чтобы не триггерить ненужные варнинги. Теперь оставляем лишь те классы, что непосредственно унаследованы от Object: const proto = func.prototype
if( 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 рантайма, то присоединяйтесь к этим чатам:
Наконец, в твиттере _jinnin можно обнаружить много свежих мыслей на тему фронтенда, JS, UX и прочей дичи. =========== Источник: habr.com =========== Похожие новости:
Ненормальное программирование ), #_javascript |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 00:40
Часовой пояс: UTC + 5