资讯专栏INFORMATION COLUMN

在javascript中安全地访问深层嵌套的值

zacklee / 3370人阅读

摘要:介绍这是一篇短文,旨在展示多种在中安全地访问深层嵌套值的方式。所以每次我们想要访问深度嵌套的数据时,都必须明确地进行手动检查。我们还触及了,可以更新深度嵌套数据而不会改变对象。

介绍

这是一篇短文,旨在展示多种在javascript中安全地访问深层嵌套值的方式。
下面的例子通过不同的方式来解决这一问题。

开始之前,让我们看下实际遇到这种状况时..

假设有一个props对象(如下),当我们需要获取user对象的posts的第一条的comments对象,通常会如何操作?

const props = {
  user: {
    posts: [
      { title: "Foo", comments: [ "Good one!", "Interesting..." ] },
      { title: "Bar", comments: [ "Ok" ] },
      { title: "Baz", comments: [] },
    ]
  }
}
// access deeply nested values...
props.user &&
props.user.posts &&
props.user.posts[0] &&
props.user.posts[0].comments

最直白的方式是确保每一个key或index的存在再去访问它的下一级。考虑的多点,当需求变化需要去请求第一条comments时,这个式子会变得越来越长。

// updating the previous example...
props.user &&
props.user.posts &&
props.user.posts[0] &&
props.user.posts[0].comments &&
props.user.posts[0].comments[0]

所以每次我们想要访问深度嵌套的数据时,都必须明确地进行手动检查。或许很难说清这一点,试想一下当我们不希望检验users对象下的posts,只是希望获取到users下的最后一条comment,和前面的解决思路是相违背的。

这个例子可能有些夸张,但你懂我的意思,为了得到深层嵌套的值,我们需要检验整个结构的每一级(所有父级)。

所以,现在我们已经更好地理解了实际想要解决的问题,让我们来看看不同的解决方案。前面一些是通过javascript,再后面通过Ramda,再再后面是Ramda和Folktale。将通过一些比较有趣并且不算高级的例子来说明,希望大家在本次专题里有所收益。

JavaScript

首先,我们不希望手动检验每一级是否为空或是未定义,我们希望有一种精简且灵活的方式来应对各种数据源。

const get = (p, o) =>
  p.reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, o)
// let"s pass in our props object...
console.log(get(["user", "posts", 0, "comments"], props))
// [ "Good one!", "Interesting..." ]
console.log(get(["user", "post", 0, "comments"], props))
// null

看一下get这个方法

const get = (p, o) =>
  p.reduce((xs, x) =>
    (xs && xs[x]) ? xs[x] : null, o)

我们传入路径(path)作为第一个参数,需要获取的对象(object)作为第二个参数。
思考一下这第二个参数o(object),你可能会问自己:我们期望这个方法有什么功能?应该是一个输入特定路径并且针对任何对象都能返回是否存在预期对象的方法。

const get = p => o =>
  p.reduce((xs, x) =>
    (xs && xs[x]) ? xs[x] : null, o)

const getUserComments = get(["user", "posts", 0, "comments"])

通过这种方式,我们可以调用getUserComments和之前的props对象或是任何其他对象。这也暗示我们必须得像这样不停琢磨这个get函数,

最终我们能打印出结果,验证下是否如预期得结果。

console.log(getUserComments(props))
// [ "Good one!", "Interesting..." ]
console.log(getUserComments({user:{posts: []}}))
// null

get函数实质上就是在减少先前的路径。

让我们来简化一下,现在我们只想访问这个id。

["id"].reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, {id: 10})

我们用提供的对象初始化reduce函数,每一层通过(xs && xs[x]) 检验对象是否被定义且有效, 然后依次递归或是返回null退出。
就像上面的例子一样,我们可以轻巧地解决这一问题。当然如果你偏向习惯用字符串路径而不是数组来表达路径,还需要对get函数做一些小改动,我将留给感兴趣的读者来实现。

Ramda

我们也可以利用Ramda函数库来实现相同的功能,而不是编写自己的函数。
Ramda提供了一个path方法,两个参数输入, path以及object。让我们用Ramda重写这个例子。

const getUserComments = R.path(["user", "posts", 0, "comments"])

现在通过getUserComments传入数据源就能得到我们希望的值,如果没有找到就会得到null

getUserComments(props) // [ "Good one!", "Interesting..." ]
getUserComments({}) // null

但是如果我们想要返回的无效值不是null呢?Ramda提供了pathOrpathOr需要传入默认值作为参数。

const getUserComments = R.pathOr([], ["user", "posts", 0, "comments"])
getUserComments(props) // [ "Good one!", "Interesting..." ]
getUserComments({}) // []

感谢Gleb Bahmutov提供对于path和pathOr的见解。

Ramda + Folktale

让我们再加入FolktaleMaybe。例如我们可以构建一个更通用的getPath函数(同样传入path和object)。

const getPath = R.compose(Maybe.fromNullable, R.path)
const userComments =
  getPath(["user", "posts", 0, "comments"], props)

调用getPath会返回Maybe.Just或是Maybe.Nothing

console.log(userComments) // Just([ "Good one!", "Interesting..." ])

