Advanced Working With Functions
Rest parameters and spread syntax
Rest parameters
...
function f(...args) {} // args is the name of the array -> parameters array
The rest parameters must be at the end
The
arguments
variableA special array-like object that contains all arguments by their index
function f() {
console.log(arguments[0]);
console.log(arguments[1]);
}
f(1, 2); // Show 1, 2
f(1); // Show 1, undefined (no second argument)
Arrow functions do not have arguments
Spread syntax
let arr = [1, 2, 3];
Math.max(...arr); // 3
let arr1 = [2, 3, 4];
Math.max(...arr, ...arr1); // 4
Math.max(...arr, 5, ...arr1, 6); // 6
The spread syntax internally uses iterators to gather elements, the same way as for...of
does. So it can be used with any iterable.
Copy an array/object
let arr = [1, 2, 3];
let arrCopy = [...arr]; // Spread the array into a list of parameters
// then put the result into a new array
console.log(arr === arrCopy); // false
let obj = { a: 1, b: 2, c: 3 };
let objCopy = { ...obj };
console.log(obj === objCopy); // false
console.log(JSON.stringify(obj) === JSON.stringify(objCopy)); // true
The old var
var
has no block scope
if (true) {
var test = true;
}
console.log(test); // true
var
tolerates redeclarations
var user = "Tus";
var user = "Tu";
// Works fine
var
variables can be declared below their use
function sayHi() {
phrase = "Hello";
console.log(phrase);
var phrase;
}
Declarations are hoisted, but assignments are not
IIFE In the past, as there was only
var
, and it has no block-level visibility, programmers invented a way to emulate it. What they did was calledimmediately-invoked function expressions
(IIFE).
(function () {
alert("Parentheses around the function");
})();
(function () {
alert("Parentheses around the whole thing");
})();
!(function () {
alert("Bitwise NOT operator starts the expression");
})();
(function () {
alert("Unary plus starts the expression");
})();
Global object
window.globalObj = {};
The new Function
syntax
Useful when creating function from string is needed
let func = new Function([arg1, arg2, ...argN], functionBody);
Scheduling: setTimeout
and setInterval
setTimeout
let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...);
Canceling with
clearTimeout
let timerId = setTimeout(...);
clearTimeout(timerId);
setInterval
let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)
Canceling with
TimerId
let timerId = setInterval(() => alert("clicl"), 2000);
setTimeout(() => {
clearInterval(timerId);
alert("stop");
}, 5000);
Nested
setTimeout
/** instead of
* let timerId = setInterval(() => alert('tick'), 2000);
**/
let timerId = setTimeout(function tick() {
alert("tick");
timerId = setTimeout(tick, 2000);
}, 2000);
Zero delay
setTimeout
This schedules the execution offunc
as soon as possible. But the scheduler will invoke it only after ther currently executing script is complete.
setTimeout(() => alert("World"));
alert("hello");
Decorators and forwarding, call/apply
Transparent caching
Let's say we have a function slow(x)
which is CPU-heavy, but its results are stable. In other words, for the same x
it always return the same result.
If the function is called often, we may want to cache (remember) the results to avoid spending extra-time on recalculations.
Instead of adding that functionality into slow()
we'll create a wrapper function, that adds caching
function slow(x) {
// there can be a heavy CPU-intensive job here
alert(`Called with ${x}`);
return x;
}
function cachingDecorator(func) {
let cache = new Map();
return function (x) {
if (cache.has(x)) {
// if there's such key in cache
return cache.get(x); // read the result from it
}
let result = func(x); // otherwise call func
cache.set(x, result); // and cache (remember) the result
return result;
};
}
slow = cachingDecorator(slow);
alert(slow(1)); // slow(1) is cached and the result returned
alert("Again: " + slow(1)); // slow(1) result returned from cache
Using func.call
for the context
The caching decorator mentioned above is not suited to work with object methods.
For instance, in the code below worker.slow()
stops working after the decoration:
// we'll make worker.slow caching
let worker = {
someMethod() {
return 1;
}
slow(x) {
// scary CPU-heavy task here
alert("Called with " + x);
return x * this.someMethod(); // (*)
}
}
// same code as before
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if (cache.has(x)) return cache.get(x);
let result = func(x); // (**)
cache.set(x, result);
return result;
};
}
alert( worker.slow(1) ); // the original method works
worker.slow = cachingDecorator(worker.slow); // now make it caching
alert( worker.slow(2) ); // Error: Canor read property 'someMethod` of undefined
The error occurs in the line (*)
that tries to access this.someMethod
and fails.
The reason is that the wrapper calls the original function as func(x)
in the line (**)
. And, when called like that, the function gets this = undefined
.
Let's fix it. There's a special built-in function method func.call(context, ...args)
that allows to call a funciton explicitly setting this
.
func.call(context, arg1, arg2, ...)
// These 2 calls do almost the same
func(1, 2, 3);
func.call(obj, 1, 2, 3);
function sayHi() {
alert(this.name);
}
let user = { name: "John" };
let admin = { name: "Admin" };
// use call to pass different objects as "this"
sayHi.call(user);
sayHi.call(admin);
In our case, we can call
in the wrapper to pass the context to the original function:
// before: let result = func(x);
let result = func.call(this, x);
Going multi-argument
Till now the cachingDecorator
was working only with single-argument functions. Now how to cache the multi-argument worker.slow
method?
let worker = {
slow(min, max) {
return min + max; // scary CPU-hogger is assumed
},
};
// cachingDecorator needs modifying
function cachingDecorator(func, hash) {
let cache = new Map();
return function () {
let key = hash(arguments); // (*)
if (cache.has(key)) {
return cache.get(key);
}
let result = func.call(this, ...arguments); // (**)
cache.set(key, result);
return result;
};
}
function hash(args) {
return args[0] + "," + args[1];
}
// should remember same-argument calls
worker.slow = cachingDecorator(worker.slow, hash);
alert(worker.slow(3, 5)); // works
alert("Again " + worker.slow(3, 5)); // same (cached)
func.apply
Instead of func.call(this, ...arguments)
we could use func.apply(this, arguments)
.
func.apply(context, args);
These two calls are almost equivalent:
func.call(context, ...args);
func.apply(context, args);
Function binding
Losing
this
let user = {
firstname: "John";
sayHi() {
alert(`Hello, ${this.firstname}!`);
}
};
setTimeout(user.sayHi, 1000); // Hello, undefined!
Solution 1: a wrapper
let user = {
firstname: "John";
sayHi() {
alert(`Hello, ${this.firstname}!`);
}
};
setTimeout(function() {
user.sayHi(); // Hello, John!
}, 1000);
Solution 2: bind
let boundFunc = func.bind(context);
let user = {
firstname: "John";
};
function func() {
alert(this.firstName);
}
let funcUser = func.bind(usert);
funcUser(); // John
Partial functions
The full syntax of
bind
:
let bound = func.bind(context, [arg1], [arg2], ...);
function mul(a, b) {
return a * b;
}
let double = mul.bind(null, 2);
alert(double(3)); // = mul(2, 3) = 6
Going partial without context
What if we'd like to fix some arguments, but not the context
this
? The nativebind
does not allow that.
function partial(func, ...argsBound) {
return function (...args) {
return func.call(this, ...argsBound, ...args);
};
}
// Usage
let user = {
firstName: "John",
say(time, phrase) {
alert(`[${time}] ${this.firstName}: ${phrase}!`);
},
};
// add a partial method with fixed time
user.sayNow = partial(user.say, new Date().getHours() + ":" + new Date().getMinutes());
user.sayNow("Hello");
// [10:00] John: Hello!