javascript:變數、作用域和記憶體問題
javascript
讀書筆記
變數、作用域和記憶體問題
1.x 變數型別
var a = 1;
var b = a; // 建立一個新的變數,和新的值
////////////////////
var p = new Object();
var q = p; // 建立一個新的變數,指向 p 指向的記憶體。
為什麼說對於引用型別,引數傳遞是按值傳遞。
var person = new Object();
function setname(obj) {
obj.name = '張飛';
obj = new Object();
obj.name = '王菲';
}
setname(person);
console.log(person.name); // 張飛
輸出結果是
張飛
。
(這一點上,和java
的表現形式相同)
class Person {
public String name;
}
public class Main {
public static void main(String[] args) {
Person p = new Person();
setName(p);
System.out.println(p.name); // 張飛
}
public static void setName(Person person) {
person.name = "張飛";
person = new Person();
person.name = "王菲";
}
}
輸出
張飛
-<>-
其實這個問題,涉及到兩個點:引數傳遞與變數作用域。
2.x 執行環境
var color = 'blue';
function changeColor() {
var anotherColor = "red";
function swapColor() {
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
// 這裡可以訪問color,anotherColor,tempColor
}
swapColor();
// 這裡可以訪問 color,anotherColor,但不能訪問 tempColor
}
// 這裡能訪問 color,但不能訪問 anotherColor 和 tempColor.
console.log(color);
內部環境可以通過作用域鏈(
scope chain
)訪問所有外部環境,但是外部環境不能訪問內部環境中的任何變數和函式。這些環境直接的聯絡是線性的,有序的。每個環境都可以向上搜尋作用域鏈,以查詢變數和函式名;但是任何環境都不能通關過向下搜尋作用域鏈而進入另一個執行環境。
對於這裡的swapColor()
而言,其作用域鏈中包含3個物件:swapColor()
的變數物件、changeColor()
的變數物件和全域性變數物件。swapColor()
的區域性環境開始時會先在自己的變數物件中搜尋變數和函式名,如果搜尋不到則再搜尋上一級的作用域鏈。changeColor()
的作用域鏈中只包含兩個物件:它自己的變數物件和全域性的變數物件。這也就是說,它不能訪問swapColor()
的環境。
2.1.x 延長作用域鏈
雖然說執行環境的型別總共只有兩種–全域性和區域性(函式),但是還是有其他辦法來延長作用域鏈。這麼說是因為有些語句可以在作用域鏈的前端臨時增加一個變數物件,該變數物件會再程式碼執行後被移除。
在兩種情況下會發生這種現象。具體來說,就是當執行流進入下列任何一個語句時,作用域鏈就會得到延長:
try-catch
語句的catch
塊;with
語句。
這兩個語句都會在作用域鏈的前端增加一個變數物件。對於with
語句來說,會將指定的的物件新增到作用域鏈中,對於catch
語句來說,會建立一個新的變數物件,其中包含的是被丟擲的錯誤物件的宣告。
function buildUrl(){
var qs="?debug=true";
with(location){
url = href + qs;
}
return url;
}
在此,with
語句接收的是location
物件,因此其變數物件中就包含了location
物件的所有屬性和方法,而這個變數物件被新增到了作用域鏈的前端。buildUrl()
函式中定義了一個變數qs
。當在with
語句中引用變數href
時(實際引用的是location.href
),可以在當前執行環境的變數物件中找到。當引用變數qs
時,引用的則是在buildUrl()
中定義的那個變數,而該變數位於函式環境的變數物件中。至於with
語句內容,則定義了一個名為url
的變數,因而url
就成了函式執行環境的一部分,所有可以作為函式的值被返回。
2.2.x 沒有塊級作用域
Javascript
沒有塊級作用域經常會導致理解上的困惑。在其他類C
的語言中,由花括號的程式碼塊都有自己的中作用域(如果用ECMAScript
的話來講,就是它們自己的執行環境),因而支援根據條件來定義變數。例如,下面的程式碼在Javascript
中並不好得到想象中的結果:
if (true){
var color="blue";
}
alert(color); // "blue"
這裡是在一個if
語句中定義了變數color
。如果是在C / C++ / Java
中color
會在if
語句執行完畢後銷燬。但在Javascript
中,if
語句中的變數宣告會將變數新增到當前的執行環境(在這裡是全域性環境)中。
- 宣告變數
使用var
宣告的變數會自動被新增到最近的環境中。在函式內容,最近的環境就是函式的區域性環境;在with
語句中,最接近的環境是函式環境。如果初始化變數時沒有使用var
宣告,該變數會自動被新增到全域性環境。(嚴苛模式下,不使用var
宣告而直接使用,會出現語法錯誤。)
2.3.x 管理記憶體
確保佔用最少的記憶體可以讓頁面獲得更好的效能。而優化記憶體佔用的最佳方式,就是為執行的程式碼只儲存必要的資料。一旦資料不再有用,最好通過將其值設定為null
來釋放其引用–這個做法叫做解除引用(dereferencing)。這一做法適用於大多數全域性變數和全域性物件的屬性。區域性變數會在它們離開執行環境時自動被解除引用,如下面這個例子所示:
function createPerson(name){
var person = new Object();
person.name = name;
return person;
}
var globalPerson = cretePerson('張飛');
// 不再使用該變數時,手動解除 globalPerson 的引用
globalPerson = null;
在這個例子中,變數 globalPerson
取得了createPerson()
函式的返回值。在createPerson()
函式內部,我們建立了一個物件並將其賦給區域性變數person
,然後又為該變數新增了一個名為name
的屬性。最後,當呼叫這個函式時,person
以函式值的形式返回被賦給全域性變數globalPerson
。由於person
在createPerson()
函式執行完畢後就離開了其執行環境,因此無需我們顯示地去為它解除引用。但是對於全域性變數globalPerson
而言,則需要我們在不使用它的時候手工為它解除引用,這也正是上面例子中最後一行程式碼的目的。
2.4.x 小結
Javascript
變數可以用來儲存兩種型別的值:基本型別值和引用型別值。基本型別的值源自5
種基本資料型別:Undefined,Null,Boolean,Number,String
。基本型別值和引用型別值具有以下特點:
- 基本型別值在記憶體中佔據固定大小的空間,因此被儲存在棧記憶體中;
- 從一個變數向另一個變數複製基本型別的值,會建立這個值的一個副本;
- 引用型別的值是物件,儲存在堆記憶體中;
- 包含引用型別的值實際上包含的並不是物件本身,而是一個指向該物件的指標;
- 從一個變數向另一個變數複製引用型別的值,複製的其實是指標,因此兩個變數最終都指向同一個物件;
- 確認一個值是哪種基本型別可以使用
typeof
操作符,而確定一個值是哪種引用型別可以使用instanceof
操作符。
所有變數(包括基本型別和引用型別)都存在於一個執行環境(也稱為作用域)當中,這個執行環境決定了變數的生命週期,以及哪一部分程式碼可以訪問其中的變數。以下是關於執行環境的幾點總結:
- 執行環境有全域性執行環境(也稱為全域性環境)和函式執行環境之分;
- 每次進入一個新的執行環境,都會建立一個用於搜尋變數和函式的作用域鏈;
- 函式的區域性環境不僅有權訪問函式的作用域變數,而且有權訪問其包含(父)環境,乃至全域性環境;
- 全域性環境只能訪問全域性環境中定義的變數和函式,而不能直接訪問區域性環境中的任何資料;
- 變數的執行環境有助於確定應該何時釋放記憶體。
Javascript
的垃圾收集例程總結:
- 離開作用域的值將被自動標記為可以回收,因此將在垃圾收集期間被刪除。
- “標記清除”是目前主流的垃圾收集演算法,這種演算法是給當前不使用的值加上標記,然後再回收其記憶體。
- 另一種垃圾收集演算法是“引用計數”,這種演算法的思想是跟蹤記錄所有值被引用的次數。
Javascript
引擎目前都不再使用這種演算法;但是在IE
中訪問非原生Javascript
物件(如DOM
元素,BOM
元素)時,這種演算法仍然可能會導致問題。 - 當程式碼中存在迴圈引用的現象時,“引用計數”演算法就會導致問題。
- 解除變數的引用不僅有助於消除迴圈引用現象,而且對垃圾回收也有好處,為了確保有效地回收記憶體,應該及時解除不再使用的全域性物件、全域性物件屬性以及迴圈引用變數的引用。
相關文章
- 第4章 變數、作用域和記憶體問題變數記憶體
- 一起學習JavaScript (4) 之變數、作用域和記憶體問題JavaScript變數記憶體
- 變數、作用域與記憶體變數記憶體
- JavaScript高階程式設計筆記 - 第四章 變數 作用域 記憶體問題JavaScript程式設計筆記變數記憶體
- JavaScript變數,資料和記憶體的相關問題JavaScript變數記憶體
- JavaScript中變數和作用域JavaScript變數
- Shell變數的作用域問題變數
- javascript中的作用域(全域性變數和區域性變數)JavaScript變數
- JavaScript之變數及作用域JavaScript變數
- JavaScript 變數的作用域鏈JavaScript變數
- golang變數作用域問題-避免使用全域性變數Golang變數
- Java(6)-Java記憶體區域和作用Java記憶體
- 現代 JavaScript 的變數作用域JavaScript變數
- 好程式設計師JavaScript教程分享JavaScript中變數和作用域程式設計師JavaScript變數
- 變數作用域變數
- 新版紅寶書第四章變數、作用域與記憶體(學習筆記)變數記憶體筆記
- 軟體測試學習中JavaScript中變數和作用域式是什麼?JavaScript變數
- golang 快速入門 [8.1]-變數型別、宣告賦值、作用域宣告週期與變數記憶體分配Golang變數型別賦值記憶體
- Python 函式和變數作用域Python函式變數
- Java記憶體區域和記憶體模型Java記憶體模型
- Go 程式碼塊與作用域,變數遮蔽問題詳解Go變數
- JS變數作用域JS變數
- SCSS 變數作用域CSS變數
- python變數與變數作用域Python變數
- 深入理解JavaScript作用域和作用域鏈JavaScript
- GO語言變數作用域-坑記錄Go變數
- js作用域(變數提升,預解析)例題JS變數
- JavaScript全域性作用域下,變數加var和不加var的區別。JavaScript變數
- C# 變數作用域C#變數
- CSS變數的作用域和預設值CSS變數
- C++區域性變數的記憶體訪問:小心技巧與安全邊界C++變數記憶體
- JavaScript之記憶體溢位和記憶體洩漏JavaScript記憶體溢位
- 記憶體和棧溢位問題定位記憶體
- 小程式白屏問題和記憶體研究記憶體
- Python 3 學習筆記之——變數作用域、模組和包Python筆記變數
- JS 底蘊之 變數、作用域和垃圾回收JS變數
- 11-程式碼塊和變數的作用域變數
- JavaScript 作用域 與 作用域鏈JavaScript