13 votes

Comment éviter de se référer accidentellement et implicitement à des propriétés de l'objet global ?

Est-il possible d'exécuter un bloc de code sans l'implicite with(global) contexte que tous les scripts semblent avoir par défaut ? Par exemple, dans un navigateur, y aurait-il un moyen de configurer un scripts de sorte qu'une ligne telle que

const foo = location;

jette

Uncaught ReferenceError : l'emplacement n'est pas défini

au lieu d'accéder à window.location quand location n'a pas été déclaré en premier ? Si ce n'est pas le cas, existe-t-il un moyen pour qu'une telle référence implicite puisse entraîner un avertissement quelconque ? Cela peut être une source de bogues lors de l'écriture du code (voir ci-dessous), donc avoir un moyen de s'en prémunir pourrait être utile.

(Bien sûr, en raison des règles de scoping ordinaires, il est possible de déclarer une autre variable avec le même nom en utilisant la commande const ou let ou dans un bloc interne, pour s'assurer que l'utilisation de ce nom de variable fait référence à la nouvelle variable plutôt qu'à la propriété globale, mais ce n'est pas la même chose).

Cela revient à demander s'il est possible d'arrêter de référencer une propriété à l'intérieur d'un fichier de type réel with déclaration :

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

Il est connu que with ne devrait pas être utilisé en premier lieu, mais malheureusement il semble que nous n'ayons pas le choix lorsqu'il s'agit de la with(global) qui permet parfois de sauver quelques personnages au prix de bugs déroutants qui apparaissent assez fréquemment : 1 2 3 4 5 6 . Par exemple :

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

(le problème ici : window.status est une propriété réservée - lorsqu'elle est assignée, elle transforme l'expression assignée en une chaîne de caractères)

Ce genre de bogues est la même raison pour laquelle l'utilisation explicite de l'option with est découragé ou interdit, pourtant l'implicite with(global) continue à poser des problèmes, même en mode strict, et il serait donc utile de trouver un moyen de le contourner.

7voto

Artyer Points 3473

Il y a certaines choses que vous devez prendre en compte avant d'essayer de répondre à cette question.

Par exemple, prenez le Object constructeur. Il s'agit d'un "Objet standard intégré" .

window.status fait partie de la Window interface .

Évidemment, vous ne voulez pas status de se référer à window.status mais voulez-vous Object de se référer à window.Object ?


La solution à votre problème de ne pas pouvoir être redéfini est d'utiliser un IIFE, ou un module, ce qui devrait être ce que vous faites de toute façon.

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

Et pour éviter d'utiliser accidentellement des variables globales, je configurerais simplement votre linter pour qu'il vous avertisse contre cela. Le forcer en utilisant une solution comme with (fake_global) aurait non seulement des erreurs exclusivement au moment de l'exécution, qui pourraient ne pas être attrapées, mais serait également plus lente.


En particulier avec ESLint, je n'arrive pas à trouver une "bonne" solution. L'activation des globaux du navigateur permet des lectures implicites.

Je suggère no-implicit-globals (Comme vous ne devriez pas polluer la portée globale de toute façon, et que cela empêche l'utilisation de la fonction var status ne pas définir de problème), et aussi ne pas activer tous les globaux du navigateur, seulement, disons, window , document , console , setInterval etc., comme vous l'avez dit dans les commentaires.

Regardez le Environnements ESLint pour voir ceux que vous souhaitez activer. Par défaut, des éléments comme Object y Array sont dans la portée globale, mais des choses comme celles listées ci-dessus et les atob ne le sont pas.

Pour voir la liste exacte des globaux, ils sont définis par ce fichier dans ESLint y le site globals Paquet NPM . Je choisirais parmi (une combinaison de) "es6", "worker" ou "shared-node-browser".

Le fichier eslintrc aurait :

{
    "rules": {
        "no-implicit-globals": "error"
    },
    "globals": {
        "window": "readonly",
        "document": "readonly"
    },
    "env": {
        "browser": false,
        "es6": [true/false],
        "worker": [true/false],
        "shared-node-browser": [true/false]
    }
}

3voto

CertainPerformance Points 110949

Si vous n'êtes pas en mode strict, une possibilité est d'itérer sur les noms des propriétés de l'objet global (ou de l'objet de l'utilisateur). with ed), et créer un autre objet à partir de ces propriétés, dont les setters et getters lancent tous les ReferenceErrors puis immergez votre code dans un autre with sur cet objet. Voir les commentaires dans le code ci-dessous.

Ce n'est pas une joli solution, mais c'est la seule à laquelle je pense :

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;
}

Mises en garde :

  • Cela utilise explicitement with ce qui est interdit en mode strict
  • Ce n'est pas exactement échapper à l'implicite with(global) ou le with(obj) scope : les variables dans la portée externe avec le même nom qu'une propriété ne seront pas référençables.
  • window a une propriété window qui fait référence à window . window.window === window . Donc, le référencement window à l'intérieur de la with va lancer. Soit vous excluez explicitement le window ou enregistrer une autre référence à window d'abord.

1voto

Patrick Roberts Points 405

Un peu plus simple à mettre en oeuvre que la réponse de @CertainPerformance, vous pouvez utiliser un Proxy pour avoir un accès implicite à tout sauf window . Le seul problème est que vous ne pouvez pas l'exécuter en mode strict :

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
}

Remarquez que même console doit avoir une référence explicite afin de ne pas être rejeté. Si vous voulez ajouter une autre exception, modifiez simplement strictWindow avec un autre bien propre en utilisant

Object.getOwnPropertyDescriptors({ window, console })

En fait, il y a un grand nombre d'objets intégrés standard pour lesquelles vous pourriez vouloir ajouter des exceptions, mais cela dépasse le cadre de cette réponse (sans mauvais jeu de mots).

À mon avis, les avantages que cela offre ne sont pas à la hauteur de ceux d'un fonctionnement en mode strict. Une bien meilleure solution consiste à utiliser un linter correctement configuré qui capture les références implicites pendant le développement plutôt qu'au moment de l'exécution en mode non strict.

0voto

customcommander Points 3735

Peut-être légèrement plus propre (YMMV) est de mettre des pièges getter (comme vous l'avez fait), mais dans un worker afin de ne pas polluer votre scope global principal. Je n'ai pas eu besoin d'utiliser with cependant, donc peut-être que c'est une amélioration.

Travailleur "Thread

//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}`);
  }
});

Le "fil" principal

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

enter image description here


Une autre option qui peut valoir la peine d'être explorée est Marionnettiste .

-3voto

Il suffit d'utiliser "use strict" . Plus sur Mode strict .

enter image description here

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X