按照ECMA-262定義,JavaScript的變量鬆散類型的本質,決定了:前端
它還只是在特定時間用於保存特定值的一個名字而已。算法
變量的值及其數據類型能夠再腳本的生命週期內改變。segmentfault
基本類型瀏覽器
簡單的數據段(Undefined、Null、Bollean、Number和String);安全
基本類型變量按值進行訪問
,由於能夠操做保存在變量中的實際的值。函數
引用類型性能
可能由多個值構成的對象;優化
是保存在內存中的對象;ui
與其它語言不一樣,JavaScript不容許直接訪問內存中的位置,即不能直接操做對象所在的內存空間。實際操做時,是在操做對象的引用而不是實際的對象。這就是所謂的 引用類型的值是按引用訪問的
。(書中註釋提出:這種說法不嚴密,當複製保存這對象的某個變量時,操做是對對象的引用。但在爲對象添加屬性時,操做的是實際的對象。----圖靈社區"壯壯的前端之路"注)url
基本類型和引用類型變量定義方式相同:建立一個變量併爲該變量賦值。
而不一樣類型值可執行操做則截然不同
,例如只能給引用類型值動態地添加屬性:
引用值類型
var person = new Object(); person.name = "Nicholas"; alert(person.name); //"Nicholas" //若是對象不被銷燬或name屬性不被刪除,則這個屬性將永遠存在。
基本類型
var name = "Nicholas"; name.age = 27; alert(name.age); //undefined //爲基本類型變量添加屬性不會報錯,但沒法訪問。
基本類型
基本類型的變量存放在棧區
(內存中的棧內存)的。
從一個變量向另外一個變量複製時,會在變量對象上建立一個新值,而後把該值複製到爲新變量分配的位置上。兩個變量能夠參與任何操做而不相互影響。例如:
var num1 = 5; var num2 = num1; num1= 6; console.log(num2); //5
引用類型
引用類型的值同時保存在棧內存和堆內存中
。棧區保存變量標識符和指向對內存中該對象的指針。
從一個變量向另外一個變量複製時,會將存儲在變量對象中的只複製一份放到爲新變量分配的空間中。但,這個值其實是一個指針,這個指針指向存儲在堆中的一個新對象。複製結束後,兩個變量實際上將引用同一個對象。改變其中一個變量,就會影響另外一個,例如:
var obj1 = new Object(); var obj2 = obj1; obj1.name = "Nicholas"; alert(obj2.name); //"Nicholas"
ECMAScript中全部函數的參數
都是按值傳遞
的。即把函數外部的值賦值給函數內部的參數,等價於把值從一個變量複製到另外一個變量同樣。能夠把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"; obj = new Object(); obj.name = "Greg"; } var person = new Object(); setName(person); alert(person.name); //"Nicholas" //即便在函數內部修改了參數的值,希望是的引用仍然保持不變。 //實際上,當在函數內部重寫obj時,這個變量引用改的就是一個局部對象了。而這個局部對象會在函數執行完畢後當即被銷燬。
typeof 檢測基本數據類型
var s = "Nicholas"; var b = true; var i = 22; var u; var n = null; //注意:使用typeof操做符,null返回object var o = new Object; alert(typeof s); //string alert(typeof b); //bollean alert(typeof i); //number alert(typeof u); //undefined alert(typeof n); //object alert(typeof o); //object
instanceof 檢測引用值類型值是何種類型的對象
語法:result = variable instanceof constructor
若是變量是給定引用類型(根據它的原型鏈來識別,見第6章)的實例,instanceof返回true.
var person = new (); var color = ["blue","yellow","red"]; alert(person instanceof Object);//true alert(color instanceof Array); //true
執行環境 定義了變量或函數有權訪問的其餘數據,決定了他們各自的行爲。每一個執行環境都有一個與之關聯的變量對象(variable object), 環境中定義的全部變量和函數都保存在這個對象中。(這個對象咱們沒法訪問,解析器在處理數據時在後臺使用。)
全局執行環境 表示最外圍的執行環境。在Web瀏覽器中,全局環境被認爲是window對象(詳見第7章)。所以全部全局變量和函數都是做爲window對象的屬性和方法建立的。某個執行環境中的全部代碼執行完畢後,該環境被銷燬,保存在其中的全部變量和函數定義也隨之銷燬
(全局執行環境知道應用程序退出---例如關閉網頁或瀏覽器時纔會被銷燬)。
每一個函數都有本身的執行環境。當執行流
進入一個函數時,函數的環境會被推入一個環境棧中。而函數執行以後,棧將其彈出,把控制權返回給以前的執行環境。ECMAScript程序中的執行流正是由這個方便的機制控制着。
做用域鏈(scope chain)---代碼在一個環境中執行時,會建立變量對象的做用域鏈。用於保證對執行環境有權訪問的全部變量和函數的有序訪問
。做用域鏈的前端,始終都是當前執行的代碼所在環境的變量對象,若是是個函數,則將其活動對象(activation object)做爲變量對象。活動對象在最開始時只包含一個變量,即arguments對象(這個對象在全局環境中不存在)。做用域鏈中的下一個變量對象來自包含(外部)環境,而再下一個變量對象則來自下一個包含環境。這樣,一直延續到全局環境 ;全局執行環境的變量始終都是自做用域鏈中的最後一個對象
。
標識符解析 是沿着做用域鏈一級一級地搜索標識符的過程。搜索過程始終從更做用域鏈的前端開始,而後逐級地向後回溯,知道找到標識符位置(若找不到,一般會致使錯誤)。
例如:
var color = "blue"; function changeColor(){ if(color === "blue"){ color = "red"; }else{ color = "blue"; } } //changeColor()的做用域鏈包含兩個對象:他本身的變量對象(其中定義着arguments對象)和全局環境的變量對象。 //能夠在函數內部訪問變量color,就是由於能夠在這個做用域中找到它。 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 } //這裏只能訪問color changeColor();
內部環境能夠經過做用域鏈訪問全部的外部環境,但外部環境不能訪問內部環境中的任何變量和函數;
這些環境之間是線性的、由次序的;
每一個環境均可以向上搜索做用域鏈,以查詢變量和函數名,但任何環境都不能經過向下搜索做用域鏈而進入另外一個執行環境;
函數參數也被看成變量來對待,所以訪問規則與執行環境中的其餘變量相同。
下列兩個語句能夠再做用域鏈的前端臨時增長一個變量對象,該變量對象會在代碼執行後被移除。具體來講,就是當執行流進入下列任一語句時,做用域鏈就會獲得加長。
//try-catch語句的catch塊; //with語句;
例如:
function buildUrl(){ var qs = "?debug=true"; with(location){ var url = href + qs; } return url; }
with語句接受了一個location對象,所以其變量對象中就包含了location對象的全部屬性和方法,而這個變量對象被添加到了做用域鏈的前端。
buildUrl()函數中定義了了一個變量qs.當在with語句中引用變量href時(實際引用的是location.href),能夠再當前執行環境的變量對象中找到。當引用了變量qs時,引用改的則是在buiildUrl()中定義的那個變量,而該變量位於函數換進改的變量對象中。
至於with語句內部,則定義了一個名爲url的變量,於是url變量就成了函數執行環境的一部分,因此能夠做爲函數的值被返回。
使用var聲明的變量會自動被添加到最接近的環境中。在函數內部,最接近的環境就是函數的局部環境;在with語句中,最接近的環境是函數環境。
若是初始化變量時沒有使用var, 改變兩會自動被添加到全局變量。
例如:
function add(num1, num2){ var sum = num1 + num2; return sum; } var result = add(10, 20); //30 alert(sum); //報錯 //若省略第二行的var, 那麼add()執行完畢後,sum依然能夠被訪問到,alert(sum)返回30.
在嚴格模式下,初始化未聲明變量會致使錯誤。
查詢標識符過程:
當在某個環境中爲了讀取或寫入而引用一個標識符時,必須經過搜索來肯定該標識符實際表明什麼。搜索過程從做用域鏈的前端開始
,向上
逐級查詢
與給定名字匹配的標識符。若是在局部環境中找到了該標識符,搜索過程中止,變量就緒。若是在局部環境沒有找到該變量名,則繼續沿做用域鏈向上搜索。搜索過程一直追溯到全局環境的變量對象。
例如:
var color = "blue"; function getColor(){ return color; } alert(getColor()); //"blue"
調用getColor()時,用到了變量color.爲了肯定變量color的值,將開始搜索:
搜索getColor()的變量對象,查找其中是否包含一個名爲color的標識符,沒找到,進行第2步;
向上繼續搜索到了定義這個變量的變量對象(全局環境的變量對象),找到了名爲color的標識符。
注:在這個過程當中,
若是存在一個局部的變量的定義,則搜索自動中止
,再也不進入另外一個變量對象。即若是局部環境中存在着同名標識符,就不會使用位於父環境中的標識符。
例如:
var color = "blue"; function getColor(){ var color = "red"; return color; } alert(getColor()); //"red"
查詢變量是有代價的。很明顯,
訪問局部變量要比訪問全局變量更快
,由於不用向上搜索做用域鏈。JavaScript引擎在優化標識符查詢方面作得不錯,所以這個差異在未來恐怕就能夠忽略不計了
。
JavaScript具備自動
垃圾收集機制。(執行環境會負責管理代碼執行過程當中使用的內存)
函數中局部變量的正常生命週期:
局部變量只在函數執行的過程當中存在。而在這個過程當中,會爲局部變量在棧(或堆)內存上分配相應的空間,以便存儲他們的值;
而後在函數中使用這些變量,直至函數執行結束;
此時,即可以釋放局部變量的內存以供未來使用。
不一樣瀏覽器,一般有兩個策略:標記清除、引用計數。
JavaScript中最經常使用的垃圾收集方式是標記清除(mark-and-sweep). 當變量進入環境(例如,在函數中聲明一個變量)時,就將這個變量標記爲「進入環境」。從邏輯上說
,永遠不能釋放進入環境的變量所佔用的內存
,由於只要執行流進入相應的環境,就可能會用到他們。而當變量離開環境是,則將其標記爲「離開環境」 。
可使用任何方式來標記變量。好比,經過翻轉某個特殊的位來記錄一個變量什麼時候進入環境,或者使用一個「進入環境的」變量列表一個「離開環境的」變量列表來跟蹤那個變量發生了變化。採起什麼策略好比何標記更重要
。
垃圾收集器的內存清除工做:
垃圾收集器 在運行的時候會給存儲在內存中的全部變量都加上標記(固然,可使用任何標記方式);
它會去掉環境中的變量以及被環境中的變量引用的變量的標記;
而在此以後再被加上標記的變量將被視爲準備刪除的變量。由於環境中的變量已經沒法訪問到這些變量了;
最後,垃圾收集器完成內存清除工做,銷燬那些表明及的值並回收他們所佔用的內存空間。
注:到2008年爲止,IE、Firefox、Opera、Chrome和Safari的JavaScript實現使用的都是標記清除是的垃圾回收策略(或相似的策略),只不過垃圾收集的時間間隔互有不一樣。
(不太常見)跟蹤記錄每個值被引用的次數。
當生命了一個變量並將一個引用類型值賦給該變量是,則這個值的引用次數就是1. 在賦給另外一個變量,引用次數再+1. 相反,若是包含對這個值引用的變量又取得了另一個值,則這個值的引用次數-1. 當這個值的引用次數變成0時,說明沒辦法在訪問這個值,於是就能夠將其佔用的內存回收回來。
如此,當垃圾收集器再次運行時,他就會釋放哪些引用次數爲0的值所佔用的內存。
這種方式可能遇到循環引用的問題,例如Netscape Navigator 3.0是最先使用引用計數策略的瀏覽器:
function problem(){ var objectA = new Object(); var objectB = new Object(); objectA.someOtherObject = objectB; objectB.someOtherObject = objectA; } //當problem()函數執行完畢後,objectA和objectB引用次數爲2. 若函數反覆調用,便會致使大量內存的不到回收。 //Netscape在Navigator4.0以後便改用標記清除策略。
IE9+,把BOM和DOM對象都轉換成了真正的JavaScript對象。避免了兩種垃圾收集算法並存致使的問題,也消除了常見的內存泄露現象。
垃圾回收機制是週期性運行的,並且 若是爲變量分配的內存數量很可觀,那麼回收工做量也是至關大的。此時,肯定垃圾回收間隔時間很是重要。
在有的瀏覽器中能夠觸發垃圾收集機制,但不建議
這麼作。在IE中,調用window.CollectGarbge()方法會當即執行垃圾收集。在Opera7及更高版本中,調用window.opera.collect()也會啓動垃圾收集機制。
分配給Web瀏覽器的可用內存數量一般比分配給桌面應用程序的少。主要是出於安全方面的考慮,防止運行JavaScript的網頁耗盡所有系統內存致使系統崩潰。
內存限制問題不只會影響給變量分配內存,也會影響調用棧以及在一個線程中可以同時執行的語句數量。
優化內存佔用的最佳方式,就是爲執行中的戴嘛只保存必要的數據。一旦數據不在游泳,最好經過最好將其設置爲null來釋放其引用----- 解除引用 (dereferencing)。適用於大多數全局變量和全局對象的屬性。
例如:
function createPerson(name){ var localPerson = new Object(); localPerson.name = name; return localPerson; } var globalPerson = createPerson("Nicholas"); globalPerson = null;//手動解除globalPerson的引用
注:解除一個值的引用並不意味着自動回收該值所佔用的內存。真正做用是讓值脫離執行環境
,以便垃圾回收器下次運行時將其回收。