【问题标题】:Most elegant way to initialise missing members of an object, whilst creating reference to new member初始化对象缺失成员的最优雅方式,同时创建对新成员的引用
【发布时间】:2018-03-07 19:39:07
【问题描述】:

请提供最优雅的解决方案。我们都处理可以在其整个生命周期中增量构建的对象。传递对象引用是处理对象最有用的方法,因为突变会影响原始对象而不仅仅是副本。

例如:

const bigObject = fetchBigObjectFromStorage()
let myMem = bigObject.member || {}

如果member 存在于bigObject 中,myMem 将获得对其的引用。如果不是,它只会接收对新空对象的引用。所以这会产生不可预知的结果:

myMem.value = 30

没有错误。 myMem.value 现在是 30。但是值最终达到了我们真正想要的位置吗?

console.log(bigObject.member.value)

这要么显示 30,要么给出 TypeError,因为 bigObject.member 未定义,取决于 bigObject.member 是否已经存在。

为避免错误,我们可以将新值放回bigObject,每次都将其放回那里,因此我们需要显式分配它:

bigObject.member = myMem

这是乱七八糟的。

所以我们可以预先排除bigObject.member 不存在的可能性,对其进行测试,并在我们开始尝试传递对它的引用之前插入它,甚至使用空值初始化所需的成员。所以我们有这个代码:

const bigObject = fetchBigObjectFromStorage()
if (!bigObject.hasOwnProperty("member")) bigObject.member = {value: null}
let myMem = bigObject.member
myMem.value = 30
console.log(bigObject.member.value)

...我们每次都不会出现错误,显示 30 个并且行为可靠(嗯,几乎...在if 中进行更多测试可能会使其无懈可击)。但这就像罪恶一样丑陋,对于如此微不足道的事情来说代码太多了。我们可以像 if 语句那样将它的一部分重构为一个小函数,使其成为通用成员初始化器,以及其他一些方法。我敢肯定,多年来,我们大多数人都尝试了很多技术。但是,从聚集的博学人群中,最优雅、最受青睐的解决方案是什么?有什么巧妙的小技巧吗?

【问题讨论】:

    标签: javascript ecmascript-6 javascript-objects lazy-initialization


    【解决方案1】:

    您可以使用条件运算符缩短 if 语句:

    const myMem = bigObject.hasOwnProperty("member") 
      ? bigObject.member
      : (bigObject.member = {value: null});
    

    如果真实性检查对您来说已经足够了,那么您可以使用短路逻辑运算符进一步简化:

    const myMem = bigObject.member || (bigObject.member = {value: null});
    

    可能是最优雅的方式是 ES6 解构使用默认初始化程序,它隐式测试 undefined

    const { member: myMem = (bigObject.member = {value: null}) } = bigObject;
    

    【讨论】:

    • @h1b9b:不,该值将在以下语句中的myMem.value = 30; 中设置。 30 不会是默认值的一部分(实际上,null 也不是必需的,空的 {} 对象字面量也可以)。
    • 第一个建议,使用条件运算符缩短,绝对是我做过的事情。如果我完全控制bigObject,我会在代码中接受第二个(短路逻辑),并且可以确定bigObject.member 要么是对象要么不存在,否则我想引入另一个测试或二。我真的很喜欢第三个选项,使用默认初始化程序的 ES6 解构,但我发现它是最不可读和最难以理解的。它会让我回答非 ES6 团队成员提出的无穷无尽的问题。不过绝对优雅。
    • @jonsilver 是的,“确保它要么是对象要么不存在”就是我所说的“如果完整性检查足够”的意思。通常你会知道你正在使用什么对象,所以这个解决方案很常见。
    • 确实,我们似乎一直都在相互同意。虽然您可能会享受知道自己正在使用哪些对象的奢侈,但在处理由多个程序员编写的旧代码库时,这可能会更加不可预测,每个程序员都遵循自己的可变质量路径。
    【解决方案2】:

    怎么样

    const bigObject = {...defaults, ...fetchBigObjectFromStorage()}
    

    defaults 类似于:

    {member: {value: null} etc}
    

    这样,您的对象始终具有所有预期的字段。

    【讨论】:

    • 一个很好的使用对象扩展运算符。我喜欢这个,因为它在保持可读性的同时非常缩写。一种非常优雅的技术,类似于在 React 中经常使用的 setState。然而,它确实假定我们事先知道bigObject 的成员将是什么。如果它们是依赖于主题上下文的键控对象,那么这种技术是不可能的。
    • @jonsilver:如果您的对象是键值对的任意集合,我会使用 Map 来代替,它是专为此类事情设计的。
    • 虽然我完全同意您关于 Map 优于 Object 对这个用例的适用性,但我担心回到现实世界中,这种转换本身可能是一个完整的项目,因为一个的代码语法不是另一个的替代品。存储为 Object 目标 JSON 键值对的数据也未针对从 Map 加载或保存进行优化。但这对于完全不同的线程来说是一个很好的主题 - 有用、优雅的技术,用于将旧代码库和数据存储转换为现代、完全优化的 JavaScript 应用程序。
    • @jonsilver:FWIW,对象传播不是 ES6 (ES2015) 的一部分。它将成为今年发布的一部分。另外,... is not an operator!
    • 确实,Felix,我相信 React 中对象传播的可用性仅取决于 Babel 支持它并将其转换为当前的 ES6 实现和该语言的先前版本。当然,我无法在我的大部分 JavaScript 工作中使用它,这些工作是非转译的,并且基于 8.x 之前的 NodeJS LTS 版本,不支持对象传播/休息。
    【解决方案3】:
    let myMem = bigObject.member = bigObject.member || {};
    myMem.value = 30;
    console.log(bigObject.member.value);  // outputs 30
    

    我认为这可能是诀窍之一

    【讨论】:

    • 这个比较简洁,但是给自己分配一个对象......我对Javascript引擎的内部了解不够,不知道如何在内存中内部处理,但看起来很浪费。尤其是大型物体还有其他人愿意发表评论吗?
    • 在这里,如果你仔细观察,我将对象(如果 member 是一个对象)分配给自身,因此它指向相同的内存位置。它不会复制内容。所以无论是大物体还是小物体,时间都是一样的。如果member 对象的内存已经分配,​​它将不会再次分配,除非我们使用对象文字{} 或分配其他一些object
    • 感谢您的澄清。是的,这很有意义,因为将对象变量分配给自身只是设置对同一引用的引用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-20
    • 1970-01-01
    • 1970-01-01
    • 2014-10-13
    • 2017-01-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多