Friday, May 29, 2020

this is new

Beware of sharing this.

Here's how to check if your this is new, or when you are accidentally using a shared this.

function Abbey() {
    this.name = 'you know my name';
    console.log('Abbey() called');
    this.jude = function () {
        console.log("Abbey's jude() called");
    }
}

Abbey.prototype.great = function () {
    console.log('great');
};


function jude() {
    console.log('root jude() called');
}


function hey() {
    console.log('Hey() called');
    try {
        this.jude();
    } catch (ex) {
        console.log("I'm not running in node/browser's REPL. Can't resolve this.jude()");
        console.log(ex);
    }
}


new Abbey();
hey();
console.log('\nthis is messy\n');
Abbey(); // forgot the new keyword
hey();


console.log('\n\nnew Abbey():');
// a's this is new. not shared this
var a = new Abbey();
console.log(a);
a.great();

Output:

dev@Developers-iMac % node y.js
Abbey() called
Hey() called
I'm not running in node/browser's REPL. Can't resolve this.jude()
TypeError: this.jude is not a function
    at hey (/Users/dev/Desktop/y.js:22:14)
    at Object. (/Users/dev/Desktop/y.js:31:1)
    at Module._compile (internal/modules/cjs/loader.js:959:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:995:10)
    at Module.load (internal/modules/cjs/loader.js:815:32)
    at Function.Module._load (internal/modules/cjs/loader.js:727:14)
    at Function.Module.runMain (internal/modules/cjs/loader.js:1047:10)
    at internal/main/run_main_module.js:17:11

this is messy

Abbey() called
Hey() called
Abbey's jude() called


new Abbey():
Abbey() called
Abbey { name: 'you know my name', jude: [Function] }
great

It's better to attach the instance's method to .prototype instead of attaching it to this. Aside from that it prevents accidentally using a shared this, it also unclutters the instance's data.

As seen from the output, the jude method become part of the data, whereas the great method doesn't.


The this can accidentally call other code when the code is run in node/browser's REPL. The following is the output of the same exact code above when it is run in REPL instead. Note that root jude is called.

Reduce this confusion

Experimented from: https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback/20279485#20279485


When reusing an instance's method from another object (see great and anotherGreat below), access it directly from the name of the function that holds the prototype, e.g., Beatles

Thursday, May 28, 2020

Make your function .apply.bind-friendly

The this.nice(); code below, though you can invoke your non-instance methods that way, does not mean you should.


Most of the time, Function.apply.bind's second parameter is null for utility functions. Utility functions are those functions that belongs to class, not to the instance of the class.

The .nice won't get invoked when using .apply.bind. Worse yet, when using function-based class (pre-ES6), this points to global this, so if there's a global nice function, it would get called instead.




Though ES6 has first-class support for class, and null will be assigned to this when invoking .apply.bind on class's static method, thus preventing miscalled/hijacked function, it's not a good practice to invoke static (non-instance) methods from a this reference.





Just call the function directly from the parent function/class:






Do note that TypeScript transpilation won't assign null to this when using Function.apply.bind against the generated class. this still receives global this when .apply's first parameter is passed with null value.

The transpilation can't prevent miscalled/hijacked function.






Note that miscalled/hijacked function happens only on node's REPL or browser's console tool. It does not happen when invoked directly via node:

Wednesday, May 27, 2020

Confuse yourself less when using .bind

.apply.bind, .call.bind

To make a shortcut for Math.max.apply(null, [5,8,6])

Use this:
var maxArray = Function.apply.bind(Math.max, null);

var maxValue = maxArray([5,8,6]);


This question was asked on stackoverflow:
Why it's important to pass null for bind as argument?


Without the null on bind:
var maxArray = Function.apply.bind(Math.max);


When maxArray([5,8,6]) is called, it is invoked like:

It's invoking the .apply on Math.max as:
Math.max.apply([5,8,6]);

The parameter [5,8,6] is assigned to this reference.

So both maxArray([5,8,6]) and Math.max.apply([5,8,6]) are basically invoking the Math.max without any parameters, and the [5,8,6] assigned to this reference.
Math.max();

We will see how .apply.bind works on our own object later and see its effect on this reference.

