有没有办法 Object.freeze() 一个 JavaScript 日期?
Posted
技术标签:
【中文标题】有没有办法 Object.freeze() 一个 JavaScript 日期?【英文标题】:Is there a way to Object.freeze() a JavaScript Date? 【发布时间】:2016-04-26 17:28:14 【问题描述】:根据MDN Object.freeze()
documentation:
Object.freeze()
方法冻结一个对象:即阻止向其添加新属性;防止现有属性被删除;并防止更改现有属性或其可枚举性、可配置性或可写性。本质上,对象实际上是不可变的。该方法返回被冻结的对象。
我原以为在某个日期调用 freeze 会阻止对该日期的更改,但它似乎不起作用。这是我正在做的事情(运行 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
的调用要么失败,要么什么都不做。任何想法如何冻结日期?
【问题讨论】:
我们正在从文件加载 JSON 编码的配置,并希望确保应用程序的其他部分不会意外更改此配置。因此,我们在整个对象上递归调用Object.freeze()
。除了这个棘手的日期问题之外,它似乎有效。
将日期存储为字符串或时间戳,并在使用前将其转换为日期对象,或者您可以为该字符串对象创建一个将返回日期的getter。这样你根本不会使用日期对象,所以这不需要冻结日期
考虑所有不可变的函数,因为它们返回一个新对象。不确定这将如何与 freeze() 交互。
这是我享受默认不变性的原因。
@BenC.R.Leggiero 同意。可变日期是 Java 和 javascript 的基本缺陷。它会导致无穷无尽的问题。
【参考方案1】:
有没有办法 Object.freeze() 一个 JavaScript 日期?
我不这么认为。不过,您可以得到 close,请参阅下面的行。但首先让我们看看为什么 Object.freeze
不起作用。
我原以为在某个日期调用 freeze 会阻止对该日期的更改...
它会如果 Date
使用对象属性来保存其内部时间值,但它没有。它改用[[DateValue]]
internal slot。 Internal slots 不是属性:
内部槽对应于与对象关联并被各种 ECMAScript 规范算法使用的内部状态。内部槽不是对象属性...
因此冻结对象不会对其改变其[[DateValue]]
内部槽的能力产生任何影响。
您可以冻结Date
,或者无论如何都有效:用无操作函数(或抛出错误的函数)替换它的所有mutator方法,然后freeze
它。但正如observed by zzzzBov (好一个!),这并不能阻止某人做Date.prototype.setTime.call(d, 0)
(故意试图绕过冻结的物体,或者作为他们正在使用一些复杂的代码)。所以它是接近,但没有雪茄。
这是一个示例(我在此处使用 ES2015 功能,因为我在您的代码中看到了 let
,因此您需要最新的浏览器才能运行它;但这也可以通过仅 ES5 的功能来完成):
"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>
我认为Date
的所有修改器方法都以set
开头,但如果不是,则很容易调整上述内容。
【讨论】:
我希望我能投票两次。这很有趣。 哈!You can freeze a Date, or effectively so anyway: Replace all its mutator methods with no-ops and then freeze it.
好主意!
"你可以冻结Date
" - ...ish,请记住,可以通过从Date.prototype
比如Date.prototype.setTime.call(frozenDate, 0)
,当然在实践中这很荒谬,但本地测试表明它至少在 Chrome 中工作。
@zzzzBov:可笑的事情?也许吧,但是一个非常非常有用的警告。
使用valueOf
/ 时间戳并将该属性呈现为不可变可能会更简单 - 并在需要时将其放入新的Date
对象中?【参考方案2】:
来自MDN's docs on Object.freeze
(强调我的):
无法更改数据属性的值。 访问器属性(getter 和 setter)的工作方式相同(并且仍然会产生您正在更改值的错觉)。请注意,作为对象的值仍然可以修改,除非它们也被冻结。 p>
Date 对象的 setTime
方法不会更改 Date 对象的属性,因此尽管已冻结实例,它仍会继续工作。
【讨论】:
【参考方案3】:这是一个非常好的问题!
T.J. Crowder's answer 有一个很好的解决方案,但它让我思考:我们还能做什么?我们如何绕过Date.prototype.setTime.call(yourFrozenDate)
?
第一次尝试:“包装”
一种直接的方法是提供一个包装日期的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
这样做是创建一个对象,该对象具有实际日期对象所具有的所有属性,并使用Function.prototype.bind
设置它们的this
。
这不是一种在钥匙周围聚集的万无一失的方式,但希望你能明白我的意图。
但是等等……再看这里和那里,我们会发现有更好的方法来做到这一点。
第二次尝试: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());
我们解决了!
...有点。看,Firefox 是目前唯一实现代理的,并且由于某些奇怪的原因不能代理日期对象。此外,您会注意到您仍然可以执行'setDate' in proxyDate
之类的操作,并且您会在控制台中看到完成。为了克服需要提供更多的陷阱;具体来说,has
、enumerate
、ownKeys
、getOwnPropertyDescriptor
,谁知道有什么奇怪的边缘情况!
...所以再想一想,这个答案几乎毫无意义。但至少我们玩得很开心,对吧?
【讨论】:
实际上,Chrome(和 NodeJS)在 stable 中实现代理。您可以放心使用它。 好一个 :) 顺便说一句:你有什么理由使用 Reflect.get 而不是 target[prop]? @KamilTomšík 对称!每个 Proxy 陷阱在 Reflect 中都有其默认实现,因此在实现代理和委托默认操作时,我喜欢使用 Reflect。除此之外没有什么特别的原因。【参考方案4】:您可以将其包装在类结构中并定义自定义 getter 和 setter 以防止不希望的更改
【讨论】:
这听起来像是对我来说最简单的答案【参考方案5】:恐怕,公认的答案实际上是有缺陷的。 您实际上可以冻结任何对象的实例,包括 Date
的实例。为了支持 @zzzzBov 的回答,冻结对象实例并不意味着该对象的状态变得恒定。
证明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
但是你可以实现我想你想要的行为如下:
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)
【讨论】:
【参考方案6】:使用代理对象可能是当今最好的解决方案。基于@Zirak's answer back in 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]"
来源:https://gist.github.com/sirlancelot/5f1922ef01e8006ea9dda6504fc06b8e
【讨论】:
【参考方案7】:使日期成为整数对我有用:
let date = new Date();
const integerDate = Date.parse(date);
let unchangedDate = new Date(integerDate);
【讨论】:
这没有提供问题的答案,而且你的代码也不起作用,打电话unchangedDate.setTime(0)
会改变日期,你可以自己检查
对我来说确实如此。问题是:任何想法如何冻结日期? integerDate 将被冻结,直到您再次将其设为变量。 .setTime(0) 它只是一个检查器,看看你是否可以更改日期,它不适用于 integerDate。
integerDate
不是日期类型以上是关于有没有办法 Object.freeze() 一个 JavaScript 日期?的主要内容,如果未能解决你的问题,请参考以下文章