古有赵子龙面对“冲锋之势,有进无退,陷阵之志,有死无生”的局面,能万军丛中取敌将首级。
在我们的Javascript中,往往用对象(Object)来存储一个数据结构。如果这个结构非常复杂,那么想要安全优雅地取出一个值,也并非简单。

这篇文章将会详细阐述在一个嵌套较深的场景中,如何安全的完成读写操作。先后会尝试多种方法,希望对读者有所启发。

本文示例借鉴A.Sharif的最新文章:Safely Accessing Deeply Nested Values In JavaScript,喜欢看英文原版的同学可以直接戳链接。

场景介绍

在React开发中,我们根据数据来渲染视图。经常会出现类似下面这种情况:

const props = {user: {posts: [{ title: 'Foo', comments: [ 'Good one!', 'Interesting...' ] },{ title: 'Bar', comments: [ 'Ok' ] },{ title: 'Baz', comments: []}],comments: [...]}
}复制代码

这是一个典型的获取用户评论信息并加以展示的场景。其实,这还嵌套的不够深,试想一个回复存在多层:回复的回复,回复的回复的回复。。。

姑且先看我们的示例吧,此时我们想获取第一个post的评论信息。用传统的javascript方法应该这么做:

props.user &&
props.user.posts &&
props.user.posts[0] &&
props.user.posts[0].comments复制代码

也许经验丰富的javascript开发者会明白使用这么多&&的意义。这是为了在对象中相关取值的过程,需要验证每一个key和index的存在性。否则会有报错,这将会是致命性的。并且props这个数据结构必然是动态生成的,存在有时valid有时invalid的情况。在测试过程中,很难复现。

同样的尴尬场景比比皆是,想象一下,如果我们需要获取一名用户最后一个评论博客的题目,就需要:

props.user &&
props.user.comments &&
props.user.comments[0] &&
props.user.comments[0].blog.title复制代码

这些例子夸张吗?其实不然。我们明白了,想要获取一个数据值,需要一层一层遍历属性的存在性。这无疑是繁琐的。

解决方案

现在明白了我们面临的困扰,接下来我会用几种方法:

  • 纯JavaScript方法;
  • 最具有函数式代表的JavaScript库-Ramda,辅以柯粒化(currying)等思想和方案解决问题。

JavaScript方案

先直接上代码:

const get = (p, o) => p.reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, o)console.log(get(['user', 'posts', 0, 'comments'], props)) // [ 'Good one!', 'Interesting...' ]
console.log(get(['user', 'post', 0, 'comments'], props)) // null复制代码

注意这里我使用了一个ES5中,比较偏向函数式思想的reduce方法。关于这个方法,我想很多人其实还并不理解,建议先去进行学习,或者参考我之前的一篇文章。

同时,我尝试获取:user->posts[0]->comments,
并配以一个反例:user->post[0]->comments;
当然,在反例中,post数组并不存在。

我们来分析一下代码。

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

我们实现的get方法中,接收两个参数,第一个p表示获取值的路径(path);另外一个参数表示目标对象。

同样,为了设计上的更加灵活和抽象。我们可以柯粒化我们的方法:

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

这样的话,就可以这个姿势调用:

const getUserComments = get(['user', 'posts', 0, 'comments'])
console.log(getUserComments(props))
// [ 'Good one!', 'Interesting...' ]
console.log(getUserComments({user:{posts: []}}))
// null复制代码

如果关于get方法中reduce的使用还不清楚,那就再看一个简单的例子:

['id'].reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, {id: 10})
// 返回10复制代码

Ramda方案

如果不自己手动设计上述方法的话,我们可以使用Ramda函数式类库完成:

const getUserComments = R.path(['user', 'posts', 0, 'comments'])复制代码

接下来调用需要这个姿势:

getUserComments(props) // [ 'Good one!', 'Interesting...' ]
getUserComments({}) // null复制代码

如果我们想在指定路径下未找到一个值时,不返回null,而是返回自定义的内容呢?我们可以使用pathOr方法,第一个参数用来设置默认输出。

const getUserComments = R.pathOr([], ['user', 'posts', 0, 'comments'])
getUserComments(props) // [ 'Good one!', 'Interesting...' ]
getUserComments({}) // []复制代码

总结

这篇文章翻译自A.Sharif的最新文章:Safely Accessing Deeply Nested Values In JavaScript,其中后半部分未做翻译。
后半部分其实分析了 Ramda+Folktale的实现,以及Ramda+Lenses的实现。

Folktale和Lenses是非常函数式Functional Programming的思想,理解起来相对晦涩且比较小众。有兴趣的读者可以点击原文去自行了解。

Happy Coding!

如果你对函数式编程并不感冒,大可只学习第一部分的实现。对于函数式编程有兴趣的同学,希望这篇文章能够抛砖引玉,欢迎与我交流。

PS: 作者Github仓库,欢迎通过代码各种形式交流。

