将点符号中的JavaScriptstring转换为对象引用
给定一个JS对象: var obj = { a: { b: '1', c: '2' } }
和一个string"ab"
我怎样才能将string转换为点符号,所以我可以去: var val = obj.ab
;
如果string只是'a',我可以使用obj[a]
但是这更复杂。 我想有一些直截了当的方法,但是现在却逃脱了。
最近的笔记:虽然我很高兴这个答案得到了许多upvotes,我也有些害怕。 如果需要将“xabc”之类的点符号string转换为引用,这可能是一个标志,说明有一些非常错误的情况(除非您正在执行一些奇怪的反序列化)。 这是过度的,因为它是不必要的元编程,也有些违反function无副作用的编码风格。 另外,如果你做这个比你需要的更多(例如作为你的应用程序默认的传递对象和引用它们的forms),你也需要大量的性能命中。 如果由于某种原因,这是服务器端JS,通常是input消毒。 新手find自己的答案应该考虑与数组表示,而不是像['x','a','b','c'],甚至更直接/简单/直接的工作跟踪引用本身,或者可能是一些预先存在的唯一ID等
这是一个优雅的单线,比其他解决scheme短10倍:
function index(obj,i) {return obj[i]} 'abetc'.split('.').reduce(index, obj)
或者在ECMAScript 6中:
'abetc'.split('.').reduce((o,i)=>o[i], obj)
(不是说我觉得eval总是像其他人一样表示它是坏的(虽然通常是这样),但是这些人会很高兴这种方法不使用eval。以上将findobj.abetc
给定的obj
和string"abetc"
。)
针对那些仍然害怕使用reduce
尽pipe它是ECMA-262标准(第5版),这是一个双线recursion实现:
function multiIndex(obj,is) { // obj,['1','2','3'] -> ((obj['1'])['2'])['3'] return is.length ? multiIndex(obj[is[0]],is.slice(1)) : obj } function pathIndex(obj,is) { // obj,'1.2.3' -> multiIndex(obj,['1','2','3']) return multiIndex(obj,is.split('.')) } pathIndex('abetc')
根据JS编译器正在进行的优化,您可能需要确保每次调用时,通过常规方法(将它们放置在闭包,对象或全局名称空间中),不会重新定义任何嵌套函数。
编辑 :
在评论中回答一个有趣的问题:
你怎么把这个变成一个二传手呢? 不仅通过path返回值,而且如果一个新的值被发送到函数中,还设置它们? – Swader 6月28日21:42
(注:遗憾的是不能用Setter返回一个对象,因为这违反了调用约定;评论者似乎是指一个普通的setter风格的函数,其副作用如index(obj,"abetc", value)
做obj.abetc = value
。)
reduce
方式并不适合这种情况,但我们可以修改recursion实现:
function index(obj,is, value) { if (typeof is == 'string') return index(obj,is.split('.'), value); else if (is.length==1 && value!==undefined) return obj[is[0]] = value; else if (is.length==0) return obj; else return index(obj[is[0]],is.slice(1), value); }
演示:
> obj = {a:{b:{etc:5}}} > index(obj,'abetc') 5 > index(obj,['a','b','etc']) #works with both strings and lists 5 > index(obj,'abetc', 123) #setter-mode - third argument (possibly poor form) 123 > index(obj,'abetc') 123
…虽然我个人build议做一个单独的函数setIndex(...)
。 我想结束一个侧面说明,问题的原始forms可以(应该)使用索引数组(可以从.split
获得),而不是string; 虽然方便function通常没有错。
如果你可以使用lodash ,有一个函数,它确实是这样的:
_.get(object,path,[defaultValue])
var val = _.get(obj, "ab");
更多的涉及recursion的例子。
function recompose(obj,string){ var parts = string.split('.'); var newObj = obj[parts[0]]; if(parts[1]){ parts.splice(0,1); var newString = parts.join('.'); return recompose(newObj,newString); } return newObj; } var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}}; alert(recompose(obj,'adab')); //blah
如果您希望多次parsing相同的path,那么为每个点符号path构build一个函数实际上具有迄今为止最好的性能 (扩展了上面评论中所链接的性能testingJames Wilkins)。
var path = 'abx'; var getter = new Function("obj", "return obj." + path + ";"); getter(obj);
使用函数构造函数与eval()在安全性和最坏情况下的性能方面有一些相同的缺点,但是对于需要结合极端活力和高性能的情况,IMO还是一个使用不足的工具。 我使用这种方法来构build数组过滤函数,并在AngularJS摘要循环中调用它们。 我的configuration文件始终显示array.filter()步骤取消引用和过滤大约2000个复杂的对象,使用dynamic定义的3-4层深的pathless于1毫秒。
当然,类似的方法可以用来创buildsetter函数:
var setter = new Function("obj", "newval", "obj." + path + " = newval;"); setter(obj, "some new val");
var a = { b: { c: 9 } }; function value(layer, path, value) { var i = 0, path = path.split('.'); for (; i < path.length; i++) if (value != null && i + 1 === path.length) layer[path[i]] = value; layer = layer[path[i]]; return layer; }; value(a, 'b.c'); // 9 value(a, 'b.c', 4); value(a, 'b.c'); // 4
与更简单的eval
方法相比,这是很多代码,但是像Simon Willison所说的, 你永远不应该使用eval 。
另外, JSFiddle 。
其他的build议是有点神秘,所以我想我会贡献:
Object.prop = function(obj, prop, val){ var props = prop.split('.') , final = props.pop(), p while(p = props.shift()){ if (typeof obj[p] === 'undefined') return undefined; obj = obj[p] } return val ? (obj[final] = val) : obj[final] } var obj = { a: { b: '1', c: '2' } } // get console.log(Object.prop(obj, 'a.c')) // -> 2 // set Object.prop(obj, 'a.c', function(){}) console.log(obj) // -> { a: { b: '1', c: [Function] } }
请注意,如果您已经在使用Lodash ,则可以使用property
或get
函数:
var obj = { a: { b: '1', c: '2' } }; _.property('a.b')(obj); // => 1 _.get(obj, 'a.b'); // => 1
下划线也有一个property
函数,但不支持点符号。
多年以来,原来的职位。 现在有一个叫“对象path”的很好的库。 https://github.com/mariocasciaro/object-path
在NPM和BOWER上可用https://www.npmjs.com/package/object-path
这很简单:
objectPath.get(obj, "ac1"); //returns "f" objectPath.set(obj, "aj0.f", "m");
并且适用于深度嵌套的属性和数组。
我已经通过ninjagecko扩展了优雅的答案,以便函数处理虚线和/或数组样式引用,并且使空string返回父对象。
干得好:
string_to_ref = function (object, reference) { function arr_deref(o, ref, i) { return !ref ? o : (o[ref.slice(0, i ? -1 : ref.length)]) } function dot_deref(o, ref) { return ref.split('[').reduce(arr_deref, o); } return !reference ? object : reference.split('.').reduce(dot_deref, object); };
看到我在这里工作的jsFiddle例子: http : //jsfiddle.net/sc0ttyd/q7zyd/
var find = function(root, path) { var segments = path.split('.'), cursor = root, target; for (var i = 0; i < segments.length; ++i) { target = cursor[segments[i]]; if (typeof target == "undefined") return void 0; cursor = target; } return cursor; }; var obj = { a: { b: '1', c: '2' } } find(obj, "ab"); // 1 var set = function (root, path, value) { var segments = path.split('.'), cursor = root, target; for (var i = 0; i < segments.length - 1; ++i) { cursor = cursor[segments[i]] || { }; } cursor[segments[segments.length - 1]] = value; }; set(obj, "ak", function () { console.log("hello world"); }); find(obj, "ak")(); // hello world
您可以用一行代码通过点符号来获取对象成员的值:
new Function('_', 'return _.' + path)(obj);
在你的情况下:
var obj = { a: { b: '1', c: '2' } } var val = new Function('_', 'return _.a.b')(obj);
为了简单起见,你可以写一个这样的函数:
function objGet(obj, path){ return new Function('_', 'return _.' + path)(obj); }
说明:
Function构造函数创build一个新的Function对象。 在JavaScript中,每个函数实际上都是一个Function对象。 用Function构造函数显式创build一个函数的语法是:
new Function ([arg1[, arg2[, ...argN]],] functionBody)
其中arguments(arg1 to argN)
必须是对应于有效的javaScript标识符的string,而functionBody
是包含函数定义的javaScript语句的string。
在我们的例子中,我们利用string函数体来用点符号来检索对象成员。
希望它有帮助。
我build议分割path并迭代它并减less你拥有的对象。 此提案适用于缺less属性的默认值 。
function getValue(object, keys) { return keys.split('.').reduce(function (o, k) { return (o || {})[k]; }, object); } console.log(getValue({ a: { b: '1', c: '2' } }, 'a.b')); console.log(getValue({ a: { b: '1', c: '2' } }, 'foo.bar.baz'));
我从里卡多·托马西(Ricardo Tomasi)的回答中复制了以下内容,并且修改了这些内容,以便根据需要创build尚不存在的子对象。 效率稍差( if
创build空对象的if
效果会更好),但是应该不错。
另外,它可以让我们做Object.prop(obj, 'a.b', false)
,这是我们以前做不到的。 不幸的是,它仍然不会让我们分配undefined
…不知道如何去那个呢。
/** * Object.prop() * * Allows dot-notation access to object properties for both getting and setting. * * @param {Object} obj The object we're getting from or setting * @param {string} prop The dot-notated string defining the property location * @param {mixed} val For setting only; the value to set */ Object.prop = function(obj, prop, val){ var props = prop.split('.'), final = props.pop(), p; for (var i = 0; i < props.length; i++) { p = props[i]; if (typeof obj[p] === 'undefined') { // If we're setting if (typeof val !== 'undefined') { // If we're not at the end of the props, keep adding new empty objects if (i != props.length) obj[p] = {}; } else return undefined; } obj = obj[p] } return typeof val !== "undefined" ? (obj[final] = val) : obj[final] }
获取/设置答案也可以在原生反应 (你不能分配给Object.prototype
目前):
Object.defineProperty(Object.prototype, 'getNestedProp', { value: function(desc) { var obj = this; var arr = desc.split("."); while(arr.length && (obj = obj[arr.shift()])); return obj; }, enumerable: false }); Object.defineProperty(Object.prototype, 'setNestedProp', { value: function(desc, value) { var obj = this; var arr = desc.split("."); var last = arr.pop(); while(arr.length && (obj = obj[arr.shift()])); obj[last] = value; }, enumerable: false });
用法:
var a = { values: [{ value: null }] }; var b = { one: { two: 'foo' } }; a.setNestedProp('values.0.value', b.getNestedProp('one.two')); console.log(a.values[0].value); // foo
这是我的代码没有使用eval
。 它也容易理解。
function value(obj, props) { if (!props) return obj; var propsArr = props.split('.'); var prop = propsArr.splice(0, 1); return value(obj[prop], propsArr.join('.')); } var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}}; console.log(value(obj, 'adab')); //returns blah
是的,4年前有人问,是的,扩展基本原型通常不是好主意,但如果将所有扩展名保留在一个地方,它们可能是有用的。
所以,这是我的方式来做到这一点。
Object.defineProperty(Object.prototype, "getNestedProperty", { value : function (propertyName) { var result = this; var arr = propertyName.split("."); while (arr.length && result) { result = result[arr.shift()]; } return result; }, enumerable: false });
现在,您将可以在任何地方获得嵌套属性,而无需导入具有function或复制/粘贴function的模块。
UPD.Example:
{a:{b:11}}.getNestedProperty('a.b'); //returns 11
UPD 2.在我的项目中,下一个扩展名为榫。 另外我读过,它可能会打破jQuery。 所以,绝对不要这样做
Object.prototype.getNestedProperty = function (propertyName) { var result = this; var arr = propertyName.split("."); while (arr.length && result) { result = result[arr.shift()]; } return result; };
这是我的实现
实施1
Object.prototype.access = function() { var ele = this[arguments[0]]; if(arguments.length === 1) return ele; return ele.access.apply(ele, [].slice.call(arguments, 1)); }
实现2 (使用数组reduce而不是slice)
Object.prototype.access = function() { var self = this; return [].reduce.call(arguments,function(prev,cur) { return prev[cur]; }, self); }
例子:
var myobj = {'a':{'b':{'c':{'d':'abcd','e':[11,22,33]}}}}; myobj.access('a','b','c'); // returns: {'d':'abcd', e:[0,1,2,3]} myobj.abaccess('c','d'); // returns: 'abcd' myobj.access('a','b','c','e',0); // returns: 11
它也可以处理数组内的对象
var myobj2 = {'a': {'b':[{'c':'ab0c'},{'d':'ab1d'}]}} myobj2.access('a','b','1','d'); // returns: 'ab1d'
这是我提出的扩展解决scheme:ninjagecko
对我来说简单的string表示法还不够,所以下面的版本支持如下的东西:
index(obj,'data.accounts [0] .address [0] .postcode');
/** * Get object by index * @supported * - arrays supported * - array indexes supported * @not-supported * - multiple arrays * @issues: * index(myAccount, 'accounts[0].address[0].id') - works fine * index(myAccount, 'accounts[].address[0].id') - doesnt work * @Example: * index(obj, 'data.accounts[].id') => returns array of id's * index(obj, 'data.accounts[0].id') => returns id of 0 element from array * index(obj, 'data.accounts[0].addresses.list[0].id') => error * @param obj * @param path * @returns {any} */ var index = function(obj, path, isArray?, arrIndex?){ // is an array if(typeof isArray === 'undefined') isArray = false; // array index, // if null, will take all indexes if(typeof arrIndex === 'undefined') arrIndex = null; var _arrIndex = null; var reduceArrayTag = function(i, subArrIndex){ return i.replace(/(\[)([\d]{0,})(\])/, (i) => { var tmp = i.match(/(\[)([\d]{0,})(\])/); isArray = true; if(subArrIndex){ _arrIndex = (tmp[2] !== '') ? tmp[2] : null; }else{ arrIndex = (tmp[2] !== '') ? tmp[2] : null; } return ''; }); } function byIndex(obj, i) { // if is an array if(isArray){ isArray = false; i = reduceArrayTag(i, true); // if array index is null, // return an array of with values from every index if(!arrIndex){ var arrValues = []; _.forEach(obj, (el) => { arrValues.push(index(el, i, isArray, arrIndex)); }) return arrValues; } // if array index is specified var value = obj[arrIndex][i]; if(isArray){ arrIndex = _arrIndex; }else{ arrIndex = null; } return value; }else{ // remove [] from notation, // if [] has been removed, check the index of array i = reduceArrayTag(i, false); return obj[i] } } // reduce with byIndex method return path.split('.').reduce(byIndex, obj) }
冒着摔死马的危险…我发现这在遍历嵌套对象时最有用,以引用您在基础对象或具有相同结构的类似对象的位置。 为此,这对嵌套对象遍历函数很有用。 请注意,我用一个数组来保存path。 将其修改为使用stringpath或数组将是微不足道的。 还要注意,与其他一些实现不同,您可以将“未定义”赋值给该值。
/* * Traverse each key in a nested object and call fn(curObject, key, value, baseObject, path) * on each. The path is an array of the keys required to get to curObject from * baseObject using objectPath(). If the call to fn() returns falsey, objects below * curObject are not traversed. Should be called as objectTaverse(baseObject, fn). * The third and fourth arguments are only used by recursion. */ function objectTraverse (o, fn, base, path) { path = path || []; base = base || o; Object.keys(o).forEach(function (key) { if (fn(o, key, o[key], base, path) && jQuery.isPlainObject(o[key])) { path.push(key); objectTraverse(o[key], fn, base, path); path.pop(); } }); } /* * Get/set a nested key in an object. Path is an array of the keys to reference each level * of nesting. If value is provided, the nested key is set. * The value of the nested key is returned. */ function objectPath (o, path, value) { var last = path.pop(); while (path.length && o) { o = o[path.shift()]; } if (arguments.length < 3) { return (o? o[last] : o); } return (o[last] = value); }
我在我的项目中使用了这个代码
const getValue = (obj, arrPath) => ( arrPath.reduce((x, y) => { if (y in x) return x[y] return {} }, obj) )
用法:
const obj = { id: { user: { local: 104 } } } const path = [ 'id', 'user', 'local' ] getValue(obj, path) // return 104
目前还不清楚你的问题是什么。 鉴于你的对象, obj.ab
会给你“2”,就像它。 如果你想操作string来使用括号,你可以这样做:
var s = 'a.b'; s = 'obj["' + s.replace(/\./g, '"]["') + '"]'; alert(s); // displays obj["a"]["b"]