Moment: 使时刻大部分是不可变的

创建于 2014-07-03  Â·  163评论  Â·  资料来源: moment/moment

关于这一点已经有很多讨论。 这是提案:

以下可变方法将在 3.0.0 中变为不可变: utc 、 local 、 zone 、 add 、 subtract 、 startOf 、 endOf 、 lang ,也在duration : add 、 subtract和lang 。

首先,所有方法都将使用methodNameMute变体进行复制。 我们还需要名为methodNameImmute不可变变体。 从 3.0 开始,普通的旧方法将默认开始使用不可变选项。

有争议的是:

  • 应该将lang设为不可变
  • 所有的 getter/setter(包括get / set )也应该是不可变的
  • 方法的可变和不可变版本的命名
  • 当然——我们应该进行切换,还是停留在不可变的 API 上

好的部分是我们今天可以制作方法的不可变版本,然后再决定要做什么。 如果我们切换,也意味着2.x分支会在3.x退出后存在相当长的一段时间。

@icambron @timrwood @gregwebs @yang @lfnavess @soswow @langalex

最有用的评论

我的两分钱是,我们要么完全坚持不变性,要么根本不去做。 有些方法是不可变的( startOf , add )而有些不是( year , get )只是令人困惑,开发人员必须跟踪哪些是哪些。

所有163条评论

我的两分钱是,我们要么完全坚持不变性,要么根本不去做。 有些方法是不可变的( startOf , add )而有些不是( year , get )只是令人困惑,开发人员必须跟踪哪些是哪些。

我也更喜欢默认情况下所有不可变的东西。 吸气剂不是不可变的吗?

以下是我在切换到不变性时看到的大问题。

伪不变性

由于 javascript 的性质,我们永远无法拥有真正的不变性。

我们能做的最好的事情就是创建一个副本,然后变异并返回该副本。 我们可以仔细包装所有公共方法,以确保我们始终在复制和变异,而不仅仅是变异,但这并不能阻止某人执行m._d = new Date()甚至m._d.setHours(1) 。

我同意@icambron ,如果我们转向不变性,那应该是一个彻底的改变。 任何可能在某个时刻更改属性的方法都将创建该时刻的副本并在副本上进行更改。

Api 表面积

切换到伪不变性的不幸之处在于,如果我们想仍然支持可变性,则需要创建许多新的 api。

以前,从变异片刻切换到克隆和变异片刻就像在正确的位置添加.clone()一样简单。 现在,我们必须为所有 setter 创建可变接口,这大大增加了 api 表面积。

这包括约 20 个 setter 方法、add/subtract、local/utc/zone/tz、startOf/endOf、lang 以及 3rd 方插件中使用的任何其他方法。

内存问题

因为我们现在每次想要更改一个值时都会创建副本,所以内存使用量会增加。 当然,新的时刻将被垃圾收集,但与此更改相关的额外成本是需要记住的。

我们必须非常小心,以确保我们不会使用使用其他 setter 的方法创建大量一次性克隆。

为了跟踪正在调用哪些方法,我使用了这个小函数包装器。

for (var method in moment.fn) {
  moment.fn[method] = (function (fn, method) {
    return function () {
      console.log(method);
      return fn.apply(this, arguments)
    }
  })(moment.fn[method], method)
}

现在,当运行一个方法时,我们可以看到原型上的其他方法是如何使用的。 并非所有这些方法都需要克隆,所以我对需要克隆的方法添加了注释。

moment().isSame(moment(), 'year')
isSame
clone        // clone
startOf      // clone
month        // clone
date         // clone
year         // clone
date         // clone
hours        // clone
minutes      // clone
seconds      // clone
milliseconds // clone
valueOf
local        // clone
zone         // clone
startOf      // clone
month        // clone
date         // clone
year         // clone
date         // clone
hours        // clone
minutes      // clone
seconds      // clone
milliseconds // clone
valueOf

即创建 21 个副本,然后立即丢弃。 显然,我们可以通过使用一些可变的内部方法来优化这一点,并且只公开不可变的版本,但它会显着增加内部复杂性,试图记录哪些时刻仍然需要克隆,哪些不需要。

性能问题

克隆片刻比变异片刻慢得多。 我为此拼凑了几个 jsperf 测试。

http://jsperf.com/moment-cloning

http://jsperf.com/moment-cloning-2

我认为第二个测试可以更好地表示切换到伪不变性时的性能损失。 如果我们将这些结果乘以上面提到的 21 个克隆实例,执行时间就会慢得多。

我确信我们可以暂时优化克隆路径,但我们需要使其速度提高 50 倍才能获得可比的性能。 我很确定这是不可能的。

概括

切换到不变性会大大增加内部和外部 api 的复杂性,并具有主要的性能和内存问题。 我认为这些成本不值得不变性提供的好处。

我认为这里列出的性能问题没有抓住要点:

一般来说,在执行变异之前,需要一个初始的 .clone() 来确保正确性。

我们不能假装当前 API 不需要 clone() 。 这里不同的主要情况是执行多个顺序突变。 这种情况是通过创建一个构建器 API 来解决的,这样所有的更改都作为单个克隆上的更改执行。

我是否缺少其他常见用例?

我最初的问题是关于startOf和endOf方法。 出于某种原因,这些名字对我来说就像“让我开始一个月”而不是“将此时刻设置为一个月的开始”。 像add和subtract这样的方法在语义上是完全可以的。 在不创建新对象的情况下向对象添加内容是完全可以的。
对我个人而言,将方法startOf和endOf重命名startOf toStartOf和toEndOf (例如“将这一刻移动到一个月的开始”)将解决问题。 恕我直言

@gregwebs抱歉,我的意思是上面的set 。

我不同意@soswow; 我认为它需要保持一致。 事实上,我认为toStartOf更强烈地暗示了不变性,就像它提供了另一种表示方式toISOString 。 更重要的是,我认为我们需要能够做出诸如“Moment 的 setter mutate moment”或“Moments setter 返回副本”之类的陈述,而不是“好吧,对于这些方法......”

关于@timrwood的担忧:

JS 对象不会真正不可变这一点并不困扰我。 关键是 API 提供了一个不可变的契约。 当然,用户可以通过摆弄带下划线的属性来作弊,即使是以不变性为主要做事方式的语言,作弊通常也是可能的。

在表面积和性能方面:我认为我们需要在内部使用 mutator 以避免耗尽所有 CPU 和内存 [1],因此我们将不得不在某种程度上支持它们。 那么我们不妨将它们暴露在外部,例如setYear()等。这增加了一堆表面积,但它并没有真正增加太多复杂性; 对于非显式变异器,从外部克隆,在内部变异。

看待这一点的一种方法是用户必须在他们的代码中进行克隆,因此 Moment 可能会为他们做。 这确实在性能敏感的地方存在链接问题,这可以通过构建器界面(根据 Greg 的想法)或让用户在那里使用 mutator 来解决。 构建器增加了一堆复杂性 [2],所以我认为我只是喜欢显式的 mutator 替代方案。 我认为现实是,大多数时候,Moment 并没有在性能敏感的情况下使用,因此这些情况不一定是最方便的 API。 我宁愿有一个很好的不可变 API,当我需要它时,它带有性能舱口。

[1] FP 领域的酷孩子通过_结构共享_ 解决了这个问题,但这在这里可能不切实际。

[2] 传统上,人们制作的构建器是单独的对象,但这在这里真的很冗长,因为您必须复制整个 setter API。 只是随口吐槽,但另一种选择是.chain()创建一个克隆时刻,上面只设置了一个isBuilding标志。 然后内部克隆被忽略,只返回用于突变的对象。 然后build()取消设置标志并返回该克隆。 问题是,如果设置了标志,你需要你的吸气剂尖叫血腥谋杀,否则人们最终会使用链式但未构建的 Moments,这些 Moments 会突然发生突变。 然后你需要区分外部和内部称为getter。 布莱克。 另一种选择是在内部将构建器所需的功能分解为一个 mixin,并在构建器和 Moment 中使用它,但从代码组织的角度来看,这可能是行不通的。

对我有用的是向函数添加一个额外的参数,一个标志(我命名为 self)来表示可变性,默认情况下是不可变的(返回一个副本或新对象),当我检测到性能时,我将标志设置为 true

这个立场解决了很多矛盾,
具有相似名称的函数执行几乎相同的代码,
或者当我检测到性能点时必须更改函数名称和可能的参数
在我的公共方法中,我开始调用带有副本的函数的代码,然后调用带有 true 标志的调用
有了这个,我也可以链接功能