如何优雅安全地在深层数据结构中取值相关推荐

  1. Js 中对 Json 数据的取值设值方式 ( 如何取 key 中含 . 的值 )

    JSON 是轻量级的文本数据交换格式,独立于语言,JSON 比 XML 更小.更快,更易解析.熟练运用Json是程序猿内需! var person = {id:001,name:"MoTec ...

  2. 整型int数据的取值范围是怎么来的?

    引言 大家都知道占2个字节的int类型,取值范围是-128~127:那么这个-128究竟是怎么来的呢? 正文 以java语言中的byte类型为例,byte占用1个字节byte,共8个bit:也就是8个 ...

  3. dataframe输出某列的数据以及统计某列的取值种数+输出某行数据

    输出某列的数据: train.ix[:,'bank_type'] 统计某列数据的取值分布: train['author'].value_counts(normalize = True, dropna ...

  4. 取值方法_「EV3进阶课」制作小游戏:数据取值体系要统一(三)

    不要着急,这部分教学内容要一步步来,如果我堆出一大堆文字,反而不利于大家接收,到时候又变成"照抄"了. 为了大家更方便吸收,后面的课程内容,我将会把发课内容减少,发课频率提高一点. ...

  5. C语言整数的取值范围

    文章目录 1.整数的取值范围 2.获取视频教程 3.版权声明 整数是我们生活中常用的数据类型,也是编程中常用的一种数据,C语言使用int关键字来定义整数变量(int是 integer 的简写). 在定 ...

  6. C语言字符型变量的存储和取值

    目录 1.问题引入 2.字符型变量的存储 3.字符型变量的取值 4.字符型变量中的-128 5.取值转换图 1.问题引入 我们知道,在C语言中,一个字符型数据占8个bit位,那么当我们定义一个字符型数 ...

  7. Int类型变量的取值范围为何是2的31次方?

    Int类型变量的取值范围解释 Int类型数据的取值范围为:-2147483648 ~2147483647,即 - 231 ~ 231 -1. 首先:在C++中一个Int类型变量占4个字节,即32位,而 ...

  8. 自学笔记十四:Matlab浮点型:创建和转换、取值范围、运算和精度问题

    1.浮点型的概念 浮点型分为单精度(single)浮点型和双精度(double)浮点型: IEEE浮点数算术标准(IEEE 754)是IEEE二进位浮点数算术标准(IEEE Standard for ...

  9. C语言中不同类型的取值范围

    C语言中的不同类型的取值范围 前言 C语言中,我们知道最基本的数据类型有int整型,float和double浮点型,char字符型,在计算机里,他们的取值并不是任意和无限的,都有相应的取值范围,那么计 ...

最新文章

  1. 七天学会ASP.NET MVC (一)——深入理解ASP.NET MVC
  2. 常考数据结构与算法:二叉树的镜像
  3. mysql建表以及列属性
  4. flutter制作具有自定义导航栏的渐进式 Web 应用程序
  5. SSM错误:Exception in thread “main“ java.lang.NoClassDefFoundError: javax/servlet/http/HttpServletReque
  6. 【云周刊】第173期:直击数博|阿里胡晓明:用100亿的投入撬动1000亿的脱贫效应...
  7. 蓝桥杯 ADV-194算法提高 盾神与积木游戏(贪心)
  8. python朴素贝叶斯分类的手写数字识别_关于利用机器学习进行手写数字的的识别...
  9. Prototype使用$A()函数
  10. Aurora-------在 MSOffice 内输入 LaTeX 公式的很好用插件
  11. 无线摄像头如何连接服务器,网络摄像头怎样连接到云服务器
  12. 1u服务器电源制作,1U服务器电源也可以做机箱电源
  13. 基于STM32MINI板步进电机程序(有代码)
  14. Java字符串拼接的优雅方式
  15. 云服务器虚拟手机版,云服务器虚拟手机
  16. c 是高级程序设计语言吗,C语言是一种高级程序设计语言。
  17. html做一个聊天输入框,js实现简易聊天对话框
  18. TS流PAT、PMT、ES、PES分析及解析代码
  19. Internet 上可用的“简单网络时间协议”时间服务器列表
  20. 51cto——让梦飞翔

热门文章

  1. pthread_testcancel和pthread_cancel函数的简单示例
  2. 【技术贴】虚拟机 VMware win7 win8网卡驱动下载 解决虚拟机不识别网卡没有本地连接...
  3. 架构师必然是孤独的领袖
  4. Java程序员总结分布式架构,你又了解多少呢?
  5. c++中几种常见的类型转换。int与string的转换,float与string的转换以及string和long类型之间的相互转换。to_string函数的实现和应用。...
  6. 细述 Java垃圾回收机制→Types of Java Garbage Collectors
  7. Android OTA在线升级二(升级包编译原理分析) 【转】
  8. mysql 协议的ResultsetRow包及解析
  9. HDU 2830 Matrix Swapping II
  10. 持续集成之路——Maven