【问题标题】:Is there a way to Object.freeze() a JavaScript Date?有没有办法 Object.freeze() 一个 JavaScript 日期?
【发布时间】: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 的基本缺陷。它会导致无穷无尽的问题。

标签: javascript node.js immutability


【解决方案1】:

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

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

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

它会如果 Date 使用对象属性来保存其内部时间值,但它没有。它改用[[DateValue]] internal slotInternal 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 之类的操作,并且您会在控制台中看到完成。为了克服需要提供更多的陷阱;具体来说,hasenumerateownKeysgetOwnPropertyDescriptor,谁知道有什么奇怪的边缘情况!

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

    【讨论】:

    • 实际上,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 不是日期类型
        猜你喜欢
        • 1970-01-01
        • 2021-02-23
        • 1970-01-01
        • 1970-01-01
        • 2011-02-27
        • 2021-05-09
        • 2015-11-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多