繁体   English   中英

有没有办法 Object.freeze() 一个 JavaScript 日期?

[英]Is there a way to Object.freeze() a JavaScript Date?

根据MDN Object.freeze()文档

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的调用要么失败,要么什么都不做。 任何想法如何冻结日期?

有没有办法 Object.freeze() 一个 JavaScript 日期?

我不这么认为。 但是,您可以接近,请参阅下面的行。 但首先让我们看看为什么Object.freeze不起作用。

我期待在某个日期调用 freeze 会阻止对该日期的更改...

如果Date使用对象属性来保存其内部时间值,它会,但它没有。 它使用[[DateValue]]内部槽代替。 内部插槽不是属性:

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

因此,冻结对象不会对其改变其[[DateValue]]内部槽的能力产生任何影响。


可以冻结Date ,或者无论如何都可以有效地冻结:用无操作函数(或引发错误的函数)替换它的所有 mutator 方法,然后freeze它。 但正如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的所有 mutator 方法都以set开头,但如果不是,则很容易调整上述内容。

来自MDN 关于Object.freeze的文档(强调我的):

无法更改数据属性的值。 访问器属性(getter 和 setter)的工作方式相同(并且仍然给人一种您正在更改值的错觉)。 请注意,作为对象的值仍然可以修改,除非它们也被冻结。

Date 对象的setTime方法不会更改 Date 对象的属性,因此它会继续工作,尽管已冻结实例。

这是一个非常好的问题!

TJ Crowder 的回答有一个很好的解决方案,但它让我思考:我们还能做什么? 我们如何绕过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

this 的作用是创建一个对象,该对象具有实际日期对象所具有的所有属性,并使用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类的操作,并且您会在控制台中看到完成。 为了克服需要提供更多的陷阱; 具体来说, hasenumerateownKeysgetOwnPropertyDescriptor ,谁知道有什么奇怪的边缘情况!

......所以再想一想,这个答案几乎毫无意义。 但至少我们玩得很开心,对吧?

您可以将其包装在类似结构的类中并定义自定义 getter 和 setter 以防止发生不希望的更改

恐怕,公认的答案实际上是有缺陷的。 您实际上可以冻结任何对象的实例,包括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)

使用 Proxy 对象可能是当今最好的解决方案。 基于@Zirak 早在 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

使日期成为整数对我有用:

let date = new Date();
const integerDate = Date.parse(date);
let unchangedDate = new Date(integerDate);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM