有没有办法 Object.freeze() 一个 JavaScript 日期?
Is there a way to Object.freeze() a JavaScript Date?
根据MDN Object.freeze()
documentation:
The Object.freeze()
method freezes an object: that is, prevents new properties from being added to it; 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.
我原以为在某个日期调用 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
的调用要么失败,要么什么都不做。有什么想法可以冻结约会吗?
来自 MDN's docs on 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). Note that values that are objects can still be modified, unless they are also frozen.
Date 对象的 setTime
方法没有改变 Date 对象的 属性,因此它继续工作,尽管已冻结实例。
Is there a way to Object.freeze() a JavaScript Date?
我不这么认为。不过,您可以获得 close,请参阅下面的行。但首先让我们看看为什么 Object.freeze
不起作用。
I was expecting that calling freeze on a date would prevent changes to that date...
如果 Date
使用一个对象 属性 来保存它的内部时间值,它会 ,但它没有。它使用 [[DateValue]]
internal slot instead. Internal slots 不是属性:
Internal slots correspond to internal state that is associated with objects and used by various ECMAScript specification algorithms. Internal slots are not object properties...
因此冻结对象对其改变其 [[DateValue]]
内部插槽的能力没有任何影响。
你 可以 冻结一个 Date
,或者无论如何有效:用 no-op 函数(或抛出错误的函数)替换它的所有修改器方法然后 freeze
它。但是作为 by zzzzBov (不错!),这并不能阻止某人做 Date.prototype.setTime.call(d, 0)
(故意绕过冻结的物体,或者作为他们使用的一些复杂代码的副产品)。所以它是 关闭,但没有雪茄。
这是一个示例(我在这里使用 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>
我 认为 Date
的所有增变器方法都以 set
开头,但如果不是,则很容易调整上面的内容。
您可以将其包装在类似 class 的结构中并定义自定义 getter 和 setter 以防止意外更改
这个问题问得好!
有一个很好的解决方案,但它让我思考:我们还能做些什么?我们怎样才能绕过 Date.prototype.setTime.call(yourFrozenDate)
?
第一次尝试:"Wrapper"
一种直接的方法是提供一个 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
谁知道有哪些奇怪的边缘情况!
...所以转念一想,这个答案几乎毫无意义。但至少我们玩得很开心,对吧?
恐怕接受的答案实际上是有缺陷的。 您实际上可以冻结任何对象的实例,包括 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)
将日期设为整数对我有用:
let date = new Date();
const integerDate = Date.parse(date);
let unchangedDate = new Date(integerDate);
使用代理对象可能是当今最好的解决方案。基于,我修改并改进了代理处理程序以获得最大兼容性:
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
根据MDN Object.freeze()
documentation:
The
Object.freeze()
method freezes an object: that is, prevents new properties from being added to it; 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.
我原以为在某个日期调用 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
的调用要么失败,要么什么都不做。有什么想法可以冻结约会吗?
来自 MDN's docs on 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). Note that values that are objects can still be modified, unless they are also frozen.
Date 对象的 setTime
方法没有改变 Date 对象的 属性,因此它继续工作,尽管已冻结实例。
Is there a way to Object.freeze() a JavaScript Date?
我不这么认为。不过,您可以获得 close,请参阅下面的行。但首先让我们看看为什么 Object.freeze
不起作用。
I was expecting that calling freeze on a date would prevent changes to that date...
如果 Date
使用一个对象 属性 来保存它的内部时间值,它会 ,但它没有。它使用 [[DateValue]]
internal slot instead. Internal slots 不是属性:
Internal slots correspond to internal state that is associated with objects and used by various ECMAScript specification algorithms. Internal slots are not object properties...
因此冻结对象对其改变其 [[DateValue]]
内部插槽的能力没有任何影响。
你 可以 冻结一个 Date
,或者无论如何有效:用 no-op 函数(或抛出错误的函数)替换它的所有修改器方法然后 freeze
它。但是作为 Date.prototype.setTime.call(d, 0)
(故意绕过冻结的物体,或者作为他们使用的一些复杂代码的副产品)。所以它是 关闭,但没有雪茄。
这是一个示例(我在这里使用 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>
我 认为 Date
的所有增变器方法都以 set
开头,但如果不是,则很容易调整上面的内容。
您可以将其包装在类似 class 的结构中并定义自定义 getter 和 setter 以防止意外更改
这个问题问得好!
Date.prototype.setTime.call(yourFrozenDate)
?
第一次尝试:"Wrapper"
一种直接的方法是提供一个 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
谁知道有哪些奇怪的边缘情况!
...所以转念一想,这个答案几乎毫无意义。但至少我们玩得很开心,对吧?
恐怕接受的答案实际上是有缺陷的。 您实际上可以冻结任何对象的实例,包括 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)
将日期设为整数对我有用:
let date = new Date();
const integerDate = Date.parse(date);
let unchangedDate = new Date(integerDate);
使用代理对象可能是当今最好的解决方案。基于
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