【问题标题】:Access property via it's keyPath in Javascript?通过 Javascript 中的 keyPath 访问属性?
【发布时间】:2013-07-15 01:14:09
【问题描述】:

我有

data = 
{
    'first': {
        'number': 1,
        'text': 'Ya.'
    },
    'second': {
        'number': 10,
        'text': 'Da.'
    }
};

我真的很想像这样访问它:

number = data['first.number'];

其实是更灵活的方式,比如:

numberOrText = data[memberName+'.'+propertyName];

有什么轻量级的库,或者 sn-p 你可以推荐吗? 这是 - https://github.com/martinvl/KVCObject - 太酷了,但有点开销。

【问题讨论】:

  • data[memberName][propertyName] 有什么问题???
  • 或 data.memberName.propertyName?
  • 如果你的路径没有非冗长的字符,你可以评估路径。您还可以使用 [].map() 或在爆炸路径上的循环每次更深入一步,将分支设置为根并继续...
  • @dandavis 其中一些将是我的解决方案,谢谢。 Eval 听起来非常简单的解决方案。您可以发布作为接受的答案吗?
  • @mohkhan:我将引用(数组 keyPaths)存储到 DOM 元素数据集上的控制器对象。像 data-key-path="viewController.controls.transform.opacitySlider";然后通过获取文档的 keyPath 的值来引用该对象。

标签: javascript object accessor kvc


【解决方案1】:

创建一个辅助函数来读取可变数量的参数或参数数组。

Object.prototype.$ = function() {
    var result = this;
    var list;
    /*
    Array .$(["first", "text"])
    String .$("second.number")
    String Parameters .$("first", "text")
    */
    if(arguments.length == 1 && Object.prototype.toString.call(arguments[0]) === "[object Array]")
        list = arguments[0];
    else if(arguments.length == 1 && typeof(arguments[0]) == 'string' && arguments[0].indexOf(".") >= 0)
        list = arguments[0].split(".");
    else
        list = arguments;
    for(var i=0; i<list.length; i++)
        result = result[list[i]];
    return result;
}

// test it
data = 
{
    'first': {
        'number': 1,
        'text': 'Ya.'
    },
    'second': {
        'number': 10,
        'text': 'Da.'
    }
};
var s = "second";
var s2 = "first.number";
console.log(data.$("first", "text"));
console.log(data.$(s, "number"));
console.log(data.$(["first", "number"]));
console.log(data.$(s2));

编辑您还可以创建一个辅助函数来对您的对象进行反规范化,但仅在对其进行反规范化后读取值,因为编辑值会导致冲突,因为您的对象将具有内部对象值的副本。

例子:

data["first"]["number"] == data["first.number"];
data["first.number"] = -1;
data["first"]["number"] != data["first.number"];

反规范化代码

function denormalize(obj, lv) {
    var more = false;
    for(var k in obj) {
        if(k.split(".").length == lv) {
            var node = obj[k]
            if(node && typeof(node) == 'object') {
                more = true;
                for(var k2 in node) {
                    obj[k + "." + k2] = node[k2];
                }
            }
        }
    }
    if(more)
        denormalize(obj, lv + 1);
    return obj;
}

// test it
data = 
{
    'first': {
        'number': 1,
        'text': 'Ya.'
    },
    'second': {
        'number': 10,
        'text': 'Da.'
    },
    "third": [{"number": 5, "text": "meh"},{"number": 6, "text": "beh"}]
};
denormalize(data, 1);
for(var k in data)
    console.log(k + " : " + data[k]);

【讨论】:

    【解决方案2】:

    基于@dandavispretty simple suggestions,我可以将访问器设置为原型属性。

    没有eval,在枚举使用Object.defineProperty方面也保持Object.prototype不变。

    解决方案实际上是这样的:

    function stringContains(string, value)
    { return string.indexOf(value) != -1; }
    
    Object.defineProperty(Object.prototype, "setValueForKey", { value: function(value, key)
    { this[key] = value; }});
    
    Object.defineProperty(Object.prototype, "setValueForKeyPath", { value: function(value, keyPath)
    {
        if (keyPath == null) return;
        if (stringContains(keyPath, '.') == false) { this.setValueForKey(value, keyPath); return; }
    
        var chain = keyPath.split('.');
        var firstKey = chain.shift();
        var shiftedKeyPath = chain.join('.');
    
        this[firstKey].setValueForKeyPath(value, shiftedKeyPath);
    }});
    
    Object.defineProperty(Object.prototype, "getValueForKey", { value: function(key)
    { return this[key]; }});
    
    Object.defineProperty(Object.prototype, "getValueForKeyPath", { value: function(keyPath)
    {
        if (keyPath == null) return;
        if (stringContains(keyPath, '.') == false) { return this.getValueForKey(keyPath); }
    
        var chain = keyPath.split('.');
        var firstKey = chain.shift();
        var shiftedKeyPath = chain.join('.');
    
        return this[firstKey].getValueForKeyPath(shiftedKeyPath);
    }});
    

    测试没问题:

    data = {
        'name' : 'data',
        'first': {
            'number': 1,
            'text': 'Ya.',
            'meta' : {
                'lang' : 'en'
            }
        },
        'second': {
            'number': 10,
            'text': 'Ba.',
            'meta' : {
                'lang' : 'en'
            }
        },
        'third': {
            'number': 100,
            'text': 'Da.',
            'meta' : {
                'lang' : 'hu'
            }
        }
    };
    
    data.setValueForKey('chunk', 'name');
    data.setValueForKeyPath('blob', 'name');
    
    var thirdLanguage = data.getValueForKeyPath('third.meta.lang');
    data.setValueForKeyPath(thirdLanguage, 'first.meta.lang');
    data.setValueForKeyPath(thirdLanguage, 'second.meta.lang');
    
    log(data);
    

    输出与hu 作为每个数据成员的语言相同。

    【讨论】:

    • 如果我是你,我不会修改 Object.prototype。如果我这样做了,我会使用 Object.defineProperty() 来掩盖它,而不是像上面的代码中那样留下 for-loop-breaking 原型属性分配......
    • 没错,感谢您的建议。很快也会修改答案。
    • 已修改,现在可以安全生产了。
    【解决方案3】:

    如果你有所有基于点的路径(没有数组语法),你可以使用 eval 或简单的滑动递归函数:

    var data = {
        'first': {
            'number': 1,
            'text': 'Ya.'
        },
        'second': {
            'number': 10,
            'text': 'Da.'
        }
    };
    
    
    // the simple but discouraged way using eval:
    alert(
      eval( 
         "data.second.text"
      )
    ); //shows "Da."
    
    
    // a non-eval looping solution take s bit more code, but can be faster to execute:
    
    function resolve(obj, path){
      var r=path.split(".");
      if(path){return resolve(obj[r.shift()], r.join("."));}
     return obj
    }
    
    alert(
       resolve(data, "first.text")
    ); //shows: "Ya."
    

    【讨论】:

    • 嘿,谢谢你的非评估建议,还有defineProperty。现在我得到了最终的解决方案,很快就会发布。
    【解决方案4】:

    我想你可能会喜欢underscore-keypath。

    var foo = {
      bar : {
        name : "Cool!"
      },
      scores : [55, 27, 100, 33]
    };
    
    _(foo).valueForKeyPath("bar.name");           // --> "Cool!"
    _(foo).setValueForKeyPath("bar.name", "BAR"); // --> sets foo.bar.name as "BAR"
    _(foo).valueForKeyPath("scores.@max");        // --> 100
    

    【讨论】:

    • 我喜欢,不错的库。语法开销对我来说有点太多了。我更喜欢vanilla。
    【解决方案5】:

    您可以使用 reduce 函数轻松解析 keypath,而无需使用任何库。

    首先,我们创建一个名为 target 的示例对象,其中包含一些嵌套对象:

    const target = {
        foo: {
            bar: {
                example: 65
            }
        }
    };
    

    然后,我们定义一个包含 keypath 字符串的变量 keypath:我们要访问目标对象内的 example 属性。

    const keypath = 'foo.bar.example';    ​
    

    艰苦的工作从今天开始! Keypath被点分隔符分割,我们得到一个keys数组。我们迭代这个数组(使用reduce函数)并且对于每次迭代,我们返回一个新对象。

    const result = keypath.split('.').reduce((previous, current) => previous[current], target);
    

    最后,结果变量值为 65。它可以工作!

    【讨论】:

    • 没问题,我附上了解释。
    • 优雅的解决方案!
    • 您必须为每个条目创建一个密钥路径
    • 你能解释一下你的意思吗,@FabricioG?
    【解决方案6】:

    ES2015可以使用解构:

    data = 
    {
        'first': {
            'number': 1,
            'text': 'Ya.'
        },
        'second': {
            'number': 10,
            'text': 'Da.'
        }
    };
    
    const {first:{number: yourValue}} = data;
    console.log(yourValue); // 1
    

    More examples

    【讨论】:

      【解决方案7】:

      我有点晚了,但我需要同样的东西,并且认为它既小又实用。 (它希望你 split('.') your.key.path 变成 ['your', 'key', 'path']

      data = 
      {
          'first': {
              'number': 1,
              'text': 'Ya.'
          },
          'second': {
              'number': 10,
              'text': 'Da.',
              'array': ['a', {'b':'bar'}, 'c']
          }
      };
      
      function valueAtPath(object, path) {
          if (!object || path.length === 0) return object
          return valueAtPath(object[path.shift()], path)
      }
      
      function setValueAtPath(object, path, value) {
          if (!object || path.length === 0) return null
          if (path.length === 1) object[path[0]] = value
          else return setValueAtPath(object[path.shift()], path, value)
      }
      
      console.log(valueAtPath(data, ['second', 'array', 1, 'b']))
      
      setValueAtPath(data, ['second', 'array', 1, 'b'], 'foo')
      console.log(data)

      【讨论】:

        【解决方案8】:

        lodash 有一个简单的方法可以做到这一点。

        _.get()


        【讨论】:

          猜你喜欢
          • 2017-11-17
          • 2012-03-10
          • 1970-01-01
          • 1970-01-01
          • 2023-03-13
          • 1970-01-01
          • 2018-02-02
          • 2015-04-28
          • 2012-02-04
          相关资源
          最近更新 更多