2013-07-16 83 views
2

通过JavaScript中的keyPath访问属性?

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

我真的想访问它喜欢:

number = data['first.number']; 

实际上,在更灵活的方式,如:

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

是否有任何轻量级图书馆,或片段,你可以建议? 这是 - https://github.com/martinvl/KVCObject - 非常酷,但有点这个开销。

+1

是什么毛病'数据[成员名称]将[PropertyName]'??? – mohkhan

+0

或data.memberName.propertyName? – Stefan

+1

如果你的路径没有非罗嗦的字符,你可以评估路径。你也可以使用[] .map()或者在展开路径上的循环来逐步深入一步,设置分支到根目录并继续... – dandavis

回答

2

基于@dandavispretty simple suggestions,我可以设置访问器原型的性能。

没有eval,也留下Object.prototype原封不动使用Object.defineProperty列举方面。

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

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在每个数据成员的语言相同。

+1

如果我是你,我不会修改Object.prototype。如果我这样做,我会使用Object.defineProperty()来覆盖它,而不是像上面的代码那样留下for-loop-breaking原型属性分配... – dandavis

+0

正是如此,感谢您的建议。将很快修改答案。 – Geri

+0

修改后,现在生产安全。 – Geri

0

建立一个帮助函数,该函数读取可变数量的参数或参数数组。

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

,如果你有所有基于点的路径(没有数组语法),你可以使用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." 
+0

嘿,谢谢你的非eval建议,也defineProperty。现在我得到了最终的解决方案,很快就会发布。 – Geri

1

我想你可能会喜欢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("[email protected]");  // --> 100 
+0

我喜欢,很好的lib。语法开销对我来说有点太过分了。我更喜欢[香草](http://vanilla-js.com/)。 – Geri

5

您可以轻松解决的keyPath与减少功能,不使用任何库。

首先,我们正在创建的实例对象,调用目标,与内部的一些嵌套对象:

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

然后,我们定义一个变量的keyPath含有的keyPath字符串:我们想访问示例我们的目标对象内有属性。

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

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

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

最后,结果变量值是65.它的工作原理!

+1

没问题,我包括一个解释。 –

+1

优雅的解决方案! – elslooo

1

ES2015可以使用解构:

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

const {first:{number: yourValue}} = data; 
console.log(yourValue); // 1 

More examples