第二章 函数

2.1 剖析函数定义

  ① 基本形式:

    function name(...) {

      ...

      return x*x;  // 若return后面没有表达式,函数将返回 undefined。

    }

  ② 关键字function 通常用于创建新函数。

  ③ 当代码遇到return语句时,立即跳出当前的函数,将返回值传递给调用该函数的代码。

  2.1.1 定义顺序

    ① 定义函数可以在调用函数之后(拥有不同时间轴)。

    ② 原理:计算机在开始执行语句前,找到所有定义的function ,然后保存相关的function。我们不必考虑多个函数定义的顺序,使用函数允许它们之间相互调用,而不考虑哪个函数在第一个位置定义。

  2.1.2 局部变量

    ① 函数一个非常重要的特性就是:内部创建的变量是函数的局部变量(在每次调用函数时创建,退出时则不再存在)。

    ② 如果没有定义同名的局部变量,函数内部则可能访问全局(非局部)变量。

    ③ 局部变量必须用 var 定义,否则视为全局变量。

    ④ 没有包含return语句,函数的实际返回值是undefined。

  2.1.3 嵌套作用域

    ① 在JavaScript中,仅区分全局变量和局部变量还不够。实际上,变量作用域可以有任意层级(或嵌套)。其他函数内部定义的函数可以调用父函数的局部变量,而内部函数里定义的函数则不仅可以调用父函数的局部变量,还可以调用祖父函数的局部变量,以此类推。

    ② 函数内部的变量集是否可见,取决于函数在程序中的位置,在函数“上面”定义的所有变量都是可见的,也就是存在于函数体内并包含函数定义的以及位于程序顶级的变量。这种方式的变量可访问性称为词法作用域

    ③JavaScript 不提供代码块(大括号之间)里产生新的局部环境。函数是唯一能创建新作用域的地方。(很多人认为这是JavaScript 设计者的小失误,新版本将增加此功能)

  2.1.4 栈(先进后出)

    ① 当函数调用时,程序控制权交给函数体,当函数体返回后,继续调用函数之后的语句。因此,当函数体运行时,计算机必须记住调用该函数的上下文,以便知道之后从哪里继续。存放上下文的地方就是栈

    ② 栈之所以存在,与一个函数体可以再次调用另外一个函数有关。每次一个函数被调用的时候,另外一个上下文就要被存储起来。(每次函数调用,当前上下文压入栈顶,函数返回时,在顶部的上下文就会被弹出栈重新获得)

    ③ 栈需要存储于计算机的内存空间里,若栈增长得太大,计算机就会抛出“堆栈溢出”或“太多递归”。(例如:两个函数互调的死循环)

  2.1.5 函数值

    ① JavaScript 里的所有东西都是值,包括function 函数。这就是说:定义的函数名可以像普通的变量一样使用,而且其内容可以传递给表达式并用于更大的表达式。

    ② 特例说明:

      var a = null;

      function b() {return "B";}

      (a||b)();  // 结果:"B"

      -->若(a||b)()表达式产生的不是函数,则调用产生错误;一旦产生的是函数,调用一切正常(如本例)。

      var a = null;

      (a ||function() {return "B";})();  // 结果:"B"

      -->代码产生效果与上例一样(除了没有定义函数名b)。

     ③ 在第5章中,我们将进一步探讨函数第一型的特征(通常是“函数都是值”这一概念的术语),并利用其特性编写一些灵活的代码。

  2.1.6 闭包  ???

    函数栈的特性及其将函数用作值的能力带来了一个有趣的问题:如果创建局部变量的函数调用不在栈上了,那局部变量会发生什么?

      function createFunction() {

        var local = 100;

        return function() {return local;};

      }  // 返回值:100

    如何处理这一情况就是向上函数变元问题(upwards Funarg problem)(许多旧编程语言禁用了这种形式),JavaScript 中,只要这个局部变量是可达的,就会尽力保存局部变量。

    这种特性称为闭包。包裹一些局部变量的一个函数叫做一个闭包。该行为不仅让我们不用担心变量是否依然存在,而且还可以创造性地使用函数值。

    例如:(下面的函数可以动态创建函数值:将函数的参数加上指定的数字。)

      function makeAdder(amount) {

        return function(number) {

          return number + amount;

        };

      }

      var addTwo = makeAdder(2);

      addTwo(3);  // 等价于function(3)  (amount在内存中一直存在,值为2) ==> 结果:5;

    例2:

      function a() {

        var i=0;

        function b() {

          alert(++i);

        }

        retrun b;

      }

      var c = a();

      c();  //等价于b();  (i的值一直在内存中)

      c();  //等价于b();  (i的值一直在内存中)

    ① 在JavaScript中,如果一个对象不再被引用,就会被GC(垃圾回收机制)回收。如果两个对象相互引用,而不再被第三个对象引用,那么相互引用的对象也会被回收。

    ② 例2中,因 a()被 b()引用,b()又被 a()外的 c 引用,所以a执行后不会被回收。

    ③ 例2中,闭包 a()中变量会存在内存中,闭包a的内部函数 b()中的变量被回收。

    ④ 例2中,c(); 相当于调用 b 函数,因为 a();的返回值是 b 函数。

  2.1.7 可选参数

    ① JavaScript 不会限制传入函数的参数数目。

    ② 若传入的参数过多,多余的参数则被忽略掉。

    ③ 若传入的参数过少,缺失的参数默认为 undefined。

    ④ 这样做的好处是:可以使用函数接受“可选参数”。坏处:意外传入的错误参数不会提示。

