JavaScript進階之理解篇


一、函數聲明與函數表達式


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中還可以模擬其他語言中的塊級作用域,即聲明一個匿名函數,然后立即調用它

 
       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM