post-css-position

Mastering Every Aspect of JavaScript Functions with Ease in 30 Minutes

JavaScript functions are very useful for code development and maintenance.

I should mention the previous articles in the JavaScript Definitive Guide:

This article will introduce the following contents:

Why we need JavaScript Functions?

A JavaScript function is a block of code designed to perform a specific task.

We know that the area calculation formula for a circle is:

S = π r 2

When we know the radius r of a circle, we can calculate the area according to the formula. Suppose we need to calculate the area of ​​three different circles:

var r1 = 12.34;
var r2 = 9.08;
var r3 = 73.1;
var s1 = 3.14 * r1 * r1;
var s2 = 3.14 * r2 * r2;
var s3 = 3.14 * r3 * r3;

When the code appears to be regularly repeated, you need to be careful. It is troublesome and inflexible to write 3.14 * x * x many times. What if you want to change 3.14 to  3.14159265359?

With a function, we don’t have to write s = 3.14 * x * x over and over again. Instead we make a more meaningful function call, e.g., s = area_of_circle(x). And the function area_of_circle itself only needs to be defined once, while we can invoke it multiple times.

Basically all high-level languages ​​support functions, and JavaScript is no exception. JavaScript functions are not only “first class citizens”, but also can be used like variables, with very powerful abstraction capabilities.

Abstraction

Abstraction is a very common concept in mathematics. For example:

Calculating the sum of the series: 1 + 2 + 3 + ... + 100, is very inconvenient to write. So the mathematician invented the summation symbol, which can be written as:

sum of 1 to 100

This abstract notation is very powerful, because we can see that the ∑ symbol can be understood as summation, rather than being reduced to a low-level addition.

Moreover, this abstract notation is extensible, such as:

Mastering Every Aspect of JavaScript Functions with Ease in 30 Minutes 1

In fact, this formula is: (1 * 1 + 1) + (2 * 2 + 1) + (3 * 3 + 1) + … + (100 * 100 + 1).

With abstraction, we do not need to consider the underlying specific computing process, but directly solve the problem at a higher level.

When writing computer programs, functions are the most basic way of abstracting code.

Function Definition and Invocation

Defining Function

In JavaScript, the way to define a function is as follows:

function abs(x) {
    if (x >= 0) {
        return x;
    } else {
        return -x;
    }
}

The above abs() function is defined as follows:

  • The function keyword indicates that this is a function definition;
  • abs is the name of the function;
  • (x) The parameters of the function are listed in parentheses, and multiple parameters are separated by ,;
  • {...} The code between the brackets is the function body. It contain several statements.

Note that once the return statement inside the function body is executed, the function is terminated and the result is returned. Therefore, the function can internally implement very complex logic through conditional judgment and looping.

If there is no return statement, the result will be returned after the function is executed, but the result is undefined.

Since the JavaScript function is also an object, the abs() function defined above is actually a function object, and the function name abs can be treated as a variable pointing to the function.

Therefore, the second way to define a function is as follows:

var abs = function (x) {
    if (x >= 0) {
        return x;
    } else {
        return -x;
    }
};

In this way, function (x) { ... } is an anonymous function that has no function name. However, this anonymous function is assigned to the variable abs, so the anonymous function can be called by the variable abs.

The above two definitions are completely equivalent . Note that the second method needs to add one semicolon ; at the end of the function body, indicating the end of the assignment statement.

Function Calls

When a function is called, the parameters are passed in order:

abs(10); // => 10
abs(-9); // => 9

JavaScript allows you to input any number of arguments without affecting the call. More arguments can be passed into a function, although they are not needed inside the function:

abs(10, 'blablabla'); // => 10
abs(-9, 'haha', 'hehe', null); // => 9

You can also call a function with no parameters. But this may cause unexpected behaviour.

abs(); // => NaN

At this point the parameter x of the function abs(x) will be set to undefined and the result of the calculation is NaN.

To avoid receiving undefined, you can check the parameters:

function abs(x) {
    if (typeof x !== 'number') {
        throw 'Not a number';
    }
    if (x >= 0) {
        return x;
    } else {
        return -x;
    }
}

Arguments

JavaScript also has a keyword arguments that works only inside functions. The keyword always points to all parameters passed in by the caller of the current function.

With arguments, you can get all the parameters passed in by the caller. That is, even if the function does not define any parameters, you can still get the value of the parameter:

function abs() {
    if (arguments.length === 0) {
        return 0;
    }
    var x = arguments[0];
    return x >= 0 ? x : -x;
}

abs(); // 0
abs(10); // 10
abs(-9); // 9

Rest Parameters

Since the JavaScript function allows to receive any number of parameters, so we have to use arguments to get all the parameters:

function foo(a, b) {
    var i, rest = [];
    if (arguments.length > 2) {
        for (i = 2; i<arguments.length; i++) {
            rest.push(arguments[i]);
        }
    }
    console.log('a = ' + a);
    console.log('b = ' + b);
    console.log(rest);
}

In order to get the parameters other than the defined parameters abwe have to use arguments, and then loop the index starting from 2 in order to exclude the first two parameters. This is very awkward. Is there a better way to get additional rest parameters?

The ES6 standard introduces the rest parameter, and the above function can be rewritten as:

function foo(a, b, ...rest) {
    console.log('a = ' + a);
    console.log('b = ' + b);
    console.log(rest);
}

foo(1, 2, 3, 4, 5);
// Result:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]

foo(1);
// Result:
// a = 1
// b = undefined
// Array []

The rest parameter can only be written at the end, with the ... identifier in front. From the running result, the parameters passed in are first bounded to ab and the extra parameters are passed to the variable  rest in the form of an array. Therefore, we no longer need to use arguments to get all the parameters.

