0

So I am looking at some code on this question, and I have no idea how this double assignment in one line actually works:

var deepAssign = function( base, names, value ) {
    // If a value is given, remove the last name and keep it for later:
    var lastName = arguments.length === 3 ? names.pop() : false;

    // Walk the hierarchy, creating new objects where needed.
    // If the lastName was removed, then the last object is not set yet:
    for( var i = 0; i < names.length; i++ ) {
        base = base[ names[i] ] = base[ names[i] ] || {}; /* this line wtf? */
    }

    // If a value was given, set it to the last name:
    if( lastName ) base = base[ lastName ] = value;

    // Return the last object in the hierarchy:
    return base;
};
var x = {}
deepAssign(x, ['a', 'b', 'c'])
console.log(x) /* wtf, how? => { a: { b: { c: {} } } } */

I would assume that the original 'base' object would be destroyed in the for loop and that 'base' would then just be the inner object only, but somehow the original passed in object is preserved. Can someone give a detailed explanation of what is going on inside that for loop? It really bothers me to have something that I don't understand inside my code.

2 Answers2

0

Variable assignment can resolve to an expression (something that resolves to a particular value - that is, something that some other variable can hold). The code in the question is a confusing way of writing:

for( var i = 0; i < names.length; i++ ) {
    base[ names[i] ] = base[ names[i] ] || {};
    base = base[ names[i] ];
}

It sets base[names[i]] to an empty object if it doesn't exist yet, and then it reassigns the object that the variable name base points to to that inner object. (The outer object that base originally referred to still exists, there is just no longer a particular variable that references it - it can still be gotten through by using standard property access from the outermost x object, though).

If you're familiar with array methods, reduce would be more appropriate and easier to read in this situation, though: have the accumulator be the current outer object, create an inner object if it doesn't exist yet, and return the inner object to be the new accumulator on the next iteration:

function assign(outermost, keyPath, value) {
  const lastKey = keyPath.pop();
  const innermostObj = keyPath.reduce((outer, prop) => {
    outer[prop] = outer[prop] || {};
    return outer[prop];
  }, outermost)
  innermostObj[lastKey] = value;
}

const settings = {};
assign(settings, ['Modules', 'Video', 'Plugin'], 'JWPlayer');
console.log(settings);
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
0

The following:

base = base[ names[i] ] = base[ names[i] ] || {};

Is translated to:

base[ names[i] ] = base[ names[i] ] || {};
base = base[ names[i] ];

This loop:

for( var i = 0; i < names.length; i++ ) {
    base = base[ names[i] ] = base[ names[i] ] || {}; /* this line wtf? */
}

is translated to:

names.reduce((a, name) => (a[name] || (a[name] = {})), base);

Basically, is creating an object when the key name doesn't exist, however, is a dangerous way of checking key existence.

This is a better approach using the operator in:

names.reduce((a, name) => (name in a ? a[name] : (a[name] = {})), base);
Ele
  • 33,468
  • 7
  • 37
  • 75