简体   繁体   English

有没有办法 Object.freeze() 一个 JavaScript 日期?

[英]Is there a way to Object.freeze() a JavaScript Date?

According to MDN Object.freeze() documentation :根据MDN Object.freeze()文档

The Object.freeze() method freezes an object: that is, prevents new properties from being added to it; Object.freeze()方法冻结一个对象:即阻止向其添加新属性; prevents existing properties from being removed;防止现有属性被删除; and prevents existing properties, or their enumerability, configurability, or writability, from being changed.并防止更改现有属性或其可枚举性、可配置性或可写性。 In essence the object is made effectively immutable.本质上,对象实际上是不可变的。 The method returns the object being frozen.该方法返回被冻结的对象。

I was expecting that calling freeze on a date would prevent changes to that date, but it does not appear to be working.我原以为在某个日期调用 freeze 会阻止对该日期的更改,但它似乎不起作用。 Here's what I am doing (running Node.js v5.3.0):这就是我正在做的事情(运行 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)

I would have expected the call to setTime to either fail or do nothing.我本来预计对setTime的调用要么失败,要么什么都不做。 Any ideas how to freeze a date?任何想法如何冻结日期?

Is there a way to Object.freeze() a JavaScript Date?有没有办法 Object.freeze() 一个 JavaScript 日期?

I don't think so.我不这么认为。 You can get close , though, see under the line below.但是,您可以接近,请参阅下面的行。 But first let's see why just Object.freeze doesn't work.但首先让我们看看为什么Object.freeze不起作用。

I was expecting that calling freeze on a date would prevent changes to that date...我期待在某个日期调用 freeze 会阻止对该日期的更改...

It would if Date used an object property to hold its internal time value, but it doesn't.如果Date使用对象属性来保存其内部时间值,它会,但它没有。 It uses a [[DateValue]] internal slot instead.它使用[[DateValue]]内部槽代替。 Internal slots aren't properties: 内部插槽不是属性:

Internal slots correspond to internal state that is associated with objects and used by various ECMAScript specification algorithms.内部槽对应于与对象关联并由各种 ECMAScript 规范算法使用的内部状态。 Internal slots are not object properties...内部插槽不是对象属性...

So freezing the object doesn't have any effect on its ability to mutate its [[DateValue]] internal slot.因此,冻结对象不会对其改变其[[DateValue]]内部槽的能力产生任何影响。


You can freeze a Date , or effectively so anyway: Replace all its mutator methods with no-op functions (or functions that throw an error) and then freeze it.可以冻结Date ,或者无论如何都可以有效地冻结:用无操作函数(或引发错误的函数)替换它的所有 mutator 方法,然后freeze它。 But as observed by zzzzBov (nice one!) , that doesn't prevent someone from doing Date.prototype.setTime.call(d, 0) (in a deliberate attempt to get around the frozen object, or as a byproduct of some complicated code they're using).但正如zzzzBov (不错!)观察到的那样,这并不能阻止某人做Date.prototype.setTime.call(d, 0) (故意试图绕过冻结的物体,或者作为一些复杂的副产品他们正在使用的代码)。 So it's close , but no cigar.所以它很接近,但没有雪茄。

Here's an example (I'm using ES2015 features here, since I saw that let in your code, so you'll need a recent browser to run it; but this can be done with ES5-only features as well):这是一个示例(我在这里使用 ES2015 功能,因为我看到let的代码中包含了它,所以你需要一个最新的浏览器来运行它;但这也可以使用 ES5 独有的功能来完成):

 "use strict"; let d = new Date(); freezeDate(d); d.setTime(0); console.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) { const names = Object.create(null); // Or use Map here for (let 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 think all the mutator methods of Date start with set , but if not it's easy to tweak the above.认为Date的所有 mutator 方法都以set开头,但如果不是,则很容易调整上述内容。

From MDN's docs on Object.freeze (emphasis mine):来自MDN 关于Object.freeze的文档(强调我的):

Values cannot be changed for data properties.无法更改数据属性的值。 Accessor properties (getters and setters) work the same (and still give the illusion that you are changing the value).访问器属性(getter 和 setter)的工作方式相同(并且仍然给人一种您正在更改值的错觉)。 Note that values that are objects can still be modified, unless they are also frozen.请注意,作为对象的值仍然可以修改,除非它们也被冻结。

The Date object's setTime method isn't changing a property of the Date object, so it continues to work, despite having frozen the instance. Date 对象的setTime方法不会更改 Date 对象的属性,因此它会继续工作,尽管已冻结实例。

This is a really good question!这是一个非常好的问题!

TJ Crowder's answer has an excellent solution, but it got me thinking: What else can we do? TJ Crowder 的回答有一个很好的解决方案,但它让我思考:我们还能做什么? How can we go around the Date.prototype.setTime.call(yourFrozenDate) ?我们如何绕过Date.prototype.setTime.call(yourFrozenDate)

1st attempt: "Wrapper"第一次尝试:“包装”

One direct way is to provide an AndrewDate function which wraps a date.一种直接的方法是提供一个包装日期的AndrewDate函数。 It has everything a date has minus the 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

What this does is create an object which has all the properties that an actual date object has and uses Function.prototype.bind to set their this . this 的作用是创建一个对象,该对象具有实际日期对象所具有的所有属性,并使用Function.prototype.bind设置它们的this

This isn't a fool proof way of gathering around the keys, but hopefully you can see my intention.这不是在钥匙周围聚集的万无一失的方式,但希望你能明白我的意图。

But wait...looking at it a little further here and there, we can see that there's a better way of doing this.但是等等......在这里和那里再看一点,我们可以看到有更好的方法来做到这一点。

2nd attempt: Proxies第二次尝试: 代理

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

var proxyDate = SuperAndrewDate(new Date());

And we solved it!我们解决了它!

...sort of. ...有点。 See, Firefox is the only one right now which implements proxies, and for some bizarre reasons date objects can't be proxied.看,Firefox 是目前唯一实现代理的,并且由于某些奇怪的原因不能代理日期对象。 Furthermore, you'll notice that you can still do things like 'setDate' in proxyDate and you'll see completions in console.此外,您会注意到您仍然可以'setDate' in proxyDate类的操作,并且您会在控制台中看到完成。 To overcome that more traps need to be provided;为了克服需要提供更多的陷阱; specifically, has , enumerate , ownKeys , getOwnPropertyDescriptor and who knows what weird edge cases there are!具体来说, hasenumerateownKeysgetOwnPropertyDescriptor ,谁知道有什么奇怪的边缘情况!

...So on second thought, this answer is nearly pointless. ......所以再想一想,这个答案几乎毫无意义。 But at least we had fun, right?但至少我们玩得很开心,对吧?

您可以将其包装在类似结构的类中并定义自定义 getter 和 setter 以防止发生不希望的更改

The accepted answer is actually flawed, I'm afraid.恐怕,公认的答案实际上是有缺陷的。 You actually can freeze an instance of any object including an instance of Date .您实际上可以冻结任何对象的实例,包括Date的实例。 In support of @zzzzBov 's answer, freezing an object instance does not imply the object's state becomes constant.为了支持@zzzzBov的回答,冻结对象实例并不意味着对象的状态变为常量。

One way to prove that a Date instance is truly frozen is by following the steps below:证明Date实例真正被冻结的一种方法是按照以下步骤操作:

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

But you can achieve the behaviour I suppose you desire as follows:但是你可以实现我想你想要的行为如下:

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)

Using a Proxy object is likely to be the best solution nowadays.使用 Proxy 对象可能是当今最好的解决方案。 Based on @Zirak's answer back in 2016 , I've revised and improved the Proxy handler for maximum compatibility:基于@Zirak 早在 2016 年的回答,我已经修改和改进了代理处理程序以获得最大的兼容性:

const noop = () => {}
const dateProxyHandler = {
  get(target, prop, receiver) {
    if (prop === Symbol.toStringTag) return "Date"
    if (typeof prop === "string" && prop.startsWith("set")) return noop

    const value = Reflect.get(target, prop, receiver)
    return typeof value === "function" && prop !== "constructor"
      ? value.bind(target)
      : value
  },
}

function freeze(value) {
  return value instanceof Date
    ? new Proxy(Object.freeze(new Date(Number(value))), dateProxyHandler)
    : Object.freeze(value)
}

const frozenDate = freeze(new Date())

frozenDate.setHours(0) // noop
frozenDate.getHours() // works :)

JSON.stringify(frozenDate) // works :)

const copiedDate = new Date(Number(frozenDate)) // works :)

Object.prototype.toString.call(frozenDate) // "[object Date]"

Source: https://gist.github.com/sirlancelot/5f1922ef01e8006ea9dda6504fc06b8e来源: https ://gist.github.com/sirlancelot/5f1922ef01e8006ea9dda6504fc06b8e

Making the date an integear is working for me:使日期成为整数对我有用:

let date = new Date();
const integerDate = Date.parse(date);
let unchangedDate = new Date(integerDate);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM