Js中安全获取Object深层对象的方法实例

目录
  • 前言
  • 正文
    • 参数
    • 例子
    • lodash的实现:
    • tokey函数:
    • castPath函数:
    • stringToPath函数:
    • memoizeCapped函数:
    • memoize函数:
    • 完整代码如下:
  • 参考资料:
  • 总结

前言

做前端的小伙伴一定遇到过后端返回的数据有多层嵌套的情况,当我要获取深层对象的值时为防止出错,会做层层非空校验,比如:

const obj = {
    goods: {
        name: 'a',
        tags: {
            name: '快速',
            id: 1,
            tagType: {
                name: '标签'
            }
        }
    }
}

当我需要获取tagType.name时判断是这样的

if (obj.goods !== null
    && obj.goods.tags !== null
    && obj.goods.tags.tagType !== null) {
  
}

如果属性名冗长,这断代码就没法看。

当然ECMAScript2020中已经推出了?.来解决这个问题:

let name = obj?.goods?.tags?.tageType?.name;

但是不兼容ES2020的浏览器中怎么处理呢?

正文

使用过lodash的同学可能知道,lodash中有个get方法,官网是这么说的:

_.get(object, path, [defaultValue])

根据 object对象的path路径获取值。 如果解析 value 是 undefined 会以 defaultValue 取代。

参数

  1. object (Object) : 要检索的对象。
  2. path (Array|string) : 要获取属性的路径。
  3. [defaultValue] ()* : 如果解析值是 undefined ,这值会被返回。

例子

var object = { 'a': [{ 'b': { 'c': 3 } }] };
​
_.get(object, 'a[0].b.c');
// => 3
​
_.get(object, ['a', '0', 'b', 'c']);
// => 3
​
_.get(object, 'a.b.c', 'default');
// => 'default'

如此问题解决,但是(就怕有“但是”)

如果因为项目、公司要求等各种原因我不能使用引入lodash库怎么办呢?

有了,我们看看lodash是怎么实现的,把代码摘出来不就可以了吗,如此又能快乐的搬砖了~~

lodash的实现:

function get(object, path, defaultValue) {
  const result = object == null ? undefined : baseGet(object, path)
  return result === undefined ? defaultValue : result
}

这里做的事很简单,先看返回,如果object即返回默认值,核心代码在baseGet中,那我们再来看baseGet的实现

function baseGet(object, path) {
  // 将输入的字符串路径转换成数组,
  path = castPath(path, object)
​
  let index = 0
  const length = path.length
  // 遍历数组获取每一层对象
  while (object != null && index < length) {
    object = object[toKey(path[index++])] // toKey方法
  }
  return (index && index == length) ? object : undefined
}
​

这里又用到两个函数castPath(将输入路径转换为数组)、toKey(转换真实key)

tokey函数:

/** Used as references for various `Number` constants. */
const INFINITY = 1 / 0
​
/**
 * Converts `value` to a string key if it's not a string or symbol.
 *
 * @private
 * @param {*} value The value to inspect.
 * @returns {string|symbol} Returns the key.
 */
function toKey(value) {
  if (typeof value === 'string' || isSymbol(value)) {
    return value
  }
  const result = `${value}`
  return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result
}

这里主要做了两件事,

  • 如果key类型为String或者symbol则直接返回
  • 如果key为其他类型,则转换为String返回

这里还用到了isSymbol函数来判断是否为Symbol类型,代码就不贴了,感兴趣的同学可以查看lodash源码

castPath函数:

import isKey from './isKey.js'
import stringToPath from './stringToPath.js'
​
/**
 * Casts `value` to a path array if it's not one.
 *
 * @private
 * @param {*} value The value to inspect.
 * @param {Object} [object] The object to query keys on.
 * @returns {Array} Returns the cast property path array.
 */
function castPath(value, object) {
  if (Array.isArray(value)) {
    return value
  }
  return isKey(value, object) ? [value] : stringToPath(value)
}
​

castPath主要是将输入的路径转换为数组的,为后面遍历获取深层对象做准备。

这里有用到了isKey()与stringToPath().

isKey比较简单判断当前value是否为object的key。

stringToPath主要处理输入路径为字符串的情况,比如:'a.b.c[0].d'

stringToPath函数:

import memoizeCapped from './memoizeCapped.js'
​
const charCodeOfDot = '.'.charCodeAt(0)
const reEscapeChar = /\(\)?/g
const rePropName = RegExp(
  // Match anything that isn't a dot or bracket.
  '[^.[\]]+' + '|' +
  // Or match property names within brackets.
  '\[(?:' +
    // Match a non-string expression.
    '([^"'][^[]*)' + '|' +
    // Or match strings (supports escaping characters).
    '(["'])((?:(?!\2)[^\\]|\\.)*?)\2' +
  ')\]'+ '|' +
  // Or match "" as the space between consecutive dots or empty brackets.
  '(?=(?:\.|\[\])(?:\.|\[\]|$))'
  , 'g')
​
/**
 * Converts `string` to a property path array.
 *
 * @private
 * @param {string} string The string to convert.
 * @returns {Array} Returns the property path array.
 */
const stringToPath = memoizeCapped((string) => {
  const result = []
  if (string.charCodeAt(0) === charCodeOfDot) {
    result.push('')
  }
  string.replace(rePropName, (match, expression, quote, subString) => {
    let key = match
    if (quote) {
      key = subString.replace(reEscapeChar, '$1')
    }
    else if (expression) {
      key = expression.trim()
    }
    result.push(key)
  })
  return result
})
​

这里主要是排除路径中的 . 与[],解析出真实的key加入到数组中

memoizeCapped函数:

import memoize from '../memoize.js'
​
/** Used as the maximum memoize cache size. */
const MAX_MEMOIZE_SIZE = 500
​
/**
 * A specialized version of `memoize` which clears the memoized function's
 * cache when it exceeds `MAX_MEMOIZE_SIZE`.
 *
 * @private
 * @param {Function} func The function to have its output memoized.
 * @returns {Function} Returns the new memoized function.
 */
function memoizeCapped(func) {
  const result = memoize(func, (key) => {
    const { cache } = result
    if (cache.size === MAX_MEMOIZE_SIZE) {
      cache.clear()
    }
    return key
  })
​
  return result
}
​
export default memoizeCapped
​

这里是对缓存的key做一个限制,达到500时清空缓存

memoize函数:

function memoize(func, resolver) {
  if (typeof func !== 'function' || (resolver != null && typeof resolver !== 'function')) {
    throw new TypeError('Expected a function')
  }
  const memoized = function(...args) {
    const key = resolver ? resolver.apply(this, args) : args[0]
    const cache = memoized.cache
​
    if (cache.has(key)) {
      return cache.get(key)
    }
    const result = func.apply(this, args)
    memoized.cache = cache.set(key, result) || cache
    return result
  }
  memoized.cache = new (memoize.Cache || Map)
  return memoized
}
​
memoize.Cache = Map

其实最后两个函数没太看懂,如果输入的路径是'a.b.c',那直接将它转换成数组不就可以吗?为什么要用到闭包进行缓存。

希望看懂的大佬能给解答一下

由于源码用到的函数较多,且在不同文件,我将他们进行了精简,

完整代码如下:

