2

Look at the call Stack and Watch, Now when i press F10, the line's value in "Watch" changes to be "object Object" and because of that everything happens, i understand the flow till this time and afterwards what happen is not clear. Now on wards, why does the code doesn't stop at line 8 even if I Provide a break point at line 8

ImageThis is the question on stackoverflow

I am trying to understand this code snippet

function assign(obj, prop, value) {
    if (typeof prop === "string")
        prop = prop.split(".");

    if (prop.length > 1) {
        var e = prop.shift();
        assign(obj[e] =
                 Object.prototype.toString.call(obj[e]) === "[object Object]"
                 ? obj[e]
                 : {},
               prop,
               value);
    } else
        obj[prop[0]] = value;
}

var obj = {},
    propName = "foo.bar.foobar";

assign(obj, propName, "Value");

How recursion is happening, as the function is not returning anything ? How the value of the arguments in the inner call being changed as the function in the top most call stack(of assign function) is completed ?

Thanks all for answers but my exact question i how its happening in this particular line (line 8).

Community
  • 1
  • 1
Ankur Marwaha
  • 1,613
  • 2
  • 15
  • 30
  • 1
    Objects in javascript are passed by reference, not by value. This means that when you pass an object reference as the argument of a function, the argument of the function is still referring to the same object. –  Aug 21 '16 at 04:44
  • It might be useful to walk through the code with a debugger. –  Aug 21 '16 at 04:47
  • Already done with debugging, can you guys explain, how this - Object.prototype.toString.call(obj[e]) - value is comming out to be "object Object" for the first time and why the code is not stopping at the inner call of the function when the function in the highest call stack starts returning even if i have applied a break point there ? – Ankur Marwaha Aug 21 '16 at 05:08
  • `Object.prototype.toString.call(obj[e]) === "[object Object]"` shouldn't return `true` in the first run in the above snippet. I think it could also be checked as `typeof obj[e] === "object"` On the other hand even if not needed it's better if these functions return the resulting object for chaining purposes. – Redu Aug 21 '16 at 06:23

3 Answers3

3

Object.prototype.toString.call(obj[e]) === "[object Object]" is never true. It is always "[object Undefined]"

  1. At the beginning:

    obj:: {}
    prop:: 'foo.bar.foobar'
    
  2. Before first call from inner function (if):

    obj:: {foo: {}}
    prop:: ['bar', 'foobar']
    
  3. Before second call from inner function (if):

    obj:: {foo: {bar: {}}}
    prop:: ['foobar']
    
  4. At last execution, as prop.length is 1 (else - no more recursion):

    obj:: {foo: {bar: {foobar: 'Value'}}}
    prop:: []
    

Objects in Javascript are always passed as Reference, refer to @Tiny Giant's comment.

nim_10
  • 477
  • 8
  • 21
  • If I add the obj to watch and observe its value, it changes like this ---------------- obj = {} and then {foobar: 'Value'} and then {bar: {foobar: 'Value'}} and then {foo: {bar: {foobar: 'Value'}}} . :( I can't understand how it changes from {foobar: 'Value'} to {bar: {foobar: 'Value'}} and so on. – Ankur Marwaha Aug 24 '16 at 10:07
  • That's right. Actually, the obj in watch is updated when the recursive call is returned too! Instead of keeping track in watch, just place console.log() and print the obj where you need. – nim_10 Aug 24 '16 at 13:43
1

Object.prototype.toString.call(obj[e]) === "[object Object]" is just a really ugly way of saying "(if) this object actually exists...". I won't explain in detail how it works, because it's not a good example and you may not understand it.

What the code wants is to be able to add/edit an object property. For example:

var person = {name: 'Liana', hair: {color:'red', style:'long'}};
assing(person, 'name', 'George');

...etc. But what if you wanted to edit a nested attribute, like the hair color?

assign(person, 'hair.color', 'brown');

Basically, the assign function is called twice:

  • assign(person, 'hair.color', ...)
  • This detects we are editing a nested attribute (color) inside hair, and it makes sure hair actually exists inside person.
  • If not, it creates person.hair (That's what the ugly line is about)
  • Then calls assing(person.hair, 'color', 'brown');

I would personally write the function like this:

function assign(object, prop, value) {
    prop = prop.split('.');

    if(prop.length > 1) {
        var first = prop.shift();
        prop = prop.join('.');
        if(typeof object[first] != 'object')
            object[first] = {};
        assign(object[first], prop, value);
    } else {
        object[prop] = value;
    }
}
mauroc8
  • 447
  • 1
  • 3
  • 9
  • Your code snippet is awesome, I wish i could accept that answer but my question is about the working flow of the code that i wrote. – Ankur Marwaha Aug 21 '16 at 09:19
  • @Promote Nabid answer explains it better than me. I wrote that code because I thought that it may help you understand what the original code does. (Which actually is the same thing, just written in a more obscure way) – mauroc8 Aug 23 '16 at 22:47
1

How recursion is happening, as the function is not returning anything ?

Returning a value is not necessary for recursion to happen. Here, the condition prop.length > 1 will stop the recursion after setting value to the desired property

How the value of the arguments in the inner call being changed as the function in the top most call stack(of assign function) is completed ?

I am not sure what you are asking about here. I think you are asking about this line

assign(obj[e] =
                 Object.prototype.toString.call(obj[e]) === "[object Object]"
                 ? obj[e]
                 : {},
               prop,
               value);

This code checks whether obj is object or not. If obj is not an object, then it will be changed to an object in the recursion call itself.

Kira
  • 1,403
  • 1
  • 17
  • 46