ECMAScript變量包含兩種不一樣數據類型的值:基本類型值和引用類型值。基本類型值是簡單的數據段,而引用類型值指那些可能由多個值構成的對象。javascript
在將一個值賦給變量時,解析器必須肯定這個值是基本類型仍是引用類型。基本類型包括如Undefined、Null、Boolean、Number和String,這5種基本類型數據類型是按值訪問的,所以能夠操做保存在變量中的實際的值;引用類型類型的值是保存在內存中的對象。前端
與其餘語言不一樣,JavaScript不容許直接訪問內存中的位置,也就是說不能直接操做對象的內存空間。在操做對象時,實際上實在操做對象的引用而不是實際的對象,所以,引用類型的值是按引用訪問的。java
1.1 動態的屬性
定義基本類型和定義引用類型的方法是類似的。可是對不一樣類型值的操做倒是大相近庭。對於引用類型的值,咱們能夠爲其添加屬性和方法,也能夠改變和刪除其屬性和方法,以下:瀏覽器
var person = new Object(); person.name = "張三"; alert(person.name); //"張三"
但不能給基本類型的值添加屬性。以下:函數
若是從一個變量向另外一個變量複製基本類型的值,會在變量對象上建立一個新值,而後將該值複製到爲新變量分配的位置上。工具
var num1 = 5; var num2 = num1; //5
num1與num2中的5是徹底獨立的,該值只是num1中5的一個副本。此後,這兩個變量能夠參與任何操做而不會相互影響。以下圖所示:spa
而當一個變量向另外一個變量複製引用類型的值時,一樣也會將存儲在變量對象中的值複製一份放到爲新變量分配的內存空間裏。不一樣的是,這個值其實是一個指針,而這個指針指向存儲在堆中的一個對象。複製結束後,兩個變量實際上將引用同一個對象。所以,改變其中一個變量,就會影響到另外一個變量,以下所示:設計
var obj1 = new Object(); var obj2 = obj1; obj1.name = "張三"; alert(obj2.name); //"張三"
1.3 傳遞參數指針
ESMAScript中全部函數的參數都是按值傳遞的。也就是說,把函數外部的值複製給函數內部的參數,就是把值從一個變量複製到另外一個變量同樣。基本類型值的傳遞如同基本類型變量的複製同樣,而引用類型值的傳遞,則如同引用類型變量的複製同樣。有很多開發者在這一點上可能感到困惑,由於訪問變量有按值和按引用兩種方式,而參數只能按值傳遞。code
在向參數傳遞基本類型的值時,被傳遞的值會被複制給一個局部變量(即命名參數)。在向參數傳遞引用類型的值時,會把這個值在內存中的地址複製給一個局部變量,所以這個局部變量的變化會反映在函數的外部。以下代碼所示
function addTen(num) { num += 10; return num; } var count = 20; var result = addTen(count); alert(count); // 20,沒有變化 alert(result); // 30
這裏的函數addTen()有一個參數num,而參數其實是函數的局部變量。在調用這個函數時,變量count做爲參數被傳遞給函數,這個變量的值是20。因而,數值20被複制給參數num以便在addTen()中使用。在函數內部,參數num的值被加上了10,但這一變化不會影響外部的count變量。參數num和變量count互不認識,它們僅僅具備相同的值。假如num是按引用傳遞的話,那麼變量count的值也將變成30,從而反映函數內部的變化。固然,使用數值等基本類型值來講明按值傳遞比較簡單,但若是使用對象,那問題就不怎麼好理解了。再舉一個例子:
function setName(obj) { obj.name = "張三"; } var person = new Object(); setName(person); alert(person.name); //"張三"
以上代碼中建立一個對象,並將其保存在了變量person中。而後這個對象被傳遞到setname()函數中以後就被複制給了obj。在這個函數內部,obj和person引用的是同一個對象。換句話說,即便這個對象是按值傳遞的,obj也會按引用來訪問同一個對象。因而,當在函數內部爲obj添加name屬性後,函數外部的person也會有所反映,由於person指向的對象在堆內存中只有一個,並且是全局對象。不少開發者錯誤的認爲:在局部做用域中修改的對象會在全局做用域中反映出來,就說明參數是按引用傳遞的。爲了證實對象是按值傳遞的,咱們在看看下面這個進過修改的例子:
function setName(obj) { obj.name = "張三"; obj = new Object(); obj.name = "李四"; } var person = new Object(); setName(person); alert(person.name); //"張三"
上面這個例子能夠看出,如過person是按引用傳遞的,那麼person就會自動被修改成指向其name屬性值爲"李四"的新對象。可是,當接下來再訪問person.name時,顯示的仍然是"張三"。這說明即便在函數內部修改了參數的值,但原始的引用仍然保存不變。實際上,當在函數內部重寫obj時,這個變量引用的就是一個局部對象了。而這個局部對象會在函數執行完畢時當即銷燬。
能夠將ECMAScript函數的參數想象成局部變量。
1.4 檢測類型
typeof操做符是肯定一個變量是字符串、數值、布爾值,仍是undefined的最佳工具。若是變量的值是一個對象或null,則typeof操做符會返回「object」。
雖然在檢測基本數據類型時typeof是一個得力助手,可是在檢測引用類型時,這個操做符用處卻不大。一般,咱們並不想知道某個值是對象,而是想知道他是什麼類型的對象。爲此ECMAScript提供了instanceof操做符,其語法以下:
result = varible instanceof constructor
若是變量是給定引用類型的實例,那麼instanceof操做符會返回true.
執行環境(execution context,爲簡單起見,有時也稱爲「環境」)是 JavaScript 中最爲重要的一個概念。執行環境定義了變量或函數有權訪問的其餘數據,決定了它們各自的行爲。每一個執行環境都有一個與之關聯的變量對象(variable object),環境中定義的全部變量和函數都保存在這個對象中。雖然咱們編寫的代碼沒法訪問這個對象,但解析器在處理數據時會在後臺使用它。
全局執行環境是最外圍的一個執行環境。根據 ECMAScript 實現所在的宿主環境不一樣,表示執行環,因境的對象也不同。在 Web 瀏覽器中,全局執行環境被認爲是 window 對象,所以全部全局變量和函數都是做爲 window 對象的屬性和方法建立的。某個執行環境中的全部代碼執行完畢後,該環境被銷燬,保存在其中的全部變量和函數定義也隨之銷燬(全局執行環境直到應用程序退出——例如關閉網頁或瀏覽器——時纔會被銷燬)。
每一個函數都有本身的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。而在函數執行以後,棧將其環境彈出,把控制權返回給以前的執行環境。ECMAScript 程序中的執行流正是由這個方便的機制控制着。
當代碼在一個環境中執行時,會建立變量對象的一個做用域鏈(scope chain)。做用域鏈的用途,是保證對執行環境有權訪問的全部變量和函數的有序訪問。做用域鏈的前端,始終都是當前執行的代碼所在環境的變量對象。若是這個環境是函數,則將其活動對象(activation object)做爲變量對象。活動對象在最開始時只包含一個變量,即 arguments 對象(這個對象在全局環境中是不存在的)。做用域鏈中的下一個變量對象來自包含(外部)環境,而再下一個變量對象則來自下一個包含環境。這樣,一直延續到全局執行環境;全局執行環境的變量對象始終都是做用域鏈中的最後一個對象。
標識符解析是沿着做用域鏈一級一級地搜索標識符的過程。搜索過程始終從做用域鏈的前端開始, 而後逐級地向後回溯,直至找到標識符爲止(若是找不到標識符,一般會致使錯誤發生)。在局部做用域中定義的變量能夠在局部環境中與全局變量互換使用,以下面這個例子所示:
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();
以上代碼共涉及 3 個執行環境:全局環境、 changeColor() 的局部環境和 swapColors() 的局部環境。全局環境中有一個變量 color 和一個函數 changeColor() 。 changeColor() 的局部環境中有一個名爲 anotherColor 的變量和一個名爲 swapColors() 的函數,但它也能夠訪問全局環境中的變量 color 。 swapColors() 的局部環境中有一個變量 tempColor ,該變量只能在這個環境中訪問到。不管全局環境仍是 changeColor() 的局部環境都無權訪問 tempColor 。然而,在 swapColors() 內部則能夠訪問其餘兩個環境中的全部變量,由於那兩個環境是它的父執行環境。下圖形象地展現了前面這個例子的做用域鏈。
圖中的矩形表示特定的執行環境。其中,內部環境能夠經過做用域鏈訪問全部的外部環境,但外部環境不能訪問內部環境中的任何變量和函數。這些環境之間的聯繫是線性、有次序的。每一個環境均可以向上搜索做用域鏈,以查詢變量和函數名;但任何環境都不能經過向下搜索做用域鏈而進入另外一個執行環境。對於這個例子中的 swapColors() 而言,其做用域鏈中包含 3 個對象: swapColors() 的變量對象、 changeColor() 的變量對象和全局變量對象。 swapColors() 的局部環境開始時會先在本身的變量對象中搜索變量和函數名,若是搜索不到則再搜索上一級做用域鏈。 changeColor() 的做用域鏈中只包含兩個對象:它本身的變量對象和全局變量對象。這也就是說,它不能訪問swapColors() 的環境。
函數參數也被看成變量來對待,所以其訪問規則與執行環境中的其餘變量相同。
JavaScript沒有塊級做用域常常會形成理解上的困惑。在其餘語言中,由花括號封閉的代碼塊都有本身的做用域(若是用ECMAScript的話來說,就是它們本身的執行環境),於是支持根據條件來定義變量。
例如,以下的代碼在JavaScript中並不會獲得想象中的結果:
<script type="text/javascript"> //javascript沒有塊級做用域 if(true){ var color="blue"; } alert(color);//彈出的值是 blue for(var i=0;i<10;i++){ } alert(i);//彈出的值是10 </script>
在JavaScript中:
(1) if語句中的變量聲明會將變量添加到當前的 執行環境(在這裏是全局環境)中,若是再函數中,則添加到函數的局部環境中.
(2) for語句建立的變量i,即便在for循環執行結束後,也依舊會存在於循環外部的執行環境中,若是在函數中,則添加到函數的局部環境中.
不少語言中都有塊級做用域,但JS沒有,它使用var聲明變量,以function來劃分做用域,大括號「{}」 卻限定不了var的做用域。用var聲明的變量具備變量提高(declaration hoisting)的效果。
ES6裏增長了一個let,能夠在{}, if, for裏聲明。用法同var,但做用域限定在塊級,let聲明的變量不存在變量提高。
示例1: 塊級做用域 if
function getVal(boo) { if (boo) { var val = 'red' // ... return val } else { // 這裏能夠訪問 val return null } // 這裏也能夠訪問 val }
變量val在if塊裏聲明的,但在else塊和if外均可以訪問到val。
把var換成let,就變成這樣了
function getVal(boo) { if (boo) { let val = 'red' // ... return val } else { // 這裏訪問不到 val return null } // 這裏也訪問不到 val }
示例2: 塊級做用域 for
function func(arr) { for (var i = 0; i < arr.length; i++) { // i ... } // 這裏也能夠訪問到i }
變量i在for塊裏聲明的,但在for外也能訪問到。
把var換成let,for外就訪問不了i
function func(arr) { for (let i = 0; i < arr.length; i++) { // i ... } // 這裏訪問不到i }
示例3: 變量提高(先使用後聲明)
function func() { // val先使用後聲明,不報錯 alert(val) // undefined var val; }
變量val先使用後聲明,輸出undefined,也不報錯。
把var換成let,就報錯了
function func() { // val先使用後聲明,報語法錯 alert(val) let val; }
示例4: 變量提高(先判斷後聲明)
function func() { if (typeof val == 'undefined') { // ... } var val = '' }
使用typeof判斷時也能夠再var語句的前面
但把var換成let,if處報語法錯
function func() { if (typeof val == 'undefined') { // ... } let val = ''; }
ES6規定,若是代碼塊中存在let,這個區塊從一開始就造成了封閉做用域。凡是在聲明以前就使用,就會報錯。即在代碼塊內,在let聲明以前使用變量都是不可用的。語法上有個術語叫「暫時性死區」(temporal dead zone),簡稱TDZ。固然TDZ並無出如今ES規範裏,它只是用來形象的描述。
let的注意事項
1. 不能重複聲明
// var和let重複聲明 var name = 'Jack'; let name = 'John'; // 兩個let重複聲明 let age = 24; let age = 30;
執行時報語法錯
2. 有了let後,匿名函數自執行就能夠去掉了
// 匿名函數寫法 (function () { var jQuery = function() {}; // ... window.$ = jQuery })(); // 塊級做用域寫法 { let jQuery = function() {}; // ... window.$ = jQuery; }
以上所述就是本文的所有內容。其中大多數來自於JavaScript高級程序設計