Object method using between lexical scope or this-binding?

Viewed 229

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.

3 Answers

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).

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)

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

Related