2

I find myself getting tripped up by console.log() a lot, because of it's asynchronous nature.

By that I mean it's tendency not to capture a variable's value at a specific point in time.

It work's fine for simple values because assignment to a variable with an immediate value binds the variable to an entirely new object., e.g.

var x = 3;
console.log(x);  //prints 3
x=4; 

But when you start working with objects that are bound by reference, things get... counter-intuitive. e.g.

var obj = {x: 3};
console.log(x); //prints 4!
obj.x = 4;

Is there some other logging function I can use that will provide me with the state of an object at the time of invocation in terms of my code? I'm looking for something that's either synchronous or at least appears to be from the result it yields.

I'd be nice if it worked cross platform, but I would be happy just to get one that works in chrome.

Luke
  • 5,567
  • 4
  • 37
  • 66
  • `console.log(JSON.stringify(x))`? – Kyll Dec 09 '15 at 01:31
  • 2
    What @Kyll says works great if `x` is not circular, and is serialisable. When it isn't, you have to improvise. In those cases, `console.log(x); debugger;` works all right to pause the code so you can inspect the value before it changes. Unfortunately, `temp = val; console.log(temp)` does not work: it is not a deep copy, so if you change `val`'s properties, `temp`'s properties change the same way. – Amadan Dec 09 '15 at 01:35
  • I never knew about debugger! https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger – Luke Dec 09 '15 at 01:39
  • I'd suggest also digging more into deep-cloning algorithms. – Kyll Dec 09 '15 at 01:40
  • Indeed. [This `clone` function](http://stackoverflow.com/a/122190/240443) would probably work for most cases: `console.log(clone(x))`. – Amadan Dec 09 '15 at 01:41
  • @Kyll can you give me an example of a case where using the `debugger` statement wouldn't do what I want? – Luke Dec 09 '15 at 01:41
  • `for (var i = 0; i < 100000; i++) { console.log(a[i]); debugger; }` would be extremely annoying. :P – Amadan Dec 09 '15 at 01:41
  • If one of you would be kind enough to summarize this dialog into an answer I would gladly accept! – Luke Dec 09 '15 at 01:43

2 Answers2

3

As requested, a summary:

For simple, serialisable objects, console.log(JSON.stringify(x)) works well:

var x = { foo: 17, bar: "quux" };
console.log(JSON.stringify(x))
// => {"foo":17,"bar":"quux"}

For HTML elements, console.log(x.outerHTML) works well.

var x = document.getElementsByTagName('p');
console.log(Array.prototype.map.call(x, function(e) {
  return e.outerHTML;
}));
// => ["<p>foo</p>", "<p id="bar">bar</p>"]
<p>foo</p>
<p id="bar">bar</p>

If objects are not serialisable, you might want to drill into them and extract the serialisable parts:

var x = { content: { foo: 17, bar: "quux" } };
x.self = x;
console.log(JSON.stringify(x.content));
// => {"foo":17,"bar":"quux"}

If none of these tricks apply, you might need to deep-clone the object (this answer gives a very nice clone function that I use below, and many caveats):

function clone(item) {
    if (!item) { return item; } // null, undefined values check

    var types = [ Number, String, Boolean ], 
        result;

    // normalizing primitives if someone did new String('aaa'), or new Number('444');
    types.forEach(function(type) {
        if (item instanceof type) {
            result = type( item );
        }
    });

    if (typeof result == "undefined") {
        if (Object.prototype.toString.call( item ) === "[object Array]") {
            result = [];
            item.forEach(function(child, index, array) { 
                result[index] = clone( child );
            });
        } else if (typeof item == "object") {
            // testing that this is DOM
            if (item.nodeType && typeof item.cloneNode == "function") {
                var result = item.cloneNode( true );    
            } else if (!item.prototype) { // check that this is a literal
                if (item instanceof Date) {
                    result = new Date(item);
                } else {
                    // it is an object literal
                    result = {};
                    for (var i in item) {
                        result[i] = clone( item[i] );
                    }
                }
            } else {
                // depending what you would like here,
                // just keep the reference, or create new object
                if (false && item.constructor) {
                    // would not advice to do that, reason? Read below
                    result = new item.constructor();
                } else {
                    result = item;
                }
            }
        } else {
            result = item;
        }
    }

    return result;
}


var el = document.querySelector('p');
x = { el: el, content: { foo: 17, bar: "quux" } };
console.log("el.attributes[0]: changed", x);
console.log("el.attributes: empty", clone(x));
el.setAttribute('changed', 'true');
<p>foo</p>

In the worst case, you can always pause the execution:

var el = document.querySelector('p');
for (var i = 0; i < 1000; i++) {
  el.textContent = "Iteration #" + i;
  // check 458th iteration:
  if (i == 458) {
    console.log("Accurate:", el);
    debugger;
    console.log("Not accurate:", el);
  }
}
<p></p>
Community
  • 1
  • 1
Amadan
  • 191,408
  • 23
  • 240
  • 301
  • You use ES5 methods in the clone snippet but use `Object.prototype.toString.call( item ) === "[object Array]"`, why not `Array.isArray`? If `item.constructor` is a `getter` it will be called, best to use `Object.getOwnPropertyDescriptor` to get the `value` so a `getter` will not be called. – Xotic750 Dec 09 '15 at 13:13
  • @Xotic750: Not my snippet; I linked the source. – Amadan Dec 09 '15 at 13:55
2

Another possibility to serialising with JSON or deep copying would be to use Node's inspect for serialising. I have made a port (inspect-x) that does 90% of it that works on browsers (not everything is possible in the browser).

var inspect = returnExports;
var x = {
  foo: 17,
  bar: 'quux',
  arr: [1,'2', undefined, null, false],
  fum: function blah() {},
  fee: new ArrayBuffer(4),
  woo: new Date(),
  wee: /match/gi,
  wiz: 1,
  poo: true,
  pee: Object('hi'),
  jqu: $(document.body),
  ppp: document.getElementsByTagName('pre')
};
document.getElementById('out').appendChild(document.createTextNode(inspect(x, {showHidden: true})));
console.log(inspect(x, {showHidden: true}));
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.3.1/es5-shim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.3.1/es5-sham.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/json3/3.3.2/json3.min.js"></script>
<script src="https://rawgit.com/Xotic750/inspect-x/master/lib/inspect-x.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<pre id="out"></pre>

Which you can compare against JSON.stringify

var inspect = returnExports;
var x = {
  foo: 17,
  bar: 'quux',
  arr: [1,'2', undefined, null, false],
  fum: function blah() {},
  fee: new ArrayBuffer(4),
  woo: new Date(),
  wee: /match/gi,
  wiz: 1,
  poo: true,
  pee: Object('hi'),
  jqu: $(document.body),
  ppp: document.getElementsByTagName('pre')
};
document.getElementById('out').appendChild(document.createTextNode(JSON.stringify(x, null, 2)));
console.log(JSON.stringify(x, null, 2));
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.3.1/es5-shim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.3.1/es5-sham.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/json3/3.3.2/json3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<pre id="out"></pre>

As you can see inspect gives much more information and can be configured for greater depths.

Xotic750
  • 22,914
  • 8
  • 57
  • 79