未定义时自动创建对象


76

如果尚不存在将属性自动添加到对象的方法,有没有简单的方法?

考虑以下示例:

var test = {}
test.hello.world = "Hello doesn't exist!"

这是行不通的,因为hello未定义。

我之所以这样问,是因为我有一些不知道它们是否已经存在的现有对象hello。实际上,我在代码的不同部分中有很多这些对象。总是检查是否hello存在以及是否不创建新对象(例如:

var test = {}
if(test.hello === undefined) test.hello = {}
test.hello.world = "Hello World!"

有没有一种自动创建对象的方法,如hello本例所示?

我的意思是在php中:

$test = array();  
$test['hello']['world'] = "Hello world";   
var_dump($test);

输出:

array(1) {
  ["hello"]=>
  array(1) {
    ["world"]=>
    string(11) "Hello world"
  }
}

好的,这是一个数组,但是在js数组中,它与对象的问题相同。


函数existcheckthingy(x,y,z){if(x === undefined)x = {}; xy = z; }用作existcheckthingy(test.hello,world,“你好,不存在!”);
bobbybee

2
@bobbybee那行不通。它将xexistcheckthingy函数范围内创建一个新对象,但此后将不会附加到该test对象。您可以通过使用“类似数组的”表示法来做得更好:existcheckthingy(a,x,y,z) { if (a[x] === undefined) a[x] = {}; a[x][y] = z;}
Jeff


@Jeff oops,忘记了:3
bobbybee

1
考虑lodash '设置'
刘若英Wooller

Answers:


132
var test = {};
test.hello = test.hello || {};
test.hello.world = "Hello world!";

如果test.hello未定义,则将其设置为空对象。

如果test.hello先前已定义,则它保持不变。

var test = {
  hello : {
    foobar : "Hello foobar"
  }
};

test.hello = test.hello || {};
test.hello.world = "Hello World";

console.log(test.hello.foobar); // this is still defined;
console.log(test.hello.world); // as is this.

这几乎是惯用的JS
Alnitak

非常感谢。但这仍然是手动检查。可能没有吗?
Marcel Gwerder

3
@MarcelGwerder不,这很短。
Alnitak

@MarcelGwerder:据我所知。
xbonez

1
@xbonez注意:未“定义”-正确。但是,空对象实际上是真实的。
Alnitak

14

新物件

myObj = {};

递归函数

function addProps(obj, arr, val) {

    if (typeof arr == 'string')
        arr = arr.split(".");

    obj[arr[0]] = obj[arr[0]] || {};

    var tmpObj = obj[arr[0]];

    if (arr.length > 1) {
        arr.shift();
        addProps(tmpObj, arr, val);
    }
    else
        obj[arr[0]] = val;

    return obj;

}

用点号标记的字符串调用它

addProps(myObj, 'sub1.sub2.propA', 1);

或与数组

addProps(myObj, ['sub1', 'sub2', 'propA'], 1);

你的对象看起来像这样

myObj = {
  "sub1": {
    "sub2": {
      "propA": 1
    }
  }
};

它也适用于非空对象!


这实际上工作得很整洁,并且在不破坏我的对象的情况下更新了正确的值。
RozzA

6

好吧,您可以Object使用返回属性的函数来扩展的原型,但如果不存在该属性,则首先添加它:

Object.prototype.getOrCreate = function (prop) {
    if (this[prop] === undefined) {
        this[prop] = {};
    }
    return this[prop];
};

var obj = {};

obj.getOrCreate("foo").getOrCreate("bar").val = 1;

1
这似乎是一个不错的解决方案,但是由于某种原因它破坏了数据表。通常,将对象内部弄乱是一个好主意吗?
杰森·刘易斯

这就是为什么扩展本机对象可能不是一个好主意的原因,因为方法可能会重叠。
Tschallacka

6

如果没有某种功能,您将无法执行此操作,因为JavaScript没有用于对象的通用getter / setter方法(例如,Python具有__getattr__)。这是一种实现方法:

function add_property(object, key, value) {
    var keys = key.split('.');

    while (keys.length > 1) {
        var k = keys.shift();

        if (!object.hasOwnProperty(k)) {
            object[k] = {};
        }

        object = object[k];
    }

    object[keys[0]] = value;
}

如果确实需要,可以将其添加到的原型中Object。您可以这样称呼它:

> var o = {}
> add_property(o, 'foo.bar.baz', 12)
> o.foo.bar.baz
12

当在同一对象上重复使用时,此功能有些问题
RozzA

@RozzA:感谢您捕获该错误,现已修复。
Blender

@Blender是否有可能读取read_property(o, 'foo.bar.baz')将返回12的属性值?添加属性后
Karthikeyan Vedi 17/12/28

5

这是带有代理的很酷的版本:

const myUpsert = (input) => {
    const handler = {
        get: (obj, prop) => {
            obj[prop] = obj[prop] || {};
            return myUpsert(obj[prop]);
        }
    };
    return new Proxy(input, handler);
};

您可以像这样使用它:

myUpsert(test).hello.world = '42';

这会将所有缺少的属性添加为空对象,并使现有属性保持不变。它实际上只是经典的代理版本test.hello = test.hello || {},尽管速度慢得多(请参阅此处的基准)。但是它看起来也要好得多,尤其是如果您要进行一个以上级别的深入研究时。我不会选择它来处理性能繁重的数据,但是它对于前端状态更新(如Redux)可能足够快。

请注意,这里有一些隐含的假设:

  1. 中间属性可以是对象,也可以是不存在的。test.hello例如,如果是字符串,这会阻塞。
  2. 只要您使用代理而不是原始对象,就一直希望这样做。

如果仅在边界有限的环境(如化简器主体)中使用它,这些情况就很容易得到缓解,在这种情况下,意外返回代理的可能性很小,并且您不想对该对象做很多其他事情。


2
var test = {}
if(!test.hasOwnProperty('hello')) {
    test.hello = {};
}
test.hello.world = "Hello World!"

2

如果hello该值{world: 'Hello world!'}不存在,它将为测试对象添加一个属性。如果您有很多这样的对象,则可以对其进行迭代并应用此功能。注意:使用lodash.js

var test = {};
_.defaults(test, { hello: {world: 'Hello world!'} });    

实际上,这是一种方便的说法:

var defaults = _.partialRight(_.assign, function(a, b) {
  return typeof a == 'undefined' ? b : a;
});        
defaults(test, { hello: {world: 'Hello world!'} });

注意:_.defaults使用循环来实现与第二个块相同的功能。

PS Checkout https://stackoverflow.com/a/17197858/1218080


1
_.set({},'abcd',“ asdf”)将创建'{“ a”:{“ b”:{“ c”:{“ d”:“ asdf”}}}}}',并且可能是更多他要找的东西
Rene Wooller

1

我已经提出了一些确实定制的东西,但据我测试,它仍然有效。

function dotted_put_var(str,val) {
    var oper=str.split('.');
    var p=window;
    for (var i=0;i<oper.length-1;i++) {
        var x=oper[i];
        p[x]=p[x]||{};
        p=p[x];
    }
    p[oper.pop()]=val;
}

然后,可以像这样设置一个复杂的变量,以确保将创建每个链接(如果尚未创建):

dotter_put_var('test.hello.world', 'testvalue'); // test.hello.world="testvalue";

看到这个工作的FIDDLE


1

我用这个:

Object.prototype.initProperty = function(name, defaultValue) {
  if (!(name in this)) this[name] = defaultValue;
};

您以后可以做fe:

var x = {a: 1};
x.initProperty("a", 2); // will not change property a
x.initProperty("b", 3); // will define property b
console.log(x); // => {a: 1, b: 3}

1
var test = {}
test.hello.world = "Hello doesn't exist!"

由于您未定义test.hello,这显然会引发错误。

首先,您需要定义hello键,然后在其中可以分配任何键。但是,如果您要创建密钥(如果不存在),则可以执行以下操作

test.hello = test.hello || {};

上面的语句将创建test.hello对象(如果未定义),如果已定义,则将分配与先前相同的值

现在您可以在test.hello中分配任何新密钥

test.hello.world = "Everything works perfect";

test.hello.world2 = 'With another key too, it works perfect';

1

let test = {};
test = {...test, hello: {...test.hello, world: 'Hello does exist!'}};
console.log(test);

使用散布运算符时,该值可以是未定义的,它将自动创建一个对象。


0

我对哥伦布的答案进行了一些更改,以允许创建数组:

function addProps(obj, arr, val) {

  if (typeof arr == 'string')
    arr = arr.split(".");

  var tmpObj, isArray = /^(.*)\[(\d+)\]$/.exec(arr[0])
  if (isArray && !Number.isNaN(isArray[2])) {
    obj[isArray[1]] = obj[isArray[1]] || [];
    obj[isArray[1]][isArray[2]] = obj[isArray[1]][isArray[2]] || {}
    tmpObj = obj[isArray[1]][isArray[2]];
  } else {
    obj[arr[0]] = obj[arr[0]] || {};
    tmpObj = obj[arr[0]];
  }

  if (arr.length > 1) {
    arr.shift();
    addProps(tmpObj, arr, val);
  } else
    obj[arr[0]] = val;

  return obj;

}


var myObj = {}
addProps(myObj, 'sub1[0].sub2.propA', 1)
addProps(myObj, 'sub1[1].sub2.propA', 2)

console.log(myObj)

我认为可以允许使用“ sub1 []。sub2 ...”将其推入sub1数组中,而不是指定索引,但这对我来说已经足够了。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.