在我的代码中,我使用数组数组(如表格、行数组)
所以我有过滤、联合等功能,以前用结果重新运行一个新数组,我检测到为了得到我多次调用同一个函数的最终结果,现在第一次调用是创建一个副本而不是改变初始数组和以下调用我使用相同的数组删除我不需要的行

一个基本的例子,可能在这里:
moment.add = function(measure, ammount, self){
}

moment.add = function (measure, ammount, self) {
var $moment = self ? 这:this.clone();
// 实际代码
返回 $moment;
}

谢谢大家的 2 美分 :)

对于协议,我同意@icambron在所有观点上的最后

还有两个大问题。

更简单的一个是新 API 应该是什么,两个选项:
1.1 不同命名的方法(可变和不可变) year / setYear , startOf / setStartOf
1.2 或 builder api chain().mutators().build() ,这是@lfnavess提出的非hacky 版本。

builder api 确实看起来更性感,但应该注意对象不要在构建模式下停留太长时间,这给我们和用户增加了另一个麻烦。

现在是难题——迁移到新版本。 我在这里看到两个选项:
2.1 开发人员必须重写他们的代码(疯狂的正则表达式可能适用于 1.1 和 AST 级别的解决方案 1.2——因为没有人使用year和month作为他们自己的方法的名称)。 python采用了这种方法——我们都可以看到结果——一种全新的语言诞生了!
2.2 始终打开 builder api 的选项(与现在相同),以及为新代码停用它的方法。 这看起来更像是 _evolutionary_,但它会引起的混乱数量可能不值得。 现在每个时刻都有两个标志:它是可变的,如果是 - 它是严格可变的(没有 getter)还是过渡可变的(getter ok)。 更不用说在函数中接收矩对象了——你应该检查模式是什么,确保维护它......真是一团糟!


现在我想到了一个疯狂的想法

写时复制克隆

m = moment();
funcIDontTrust(m.clone());  // doesn't actually clone

function funcIDontTrust(m) {
  m.year(2005);  // perform the clone here
  console.log(m);
}

我不确定用这种方法可以减少多少,因为时刻实例很轻。 此外,所有的突变器现在都必须执行检查。

有几种方法可以在不同的场景中以不同的性能实现。 好消息是它向后兼容,我们将为我们和我们的用户节省大量精力。 我认为这比重新发明轮子更重要。

我不确定我们在这里得到了什么。

切换到 immutablilty 会产生大量相关成本,也许我错过了它,但是
我真的没有看到可比的好处。

主要的好处似乎是开发人员的偏好。 这一切都是为了让开发人员没有
在传递它时考虑一下所有权?

我不认为切换到不变性会降低 bug 的频率,它只会
更改错误类型。 @ichernev的示例甚至显示了会出现的确切类型的错误
表面,同样难以追踪。

m = moment();
funcIDontTrust(m.clone());  // doesn't actually clone

function funcIDontTrust(m) {
  m.year(2005);  // perform the clone here
  // m is still in 2014
  // m.year(2005) created a clone but did not assign it to anything
  // it should be `m = m.year(2005)`
  console.log(m);
}

这是可变性和不变性之间的优缺点列表。 如果我错过了什么,
让我知道,我会编辑此评论。

| 不可变 | 可变 |
| --- | --- |
| 一些开发人员更喜欢它 | 其他一些开发人员更喜欢它 |
| 绕过时刻避免错误| 避免在忘记分配克隆时刻时出现错误 |
| 几十个新的 api 方法,也将支持可变性 | 使用现有的 .clone() 方法,已经支持不变性 |
| | 快一个数量级 |
| | 使用显着更少的内存 |

我确实认为不变性很有用,但我认为它不适合 JavaScript。 我认为一个不可变的接口对于像 Elm 这样的语言来说可能是有意义的,其中期望不可变,但对于 JavaScript,我认为可变性是可取的。

typeof a === "object"内置函数的大部分 api 都是可变的。 Array#push,pop,reverse,sort,shift,splice,unshift都改变了一个数组的内容而不是返回一个新的数组。 所有 16 个Date#setX方法都会改变它们的实例。

我认为我们看到很多人抱怨时刻是可变的,但如果我们转换,我认为我们也会有同样多的人抱怨。 两年前, eod/sod方法已经发生了这种

在看了一堆关于可变性的老问题之后,我想我在这里可能听起来像是一个破纪录。 在双方,这是过去几年提出的相同点。 我只是想确保在讨论中表达了保留可变 api 的论点。

@timrwood这些是很好的比较,但很明显您没有花时间了解不可变用例。 我们已经讨论了为什么您发布的性能比较假设 API 实现不佳并且没有意义。

错误比较也是无效的。 因为 momentjs 支持链式 API,人们可以期望它是不可变的。

var newM = m.year(2005) // wrong, these are both the same!

所以不可变和可变现在都有同样的问题。 如果您摆脱了链接 API,您可以使用当前的可变版本来避免它。

因此,不可变 API 比可变 API 更可取,因为您可以安全地在函数之间传递片刻。 对于当前的可变时刻,如果我在函数之间传递片刻,我有 2 个选项

1)疯狂的错误方式(这可能是最常见的):调查所有源代码以确保没有不需要的突变。 编写单元测试以确保不会出现不需要的突变。
2)理智的方式(让我们假设每个人都这样做),防御性编程:记住在我的函数中发生变异之前调用 clone() 函数。

使用不可变 API,我们不必记住每次都调用 clone()。 相反,我们必须记住调用让我们避免克隆的 API 函数,但这只是性能优化,而不是正确性问题。

很明显你没有花时间去理解不可变用例

这是不公平的说法。 我的论点是我看到了好处,但不认为它们会超过成本。

你可以安全地在函数之间传递片刻

我们不必记得调用 clone

这不是不变性的用例吗? 如果还有更多我没有花时间理解的内容,请告诉我,但这似乎是过去几年唯一的论点。

@timrwood是的,就是这样。

但是我没有看到您承认您的案例 _against_ 不变性(糟糕的性能,促进了可变 API 中不存在的不同类型的错误)无效的迹象。

我不知道 freeze() 是否有帮助
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze

我认为我们应该坚持 ecmascript 5 的观点,并且可能添加一个深度冻结当前对象的函数,或者一个自动创建冻结对象的全局标志

http://blogorama.nerdworks.in/preventextensionssealandfreeze/

可能是构造函数中的额外参数来创建冻结对象,因为冻结对象无法解冻

@lfnavess在提到写入副本之前,我想过freeze 。 问题是......没有人使用它/知道它,当它不抛出异常(在非严格模式下)时也没有帮助 - 它实际上会创建疯狂的错误供您跟踪​​。

@timrwood我认为我没有把我的例子说清楚。 在m.year(2014) // clone here我的意思是内部时刻实际上会进行克隆(分配更多内存),而 m 会自动指向该新内存。 嗯,这基本上意味着clone()还应该分配一些 shell 内存(指向内部日期表示的东西),我只是不确定这样做会获得多少。

创建clone的半成品版本,只克隆接口,以及更改底层数据(从共享存储到特定于实例)的能力——这实际上取决于 Date 对象的昂贵程度。 缺点是每个函数都需要执行this._storage._d而不是this._d ,我不确定这是否会克服好处。

我没有得到任何关于如何处理迁移现有库/用户的评论。 我真的不喜欢上面列出的任何选项。

反向兼容性是 IMO 反对这一点的最有力论据。 如果我们这样做,我们就必须接受这是一个重大的突破性变化。 如果我们不想接受这一点,我们就不应该这样做。

值得一提的是 re:perf 还可以从不变性中获得一些巨大的优势; 这不是单调的性能打击。 例如,您可以在对象级别缓存内容,因为它们永远不会改变。 我也认为我们应该能够优化clone()的生活垃圾; AFAIK 它涉及克隆日期并复制五个值; 我认为我们应该像newThing._offset = oldThing._offset那样对它们进行硬编码。

编辑,参数,否 - 插件也添加字段(例如此处)。

鉴于对向后兼容性的强烈渴望,但又要保持轻量级,我认为最好的解决方案是分叉 javascript 源代码(在这个项目的源代码中或开始一个全新的项目)。 有1个以上的互联网时间图书馆的空间。

另外:re: @ichernev关于结构共享的想法,一种可能性是使用原型继承而不是包装共享状态对象。

我们 WhoopInc 已经潜伏在这个讨论中一段时间​​了。 由于这里的讨论似乎在循环进行,本周末我花了一些时间来探索具有构建器 API 的不可变版本的 moment 可能是什么样子。 (除非受邀,否则我无意提交针对 moment 的 PR,因为我故意进行了比我预期在 moment 中实现的更尖锐的 API 更改。)结果如下: https://github。 com/WhoopInc/frozen-moment

我刚进入几个小时,所以一切都非常粗糙,但根据测试结果,我认为大部分核心时刻功能都在工作。 我将继续跟踪此对话,并欢迎针对我们的 repo 问题中的 fork 提供反馈。

今晚我将尝试在该 repo 上发布一些更新的文档,但基本上我只是将所有 setter 和 mutation 方法拆分为一个单独的构建器对象。 所以使用 API 可能看起来像frozenMoment("2014-07-21").thaw().subtract(1, "day").startOf("day").freeze().format("YYYY-MM-DD") 。 (尽管在这个特定示例中,使用frozenMoment.build("2014-07-21").subtract...使用构建器启动链而不是从frozenMoment 初始化构建器会更有效)

FWIW,当我开始使用 moment 时,我假设它遵循 FP 原则,并且每次调用函数时都会返回相同的值:

var now = moment();
var yesterday = now.subtract(1, 'days');
var dayBeforeYesterday = now.subtract(2, 'days');

当然,我没有得到我预期的结果。 作为一个新用户,这让我措手不及。

考虑这个伪代码,它演示了我期望代码的行为方式:

var now = now;
var yesterday = now - 1day;
var dayBeforeYesterday = now - 2days;

但相反,它最终像这样工作,这让我觉得很奇怪:

var now = now;
var yesterday = now = now - 1day;
var dayBeforeYesterday = now = now - 2days;

现在,尽管很乏味,我只是小心翼翼地到处.clone() 。

var now = moment();
var yesterday = now.clone().subtract(1, 'days');
var dayBeforeYesterday = now.clone().subtract(2, 'days');

IMO,Javascript 容易出现细微的错误,我觉得 FP 原则有助于最大限度地减少这些错误。

我同情这是一个艰难的决定。 我很欣赏你的工作。 Moment.js 很棒。

+1 表示 100% 不变性。

老实说,它不是一成不变的,这有点令人沮丧。

+1 表示版本 3 中的 100% 不变性

肯定应该有一个不可变的 API。 作为其他日期库(特别是 .NET DateTime和 Joda Time / Noda Time)的用户,我直觉地希望add方法不会改变日期对象。

+1 不变性

+1 表示 100% 不变性

如果决定是为了不变性,我愿意付出我的时间来实现它。 也许通过视频通话配对。 我想为开源做出更多贡献,但需要学习技巧。

对我来说,不变性更可取,但它应该从一开始就完成。 这是一个巨大的突破性变化。 这个项目的一个分支专注于不变性将是一个更好的主意。

@dsherret这就是semver的用途。 我们只是撞了主要版本。

然而,通过一些努力,它可以作为一个配置选项引入:“你希望一切都是不可变的吗?对还是错”。 默认为假。

此处不受支持的非官方 ymmv 不可变时刻插件: https ://gist.github.com/timrwood/fcd0925bb779f4374a7c

哈哈! 我很惊讶这么晚才来到这里并发现我是更不可变的 API 的第一批支持者之一.. :)

是的,我为下一个主要版本的不变性 +1。

另一个 +1 来自我的不可变默认时刻。
这是我的“时刻”混合物: https ://gist.github.com/idrm/a91dc7a4cfb381dca24e(使用风险自负!)。 只需将您的 moment() 调用替换为 imoment() 就足够了。 所有静态 moment.xyz()(例如 moment.min()、moment.max() 等)函数调用应保持原样。

+100 万美元

+1 不变性

我是否还可以在此讨论线程的先前建议中添加 +1 以重命名某些函数,以便它们更易于阅读(“startOf”到“toStartOf”,“add”到“plus”,“month”到“withMonth” “, 等等。)? 也就是说,假设您采用不可变默认路由。 我经常使用 Joda Time,很容易弄清楚“date.withDayOfMonth(1).withDayOfWeek(DateTimeConstants.MONDAY)”是什么意思。
这些也不必在主要发行版 JS 中; 将这些附加到 vanilla JS 之上的附加组件也可以正常工作(哎呀,我强烈考虑自己编写这样一个 Joda 时间对齐的附加组件,以与我的“瞬间”模块相结合)。

@ichernev , @icambron ,你已经决定了吗? 在 3.0 中,时刻将是不可变的吗? 如果是这样:您预计 3.0 什么时候发布? 我问是因为我正在考虑使用冻结时刻或自己编写一个小包装 - 我不知道我是否应该等待。

仅供参考,冻结时刻最近大多处于保持模式——我已经从上游时刻移植了一堆 PR,但还没有真正进行任何其他应该在那个分支中发生的重构。

也就是说,如果您只需要默认的英语语言环境,那么冻结时刻应该会很好用。 (一切都适用于我的用例,并且我一直保持 Moment 的高单元测试覆盖率。)非英语语言环境被破坏,因为在移植 Moment 最近重构的语言环境 API 后我没有更新它们。

一旦我们对 Moment 3.0 做出决定,我将计划更认真地处理 Moment 中的不变性工作,或者在适当的时候在冻结时刻分叉上工作。 我最初的目标是修复语言环境支持并保持与 Moment 3.0 的 API 相同的功能。

+1 不变性

+1 表示不变性。 也会改变对象的format方法呢?

+1 不变的moment.js
或者也许是 moment.js 的一个分支? 不可变的moment.js? 因为这肯定会是一个突破性的变化。

:100:
做那些事!

+1 这些天不变性是我期望在任何好的 JS API 中的东西

:+1: 是的,我们非常喜欢这个。 目前我们到处都撒了 .clone() 。

+1 这将是一个非常好的改进

:+1: 不可变的所有东西

+1 表示不可变

让它一切不可变。 我厌倦了在操作 now 变量然后再次更改 now 之前克隆每个时刻(现在)变量。

+1 不变性

我不会期望startOf('day')会发生变异(尽管如果我更仔细地阅读,我会承认它有据可查)。 这在我的应用程序中引起了一个有趣的错误。

我绝对同意大多数 moment.js 操作符的可变性都很尴尬。 mmnt.startOf('day')是超级反直觉的,因为它改变了mmnt 。

我将 moment.js 用于具有大量循环和日期比较的日历类型用例。 我用clone()遇到了性能问题,它们很糟糕。 对我和其他人来说,对克隆和变异进行一些控制是必不可少的。
尽管clone()看起来很尴尬,但它的作用非常清晰,并且使性能重构对我来说非常容易。

如果每个方法都为了拥有更好的 API 而在内部盲目地使用clone() ,我想我们会错过重点。

我的 2 美分 :-)

@jdurand我宁愿显式变异而不是显式克隆。

@dsherret我不介意任何事情。 我主要关心的是不要隐式克隆,因为这是一项非常昂贵的操作。
有人提到 setter 需要返回一个克隆副本,这让我感到害怕; 这将是非常低效的。

@jdurand它可能效率更低,但我认为隐式克隆将使大多数应用程序受益,其中克隆对象和变异对象之间的差异不会对最终用户的体验产生明显影响。 我认为开发人员的轻松和尝试防止开发人员错误应该优先于节省的几个毫秒,因为大多数开发人员不会同时执行数千个日期操作。 对于那些是,他们可以明确地说他们想要变异而不是克隆。

旁注:通过此更改,我认为可以进行一些性能改进……例如,可以通过引用过去的不可变对象来消除克隆,然后存储要为此对象执行的操作(例如add(1, 'days') )。 只有在调用.toString() 、 .format() 、 .toDate() 、 .day()等时执行操作才能计算结果。 这将是一个巨大的变化,它最终可能不会更快......需要进行一些单元测试来比较性能(此外,可能还有其他问题我没有考虑过,因为我从未看过任何代码在 moment.js 中,而不是如何克隆)。

@dsherret我喜欢 _builder_/_lazy_ 方法; 事后看来,它从一开始就应该是这样构建的。
正如您所说,我认为考虑到 API 兼容性的不可变分支是最好的。