Both results to -Infinity:
dev@Developers-iMac % node
Welcome to Node.js v12.14.0.
Type ".help" for more information.
> var maxArray = Function.apply.bind(Math.max);
undefined
> maxArray([5,8,6]);
-Infinity
> Math.max.apply();
-Infinity
> Math.max();
-Infinity
>


We can invoke the maxArray properly like the following, but it somehow defeats the purpose of automagically passing the null on first parameter of Math.max.apply(null, [5,8,6]) expression
dev@Developers-iMac % node
Welcome to Node.js v12.14.0.
Type ".help" for more information.
> var maxArray = Function.apply.bind(Math.max);
undefined
> maxArray(null, [5,8,6]);
8


This is the result if we pass three parameters to Function.apply.bind:
dev@Developers-iMac % node
Welcome to Node.js v12.14.0.
Type ".help" for more information.
> var maxArray = Function.apply.bind(Math.max, null, [10, 40, 30]);
undefined
> maxArray()
40
> maxArray([50,60])
40
>

As for the .call, it is the same as .apply, as both them always require an object to be passed on their first parameter, which will then gets assigned to this reference, usually null is used as reference for this for utility functions. .apply is more flexible than .call, as .apply can grow its parameter, whereas .call's parameters are fixed at invocation.

Guess the output of Math.max.call(8,7,6)?

dev@Developers-iMac % node
Welcome to Node.js v12.14.0.
Type ".help" for more information.
> Math.max(8,7,6)
8
> Math.max.call(8,7,6)
7
> Math.max.apply(8,7,6)
Thrown:
TypeError: CreateListFromArrayLike called on non-object
> Math.max.apply([8,7,6])
-Infinity
> Math.max.apply([8,7,6],[2,4,3])
4
> Math.max.apply(null,[2,4,3])
4
> Math.max.call(null,8,7,6)
8

Here are other observations:




Why .apply.bind requires two parameters:




As we can see, if we don't pass an object reference to first parameter of .bind and we pass only one parameter, that passed parameter gets assigned to *this* reference, as seen on theThis property on line 7, likewise on this[0] of line 8. Then the actual parameter of the method something receives nothing (undefined).

Tuesday, May 5, 2020

flex is flexible, but it's not griddable

<div id="band">
    <div style="background-color: red">John</div>
    <div style="background-color: green">Paul</div>
    <div style="background-color: blue">George</div>
    <div style="background-color: yellow">Ringo</div>
</div>


<style>
    body {
        background-color: slategray;

        display: flex;
        justify-content: center;
        align-items: center;
    }

    #band {
        background-color: white;

        margin: 0 auto;
        border-style: solid;
        border-width: 2px;

        max-width: 740px;
        width: 100%;

        display: flex;
        /* justify-content: flex-start; */ /* flex-start is the default justify-content */
        flex-wrap: wrap;
    }

    #band > div {
        width: 200px;
        height: 200px;

        /* center all the names inside each box */
        display: flex;
        justify-content: center;
        align-items: center;
    }
</style>


Its output is:




If we change the justify-content to center:
display: flex;
justify-content: center;
flex-wrap: wrap;


Not only the first row's items are centered, the last item will be centered as well, which is not we want:



If we want to wrap extra items at leftmost and keep the whole grid of items at the center, we need to use grid instead of flex, and replace flex-wrap: wrap to repeat autofit and minmax. To wit:
display: grid;
justify-content: center;
grid-template-columns: repeat(auto-fit, minmax(200px, max-content));

The output is correct:



On the above display grid and grid-template-columns settings, if we change the justify-content to end:
display: grid;
justify-content: end; /* flex-end works as well, but we will just be confusing ourselves with that, we are using grid not flex */
grid-template-columns: repeat(auto-fit, minmax(200px, max-content));

The output is:


To contrast with flex-end of display: flex:
display: flex;
justify-content: flex-end;
flex-wrap: wrap;

The output is:


Same settings above, but with five items:



Let's use grid again with justify-content of end:
display: grid;
justify-content: end;
grid-template-columns: repeat(auto-fit, minmax(200px, max-content));

Its output:



This flex's flex-start:
display: flex;
justify-content: flex-start;
flex-wrap: wrap;

And grid's start:
display: grid;
justify-content: start;
grid-template-columns: repeat(auto-fit, minmax(200px, max-content));

Have same output: