JavaScript Tutorial 5: Function

Javascript tutorial (学习笔记) 系列基本上翻译自 “A re-introduction to JavaScript (JS Tutorial)” 加上自己学习过程中的心得体会.  这一篇是第五篇(接Javascript Tutorial (学习笔记) 4):

Functions

Along with objects, functions are the core component in understanding JavaScript. The most basic function couldn’t be much simpler:

function add(x, y) {
    var total = x + y;
    return total;
}

This demonstrates everything there is to know about basic functions. A JavaScript function can take 0 or more named parameters. The function body can contain as many statements as you like, and can declare its own variables which are local to that function.

这里可能又需要解释一下了, 前文说道, javascript的变量没有local的概念就是全局都能访问到, 比如for, 或者if里面定义的变量, 在if外面也可以访问到, 这个其实是因为他们都在全局函数”main”里面, javascrip是以函数function作为域的单位, 域之间是不可见的, 在每一个单独的域(function)里面, 不管在哪里定义的变量(当然不能又是在内部的function定义), 在该域的全局范围内都可以访问到. 一下是一段测试代码:

#! /usr/bin/env node

for(var sum1 = -19; false; ) {} //sum1 is within the "main" scope

{
    {
        sum2 = -22;
    }
}

function add(x, y) {
    var sum = x + y; //sum is only visible within add
    console.log("within add, sum1 = " + sum1); //print sum1 = -19 so sum1 is accessible
    console.log("within add, sum2 = " + sum2); //print sum2 = -22 so sum1 is accessible
    return sum;
}

console.log("sum1 = " + sum1); //print sum1 = -19 so sum1 is accessible
console.log("sum2 = " + sum2); //print sum2 = -22 so sum1 is accessible
console.log("sum x + y = " + add(3, 8)); //print 11
console.log("sum x + y = " + sum); //ReferenceError: sum is not defined

/**
 * sum1 = -19
 * sum2 = -22
 * within add, sum1 = -19
 * within add, sum2 = -22
 * sum x + y = 11
 *
 * console.log("sum x + y = " + sum); //ReferenceError: sum is not defined
                             ^
                             ReferenceError: sum is not defined

 *
 */

The return statement can be used to return a value at any time, terminating the function. If no return statement is used (or an empty return with no value), JavaScript returns undefined.

The named parameters turn out to be more like guidelines than anything else. You can call a function without passing the parameters it expects, in which case they will be set to undefined.

> add()
NaN // You can't perform addition on undefined

You can also pass in more arguments than the function is expecting:

> add(2, 3, 4)
5 // <span style="color: #ff0000;">added the first two; 4 was ignored</span>

That may seem a little silly, but functions have access to an additional variable inside their body called arguments, which is an array-like object holding all of the values passed to the function. Let’s re-write the add function to take as many values as we want:

function add() {
    var sum = 0;
    for (var i = 0, j = arguments.length; i < j; i++) {
        sum += arguments[i];
    }
    return sum;
}

> add(2, 3, 4, 5)
14

That’s really not any more useful than writing 2 + 3 + 4 + 5 though. Let’s create an averaging function:

function avg() {
    var sum = 0;
    for (var i = 0, j = arguments.length; i < j; i++) {
        sum += arguments[i];
    }
    return sum / arguments.length;
}
> avg(2, 3, 4, 5)
3.5

This is pretty useful, but introduces a new problem. The avg() function takes a comma separated list of arguments — but what if you want to find the average of an array? You could just rewrite the function as follows:

function avgArray(arr) {
    var sum = 0;
    for (var i = 0, j = arr.length; i < j; i++) {
        sum += arr[i];
    }
    return sum / arr.length;
}
> avgArray([2, 3, 4, 5])
3.5

But it would be nice to be able to reuse the function that we’ve already created. Luckily, JavaScript lets you call a function and call it with an arbitrary array of arguments, using the apply() method of any function object.

> avg.apply(null, [2, 3, 4, 5])
3.5

The second argument to apply() is the array to use as arguments; the first will be discussed later on (注: 第一个参数的意思其实是在函数运行时指定的this, 而且this值并不一定是该函数执行时真正的this值, 一般设置成null即可, 涉及高级用法的时候会比较灵活). This emphasizes the fact that functions are objects too.

JavaScript lets you create anonymous functions.

var avg = function() {
    var sum = 0;
    for (var i = 0, j = arguments.length; i < j; i++) {
        sum += arguments[i];
    }
    return sum / arguments.length;
}

This is semantically equivalent to the function avg() form. It’s extremely powerful, as it lets you put a full function definition anywhere that you would normally put an expression. This enables all sorts of clever tricks. Here’s a way of “hiding” some local variables — like block scope in C(因为前文已经了解到, 普通的block, 比如花括号, 是不能阻止某一个变量只局限于那个block以内, 但是function可以成为一个独立的域, 里面的变量只局限于function内部访问):

> var a = 1;
> var b = 2;
> (function() {
    var b = 3;
    a += b;
})(); //注: 第一个括号其实是直接使用匿名函数整体的函数定义作为名字, 后面加上()进行调用,从而得到定义的同时直接执行
> a
4
> b
2

JavaScript allows you to call functions recursively. This is particularly useful for dealing with tree structures, such as you get in the browser DOM.

function countChars(elm) {
    if (elm.nodeType == 3) { // TEXT_NODE
        return elm.nodeValue.length;
    }
    var count = 0;
    for (var i = 0, child; child = elm.childNodes[i]; i++) {
        count += countChars(child);
    }
    return count;
}

This highlights a potential problem with anonymous functions: how do you call them recursively if they don’t have a name? The arguments.callee usage is deprecated and even disallowed in strict mode. Instead, you should use “named anonymous functions” as below:

var charsInBody = (function counter(elm) {
    if (elm.nodeType == 3) { // TEXT_NODE
        return elm.nodeValue.length;
    }
    var count = 0;
    for (var i = 0, child; child = elm.childNodes[i]; i++) {
        count += counter(child);
    }
    return count;
})(document.body); <span style="color: #ff0000;">//还是一个在定义的同时直接执行的匿名函数.</span>

The name provided to an anonymous function as above is (or at least should be) only available to the function’s own scope. This both allows more optimizations to be done by the engine and a more readable code.

Written on August 27, 2013