js 怎么用变量获取一个对象任意深度的属性值

let x={a:{b:{c:{d:1}}}};
在不使用eval的前提下,能不能通过x[变量]的方式获取到x.a.b.d的值?

然后还有个问题,为啥正常取值会比用eval慢一点,是我代码有问题吗?

console.time('正常方式获取');
console.log(x.a.b.c.d);
console.timeEnd('正常方式获取');
console.time('eval方式获取');
console.log(eval('x.a.b.c.d'));
console.timeEnd('eval方式获取');

输出:
1
正常方式获取: 1.644ms
1
eval方式获取: 0.066ms

补充一下,上面的是在node下运行的,我发现在浏览器里eval会慢一点,这啥情况?
阅读 5.9k
4 个回答
  1. lodash 里面有个 get 实现
  2. 如果你不想引入一整个lodash的话,可以使用 30-seconds-of-code 里面的 get 实现
const get = (from, ...selectors) =>
  [...selectors].map(s =>
    s
      .replace(/\[([^\[\]]*)\]/g, '.$1.')
      .split('.')
      .filter(t => t !== '')
      .reduce((prev, cur) => prev && prev[cur], from)
  );

'js 怎么用变量获取一个对象任意深度的属性值'
自己写方法也是一种方案:

let x = { a: { b: { c: { d: 1 } } } };
Object.prototype.getObjVal = function(key) {
  const _ = (obj) => {
    const keys = Object.keys(obj)
    const len = keys.length
    for (let i = 0; i < len; i++) {
      const k = keys[i]
      const val = obj[k]
      if (key === k) {
        return val
      } else if (Object.prototype.toString.call(val) === '[object Object]') {
        let res = _(val)
        if (res !== undefined) {
          return res
        }
      }
    }
  }
  return _(this)
}
console.log(x.getObjVal('a')) // { b: { c: { d: 1 } } }
console.log(x.getObjVal('c')) // { d: 1 }
console.log(x.getObjVal('d')) // 1
console.log(x.getObjVal('g')) // undefined

在谷歌控制台试了下:

let x={a:{b:{c:{d:1}}}};
console.time('正常方式获取');
for (let i = 0; i < 10000; i ++) {
  x.a.b.c.d
}
console.timeEnd('正常方式获取');
console.time('eval方式获取');
for (let i = 0; i < 10000; i ++) {
  eval('x.a.b.c.d')
}
console.timeEnd('eval方式获取');

image.png

实测谷歌浏览器eval是要慢很多的!

有个项目叫做 jsonPath,可以像 xPath 一样在 JSON 结构的数据中进行查询,可惜没有 graphQL 那么有名,不大可能成为 SQL 的替代品。
我写了一个最简版的,只能精确获取特定路径的数据,语法也更严格——点号对应键名,方括号对应索引(当然,原因是完整的查询引擎太难写),并且返回查询的详情:

const deepPath = path => {
  const pathArr = [];
  let i = '', part = '', s = '';

  for (i in path) {
    s = path[i];
    if (s === '.' || s === '[') {
      if (part) {
        pathArr.push(part);
        part = '';
      }
    } else if (s === ']') {
      if (part) {
        pathArr.push(~~part);
        part = '';
      }
    } else {
      part += s;
    }
  };

  if(s !== ']'){
    pathArr.push(part)
  }

  return pathArr;
};

const deepValue = (objct, path /*, addPath*/) => {
  const pathArr = deepPath(path);
  let part = '',
    hasPath = true,
    cur = objct,
    lastPath = '',
    i = '';

  // 遍历取值
  while (!!pathArr.length){
    i = pathArr.shift();

    if (cur.hasOwnProperty(i)) {
      cur = cur[i];
      lastPath += typeof i === 'number' ? `[${i}]` : `${i}.`
    } else {
      hasPath = false;
      lastPath = lastPath.replace(/\.$/, '');
      break
    }
  }

  return {
    value: hasPath? cur: null,
    hasPath,
    lastPath,
    lastValue: cur
  }
};
let x={a:{b:{c:{d:1}}}};
console.log(deepValue(x, 'a.b.c.d').value);

当然,效率肯定不忍看,我的本意是用这玩意写一个类 React 的玩具框架,开发者自己指定更新路径,对状态树进行原子化操作,省去 diff 操作。

推荐问题