[每天學3章,一周掌握js]第1章:變量、作用域和內存問題

第一節:變量——基本類型和引用類型

【基本數據類型】:保存在棧中的數據段,如undefined、Null、boolean、Number、String 

按值訪問,直接操作位於【棧內存】中的值

【引用數據類型】:保存於堆內存中的對象,大小不固定,棧內存保存的隻是訪問地址,如Object/Date/Array/RegExp/Function。

按引用訪問,通過存儲的地址,訪問位於【堆內存】中的對象

棧存儲值和存儲地址的區別

1.動態屬性

可以給引用類型動態的添加屬性,以便以後使用,而基本類型則無法添加。

例如:

1
var person=new Object();
2
person.name="jim";
3
alert(person.name);
2.復制變量值
復制基本數據類型,實際是復制的棧內存中的數據段,復制後的操作,對原變量不產生影響。而復制引用類型,則是復制的棧內存中的地址,對此引用類型的操作,會在原類型中反映出來,例如:

1
var p1=new Object();
2
var p2=p1;
3
p1.name="jim";
4
alert(p2.name);// 同樣會輸出 jim
3.傳遞參數

ECMAScript 中所有函數的參數都是按值傳遞的。

向參數傳遞基本類型的值時,被傳遞的值會被復制給一個局部變量,即命名參數,就是arguments對象中的一個元素。

向參數傳遞引用類型的值時,會把棧內存的地址復制給一個局部變量,因此這個局部變量的變化會引起原引用類型的變化,進而反映在函數外部。

示例:

1
function showname(obj){
2
   obj.name="pp";
3
}
4
var person=new Object();
5
showname(person);
6
alert(person.name);// pp
 糾錯:如果局部作用域中修改的變量會在全局作用域中反映出來,就說明參數是按引用傳遞的。(x)

示例:

1
function showname(obj){
2
   obj.name="pp";
3
   var obj=new Object();
4
   obj.name="pp2"
5
}
6
var person=new Object();
7
showname(person);
8
alert(person.name);// 依然輸出pp
在這裡,如果參數是按引用傳遞的話,那麼person傳遞給obj後,obj得到的就是指向person的指針,obj重新定義Object並將name改為pp2後,則person也會相應的改變。如果是引用傳遞,重定義obj相當於通過指針重定義person,實際person.name輸出的還是pp,說明person並沒有被改變,那麼就證明,參數不是按引用傳遞的!
這裡person傳遞給obj的實際是person保存在棧內存中指向堆內存中對象的地址,即person和obj兩個對象指向同一個堆內存中的對象,然後,obj重定義後,obj不再指向原對象,而是指向新創建的局部對象,按照js的運行機制,該對象會在函數執行完後立即刪除,函數外部無法再次訪問。

4.檢測變量類型

typeof 操作符是檢測基本類型變量的得力助手。檢測引用類型的話,就需要使用instanceof操作符。

instanceof會檢測出引用類型變量是具體的哪個類型的對象。例如:

1
alert(person instanceof Object ) //檢測變量person是否是Object類型
2
alert(colors instanceof Array ) //檢測變量colors是否是Array類型
3
//………..
 註意:根據規定,所有引用類型的值,都是Objcect的實例,因此,檢測一個引用類型的值和Object的構造函數時,instanceof都會返回true。
 BUG:使用typeof檢測函數時,該操作符會返回"function"。在safari和chrome瀏覽器中使用typeof檢測正則表達式時,會錯誤di返回"function".

第二節:執行環境及作用域

1.相關定義說明

變量對象:每個執行環境都有一個與之對應的變量對象,環境中定義的所有變量和函數都保存在這個對象中,這個對象我們無法訪問,但是解析器會在後臺調用。

註意:web中的全局執行環境是【window對象】,因此所有全局變量和函數都是作為window對象的屬性和方法創造的。

環境的銷毀:執行環境中的代碼全部執行完畢之後,該環境也會被銷毀,保存在其中的所有變量和函數定義也會隨之銷毀。全局環境直到程序退出(例如關閉瀏覽器或網頁時)才會銷毀。

環境執行機制:每個函數都有自己的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中,而在函數執行之後,棧將其環境彈出,把控制權返回給之前的執行環境。

作用域鏈及作用:當代碼在一個環境中執行的時候,會創建變量對象的【作用域鏈】。作用域鏈的作用就是保證環境有權訪問的所有變量和函數的有序訪問。

2.延長作用域鏈:

1
以下語句會延長作用域鏈:
2
 
3
try-catch語句中的catch塊;
4
 
5
with語句;
說明:

正常情況下,作用域鏈的前端當前代碼所在環境的的變量對象。

但是這兩個語句,會在變量對象的前面臨時添加一個新的變量對象。該臨時對象會在執行完後被刪除。
鏈接:深入理解js函數作用域和作用域鏈 

http://my.oschina.net/ws2042/blog/65257

3. 註意——js沒有塊級作用域

js不像其他語言一樣,用{}標明塊級作用域,js沒有塊級作用域,例如:

1
if(true){
2
var color="red";
3
}
4
alert(color);// red
1
for(var i;i<10;i++){
2
   //….
3
}
4
alert(i);  //10
以上兩個例子,如果是在其他有塊級作用域的語言中是錯誤的。
1.聲明變量的作用:

通過var 字符聲明變量後,變量會被自動添加到最近的作用域中,如果沒有var聲明的話,那麼默認添加到全局作用域中。

例如:

1
function test(){
2
   var a=10;
3
   var b=2;
4
   c=a+b;
5
}
6
test();
7
alert(a); // error
8
alert(b); // error
9
alert(c); // 12
2.查詢標識符:

當在某個環境中為瞭讀取或寫入引入一個標識符時,要通過搜索來確定該標識符代表什麼。搜索過程從作用域的前端開始,向上逐級查詢與給定名字匹配的標識符。如果在局部環境中找到則停止,變量就緒。如果局部環境沒有,則繼續沿作用域鏈向上搜索。

之前也說過,每個環境作用域的前端一般都是該作用域所有變量和函數的變量對象,js解析器會對其進行訪問。現在向上搜索時就是搜索該變量對象。更具體的解釋查看上述章節中的引用鏈接。

因此從讀取速度方面考慮,局部變量,要比全局變量讀取的更快些。

 

第3節:內存問題——垃圾收集

js相對其他語言來說,在垃圾收集,內存釋放方面有其很便捷的一方面,無需手工的跟蹤內存進行釋放,這些js解析器會自動釋放內存。

1.垃圾收集策略:

標記清除
引用計數
簡單羅列下,不做詳細說明。

2.性能問題

本節主要說下因為瀏覽器垃圾回收策略的不同引起的性能問題:

首先還是臭名昭著的IE6:

IE6中,垃圾回收是根據內存分配量運行的,當有256個變量、4096個對象(或數組)字面量和數組元素 或者64k的字符串中任何一個滿足時,垃圾收集器就會運行。

那麼這樣也產生瞭一個問題,存在於ie6中的問題,如果一個js中包含有上述的那麼多變量,那麼該腳本很可能在生命周期中一直保有那麼多的變量,這樣一來,垃圾收集器就會頻繁的運行,從而產生嚴重的性能問題。

IE7中的收集策略已經對此做出瞭修復。ie7中的各項臨界值與ie6是相等的,不同的是,當垃圾回收的內存量低於總量的15%,這個臨界值就會翻倍,如果回收內存量到85%,則會hui'fu默認臨界值。

引用計數策略引發的性能問題:

采用引用計數策略作為垃圾回收機制的瀏覽器,當js腳本中出現循環引用的情況時,對於循環引用的變量其引用數永遠不會為0,那麼這兩個變量也就不會被回收,從而造成內存浪費。

例如:

1
function family(){
2
   var a=new Object();
3
   var b=new Object();
4
   a.brother=b;
5
   b.sister=a;
6
}
7
//此例子中a和b形成瞭循環引用,對於引用次數策略的垃圾回收器,不會將其回收,即便該局部作用域已經銷毀。a、b將仍占有內存。
同樣悲劇的是:
IE中的訪問非原生的javascript對象時,也會出現這樣的問題,因為IE中非原生js對象,例如BOM 和DOM中的對象就是使用c++以COM(組件對象模型)對象的形式實現的,而COM中的垃圾回收機制就是引用計數。

解決循環引用引起的內存問題,最好的辦法是在不使用相互引用的變量時,將他們手動的斷開引用。

例如:

1
var element=document.getElementBtId("some_element");
2
var myObject=new Object();
3
myObject.element=element;
4
element.someObject=myObject;
5
 
6
//這樣斷開引用
7
 
8
myObject.element=null;
9
element.someObject=null;
3.管理內存

確保在不影響js功能的同時,保證js占用最少的內存,可以提升web性能。

優化內存占用的最佳方法就是執行中的代碼隻保存必要的數據。一旦數據不再有用,最好通過將其值設置為null來釋放其引用——這個方法叫做:解除引用。

這個方法適用於大多數的全局變量和全局對象屬性,局部變量會在他們離開執行環境是自動解除引用。

 
01
function test(a){
02
   var c=new Object();
03
   c.name=a;
04
   return c;
05
}
06
 
07
var b=test(2);
08
 
09
//對b的使用結束後,通過設置null,解除其引用;這樣b就會脫離運行環境,從而下次回收時被回收。
10
 
11
var b=null;
 
作者:自由的靈魂

You May Also Like