Tuesday 22 November 2016

Javascript: 'this' and call

In this post, my assertion that

impl.apply(impl, deps) = impl.apply(null, deps);

just shows how naive I was due to the lack of understanding of 'this' mechanism.

From You Don't Know JS: this & Object Prototypes

function foo(num) {
    console.log( "foo: " + num );

    // keep track of how many times `foo` is called
    // Note: `this` IS actually `foo` now, based on
    // how `foo` is called (see below)
    this.count++;
}

foo.count = 0;

var i;

for (i=0; i<10; i++) {
    if (i > 5) {
        // using `call(..)`, we ensure the `this`
        // points at the function object (`foo`) itself
        foo.call( foo, i );
    }
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9

// how many times was `foo` called?
console.log( foo.count ); // 4 
The first parameter foo essentially makes 'this' point to the foo object.
Now replace the line with

foo.call( null, i );

It prints out '0' because this refers to window object now.

I am tempted to boldly make this assertion that the following two pairs of method invocation can always be used interchangeably.

foo.call( foo, i ) = foo.bind(foo)(i)
foo.call( null, i) = foo(i)  

Let's see when I can be proved wrong.


Javascript: dabble in 'this'

It is probably going to take me a while to fully grasp the 'this' concept in Javascript. Today I just dabble in the topic and present my shallow understandings.

It still comes from You Dont Know Js Scope and Closure, Appendix C: Lexical this.

var obj = {
    id: "awesome",
    cool: function coolFn() {
        console.log( this.id );
    }
};

var id = "not awesome";

obj.cool(); // awesome

setTimeout( obj.cool, 100 ); // not awesome

It looks like when invoked from setTimeout(), this refers to the window object. Then the author introduced various solutions, including self, bind(), and a new feature of ES6: arrow function. These are all cool stuff. But how to address the original problem -- that is, to get setTimeout() function to print 'awesome' as well?

The simplest solution is replace this with obj

console.log( obj.id );

Then I attempt to use bind().

cool2: function coolFn2() {
    console.log( this.id );
}.bind(obj)
It doesn't work because obj cannot be referenced within itself, well, unless it is referenced within a function. Therefore the 2nd attempt.

cool3: function coolFn3() {
    (function(){
        console.log(this.id); 
    }.bind(obj))();
} 
Hey it works. I wouldn't have come up with this solution if I hadn't read the previous chapters of 'You Dont Know Js'. So once again, kudos to the author.

Javascript: apply

In You Dont Know JS Chapter 5: Scope Closure, there is following code snippet to illustrate the power of module pattern.

var MyModules = (function Manager() {
    var modules = {};

    function define(name, deps, impl) {
        for (var i=0; i<deps.length; i++) {
            deps[i] = modules[deps[i]];
        }
        modules[name] = impl.apply(impl, deps);
        console.log("module "+modules[name]);
    }

    function get(name) {
        return modules[name];
    }

    return {
        define: define,
        get: get
    };
})();

MyModules.define( "bar", [], function(){
    function hello(who) {
        return "Let me introduce: " + who;
    }

    return {
        hello: hello
    };
} );

MyModules.define( "foo", ["bar"], function(bar){
    var hungry = "hippo";

    function awesome() {
        console.log( bar.hello( hungry ).toUpperCase() );
    }

    return {
        awesome: awesome
    };
} );

var bar = MyModules.get( "bar" );
var foo = MyModules.get( "foo" );

console.log(bar.hello( "hippo" )); // Let me introduce: hippo

foo.awesome(); // LET ME INTRODUCE: HIPPO

What does this line do?
modules[name] = impl.apply(impl, deps);

It is another way of invoking a function, sort of like Java reflection. And we get to pass an array variable in the second argument to replace a list of parameters. What is the first argument for?

According  to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply

fun.apply(thisArg, [argsArray])

Parameters

thisArg
The value of this provided for the call to fun. Note that this may not be the actual value seen by the method: if the method is a function in non-strict mode code, null and undefined will be replaced with the global object, and primitive values will be boxed.
I don't quite understand this definition. As far as I am concerned,

fun.apply(thisArg, [argsArray]) = thisArg.fun(arg1, arg2....)

In this case, I also don't understand why the first parameter is 'impl'. But I have proved it can be replaced with 'null'.

The next question is why the function needs to be called in such a unconventional way. Could it be called normally?

If we put

modules[name] = impl(deps);

bar.hello() executes OK, but foo.awsome() throws TypeError: bar.hello is not a function.

Then try

modules[name] = impl(deps[0]);

It does work. But obviously it's not a universal solution. Hence the use of apply().