寫在最前面:這是我即將開始寫的一個系列,主要是在框架橫行的時代,雖然上班用的是框架,但是對于面試,以及技術進階,JS基礎知識的鋪墊是錦上添花,也是不得不學習的一塊知識,雖然開汽車的不需要很懂汽車,只需要掌握汽車的常用功能即可。但是如果你懂汽車,那你也能更好地開車,同理。當然,一篇文章也不會光光只講一個知識點,一般會將有關聯(lián)的知識點串聯(lián)起來,一邊記錄自己的學習,一邊分享自己的學習,互勉!如果可以的話,也請給我點個贊,你的點贊也能讓我更加努力地更新!
概覽
- 食用時間: 5-10分鐘
- 難度: 簡單,別跑,看完再走
JS編譯原理
我們先來看一行代碼
var name='jack';
在我們眼中,這是一行代碼,一個語句就能搞定的事情呀,但是,在JS的眼里,這句話的代碼應該是下面這種方式呈現(xiàn)的
var name; //編譯階段處理
name='jack'; //執(zhí)行階段處理
是不是發(fā)現(xiàn)這和你原本認識的JS不太一樣,那是因為JS編譯主要分為兩個階段,編譯階段和執(zhí)行階段,讓我們首先來看下這兩個階段分別做了那些事情:
-
編譯階段
這個階段的主角就是所謂的編譯器,這個編譯器會找遍當前作用域,看看是不是已經(jīng)存在一個叫
name的變量。如果已經(jīng)存在,那么就什么都不做,直接忽略var name這個聲明,繼續(xù)編譯下去;如果沒有,則在當前作用域里新增一個叫name的變量。然后,編譯器會為引擎生成運行時所需要的代碼,程序就進入了執(zhí)行階段 -
執(zhí)行階段
這個階段的主角就是大家所熟悉的JS引擎啦,JS引擎在運行的時候,也會先找遍當前作用域,看看是否有一個叫
name的變量,如果有的話,那么剛剛好,直接賦值,如果沒有的話,那就是當前作用域沒有,那怎么辦,那就考慮探出頭去,去外面( 父級作用域 )看看有沒有,沒有的話,再去外面查找,一層又一層( 當然如果是還有父層的話 ),如果最終還是找不到的話,那么JS引擎也表示無能為力,那就拋個異常給別人看看吧,表示自己已經(jīng)努力過了。上面提到的去外面查找,一層又一層,從當前作用域再到父級作用域,再到父級的父級作用域,以此類推,就是所謂的作用域鏈了,就跟鏈條一樣,一節(jié)有一節(jié)往上,是不是形容地可以說是很貼切了??偨Y而言就是,作用域套作用域,也就有了所謂的作用域鏈
作用域
-
定義
大家都知道,變量最基本的能力就是能夠存儲變量當中的值、并且允許我們對這個變量的值進行訪問和修改,而對于變量存儲,訪問的一套規(guī)則,就是所謂的作用域
-
分類
-
全局作用域
在任何函數(shù)外或者代碼塊之外的頂層作用域就是全局作用域,而里面的變量就是全局變量
var name='jack'; //全局作用域 function showName(){ //函數(shù)作用域 console.log(name); } { name='test'; //塊級作用域 } showName(); //test可以看到全局變量,無論是在全局作用域,函數(shù)作用,還是塊級作用域中都可以正常訪問
-
函數(shù)作用域
在函數(shù)內(nèi)的作用域就是函數(shù)作用域
function showName(){ var name='jack'; //函數(shù)作用域 } showName(); //方法調(diào)用 { console.log(name); //塊級作用域,Uncaught ReferenceError: name is not defined } console.log(name); //全局作用域,Uncaught ReferenceError: name is not defined可以看到函數(shù)內(nèi)部變量,在全局作用域以及塊級作用域中,都無法訪問,只有在函數(shù)內(nèi)部,才能訪問的到,所以函數(shù)內(nèi)的變量也被稱為局部變量
-
塊作用域
在
ES6中新出的兩個新關鍵字let和const中,自帶塊級作用域,塊級作用域相當于是只在這塊代碼塊中生效,如果它被大括號{}所包圍,那么大括號中就是一段代碼塊,代碼塊中使用let和const聲明的變量也被稱為局部變量{ let name='jack'; } console.log(name); //Uncaught ReferenceError: name is not defined function showName{ console.log(name); } showName(); //Uncaught ReferenceError: name is not defined可以看到塊級作用域中的變量,出了那個代碼塊,就找不到了
-
其實上面的三種情況,結合JS編譯原理和作用域鏈向外不向內(nèi)查找,思考一下,也不難理解
作用域鏈
回到作用域鏈,其實在上面已經(jīng)解釋的差不多了,作用域和作用域的嵌套,就產(chǎn)生了作用域鏈,另外要記住的一個特性就是作用域鏈的查找,向外不向內(nèi),想想探出頭去,而不是看著鍋里,就可以了
變量提升
先來看一段代碼
name='jack';
console.log(name); //jack
var name;
你會發(fā)現(xiàn),這段代碼不會發(fā)生報錯,并且能正常地運行,結合上面所說的JS編譯原理,你就能想到,在JS的眼中,它的代碼實際上是這樣子的,這就是所謂的代碼提升,說白了那就是代碼的聲明提到代碼的最前面
var name;
name='jack';
console.log(name); //jack
其實這個變量提升應該是照道理接著編譯原理寫下來的,為什么放到了最后呢,因為如果你忘了,正好往上翻一下,重新回溫一遍JS編譯原理
緊接著上面,讓我們來看下不吃變量提升這一套的 let 和 const ,先來看一段代碼
name='jack';
console.log(name) //Uncaught ReferenceError: Cannot access 'name' before initialization
let name;
黑人問號 ??? ,說好的變量提升呢,記住 let 和 const 的一個特點,禁用變量提升,這也是 ES6 故意為之的,將生命前不可用做到了強約束,總結而言,** var 存在變量提升, let 和 const 不存在變量提升**
既然已經(jīng)提到了 const ,順帶提一下它聲明了以后必須賦值的操作
const name; //Uncaught SyntaxError: Missing initializer in const declaration
暫時性死區(qū)
緊接著上面,讓我們來看下什么叫做暫時性死區(qū),先來看一段代碼
var name='jack';
{
name='bob';
let name; //Uncaught ReferenceError: Cannot access 'name' before initialization
}
記住 ES6 中的一個特性,如果區(qū)塊中存在 let 和 const 命令,這個區(qū)塊對這些命令聲明的變量,從一開始就形成了封閉作用域。因為JS清楚地感知到了 name 是用 let 聲明在當前這個代碼塊內(nèi)的,所以會給這個變量 name 加上了暫時性死區(qū)的限制,它就不往外探出頭了。
那么,如果我們把上面的let name;去掉,程序也將正常運行, name 的值也被成功修改為了bob,就是正常地按照作用域鏈的規(guī)則,向外探出頭去了。