ref – https://blog.logrocket.com/anomalies-in-javascript-arrow-functions
Named function parameters
Initially, we have an issue where if we create a function with duplicate parameter name, JS will use the latest name as the parameter.
1 2 3 4 5 6 |
function logParams (first, second, first) { console.log(first + ', ' + second); } logParams('hi', 'rick', 'hey'); // output hey, rick logParams('hi', 'rick'); // undefined, rick |
As we can see, the first parameter is a duplicate; thus, it is mapped to the value of the third argument passed to the function call, completely overriding the first argument passed. This is not a desirable behavior.
The good news is that this behavior is not allowed in strict mode. Defining a function with duplicate parameters in strict mode will throw a Syntax Error indicating that duplicate parameters are not allowed. At compile time, it will throw an error:
Unlike regular functions, arrow functions do not allow duplicate parameters, whether in strict or non-strict mode. Duplicate parameters will cause a Syntax Error to be thrown during compile time.
1 2 3 4 |
// Always throws a syntax error const logParams = (first, second, first) => { console.log(first, second); } |
Function Overload
Function overloading is the ability to define a function such that it can be invoked with different call signatures (shapes or number of arguments). Arguments binding for JavaScript functions makes this possible.
1 2 3 |
function average() { console.log(arguments); } |
However, for arrow function, arguments is not defined.
Unlike regular functions, the arguments binding does not exist for arrow functions. However, they have access to the arguments object of a non-arrow parent function.
1 2 3 4 5 6 |
function average() { return (() => { // have access to non-arrow parent function console.log(arguments); // ok } } |
For arrow functions, we have ES6 parameters.
1 2 3 |
const average = (...args) => { console.log(args); } |
– A rest parameter is not the same as the internal arguments object inside the function. The rest parameter is an actual function parameter, while the arguments object is an internal object bound to the scope of the function.
– A function can only have one rest parameter, and it must always be the last parameter. This means a function can have a combination of named parameters and a rest parameter. In other words, if you declared other parameters, obviously, the rest parameter will not capture all the function’s arguments. The individually declared parameters will be bound to their respective param names. Thus, from a rest parameter sense, it will NOT be able to reach them.
– By definition, when it is the only function parameter, it captures all function arguments. On the other hand, the arguments object of the function always captures all the function’s arguments.
– The rest parameter points to an array object containing all the captured function arguments, whereas the arguments object points to an array-like object containing all the function’s arguments.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const baseConvert = (num, ...args) => { // destructure the `args` array and // set the `fromRadix` and `toRadix` local variables let [fromRadix = 10, toRadix = 10] = args; } // num => 123, fromRadix => 10, toRadix => 10 console.log(baseConvert(123)); // num => 255, fromRadix => 10, toRadix => 2 console.log(baseConvert(255, 2)); // num => 'ff', fromRadix => 16, toRadix => 8 console.log(baseConvert('ff', 16, 8)); |
Constructor functions
When a regular JavaScript function is invoked with the new keyword, the function’s internal [[Construct]] method is called to create a new instance object and allocate memory. After that, the function body is executed normally, mapping this to the newly created instance object. Finally, the function implicitly returns this (the newly created instance object), except a different return value has been specified in the function definition.
Also, all regular JavaScript functions have a prototype property. The prototype property of a function is an object that contains properties and methods that are shared among all instance objects created by the function when used as a constructor.
Unlike regular functions, arrow functions can never be called with the new keyword because they do not have the [[Construct]] method. As such, the prototype property also does not exist for arrow functions.
Arrow functions cannot be used as constructors. They cannot be called with the new keyword. Doing that throws an error indicating that the function is not a constructor.
Also, because arrow functions cannot be called with the new keyword, there is really no need for them to have a prototype. Hence, the prototype property does not exist for arrow functions.
1 2 3 4 5 6 7 8 9 10 11 |
const Square = (length = 10) => { this.length = parseInt(length) || 10; } // throws an error const square = new Square(5); console.log(Square.prototype); // undefined // throws an error Square.prototype.getArea = function() {} |
This
arrow functions manipulate ‘this’ to reference its parent context, to whatever its nearest non-arrow function’s this context.
es5 functions’s this references the calling object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function Timer (seconds = 60) { this.seconds = parseInt(seconds) || 60; console.log(this.seconds); this.interval = setInterval(function () { console.log(--this.seconds); if (this.seconds == 0) { this.interval && clearInterval(this.interval); } }, 1000); } const timer = new Timer(30); |
If you run this code, you will see that the countdown timer seems to be broken. It keeps logging NaN on the console infinitely.
The problem here is that inside the callback function passed to setInterval(), this points to the global window object instead of the newly created instance object within the scope of the Timer() function. Hence, both this.seconds and this.interval are undefined.
to fix, use .bind function:
1 2 3 4 5 6 7 8 9 10 11 12 |
function Timer (seconds = 60) { this.seconds = parseInt(seconds) || 60; console.log(this.seconds); this.interval = setInterval((function () { console.log(--this.seconds); if (this.seconds == 0) { this.interval && clearInterval(this.interval); } }).bind(this), 1000); } |
or arrow function:
1 2 3 4 5 6 7 8 9 10 11 12 |
function Timer (seconds = 60) { this.seconds = parseInt(seconds) || 60; console.log(this.seconds); this.interval = setInterval(() => { console.log(--this.seconds); if (this.seconds == 0) { this.interval && clearInterval(this.interval); } }, 1000); } |