If the parameters passed in are not filled with the normally defined parameters, it does not matter. The rest parameter will receive an empty array (note that it is not undefined).

WARNING: The arguments feature is no longer recommended. Use the rest parameter.

Because the rest parameter is a new ES6 feature, you need to test if the browser supports it. Write a sum() function with the rest parameter , receive any arguments and return their sum:

'use strict';

// TODO: Write the sum function.
function sum() {

}

// test:
var i, args = [];
for (i=1; i<=100; i++) {
    args.push(i);
}
if (sum() !== 0) {
    console.log('Test failed: sum() = ' + sum());
} else if (sum(1) !== 1) {
    console.log('Test failed: sum(1) = ' + sum(1));
} else if (sum(2, 3) !== 5) {
    console.log('Test failed: sum(2, 3) = ' + sum(2, 3));
} else if (sum.apply(null, args) !== 5050) {
    console.log('Test failed: sum(1, 2, 3, ..., 100) = ' + sum.apply(null, args));
} else {
    console.log('test passed!');
}

Be careful with your return statement

We have talked about the JavaScript engine that it has a mechanism to automatically add a semicolon at the end of the line, which may allow you to encounter a large pitfall of the return statement:

function foo() {
    return { name: 'foo' };
}

foo(); // { name: 'foo' }

If you split the return statement into two lines:

function foo() {
    return
        { name: 'foo' };
}

foo(); // undefined

Be careful , because the JavaScript engine automatically adds a semicolon mechanism at the end of the line, the above code actually becomes:

function foo() {
    return; // A semicolon is automatically added, which is equivalent to return undefined;
        { name: 'foo' }; // This line of statements has not been executed.
}

So the correct multi-line writing is:

function foo() {
    return { // There is no automatic semicolon here, because { indicates that the statement has not ended yet.
        name: 'foo'
    };
}

Exercise 1

Define a function area_of_circle() that calculates the area of ​​a circle . It has two parameters:

  • r: indicates the radius of the circle;
  • Pi: indicates the value of π, if not, the default is 3.14
'use strict';

function area_of_circle(r, pi) {
// TODO: Add your code here!
}
// Test:
if (area_of_circle(2) === 12.56 && area_of_circle(2, 3.1416) === 12.5664) {
    console.log('test passed');
} else {
    console.log('test failed');
}

John is a JavaScript rookie. He wrote a max() function that returns the larger of the two numbers:

'use strict';

function max(a, b) {
 
if (a > b) {
        return
                a;
    } else {
        return
                b;
    }
}
console.log(max(15, 20));

But John complained that his browser is flawed, and the max() function always returns undefined no matter what numbers have been passed in. Please help him point out the bug and fix it.

Variable Scope and Deconstruction Assignment

In JavaScript, variables declared by the var keyword is actually scoped.

If a variable is declared inside a function body, the scope of the variable is the entire function body, and the variable cannot be referenced outside the function body:

'use strict';

function foo() {
    var x = 1;
    x = x + 1;
}

x = x + 2; // ReferenceError! Unable to reference variable x outside of function

If two different functions declare the same variable, then the variable only works in the body of the respective function. In other words, variables of the same name inside different functions are independent of each other and do not affect each other:

'use strict';

function foo() {
    var x = 1;
    x = x + 1;
}

function bar() {
    var x = 'A';
    x = x + 'B';
}

Since JavaScript functions can be nested, internal functions can access variables defined by external functions. But the external functions can not access variables defined by internal functions.

'use strict';

function foo() {
    var x = 1;
    function bar() {
        var y = x + 1; // Bar can access foo's variable x!
    }
    var z = y + 1; // ReferenceError! Foo can't access bar variable y!
}

What if the name of variables in internal and external functions are duplicated? To test it out:

'use strict';
function foo() {
     var x = 1;
     function bar() {
         var x = 'A';
         console.log('x in bar() = ' + x); // 'A'
     }
     console.log('x in foo() = ' + x); // 1
     bar();
 }
 foo();

When looking for variables, JavaScript functions start from their own definitions, from “inside” to “outside”. If the internal function defines a variable with the same name as the external function, the variable of the internal function will “mask” the variable of the external function.

However, I suggest you to use different names for variables to avoid confusion.

Variable Promotion

JavaScript function has a feature, it will first scan the entire function body statement, “lift” all declared variables to the top of the function:

'use strict';

function foo() {
    var x = 'Hello, ' + y;
    console.log(x);
    var y = 'Bob';
}

foo();

Although it is a strict mode, the statement var x = 'Hello, ' + y; does not report an error because the variable y is declared later. But the console.log display Hello, undefined meaning that the value of the variable y is undefined. The reason behind this beaviour is that the JavaScript engine automatically raises the declaration of variable y, but does not increase the assignment of y.

For the above foo()function, the code interpreted by the JavaScript engine is equivalent to:

function foo() {
    var y; // Raise the declaration of the variable y, at this time y is undefined
    var x = 'Hello, ' + y;
    console.log(x);
    y = 'Bob';
}

Due to this weird “characteristic” of JavaScript, when we define variables inside a function, we strictly follow the rule of “first declare all variables inside the function”. The most common practice is to use a var variable to declare all the variables used inside the function:

function foo() {
    var x = 1, // x is initialized to 1
        y = x + 1, // y is initialized to 2
        z, i; // z and i are undefined
    // Other statements
    for (i=0; i<100; i++) {
        ...
    }
}

Global Scope

Variables that are not defined within any function are existed in the global scope. In fact, JavaScript has a global window object by default, and a global scope variable is actually bounded to window as an attribute:

'use strict';

var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'

Therefore, direct access to the global variable course or window.course has exactly the same result.

As you may have noticed, since there are two ways to define a function. The function defined by the variable style, i.e., var foo = function () {}, is actually a global variable. Therefore, the definition of the top-level function is also treated as a global variable and bounded to the window object:

'use strict';

function foo() {
    alert('foo');
}

foo(); // call foo() directly
window.foo(); / / Called by window.foo()

Actually, the alert() function is also a property of the window variable:

'use strict'; 

window.alert('call window.alert()'); 
// Save alert to another variable: 
var old_alert = window.alert; 
// Assign a new function to alert: 
window.alert = function () {}
/ / Restore alert: 
window.alert = old_alert; 
alert (' can also use alert ()!');

This shows that JavaScript actually has only one global scope. Any variable (the function is also treated as a variable), if it is not found in the current function scope, will continue to look up, and finally, if it is not found in the global scope, an ReferenceError error is reported.

Namespace

Global variables are bounded to the window object. Different JavaScript files that use the same global variables or define top-level functions with the same name will cause naming conflicts, which is hard to debug.

One way to reduce conflicts is to bind all of your own variables and functions to a global variable. E.g:

// The only global variable MYAPP:
var MYAPP = {};

// Other variables:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;

// Other functions:
MYAPP.foo = function () {
     Return 'foo';
};

Putting all of your own code into a unique namespace MYAPP will greatly reduce the possibility of global variable conflicts.

Many well-known JavaScript libraries adopt this technique: jQuery, YUI, underscore, and more.

Local Scope

We can not use var to define variables with local scope, i.e., in a block of statements such as loops:

'use strict';

function foo() {
    for (var i=0; i<100; i++) {
        //
    }
    i += 100; // can still reference variable i
}

In order to achieve the block-level scope, ES6 introduced a new keyword let, which can declare a block-level variable:

'use strict';

function foo() {
    var sum = 0;
    for (let i=0; i<100; i++) {
        sum += i;
    }
    // SyntaxError:
    i += 1;
}

Constant

The keywords var and let are used to declare variables, where the variable can be changed later. If you want to declare a constant, it is not possible before ES6. We usually use all uppercase variables to indicate “this is a constant, do not modify its value”.

The ES6 standard introduces the new keyword const to define constants. const and let both have block-level scope:

'use strict';

const PI = 3.14;
PI = 3; // Some browsers do not report an error, but have no effect!
PI; // 3.14

Destructive Assignment

Starting with ES6, JavaScript introduces destructive assignment that assigns values ​​to a set of variables at the same time.

What is destructive assignment? Let’s take a look at the traditional approach of how to assign the elements of an array to several variables:

var array = ['hello', 'JavaScript', 'ES6'];
var x = array[0];
var y = array[1];
var z = array[2];

Now, in ES6, you can use destructive assignment to assign values ​​directly to multiple variables:

'use strict'; 

// If the browser supports destructuring assignments, no error will be reported:
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];

If the array itself is nested, you can also destructure the assignment by the following form. Noting that the nesting level and position are consistent:

let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
x; // 'hello'
y; // 'JavaScript'
z; // 'ES6'

Destructive assignments can also ignore certain elements:

let [, , z] = ['hello', 'JavaScript', 'ES6']; // Ignore the first two elements, only assign the third element to z
z; // 'ES6'

If you need to take a few properties from an object, you can also use destructive assignment to quickly get the specified properties of the object:

'use strict'; 

var person = { 
    name: 'John', 
    age: 20, 
    gender: 'male', 
    passport: 'G-12345678', 
    school: 'No.4 middle school' 
}; 
var {name, age, passport} = person;

When destructuring an object, you can also directly assign values ​​to nested object properties, as long as the corresponding levels are consistent:

'use strict'; 
var person = {
    name: 'John',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school',
    address: {
        city: 'Beijing',
        street: 'No.1 Road',
        zipcode: '100001'
    }
};
var {name, address: {city, zip}} = person;
name; // 'John'
city; // 'Beijing'
zip; // Undefined, because the property name is zipcode instead of zip
// Note: address is not a variable, but to get city and zip to get the properties of a nested address object.
address;// Uncaught ReferenceError: address is not defined

When destructing an object property with a destructor assignment, if the corresponding property does not exist, the variable will be assigned to the value undefined.

If the variable name and attribute name to be used are inconsistent, you can get it with the following syntax:

var person = {
    name: 'John',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school'
};

// Assign the passport attribute to the variable id:
let {name, passport:id} = person;
name; // 'John'
id; // 'G-12345678'
// Note: The passport is not a variable, but to get the variable id to get the passport attribute:
passport; // Uncaught ReferenceError: passport is not defined

Destructuring assignments can also use default values, which avoids the undefined problem of non-existing properties:

var person = {
    name: 'John',
    age: 20,
    gender: 'male',
    passport: 'G-12345678'
};

// If the person object does not have a single attribute, the default assignment is true:
var {name, single=true} = person;
name; // 'John'
single; // true

Sometimes, if a variable has already been declared, a syntax error will be reported when the variable is assigned again:

// Variable declaration
var x, y;
// Destructive assignment
{x, y} = { name: 'John', x: 100, y: 200};
// Uncaught SyntaxError: Unexpected token =