/**
   * Gets the value at `path` of `object`. If the resolved value is
   * `undefined`, the `defaultValue` is returned in its place.
   * @example
   * const object = { 'a': [{ 'b': { 'c': 3 } }] }
   *
   * get(object, 'a[0].b.c')
   * // => 3
   *
   * get(object, ['a', '0', 'b', 'c'])
   * // => 3
   *
   * get(object, 'a.b.c', 'default')
   * // => 'default'
   */
  safeGet (object, path, defaultValue) {
    let result
    if (object != null) {
      if (!Array.isArray(path)) {
        const type = typeof path
        if (type === 'number' || type === 'boolean' || path == null ||
        /^\w*$/.test(path) || !(/.|[(?:[^[]]*|(["'])(?:(?!\1)[^\]|\.)*?\1)]/.test(path)) ||
        (object != null && path in Object(object))) {
          path = [path]
        } else {
          const result = []
          if (path.charCodeAt(0) === '.'.charCodeAt(0)) {
            result.push('')
          }
          const rePropName = RegExp(
            // Match anything that isn't a dot or bracket.
            '[^.[\]]+|\[(?:([^"'][^[]*)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))'
            , 'g')
          path.replace(rePropName, (match, expression, quote, subString) => {
            let key = match
            if (quote) {
              key = subString.replace(/\(\)?/g, '$1')
            } else if (expression) {
              key = expression.trim()
            }
            result.push(key)
          })
          path = result
        }
      }
      let index = 0
      const length = path.length
      const toKey = (value) => {
        if (typeof value === 'string') {
          return value
        }
        const result = `${value}`
        return (result === '0' && (1 / value) === -(1 / 0)) ? '-0' : result
      }
      while (object != null && index < length) {
        object = object[toKey(path[index++])]
      }
      result = (index && index === length) ? object : undefined
    }
    return result === undefined ? defaultValue : result
  }

代码借鉴自lodash

参考资料:

总结

到此这篇关于Js中安全获取Object深层对象的文章就介绍到这了,更多相关Js获取Object深层对象内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JS 对象(Object)和字符串(String)互转方法

    利用原生JSON对象,将对象转为字符串 var jsObj = {}; jsObj.testArray = [1,2,3,4,5]; jsObj.name = 'CSS3'; jsObj.date = '8 May, 2011'; var str = JSON.stringify(jsObj); alert(str); 从JSON字符串转为对象 var jsObj = {}; jsObj.testArray = [1,2,3,4,5]; jsObj.name = 'CSS3'; jsObj.da

  • Javascript 面向对象 对象(Object)

    javascript中的对象创建声明: var obj = {}; 或者 var obj = new Object(); 为对象加入属性,方法: //=====第一种写法==================================== obj.name = '小明'; //为对象加属性 obj.updateName = function(name){//为对象定义updateName方法 this.name = name; } alert(obj.name); obj.updateNam

  • 深入理解JavaScript中的对象复制(Object Clone)

    JavaScript中并没有直接提供对象复制(Object Clone)的方法.因此下面的代码中改变对象b的时候,也就改变了对象a. a = {k1:1, k2:2, k3:3}; b = a; b.k2 = 4; 如果只想改变b而保持a不变,就需要对对象a进行复制. 用jQuery进行对象复制 在可以使用jQuery的情况下,jQuery自带的extend方法可以用来实现对象的复制. a = {k1:1, k2:2, k3:3}; b = {}; $.extend(b,a); 自定义clone

  • Javascript 对象(object)合并操作实例分析

    本文实例讲述了Javascript 对象(object)合并操作.分享给大家供大家参考,具体如下: 对象的合并 需求:设有对象 o1 ,o2,需要得到对象 o3 var o1 = { a:'a' }, o2 = { b:'b' }; // 则 var o3 = { a:'a', b:'b' } 方法1:使用JQuery的extend方法 **方法定义**:jQuery.extend([deep], target, object1, [objectN]) > 用一个或多个其他对象来扩展一个对象,返

  • 详谈js中数组(array)和对象(object)的区别

    •object 类型: ◦ 创建方式: /*new 操作符后面Object构造函数*/ var person = new Object(); person.name = "lpove"; person.age = 21; /*或者用对象字面量的方法*/ var person = { name: "lpove"; age : 21; } •array类型 ◦ 创建方式: `var colors = new Array("red","blu

  • js如何打印object对象

    js调试中经常会碰到输出的内容是对象而无法打印的时候,光靠alert只能打印出object标示,却不能打印出来里面的内容,甚是不方便,于是各方面整理总结了如下一个函数,能够将数组或者对象这类的结果一一打印出来,具体代码如下: function writeObj(obj){ var description = ""; for(var i in obj){ var property=obj[i]; description+=i+" = "+property+"

  • JavaScript中使用Object.create()创建对象介绍

    对于对象的创建,除了使用字面量和new操作符,在ECMAScript 5标准中,还可以使用Object.create()来进行.Object.create()函数接受2个对象作为参数:第一个对象是必需的,表示所创建对象的prototype:第二个对象是可选的,用于定义所创建对象的各个属性(比如,writable.enumerable). 复制代码 代码如下: var o = Object.create({x:1, y:7}); console.log(o);//Object {x=1, y=7}

  • 详解Javascript中的Object对象

    Object是在javascript中一个被我们经常使用的类型,而且JS中的所有对象都是继承自Object对象的.虽说我们平时只是简单地使用了Object对象来存储数据,并没有使用到太多其他功能,但是Object对象其实包含了很多很有用的属性和方法,尤其是ES5增加的方法,因此,本文将从最基本的介绍开始,详细说明了Object的常用方法和应用. 基础介绍 创建对象 首先我们都知道,对象就是一组相似数据和功能的集合,我们就是用它来模拟我们现实世界中的对象的.那在Javascript中,创建对象的方

  • Js中安全获取Object深层对象的方法实例

    目录 前言 正文 参数 例子 lodash的实现: tokey函数: castPath函数: stringToPath函数: memoizeCapped函数: memoize函数: 完整代码如下: 参考资料: 总结 前言 做前端的小伙伴一定遇到过后端返回的数据有多层嵌套的情况,当我要获取深层对象的值时为防止出错,会做层层非空校验,比如: const obj = {    goods: {        name: 'a',     tags: {            name: '快速',  

  • JS中使用变量保存arguments对象的方法

    迭代器(iterator)是一个可以顺序存取数据集合的对象.其一个典型的API是next方法.该方法获得序列中的下一个值. 迭代器示例 题目:希望编写一个便利的函数,它可以接收任意数量的参数,并为这些值建立一个迭代器. 测试代码好下: var it=values(,,,,,,,,); it.next();// it.next();// it.next();// 分析:由于values函数需要接收任意多个参数,这里就需要用到上一节讲到的构建可变参数的函数的方法.然后里面的迭代器对象来遍历argum

  • 详解js中的原型,原型对象,原型链

    理解原型 我们创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法.看如下例子: function Person(){ } Person.prototype.name = 'ccc' Person.prototype.age = 18 Person.prototype.sayName = function (){ console.log(this.name); } var person1 = ne

  • Node.js中ES6模块化及Promise对象

    目录 ES6模块化 CommonJS 规范 模块化开发好处 模块化规范划分 ES6模块化开发注意点 ES6 导入导出 Promise对象 使用语法 Promise中的同步异步 Promise 封装 第三方then-fs解决回调地狱 ES6模块化 CommonJS 规范 node.js 遵循了 CommonJS 的模块化规范.其中: 导入其它模块使用 require()方法 模块对外共享成员使用 module.exports 对象 模块化开发好处 模块化开发的好处有很多,其中: 实现了在JS文件中

  • Js与Jq获取浏览器和对象值的方法

    JS and Jquery 都能获取页面元素的宽度,高度和相对位移等数值,那他们之间能相互转换或替代吗,写法又有哪些差异呢?本文将详细为你介绍. 1.Js获取浏览器高度和宽度 document.documentElement.clientWidth ==> 浏览器可见区域宽度 document.documentElement.clientHeight ==> 浏览器可见区域高度 document.body.clientWidth ==> BODY对象宽度 document.body.cl

  • JS中可能会常用到的一些数据处理方法

    目录 DOM处理 数组 方法 总结 DOM处理 DOM 为文档提供了结构化表示,并定义了如何通过脚本来访问文档结构.目的其实就是为了能让js操作html元素而制定的一个规范.DOM就是由节点组成的. 检查一个元素是否被聚焦 const hasFocus = ele => (ele === document.activeElement); 检查用户是否滚动到页面底部 const isAtBottom = () => document.documentElement.clientHeight +

  • js中匿名函数的创建与调用方法分析

    本文实例分析了js中匿名函数的创建与调用方法.分享给大家供大家参考.具体实现方法如下: 匿名函数就是没有名字的函数了,也叫闭包函数(closures),允许 临时创建一个没有指定名称的函数.最经常用作回调函数(callback)参数的值,很多新手朋友对于匿名函数不了解.这里就来分析一下. function 函数名(参数列表){函数体;} 如果是创建匿名函数,那就应该是: function(){函数体;} 因为是匿名函数,所以一般也不会有参数传给他. 为什么要创建匿名函数呢?在什么情况下会使用到匿

  • java、js中实现无限层级的树形结构方法(类似递归)

    js中: var zNodes=[ {id:0,pId:-1,name:"Aaaa"}, {id:1,pId:0,name:"A"}, {id:11,pId:1,name:"A1"}, {id:12,pId:1,name:"A2"}, {id:13,pId:1,name:"A3"}, {id:2,pId:0,name:"B"}, {id:21,pId:2,name:"B1&qu

  • android中intent传递list或者对象的方法

    本文实例讲述了android中intent传递list或者对象的方法.分享给大家供大家参考.具体实现方法如下: 方法一: 如果单纯的传递List<String> 或者List<Integer>的话 就可以直接使用 代码如下: 复制代码 代码如下: intent.putStringArrayListExtra(name, value)  intent.putIntegerArrayListExtra(name, value) 方法二: 如果传递的是List<Object>

  • jQuery获取单击节点对象的方法

    本文实例讲述了jQuery获取单击节点对象的方法.分享给大家供大家参考,具体如下: event.target属性: <script language="JavaScript" type="text/javascript"> $("document").ready(function () { $(".menu").bind("click", function (event) { var click

随机推荐