有没有一种方法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
并在控制台中看到完成。 为了克服需要提供更多的陷阱; 具体来说, has
, enumerate
, ownKeys
, getOwnPropertyDescriptor
和谁知道有什么奇怪的边缘情况!
……所以,第二个想法,这个答案几乎是毫无意义的。 但至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)