This is because the JavaScript engine treats the { opening statement as a block and the = symbol is no longer legal. The solution is to use parentheses:

var x, y;
({x, y} = { name: 'John', x: 100, y: 200});

Application Scenarios

Destructive assignments can greatly simplify the code in many cases. For example, swapping the values ​​of two variables x and y can be written like this. No temporary variables are required:

var x=1, y=2;
[x, y] = [y, x]

Quickly get the domain name and path of the current page:

var {hostname:domain, pathname:path} = location;

If a function receives an object as a parameter, you can use destructive assignment to directly bind an object’s properties to variables. For example, the following function can quickly create a Date object:

function buildDate({year, month, day, hour=0, minute=0, second=0}) {
    return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
}

It is convenient that the incoming object only needs to have three properties yearmonth and day:

buildDate({ year: 2017, month: 1, day: 1 });
// Sun Jan 01 2017 00:00:00 GMT+0800 (CST)

You can also pass in hourminute and second properties:

buildDate({ year: 2017, month: 1, day: 1, hour: 20, minute: 15 });
// Sun Jan 01 2017 20:15:00 GMT+0800 (CST)

Using destructive assignments can reduce the amount of code, but it needs to work in modern browsers that support this feature. Browsers that currently support destructive assignment include Chrome, Firefox, Edge, and more.

Object Method

Bind a function in an object, called the method of this object.

In JavaScript, the definition of an object is like this:

var person = {
    name: 'John',
    birth: 1990
};

However, if we bind a function to person, we can do more. For example, write a age() method and return John's age:

var person = {
    name: 'John',
    birth: 1990,
    age: function () {
        var y = new Date().getFullYear();
        return y - this.birth;
    }
};

person.age; // function person.age()
person.age();

A function bounded to an object is called a method, and it has no difference from a normal function. But it uses a this keyword internally.

So what is the this stuff?

Inside a method, the this keyword is a special variable that always points to the current object, which is the variable person. So, this.birth actually means person.birth.

Let us write above codes separately:

function getAge() {
    var y = new Date().getFullYear();
    return y - this.birth;
}

var person = {
    name: 'John',
    birth: 1990,
    age: getAge
};

person.age(); // => outputs the right result, 29
getAge(); // NaN

Why does the function getAge() called independently returns NaNPlease note that we have entered a big pitfall of JavaScript.

If the JavaScript function use the this keyword, what is this pointing to?

The answer is, depending on the situation!

If called in the form of a method, for example person.age(), the keyword this points to the called object, that is person. This is in line with our expectations.

If the function is called separately, for example getAge(), at this point, the function this points to the global object, that is window.

Even more worse way is that if you write:

var fn = person.age; // Get the person's age function first
fn(); // NaN

It is not working!

IMPORTANT: To use the keyword this correctly, you must use the form obj.xxx() to call a method!

Since this is a huge design mistake, it is not that simple to correct. ECMA decided to let the keyword this point to undefined in strict mode, so you will get an error:

'use strict';

var person = {
    name: 'John',
    birth: 1990,
    age: function () {
        var y = new Date().getFullYear();
        return y - this.birth;
    }
};

var fn = person.age;
fn(); // Uncaught TypeError: Cannot read property 'birth' of undefined

This decision simply exposes the error in time and does not address the correct object that this should be pointed to.

Sometimes, you may rewrite the method in the following way:

'use strict';

var person = {
    name: 'John',
    birth: 1990,
    age: function () {
        function getAgeFromBirth() {
            var y = new Date().getFullYear();
            return y - this.birth;
        }
        return getAgeFromBirth();
    }
};

person.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined

The result was wrong again! The reason is that the this pointer is only pointed within the function of the method age. And in the function getAgeFromBirth, this points to undefined! (In non-strict mode, it re-points to the global object window!)

To achieve this, we use a that variable to capture this first:

'use strict';

var person = {
    name: 'John',
    birth: 1990,
    age: function () {
        var that = this; // Capture this at the beginning of the method
        function getAgeFromBirth() {
            var y = new Date().getFullYear();
            return y - that.birth; // Use that instead of this
        }
        return getAgeFromBirth();
    }
};

person.age(); // 25

By using var that = this;, you can safely define other functions inside the method instead of stacking all the statements into one method.

Apply

In a separate function call, depending on whether it is in strict mode, this points to undefined or window. However, we can still control the this pointer!

To specify which object the pointer this points to, you can use the function’s own apply method, which takes two arguments, the first argument is the variable that needs to be bounded, and the second argument is an Array that represents the function’s parameters.

Use the apply method to make a getAge()call:

function getAge() {
    var y = new Date().getFullYear();
    return y - this.birth;
}

var person = {
    name: 'John',
    birth: 1990,
    age: getAge
};

person.age(); // success
getAge.apply(person, []); // 25, this points to person, empty parameter

Another similar approach is the call() method where the differences are:

  • The apply() method packs the parameters into Array;
  • The call() method passes the parameters in order.

For example, call Math.max(3, 5, 4), use apply() and call() as follows:

Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5

For normal function calls, we usually bind this to null.

Decorator

Utilizing the apply() method, we can also dynamically change the behavior of a function.

All JavaScript objects are dynamic, and even the built-in functions, we can redirect them to new functions.

Now suppose we want to count how many times the function parseInt() has been called. You can find all the calls and add them manually adds the code count += 1. But this is too awkward. The best solution is to replace the default with our own function parseInt():

'use strict';

var count = 0;
var oldParseInt = parseInt; // save the original function

window.parseInt = function () {
    count += 1;
    return oldParseInt.apply(null, arguments); // call the original function
};

// Test:
parseInt('10');
parseInt('20');
parseInt('30');
console.log('count = ' + count); // 3

Higher-Order Function

What is a higher order function?

Since a variable can point to a function, and a function’s argument can receive the variable, then one function can receive another function as a parameter. This function is called a higher-order function.

One of the simplest higher order function is:

function add(x, y, f) {
    return f(x) + f(y);
}

When we call add(-5, 6, Math.abs), parameters xy and f receive -56 and function Math.abs, respectively. According to the function definition, we can derive the calculation process as:

x = -5;
y = 6;
f = Math.abs;
f(x) + f(y) ==> Math.abs(-5) + Math.abs(6) ==> 11;
return 11;

Verify with code:

'use strict';

function add(x, y, f) {
    return f(x) + f(y);
}
var x = add(-5, 6, Math.abs); // 11
console.log(x);

Map and Reduce

If you’ve read Google’s famous paper ” MapReduce: Simplified Data Processing on Large Clusters, “, you can probably understand the concept of map/reduce.

Map

For example, if we have a function f(x)=x2 , to apply this function to an array [1, 2, 3, 4, 5, 6, 7, 8, 9], we can use the map method as following:

map

The map() method is defined in Array. We call map()method on an Array with our own function. In last, we get a new Array:

'use strict';

function pow(x) {
    return x * x;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var results = arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
console.log(results);

Note: The argument passed to the map() method is the function pow.

Why not directly calculate the results with a loop?

var f = function (x) {
    return x * x;
};

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var result = [];
for (var i=0; i<arr.length; i++) {
    result.push(f(arr[i]));
}

Indeed, we can accomplish this computation without map. But from the loop code above, we can’t understand the code at a glance “put f(x) on every element of the Array and generate a new Array of results.”

So, as a higher-order function, map() in fact abstracts the rules of the operation. So we have ability to not only calculate the simple f(x)=x2, but also calculate any complex function. For example, converting all the numbers into strings:

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9']

Only one line of code is sufficient.

Reduce

Let’s learn another concept: reduce. The reduce() method is also defined in Array. It applies a function to the array, where the function must receive two parameters. Concretely, the function is used in the following way:

[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)

For example, for summation of an Array, you can use reduce to achieve:

var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
    return x + y;
}); // 25

Exercise 2

Using the reduce() to compute the product of an array:

'use strict';

function product(arr) {
// TODO: Add your code here.
}

// Test:
if (product([1, 2, 3, 4]) === 24 && product([0, 1, 2]) === 0 && product([99, 88, 77, 66]) === 44274384 ) {
    console.log('Test passed!');
}
else {
    console.log('Test failed!');
}

To transform [1, 3, 5, 7, 9] into an integer 13579, reduce() can also do us a favour:

var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
    return x * 10 + y;
}); // 13579

If we continue to improve this example, we can find a way to turn a string 13579 into an Array — [1, 3, 5, 7, 9] and then use reduce() to write a function that converts a string to a Number.

Exercise 3

Implement a string2int() function with map and reduce operations. Don’t use the built-in parseInt() function.

'use strict';

function string2int(s) {
// TODO: Add your code here
}

// Test:
if (string2int('0') === 0 && string2int('12345') === 12345 && string2int('12300') === 12300) {
    if (string2int.toString().indexOf('parseInt') !== -1) {
        console.log('Do not use parseInt()!');
    } else if (string2int.toString().indexOf('Number') !== -1) {
        console.log('Do not use Number()!');
    } else {
        console.log('test passed!');
    }
}
else {
    console.log('Test failed!');
}

Exercise 4

Please change the non-standard English names entered by the users into the first letter uppercase and other lowercase canonical names.

Input: ['adam', 'LISA', 'barT'], output: ['Adam', 'Lisa', 'Bart'].

'use strict';

function normalize(arr) {
}

// Test
if (normalize(['adam', 'LISA', 'barT']).toString() === ['Adam', 'Lisa', 'Bart'].toString()) {
    console.log('test passed!');
}
else {
    console.log('Test failed!');
}

John hopes to convert strings into integers, with the following simple code:

'use strict';

var arr = ['1', '2', '3'];
var r;
r = arr.map(parseInt);
console.log(r);

The results turned out to be 1, NaN, NaN. Please help John find the reason and correct the code.

Hint: Refer to the documentation for Array.prototype.map() .

Filter

Filter is also a common operation, it is used to filter out some elements of an array, and then return the remaining elements.

Similar to the map() method, the filter() method receives function as input. The difference is that filter() applies the passed function on each element in turn, and then decides retain or discard the element based on the returned value of the passed function.

For example, in an Array, delete the even number, just keep the odd number, you can write:

var arr = [1, 2, 4, 5, 6, 9, 10, 15];
var r = arr.filter(function (x) {
    return x % 2 !== 0;
});
r; // [1, 5, 9, 15]

To delete all empty strings in an array, you can write:

var arr = ['A', '', 'B', null, undefined, 'C', '  '];
var r = arr.filter(function (s) {
    return s && s.trim();
});
r; // ['A', 'B', 'C']

It can be seen that the key point to use this higher-order function is to correctly implement a “filter” function.

Callback

The received callback function of filter() can actually have multiple parameters. Usually we only use the first parameter to represent an element of an array. The callback function can also accept two other parameters, indicating the position of the element and the array itself:

var arr = ['A', 'B', 'C'];
var r = arr.filter(function (element, index, self) {
     console.log(element); // Print 'A', 'B', 'C' in sequence
     console.log(index); // print 0, 1, 2 in sequence
     console.log(self); // self is the variable arr
     return true;
});

Utilize filter, repeated elements in an array can be easily removed :

'use strict';

var r,
    arr = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry'];

r = arr.filter(function (element, index, self) {
    return self.indexOf(element) === index;
});

console.log(r.toString());

Removing a duplicated element relies on the fact that the indexOf method always returns the position of the first element.

Exercise 5

