Автоматично створювати об’єкт, якщо він не визначений


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 це та сама проблема, що і з об'єктами.


функція існуєcheckthingy (x, y, z) {if (x === невизначено) x = {}; xy = z; } використовується як існуєcheckthingy (test.hello, world, "Привіт не існує!");
bobbybee

2
@bobbybee Це не спрацює. Він створить новий об’єкт xв області дії existcheckthingyфункції, але його тоді не буде приєднано до testоб’єкта. Ви можете зробити краще, використовуючи "схожий на масив" запис:existcheckthingy(a,x,y,z) { if (a[x] === undefined) a[x] = {}; a[x][y] = z;}
Джефф


@Jeff ой, забув про це: 3
bobbybee

2
розгляньте lodash 'set'
Rene Wooller

Відповіді:


133
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

Це дуже коротке спасибі за це. Але це все одно ручна перевірка. Хіба не можливо без?
Марсель Гвердер

3
@MarcelGwerder ні, це як можна коротше.
Alnitak

@MarcelGwerder: Не наскільки я знаю.
xbonez

1
@xbonez NB: не "визначено" - істинно . Однак порожній предмет насправді неправдивий.
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

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 "}}}} ', і може бути більше того, що він шукав
Рене Вуллер

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масив, замість того, щоб вказати індекс, але цього мені зараз досить.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.