将我们的返回结果包在Maybe中有什么用呢?通过采用这种方式,我们可以安全地使用userComments,无需手动检验userComments是否返回nul。

console.log(userComments.map(x => x.join(",")))
// Just("Good one!,Interesting...")

没有任何值时也是如此。

const userComments =
    getPath(["user", "posts", 8, "title"], props)

console.log(userComments.map(x => x.join(",")).toString())
// Nothing

我们可以把所有属性包裹在Maybe内。这使我们能够使用composeK来实现链式调用。

// example using composeK to access a deeply nested value.
const getProp = R.curry((name, obj) =>
  Maybe.fromNullable(R.prop(name, obj)))
const findUserComments = R.composeK(
  getProp("comments"),
  getProp(0),
  getProp("posts"),
  getProp("user")
)
console.log(findUserComments(props).toString())
// Just([ "Good one!", "Interesting..." ])
console.log(findUserComments({}).toString())
// Nothing

这种方式是非常前卫的,使用Ramda地path方法其实就足够了。不过让我简单看下下面这个例子(通过Ramda地composechain实现同样的效果)

// using compose and chain
const getProp = R.curry((name, obj) =>
  Maybe.fromNullable(R.prop(name, obj)))
const findUserComments =
  R.compose(
    R.chain(getProp("comments")),
    R.chain(getProp(0)),
    R.chain(getProp("posts")),
    getProp("user")
  )
console.log(findUserComments(props).toString())
// Just([ "Good one!", "Interesting..." ])
console.log(findUserComments({}).toString())
// Nothing

通过pipeK也能实现同样的效果。

// example using pipeK to access a deeply nested value.
const getProp = R.curry((name, obj) =>
  Maybe.fromNullable(R.prop(name, obj)))
const findUserComments = R.pipeK(
  getProp("user"),
  getProp("posts"),
  getProp(0),
  getProp("comments")
)
console.log(findUserComments(props).toString())
// Just([ "Good one!", "Interesting..." ])
console.log(findUserComments({}).toString())
// Nothing

还可以用map配合pipeK。感谢Tom Harding提供pipeK的例子。

Lenses

最后,我们还可以使用LensesRamda就带有lensProplensPath

// lenses
const findUserComments =
  R.lensPath(["user", "posts", 0, "comments"])
console.log(R.view(findUserComments, props))
// [ "Good one!", "Interesting..." ]
总结

我们应该对如何检索嵌套数据的多种方法有了清楚的理解。除了知道如何自己实现外,还应该对Ramda提供的关于这个问题的功能有一个基本的了解。甚至可以更好地i理解为什么将结果包含Either或Maybe中。我们还触及了Lenses,可以更新深度嵌套数据而不会改变对象。
最后,你再也不会去编写下面这样的代码了。

// updating the previous example...
props.user &&
props.user.posts &&
props.user.posts[0] &&
props.user.posts[0].comments &&
props.user.posts[0].comments[0]

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107638.html

相关文章

  • 【译】教你如何避开「Cannot read property of undefined」

    摘要:比如的,的提供访问安全对象的支持。在使用的情况下,这意味着表达式在达到其第一个假值后将停止向后执行。这便可用于安全地访问嵌套属性。与上述短路示例类似,此方法通过检查值是否为假来进行操作。该方法优于该方法的地方是避免属性名称的重复。 Uncaught TypeError: Cannot read property foo of undefined.这种错误想必在我们日常开发中都到过,这有...

    xiaoqibTn 评论0 收藏0
  • 如何优雅安全深层数据结构取值

    摘要:如果这个结构非常复杂,那么想要安全优雅地取出一个值,也并非简单。这是为了在对象中相关取值的过程,需要验证每一个和的存在性。并且这个数据结构必然是动态生成的,存在有时有时的情况。在测试过程中,很难复现。 古有赵子龙面对冲锋之势,有进无退,陷阵之志,有死无生的局面,能万军丛中取敌将首级。在我们的Javascript中,往往用对象(Object)来存储一个数据结构。如果这个结构非常复杂,那么...

    RobinQu 评论0 收藏0
  • 如何优雅安全深层数据结构取值

    摘要:如果这个结构非常复杂,那么想要安全优雅地取出一个值,也并非简单。这是为了在对象中相关取值的过程,需要验证每一个和的存在性。并且这个数据结构必然是动态生成的,存在有时有时的情况。在测试过程中,很难复现。 古有赵子龙面对冲锋之势,有进无退,陷阵之志,有死无生的局面,能万军丛中取敌将首级。在我们的Javascript中,往往用对象(Object)来存储一个数据结构。如果这个结构非常复杂,那么...

    liaorio 评论0 收藏0
  • 前端数据扁平化与持久化

    摘要:与持久化工程师花了年时间打造,与同期出现。有持久化数据结构,如等,并发安全。总结篇幅有限,时间也比较晚了,关于前端数据的扁平化与持久化处理先讲这么多了,有兴趣的同学可以关注下,后面有时间会多整理分享。 (PS: 时间就像海绵里的水,挤到没法挤,只能挤挤睡眠时间了~ 知识点还是需要整理的,付出总会有收获,tired but fulfilled~) 前言 最近业务开发,从零搭建网页生成器,...

    dreamtecher 评论0 收藏0

发表评论

0条评论

zacklee

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<