多 2 美分:

  1. 我们真的应该担心性能吗,当 moment 显然是为了方便而不是性能而构建的? 我认为在 m.add('year',1) 中解析 'year' 比克隆慢得多。
  2. 除非有一个总分叉(不同的名称,不同的文档),否则维护 2 个版本会很麻烦。 我认为聪明的人应该想出一个想法,从相同的代码库生成 moment.immutable.min.js 和 moment.min.js ......

我们从什么时候开始如此害怕破坏变化? 当前版本是稳定的,人们仍然可以在不重构他们的代码库的情况下使用它。

维护两个代码库很痛苦,会减慢你的速度,而且仅仅拥有一个可变/不可变版本并不值得。

所以让我们用一个完全不可变的时刻,提升主要版本并完成它:舞者:

我刚开始使用这个库时发现了这个线程,但一直在调试一些代码并处理对我来说似乎是可变的不可预测的副作用。 很想看到一个完全不可变版本的库!

无耻的插头:我已经厌倦了在这里等待一个坚定的决定。 在过去的几天里,我复活了Frozen Moment并重新编写了它以充当 Moment 本身的插件。 给@wyantb 的帽子提示,帮助我在周末完成第一个预览版。

Frozen Moment 提供了一种与 Moment 一样工作的不可变类型。 基本上,不可变版本包装了 Moment 的功能,并在适当的时候为您调用.clone() 。

对于那些喜欢构建器 API 的人,我会挑战您将 Moment 本身视为构建器对象的一个​​非常好的实现! Frozen Moment 添加了我们都想要的核心不可变类型,以及一种从可变 Moment 构建不可变 Frozen Moment 的机制。

对于那些只想使用方便的不可变 API 的人——好吧,我也打算支持它。 我还没有构建将直接构建 Frozen 实例的构造函数,但这在 TODO 列表中。 在短期内,解决方法是使用moment().freeze()或moment.utc().freeze()创建所有内容。

Frozen Moment 显然是一个年轻的代码库,所以可能有一些粗糙的地方——但我鼓励这里的任何人都尝试一下,并为任何不能按您期望的方式工作的问题提交问题。

哦,还有一件事:我还没有为此做广告,但是 Frozen Moment 实例应该“适用于”大多数 Moment 插件。 只需确保所有其他 Moment 插件在 Frozen Moment 之前注册。 如果您发现一个插件在不可变的 Frozen Moments 中无法按预期工作,请提交一个错误,我会调查它。

+1 不变性

+1 表示不可变

有没有人考虑过在Immutable JS之上实现 moment ? 它是 JS 中针对不变性的优化库,它重用了不可变对象的未更改部分,从而减少了内存问题。

+1
由于这个原因,我刚刚修复了一个 3 年前的错误: https :

所以用户要求不变性,核心团队却找借口不做? :p
来吧伙计们,这个变化比在 ES6 中重写代码重要得多 ^^ 在目前的形式中,API 简直是糟糕的,有点像 JS Array 一个,其中一些方法是不可变的(过滤器、连接等),但有些方法是不可变的其他人不是(反向,排序),除了它们对向后兼容性的约束比库高得多。

+1

一个总是让我着迷的小陷阱(恕我直言,这是不变性的好理由):

var today = moment();
for (var i = 0; i < 7; i++) {
   week.push(today.subtract(i, 'days').format('dd').toUpperCase());
}

遗憾的是,这不会生成带有日期名称的数组,而是生成一些奇怪的东西,因为您实际上是这样计算日期的:

i = 0 = today -0;
i = 1 = today -0 -1;
i = 2 = today -0 -1 -2;
etc

所以你必须将它重构为:

var today = moment();
for (var i = 0; i < 7; i++) {
            if (i == 0) {
                week.push(today.subtract(0, 'days').format('dd').toUpperCase());
            }
            else {
                week.push(today.subtract(1, 'days').format('dd').toUpperCase());
            }
        }

@faebser很好的例子

+1 不变性

+1

@faebser今天早上发生在我身上。 Angular 的双向绑定 + 可变性让 **s 痛苦地保持克隆日期以防止修改当前日期。

+1 表示不变性,这只花了我几个小时。

:+1:

+1 不变性

我对这个话题有点纠结。

我的纯粹主义者想大喊“+1 表示不变性!Moment 对象显然属于ValueObject类型。”

然而,我们不能忽视 moment.js 是 GitHub 上第13大最受欢迎的 javascript 存储库(全能第 24位),并且在 bower.io 上搜索“moment”会返回111 个匹配结果。 即使我们可以为应用程序构建者提供一种渐进的方式来实现一个不可变的 moment 版本,它也会在其依赖项中造成巨大的混乱。

@ichernev也许是一个更谦虚的建议:是否有可能将 moment 文档页面的一小部分用于讨论可变性与不变性? 您可以简要说明您对该主题的官方立场,也可以添加一个简短的论点摘要,放置指向该主题的链接,或者如果您想要特定的讨论输入,它可以像 CTA 一样工作。

现在文档页面没有提到术语“不可变”。 谷歌搜索“不可变时刻”将您带到此页面,但我花了两个小时阅读,我仍然不确定您目前对该主题的立场。 如果 Google 的热门“时刻不变”能快速回答当下不变性的未来,那就太好了 :)

引用@jehoshua02 :
“我同情这是一个艰难的决定。我很欣赏你的工作。Moment.js 很棒。”

这是我的建议。 添加一个moment.immutable工厂,而不是使基矩不可变。 它是moment函数的一个属性,并将包含与moment完全相同的 API 签名,但不可变。

它甚至可能只是可变moment工厂的原型扩展,使用该原型的功能,但改为克隆。

编辑:看来WhoopInc/frozen-moment正是我正在寻找的。

@thomasvanlankveld重大更改是主要版本号的用途。 任何使用 Bower 和 npm 的人都可以选择坚持当前的主要版本号; 我们不应该担心这里的向后兼容性 - 除了那些只是从 CDN 提供服务的人。 但是如果他们使用来自 CDN 的 momentjs,他们可能对时不时更新库感兴趣。

我认为在下一个主要版本 - 或之后的主要版本 - 中具有不变性应该在路线图上。

所以看起来我也遇到了这个问题。

http://stackoverflow.com/questions/33002430/moment-js-formatting-incorrect-date

所以我完全支持不变性。

+1 对于不变性

刚刚因为这种令人惊讶和意外的行为而失去了一个晚上。
另一个 +1 的不变性与相应的主要版本更改以使其清楚。

+1 对于不变性

+1 表示完全不变性。
由于“有时可变,有时不可变”而损失的总小时数一定非常大。

是的,我的意思是认真的。 谁关心日期时间库中的性能? 喜欢..真的吗? 我猜 99.9% 的用户没有做任何甚至远程需要良好性能的事情。 通常你处理几个日期,或者在最坏的情况下处理几百个。 每秒处理数百万个日期的少数用户可以使用优化的可变 API 点。

不变性是唯一合理的设计选择。 有几项研究表明,使用不可变类型进行编程比使用可变类型更不容易出错。

+1 表示不变性。 这花了我几个小时。

部分问题在于像.startOf()这样的方法名称并不意味着底层对象的突变。

我在使用 Moment 时遇到了性能瓶颈,所以我可以向您保证,这种情况有时会发生。

但是,我不相信不可变时刻本质上会降低效率,并且可以设想一些情况下它们会更有效率。

这个争论很久以前在 Java 世界就已经解决了。 不可变日期获胜,最受欢迎的实现(JodaTime)最终成为 Java 8 标准库的一部分。

使用 Java 8 的LocalTime一直是那些“为什么我们不_总是_这样做?”的人之一。 时刻。 我很少宣传技术,但老实说,我看不到可变日期对象的任何好处。

所以,是的......我讨厌这些线程被 +1 淹没,但事实是,如果 Moment 没有,其他人将创建一个不可变的 JS 日期库。

我最近偶然发现了一个新的 npm 模块,它声称将大部分 JodaTime API(即 Java 8 日期)移植到 JS。

这会给节点和浏览器带来诸如不变性、LocalDate 和 LocalTime 之类的东西。

在 Java 中使用了这些概念后,其他一切都让人感觉杂乱无章且容易出错。

关联?

2015 年 12 月 11 日星期五下午 4:30 Andrew Schmadel通知@ github.com
写道:

我最近偶然发现了一个新的 npm 模块,它声称可以移植大部分
JodaTime API(即 Java 8 日期)到 JS。

这将带来诸如不变性、LocalDate 和 LocalTime 之类的东西
节点和浏览器。

