细说JavaScript的函数

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了细说JavaScript的函数相关的知识,希望对你有一定的参考价值。

这是第二篇博客,路还很漫长,我才刚开始。

javascript中几乎万物皆对象,函数也不例外。然而函数不只是把一堆属性打包在一起,函数是这门语言的核心。要理解它的核心还得从基础说起。

一、JavaScript中的代码块

      要理解JavaScript函数,你得先理解代码块。JavaScript的代码块只是把语句组合在一起。代码块以左花括号“{”开头以右花括号“}”结束。简单来说,代码块让花括号中的语句在一起执行。代码块是JavaScript中最基本的控制结构。下面是JavaScript代码块的一些例子:

// Immediately invoked function expression
;!function () {
   var triumph = false,
         cake = false,
         satisfaction = 0,
         isLie,
         note;

    // Block used as part of a function expression
   var isLie = function (val) {
         return val === false;
   }

   // Block used as part of a conditional statement
   if (isLie(cake)) {
        triumph = true;
        makeNote(‘huge success‘);
        satisfaction += 10;
   }

   // Block used as part of a function declaration
   function makeNote(message) {
        note = message;
   }
}();

正如你所看到的,函数本质上就是开发者可以调用的有名字的代码块。举个例子:

// The inline conditional block statement is executed only once per cycle.
if (isLie(cake)) {
   ...
}

function makeNote(message) {
   ...
}

// The function declaration is executed as many times as it is called.
makeNote("Moderate Success");
makeNote("Huge Success");

1. 函数参数

   可以通过把参数传进函数体来初始化诸如控制语句(if,for,while...)的函数。在JavaScript中,变量要么是复合类型(如对象,数组),要么是基本类型(如字符串,整数)。当复合类型做参数时,它通过引用传递给函数体,JavaScript传递的是变量在内存堆中位置的指针,而不是变量的拷贝。相反,当给函数传达基本类型时,JavaScript按值进行传递。这点不同可能会导致微妙的bug,因为函数经常被当作概念上的黑盒,人们往往假定函数返回变量时只会影响封闭的作用域。当按引用传递时,即使函数没有返回值,参数对象也可能被修改。下面是通过引用传递和按值传递的例子:

var object = {
             ‘foo‘: ‘bar‘
       },
       num = 1;

       // Passed by reference 按引用传递
       ;!function(obj) {
           obj.foo = ‘baz‘;
       }(object);
       // => Object {foo: "baz"}
       console.log(object);

       // Passed by value 按值传递

       ;!function(num) {
           num = 2;
       }(num);
       // => 1
       console.log(num);

(1) 参数对象—arguments

    参数对象适用于不需要提前指定参数数量的函数。参数对象就像通配符,可以像数组一样遍历参数对象来访问任意数量的参数。比如:

var sum = function () {
            var len = arguments.length,
                total = 0;
            for (var x = 0; x < len; x++) {
                total += arguments[x];
            }
            return total;
        };

        // => 6
        console.log(sum(1, 2, 3));

   然而参数对象只是跟数组有些像而已,如果你用更多的数组方法来重写这段脚本就会产生错误:

var sum = function () {
               var len = arguments.length,
                   total = 0;
               while (arguments.length > 0) {
                   total += arguments.pop();
               }    
               return total;
        };
        // Uncaught TypeError: arguments.pop is not a function
        sum(1, 2, 3);

幸运的是,ECMAScript 6改善了函数参数的使用方式,很少需要使用原来的参数对象了。我们可以看看参数里加入的几个新特性。

(2)默认参数 (ECMAScript 6)

许多编程语言允许在函数签名中为参数选择默认值。JavaScript会在ECMAScript 6中支持这个特性。

var join = function (foo = ‘foo‘, baz = (foo === ‘foo‘) ? join(foo + "!") : ‘baz‘) {
            return foo + ":" + baz;
        }

        // => hi:there
        console.log(join("hi", "there"));

        // Use the default parameter when not supplied
        // => hi:baz
        console.log(join("hi"));

        // Use the default parameter when undefined is supplied
        // => foo:there
        console.log(join(undefined, "there"));

        // Use an expression which has access to the current set of arguments
        // => foo:foo!:baz
        console.log(join(‘foo‘));

(3) 剩余参数rest (ECMAScript 6)

