做用域鏈是javascript語言裏很是紅的概念,不少學習和使用javascript語言的程序員都知道做用域鏈是理解javascript裏很重要的一些概念的關鍵,這些概念包括this指針,閉包等等,它很是紅的另外一個重要緣由就是做用域鏈理解起來太難,就算有人真的感受理解了它,可是碰到不少實際問題時候任然會是丈二和尚摸不到頭腦,例如上篇引子裏講到的例子,本篇要講的主題就是做用域鏈,再無別的內容,但願看完本文的朋友能有所收穫。javascript
講做用域鏈首先要從做用域講起,下面是百度百科裏對做用域的定義:html
做用域在許多程序設計語言中很是重要。java
一般來講,一段程序代碼中所用到的名字並不老是有效/可用的,而限定這個名字的可用性的代碼範圍就是這個名字的做用域。程序員
做用域的使用提升了程序邏輯的局部性,加強程序的可靠性,減小名字衝突。編程
在我最擅長的服務端語言java裏也有做用域的概念,java裏做用域是以{}做爲邊界,不過在純種的面嚮對象語言裏咱們不必把做用域研究的那麼深,也不必思考複雜的做用域嵌套問題,由於這些語言關於做用域的深度運用並不會給咱們編寫的代碼帶來多大好處。可是在javascript裏卻大不相同,若是咱們不能很好的理解javascript的做用域咱們就沒辦法使用javascript編寫出複雜的或者規模宏大的程序。閉包
由百度百科裏的定義,咱們知道做用域的做用是保證變量的名字不發生衝突,用現實的場景來理解有我的叫作張三,張三雖然只是一個名字,可是認識張三的人根據名字就能惟一確認這我的究竟是誰,可是這個世界上叫作張三的人可不止一個,特別是兩個叫張三的人有交集的時候咱們就要有個辦法明確指定這個張三毫不是另一個張三,這時咱們可能會根據兩大張三年齡的差別來區分:例如一個張三叫大張三,相對的另一個張三叫小張三了。編程語言裏的做用域其實就是爲了作相似的標記,做用域會設定一個範圍,在這個範圍裏咱們是不會弄錯變量的真實含義。編程語言
前面我講到在java裏經過{}來設置做用域,在{}裏面的變量會獲得保護,這種保護就是不讓{}裏的變量被外部變量混淆和污染。那麼{}的方式適合於javascript嗎?咱們看看下面的例子:函數
var s1 = "sharpxiajun"; function ftn(){ var s2 = "xtq"; console.log(this);// 運行結果: window console.log("s1:" + this.s1 + ";s2:" + this.s2);//運行結果:s1:sharpxiajun;s2:undefined console.log("s1:" + this.s1 + ";s2:" + s2);// 運行結果:s1:sharpxiajun;s2:xtq } ftn();
在javascript世界裏有一個大的做用域環境,這個環境就是window,window環境不須要咱們本身使用什麼方式構建,頁面加載時候頁面會自動構造的,上面代碼裏有一個大括號,這個大括號是對函數的定義,運行之,咱們發現函數做用域內部定義的s2變量是不能被window對象訪問的,所以s2變量是被{}保護起來了,它的生命週期和這個函數的生命週期有關。學習
由這個例子是否是說明在javascript裏,變量也是被{}保護起來了,在javascript語言裏還有非函數的{},咱們再看看下面的例子:this
if (true){ var a = "aaaa"; } console.log(a);// 運行結果:aaaa
咱們發現javascript裏{}有時是起不到定義做用域的功能。這也說明javascript裏的做用域定義是和其餘語言例如java不一樣的。
在javascript裏做用域有一個專門的定義execution context
,有的書裏把這個名字翻譯成執行上下文,有的書籍裏把它翻譯成執行環境,我更傾向於後者執行環境,下文我提到的執行環境就是execution context。這個命名很是形象,這個形象體如今execution這個單詞,execution含義就是執行,咱們來想一想javascript裏那些狀況是執行:
狀況一:當頁面加載時候在script標籤下的javascript代碼會按順序執行,而這些能被執行的代碼都是屬於window的變量或函數;
狀況二:當函數的名字後面加上小括號(),例如ftn(),這也是在執行,不過它執行的是函數。
如此說來,javascript裏的執行環境有兩類一類是全局執行環境,即window表明的全局環境,一類是函數表明的函數執行環境,這也就是咱們常說的局部做用域。
執行環境在javascript語言裏並不是是一個抽象的概念,而是有具體的實現,這個實現實際上是個對象,這個對象也有個名字叫作variable object,這個變量有的書裏翻譯爲變量對象,這是直譯,有的書裏把它稱爲上下文變量,這裏我仍是傾向於後者上下文變量,下文裏提到的上下文變量就是指代variable object。上下文變量存儲的是上下文變量所處執行環境裏定義的全部的變量和函數。
全局執行環境的上下文變量是能夠訪問到的,它就是window對象,因此咱們說window能表明全局做用域是有道理的,可是局部做用域即函數的執行環境裏的上下文變量是代碼不能訪問到的,不過javascript引擎在處理數據時候會使用到它。
在javascript語言裏還有一個概念,它的名字叫作execution context stack
,翻譯成中文就是執行環境棧,每一個要被執行的函數都會先把函數的執行環境壓入到執行環境棧裏,函數執行完畢後,這個函數的執行環境就會被執行環境棧彈出,例如上面的例子:函數執行時候函數的執行環境會被壓入到執行環境棧裏,函數執行完畢,執行環境棧會把這個環境彈出,執行環境棧的控制權就會交由全局環境,若是函數後面還有代碼,那麼代碼就是接着執行。若是函數裏嵌套了函數,那麼嵌套函數執行完畢後,執行環境棧的控制權就交由了外部函數,而後依次類推,最後就是全局執行環境了。
講到這裏咱們大名鼎鼎的做用域鏈要登場了,函數的執行環境被壓入到執行環境棧裏後,函數就要執行了,函數執行的第一步不是執行函數裏的第一行代碼而是在上下文變量裏構造一個做用域鏈,做用域鏈的英文名字叫作scope chain
,做用域鏈的做用是保證執行環境裏有權訪問的變量和函數是有序的,這個概念裏有兩個關鍵意思:有權訪問和有序,咱們看看下面的代碼:
var b1 = "b1"; function ftn1(){ var b2 = "b2"; var b1 = "bbb"; function ftn2(){ var b3 = "b3"; b2 = b1; b1 = b3; console.log("b1:" + b1 + ";b2:" + b2 + ";b3:" + b3);// 運行結果:b1:b3;b2:bbb;b3:b3 } ftn2(); } ftn1(); console.log(b1);// 運行結果:b1
有這個例子咱們發現,ftn2函數能夠訪問變量b1,b2,這個體現了有權訪問的概念,當ftn1做用域裏改變了b1的值而且把b1變量從新定義爲ftn1的局部變量,那麼ftn2訪問到的b1就是ftn1的,ftn2訪問到b1後就不會在全局做用域裏查找b1了,這個體現了有序性。
下面我要總結下上面講述的知識:
本篇的小標題是:做用域鏈的相關問題,這個標題定義的含義是指做用域鏈是大名鼎鼎了,可是做用域鏈在廣大程序員的理解裏其實包含的意義已經超越了做用域鏈在javascript語言自己的定義。廣大程序員對做用域鏈的理解有兩塊一塊是做用域,而做用域在javascript語言裏指的是執行環境execution context
,執行環境在javascript引擎裏是經過上下文變量體現的variable object,javascript引擎裏還有一個概念就是執行環境棧execution context stack
,當某一個函數的執行環境壓入到了執行環境棧裏,這個時候就會在上下文變量裏構造一個對象,這個對象就是做用域鏈scope chain
,而這個做用域鏈就是廣大程序員理解的第二塊知識,做用域鏈的做用是保證執行環境裏有權訪問的變量和函數是有序的,做用域鏈的變量只能向上訪問,變量訪問到window對象即被終止,做用域鏈向下訪問變量是不被容許的。
不少人經常認爲做用域鏈是理解this
指針的關鍵,這個理解是不正確的的,this指針構造是和做用域鏈同時發生的,也就是說在上文變量構建做用域鏈的同時還會構造一個this對象,this對象也是屬於上下文變量,而this變量的值就是當前執行環境外部的上下文變量的一份拷貝,這個拷貝里是沒有做用域鏈變量的,例如代碼:
var b1 = "b1"; function ftn1(){ console.log(this);// 運行結果: window var b2 = "b2"; var b1 = "bbb"; function ftn2(){ console.log(this);// 運行結果: window var b3 = "b3"; b2 = b1; b1 = b3; console.log("b1:" + b1 + ";b2:" + b2 + ";b3:" + b3);// 運行結果:b1:b3;b2:bbb;b3:b3 } ftn2(); } ftn1();
咱們看到函數ftn1和ftn2裏的this指針都是指向window,這是爲何了?由於在javascript咱們定義函數方式是經過function xxx(){}
形式,那麼這個函數無論定義在哪裏,它都屬於全局對象window,因此他們的執行環境的外部的執行上下文都是指向window。
可是咱們都知道現實代碼不少this指針都不是指向window,例以下面的代碼:
var obj = {
name:"sharpxiajun", ftn:function(){ console.log(this);// 運行結果: Object { name="sharpxiajun", ftn=function()} console.log(this.name);//運行結果: sharpxiajun }
}
obj.ftn();// :
運行之,咱們發現這裏this指針指向了Object,這就怪了我前文不是說javascript裏做用域只有兩種類型:一個是全局的一個是函數,爲何這裏Object也是能夠製造出做用域了,那麼個人理論是否是有問題啊?那咱們看看下面的代碼:
var obj1 = new Object(); obj1.name = "xtq"; obj1.ftn = function(){ console.log(this);// 運行結果: Object { name="xtq", ftn=function()} console.log(this.name);//運行結果: xtq } obj1.ftn();
這兩種寫法是等價的,第一種對象的定義方法叫作字面量定義,而第二種寫法則是標準寫法,Object對象的本質也是個function,因此當咱們調用對象裏的函數時候,函數的外部執行環境就是obj1自己,即外部執行環境上下文變量表明的就是obj1,那麼this指針也是指向了obj1。