在 Java 中使用过这些概念后,其他一切都让人感觉杂乱无章
并且容易出错。

—
直接回复此邮件或在 GitHub 上查看
https://github.com/moment/moment/issues/1754#issuecomment -163964349。

哇。
(在手机上输入,请原谅简洁)

由于我还没有插话,我只想声明我支持 moment 3.0 中的不变性。 主要是因为我来自 DDD 思想流派,其中像moment这样的对象将被视为 _value objects_,因此最好将其实现为不可变的。

即使性能受到显着影响,这仍然是正确的做法。 时刻应该自然地融入其他人的设计中。 直观的 API 胜过性能,并且变异在值对象上不直观(恕我直言)。

我还认为 moment 3.0 应该删除它对Date对象的依赖,但这是针对不同线程的讨论。

我完全同意这里的@mj1856 。

我一直在时刻实例上使用Object.freeze ,这通常实现了我所需要的; 除了我刚刚发现以下失败:

let now = Object.freeze(moment());
if (now.isSameOrBefore(anotherTime)) { // throws exception
}

例外:

TypeError: Can't add property _isValid, object is not extensible
 at valid__isValid (C:\git\quick-test\node_modules\moment\moment.js:93:24)
 at Moment.moment_valid__isValid [as isValid] (C:\git\quick-test\node_modules\moment\moment.js:2195:16)
 at Moment.isSame (C:\git\quick-test\node_modules\moment\moment.js:1945:44)
 at Moment.isSameOrBefore (C:\git\quick-test\node_modules\moment\moment.js:1962:21)

可以修复这个问题,以便在需要时可以使用Object.freeze吗?

@wmertens我认为就是这样: https :

@ichernev , @mj1856 ,因为我已经有一段时间没有参与 moment 核心开发了,并且不变性有相当大的兴趣,我正在收回我以前的立场。

我不确定我是否是唯一的阻挠者,但我很乐意在 3.0 中推进不变性。

@gabrielmaldi @wmertens 是的。 就是这样。 为我不连贯的评论道歉——我清楚地点击了一篇写到一半的帖子上的“提交”按钮。

整理一些我杂乱无章的想法:

  • 显然对 JS 的不可变日期对象有一些兴趣。 在其他几种语言中都有成熟的不可变日期库,并且在 JS 中存在很多对不可变对象的普遍推动( immutable.js有 10,500 颗星,如果有任何迹象的话)。 至少,我认为这值得进行概念验证。
  • 尽管有这种兴趣,但似乎编写的代码很少。 js-joda似乎是为 JS 编写不可变日期库的第一次认真尝试。
  • 不变的时刻将是一个巨大的突破性变化,这引发了一些哲学问题。 虽然我不想失去非常庞大的 MomentJS 社区的支持,但对于我们这些对不可变 JS 日期感兴趣的人来说,彻底打破并为 js-joda 做出贡献而不是尝试并不一定是一件可怕的事情推动 Moment(一个拥有庞大且已建立的用户群的成熟库)进行相当彻底的改变。
  • 旁白:js-joda 还很年轻,目前还不清楚作者对图书馆的目标和意图是什么。 特别是,他需要选择一个许可证,我们可能要考虑是否通过忠实地重新实现 Joda Time 或 JSR-310 来满足典型 JS 开发人员的需求。

+1 表示不变性,以及将产生的更明智的代码。

这将是一项重大的努力,因此最诚挚地感谢那些将(并且已经)实现它的人。

Moment 被广泛使用,我认为采用接近以下内容的方法是可行的,假设实现类似于@butterflyhug的https://github.com/WhoopInc/frozen-moment

3.x:不可变作为选项,默认为false,并在全局时刻导出上设置了一个标志,该标志将设置为true; console.warn 库加载(在开发模式下)
4.x: immutable 作为一个选项,默认为 true,flag 仍然可以设置为 false; console.warn 关于 5.x 的时间表(在开发模式下)
5.x:不可变是唯一的方式

在前面和中间有一个页面来描述长期不变性愿景——比如说,沿着我制定的 3.x/4.x/5.x 大纲每年发布一个主要版本——我认为它会给出一个合理的受影响的任何人更新其代码的金额。

一些观察:

  1. 我构建了WhoopInc/frozen-moment,希望这里的一群人可能愿意用它来构建东西,作为在不可变性会破坏插件等的时刻生态系统中找到问题点的一种方式。到目前为止,很少有人这样做,这让我不那么热衷于在冻结时刻和/或社区布道上工作,以帮助插件作者支持不变的时刻。

也就是说,我仍然愿意帮助修复人们在使用冻结时刻时发现的错误——无论这些错误是我的,还是在另一个从未考虑过时刻可能变得不可变的可能性的时刻插件中。 只需在冻结时刻提交问题,我就会看一看。 而且我仍然认为,围绕像冻结时刻这样的社区努力可以帮助我们理解和减轻向不变时刻过渡的痛苦。

  1. 如果 moment 在 3.0 中实现不变性,那么编写和维护包装器以在不可变 3.x API 之上实现可变 moment 2.x API 将非常简单。 从概念上讲,这实际上看起来很像冻结时刻:在任何地方,冻结时刻都隐式clone() s,这个可变性包装器会隐式地将其内部引用更改为新的不可变时刻值。 这也有助于简化在大型代码库中使用时刻的人们的过渡。
  2. 如果需要,我愿意帮助思考潜在问题并在第 3 时刻实现不变性。
  3. js-joda 将使用 BSD 许可证直接将 JSR-310 移植到 JavaScript 中。

@butterflyhug - 感谢您构建冻结时刻! 当我发现这一点时我很兴奋,但担心从我的项目中引入对冻结时刻的依赖,因为如果支持被放弃,从不可变时刻更改为可变时刻将是我代码中的一项重要工作。 如果您的目标是获得反馈并且您积极支持它,那么我会更愿意这样做。 :-)

我不知道有多少其他人可能有同样的思维过程。

我很好奇这里的人们是否认为出于性能原因应该结合打破对Date对象的依赖(甚至添加某种惰性求值)的概念来探索不变性。

我建议一步一步地做,@schmod。 这已经是一个(显然)大的变化

@RobertMcCarter 是的,我计划在可预见的未来支持冻结时刻,除非/直到在核心库中实现某些不变性选项——很大程度上是因为我个人将它用于我期望的一些事情维持一段时间。 也就是说,我的用例并不涵盖完整的 Moment 生态系统,所以我依靠其他人的反馈和使用来帮助确定什么是重要的。

+1 表示不变性,在第一次使用 endOf() 后花了半个小时弄清楚我的应用程序发生了什么。 假设我没有仔细阅读文档,我只是假设一个具有该名称的方法会返回 endOf 月份,并且使 moment 实例不受影响。 老实说这是一个惊喜,我觉得它不会以这种方式发生很荒谬,或者我的头脑太习惯了 - 在我看来 - 拥有 - 大部分 - 不可变的 API 的巨大好处

+1 不变性:)

我不在乎不变性,不要管这个优秀的库!

@es6Test你有什么理由不同意不变性? 除了抗拒改变之外,你还有什么具体的原因吗?

+1
我认为,如果它是不可变的,我的代码中充满了 .clone() 方法,并且有时可变性会导致很难找到错误,那么它会简化库的使用。

+1 请,更多的不变性^_^

@danpantry在制作了更多应用程序之后,我改变了主意。 我更喜欢不变性。

但是我仍然希望有一些可变的变量,我经常喜欢为时刻添加天数,我不想创建更多的变量来保存结果。

@es6Test如果你_真的_不喜欢它,就使用let吗?

let date = moment()
// with immutability
date = date.add(5)

我也赞成不变性。 这是 Python 的datetime库的一个特性,它真正让它大放异彩。 您可以随意抛出对象而无需担心修改它们,就像使用字符串一样。

在 React / Redux 世界中,不变性是非常重要的。 我已经遇到了一些棘手的问题,因为 moment 本质上并不是一成不变的。 性能在这里无关紧要,因为它可以被减轻。 Facebook 的 ImmutableJs 库证明可以在不牺牲性能的情况下实现不可变。

我也会一直在不变性上 +1。 在那之前,你不会从不变性中获得任何好处,所以半途而废是没有意义的。

现在比以往任何时候都更多地使用不变性和函数式编程(我喜欢它)。 我将提供自己的时间来为这项倡议做出贡献。 贡献者请与我联系,让我知道我可以如何提供帮助。

由于我们对此非常感兴趣,因此该团队希望对此进行更新。

目前,我们希望您知道我们已经听到了您的担忧。 整个维护团队都同意,如果我们从头开始编写库,我们会选择使其不可变。 然而,现在的情况并非如此。

就目前而言,我们每月有 400 万次 NPM 下载,还有无数用户依赖于通过其他渠道获取时刻。 所有这些用户的代码都希望 moment 像今天一样可变。 此外,我们有许多插件依赖于 moment 是可变的。

代码很容易编写,但作为一个在业余时间做这件事的小团队,很难满足这种变化的支持需求。 也就是说,我们愿意考虑。 我们想知道的是:

  • 为什么定格插件不能满足您的需求?
  • 你是否只需要一个不可变的 API(一个总是返回一个克隆的 API),或者我们出于某种原因需要在内部真正不改变对象?
  • 向当前代码添加第二个不可变的 API 会满足您的需求吗?

冻结时刻插件的问题在于它是选择加入,而不是选择退出。 每一个
moment() 需要一个 .freeze() 才能使其不可变。

不变性的全部意义在于,当我拥有对象 X 时,我知道
除非我明确地使它可变,否则它需要不受更改的影响。
任何允许我在内部更改对象的公共函数
显式可变性是有问题的。

我认为一种解决方法是做几件事:

  1. 向当前代码添加第二个不可变 API
  2. 有一个全局的 moment() 设置,如果它被设置为不可变模式,所有
    创建的 moment() 实例是不可变的,如果尝试进行可变调用,
    不会发生变异,而是使用适当的消息出错以使用不可变
    api 调用。

我认为这会让每个人都满意。 你怎么认为?

2016 年 5 月 23 日星期一下午 12:11,Maggie Pint通知@ github.com
写道:

团队想就此事提供最新消息,因为我们有很多
兴趣。

目前,我们希望您知道我们已经听到了您的担忧。 这
整个维护团队都同意我们是从
从根本上来说,我们会选择让它不可变。 然而,事实并非如此
就是现在。

目前,我们每月有 400 万次 NPM 下载,以及
不计其数的用户依赖于通过其他方式获得时刻
渠道。 所有这些用户的代码都希望 moment 是可变的
就是今天。 另外,我们有很多插件依赖于moment is
可变的。

代码很容易写,但是需要这种类型的支持
作为一个小团队,在我们的空闲时间里做这件事是很难接受的
时间。 也就是说,我们愿意考虑。 我们想知道的是:

  • 为什么定格插件不能满足您的需求?
  • 你是否只需要一个不可变的 API(一个总是返回一个
    克隆),或者我们是否出于某种原因需要在内部真正不改变
    对象?
  • 在当前代码中添加第二个不可变的 API 会满足您的需求吗?
    需要?

—
您收到此消息是因为您发表了评论。
直接回复此邮件或在 GitHub 上查看
https://github.com/moment/moment/issues/1754#issuecomment -221062274

来自 Gmail 帐户的 Eric Lau。

感谢您回复@maggiepint。

  1. 冻结时刻插件的一些问题:它不是默认的,它需要额外的工作才能使用,它被积极维护的可能性较小,我的假设是它对某些东西有性能影响(可能很小)构建为不可变的。 我认为最大的问题是忘记使用它,尤其是在有很多工程师的大型项目中。
  2. API 应该是无副作用的。 返回一个克隆是好的,但如果你还修改了原始对象,这将是一个副作用。 以下代码在声明结束时不应修改开始:
start = moment();
end = start.add(10, 'minutes');
  1. 你的意思是除了“add”之外还有“immutableAdd”之类的功能吗? 如果是这样,那么从技术上讲是的,特别是如果您创建一个包装器以使用使用相同名称的不可变函数:
import "moment/immutable";
start = moment();
end = start.add(10, 'minutes'); // immutable version of add

在我看来,这种方法对于那些想要使用不可变 API 的人来说是一种优雅的升级——现有代码仍然可以工作,没有人被迫进入,并且对于那些想要使用不可变 API 的人来说,需要的更改更少。

我们有意使代码副作用免费。 基本模式是挂钩到当前函数,让它们调用 clone,然后对该克隆进行操作。

不过,从技术上讲,如果您正在编写真正不可变的代码,则应使用不会更改的值来构造对象。 但是,这样做会增加进行此更改的难度。 调用 clone 然后做我们之前做的事情要容易得多。

我支持 Drew 的想法:

导入“时刻/不可变”;
开始=时刻();
end = start.add(10, '分钟'); // 不可变版本的 add

那会很棒,因为它是不可变的选择退出并且没有更改
函数名。

2016 年 5 月 23 日星期一下午 12:53,Maggie Pint通知@github.com
写道:

我们有意使代码副作用免费。 基础的
模式将是挂钩到当前函数,让它们调用克隆,
然后对该克隆进行操作。

但从技术上讲,如果您正在编写真正不可变的代码,对象
应该用不会改变的值来构造。 这样做会
虽然增加了进行此更改的难度。 更容易
调用 clone 然后做我们之前在做的事情。

—
您收到此消息是因为您发表了评论。
直接回复此邮件或在 GitHub 上查看
https://github.com/moment/moment/issues/1754#issuecomment -221076796

来自 Gmail 帐户的 Eric Lau。

@ericlau-solid

  1. 有一个全局的 moment() 设置,如果它被设置为不可变模式,所有
    创建的 moment() 实例是不可变的,如果尝试进行可变调用,
    不会发生变异,而是使用适当的消息出错以使用不可变
    api 调用。

如果页面上的 2 个库期望不同的时刻行为,这将中断。 例如,您使用 datepicker,它期望一个可变时刻,而您自己的代码期望一个不可变时刻——它不会工作。

拥有不可变的 api(ala 冻结时刻)

这也不是完美的,因为没有任何插件可以使用它。 实际上,没有办法引入不变性并保持所有现有插件的功能。 在有人花时间让它们工作之前,这些插件要么根本无法工作,要么无法在不变的时刻工作。

导入“时刻/不可变”;

所以这基本上是提出一个具有类似界面的不同库。 当然,它不会破坏代码,但它可能会引起混乱,并使某些项目安装了 2 个版本(例如 datepicker 将采用可变版本,而您的代码将采用不可变版本)。

最好的选择是创建一个类似于当前 API 的 API,但具有不可变对象。 叉子可能是有序的。 毕竟这就是自由软件的方式。

“添加第二个 API / 发布子模块”选项最适合兼容性,因为开发人员将能够逐步升级他们的代码。 如果可能的话, moment()的含义(通过 module-land 中的import moment; ,或浏览器构建中的moment全局)根本不应该改变,因为有一个大量遗留代码。

IMO,允许以下内容的解决方案将是理想的:

import moment from 'moment';
import {immutable as immoment} from 'moment';

var a = moment(); // mutable moment
var b = moment().immutable(); // immutable moment
var c = immoment(); // also an immutable moment; shorthand

不是一个不同的库,但是是的 - 有两种不同类型的时刻。 假设可变和不可变的 moment 对象在“幕后”共享大量代码,对于需要使用这两种语法的开发人员来说,库的大小不需要增加太多。 如果应用程序仅使用一种语法,则可以使用tree-shaking来生成优化的构建。

分叉该库可能会使大多数开发人员需要加载的代码大小增加一倍,这对于大多数 Web 应用程序来说是不可取的。


我不认为假设任何插件都可以“开箱即用”的不可变时刻工作是一个好主意,并且不会认为这是一个合理的要求。

我也不认为可变和不可变 API 相同是合理的要求/假设。 我们应该努力使升级路径尽可能顺畅,但不应该将自己锁定在任何笨拙的语法中。


我还认为关于“不可变”API 的外观有很多争论/争论的空间。 建造者模式? 构造函数实例化? 价值对象? 实现当前的 API,但是否有任何“setter”方法返回一个克隆?

我的一个要求是,新的 API 应该清楚明确地说明我们正在使用哪种时刻。 IMO, freeze未通过该测试,并且除了每次我通过时调用clone()之外几乎没有提供任何保护。

所以这基本上是提出一个具有类似界面的不同库。 当然,它不会破坏代码,但它可能会引起混乱,并使某些项目安装了 2 个版本(例如 datepicker 将采用可变版本,而您的代码将采用不可变版本)。

不是一个单独的库,只是一个围绕同一个库的纤薄包装器。 只需要安装一个版本的 moment。 您可以导入 'moment/immutable' 并且 datepicker 可以在同一个项目中导入 'moment' 没有问题。

如果插件没有更新以处理不可变的时刻,则与接收或返回时刻的插件接口可能会出现问题。

假设您将一个不可变的时刻传递给一个插件,该插件目前将其视为可变的。 在插件更新之前,需要有一种方法可以在给插件片刻之前转换为可变的。 理想情况下,在项目维护者有一些时间来支持不变性后,它会被弃用。 可能还需要一种检测不变性的方法(对于插件作者)。

另一方面,更新为使用不可变接口并向用户返回不可变时刻的库可能会抛弃那些不使用不变性并且不期待不可变时刻的人。 我相信这也可以通过不推荐使用的转换方法来处理。 插件应该返回与传入的相同类型的时刻。

@maggiepint是的,我理解你的意思,但我认为这可能是一个太大的飞跃。 就我个人而言,我会接受一种简单的clone隐藏在幕后的方法。 尤其是在解决问题的时候。

@tacomanator这也是我想做的。 重做整个事情以使其在内部真正不可变并不是非常站得住脚的。
我只是不知道人们是否出于某种原因想要这样做。

@schmod您能否简要说明为什么您认为freeze不是一个清晰明确的 API?

如果函数名称太可爱,我很乐意考虑将重命名 Frozen Moment 的方法作为该 repo 上的一个问题的论点。 另一方面,如果您认为不可能从可变转换为不可变(然后返回),那么我怀疑这可能会引发一些讨论——尽管请注意,我也愿意接受冻结时刻如果我们粗略地达成共识,那将是一个更好的 API。

@maggiepint我是否正确地将“向当前代码添加第二个不可变的 API”视为将 Frozen Moment 的一部分或全部与核心库捆绑在一起? 如果有帮助,我很乐意帮助将 Frozen Moment 或其一些想法迁移到 moment 组织中(作为官方插件或作为核心库的一部分),并帮助在新的上下文中维护它。

@butterflyhug你说的正是@ichernev 的想法。 他倾向于使用库附带的官方插件。 也许我们可以在某个时候在 Gitter 中协调这个? 我必须出差三天,所以我的时间有限。 其余的人应该在附近。

@蝴蝶拥抱

我认为freeze是一个很好的名字,在_every single_ 调用之后必须记住使用它真是太糟糕了。

我也认为import 'moment/immutable'有同样的问题,很容易忘记它。

老实说,您正在关注 semver,我认为正确的方法是让一切都使用不可变的_默认_并发布此更改作为它自己的主要版本,并计划在一段时间(12 个月?)后不再支持旧版本。 新功能/修复可以合并到不可变和可变的轨道中,并为使用可变版本的用户提供迁移计划。

在上述 12 个月之后,旧版本_仍然_工作,但他们不会收到任何 TLC。

当然,这是很多开销,但我认为正确地进行这种更改比为了向后兼容性而尝试捏造它会更好。 它还使更改变得更加复杂,这可能会打消核心开发团队的兴趣。 我不知道。

这种方法的另一个潜在缺点是用户从 CDN 使用此脚本而不指定显式版本(无论出于何种原因),如果发布向后兼容的 API,这可能会导致他们的站点中断。 我只能说“我早就告诉过你了”,但我知道这暂时可能不是一个可以接受的风险。

如果您依赖于一个全局对象,那么拥有同一个库的两个版本只会是一个问题,并且除了继续使用freeze()之外,并没有真正巧妙的方法来解决这个问题。


TLDR 听起来您试图解决的问题是由您已经在使用的 semver 解决的。 为什么不按照应该使用的方式使用它? 重大更改的主要版本。 唯一会中断的情况是当您依赖全局moment变量并使用 CDN 时,但我们在此处所做的任何更改无论如何都会中断

我也投票支持 semver 和 immutable 3.0 版本。

我不反对freeze名字,但我发现它本质上是有问题的,很难对片刻是否被冻结做出安全的假设。 ( Object.freeze()也有类似的问题)

一个不变的时刻不应该是不可冻结的,也不应该怀疑它是否被冻结。 这可以通过构建器模式的严格变体来实现(即,对象 _only_ 具有 setter,直到.build()被调用,之后它 _only_ 具有 getter),或者有一个时刻被“冻结”它被实例化,所有的 setter 都返回一个克隆。

例如:

/* BUILDER PATTERN */
var bldr = moment.immutable()
  .hours(5)
  .minutes(30)
  .seconds(25);

bldr.hours();  // throws exception.  builder has no getters

var time = bldr.build();

time.hours(); // 5
time.hours(6); // throws, OR returns a clone

/*  A more explicit variant:  */
var bldr = moment.immutable()
  .setHours(5);

bldr.getHours; // undefined

var time = bldr.build();
time.getHours(); // 5
time.setHours;   // undefined
/* VALUE OBJECT */
var time = moment.immutable()   // 00:00:00
  .hours(5)       // new object => 05:00:00
  .minutes(30)    // new object => 05:30:00
  .seconds(25);   // new object => 05:30:25

/*  Same thing, but more efficient:  */
var time2 = moment.immutable(5,30,25); // 05:30:25

time.hours();   // 5
time.hours(6);  // new object => 06:30:25

第一个例子非常像 java,但也没有关于矩是如何构造的,以及你可以用它们做什么。 它还为构建新时刻提供了一种高效且直观的途径,而且开销很小,并密切关注大多数人目前使用时刻的方式。

我赞成让 setter 在构建时刻抛出异常,因为这种行为会进一步减少歧义(以牺牲冗长为代价),并迫使开发人员在他们创造新时刻时承认。

第二个例子似乎有更多的开销,因为我们正在创建(和丢弃)很多对象。 但是,还有很大的优化空间。 现代 GC 相当不错; 懒惰的评估可能会有所帮助; 我们可以使 Moment 对象尽可能轻量级(去掉底层的Date )等(为了比较,这种模式与 JavaScript 中处理字符串的方式没有什么不同)


我是 semver 的粉丝,但是浏览器用例使破坏性更改成为问题,因为在没有模块加载器的情况下不可能在同一页面上拥有两个版本的 moment。

如前所述,有很多依赖于 Moment 的遗留代码。 至少,我们需要一个支持这两种模式的版本,以实现平稳的过渡期。

+1 表示返回新时刻的可组合方法。 我什至不在乎它们是否是不可变的。 只需给我组合,这样我就可以将方法链接在一起,或者在一行(而不是 2)中分配突变时刻。

@alexyuly为什么克隆不适合你? 如果要内联编写,可以执行以下操作:

var a = moment();
var b = a.clone().subtract(1, 'week').startOf('day');

管他呢。

@maggiepint好吧,我觉得很愚蠢,看起来您可以http://momentjs.com/docs/#/manipulating/start -of/ <- 没有提及任何返回值。

小更新 - 这篇博文是我此时对此问题的立场: https :

我不代表团队的其他人说话,但他们已经看到了,我通常会说我们都在一个相似的地方。

@maggiepint伟大且非常有效的观点

谢谢你。 不变性是 redux 中的王者,所以我肯定会切换到 js-joda。 我可能仍然保持相对时间的时刻。

Eric Lau - Gmail

2016 年 6 月 24 日星期五上午 11:12 -0700,“Maggie Pint”通知@github.com 写道:

小更新 - 这篇博文是我此时对此问题的立场: https :

我不代表团队的其他人说话,但他们已经看到了,我通常会说我们都在一个相似的地方。

—
你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看,或将线程静音。

由突变引起的一些更典型的错误/混淆: http :

+1 对此但实际上对于 Redux 我发现这是一个令人惊讶的小问题,因为我根本没有将 Moment 实例放入 Redux 状态对象中。 只有日期字符串或时间戳,我在渲染方法中使用 Moment 只是为了将日期字符串转换为其他字符串。

我这样做主要是因为这样很容易序列化和反序列化状态而不需要任何额外的工作,但它也很好地隐藏了 React 和 Redux 的突变。

尽我所能在本周末为您提供有关此的更新。 我们有一个计划,但在我承诺之前,我需要确保这一切都会成功。 不幸的是,我下周要从明尼阿波利斯搬到西雅图,所以这可能会放慢一段时间,但我想继续前进。

