Как избежать случайного неявного обращения к свойствам глобального объекта? - PullRequest
0 голосов
/ 20 февраля 2019

Возможно ли выполнить блок кода без неявного контекста with(global), который, по-видимому, по умолчанию имеется во всех сценариях?Например, в браузере можно было бы настроить скрипт так, чтобы строка, такая как

const foo = location;

throws

Uncaught ReferenceError: местоположение не определено

вместо доступа к window.location, когда location не был объявлен первым?Не имея этого, есть ли способ, что такая неявная ссылка может привести к некоторому предупреждению?Это может быть источником ошибок при написании кода (см. Ниже), поэтому может быть полезен способ защиты от него.

(Конечно, из-за обычных правил видимости, можно объявить другую переменную сто же имя, используя const или let, или внутри внутреннего блока, чтобы гарантировать, что использование этого имени переменной ссылается на новую переменную, а не на глобальное свойство, но это не одно и то же.)

Этоможет быть похоже на вопрос о том, можно ли прекратить ссылаться на свойство из оператора фактического with:

const obj = { prop: 'prop' };
with (obj) {
  // how to make referencing "prop" from somewhere within this block throw a ReferenceError
}

Известно, что with не следует использовать в первомместо, но, к сожалению, кажется, у нас нет выбора, когда дело доходит до with(global), который иногда экономит несколько символов за счет сбивающих с толку ошибок, которые появляются довольно часто: 1 2 3 4 5 6 .Например:

var status = false;
if (status) {
  console.log('status is actually truthy!');
}

(проблема здесь: window.status является зарезервированным свойством - при присвоении оно приводит приведенное выражение к строке)

Эти видыошибки - это та же самая причина, по которой явное использование with не рекомендуется или запрещено, однако неявное with(global) продолжает вызывать проблемы, даже в строгом режиме, поэтому было бы полезно найти способ обойти это.

Ответы [ 5 ]

0 голосов
/ 09 марта 2019

Несколько проще реализовать, чем ответ @ CertainPerformance, вы можете использовать Proxy, чтобы перехватить неявный доступ ко всему, кроме window.Единственное предупреждение: вы не можете запустить это в строгом режиме:

const strictWindow = Object.create(
  new Proxy(window, {
    get (target, property) {
      if (typeof property !== 'string') return undefined
      console.log(`implicit access to ${property}`)
      throw new ReferenceError(`${property} is not defined`)
    }
  }),
  Object.getOwnPropertyDescriptors({ window })
)

with (strictWindow) {
  try {
    const foo = location
  } catch (error) {
    window.console.log(error.toString())
  }

  // doesn't throw error
  const foo = window.location
}

Обратите внимание, что даже console должна иметь явную ссылку, чтобы не бросать.Если вы хотите добавить это в качестве другого исключения, просто измените strictWindow с другим собственным свойством, используя

Object.getOwnPropertyDescriptors({ window, console })

На самом деле, существует много стандартных встроенных объектов , вы можетехочу добавить исключения для, но это выходит за рамки этого ответа (без каламбура).

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

0 голосов
/ 09 марта 2019

Есть несколько вещей, которые необходимо учитывать, прежде чем пытаться ответить на этот вопрос.

Например, возьмите конструктор Object.Это «Стандартный встроенный объект» .

window.status является частью Window интерфейса .

Очевидно, вы не хотите, чтобы status ссылался на window.status, но хотите ли вы, чтобы Object ссылался на window.Object?


Решение вашей проблемы с нимневозможность переопределения означает использование IIFE или модуля, который должен быть тем, чем вы в любом случае занимаетесь.

(() => {
  var status = false;
  if (!status) {
    console.log('status is now false.');
  }
})();

И чтобы предотвратить случайное использование глобальных переменных, я бы просто настроил ваш линтер напредупредить против этого.Принудительное использование такого решения, как with (fake_global), не только будет иметь ошибки исключительно во время выполнения, которые могут быть не обнаружены, но и будет медленнее.


В частности, с ESLint я не могу найти«хорошее» решение.Включение глобальных переменных браузера позволяет выполнять неявные операции чтения.

