Prototype, this, and lambda functions in JavaScript
Events in JavaScript are always asynchronous which means they most likely expect a callback function. For example:
setTimeout( whatHappensNext );
Note that there are no parentheses next to the whatHappensNext function name!
Look at the following code to grok the difference between parentheses and no parentheses when it comes to functions in JavaScript:
function add(a, b) { console.log("called add"); return a + b; // the return object (a plus b) } add; // Does nothing add(); // Calls add var four = add; // points function add to variable four var five = add(2,3); // calls function add // and points the return object to variable five console.log(four); // [Function: add] console.log(five); // 5
So remember, the function name without parentheses is just a pointer to the function object.
If you for example point variable A at a function object, writing A will do nothing, while writing A() with parenthesis will call the function object.
If you then point another variable B at variable A without parenthesis, variable B will point at the same function object as variable A.
But if you point variable B at variable A() with parenthesis, variable B will point to the object returned by the function object.
this
What "this" points to in JavaScript depends on if the function was called with the new keyword ...
If it was called with the "new" keyword, "this" points to the class/function object. While if no "new" keyword was used "this" points to the global scope (or the global variable in NodeJS).
function Cat(name) { var cat = this; cat.name = name; console.log(cat); } Cat("Garfield"); // console log's the global scope because no "new" keyword new Cat("Garfield"); // console log's: Cat { name: 'Garfield' }
So the "this" variable is only used/useful when creating custom objects by using the "new" keyword.
Note that I used capital C in Cat to give a hint that Cat is a custom object and not a function!
Prototype
In JavaScript, objects can share properties and methods (functions) with other objects! This is called the object's prototype.
Lets say you have two arrays, they can both use the push method:
var arrA = [1,2,3]; var arrB = [4,5,6]; arrA.push(4); // Calls Array.prototype.push, where "this" points to arrA's object arrB.push(7); // Calls Array.prototype.push, where "this" points to arrB's object
When to use the new keyword
Use the new keyword to create new custom objects, and not when calling functions.
The built in custom objects String, Number, Array, Boolean, RegExp and Object doesn't need the new keyword:
var string = "abc"; // new String var number = 42; // new Number var array = []; // new Array var boolean = true; // new Boolean var regexp = /str/g; // new RegExp var object = {}; // new Object
Also note that the native objects are immutable, meaning you can not change them, only create new ones.
var luckyNr = 8; // new Number luckyNr = 7; // new Number
Remember the difference between custom objects and functions!
function CustomObject() {}; // use with new function customFunction() {};
There is an unwritten rule in the JavaScript community that the first letter in custom objects should be named with Big/Uppercase Captial letter.
You can also ask yourself: Do I want to create a new object, or call a function ?
Prototype vs "this"
Your custom objects can also have prototype properties and methods.
But remember that prototype functions are just normal functions!
function Cat(name) { var cat = this; cat.name = name; } Cat.prototype.meow = function meow() { var cat = this; console.log(cat.name + " says meow"); } console.log(Cat("Garfield")); // console log's "undefined" because nothing was returned console.log(new Cat("Garfield")); // console log's "Cat { name: 'Garfield' }" var garfield = Cat("Garfield"); // "this" will point to the global scope // because the "new" keyword was omitted. var newGarfield = new Cat("Garfield"); // "this" will point to // the custom Cat object Cat.prototype.meow(); // console log's "undefined says meow" // because "this" points to the global scope, // because the function is called directly newGarfield.meow(); // console log's "Garfield says meow", // because the function is called via the Cat object garfield.meow(); // TypeError: Cannot read property 'meow' of undefined, // because calling Cat() didn't return an object
Variable this in (prototype) functions points either to the global scope or the (custom) object depending on if it's called directly or via (custom) object.
Events vs "this"
Prototype functions are just normal functions ...
function Worker(name) { var worker = this; worker.name = name; } Worker.prototype.next = function next() { var worker = this; console.log(worker.name + " doing work!"); } var worker = new Worker("Robot A"); setTimeout( worker.next , 3000); // Points to the actual function, // so when it's called in the future "this" // points to the global scope setTimeout( worker.next(), 3000); // Called right away, // passes the return object to // the setTimout function
Remember the difference between calling the function using parentheses and pointing to the function (without parentheses) ...
In the first setTimeout, we pass the pointer (variable name) to the worker.next prototype function.
When it's later called by setTimeout "this" in Worker.prototype.next will point to the global scope.
Because "this" inside Worker.prototype.next will point to the global scope, console.log prints:
"undefined doing work!", because name is not defined in the global scope.
For it to work, you have to run worker.next() inside a function ...
Lambda function
Where JavaScript expect a callback function, like in setTimout, window.onload, button.addEventListener, etc it's always safe the give a named lambda function that in turn does what you want, for example calling a prototype method.
Optimization note: Functions add a lot over overhead, although if it's run many times, most JavaScript engines will inline them, so it doesn't matter.
setTimeout( function runWorkerNext() { worker.next(); }); // EcmaScript 6: (warning: function will be anonymous) setTimeout( () => { worker.next(); }); setTimeout( () => worker.next() );
Only use the arrow function when you need a quick (as in fast to type) throwaway function
Why should I name lambda functions ?
By naming the functions it gets easier to debug and lift them out:
setTimeout( runWorkerNext ); // Lifted out function runWorkerNext() { worker.next(); }
Lifting functions out can be good for readability and prevents the Christmas tree from hell or Pyramid of doom when calling many asynchronous functions after each other.
And stack traces with named functions are more helpful then stack traces with anonymous functions.
this in subfunctions
function Cat(name) { this.name = name; } Cat.prototype.meow = function meow() { console.log(say("meow")); function say(sound) { // "this" in here points to the global scope return this.name + " says " + sound; } } Cat.prototype.meow2 = function meow() { // Only works in EcmaScript 6+ // Arrow functions binds "this" to the lexical (function) scope say = (sound) => this.name + " says " + sound; console.log(say("meow")); } Cat.prototype.meow3 = function meow() { var cat = this; // Point to the right "this" console.log(say("meow")); function say(sound) { return cat.name + " says " + sound; } } var garfield = new Cat("Garfield"); garfield.meow(); // undefined says meow garfield.meow2(); // Garfield says meow garfield.meow3(); // Garfield says meow
Always start custom objects and it's prototype functions by saving "this" in a variable!
Factory functions
If you think "this" and "new" is too confusing. You can use factory functions:
Optimization note: Factory functions are much slower the prototype's and take up more memory!
function worker(name) { return { next: function next() { console.log(name + " doing work!"); } } } var robot = worker("Robot A"); // "new" can be omitted if you don't want // to inherit the prototype or use "this" // worker returns a new object that has a next function setTimeout( robot.next , 3000); // Called in the future setTimeout( robot.next(), 3000); // Called right away
Written by Johan Zetterberg Nov 25, 2016.