有时函数需要支持任意数量的参数,然而用参数对象来做会有些麻烦。

 1  var dispatcher = {
 2             join: function (before, after) {
 3                 return before + ‘:‘ + after
 4             },
 5             sum: function () {
 6                 var args = Array.prototype.slice.call(arguments);
 7                 return args.reduce(function (previousValue, currentValue, index, array) {
 8                     return previousValue + currentValue;
 9                 });
10             }
11         }
12         var proxy = {
13             relay: function (method) {
14                 var args;
15                 args = Array.prototype.splice.call(arguments, 1);
16                 return dispatcher[method].apply(dispatcher, args);
17             }
18         };
19         // => bar:baz
20         console.log(proxy.relay(‘join‘, ‘bar‘, ‘baz‘));
21         // => 28
22         console.log(proxy.relay(‘sum‘, 1, 2, 3, 4, 5, 6, 7));

在前面的例子里,我们的proxy对象期待一个参数,这个参数作为方法在dispatcher上进行调用。它不知道在这个函数调用时还需要多少其他的参数。要知道参数对象并不是数组,也没有splice,map或reduce这样的方法。为了把剩下任意数量的参数传送给dispatcher,你必须用数组来处理它们。

   有了剩余参数rest,你不必再像对暗号一样跟函数打交道。这里用剩余参数rest来重写前面的方法:

var dispatcher = {
            join: function (before, after) {
                return before + ":" + after
            },
            sum: function (...rest) {
                return rest.reduce(function (previousValue, currentValue, index, array) {
                    return previousValue + currentValue;
                });
            }
        };
        var proxy = {
            relay: function (method, ...goodies) {
                return dispatcher[method].apply(dispatcher, goodies);
            }
        };
        // => bar:baz
        console.log(proxy.relay(‘join‘, ‘bar‘, ‘baz‘));
        // => 28
        console.log(proxy.relay(‘sum‘, 1, 2, 3, 4, 5, 6, 7));

2. 函数的类型

现在你已经对代码块和参数有了更好的理解,让我们进一步深入到函数声明和函数表达式,看看如何在JavaScript中使用这两种类型的函数。如果不仔细看,两者可能非常相似。

// Function Declaration
function isLie(cake) {
   return cake === true;
}

// Function Expression
var isLie = function(cake) {
   return cake === true;
}

 二者之间唯一的区别发生在赋值期间。解释器可以在语法解析期间访问到函数声明。而函数表达式是赋值表达式的一部分,在整个程序赋值完成前都不能执行。这点区别看起来很小,但影响很大。考虑一下这种情况:

// => Hi, I‘m a function declaration!
        declaration();
        function declaration() {
            console.log("Hi, I‘m a function declaration!");
        }

        // => Uncaught TypeError: expression is not a function
        expression();
        var expression = function () {
            console.log("Hi, I‘m a function expression!");
        }

就像前面例子里所看到的,函数表达式在被调用时抛出了异常,但函数声明执行正常。这个异常揭示了函数声明和函数表达式的关键不同。JavaScript可以理解声明的函数,并能在程序执行前将函数解析。因此,函数的调用在声明前后都没关系,因为JavaScript在幕后已经将函数提升到当前作用域的顶端。而函数表达式直到变量被赋值时才进行计算,因此当它被调用时仍然是undefined。所以优秀的代码风格应该在当前作用域的顶端定义所有变量。这样做的话,脚本的顺序就跟JavaScript语法解析时的顺序一样了。

      前面省略了一个概念,当语法解析时,JavaScript会把所有函数声明移到当前作用域的顶端。所以声明式函数可以放在脚本中的任意位置。为了更进一步探索声明和表达式间的区别,看看这个例子:

function sayHi() {
            console.log("hi");
        }

        var hi = function sayHi() {
            console.log("hello");
        }

        // => hello
        hi();
        // => hi
        sayHi();

你在看这段代码的时候可能会以为函数声明会崩溃,因为它跟函数表达式重名了。然而因为第二个函数是赋值变量的一部分,拥有自己的作用域,所以JavaScript把它们当成不同的实体。

 

以上是关于细说JavaScript的函数的主要内容,如果未能解决你的问题,请参考以下文章

如何将此 JavaScript 代码片段翻译成 Parenscript?

10个JavaScript代码片段,使你更加容易前端开发。

10个JavaScript代码片段,使你更加容易前端开发。

函数重载细说

细说Python的lambda函数用法,建议收藏

jQ选择器学习片段(JavaScript 部分对应)