根据MDN Object.freeze()
documentation:
Object.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
的调用失败或什么也不做。有关如何冻结日期的任何想法吗?
答案 0 :(得分:61)
有没有办法让Object.freeze()成为一个JavaScript日期?
我不这么认为。您可以关闭,但请参阅下面的行。但首先让我们看看为什么Object.freeze
无效。
我原以为在某个日期冻结呼叫会阻止对该日期的更改......
如果 Date
使用对象属性来保存其内部时间值,那么,但它不会。它使用[[DateValue]]
internal slot代替。 Internal slots属性不是:
内部插槽对应于与对象关联并由各种ECMAScript规范算法使用的内部状态。内部插槽不是对象属性...
冻结对象对其变异[[DateValue]]
内部插槽的能力没有任何影响。
你可以冻结Date
,或者无论如何都是有效的:用无操作函数(或抛出错误的函数)替换所有的mutator方法然后freeze
它。但正如observed (很好的一个!) 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>
&#13;
我认为 Date
的所有mutator方法都以set
开头,但如果没有,则很容易调整上述内容。
答案 1 :(得分:8)
来自MDN's docs on Object.freeze
(强调我的):
无法更改数据属性的值。 访问者属性(getter和setter)的工作方式相同(并且仍然给出了您正在更改值的错觉)。请注意,仍然可以修改作为对象的值,除非它们也被冻结。
Date对象的setTime
方法不会更改Date对象的属性,所以它会继续工作,尽管已经冻结了实例。
答案 2 :(得分:6)
您可以将其包装在类似结构的类中,并定义自定义getter和setter以防止意外更改
答案 3 :(得分:6)
这是一个非常好的问题!
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
。
这不是围绕钥匙收集的简单方法,但希望你能看到我的意图。
但是等等......在这里和那里进一步观察它,我们可以看到有更好的方法。
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
以及谁知道有什么奇怪的边缘情况!
......所以第二个想法,这个答案几乎毫无意义。但至少我们玩得很开心吧?
答案 4 :(得分:2)
接受的答案实际上是有缺陷的,我很害怕。 您实际上可以冻结任何对象的实例,包括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)
答案 5 :(得分:0)
使用 Proxy 对象可能是当今最好的解决方案。基于 @Zirak's answer back in 2016,我修改并改进了代理处理程序以获得最大的兼容性:
Select
(e.FirstName+ ' '+ e.LastName) as EmployeeName,
od.OrderID,
od.ProductID,
od.UnitPrice,
od.Quantity,
(od.UnitPrice * od.Quantity) as TotalSales,
od.Discount
from
dbo.[Order Details] od
inner join dbo.Orders as o on od.OrderID = o.OrderID
inner join dbo.Employees e on o.EmployeeID = e.EmployeeID
来源:https://gist.github.com/sirlancelot/5f1922ef01e8006ea9dda6504fc06b8e
答案 6 :(得分:-1)
使日期成为整数对我有用:
let date = new Date();
const integerDate = Date.parse(date);
let unchangedDate = new Date(integerDate);