javascript高級程序設計第七章(函數表達式、閉包)筆記

第七章 函數表達式

函數有一個非標準的name屬性,通過這個屬性可以訪問到給定函數指定的名字。


(一)創建函數的幾種方式:

1、函數聲明

function functionName(arg0,arg1,arg2){}

2、函數表達式:如果想要創建匿名函數、給 prototype(原型)添加函數或是將函數用作其它對象的 property(屬性),都可以用 Function Expression。

var functionName = function(arg0,arg1,arg2){}//匿名函數(拉姆達函數),function後面沒有標識符,name屬性爲空字符串

函數聲明與函數表達式的異同

  • 函數表達式使用之前必須先賦值
  • 注意:if(condition){}else{}兩個大括號中可以是函數表達式,但不能是函數聲明,函數聲明只會識別else後面的函數。
  • 在把函數當成值來使用的情況下,都可以使用匿名函數。
  • 函數聲明和函數表達式的函數名的使用範圍:
    函數聲明:函數名在自身作用域和父作用域內是可獲取的
    函數表達式:函數名(如果有的話)在作用域外是不可獲取的
  • 優劣:一般情況下用函數表達式比較好,函數聲明只是在當前作用域定義了一個變量

函數聲明提升

解釋
- 函數聲明和函數變量通常會被 JavaScript 解釋器移(‘hoisted’)到當前作用域頂部
- 執行 JavaScript 過程中,有 Context(ECMA 5 將之分解爲 LexicalEnvironment詞彙環境、VariableEnvironment 變量環境和 ThisBinding綁定)和 Process(一系列按序調用的語句)兩個概念。當程序進入執行域時,Declaration 會造成 VariableEnvironment。它們不同於 Statement(比如 return),也不遵循 Statement 的運行規則。

Function Expression 會被提升嗎?

var bar = function() {
    return 3;
};

等號左邊的(var bar)是 Variable Declaration。Variable Declaration 會被提升,但是 Assignment Expression(賦值表達式)不會。所以當 bar 提升時,解釋器會這樣初始化:var bar = undefined。而函數定義本身不會被提升。

官方是禁止在非功能模塊(比如 if)用函數聲明

(二)遞歸調用(arguments.callee)

argument。callee是一個指向正在執行的函數的指針。這樣比直接調用函數名更保險(防止函數中途變化)

1、a中途被重新複製容易造成錯誤

function a(){
  if(num<= 1){
   return 1;
   }else{
   return num*a(num-1);
   }
}

2、

function a(num){
if(num<=1){
return 1;
}else{
return num*arguments.callee(num-1);
}
}

3、嚴格模式下,不能通過腳本訪問argument.callee(所以下面這種方式,在嚴格模式非嚴格模式下都適用)

var a = (function f(num){
if(num<=1){
return 1;
}else{
return num*f(num-1);
}
})

(三)作用域鏈

作用域鏈本質上是一個指向變量對象的指針列表,只引用但不實際包含變量對象


當執行一個函數的時候發生了什麼:

包含的東西:作用域鏈、執行環境、變量對象(活動對象)
全局環境的變量對象始終存在,而局部環境的變量對象,則只在函數執行的過程中存在。

  • 在創建一個新函數時:會創建一個預先包含全局變量對象的作用域鏈,這個作用域鏈被保存在內部[[scope]]屬性中
  • 當調用這個函數時:會創建一個執行環境,然後通過複製函數的[[scope]]屬性中的對象構建起執行環境的作用域鏈。作用域鏈第一位是當前活動對象,第二位是外層對象,最高位爲全局變量對象。
  • 後面創建活動對象:推入執行環境作用域鏈的前端。
  • 當函數執行完畢後,局部活動對象就會被銷燬。

(四)閉包

  • 在函數內部定義的函數會將外部函數的活動對象添加到它的作用域鏈中。外部函數執行完畢後,其活動對象也不會被銷燬,因爲內部函數的作用域鏈仍然在引用這個活動對象。但是外部環境的作用域鏈會被銷燬。(P180圖,學會理解
  • 普通的在外部函數內部返回內部函數的副作用
    (1)有多個內部函數同時引用外部函數變量對象中的某個變量,會使這幾個函數無法區分這個變量的值(一個for循環的例子

例子:js中循環綁定處理程序(一個知識點)

(2)在閉包中使用this對象
- 改進的閉包:將立即執行的匿名函數的結果賦值。。。

function(num){
return function(){
與num相關的操作
}
}(i)
  • 閉包的常用組合:this或者argument進行賦值傳遞

(五)this對象

1、this的幾種情況
- 在全局函數中,this等於window
- 當函數被作爲某個對象的方法調用時,this等於那個對象;
- 匿名函數的執行環境具有全局性,因此其this對象通常指向window。

2、每個函數在被調用時都會自動取得兩個特殊變量:this和arguments.內部函數在搜索這兩個變量時,只會搜索到其活動對象位置,因此永遠不可能直接訪問外部函數中的這兩個變量。

(六)內存泄漏

1、閉包導致的內存泄漏:

function assignHandler(){
   var element = document.getElementById('someElement');
   element.onclick = function(){
     alert(element.id);
   }
}//此閉包,內部函數引用了外部函數中的變量導致其無法釋放
  • 原因:因爲IE9之前版本的瀏覽器的垃圾回收機制——如果閉包的作用域鏈中保存着一個HTML勻速,那麼就意味着該元素將無法被銷燬
  • 解決
function assignHandler(){
  var element = document.getElementById('someElement');
  var id = element.id  //消除了循環引用,但不能完全解決內存泄漏問題,因爲閉包會引用外部函數的整個活動對象。
  element.onclick = function(){
    alert(id)
  }
  element = null;
}

。。。

(七)自執行匿名函數模仿塊級作用域

補充的基礎知識:

  • 在塊語句中定義的變量,實際上是在外部函數中而非語句中創建的;
  • javascript從來不會告訴你是否多次聲明瞭同一個變量;遇到這種情況,它只會對後續的聲明視而不見。(不過會執行後續聲明中的變量初始化)
  • 將匿名函數用括號括起來的原因:javascript將function關鍵字當作一個函數聲明的開始,而函數聲明後面不能跟圓括號;然而,函數表達式後面可以跟圓括號。

爲什麼需要局部作用域:

限制全局作用域中添加過多的變量和函數。如果一個項目由多人開發,過得全局變量和函數很容易導致命名衝突。

(八)私有變量及特權方法

1、私有變量:

任何在函數中定義的變量,都可以認爲是私有變量:函數的參數;局部變量;在函數內部定義的其他函數。

2、利用閉包創建用於訪問私有變量的公有方法(特權方法)

(1)在構造函數上創建對象的特權方法:針對每個實例都會創建同樣一組新方法(浪費內存)
  • 在構造函數中定義特權方法一;
function MyObject(){
   var privateVariable =10;
   function privateFunction(){
      return false;
   }
   //特權方法
   this.publicMethod = function(){
      privateVariable++;
      return privateFunction();
   }
}
  • 利用私有和特權成員,可以隱藏哪些不應該被直接修改的數據;
function Person(name){
   this.getName = function(){
      return name;
   }
   this.setName = function(){
      name = value;
   }
}
(2)在私有作用域中定義所有實例共享的特權方法(而其包含函數中的變量也就成了靜態私有變量)
(function(){
  var privateVariable = 10;
  function privateFunction(){
     return false;
  }
  myObject = function(){
  }//沒有使用函數聲明而是函數表達式,因爲函數聲明只能創建局部函數。同時也沒用var。因爲我們想要全局。但是,這在嚴格模式下給未聲明的變量賦值會導致錯誤。
  MyObject.prototype.publicMethod = function(){
     privateVariable++;
     return privateFunction();
  }
})()
(3)爲單例(只有一個實例的對象)創建私有變量和特權方法,使其得到增強

javascript是以對象字面量的方式來創建單例對象的。

var singleton = function(){
  var privateVariable = 10;
  function privateFunction(){
    return false;
  }
  return{
     publicProperty:true,
     publicMethod:function(){
        privateVariable++;
        return privateFunction();
     }
  }
}()
  • 用處:需要對單例進行某些初始化,同時又需要維護其私有變量時非常有用。如,用單例來管理應用程序級的信息。簡言之,如果必須創建一個對象並以些數據對其進行初始化,同時還要公開一些能夠訪問這些私有數據的方法,那麼就可以使用模塊模式。
  • 如果單例要求必須是某種類型的實例,同時還必須添加某些屬性和方法,則使用增強的模塊模式
var singleton = function(){
   var privateVariable = 10;
   function privateFunction(){
      return false;
   }
   var object = new CustomType();
   object.publicProperty = true;
   object.publicMethod = function(){
     privateVariable++;
     return privateFunction();
   }
   return object;
}()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章