web-dev-qa-db-fra.com

Existe-t-il un moyen de Object.freeze () une date JavaScript?

Selon documentation MDN Object.freeze() :

La méthode Object.freeze() fige un objet: c'est-à-dire empêche d'y ajouter de nouvelles propriétés; empêche la suppression des propriétés existantes; et empêche les propriétés existantes, ou leur énumérabilité, configurabilité ou écriture, d'être modifiées. En substance, l'objet est rendu effectivement immuable. La méthode renvoie l'objet gelé.

Je m'attendais à ce que le gel des appels à une date empêche les modifications de cette date, mais cela ne semble pas fonctionner. Voici ce que je fais (en exécutant Node.js v5.3.0):

let d = new Date()
Object.freeze(d)
d.setTime(0)
console.log(d) // Wed Dec 31 1969 16:00:00 GMT-0800 (PST)

Je m'attendais à ce que l'appel à setTime échoue ou ne fasse rien. Des idées pour figer une date?

75
Andrew Eisenberg

Existe-t-il un moyen de Object.freeze () une date JavaScript?

Je ne pense pas. Vous pouvez obtenir fermer, cependant, voir sous la ligne ci-dessous. Mais voyons d'abord pourquoi Object.freeze Ne fonctionne pas.

Je m'attendais à ce que le gel des appels à une date empêche les modifications de cette date ...

Il serait si Date utilisait une propriété d'objet pour conserver sa valeur de temps interne, mais ce n'est pas le cas. Il utilise un [[DateValue]] slot interne à la place. Les emplacements internes ne sont pas des propriétés:

Les emplacements internes correspondent à l'état interne associé aux objets et utilisé par divers algorithmes de spécification ECMAScript. Les emplacements internes ne sont pas des propriétés d'objet ...

Le gel de l'objet n'a donc aucun effet sur sa capacité à muter son emplacement interne [[DateValue]].


Vous pouvez geler un Date, ou effectivement de toute façon: remplacez toutes ses méthodes de mutation par des fonctions no-op (ou des fonctions qui génèrent une erreur) puis freeze il. Mais comme observé par zzzzBov (Sympa!), cela n'empêche pas quelqu'un de faire Date.prototype.setTime.call(d, 0) ( dans une tentative délibérée de contourner l'objet gelé, ou en tant que sous-produit d'un code compliqué qu'ils utilisent). C'est donc fermer, mais pas de cigare.

Voici un exemple (j'utilise les fonctionnalités ES2015 ici, car j'ai vu que let dans votre code, vous aurez donc besoin d'un navigateur récent pour l'exécuter; mais cela peut être fait avec des fonctionnalités uniquement ES5 également ):

"use strict";

let d = new Date();
freezeDate(d);
d.setTime(0);
snippet.log(d);

function nop() {
}

function freezeDate(d) {
  allNames(d).forEach(name => {
    if (name.startsWith("set") && typeof d[name] === "function") {
      d[name] = nop;
    }
  });
  Object.freeze(d);
  return d;
}
function allNames(obj) {
  var names = Object.create(null); // Or use Map here
  var thisObj;
  
  for (thisObj = obj; thisObj; thisObj = Object.getPrototypeOf(thisObj)) {
    Object.getOwnPropertyNames(thisObj).forEach(name => {
      names[name] = 1;
    });
  }
  return Object.keys(names);
}
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="//tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

I pensez toutes les méthodes de mutation de Date commencent par set, mais sinon, il est facile de modifier ce qui précède.

61
T.J. Crowder

De documents de MDN sur Object.freeze (soulignement le mien):

Les valeurs ne peuvent pas être modifiées pour les propriétés des données. Les propriétés des accesseurs (getters et setters) fonctionnent de la même manière (et donnent toujours l'illusion que vous modifiez la valeur). Notez que les valeurs qui sont des objets peuvent toujours être modifiés, sauf s'ils sont également gelés.

La méthode setTime de l'objet Date ne modifie pas une propriété de l'objet Date, elle continue donc de fonctionner, malgré le blocage de l'instance.

8
zzzzBov

Vous pouvez l'envelopper dans une structure de classe et définir des getters et setters personnalisés afin d'empêcher un changement indésirable

6
Mensur Grišević

C'est une très bonne question!

Réponse de T.J. Crowder a une excellente solution, mais cela m'a fait réfléchir: que pouvons-nous faire d'autre? Comment contourner la Date.prototype.setTime.call(yourFrozenDate)?

1ère tentative: "Wrapper"

Une façon directe consiste à fournir une fonction AndrewDate qui enveloppe une date. Il a tout ce qu'une date a moins les setters:

function AndrewDate(realDate) {
    var proto = Date.prototype;
    var propNames = Object.getOwnPropertyNames(proto)
        .filter(propName => !propName.startsWith('set'));

    return propNames.reduce((ret, propName) => {
        ret[propName] = proto[propName].bind(realDate);
        return ret;
    }, {});
}

var date = AndrewDate(new Date());
date.setMonth(2); // TypeError: d.setMonth is not a function

Cela crée un objet qui possède toutes les propriétés d'un objet date réel et utilise Function.prototype.bind pour définir leur this.

Ce n'est pas une façon infaillible de rassembler autour des clés, mais j'espère que vous pouvez voir mon intention.

Mais attendez ... en regardant un peu plus loin ici et là, nous pouvons voir qu'il existe une meilleure façon de procéder.

2e tentative: procurations

function SuperAndrewDate(realDate) {
    return new Proxy(realDate, {
        get(target, prop) {
            if (!prop.startsWith('set')) {
                return Reflect.get(target, prop);
            }
        }
    });
}

var proxyDate = SuperAndrewDate(new Date());

Et nous l'avons résolu!

...sorte de. Voir, Firefox est le seul en ce moment à implémenter des proxys, et pour des raisons bizarres, les objets de date ne peuvent pas être mandatés. De plus, vous remarquerez que vous pouvez toujours faire des choses comme 'setDate' in proxyDate et vous verrez les compléments dans la console. Pour surmonter ce problème, davantage de pièges doivent être fournis; spécifiquement, has , enumerate , ownKeys , getOwnPropertyDescriptor et qui sait quels sont les cas Edge étranges!

... Donc, à la réflexion, cette réponse est presque inutile. Mais au moins, nous nous sommes amusés, non?

6
Zirak

La réponse acceptée est en fait imparfaite, je le crains. Vous pouvez réellement geler une instance de n'importe quel objet, y compris une instance de Date. À l'appui de @ zzzzBov réponse, le gel d'une instance d'objet n'implique pas que l'état de l'objet devienne constant.

Une façon de prouver qu'une instance Date est vraiment figée est en suivant les étapes ci-dessous:

var date = new Date();
date.x = 4;
console.log(date.x); // 4
Object.freeze(date);
date.x = 20; // this assignment fails silently, freezing has made property x to be non-writable
date.y = 5; // this also fails silently, freezing ensures you can't add new properties to an object
console.log(date.x); // 4, unchanged
console.log(date.y); // undefined

Mais vous pouvez obtenir le comportement que je suppose que vous désirez comme suit:

var date = (function() {
    var actualDate = new Date();

    return Object.defineProperty({}, "value", {
        get: function() {
            return new Date(actualDate.getTime())
        },
        enumerable: true
    });
})();

console.log(date.value); // Fri Jan 29 2016 00:01:20 GMT+0100 (CET)
date.value.setTime(0);
console.log(date.value); // Fri Jan 29 2016 00:01:20 GMT+0100 (CET)
date.value = null;       // fails silently
console.log(date.value); // Fri Jan 29 2016 00:01:20 GMT+0100 (CET)
2
Igwe Kalu