【读书笔记】读《JavaScript高级程序设计-第2版》 - 函数部分

1. 定义数组

         函数其实是对象,每一个函数都是Function类型的实例,并且都与其余引用类型同样具备属性和方法。因为函数是对象,所以函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。闭包

         对于函数的定义有如下三种类型:app

         函数声明式定义——如函数

1 function sum(num1, num2) {return num1 + num2;}

         函数表达式定义——如this

1 var sum = function(num , num2) {return num1 + num2;};  //注意函数末尾有一个分号,就像声明其余变量同样

      Function构造函数——如spa

1  var sum = Function("num1", "num2", "return num1 + num2"); //不推荐,可是这种写法对于理解“函数是对象,函数名是指针”的概念却是很是直观的。

         因为函数名仅仅是指向函数的指针,所以函数名与包含对象指针的其余变量没有区别,也就是说,一个函数可能会有多个名字,如 prototype

1  function sum(num1, num2) {return num1 + num2;}
2  var anotherSum = sum;  //注意:使用不带圆括号的函数名是访问函数的指针
3  sum = null;
4  alert(anotherSum(10, 10));  //20

2. 没有重载指针

         将函数名做为指针,很容易理解为何js中没有函数重载的概念。调试

1  function addNum(num) {return num + 100;}
2  function addNum(num) {return num + 200;}
3  var result = addNum(100);  //300

  显而后面的函数覆盖了前面的函数。code

         如下作以等效替换——

1  var addNum = function(num) {return num + 100;}
2  addNum = function(num) {return num + 200;}
3  var result = addNum(100);  //300

  所以说,在建立第二个函数的时候,实际上覆盖了引用第一个函数的变量addNum

 3. 函数声明与函数表达式

         解析器会率先读取函数声明,并使其在执行任何代码以前可用(能够访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。或者理解为第一种定义方式会在代码执行以前被加载到做用域中,然后者则是在代码执行到那一行的时候才会有定义。

4. 做为值的函数

         由于js中的函数名自己就是变量,因此函数能够当作值来使用。也就是说,不只能够向传递参数同样把一个函数传递给另外一个函数,并且能够将一个函数做为另外一个函数的结果返回。

5. 函数内部属性(函数被调用后所具备的属性)

  每一个函数在被调用时,其活动对象都会自动取得两个特殊的对象:argumentsthis

  arguments是一个类数组对象,包含着传入函数中的全部参数,这个对象还有一个名为callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数。

         this引用的是函数据以执行操做的对象,或者说是函数在执行时所处的做用域。(当在网页的全局做用域中调用函数时,this对象引用的是window)        

1  window.color = "red";
2  var o = { color:"blue"};
3  function saycolor() {
4      alert(this.color); 
5  }
6  saycolor();  //red:在全局做用域中调用
7  o.saycolor = saycolor;
8  o.saycolor();  //blue  //此时this引用的是对象o

  注意:函数的名字仅仅是一个包含指针的变量而已,所以,即便是在不一样的环境中执行,全局的saycolor()函数与o.saycolor()指向的仍然是同一个函数。

 6. 函数自己(固有)属性和方法

  每个函数都有两个属性:lengthprototype其中,length属性表示的是函数但愿接受的命名参数的个数,如

1  function sum(num1, num2) {return num1 + num2;}
2  alert(sum.length)  //2

         prototype属性是保存它们全部实例方法的真正所在。换句话说,诸如toString()valueOf()等方法实际上都保存在prototype名下,只不过是经过各自对象的实例访问罢了。

        每一个函数都包含两个非继承而来的方法:apply()call()。这两个方法的用途都是在特定的做用域中调用函数,实际上等于设置函数体内this对象的值。先看apply()方法的使用——

 1  function sum(num1, num2) {
 2      return num1 + num2;
 3  }
 4  function callSum1(num1, num2) {
 5      return sum.apply(this, arguments);  //传递arguments对象
 6  }       
 7  function callSum2(num1, num2) {
 8      return sum.apply(this, [num1, num2]);  //传递数组
 9  }       
10  alert(callSum1(10, 10));  //20这里的this做用域是window对象
11  alert(callSum2(10, 10));  //20这里的this做用域是window对象

  call()apply()的做用相同,区别在于接受参数的方式不一样。如

1  function callSum3(num1, num2) {
2      return sum.call(this, num1, num2);  //传递的参数必须逐个列举出来
3      // return sum.call(this, arguments[0], arguments[1]);
4  }

  对因而使用apply()仍是call(),彻底取决于采起哪一种给函数传递参数的方式最方便。

         对于这两个方法的真正强大之处在于可以扩充函数赖以运行的做用域。如

 1  window.color = "red";
 2  var o = { color:"blue"};    
 3  function saycolor() {
 4      alert(this.color); 
 5  }       
 6  saycolor();  //red  
 7  saycolor.call(this);  //red
 8  saycolor.apply(window);  //red
 9  saycolor.apply(o);  //blue
10         
11  //o.saycolor = saycolor;  //对象和方法具备必定的耦合
12  //o.saycolor();  //blue

  所以,使用call()(或者apply())来扩充做用域的最大好处是对象不须要与方法有任何耦合关系。

7. 理解返回值

         函数在定义时没必要指定是否返回值。若是函数具备返回值,函数会在执行完return语句以后中止并当即退出,所以,位于return语句以后的任何代码都永远不会执行。另外,return语句也能够不带有任何返回值,在这种状况下,函数在中止执行后将返回undefined值,这种作法通常用在须要提早中止函数执行而又不须要返回值的状况下。如

1  function sayHi(name, msg) {
2      return;
3      alert("Hello " + name + "," + msg);  //永远不会被调用
4  }

          推荐的作法是要么让函数始终都返回一个值,要么永远都不要返回值。不然,若是函数有时候返回值,有时候不返回值,会给调试代码带来不便。

8. 理解参数

  JavaScript函数不介意传递进来多少个参数,也不在意传进来的参数是什么数据类型。如

1  //常规写法
2  function sayHi(name, msg) {
3      alert("Hello " + name + "," + msg);  //永远不会被调用
4  }
5  //变通写法
6  function syaHi() {
7      alert("Hello " + arguments[0] + "," + arguments[1]);
8  }
9  sayHi("King", "How are you?");  //两个函数的执行结果是一致的

  之因此会这样,缘由是JavaScript中的参数在内部是用一个数组来表示的。函数接收到的始终是一个数组,而不关心数组汇总包含哪些参数。若是这个数组不包含任何元素,无所谓;若是包含多个参数,也没有问题。

         能够经过arguments.length属性能够获知有多少个参数传递了函数。

         固然,还能够arguments对象和命名参数一块儿使用,如  

1  function doAdd(num1, num2) {
2      if (arguments.length == 1) {
3          alert(num1 + 10);
4      } else if (arguments.length == 2) {
5          alert(num1 + num2);
6      }
7  }

  没有传递值的命名参数将自动被赋予undefined值,这就跟定义了变量但又没有初始化同样。 

9. 匿名函数——递归

  一个很是经典的递归阶乘函数:

 1  function factorial(num) {
 2      if (num <= 1) {
 3          return 1;
 4      } else {
 5          return num * factorial(num - 1);
 6      }
 7  }
 8  var anotherFactorial = factorial;
 9  factorial = null;
10  alert(anotherFactorial(4));  //出错,源于内部定义已经引用方法factorial

  方法改进以下:

1  function factorial(num) {
2      if (num <= 1) {
3            return 1;
4      } else {
5            return num * arguments.callee(num - 1); //arguments.callee指代的是被调用者
6      }
7  }  //方法经改进后alert(anotherFactorial(4));就不会出错了

10. 匿名函数——闭包

  闭包指有权访问另外一个函数做用域中的变量的函数。(最核心的目的是从函数的外部读到函数的内部变量)

 1  function f1(){
 2      var n=999;
 3      //它自己是一个匿名函数,同时也是一个闭包
 4      //这里的变量nAdd是一个全局变量
 5     nAdd=function(){
 6          n+=1;
 7      }
 8    function f2(){
 9      //能够对上层函数f1的局部变量进行任何操做
10      alert(n);
11    }
12    return f2;
13  }
14  //闭包是一个函数,这个函数能够访问到另外一个函数的局部变量
15  var result=f1();  //因为闭包函数的存在,使得f1()函数中的局部变量被保存在内存中,使得当f1()函数被调用后,其内部的变量n依旧存在,并无在f1调用后被自动清除
16  //n一直保存在内存中的缘由剖析:
17  //f1是f2的父函数,而f2被赋给了一个全局变量result,这致使f2始终在内存中,
18  //而f2的存在依赖于f1,所以f1也始终在内存中,不会再调用结束后,被垃圾收集机制回收
19  result();  // 999
20  nAdd();  //由于n一直保存在内存中,nAdd()自己就是一个闭包,nAdd()是对其上层父函数f1中局部变量的操做,因此n的值变为了1000
21  result(); // 1000

   思考——

 1  var name = "The Window";
 2  var object = {
 3    name : "My Object",
 4    getNameFunc : function(){
 5      return function(){
 6        return this.name;   // The Window
 7      };
 8    }
 9  };
10  alert(object.getNameFunc()()); //The Window  ??

  this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被当作某个对象的方法调用时,this等于那个对象。不过,匿名函数的执行环境具备全局性,所以其this对象一般指向window

  为何会这样呢?缘由是在另外一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的做用域链中。每一个函数再被调用的时候,其活动对象都会自动取得两个特殊变量:thisarguments。内部函数在搜索者两个变量时,只会搜索到其活动对象为止,所以不可能直接访问到外部函数中的这两个变量。

  改进:把外部做用域中的this对象保存在一个闭包可以访问到的变量里,就可让闭包访问该对象了,以下所示:

 1  var name = "The Window";
 2  var object = {
 3      name : "My Object",
 4    getNameFunc : function(){
 5          var that = this;
 6          return function(){
 7              return that.name;   // My Object
 8          };
 9      }
10  };
11  alert(object.getNameFunc()()); // My Object

  注意:因为闭包会携带包含它的函数的做用域,所以会比其余函数占用更多的内存。过分使用闭包可能致使内存占用过多,所以建议只在绝对必要时再考虑使用闭包

11. 匿名函数——模仿块级做用域

1  function output(count) {
2      for(var i = 0; i < count; i++){
3      alert(i);
4  }
5  var i;//JavaScript不会告诉是否屡次声明了变量,只会对后续的声明视而不见,但会改变其值
6  }

  从i有了定义开始,就能够在函数内部随处访问它。

  咱们想要在i使用完以后当即销毁掉。能够用匿名函数来模仿块级做用域来实现。

1  (function(){
2      //块级做用域
3  })()

  注意:不能够丢掉左边的小括号,即须要将函数封装在一对括号中。不然jsfunction关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号。对于函数表达式的写法,如

1  var someFunc = function(){};
2  someFunc();

  其表达式变量someFunc随后跟了一个小括号,表明函数的调用。所以,同理而言,(function(){//块级做用域})()的左侧小括号表明一个匿名函数表达式,然后边的小括号表明该匿名函数的执行。所以,其内部的变量在执行后随即被销毁。

  因此,不管在什么地方,只要临时须要一些变量,就可使用块级做用域(也称为私有做用域)。对于上面的例子可改造以下:

1  function output(count) {
2      (function(){
3          //可以访问到count变量是由于块级做用域自己是一个闭包
4          for(var I = 0; I < count; i++){ 
5              alert(i);
6          }
7      )();
8      alert(i);  //报错
9  }

  这种技术常常在全局做用域中被用在函数外部,从而限制向全局做用域中添加过多的变量和函数。通常来讲,咱们应该尽可能减小向全局做用域中添加变量和函数。在一个由不少人开发人员共同参与的大型应用程序中,过多的全局变量和函数很容易致使命名冲突。经过建立私有做用域,每一个开发人员既可使用本身的变量,又没必要担忧搞乱全局做用域。并且,这种作法能够减小闭包占用的内存问题,由于没有指向匿名函数的引用。只要函数执行完毕,就能够当即销毁其做用域链了。

12.关于匿名函数,总结以下:

  1>任何函数表达式从技术上说都是匿名函数,由于没有引用它们的肯定的方式;

  2>递归函数应该始终使用arguments.callee来递归地调用自身,不要使用函数名,由于函数名可能会发生变化;

  3>当在函数内部定义了其余函数时,就建立了闭包。闭包有权访问包含函数内部的全部变量;是源于:

    a>在后台执行环境中,闭包的做用域链包含着它本身的做用域、包含函数的做用域和全局做用域;

    b>一般,函数的做用域及其全部变量都会在函数执行结束后被销毁。可是,当函数返回了一个闭包时,这个函数的做用域将会一直在内存中保存到闭包不存在为止;

  4>使用闭包能够在JavaScript中模仿块级做用域,要点以下:

    a>建立并当即调用一个函数,这样既能够执行其中的代码,又不会在内存中留下对该函数的引用;

    b>结果就是函数内部的全部变量都会被当即销毁,除非将某些变量赋值给了包含做用域中的变量;

相关文章
相关标签/搜索