【javascript 變量和做用域】

今天學習了javascript 的變量和做用域的基本知識,對於之前在開發中遇到的一些不懂的小問題也有了系統的認識,收穫仍是比較多的。javascript

【基本類型和引用類型】前端

ECMAScript 變量可能包含兩種不一樣數據類型的值:基本類型值和引用類型值。基本類型值指的是簡單的數據段,而引用類型值指那些可能由多個值構成的對象。咱們常見的五種基本類型的值:Undefined、Null、Boolean、Number 和 String ,這五種基本數據類型是按值訪問的,所以能夠操做保存在變量中的實際的值。引用類型的值是保存在內存中的對象,也就是說不可以直接操做對象的內存空間,引用類型的值是按引用訪問的。注意:咱們不能給基本類型的值添加屬性,例如如下代碼:java

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  5
num1 = 6;
console.log(num1,num2);  // 6  5

下面的表格形象的展現的複製基本類型值的一個過程:學習

 

複製前的變量對象 複製後的變量對象
       
    num2

5spa

(Number類型)指針

num1

5code

(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 。再看一段更加詳細的代碼:

//這裏只能訪問 color
var 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);    // 30
console.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);    // 30
console.log(num);      // 30

2.查詢標識符

當在某個環境中爲了讀取或寫入而引用一個標識符時,必須經過搜索來肯定該標識符實際表明什麼。搜索過程從做用域鏈的前端開始,向上逐級查詢與給定名字匹配的標識符。若是在局部環境中找到該標識符,搜索過程中止,變量就緒。若是在局部變量中沒有找到該變量名,則繼續沿做用域鏈向上搜索。搜索過程會一致追溯到全局環境變量。若是在全局環境變量也沒有找到該標識符,說明該變量還沒有定義。請查看如下代碼:

var color = 'blue';
function getColor(){
    return color;
}
console.log(getColor());   // blue

getaColor() 在搜索局部變量的時候沒有找到 color ,而函數執行語句是必定要返回一個 color ,與是便到全局環境變量中去搜索,找到了 color 。須要注意的是,搜索的過程當中若是存在一個局部變量的定義,則搜索會自動中止,再也不進入另外一個變量對象。也就是說,若是局部環境存在着同名標識符,就不會使用位於父環境的標識符,例如如下代碼:

var color = 'blue';
function getColor(){
    var color = 'red';
    return color;
}
console.log(getColor());   // red

做用域鏈對於理解閉包的概念相當重要,還望可以加深理解。

相關文章
相關標籤/搜索