有没有一种方法Object.freeze()的JavaScriptdate?

根据MDN Object.freeze()文档 :

Object.freeze()方法冻结一个对象:也就是说,阻止新的属性被添加到它; 防止现有的属性被删除; 并防止现有属性或其可枚举性,可configuration性或可写性被更改。 实质上,该对象是有效的不可变的。 该方法返回被冻结的对象。

我期待在一个date冻结呼叫将阻止该date的变化,但它似乎并没有工作。 这就是我在做什么(运行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) 

我会期待的调用setTime要么失败,要么什么都不做。 任何想法如何冻结date?

有没有一种方法Object.freeze()的JavaScriptdate?

我不这么认为。 不过,您可以closures ,看下面的行。 但首先让我们看看为什么只是Object.freeze不起作用。

我期待在一个date冻结呼叫将阻止更改到该date…

如果 Date使用一个对象属性来保存它的内部时间值,但它不会。 它使用一个[[DateValue]] 内部插槽 。 内部插槽不是属性:

内部插槽对应于与对象关联并由各种ECMAScript规范algorithm使用的内部状态。 内部插槽不是对象属性…

因此,冻结对象不会影响其对[[DateValue]]内部插槽进行变异的能力。


可以冻结一个Date ,或者有效的如此:将所有的mutator方法replace为no-op函数(或者引发错误的函数),然后freeze它。 但是正如zzzzBov (nice one!)所观察到的那样 ,这并不妨碍某人执行Date.prototype.setTime.call(d, 0) (故意尝试绕过冻结的对象,或者作为一些复杂的副产品他们正在使用的代码)。 所以它很近 ,但没有雪茄。

下面是一个例子(我在这里使用ES2015的function,因为我看到了let你的代码,所以你需要一个最近的浏览器来运行它,但是这也可以用ES5的function来完成):

 "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> 

从MDN的Object.freeze文档 (重点是我的):

数据属性的值不能更改。 访问器属性(getter和setter)的工作原理是相同的(并且仍然给出你正在改变值的错觉)。 请注意,对象的值仍然可以修改,除非它们也被冻结。

Date对象的setTime方法不会改变Date对象的属性,所以它仍然可以继续工作,尽pipe冻结了实例。

你可以把它包装在一个类似结构的类中,并定义自定义的getter和setter以防止不希望的变化

这是一个非常好的问题!

TJ Crowder的回答有一个很好的解决scheme,但是让我思考:我们还能做什么? 我们如何绕过Date.prototype.setTime.call(yourFrozenDate)

第一次尝试:“包装”

一种直接的方法是提供包装date的AndrewDate函数。 它有一个约会减去安装者的一切:

 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 

它所做的是创build一个具有实际date对象所有属性的对象,并使用Function.prototype.bind来设置它们。

这不是靠近钥匙聚集的傻瓜,但希望你能看到我的意图。

但是等等…在这里和那里看得更远,我们可以看到有一个更好的方法来做到这一点。

第二次尝试: 代理

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

我们解决了它!

…有点。 看看,Firefox是唯一一个实现代理的人,而且由于一些奇怪的原因,date对象不能被代理。 此外,您会注意到,您仍然可以'setDate' in proxyDate执行诸如'setDate' in proxyDate并在控制台中看到完成。 为了克服需要提供更多的陷阱; 具体来说, hasenumerateownKeysgetOwnPropertyDescriptor和谁知道有什么奇怪的边缘情况!

……所以,第二个想法,这个答案几乎是毫无意义的。 但至less我们玩得很开心,对吧?

我接受的答案实际上是有缺陷的。 您实际上可以冻结任何对象的实例,包括Date一个实例。 在支持@zzzzBov的回答中,冻结对象实例并不意味着对象的状态变成常量。

certificateDate实例真正被冻结的一种方法是遵循以下步骤:

 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 

但是你可以达到我想你想要的行为如下:

 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)