一、函數聲明與函數表達式
1、函數聲明之后,可以在聲明之前調用,也可以在聲明之后調用,因為所有的函數聲明(包括var聲明的變量)都會在代碼執行之前就加載到作用域中,
函數名其實是一個Function類型的對象的引用,聲明函數時函數名其實也就被賦值了;
2、而函數表達式則不同,函數表達式是將函數賦值給一個變量,只有當代碼執行到那一行的時候,函數才真正的有定義,因此這個變量只有在表達式之后才能使用,
否則這個變量為undefined,如果這個變量不是通過var關鍵字聲明,那么它就沒有任何值。
例1:
fn1(); //fn1 fn2(); //fn2 is not a function console.log(typeof fn2); //undefined function fn1(){ console.log("fn1"); } var fn2 = function(){ console.log("fn2"); } fn2(); //fn2
例2:
var fn = 10; function fn(){ console.log("test"); } console.log(fn); //10 fn(); //fn is not a function
在例2的代碼中,變量聲明會在代碼執行之前就加載到作用域中,函數聲明時,函數名就是一個Function類型的對象引用,此時它已經被賦值了,
同一個變量被多次聲明時,只會忽略后面的聲明,但會執行后面的變量初始化;此時變量fn的值已經被覆蓋了,值為10。
例2中的代碼可以理解為下面的代碼:
var fn; fn = function() { console.log("test"); } fn = 10; console.log(fn); //10 fn(); //fn is not a function
二、函數執行環境、變量對象與作用域鏈
1、后台中每個函數的執行環境都有一個變量對象,全局環境中的變量對象(瀏覽器中的window對象)始終存在,當函數被調用時,其活動對象等同於變量對象,
函數中的局部執行環境中的活動對象只存在函數執行過程中,函數執行完后,其活動對象就會被撤銷;
2、當一個函數第一次被執行時,會創建一個執行環境(execution context)和相應的作用域鏈(包含了所有外部函數的活動對象及全局變量對象),
並把作用域鏈賦值給函數的一個特殊的內部[[Scope]]屬性中,然后用this,arguments和其他參數初始化函數的活動對象(activation object),函數在執行時,
會通過復制函數的[[Scope]]屬性中的對象構建起執行環境的作用域鏈,注意函數本身也有一個作用域,作為執行環境作用域鏈的前端。
3、理解函數執行環境、變量對象與作用域鏈,是理解閉包的關鍵所在。
4、關於this,arguments對象需要注意的地方:
當一個函數第一次被執行時,就會用this,arguments對象等其他參數初始化函數的活動對象,但是如果是閉包(即內部函數)在搜索這兩個對象時,
只會搜索到其活動對象為止,而不會搜索其他函數作用域中的活動對象,也就是說內部函數無法訪問其包含函數的this,arguments對象,
但是可以通過變量的形式來訪問外部函數中的this,arguments對象,由於閉包的執行環境具有全局性,因此其this指向window對象。
三、閉包及其所存在的問題
閉包:是指有權訪問外部函數作用域中的變量的函數,創建一個閉包的方式就是在一個函數中創建另一個函數
例3:
function fn(name){ return function(){ return name } } var nm = fn("yjh"); console.log(nm()); //yjh
在例3中,fn中有一個匿名函數,即閉包,在上面代碼的執行中,結果為yjh,因為閉包可以訪問外部函數作用域中的變量,
內部函數中的變量是從作用域鏈的前端開始搜索的,如果沒有找到,直至搜索到作用域鏈的末端(全局執行環境)。
閉包所存在的問題:
就拿例3中的例子來說,在函數fn執行完畢后,其變量name(包括其活動對象)並沒有被銷毀,因為內部函數(閉包)的作用域鏈包含了fn中的作用域,
內部函數仍然在引用fn中的活動對象,當fn執行完后,其作用域鏈會被銷毀,但是它的活動對象會繼續保存在內存中,直到匿名函數被銷毀,它的活動對象才會被銷毀;
由於閉包會攜帶包含它的函數的作用域,因此會比其他函數占用更多的內存,過多的使用閉包會導致內存占用過多。
四、塊級作用域
JavaScript中沒有塊級作用域,不像其他編程語言中擁有塊級作用域的概念,但是我們可以在js中模擬塊級作用域的概念,我們都知道,
一般而言,函數執行完畢后,執行環境中的活動對象就會被撤毀,因此我們可以像下面這樣:
(function(){ })()
聲明一個匿名函數,然后立即調用它,這樣在這個匿名函數中聲明的變量在函數執行完畢就會被撤毀,外部函數也不能訪問其中定義的變量
函數聲明和調用的幾種寫法:
function(){} //缺少(或意外)標識符(ie9以下的ie瀏覽器不會報錯) function fn(){} //常規函數聲明 var fn = function(){} //賦值表達式 (fn = function(){})() //函數被立即調用 var fn = function(){}() //函數被立即調用 (function(){})() //函數被立即調用
總結:
1、變量,函數聲明都會提前加載到作用域中,函數賦值表達式只有當代碼執行到那一行時才會有定義
2、每一個函數都有一個執行環境(作用域),變量對象(活動對象),以及相應的作用域鏈(各個活動對象的指針列表),當函數執行時,作用域鏈會賦值給函數
的內部特殊屬性[[Scope]]以構建起函數執行環境的作用域鏈
3、閉包就是指有權訪問外部作用域的變量的函數,創建閉包的方式就是在一個函數中創建另一個函數,閉包會包含外部函數的作用域,因此占用的內存會比其他函數多,
過多的使用閉包會使內存占用過多,還會導致外部函數的活動對象不會被釋放,只有閉包的引用被撤銷,外部函數作用域中的活動對象才會被釋放
4、閉包(內部函數)在搜索this,arguments對象時,只會在自身的活動對象中搜索,永遠也不能訪問外部作用域中的this,arguments對象,但可以通過變量賦值的
形式來訪問外部函數作用域中的this,arguments對象,由於閉包的執行環境具有全局性,因此其this對象指向window對象,全局環境中沒有arguments對象的
5、在js中還可以模擬其他語言中的塊級作用域,即聲明一個匿名函數,然后立即調用它