【讀書筆記】讀《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>結果就是函數內部的全部變量都會被當即銷燬,除非將某些變量賦值給了包含做用域中的變量;

相關文章
相關標籤/搜索