標籤:style blog http io ar color 使用 sp java
本文要點:
1.實值型別和參考型別的區別
2.複製變數值
3.記憶體
4.傳遞參數
5.執行環境及範圍問題
1.基本類型值有:undefined,NUll,Boolean,Number和String,這些類型分別在記憶體中佔有固定的大小空間,他們的值儲存在棧空間,我們通過按值來訪問的。
(1)實值型別:數值、布爾值、null、undefined。
(2)參考型別:對象、數組、函數。
如果賦值的是參考型別的值,則必須在堆記憶體中為這個值分配空間。由於這種值的大小不固定(對象有很多屬性和方法),因此不能把他們儲存到棧記憶體中。但記憶體位址大小是固定的,因此可以將記憶體位址儲存在棧記憶體中。
兩者區別如下:
- 基本類型值在記憶體中佔據固定大小的空間,因此被儲存在棧記憶體中;
- 從一個變數向另一個變數複製基本類型的值,會建立這個值的一個副本; 參考型別的值是對象,儲存在堆記憶體中;
- 包含參考型別值的變數實際上包含的並不是對象本身,而是一個指向該對象的指標;
- 從一個變數向另一個變數複製參考型別的值,複製的其實是指標,因此兩個變數最終都指向同一個對象;
- 確定一個值是哪種基本類型可以使用typeof操作符,而確定一個值是哪種參考型別可以使用instanceof操作符。
- 所有變數(包括基本類型和參考型別)都存在於一個執行環境(也稱為範圍)當中,這個執行環境決定了變數的生命週期,以及哪一部分代碼可以訪問其中的變數。
基本類型和參考型別的值動態屬性
var person = new Object();person.name = "Nicholas";alert(person.name); // Nicholas
var name = "Nicholas";name.age = 27;alert(name.age); // undefined
這說明只能給參考型別值動態添加屬性。
簡而言之,堆記憶體存放引用值,棧記憶體存放固定類型值。
<script type="text/javascript">
var man = new Object();//man指向了棧記憶體的空間地址
man.name = "Jack";
var man2 = man;//man2獲得了man的指向地址
alert(man2.name);//兩個都彈出Jack
alert(man.name);
</script>
2.複製變數值
再看下面這個例子:
例一:
var man = new Object();//man指向了棧記憶體的空間地址
man.name = "Jack";
var man2 = man;//man2獲得了man的指向地址
man2.name = "ming";//因為他們都指向同一個object,同一個name,不管修改誰,大家都修改了
alert(man2.name);//兩個都彈出ming
alert(man.name);
例二:
var num1 = 5;var num2 = num1;
num1中儲存的值是5.當使用num1的值來初始化num2時,num2中也儲存了值5.但num2中的5與num1中的5是完全獨立的,該值只是num1中5的一個副本。
var obj1 = new Object();var obj2 = obj1;obj1.name = "Nicholas";alert(obj2.name); // Nicholas
變數obj1儲存了一個對象的新執行個體。然後,這個值被複製到了obj2中;換句話說,obj1和obj2都指向同一個對象。這樣,當為obj1添加name屬性後,可以通過obj2來訪問這個屬性。
由以上可以得出:在變數複製方面,基本類型和參考型別也有所不同,基本類型複製的是值本身,而參考型別複製的是地址。
3.記憶體問題
javascript具有自動記憶體回收機制,一旦資料不再使用,可以將其設為"null"來釋放引用
循環參考
一個很簡單的例子:一個DOM對象被一個Javascript對象引用,與此同時又引用同一個或其它的Javascript對象,這個DOM對象可能會引發記憶體泄露。這個DOM對象的引用將不會在指令碼停止的時候被記憶體回收行程回收。要想破壞循環參考,引用DOM元素的對象或DOM對象的引用需要被賦值為null。
閉包
在閉包中引入閉包外部的變數時,當閉包結束時此對象無法被記憶體回收(GC)。
var a = function() { var largeStr = new Array(1000000).join(‘x‘); return function() { return largeStr; }}();DOM泄露
當原有的COM被移除時,子結點引用沒有被移除則無法回收。
var select = document.querySelector;var treeRef = select(‘#tree‘);//在COM樹中leafRef是treeFre的一個子結點var leafRef = select(‘#leaf‘); var body = select(‘body‘);body.removeChild(treeRef);//#tree不能被回收入,因為treeRef還在//解決方案:treeRef = null;//tree還不能被回收,因為葉子結果leafRef還在leafRef = null;//現在#tree可以被釋放了。
Timers計(定)時器泄露
定時器也是常見產生記憶體泄露的地方:
for (var i = 0; i < 90000; i++) { var buggyObject = { callAgain: function() { var ref = this; var val = setTimeout(function() { ref.callAgain(); }, 90000); } } buggyObject.callAgain(); //雖然你想回收但是timer還在 buggyObject = null;}調試記憶體
Chrome內建的記憶體調試工具可以很方便地查看記憶體使用量情況和記憶體泄露:
在 Timeline -> Memory 點擊record即可:
4.傳遞參數
ECMAScript中,所有函數的參數都是按值傳遞的,
<script type="text/javascript"> function box(num){ //按值傳遞 num+=10; return num; } var num = 10; var result = box(num); alert(result); //如果是按引用傳遞,那麼函數裡的num會成為類似全域變數,把外面的number替換掉 alert(num); //也就是說,最後應該輸出20(這裡輸出10)</script>
再看一個例子:
function addTen(num){ num +=10; return num;}var count = 20;var result = addTen(count);alert(count); // 20, 沒有變化alert(result); //30
function setName(obj){ obj.name = "Nicholas";}var person = new Object();setName(person);alert(person.name); // "Nicholas"
function setName(obj){ obj.name = "Nicholas"; obj = new Object(); obj.name = "Greg";}var person = new Object();setName(person);alert(person.name); // "Nicholas"
如果person是按引用傳遞的,那麼person就會自動被修改為指向其name屬性值為"Greg"的新對象。
javascript沒有按引用傳遞的,如果存在引用傳遞的話,那麼函數內的變數將是全域變數,在外部也可以訪問。但這明顯是不可能的。
5.執行環境及範圍
執行環境是javascript中最為重要的概念之一,執行環境定義了變數或函數有權訪問其他資料。
全域執行環境是最外圍的執行環境,在web瀏覽器中,全域執行環境是window對象,因此,所有的全域變數的函數都是作為window的屬性和方法建立的。
<script type="text/javascript"> var name = "Jack"; //定義全域變數 function setName(){ return "trigkit4"; } alert(window.name); //全域變數,最外圍,屬於window屬性 alert(window.setName()); //全域函數,最外圍,屬於window方法</script>
當執行環境內的代碼執行完畢後,該環境被銷毀,儲存其中的變數和函數也隨之銷毀,如果是全域環境,需所有程式執行完畢或網頁完畢後才會銷毀。
去掉var的局部變數
<script type="text/javascript"> var name = "Jack"; function setName(){ name = "trigkit4"; //去掉var變成了全域變數 } setName(); alert(name);//彈出trigkit4</script>通過傳參,也是局部變數
<script type="text/javascript"> var name = "Jack"; function setName(name){ //通過傳參,也是局部變數 alert(name); } setName("?trigkit4");//彈出trigkit4 alert(name);//彈出Jack</script>
函數體內還包含函數,只有這個函數才可以訪問內一層的函數
<script type="text/javascript"> var name = "Jack"; function setName(){ function setYear(){ //setYear()方法的範圍在setName()內 return 21; } } alert(setYear());//無法訪問,出錯 </script>
可以通過如下方法進行訪問:
<script type="text/javascript"> var name = "Jack"; function setName(){ function setYear(){ //setYear()方法的範圍在setName()內 return 21; } return setYear(); } alert(setName()); //彈出21</script>
再一個範圍例子:
<script type="text/javascript"> var name = "Jack"; function setName(){ function setYear(){ //setYear()方法的範圍在setName()內 var b = "hi"; //變數b的範圍在setYear()內 return 21; } alert(b);//無法訪問 }</script>
當代碼在一個環境中執行的時候,就會形成一種叫做範圍鏈的東西,它的用途是保證對執行環境中有存取權限的變數和函數進行有序訪問(指按照規則層次來訪問),範圍鏈的前端,就是執行環境的變數對象。
範圍
變數沒有在函數內聲明或者聲明的時候沒有帶var就是全域變數,擁有全域範圍,window對象的所有屬性擁有全域範圍;在代碼任何地方都可以訪問,函數內部聲明並且以var修飾的變數就是局部變數,只能在函數體內使用,函數的參數雖然沒有使用var但仍然是局部變數。
沒有塊級範圍
沒有塊級範圍
// if語句:<script type="text/javascript">if(true){ //if語句的花括弧沒有範圍的功能。var box = "trigkit4";}alert(box);//彈出 trigkit4</script>
for迴圈語句也是如此。
變數的查詢
在變數的查詢中,訪問局部變數要比全域變數來得快,因此不需要向上搜尋範圍鏈。
如下例子:
<script type="text/javascript"> var name = "Jack"; function setName(){ var name = "trigkit4"; return name; //從底層向上搜尋變數 } alert(setName()); </script>
每個環境都可以向上搜尋範圍鏈,以查詢變數和函數名;但任何環境都不能通過向下搜尋範圍鏈而進入另一個執行環境。在這裡,如果去掉var name = "trigkit4",那麼將彈出“Jack”
閉包
在閉包中引入閉包外部的變數時,當閉包結束時此對象無法被記憶體回收(GC)。
var a = function() { var largeStr = new Array(1000000).join(‘x‘); return function() { return largeStr; }}();
詳解JS變數、範圍、記憶體