Как настроить полифилл globalThis в универсальном JavaScript

Как настроить полифилл globalThis в универсальном JavaScript

Предложенное свойство globalThis предполагает введение единого механизма доступа к глобальному значению this в любой среде JavaScript. Это похоже на обычный полифилл, но всё же немного отличается, и понять, что это на самом деле, довольно сложно.

В статье описаны трудности реализации правильного полифилла globalThis. Для него существуют следующие требования:

  • должен работать в любой среде JavaScript, включая браузеры, воркеры и расширения браузеров. А также в Node.js, Deno и standalone бинарных файлах на движке JavaScript;
  • должен поддерживать грязный и строгий (strict) режимы работы, а также модули JavaScript;
  • должен работать независимо от контекста, в котором запущен код (т. е. полифилл должен выдавать правильный результат, даже если его на этапе сборки обернут упаковщиком в строгий режим работы).

Обратите внимание, что в модулях JavaScript есть область-посредник между глобальной областью видимости и вашим кодом. Область видимости модуля скрывает значение this глобальной области видимости. Поэтому ключевое слово this, которое видно на верхнем уровне в модулях, на самом деле имеет свойство undefined.

TL;DR globalThis != глобальный объект, но globalThis == this из глобальной области видимости.

Альтернативы globalThis

Так сложилось, что для доступа к глобальному объекту в разных средах JavaScript требуется разный синтаксис. Например, в веб-среде можно использовать windowselfили frames, при этом для веб-воркеров (и сервис-воркеров) работать будет только self.

Node.js не работает со всем вышеперечисленным, в его случае нужно использовать global.

Ключевое слово this может использоваться в функциях, запущенных в грязном режиме, но в модулях и функциях, запущенных в строгом режиме, для него будет возвращаться значение undefined.

Проблема выше решается использованием Function(‘return this’)(), но в средах с отключенной функцией eval() вроде CSP нельзя использовать Function подобным образом.

Примечание setTimeout(‘globalThis = this’, 0) не следует использовать по тем же причинам, что и eval() и Function. Кроме того, функция setTimeout не является частью ECMAScript, а следовательно, не будет доступна во всех средах выполнения JavaScript. Вдобавок к этому, setTimeout асинхронна, и даже если бы она везде поддерживалась, использовать её в полифилле, от которого зависит другой код, было бы неразумно.

Примитивный полифилл

Похоже, вышеперечисленные методы можно было бы объединить в один полифилл, как например этот:

Но, к сожалению, такой полифилл не будет работать в функциях во время исполнения кода в строгом режиме, в модулях JavaScript и в небраузерных средах (кроме поддерживающих GlobalThis).

Надёжный полифилл

А можно ли вообще написать надёжный полифилл globalThis? Возьмём в качестве примера среду, в которой:

  1. Нельзя полагаться на значение globalThiswindowselfglobal или this.
  2. Нельзя использовать конструктор Function или eval().
  3. Можно полагаться на целостность остальной встроенной функциональности JavaScript.

В таком случае есть решение, но оно не идеально.

Если установить значение функции на globalThis и вызвать его как метод, то можно получить доступ к this используя следующую функцию:

Как можно сделать что-то подобное, не полагаясь на globalThis или на специфичную связанную сущность, которая на него ссылается? Нельзя же просто сделать следующее:

Функция foo() больше не является методом, а поэтому в строгом режиме или в модулях JavaScript у ключевого слова this будет значение undefined. Однако, это не относится к геттерам и сеттерам.

Скрипт выше устанавливает геттер на полифилл globalThis, получает к нему доступ, чтобы в итоге ссылаться на globalThis, затем очищает полифилл, удаляя геттер. Таким образом у нас появляется доступ к globalThis при любых обстоятельствах, но этот метод опирается на глобальный this в первой строке (где написано globalThis). Можно ли как-то избавиться от этой зависимости? Как можно установить глобально доступный геттер без прямого доступа к globalThis?MSK VUE.JS MEETUP #118 июля в 19:00, Москва, беcплатноtproger.ru События и курсы на tproger.ru

Вместо установки геттера на globalThis, нужно установить его на то, что глобально наследует объект — Object.prototype.

Примечание В спецификации ECMAScript не указано, что глобальное this наследует именно Object.prototype — только указано, что это строго должен быть объект. Функция Object.create(null) создаёт объект, который не наследуется от Object.prototype. Движок JavaScript мог бы использовать такой объект как глобальное this, не нарушая требования спецификации, но в этом случае фрагмент кода выше всё равно не сработал бы. Однако, в современных движках разработчики, похоже, согласны с тем, что глобальное this должно включать Object.prototype в своей цепочке прототипов.

Во избежание проблем с Object.prototype в современных средах JavaScript, где полифилл globalThis уже доступен, изменим его следующим образом

Или можно использовать __defineGetter__ 

Как вам такое? Перед вами самый ужасающий полифилл из когда-либо существовавших. Такой подход полностью противоречит общепринятой практике, согласно которой нельзя изменять объекты, которыми вы не владеете.

Не стоит играться с встроенными прототипами вообще — это объясняется в JavaScript Engine Fundamentals: optimizing prototypes.

С другой стороны, единственный способ сломать этот полифилл — изменить objectObject.defineProperty (или Object.prototype._ _defineGetter) перед его запуском.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *