《Javascript 高階程式設計(第三版)》筆記0x5 變數|作用域|記憶體
目錄
基本型別和引用型別的值
基本型別值指的是簡單的資料段,而引用型別值指那些可能由多個值構成的物件。引用型別的值是儲存在記憶體中的物件。與其他語言不同, JavaScript 不允許直接訪問記憶體中的位置,也就是說不能直接操作物件的記憶體空間。在操作物件時,實際上是在操作物件的引用而不是實際的物件。為此,引用型別的值是按引用訪問的。(注:為物件新增屬性時,操作的是實際的物件。)
動態的屬性
//引用型別
var person = new Object();
person.name = "Nicholas";
alert(person.name); //"Nicholas"
//基本型別
var name = "Nicholas";
name.age = 27;
alert(name.age); //undefined
複製變數值
//基本型別:複製副本,互不影響 var num1 = 5; var num2 = num1; //引用型別:指標引用,相互影響 var obj1 = new Object(); var obj2 = obj1; obj1.name = "Nicholas"; alert(obj2.name); //"Nicholas"
傳遞引數
ECMAScript 中所有函式的引數都是按值傳遞的。也就是說,把函式外部的值複製給函式內部的引數,就和把值從一個變數複製到另一個變數一樣。基本型別值的傳遞如同基本型別變數的複製一樣,而引用型別值的傳遞,則如同引用型別變數的複製一樣。
//基本型別
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"
//當在函式內部重寫 obj 時,這個變數引用的就是一個區域性物件了。而這個區域性物件會在函式執行完畢後立即被銷燬。
可以把 ECMAScript 函式的引數想象成區域性變數。
檢測型別
var s = "Nicholas";
var b = true;
var i = 22;
var u;
var n = null;
var o = new Object();
alert(typeof s); //string
alert(typeof i); //number
alert(typeof b); //boolean
alert(typeof u); //undefined
alert(typeof n); //object
alert(typeof o); //object
//檢測引用型別result = variable instanceof constructor
alert(person instanceof Object); // 變數 person 是 Object 嗎?
alert(colors instanceof Array); // 變數 colors 是 Array 嗎?
alert(pattern instanceof RegExp); // 變數 pattern 是 RegExp 嗎?
執行環境及作用域
execution context。定義了變數或函式有權訪問的其他資料,決定了它們各自的行為。每個執行環境都有一個與之關聯的變數物件(variable object),環境中定義的所有變數和函式都儲存在這個物件中。
全域性執行環境:
根據 ECMAScript 實現所在的宿主環境不同,表示執行環境的物件也不一樣。在 Web 瀏覽器中,全域性執行環境被認為是 window 物件,因此所有全域性變數和函式都是作為 window 物件的屬性和方法建立的。某個執行環境中的所有程式碼執行完畢後,該環境被銷燬,儲存在其中的所有變數和函式定義也隨之銷燬(全域性執行環境直到應用程式退出——例如關閉網頁或瀏覽器——時才會被銷燬)。
函式執行環境:
當執行流進入一個函式時,函式的環境就會被推入一個環境棧中。而在函式執行之後,棧將其環境彈出,把控制權返回給之前的執行環境。 ECMAScript 程式中的執行流正是由這個方便的機制控制著。
作用域鏈(scope chain):
保證對執行環境有權訪問的所有變數和函式的有序訪問。作用域鏈的前端,始終都是當前執行的程式碼所在環境的變數物件。如果這個環境是函式,則將其活動物件(activation object)作為變數物件。活動物件在最開始時只包含一個變數,即 arguments 物件(這個物件在全域性環境中是不存在的)。作用域鏈中的下一個變數物件來自包含(外部)環境,而再下一個變數物件則來自下一個包含環境。這樣,一直延續到全域性執行環境;全域性執行環境的變數物件始終都是作用域鏈中的最後一個物件。
var color = "blue";
function changeColor(){
if (color === "blue"){
color = "red";
} else {
color = "blue";
}
}
changeColor();
alert("Color is now " + color);
區域性作用域中定義的變數可以在區域性環境中與全域性變數互換使用
var color = "blue";
function changeColor(){
var anotherColor = "red";
function swapColors(){
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
// 這裡可以訪問 color、 anotherColor 和 tempColor
}
// 這裡可以訪問 color 和 anotherColor,但不能訪問 tempColor
swapColors();
}
// 這裡只能訪問 color
changeColor();
延長作用域鏈
可以在作用域鏈的前端臨時增加一個變數物件,該變數物件會在程式碼執行後被移除。
try-catch 語句的 catch 塊;
with 語句
function buildUrl() {
var qs = "?debug=true";
with(location){
var url = href + qs; //location.href
}
return url;
}
沒有塊級作用域
/*
* 在一個 if 語句中定義了變數 color。如果是在 C、 C++或 Java 中, color 會在 if 語句執
* 行完畢後被銷燬。但在 JavaScript 中, if 語句中的變數宣告會將變數新增到當前的執行環境(在這裡是
* 全域性環境)中。
*/
if (true) {
var color = "blue";
}
alert(color); //"blue"
/*
* 對於 JavaScript 來說,由 for 語句建立的變數 i 即使在 for 迴圈執行結束後,也依舊會存在
* 於迴圈外部的執行環境中。
*/
for (var i=0; i < 10; i++){
doSomething(i);
}
alert(i); //10
宣告變數
使用 var 宣告的變數會自動被新增到最接近的環境中。在函式內部,最接近的環境就是函式的區域性環境;在 with 語句中,最接近的環境是函式環境。如果初始化變數時沒有使用 var 宣告,該變數會自動被新增到全域性環境。
function add(num1, num2) {
var sum = num1 + num2;
return sum;
}
var result = add(10, 20); //30
alert(sum); //由於 sum 不是有效的變數,因此會導致錯誤
function add(num1, num2) {
sum = num1 + num2;
return sum;
}
var result = add(10, 20); //30
alert(sum); //30
查詢識別符號
搜尋過程從作用域鏈的前端開始,向上逐級查詢與給定名字匹配的識別符號。如果在區域性環境中找到了該識別符號,搜尋過程停止,變數就緒。如果在區域性環境中沒有找到該變數名,則繼續沿作用域鏈向上搜尋。搜尋過程將一直追溯到全域性環境的變數物件。如果在全域性環境中也沒有找到這個識別符號,則意味著該變數尚未宣告。
var color = "blue";
function getColor(){
return color;
}
alert(getColor()); //"blue"
var color = "blue";
function getColor(){
var color = "red";
return color;
}
alert(getColor()); //"red"
垃圾收集
JavaScript 具有自動垃圾收集機制,也就是說,執行環境會負責管理程式碼執行過程中使用的記憶體。(週期性地執行)
標記清除
垃圾收集器在執行的時候會給儲存在記憶體中的所有變數都加上標記(當然,可以使用任何標記方式)。然後,它會去掉環境中的變數以及被環境中的變數引用的變數的標記。而在此之後再被加上標記的變數將被視為準備刪除的變數,原因是環境中的變數已經無法訪問到這些變量了。最後,垃圾收集器完成記憶體清除工作,銷燬那些帶標記的值並回收它們所佔用的記憶體空間。
引用計數
跟蹤每個值被引用次數計數,當計數為零時回收。注意:迴圈引用會導致無法回收。
效能問題
觸發垃圾收集的變數分配、字面量和(或)陣列元素的臨界值被調整為動態修正。
管理記憶體
出於安全考慮,分配給 Web瀏覽器的可用記憶體數量通常要比分配給桌面應用程式的少。因此,確保佔用最少的記憶體可以讓頁面獲得更好的效能。而優化記憶體佔用的最佳方式,就是為執行中的程式碼只儲存必要的資料。一旦資料不再有用,最好通過將其值設定為 null 來釋放其引用——這個做法叫做解除引用(dereferencing)。這一做法適用於大多數全域性變數和全域性物件的屬性。區域性變數會在它們離開執行環境時自動被解除引用。
function createPerson(name){
var localPerson = new Object();
localPerson.name = name;
return localPerson;
}
var globalPerson = createPerson("Nicholas");
// 手工解除 globalPerson 的引用
globalPerson = null;
解除引用的真正作用是讓值脫離執行環境,以便垃圾收集器下次執行時將其回收。