Я бы предложил no-implicit-globals (Так как вам все равно не следует загрязнять глобальную область действия, и это препятствует тому, чтобы var status ничего не определялпроблема), а также не включив все глобальные переменные браузера, скажем, только window, document, console, setInterval и т. д., как вы сказали в комментариях.

Посмотрите на Среды ESLint , чтобы увидеть, какие из них вы хотите включить.По умолчанию такие вещи, как Object и Array находятся в глобальной области видимости, но такие вещи, как перечисленные выше и atob, отсутствуют.

Чтобы увидеть точный список глобальных переменных, они определяются этот файл в ESLint и globals NPM пакет .Я бы выбрал из (комбинации) "es6", "worker" или "shared-node-browser".

Файл eslintrc будет иметь:

{
    "rules": {
        "no-implicit-globals": "error"
    },
    "globals": {
        "window": "readonly",
        "document": "readonly"
    },
    "env": {
        "browser": false,
        "es6": [true/false],
        "worker": [true/false],
        "shared-node-browser": [true/false]
    }
}
0 голосов
/ 06 марта 2019

Возможно, немного очиститель (YMMV) предназначен для установки ловушек геттеров (как вы это сделали), но на рабочем месте, чтобы вы не загрязняли свою основную глобальную область.Мне не нужно было использовать with, хотя, возможно, это и есть улучшение.

Рабочий "поток"

//worker; foo.js
addEventListener('message', function ({ data }) {
  try {
    eval(`
      for (k in self) {
        Object.defineProperty(self, k, {
          get: function () {
            throw new ReferenceError(':(');
          }
        });
      }
      // code to execute
      ${data}
    `);
    postMessage('no error thrown ');
  } catch (e) {
    postMessage(`error thrown: ${e.message}`);
  }
});

Основной "поток"

var w = new Worker('./foo.js');
w.addEventListener('message', ({data}) => console.log(`response: ${data}`));
w.postMessage('const foo = location');

enter image description here


Другой вариант, который стоит изучить, это Кукольник .

0 голосов
/ 09 марта 2019

Просто используйте "use strict".Подробнее о Строгий режим .

enter image description here

0 голосов
/ 20 февраля 2019

Если вы не в строгом режиме, одна возможность состоит в том, чтобы перебрать имена свойств глобального (или with ed) объекта и создать другой объект из тех свойств, чьи установщики и получатели все выбрасывают ReferenceErrorsи затем вложите свой код в другой with поверх этого объекта.См. Комментарии в коде ниже.

Это не хорошее решение, но это единственное, что я могу придумать:

const makeObjWhosePropsThrow = inputObj => Object.getOwnPropertyNames(inputObj)
  .reduce((a, propName) => {
    const doThrow = () => { throw new ReferenceError(propName + ' is not defined!'); };
    Object.defineProperty(a, propName, { get: doThrow, set: doThrow });
    return a;
  }, {});

// (using setTimeout so that console shows both this and the next error)
setTimeout(() => {
  const windowWhichThrows = makeObjWhosePropsThrow(window);
  with (windowWhichThrows) {
    /* Use an IIFE
     * so that variables with the same name declared with "var" inside
     * create a locally scoped variable
     * rather than try to reference the property, which would throw
     */
    (() => { 
      // Declaring any variable name will not throw:
      var alert = true;  // window.alert
      const open = true; // window.open
      
      // Referencing a property name without declaring it first will throw:
      const foo = location;
    })();
  }
});

const obj = { prop1: 'prop1' };
with (obj) {
  const inner = makeObjWhosePropsThrow(obj);
  with (inner) {
    // Referencing a property name without declaring it first will throw:
    console.log(prop1);
  }
}
.as-console-wrapper {
  max-height: 100% !important;
}

Предостережения:

  • Это явно использует with, что запрещено в строгом режиме
  • Это не't точно экранирует неявную with(global) область или with(obj) область: переменные во внешней области с тем же именем, что и у свойства, не будут ссылаться.
  • windowимеет свойство window, которое относится к window.window.window === window.Таким образом, ссылка window внутри with будет выброшена.Либо явным образом исключите свойство window, либо сначала сохраните другую ссылку на window.
...