2.2 技巧

  2.2.1 避免重复

    ① 发明函数的原因就是为了代码复用。

    ② 定义函数条件:

      a. 代码可能在不同项目中使用。

      b. 尽量不要在函数内包括print 行为。更为灵活,而且print(XX(...))不会比printXX(...)更难输入。

      c. 在自己明确自己需要该函数前,不要定义函数(引诱我们为每个小功能编写复杂框架的陷阱,并且这些功能没有任何实际作用)。

  2.2.2 纯函数

    ① 用Purity 描述函数,指这个函数是否存在副作用。

    ② 纯函数在数理上指:当使用函数的时候,同样的参数总是返回同样的值,而没有副作用。

    ③ 纯函数和非纯函数主要区别:在代码设计和思维层面上。

      a. 纯函数调用,可以用结果直接替换而不需要改变代码的意义。当不确定函数是否正常,只需调用测试即可。

      b. 非纯函数,因各种因素可能返回不同的值并产生副作用,我们可能很难测试和理解这些副作用。

    ④ 多数情况下,我们需要非函数。少数情况,纯函数也可以解决,但非纯函数会更方便、更有效。

     原则:若我们可以很自然地使用纯函数,那么就用纯函数,否则我们使用非纯函数(不用觉得使用非纯函数很低级)。

  2.2.3 递归

    一个函数调用自身叫做递归。

    ① 在大多数JavaScript 实现里,使用递归会比循环慢10倍。在Javascript 里,运行简单的循环比多次调用一个函数方便的多。

    ② 如果再该函数上使用一个足够打的幂数,将会导致栈溢出。

    ③ 很多开发人员反复的基本原则:只有在证明程序运行太慢时采取关注效率问题。一旦出现这种情况,找出占用最多时间的代码,然后将这些美观的代码改成高效的代码。

    ④ 有些问题用递归解决比循环要简单得多。大部分是一些需要探测或处理几个分支情况(每个分支又可能生成更多分支)的问题。

      例如:从数字1开始,重复执行加5或者乘3这个步骤,编写函数,找出指定数字的运算序列。

        function findSequence(goal) {

          function find(start,history) {

            if(start == goal)

              retrun history;

            else if(start > goal)

              return null;

            else

              return find(start + 5, "(" + history + "+5)") ||

                  find(start * 3, "(" + history + "*3)");

          }

          return find(1,"1");

        }

        findSequence(24);  // 结果:(((1 * 3) + 5) * 3)

posted @ 2014-09-24 10:03  被盗的小偷  阅读(88)  评论(0)    收藏  举报