小更新,为更大的更新做准备,因为我注意到那篇博文仍然有一些流量。 我目前正在重新构建 moment 以使用 Babel。 这让我们将@butterflyhug的插件引入核心存储库,并使其得到官方支持。 在此过程中,我们将清理有关插件的一些内容。 @butterflyhug非常慷慨地为我们提供了大量帮助。 此外,我正在与 LoDash 的 John-David Dalton(我们都在 Microsoft 工作)会面,讨论引入官方插件时的策略,因为他已经使用 LoDash 功能实现了这一点。 长篇博文和 RFC 即将发布。

@maggiepint在正式支持冻结时刻的同时,您是否打算解决https://github.com/WhoopInc/frozen-moment/issues/20?

我认为只要每次调用moment()默认返回一个冻结时刻,这种不变性的方法就足够了,并且开发人员不必记住每次调用.freeze() 。

@gabrielmaldi好问题。 我正在编写 RFC(现在应该在任何一天完成),是的,我的明确目标是为不可变用途提供更好的故事。 我的建议与我对 WhoopInc/frozen-moment#20 的评论一致,并考虑了静态方法。 当我针对 RFC 存储库打开 PR 时,我将在此处和该问题中发布指向我的 RFC 的链接,我们当然欢迎届时社区对该提案的反馈。

我已经写完了 RFC 草案并打开了 PR 。

这是一个相当长的文档,部分原因是关于我们希望如何向用户提供此功能还有一些悬而未决的问题,因此我试图强调各种方法的优缺点。 但我认为实现的想法非常扎实,所以如果您对提议的面向用户的 API有保留,我很想听听这里的人的意见。

RFC 的执行摘要以及我们要回答的主要问题: https :

@maggiepint我试图对那篇文章发表评论,但由于某种原因它被吞了。 这是我写的:


我对这些变化最大的担忧是,它们回应了社区中不成比例的声音和表达的部分——避免了普通开发人员的安静堡垒,他们甚至不知道正在进行的讨论。

GitHub 的线程并不是更广泛的开发社区的缩影。 该站点与网络上的许多论坛一样存在参与偏见,并且偏向于某种开发人员:在智力上从事计算理论; 对想法而不是应用感兴趣; 我什至敢说社会倾向于围绕流行趋势团结起来。 这些开发人员自然会被诸如不变性和函数式编程之类的哲学事业所吸引,并且比任何其他群体都更接近您。 他们将是房间里最响亮的声音,他们会为这种改变而大声疾呼——但更广阔的世界呢?

更广阔的世界需要有效的东西。 它想知道它当前使用的库将继续接收更新 - 至少是错误修正,但理想情况下是小的增量改进,使他们的生活更美好。 但它没有这么说,因为它没有主动去寻找这些论坛,而且因为它需要厚脸皮来反对潮流。

如果你打算进行这种改变,我认为你需要向这些人说明为什么这种改变会让他们的生活变得更好; 为什么他们应该升级; 为什么他们不必担心他们的遗留项目不会收到间接错误修正; 基本上 - 他们现在可以做而他们目前不能做的事情。

我还认为你也应该非常小心地验证这种变化的实际重要性。 我想知道您是否应该将此更改作为插件或包装器发布,并在将其合并到主干之前的几个月内仔细监控其使用情况。 如果不变性被过度代表为一个利基问题,您将找到一种方法来满足这些声音用户,而无需改变整个库的进程。

作为第一次使用 startOf endOf 一段时间后,我在这里结束了。 这些方法令人惊讶地变异!

+1 完全不变性

也许另一个解决方案是让momentjs 中的对象是可变的。 文档在克隆部分确实提到了它,但它不够突出。

另一个解决方案,如果您想要可变性,请使用面向对象的概念并使用NEW关键字与 moment() 工厂模式创建对象。

更广阔的世界需要有效的东西。
...你需要向这些人说明为什么这种改变会让他们的生活变得更好

我目睹的每个人都落入了“时刻”变异的陷阱。
即使在使用 moment 3 年后,我仍然必须告诉自己“哦,该死,您在这里使用 .startOf .. 如果您需要副本,最好检查两次”。

当前的行为并不直观,而且早就应该这样做了。
尝试使 array.filter/map 发生变化,看看这有多有趣。

关于...
性能/内存:我从来没有链接过超过 2 个函数,通常是.set().get()
伪不变性:在 java-pass-by-ref-gen 出来之前,需要很多很多代。

我喜欢@AdamHess的关于选择您是在寻找 OOP 还是不变性的想法。

关于我们为什么会感到困惑的两分钱是:返回值。 如果moment.add('1', 'days')返回 undefined,就像 JS 中的可变函数通常那样,那么混乱就会消失。 返回相同的对象,至少对我来说,意味着我有一个新副本。 当然,这会破坏可链接性。

我认为更多开发人员遇到内存使用问题的可能性很小(特殊用例除外)。 不过,Moment 的可变性已经让我感到不安。 日期应被视为字符串或数字之类的值。

+1 默认情况下不可变

顺便说一下,PHP 也存在同样的问题。 你想和PHP一样吗?! 😆

在 PHP 中,他们通过提供DateTimeImmutable (除了正常的DateTime )来解决它。

如果我们不将默认行为更改为不可变,我们至少应该考虑使用一流的替代 API,如imoment / momenti (或其他)。 我实际上总是使用它(通过可变 API),我希望我使用的任何其他库也将使用不可变版本/API。

我也投票支持不变性,如果这是计划,我愿意帮助实施。 在进行加法和减法时,我已经遇到了可变性问题,甚至更令人困惑的是,这些方法确实由于链接而返回了实例。

顺便说一下,克隆持续时间如何? 这样做的最佳方法是什么,找不到任何不觉得hacky的方法。

@ngerritsen我相信最好的选择是moment.duration(existingDuration) 。

回复:实现,#3548 仍然是一个活跃的 PR。 希望没有多少代码级的工作了,但是有更多的眼睛来验证大的变化永远不会有什么坏处。 我们还需要在文档等方面进行工作,然后才能进行主要版本提升,这对于发布这样的更改是必要的。 如果您愿意为这份清单中的任何一项提供帮助,我相信我们将不胜感激。 😀

刚刚花了一个小时试图找出一个由 .startOf() 改变原始日期引起的微妙错误......感谢您的辛勤工作,momentjs 在展示如何为 JS 构建出色的日期库方面做得很好,但我正在切换到目前为止,fns 是因为这种错误的非常微妙的性质,并且因为在我介绍了一些通用的 FP 概念之后(主要归功于 React、Redux 和 ELM),我开始欣赏不变性的一般好处。

就其价值而言,lodash 已经在 lodash/fp 中采用了更 FP 的方法。 我建议看看 lodash/fp 的实现方式,因为它们包装了现有的函数,而不必完全重写所有内容。 Lodash 的家伙们也非常非常关心性能。

我也同意@mull ,真正的问题在于我的链式 API,IMO 不仅在这种情况下,而且在更普遍的情况下(例如 jQuery)都有一些很大的设计缺陷。 如果方法变异日期会返回未定义,我会更好(至少这是我应用于我编写的代码的一般规则)

虽然 moment.frozen 命名空间正在开发中,但我建议 - 正如之前的海报所建议的那样 - 只使用date-fns 。

由于可变时刻,刚刚修复了另一个错误 🎉

切线相关,如果 3.0 可以转移到 ES6 类,那就太棒了:

let mom1 = new Moment();
let mom2 = Moment.parse('2019-03-01T14:55');
// etc

这样的举动也可以指导不变性讨论。 我会说所有方法都应该是不可变的,只有一个例外。 一个名为.set('minute/hour/year/etc', 18) 。

我刚开始使用 Moment,并且感到_震惊_这个库从一开始就不是完全不可变的。 我正在使用时刻不可变的,直到这个问题得到解决。

@alancnet时刻可能永远不会改变。 这是一个太大的变化,并且已经学会接受当前行为的用户有足够的反击。

查看他们的新项目: luxon
它非常好,现代,不可变(!),并且应该比将所有内容包装在.clone()调用中的 moment-immutable 表现得更好。

对于那些想从 momentjs 过渡到现代方法的人,请查看https://github.com/you-dont-need/You-Dont-Need-Momentjs

很惊讶它是可变的!

luxon距离替代品

@alamothe这个问题在网站上有明确的回答: https : //moment.github.io/luxon/docs/manual/moment.html

关闭这个。 正如其他人所指出的,如果您想要一个几乎不可变的 API,请使用 Luxon。 谢谢。

此页面是否有帮助?
0 / 5 - 0 等级