今天學習了javascript 的變數和範圍的基本知識,對於以前在開發中遇到的一些不懂的小問題也有了系統的認識,收穫還是比較多的。 【基本類型和參考型別】 ECMAScript 變數可能包含兩種不同資料類型的值:基本類型值和參考型別值。基本類型值指的是簡單的資料區段,而參考型別值指那些可能由多個值構成的對象。我們常見的五種基本類型的值:Undefined、Null、Boolean、Number 和 String ,這五種基礎資料型別 (Elementary Data Type)是按值訪問的,因此可以操作儲存在變數中的實際的值。參考型別的值是儲存在記憶體中的對象,也就是說不能夠直接操作對象的記憶體空間,參考型別的值是按引用訪問的。注意:我們不能給基本類型的值添加屬性,例如以下代碼: var name = 'name1';name.age = 22;console.log(name.age); // undefined 【複製變數值】 從一個變數向另一個變數複製基本類型值很參考型別值時存在不同的情況,如果從一個變數向另一個變數複製基本類型的值,會在變數對象上建立一個新值,然後把該值複製到為新變數分配的位置上,例如: var num1 = 5;var num2 = num1;通過以上的複製方式,num1 中的 5 和 num2 中的 5 是完全獨立的,也就是說修改 num1 或者 num2 是不會影響到另外一個值的,我們參考如下的代碼: var num1 = 5;var num2 = num1;console.log(num1,num2); // 5 5num1 = 6;console.log(num1,num2); // 6 5下面的表格形象的展示的複製基本類型值的一個過程: 複製前的變數對象 複製後的變數對象 num2 5 (Number類型) num1 5 (Number類型) num1 5 (Number類型) 當從一個變數向另一個變數複製參考型別的值的時候,同樣也會將儲存在變數對象中的值複製一份放到為新變數分配的空間中。但是這個值的副本實際上是一個指標,而這個指標指向儲存在堆中的一個對象。複製操作結束後,兩個變數引用的其實是一個值。因此,改變任意一個變數都會影響到另外一個變數。例如以下代碼: 複製代碼var per1 = new Object();var per2 = per1;per1.name = 'name1';console.log(per1.name,per2.name); // name1 name1 per1.name = 'name2';console.log(per1.name,per2.name); // name2 name2 per2.name = 'name3';console.log(per1.name,per2.name); // name3 name3複製代碼詳細的展示了儲存在變數對象中和儲存在堆中的對象之間的關係: 【傳遞參數】 ECMAScript 中所有函數的參數都是按值傳遞的,也就是說,把函數外部的值複製給函數內部的參數,就和把值從一個變數複製到另外一個變數一樣。基本類型值的傳遞如同基本類型變數的複製一樣,參考型別的傳遞如同參考型別變數的複製一樣。例如以下代碼: 複製代碼function addNum(num){ num += 10; return num;}var count = 20;var result = addNum(count);console.log(count,result); // 20 30複製代碼這裡的函數 addNum() 有一個參數 num ,而參數實際上是函數的局部變數。在調用這個函數時,變數 count 作為參數被傳遞給函數,這個變數的值是20。於是,數值20被複製給參數 num 。但是 num 的改變並不能影響 count 的值,所以 count 輸出的值仍然是20 。再舉一個例子: function setName(obj){ obj.name = 'name5';}var newObj = new Object();setName(newObj);console.log(newObj.name); // name5這段代碼看起來是在局部範圍中修改了 newObj 的 name 的值,在全域範圍也反映出來了,這樣的理解是錯誤的。再看一段代碼: 複製代碼function setName(obj){ obj.name = 'name6'; var obj = new Object(); obj.name = 'name7';}var newObj = new Object();setName(newObj);console.log(newObj.name); // name6複製代碼對比兩段代碼可以看出,如果 newObj 是按引用傳遞的,那麼 newObj 的 name 屬性應該是 name7 才對,但是 name 屬性是 name6,這說明及時在函數內部修改了參數的值,但原始的引用仍然保持未變。 【執行環境和範圍】 執行環境定義了變數或函數有權訪問的其他資料,決定了他們各自的行為。全域執行環境是最外圍的一個執行環境,在Web瀏覽器中,全域執行環境被認為是 window 對象,因此所有的全域變數和函數都是作為 window 對象的屬性和方法建立的。每個函數都有自己的執行環境。當代碼在一個環境中執行時,會建立變數對象的一個範圍鏈。範圍鏈的用途,是保證對執行環境有權訪問的所有變數和函數的有序訪問。範圍鏈的前端,時鐘都是當前執行的代碼所在環境的變數對象。如果這個環境是函數,則將其使用中的物件作為變數對象。範圍鏈中的下一個變數對象來自包含(外部)環境,再下一個變數對象則來自下一個包含環境。全域執行環境的變數對象始終都是範圍鏈的最後一個對象。標識符解析是沿著範圍鏈一級一級的搜尋標識符的過程。請看如下代碼: 複製代碼var color = 'blue';function changeColor(){ if(color == 'blue'){ color = 'red'; } else{ color = 'blue'; }}changeColor();console.log(color); // red複製代碼函數 changeColor() 的範圍包含兩個對象:它自己的變數對象(其中定義著 arguments 對象)和全域環境的變數對象。當 changeColor 在執行的時候,在自己的範圍中並沒有找到 color ,於是便到全域環境中找,找到了 color 的值為 blue ,然後按照 changeColor() 函數的規則將 color 的值設定為 red 。再看一段更加詳細的代碼: 複製代碼//這裡只能訪問 colorvar color = 'blue';function changeColor(){ //這裡可以訪問 color 、newColor ,但是不能訪問 temColor var newColor = 'red'; function swapColor(){ //這裡可以訪問 color 、newColor 和 temColor var temColor = newColor; newColor = color; color = temColor; } swapColor(); console.log(color,newColor); //red blue }function showColor(){ console.log(color); //blue}showColor();changeColor();複製代碼代碼的內容自己體會一下,這裡不做詳細的解釋。 【沒有塊級範圍】 看如下的代碼: for(var i = 0;i < 10;i ++){ i += 1;}console.log(i); // 10對於有塊級範圍的語言來說, for 語句初始設定變數的運算式所定義的變數,只會存在於迴圈的環境之中。在 javascript 中, i 並會在 for 迴圈執行結束後被銷毀,反而被添加到了當前的執行環境(全域環境)中。 1.聲明變數 使用 var 聲明的變數會自動被添加到最接近的環境中。在函數內部,最接近的環境就是函數的局部環境。請看如下代碼: 複製代碼function addNum(num1,num2){ var num = num1 + num2; return num;}var result = addNum(10,20);console.log(result); // 30console.log(num); // num is not defined 複製代碼以上代碼如果不使用 var 聲明 num 的話是不會導致錯誤的,例如: 複製代碼function addNum(num1,num2){ num = num1 + num2; return num;}var result = addNum(10,20);console.log(result); // 30console.log(num); // 30複製代碼2.查詢標識符 當在某個環境中為了讀取或寫入而引用一個標識符時,必須通過搜尋來確定該標識符實際代表什麼。搜尋過程從範圍鏈的前端開始,向上逐級查詢與給定名字匹配的標識符。如果在局部環境中找到該標識符,搜尋過程停止,變數就緒。如果在局部變數中沒有找到該變數名,則繼續沿範圍鏈向上搜尋。搜尋過程會一致追溯到全域環境變數。如果在全域環境變數也沒有找到該標識符,說明該變數尚未定義。請查看以下代碼: var color = 'blue';function getColor(){ return color;}console.log(getColor()); // bluegetaColor() 在搜尋局部變數的時候沒有找到 color ,而函數執行語句是一定要返回一個 color ,與是便到全域環境變數中去搜尋,找到了 color 。需要注意的是,搜尋的過程中如果存在一個局部變數的定義,則搜尋會自動停止,不再進入另一個變數對象。也就是說,如果局部環境存在著同名標識符,就不會使用位於父環境的標識符,例如以下代碼: var color = 'blue';function getColor(){ var color = 'red'; return color;}console.log(getColor()); // red範圍鏈對於理解閉包的概念至關重要,還忘能夠加深理解。