4

I am having trouble determining what concept explains the reason as to why the value of the object's property "count" is retained in the code below.

I have read and reviewed the this and object prototype section from Getify's You Don't Know JS as well as their section explaining lexical this. However, I am not able to understand my code below. Is it lexical scoping? Or is it a this binding that allows the value of count to retained?

Below is the example code:

var obj = {
    count: 0,
    method: function() {
        console.log("in method: " + this.count)
        return this.count++;
    },
}

// here is where I have issue, when the method is invoked as a function
for (var i = 0; i<10; i++) {
    console.log(obj.method()) // invoked as a function
}

// I've left this small block in for convenience
// I have no trouble with understanding why this block outputs what it outputs
for (var i = 0; i<10; i++) {
    console.log(obj.method) // "gets its value (a reference to a function) and then logs that" from TJ Crowder
}

I expect the output of the first method call to obj.method() to output

// 0
// in method 0
// 1
// in method 1
// 2
.
.
.
// 10
// in method 10

I have no problem with what is output. My question again is, Is it lexical scoping? Or is it a this binding that allows the value of count to retained?

Thank you for taking your time to help.

Edit 1 With help from Tj Crowder's post below, I edited code snippet to clear up mistakes because it detracted from my question.

Kyle Fong
  • 131
  • 3
  • 12
  • Perhaps it could help to distinguish the different log calls? Then you could see which one prints which output – Nico Haase Jan 08 '19 at 08:28
  • 4
    You are returning nothing from `method()` function, so it print `undefined`. Wanted to do `return this.count;` ? – Orelsanpls Jan 08 '19 at 08:30
  • I've edited the snippet to express the original intent of my question. The code I had provided originally was misleading as to the root problem and to what ya'll had responded to, I apologize and thank you for responding. – Kyle Fong Jan 08 '19 at 08:53

3 Answers3

3

The issue has nothing to do with this or scoping. :-) You see the undefineds because method doesn't return anything, so calling it results in the value undefined, which you're logging via console.log. To make it return the value of count, you'd add a return:

method: function() {
    console.log(this.count)
    return this.count++;
//  ^^^^^^
},

That returns the value of this.count as it was prior to the increment (which seems to be what you expect from your expected output).

Live Example:

var obj = {
    count: 0,
    method: function() {
        console.log("in method: " + this.count)
        return this.count++;
    },
    timeOutMethod: function() { // I understand here we explicitly bind this, no problem here
        setTimeout(function() {
            console.log(this.count++)
        }.bind(this), 100)
    }
}

// here is where I have issue, when the method is invoked as a function
for (var i = 0; i<10; i++) {
    console.log(obj.method()) // invoked as a function
}
.as-console-wrapper {
  max-height: 100% !important;
}

Separately, on this:

for (var i = 0; i<10; i++) {
    console.log(obj.method) // invoked as a property
}

that doesn't invoke method at all, it just gets its value (a reference to a function) and then logs that (you'll see some representation of the function in the console).

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    Total forehead slap! Not providing the return value certainly explains why I was getting undefined. Thank you! And separately, you've cleared up a confusion I've had for awhile - I was confused about what occurs when a property is accessed and logged to the console and how to explain it accurately. – Kyle Fong Jan 08 '19 at 08:44
1

It is binding.

Scope is the concept of what variables are accessible and what variables the language hide from you. In machine language all memory addresses are readable and writable so in machine language and some assembly languages the concept of scope does not exist (all variables are basically global). Later languages introduced the concept of global vs local variables with the introduction of functions. This concept was further evolved into closures - the ability to create multiple instances of scopes.

Binding is the concept of which property/attribute belong to which object. In languages like Java and C++ which implement early binding the concept of binding merely governs how methods access properties (usually this allows the language to not need a "this" keyword). Late binding languages have slightly more complicated rules because the binding is determined at runtime instead of compile time. Javascript is not only late binding but also dynamic - allowing programmers to change what object this point to using things like Function.prototype.call(). And assigning methods of one object to another object at runtime (eg. b.foo = a.foo)

slebetman
  • 109,858
  • 19
  • 140
  • 171
  • thank you for the historical context of the use of scope and binding in programming languages. I re-read resources on 'this' and lexical scoping and *hopefully* have a correct understanding of how this operates. I posted an explanation of what I think may be correct in assuming is the mechanism that allows this.count property to be accessed. Would you care to look it over and see what you agree or disagree upon and how my explanation could be improved? – Kyle Fong Jan 09 '19 at 00:30
0

TL;DR The mechanism that binds the property count to the this value is implicit binding (Getify's You Don't Know JS), Implicit Binding

First If I've came to a false conclusion in my explanation, would you care to provide what you agree or disagree upon, and share how should I be thinking about this issue? I'm looking to improve, Thanks!

Explanation: When obj.method() is invoked, we have a property lookup for this.count. If we inspect the call-site at the time of invoking obj.method(), obj is variable exists in the global scope. The properties of obj are accessed via the prototype chain. When we execute the property lookup of this.count, we attempt to access the property within obj. When the property is not found, we look up the prototype chain for the property. obj.count contains/owns the property count at the time we access the count property via the 'this' binding.

Causes of my issue: I mixed concepts of how this-binding operates in arrow-fcns., lexical scope, and having too literal of an interpretation of 'this'.

Examples

Below are examples of code that were sources of my confusion. My issue arose from conflating the conventions of how this-binding operates in different styles of code. I mixed up concepts from the following:

  1. How this keyword operates in arrow functions (i.e. lexical scope)

    function wait() {
        setTimeout(() => {
            console.log(this) // lexically bound
        }, 100);
    }
    
    wait();
    
    function foo() {
        // console.log(this) - will output {a: 2}
        return (a) => {
            console.log(this.a) // 'this' value is adopted from foo's lexical scope
        }
    }
    
    var obj = {
        a: 2
    }
    
    var obj2 = {
        a: 3
    }
    
    // foo is 'this'-bound to obj1
    // bar, a reference to the returned arrow-function will also be 'this'-bound to obj1
    // the arrow function binding cannot be overridden
    var bar = foo.call(obj) 
    bar.call(obj2)
    
  2. Instead of understanding how this really works, lexical scope is used as a workaround. Here, data.count is incremented

    function foo() {
        return data.count++; // the obj property data.count is accessed via global scope
    }
    
    var data = {
        count: 0,
    }
    
    for (var i = 0; i<10; i++) {
        foo()
    }
    
    console.log(data.count)
    
  3. And finally, another misunderstanding can arise from taking too literal an interpretation of the this-binding. I cite the snippet is from Getify's book

    function foo(num) {
        console.log( "foo: " + num );
    
        // keep track of how many times `foo` is called
        this.count++;
    }
    
    foo.count = 0;
    
    var i;
    
    for (i=0; i<10; i++) {
        if (i > 5) {
            foo( i );
        }
    }
    // foo: 6
    // foo: 7
    // foo: 8
    // foo: 9
    
    // how many times was `foo` called?
    console.log( foo.count ); // 0 -- not what we expected
    

Aside: Thank you to @TJ Crowder and @slebetman for their help in clarifying other misunderstandings on scope and this-binding

Kyle Fong
  • 131
  • 3
  • 12
  • 1
    You really need to study about how objects work in javascript. `foo()` is a function and while yes, functions are objects as well, having `this.count` inside `foo` does not add the property `count` to the function `foo`. Instead it attempts to access the property `count` from the object `foo` is attached to. If you for example call `bar.foo()` it would access `bar.count`. If you call `foo()` it will be the property `count` of the global object, which if this was executed in a browser would be `window.count`, not `foo.count` (node.js is more complicated) – slebetman Jan 09 '19 at 03:38
  • Remember, binding and scopes are not at all related to each other. Scope is about variable hiding (local vs global vs closure) - it's about how functions behave. Binding is about the structure of objects (not functions) – slebetman Jan 09 '19 at 03:40
  • Also, you really should study more about closures (what you seem to think you need to work around and call "lexical scope"). If you understand the difference between global and local variables then it's easier to understand closures. – slebetman Jan 09 '19 at 03:42
  • 1
    Another comment. Stop thinking about context (as you are using it here) when you want to explain binding. Context is ONLY INVOLVED in scopes. You need to think about inheritance chains instead. See, like I said, they are not at all related to each other. (yes, in the ECMAScript standard they use the word "context" with two different meanings but an object's "context" is never related to the word "global") – slebetman Jan 09 '19 at 03:45