前言
這篇和大家說一下javascript中的變量和作用域,由於是將基礎嘛,主要給大家捋一下知識,不想翻開書復習的道友可以看一下,打算剛開始學習javascript的同學可以掃一眼。
PS:jQuery源碼交流群( 239147101)等你來,群里高手雲集,讓我受益匪淺,盡量少灌水。
變量
javascript中有兩種變量,分別是基本類型和引用類型,基本類型是Null,Undefined,String,Boolean,Number這五種,前面簡單的介紹了。引用類型是指Object,Array,Date,RegExp,Function這些。創建這兩種變量是類似的,都是創建一個變量然后給它賦值。不同的原因主要是在內存中位置和操作不同。
基本類型
基本類型比較簡單,基本類型的值保存在棧中。看例子
var v = 1;
基本類型變量在內存中的表示,沒有涉及堆
看看復制變量之后在內存中的表示,執行的代碼如下
var v = 1; var n = v;
解釋一下,如果一個變量把基本類型的值復制給另一個變量時,會創建一個新的相同的值,把這個新的值賦值給新的變量,這樣,內存中就有兩個一樣的值了,分別是新的變量和就的變量指向的值,雖然值是一樣的。
引用類型
應用類型的變量創建和基本類型的創建是一樣的,主要看在內存中的存儲方式,代碼
var obj = new Object();
可以看到,變量obj存儲的是一個地址(個人的理解),其實obj的值是一個指針,指向了堆中的對象,可以找到堆中的new出來的對象。再看看復制一個變量時的情況
var obj = new Object(); var o = obj;
解釋一下,這和基本類型的復制是不同的,我們可以看出,當一個變量向另一個變量復制引用類型的值時,也會將存儲在變量對象中的值復制一個給新的變量。復制的這個值實際上就是一個指針,指向堆中的一個對象,由於指針是相同的,所以知指向的對象就是同一個對象,此時無論你該哪一個變量,都會影響另一個變量,因為指向的對象是用一個嘛,例如
var obj = new Object(); var o = obj; o.name = "hainan"; console.log(obj.name);//"hainan"
而基本類型則不會,看例子
var v = 1; var n = v; v = 100; console.log(n);//1 console.log(v);//100
這個比較簡單,不懂的話看上面的那個內存的圖就懂了。
區別
上面說了復制的那個區別,還有一個就是基本類型不能添加屬性和方法,而引用類型則可以添加,看例子吧,很簡單
//基本類型 var peo = "hainan"; peo .age = 25; console.log(peo.age);//undefined,但是不報錯 //引用類型 var people = new Object(); people.age = 25; console.log(people.age);//25
函數傳參
javascript中函數參數都是按值傳遞的,也就是把函數外部的值復制給函數內部的參數,和復制變量一樣,無論傳遞的是什么類型,都和復制變量一樣。首先看一下傳遞基本類型的例子
function test(n){ return n = n+100; } var num = 10; var result = test(num); console.log(num);//10 沒有發生變化 console.log(result);//110
簡單解釋一下,調用函數時,傳遞一個基本類型的參數num給函數,此時,復制值給內部的參數n,這樣num和n變量都有了相同的值,但是,這兩個之間沒有任何的關系,只是值相同而已,想想前面的圖就清楚了,內部的變量也就是參數n改變了之后,num的值並沒有發生改變。
接下來看看傳遞引用類型的例子
function test(obj){ obj.name = "hainan"; } var people = new Object(); test(people); console.log(people.name);//"hainan"
解釋一下,其實這個也和復制引用類型變量是一樣的,傳遞一個引用類型給函數參數時,把外部的值復制給內部的函數參數,由於是引用類型這個值是一個指針,所以外部的引用類型變量和內部的參數此時會指向同一個對象,回想上面復制的圖,當內部的參數指向的對象改變時,外部的變量指向的對象一定會改變,是同一個對象嘛。
所以,現在你只需知道,傳遞參數和復制變量是同一回事,不會的時候回想內存中的存儲方式就明白了,管它是按值還是按引用傳遞呢,只是一個說法罷了,呵呵,但是面試的時候可能會問,記住javascript是值傳遞就行了。
作用域
執行環境
每個函數都有自己的執行環境(execution context),執行環境定義了變量和函數有權訪問的數據,太官方了,就是每一個函數都有自己可以訪問的范圍,在自己的范圍內的數據才可以得到。每一個執行環境中都有一個變量對象,保存着該環境中定義的變量和函數。全局執行環境是最外面的一個執行環境,就是window對象,所有的全局變量和函數就是它的屬性。每一個執行環境執行完之后,這個環境就會被銷毀,里面的變量對象也就沒有了,當然變量對象中的變量和函數也就銷毀了。
作用域
javascript的作用域是指變量和函數可以訪問的范圍,分為局部作用域和全局作用域,這個和C語言是類似的,但是不同點是javascript的作用域沒有塊級作用域,不像C語言的{}可以表示一個塊級的作用域,javascript只有函數作用域,在函數內部聲明的變量只能在函數體和子函數可以訪問,這個函數的外部不能訪問。看例子
//沒有塊級作用域 if(true){ var n = 1; } console.log(n);//1 //注意 for(var i=0;i<10;++i){ } console.log(i);//10
上面的例子要是在C語言或者java中n和i會在{}語言執行完之后銷毀,在javascript中可以看到,它們並沒有銷毀,說明並沒有塊級作用域。
function test(a,b){ var sum = a + b; return sum; } test(1,2);//3 console.log(sum);//sum is not defined
可以看出sum是在函數的外部訪問不到的,因為sum函數是在函數的局部作用域中定義的,在函數執行結束時,內部的變量就會銷毀,所以外部的作用域訪問不到它,這就是函數的作用域。
作用域鏈
作用域鏈(scope chain)是保證在執行環境中有序的訪問變量和函數,我們可以這樣想,每個函數都有一個自己的執行環境,這個執行環境的嵌套就像套娃是一樣的,大的套小的,內部的執行環境的變量有權訪問外部的執行環境的數據,而外部的不可以反問內部的,所以當內外的執行環境中都有一個相同的變量或函數時,你是先訪問哪一個呢?所以就有了作用域鏈這個概念。這個作用域鏈的每一個節點是一個變量對象,函數中有一個活動變量對象的概念,剛開始時只有函數的arguments對象,之后會把當前的變量對象中的變量和函數復制到活動對象中。作用域鏈的第一個變量對象是活動對象,之后就是下一個包含環境,之后是包含環境的包含環境......直到全局執行環境的變量對象。 看例子可能大家會明白
function test(a,b){ var sum = a + b; return sum; } var s = test(1,2);
分析上面的這段代碼,在未調用test()函數之前,看看作用域鏈情況
在未執行函數的時候,可以看到函數的作用域鏈只用一個全局的變量對象,里面有window和test函數等,在執行var s = test(1,2);語句的時候,作用域鏈會發生變化,會增加一個函數的活動對象到作用域鏈的前面,這個活動對象的初始值只有函數的arguments對象,之后會把函數的內部變量等復制到這個活動對象中,請看下面的圖
這就是函數的作用域鏈,以后要訪問某個變量的時候,會沿着這個作用域鏈進行查找,即沿着活動對象->外部函數的活動對象->外部函數的外部函數的活動對象->......->終點的全局執行環境,在這期間在某個活動對象中內部有這個值就會返回這個值,這個過程就會停止,不在進入另一個執行環境中,看個例子
//全局變量 var color = "red"; function getColor(){ //內部變量 var color = "blue"; return color; } console.log(getColor());//blue
看到這里大家會明白了。
PS:其實javascript的閉包和作用域鏈有很大的聯系,這里咱不討論,閉包會單獨討論。
小結
這就是javascript的作用域了,主要大家還是好好看看變量內存那塊,分析幾個就會了。注意一下javascript沒有塊級作用域這一說法,這點大家要小心了。最近幾天天天在群里看大家的聊天記錄,論文也不想寫了,哎,坐等回家吃豬肉了,希望老師別鄙視我。