Please try to use filter() to filter out the prime numbers:

'use strict';

function get_primes(arr) {
// Add your code here
}

// Test:
var x,
    r,
    arr = [];

for (x = 1; x < 100; x++) {
    Arr.push(x);
}
r = get_primes(arr);
if (r.toString() === [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71 , 73, 79, 83, 89, 97].toString()) {
    console.log('test passed!');
} else {
    console.log('Test failed: ' + r.toString());
}

Sorting Algorithm

Sorting is often used in programs. Whether using bubble sorting or quick sorting, the core of sorting is to compare the size of two elements. We can compare numbers directly, but what about strings and objects? It is meaningless to directly compare them from mathematical aspects. So the process of comparison must be abstracted by functions.

For two elements x and y, the comparison function works as follows:

  • if x < y, return -1;
  • if x == y, return 0;
  • if x > y, return 1.

The sort() method is used for sorting, but the results of sorting may surprise you:

// looks normal:
['Google', 'Apple', 'Microsoft'].sort(); // ['Apple', 'Google', 'Microsoft'];

// The apple is at the end:
['Google', 'apple', 'Microsoft'].sort(); // ['Google', 'Microsoft", 'apple']

// Unable to understand the result:
[10, 20, 1, 2].sort(); // [1, 10, 2, 20]

In the second sort, apple ranked last because the strings are sorted according to ASCII, and the lowercase ASCII a is after the uppercase letters.

What is the third sort result? Can simple numeric sorting be wrong?

This is because the sort()method first converts all elements to String and then sorts them. The result is '10' ranked first because the character '1' is smaller than '2' in the ASCII.

If you don’t know the default rule of the sort() method, sort the numbers directly will definitely encounter incomprehensible behaviours!

Fortunately, the sort()method is a higher-order function, it can also receive a comparison function to achieve a custom sort.

To sort by number, we can write:

'use strict';

var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
    if (x < y) {
        return -1;
    }
    if (x > y) {
        return 1;
    }
    return 0;
});
console.log(arr); // [1, 2, 10, 20]

If you want to sort in reverse order, we can put the big number in front:

var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
    if (x < y) {
        return 1;
    }
    if (x > y) {
        return -1;
    }
    return 0;
}); // [20, 10, 2, 1]

By default, sorting strings is based on their positions in the ASCII table. Now, we propose that sorting should ignore case and sort in alphabetical order. To implement this algorithm, you don’t have to make any changes to the existing code, as long as we can define a comparison algorithm that ignores the case:

var arr = ['Google', 'apple', 'Microsoft'];
arr.sort(function (s1, s2) {
    x1 = s1.toUpperCase();
    x2 = s2.toUpperCase();
    if (x1 < x2) {
        return -1;
    }
    if (x1 > x2) {
        return 1;
    }
    return 0;
}); // ['apple', 'Google', 'Microsoft']

In fact, first convert the strings to uppercase (or both to lowercase) and then compare them.

As can be seen from the above examples, the abstraction capabilities of higher-order functions are very powerful, and the core code can be kept very concise.

Finally, we should mention that the sort() method will modify Array directly, and the array returned is still current Array:

var a1 = ['B', 'A', 'C'];
var a2 = a1.sort();
a1; // ['A', 'B', 'C']
a2; // ['A', 'B', 'C']
a1 === a2; // true, a1 and a2 are the same

Array

For arrays, in addition to map()reducefilter()sort(), the Array object also provides a lot of very useful high-order functions.

Every

The every() method can determine if all elements of an array meet the test condition.

For example, given an array containing several strings, determine if all strings meet the specified test condition:

'use strict';
var arr = ['Apple', 'pear', 'orange'];
console.log(arr.every(function (s) {
    return s.length > 0;
})); // true, Because each element satisfies s.length>0

console.log(arr.every(function (s) {
    return s.toLowerCase() === s;
})); // false, Because not every element is all lowercase

Find

The find() method is used to find the first element that meets the test condition, and if found, returns this element; otherwise, returns undefined:

'use strict';
var arr = ['Apple', 'pear', 'orange'];
console.log(arr.find(function (s) {
    return s.toLowerCase() === s;
})); // 'pear'

console.log(arr.find(function (s) {
    return s.toUpperCase() === s;
})); // undefined

findIndex

findIndex() is similar to find(). It is also used to find the first element that meets the test condition. The difference is that findIndex() will return the index of the finded element. If not found, return -1:

var arr = ['Apple', 'pear', 'orange'];
console.log(arr.findIndex(function (s) {
    return s.toLowerCase() === s;
})); // 1

console.log(arr.findIndex(function (s) {
    return s.toUpperCase() === s;
})); // -1

forEach

forEach() is similar to map(). It also applies the passed function to each element, but does not return a new array. The forEach() method often used to traverse arrays, so the passed function does not need to return a value:

'use strict';
var arr = ['Apple', 'pear', 'orange'];
arr.forEach(console.log); // Print each element in turn

Closure

Function as the Return Value

In addition to accepting functions as arguments, higher-order functions can also return functions as result.

Let’s implement summation of an Array. Usually, the summation function is defined like this:

function sum(arr) {
    return arr.reduce(function (x, y) {
        return x + y;
    });
}

sum([1, 2, 3, 4, 5]); // 15

However, what if you don’t need to sum immediately, but in the code that follows, recalculate as needed?

Instead of returning the result of the summation, you can return the summed function!

function lazy_sum(arr) {
    var sum = function () {
        return arr.reduce(function (x, y) {
            return x + y;
        });
    }
    return sum;
}

When we call lazy_sum(), the result is not the summation result, but the summation function:

var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()

When the function f is called , the result of the summation is actually calculated:

f(); // 15

In this example, we define the function sum in the function lazy_sum, and the internal function sum can refer to the parameters and local variables of the external function lazy_sum. When lazy_sum returns the function sum, the related parameters and variables are saved in the returned function. This program structure is called “Closure”, which has great power.

Please note that when we call lazy_sum(), each call returns a new function, even if the same argument is passed in:

var f1 = lazy_sum([1, 2, 3, 4, 5]);
var f2 = lazy_sum([1, 2, 3, 4, 5]);
f1 === f2; // false

The results of the sum call of f1() and f2() do not affect each other.

Closure

Notice that the returned function references a local variable arr inside its definition. When a function returns a function, its internal local variables are also referenced by the new function. So the closure is simple to use, and it is not easy to implement.

Another problem to be aware of is that the returned function is not executed immediately. Instead f() is executed until it is called. Let’s look at an example:

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push(function () {
            return i * i;
        });
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

In the above example, each time a loop is created, a new function is created. And then the three functions created are added to one Array and returned.

You might think that calling f1()f2() and the f3() should return 149, respectively. But the actual result is:

f1(); // 16
f2(); // 16
f3(); // 16

All are 16! The reason is that the returned function references the variable i, but it is not executed immediately. When all three functions are returned, the variables i they referenced has became 4, so the result is 16.

One thing to keep in mind is that: when returning a closure, the returned function should not reference any loop variables, or variables that will change later.

What if you must quote a loop variable? The method is to create a function that binds the current value of the loop variable with the parameter of the function. Regardless of how the loop variable is subsequently changed, the value bounded to the function argument is unchanged:

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push((function (n) {
            return function () {
                return n * n;
            }
        })(i));
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 1
f2(); // 4
f3(); // 9

Note that the syntax for “create an anonymous function and execute it immediately” is used here:

(function (x) {
    return x * x;
})(3); // 9

In theory, creating an anonymous function and executing it right away can be written like this:

function (x) { return x * x } (3);

However, due to the problem of JavaScript syntax parsing, a SyntaxError error is reported. So the entire function definition needs to be enclosed in parentheses:

(function (x) { return x * x }) (3);

Usually, an anonymous function that is executed immediately can be written as this:

(function (x) {
    return x * x;
})(3);

Having said that, isn’t the closure only meant to return a function and then delay execution?

Of course not! Closures have very powerful features. Give a example:

In object-oriented programming languages, such as Java and C++, to encapsulate a private variable inside an object, you can modify a private member variable.

In a language like JavaScript, there are only functions and no class mechanism. With a closure, you can also encapsulate a private variable. We create a counter with JavaScript:

'use strict';

function create_counter(initial) {
    var x = initial || 0;
    return {
        inc: function () {
            x += 1;
            return x;
        }
    }
}

It works like this:

var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3

var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13

In the returned object, a closure is implemented that carries a local variable x. External code can not access to the variable x. In other words, a closure is a function that carries a state, and its state can be completely hidden from the outside.

Closures can also turn multi-parameter functions into single-parameter functions. For example, to calculate xy , you can use the Math.pow(x, y) function. But considering that x2 and x3 are often calculated, we can use closures to create new functions pow2 and pow3:

'use strict';

function make_pow(n) {
    return function (x) {
        return Math.pow(x, n);
    }
}

Arrow Function

A new type of function has been added to the ES6 standard: Arrow Function.

Why it is the name arrow function? Because it is defined by an arrow:

x => x * x

The arrow function above is equivalent to:

function (x) {
    return x * x;
}

Before continuing to learn the arrow function, please test if your browser supports the arrow function in ES6:

'use strict';
var fn = x => x * x;
console.log ('Your browser supports ES6's Arrow Function!');

The arrow function is equivalent to an anonymous function and simplifies function definition. 

The arrow function has two formats. The first is similar as above, which contains only one expression, omitting { ... } and return. There is another way to include multiple statements, and you can’t omit { ... }and return:

x => {
    if (x > 0) {
        return x * x;
    }
    else {
        return - x * x;
    }
}

If the argument is not one, you need to add the brackets ():

// Two arguments:
var f1 = (x, y) => x * x + y * y

// No argument:
var f2 = () => 3.14

// Variable parameters:
var f3 = (x, y, ...rest) => {
    var i, sum = x + y;
    for (i=0; i<rest.length; i++) {
        sum += rest[i];
    }
    return sum;
}

If you want to return an object, be aware that if it is a single expression, you will get an error if you write in this way:

// SyntaxError:
x => { foo: x }

Because the brackets { ... } is a grammatical conflict with the function body. It should be changed to:

// ok:
x => ({ foo: x })

This Pointer

The arrow function seems to be a shorthand for anonymous functions, but in fact, there is a clear distinction between the arrow function and the anonymous function: the this pointer of the arrow function is in the lexical scope, which is determined by the context.

Looking back at the previous example, the following example does not give the expected result due to the error handling of the binding of this by the JavaScript function:

var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        var fn = function () {
            return new Date().getFullYear() - this.birth; // This points to window or undefined
        };
        return fn();
    }
};

Now, the arrow function completely fixes the this pointer. this always pointing to the lexical scope, which is the outer caller obj:

var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        var fn = () => new Date().getFullYear() - this.birth; // This points to the obj object
        return fn();
    }
};
obj.getAge(); // 25

If you does not use the arrow function, you can write a hack like this as we said before:

var that = this;

It is no longer needed.

Since the lexical scope is already bounded to this in the arrow function, when the arrow function is used with call() or apply(), the this binding cannot be performed. I.e., the first parameter passed in is ignored:

var obj = {
    birth: 1990,
    getAge: function (year) {
        var b = this.birth; // 1990
        var fn = (y) => y - this.birth; // this.birth is still 1990
        return fn.call({birth:2000}, year);
    }
};
obj.getAge(2015); // 25

Exercise 6

Use the arrow function to simplify the function passed in when sorting:

'use strict'
var arr = [10, 20, 1, 2];
arr.sort((x, y) => {
    ???
});
console.log(arr); // [1, 2, 10, 20]

Generator

The generator is a new data type introduced by the ES6 standard. A generator looks like a function, but can return multiple times.

Let’s review the concept of functions first. A function is a complete piece of code. Calling a function is passing in the arguments and returning the result:

function foo(x) {
    return x + x;
}

var r = foo(1); // Invoke the foo function

During the execution of the function, if the return statement is not encountered, the execution logic can not return to the called code. (If there is no return statement, the function implicit executes return undefined; in the end of the function.)

The generator is very similar to the function, defined as follows:

function* foo(x) {
    yield x + 1;
    yield x + 2;
    return x + 3;
}

The generator differs from the function in that the generator is defined with  function* (note the extra * symbol). And, in addition to the return statement, it can yield multiple results.

Most of the beginners immediately get confused. The generator is a “function” that can return multiple times? Is it useful?

Let’s see this example.

The famous Fibonacci sequence, which consists 01 at the beginning:

0 1 1 2 3 5 8 13 21 34 ...

To write a function that produces a Fibonacci sequence, you can write:

function fib(max) {
    var t,
        a = 0,
        b = 1,
        arr = [0, 1];
    while (arr.length < max) {
        [a, b] = [b, a + b];
        arr.push(b);
    }
    return arr;
}

// Test:
fib(5); // [0, 1, 1, 2, 3]
fib(10); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

The function can only be returned once, so one must returns an Array. However, if you switch to a generator, you can return a number at a time and keep returning multiple times.

Rewrite above codes with a generator as follows:

function* fib(max) {
    var t,
        a = 0,
        b = 1,
        n = 0;
    while (n < max) {
        yield a;
        [a, b] = [b, a + b];
        n++;
    }
    return;
}

Try it directly:

fib(5); // fib {[[GeneratorStatus]]: "suspended" [[GeneratorReceiver]]: Window}

Calling a generator directly is not the same as calling a function. The invocation of  fib(5) just creates a generator object and has not yet been executed.

There are two ways to call the generator object.

One is to repeatedly call the next() method of the generator object:

var f = fib(5);
f.next(); // {value: 0, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 2, done: false}
f.next(); // {value: 3, done: false}
f.next(); // {value: undefined, done: true}

The next() method executes the generator’s code, then returns an object {value: x, done: true/false} each time it encounters the yield keyword, and then “pauses”. The value is the value returned by yield. The done indicates whether the generator has finished. If done is true, then value is the value return by the return keyword.

NOTICE: When the returned done is true, it means that the generator object has finished its job. Do not continue to call next().

The second method is to use for ... of to iterate the generator object directly with a loop, which does not require our own judgment of done:

'use strict'

function* fib(max) {
    var
        t,
        a = 0,
        b = 1,
        n = 0;
    while (n < max) {
        yield a;
        [a, b] = [b, a + b];
        n ++;
    }
    return;
}
for (var x of fib(10)) {
    console.log(x); // => 0, 1, 1, 2, 3, ...
}

What is the advantage of generator compared to ordinary functions?

Because the generator can return multiple times during execution, it looks like a function that remembers the state of execution. With this, writing a generator can do what you need to do with object-oriented programming. For example, to save a state with an object, you have to write:

var fib = {
    a: 0,
    b: 1,
    n: 0,
    max: 5,
    next: function () {
        var r = this.a,
            t = this.a + this.b;
        this.a = this.b;
        this.b = t;
        if (this.n < this.max) {
            this.n++;
            return r;
        } else {
            return undefined;
        }
    }
};

It is quite cumbersome to save the state with the properties of the object.

Another great benefit of generator is the ability to turn asynchronous callback code into “synchronous” code. This benefit will not be realized until after learning AJAX.

Without generator, you need to write code like this when using AJAX:

ajax('http://url-1', data1, function (err, result) {
    if (err) {
        return handle(err);
    }
    ajax('http://url-2', data2, function (err, result) {
        if (err) {
            return handle(err);
        }
        ajax('http://url-3', data3, function (err, result) {
            if (err) {
                return handle(err);
            }
            return success(result);
        });
    });
});

The more callbacks, the more ugly the code.

With the beautiful era of generators, you can write this when using AJAX:

try {
    r1 = yield ajax('http://url-1', data1);
    r2 = yield ajax('http://url-2', data2);
    r3 = yield ajax('http://url-3', data3);
    success(r3);
}
catch (err) {
    handle(err);
}

It seems to be synchronous code, the actual execution is asynchronous.

Exercise 7

To generate an auto-incrementing ID, you can write a next_id() function:

var current_id = 0;

function next_id() {
    current_id++;
    return current_id;
}

Since the function cannot save state, a global variable current_id is needed to hold the number.

No closure, try generator rewrite:

'use strict';
function* next_id() {
// TODO: Add your code here
}

// Test:
var x,
    pass = true,
    g = next_id();
for (x = 1; x < 100; x ++) {
    if (g.next().value !== x) {
        pass = false;
        console.log('Test failed!');
        break;
    }
}
if (pass) {
    console.log('test passed!');
}

Closing Words

Thanks for reading this long article!

Congratulations! You now know how to write a function, a closure, a arrow function. You are an expert of JavaScript functions.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *