- First Class Functions
- Scoping
- Invocation Context (
this
) - Indirect Invocation
this
and Nested Function Issue- Arguments
This function returns the circumference of a circle:
var circumference = function (circle) {
return 2 * Math.PI * circle.radius;
};
//define circle object
var circle = {x: 100, y: 100, radius: 50};
//invoke the function
console.log(circumference(circle)); //314.159
This example is a JSON replacer function which filters out property radius
from object serialization process:
var filter = function (key, value) {
//don't serialize property 'radius'
if (key === 'radius') {
return undefined;
} else {
return value;
}
};
//define circle object
var circle = {x: 100, y: 200, radius: 50};
//get string version of circle object
var info = JSON.stringify(circle, filter);
//print the string equivalent of object
console.log(info); // '{'x': 100, 'y': 200}'
In this example an outer function is defined that returns inner function:
function outer(x) {
//this secret is a closure
var secret = 5;
return function () {
console.log(secret + x);
}
}
//get the inner function
var inner = outer(10);
//invoke inner function
inner(); //15
This function tries to check the presence of variable a
before and after its declaration:
function scopeTest() {
console.log(a); // undefined - this means variable a is hoisted at this point. No ReferenceError
var a = 1;
console.log(a); //1
}
scopeTest();
This function illustrates the hoisting of function statement:
function outer() {
//function inner is in scope here
//it could be invoked at this place
inner();
console.log(typeof inner === 'function');
//true
function inner() {
console.log('I can be invoked anywhere inside outer function');
}
//function inner is in scope here
//it could be invoked at this place
inner();
console.log(typeof inner === 'function');
//true
}
outer();
This function illustrates the hoisting of function definition expression.
function outer() {
//function inner is not in scope here
//it can not be invoked at this place
inner(); //typeError
console.log(typeof inner === 'function');
//false
var inner = function () {
console.log('I\'m not hoisted at the top');
};
//function inner is in scope here
// it could be invoked at this place
inner();
console.log(typeof inner === 'function');
//true
}
outer();
This function returns the word boundaries of a string:
//\w to \W or \W to \w transition is a word boundary
function wordBoundaries(subject) {
//regex for word boundary position
var pattern = /\b/g;
//invoke match method defined on the string
return subject.match(pattern).length;
}
var book = 'JavaScript: The Good Parts';
console.log(wordBoundaries(book)); //8
//define cylinder object
var cylinder = {
radius: 10,
height: 20
};
/*
define function on cylinder object
volume is the property of cylinder object
this inside function is the cylinder object
this.radius means radius property of the cylinder object
this = invocation context = cylinder object
*/
cylinder.volume = function () {
return Math.PI * Math.pow(this.radius, 2) * this.height;
};
//invoke the method on the cylinder object
console.log(cylinder.volume());
This is a constructor function, and in this example it accepts 2 arguments and sets the newly created object's properties:
//`this` or invocation context is the newly created cylinder object
function Cylinder(radius, height) {
this.radius = radius; //object property
this.height = height; //object property
//object method
this.volume = function () {
return Math.PI * Math.pow(this.radius, 2) * this.height;
};
}
//create object using constructor function
//this inside constructor = cylinder1
var cylinder1 = new Cylinder(10, 20);
console.log(cylinder1.volume());
//create another object
//this inside constructor = cylinder2
var cylinder2 = new Cylinder(20, 10);
console.log(cylinder2.volume());
//Accepts 2 arguments and set the newly created object's properties
//this or invocation context is the newly created cylinder object
function Cylinder(radius, height) {
this.radius = radius; //object property
this.height = height; //object property
}
Now volume method is defined on the prototype object.
//prototype object is the property defined on Cylinder constructor
Cylinder.prototype.volume = function () {
return Math.PI * Math.pow(this.radius, 2) * this.height;
};
//create object using constructor function
//this inside constructor = cylinder1
var cylinder1 = new Cylinder(10, 20);
console.log(cylinder1.volume());
//create another object
//this inside constructor = cylinder2
var cylinder2 = new Cylinder(20, 10);
console.log(cylinder2.volume());
This is a function that returns the circumference of a circle.
//`this` keyword is not associated with any object
var circumference = function () {
return 2 * Math.PI * this.radius;
};
//define circle objects
var circle1 = {x: 100, y: 200, radius: 50};
var circle2 = {x: 200, y: 300, radius: 25};
//invoke the function
//this = circle1
console.log(circumference.call(circle1)); //314.159
//this = circle2
console.log(circumference.call(circle2)); //157.079
This function makes all arguments non enumerable.
var makeNonEnumerable = function () {
//iterate over all arguments and change the enumerable attribute false
for (var i = 0; i < arguments.length; i++){
Object.defineProperty(this,arguments[i],{enumerable:false});
}
};
var testObject1 = {x:1,y:2,z:3};
//make x and y property non enumerable
//We pass individual argument instead of array
makeNonEnumerable.call(testObject1, 'x', 'y");
//check the enumerable attribute by console.log
Object.getOwnPropertyDescriptor(testObject1, 'x').enumerable; //false
Object.getOwnPropertyDescriptor(testObject1, 'y').enumerable; //false
Object.getOwnPropertyDescriptor(testObject1, 'z').enumerable; //true
var testObject2 = {p:1, q:2, r:3};
//We pass array instead of individual argument
makeNonEnumerable.apply(testObject2,['p', 'q']);
Object.getOwnPropertyDescriptor(testObject2, 'p').enumerable; //false
Object.getOwnPropertyDescriptor(testObject2, 'q').enumerable; //false
Object.getOwnPropertyDescriptor(testObject2, 'r').enumerable; //true
var property;
for(property in testObject1){
console.log(property);
}
This function finds all binary numbers inside a string.
/*
regex pattern checks digit (0 or 1) one or more times between word boundaries
\b -> word boundary
+ -> repeat 1 or more time - you can make it lazy by +?
[01]+ -> repeat 0 or 1 one or more time
g -> global match
match method -> return an array with all matches
*/
function binaryNumbers() {
var pattern = /\b[01]+\b/g;
//this keyword is not associated with any object
this.result = this.subject.match(pattern);
}
//create 2 objects
var object1 = {subject: '100 1234 1010 string'},
object2 = {subject: '1234 1112 1010 string'};
//associate this with object1
//this.result will set result property on object1
binaryNumbers.call(object1);
//associate this with object2
//this.result will set result property on object2
binaryNumbers.call(object2);
//query result property on object1
console.log(object1.result); //[ '100', '1010' ]
//query result property on object2
console.log(object2.result); // [ '1010' ]
This function finds all binary numbers inside a string.
/*
regex pattern checks digit (0 or 1) one or more times between word boundaries
\b -> word boundary
+ -> repeat 1 or more time - you can make it lazy by +?
[01]+ -> repeat 0 or 1 one or more time
g -> global match
match method -> return an array with all matches
*/
function binaryNumbers() {
var pattern = /\b[01]+\b/g;
//this keyword is not associated with any object
this.result = this.subject.match(pattern);
}
//create 2 objects
var object1 = {subject: '100 1234 1010 string'},
object2 = {subject: '1234 1112 1010 string'};
//associate this with object1
//this.result will set result property on object1
object1.method = binaryNumbers;
object1.method();
delete object1.method;
//associate this with object2
//this.result will set result property on object2
object2.method = binaryNumbers;
object2.method();
delete object1.method;
//query result property on object1
console.log(object1.result); //[ '100', '1010' ]
//query result property on object2
console.log(object2.result); // [ '1010' ]
In this example the reducer object has one array and a method reduced:
//Below is the calculation using reduce method and array [100, 200, 300]
//x = 100, y = 200
//0.5 * (100 + 200) = 150 -> this will become x in next iteration
//x = 150, y = 300
//0.5 * (150 + 300) = 225 -> final value
var reducer = {
a: [100, 200, 300],
reduce: function () {
return this.a.reduce(function (x, y) {
return 0.5 * (x + y);
});
}
};
console.log(reducer.reduce()); //225
//reducer object has one array a and method reduce
//reduce does the job of reducing using reduce method 0.5 * (x + y)
//Below is the calculation using reduce method and array [100, 200, 300]
//x = 100, y = 200
//0.5 * (100 + 200) = 150 -> this will become x in next iteration
//x = 150, y = 300
//0.5 * (150 + 300) = 225 -> expected value -> but we get NaN
var reducer = {
a: [100, 200, 300],
factor: 0.5,
reduce: function () {
return this.a.reduce(function (x, y) {
return this.factor * (x + y);
});
}
};
console.log(reducer.reduce()); //NaN
//reducer object has one array a and method reduce
//reduce does the job of reducing using reduce method 0.5 * (x + y)
//Below is the calculation using reduce method and array [100, 200, 300]
//x = 100, y = 200
//0.5 * (100 + 200) = 150 -> this will become x in next iteration
//x = 150, y = 300
//0.5 * (150 + 300) = 225
var reducer = {
a: [100, 200, 300],
factor: 0.5,
reduce: function () {
var self = this;
return this.a.reduce(function (x, y) {
return self.factor * (x + y);
});
}
};
console.log(reducer.reduce()); //225
This function tries to explain the flexibility of arguments supplied:
function test(x, y) {
// I don't do anything
console.log(x);
console.log(y);
//print arguments object - Array like object
console.log(arguments);
}
//no argument supplied
test();
// x - undefined
// y - undefined
// { }
//less arguments supplied
test(1);
// x - 1
// y - undefined
// { '0': 1}
//arguments = parameters = 2
test(1, 2);
// x - 1
// y - 2
// {'0': 1, '1': 2 }
//more argument supplied than actual parameters
test(1, 2, 3);
// x - 1
// y - 2
// {'0': 1, '1': 2, '2': 3}
This function adds all the arguments supplied:
function add() {
console.log(arguments.length); //3
var sum = 0;
//iterate over all arguments
//trick - save arguments.length in some variable
for (var i=0; i < arguments.length; i++){
sum +=arguments[i];
}
return sum;
}
console.log(add(1,2,3)); //6
This function returns object properties based on the flag onlyEnumerable:
/*
getProperties(obj) -> return enumerable own properties
getProperties(obj, false) -> return enumerable as well as non enumerable properties
getProperties(obj, true) -> return enumerable own properties
*/
function getProperties(obj, onlyEnumerable) {
//if onlyEnumerable is not passed, set it to true
if (onlyEnumerable === undefined) {
onlyEnumerable = true;
}
if (onlyEnumerable) {
return Object.keys(obj);
} else {
// enumerable + non enumerable
return Object.getOwnPropertyNames(obj);
}
}
//define object with 2 properties
//by default newly created properties are enumerable
var obj = {x: 1, y: 2};
//define one non enumerable property "z"
Object.defineProperty(obj, 'z', {enumerable: false, value: 3});
console.log(getProperties(obj)); // [ 'x', 'y' ]
console.log(getProperties(obj, false)); // [ 'x', 'y', 'z' ]
console.log(getProperties(obj, true)); // [ 'x', 'y' ]