我眼中的js編程(2)--詳解作用域內(nèi)變量和函數(shù)的聲明與訪問

我眼中的js編程(1)主要介紹了js是用來做什么的碾篡,這一篇開始及以后總結(jié)js具體該怎么用。本篇總結(jié)了作用域內(nèi)變量和函數(shù)的聲明與訪問筏餐。先看一段有意思的代碼开泽。

var a = a;
console.log(a) // undefined

let b = b;
console.log(b) // ReferenceError

let c;
c = c;
console.log(c) // undefined

很有意思的結(jié)果。為什么是這樣呢魁瞪?這個問題放一邊穆律,我先扯點沒用的,等讀到文章最后佩番,如果你理解了众旗,就一定會知道為什么。

(ps:先扯會兒淡)所有的編程語言都有相通之處趟畏,也有其各自擅長的地方贡歧。js和其他語言一樣,數(shù)組赋秀、函數(shù)利朵、對象等數(shù)據(jù)類型以及各個數(shù)據(jù)類型處理數(shù)據(jù)的api、運算符猎莲、變量的作用域绍弟、對象的創(chuàng)建和繼承,這些概念著洼,雖然各種語言的語法規(guī)則不一樣樟遣,但是本質(zhì)上是一樣的而叼。一通百通,沒有太多新鮮之處豹悬。

js的獨特之處在于能夠處理網(wǎng)頁的交互效果葵陵。這事兒只有js能干,因為瀏覽器只有js引擎瞻佛,沒有php引擎脱篙、java引擎,為什么是這樣伤柄?這和js與瀏覽器的歷史有關(guān)绊困,阮一峰老師有一篇文章Javascript誕生記很好的做了詮釋。

作用域

作用域有什么用适刀?某個功能的代碼秤朗,把其中沒有必要暴露的函數(shù)和變量封裝起來,實現(xiàn)最小暴露蔗彤。

(ps:繼續(xù)扯淡)軟件設(shè)計中有最小暴露原則川梅,就是用來實現(xiàn)某功能的代碼,應該最大限度的暴露最少的東西然遏。不止是軟件贫途,生活中的事物也遵循著這個原則,比如數(shù)據(jù)線待侵,只是暴露了一個和手機的接口丢早,其余的線路都在包裝線內(nèi)部隱藏,比如智能手機秧倾,暴露在外面的就是個外殼和屏幕怨酝,復雜的線路和芯片被隱藏在手機內(nèi)部,而拆解開手機內(nèi)部的具體零件那先,每個零件同樣也是遵循著最小暴露原則农猬,甚至社會組織結(jié)構(gòu)比如飯店,大廳的餐桌售淡、服務員對外暴露斤葱,而后廚的炊具、廚師隱藏在內(nèi)部揖闸,而且仔細觀察生活中的各類事物揍堕,無不遵循這這樣的原則,而且是遞歸最小暴露汤纸,即拆解開來的零件同樣也遵守衩茸,零件的零件仍然遵守。腦洞大開扯遠了贮泞。楞慈。幔烛。下面說一下作用域怎么使用?聊聊作用域的相關(guān)規(guī)則(先聊有啥用囊蓝,再聊怎么用说贝,要知其然和所以然)。

變量的作用域在寫代碼的時候就確定了慎颗。es6之前js只有全局作用域和函數(shù)作用域(try-catch語句的作用域、eval()方法的作用域等暫不考慮)言询,在es6中有了塊作用域俯萎、新的全局let作用域、for循環(huán)作用域运杭、模塊作用域(參考自深入淺出es6)夫啊。js中變量的作用域是整個前后封閉的函數(shù)代碼塊,而不是開始于變量聲明之處(有些編程語言的作用域是這樣的)辆憔。嵌套作用域是編程語言的核心理念之一撇眯,js中常見的作用域()有:

  • 函數(shù)作用域
    var聲明的變量所在的函數(shù)的整個代碼塊。
  • 塊作用域
    let(const)聲明的變量(常量)所在的外層塊{ }
if(true){
  let a = 5
}
console.log(a) // ReferenceError
  • 新的全局let作用域
    let聲明的全局變量不是全局對象的屬性虱咧,let聲明的全局變量存在于一個不可見的塊作用域中熊榛,理論上是頁面中包含所有js代碼的不可見的外層塊。
let a = 1
console.log(window.a) // undefined
  • for作用域
    for循環(huán)中()中變量是let聲明的時候腕巡,比如for(let i = 0;i<5;i++){...}玄坦,每次迭代都為i綁定新的塊作用域,這個塊就是for(){ }的{ }
for(let i = 0;i<5;i++){
  setTimeout(function(){
    console.log(i) // 每個1s輸出一次绘沉,分別輸出0 1 2 3 4
  },1000 * i)
}
  • 模塊作用域

常量和變量

js中的變量和常量是需要用關(guān)鍵字聲明的(php不用聲明)
關(guān)鍵字var聲明變量:var age = 18
關(guān)鍵字let聲明變量:let name = 'yanhaoqi'
關(guān)鍵字const聲明常量:const STUDENT_NUM = 30
常量和變量有全局局部之分煎楣,下面以變量為例說明。

  • 全局變量
    全局變量定義在全局對象中车伞,可在任何作用域訪問到择懂。
    ECMAScript本身具有全局對象,但全局對象不是任何其他對象的屬性另玖,所以它沒有名字困曙。瀏覽器環(huán)境的全局對象是window,表示允許js代碼的瀏覽器窗口日矫,瀏覽器窗口就是瀏覽器端js的最大操作范圍(權(quán)限)赂弓。node環(huán)境下的全局對象是globle
  • 局部變量
    局部變量聲明在局部作用域哪轿,只在局部作用域內(nèi)可見盈魁。

下面主要講 作用域內(nèi)變量的訪問規(guī)則
先看兩段代碼:

console.log(name) // undefined
var name
console.log(name) // undefined
name = 'yanhaoqi'
console.log(name) // yanhaoqi
console.log(age) // ReferenceError
let age
console.log(age) // undefined
age = 18
console.log(age) // 18

為什么會有這樣的區(qū)別呢?你可能會說窃诉,let聲明的變量沒有變量提升杨耙,其實這樣說是不完全準確的赤套,在這里我深入討論下一些細節(jié)。

js引擎在編譯和解釋代碼的時候珊膜,聲明的變量有三個階段:
聲明階段(Declaration Phase) :在當前作用域中注冊一個變量(作用域在編譯和解釋之前已經(jīng)確定)
初始化階段(Initialization Phase):在作用域中為變量綁定內(nèi)存容握,變量初始化為undefined。
賦值階段(Assignment Phase):為初始化的變量分配一個具體的值车柠。

var聲明的變量

上面第一段代碼剔氏,js引擎編譯和解釋過程如下:

  • 第一步
    在執(zhí)行任何語句之前,先找到這段代碼中所有的聲明var name進行處理(引擎在編譯時候的任務之一)
    name變量在任何代碼執(zhí)行前先在作用域頂部通過了 聲明階段 竹祷,在作用域注冊了變量name
    然后緊跟著來到 初始化階段 谈跛,name初始化為undefined,兩個階段之間沒有任何間隙
    這個過程叫變量提升
  • 第二步
    開始執(zhí)行第一句代碼console.log(name)塑陵,此時結(jié)果是undefined
    然后開始執(zhí)行第一句代碼console.log(name) 結(jié)果是undefined
  • 第三步
    執(zhí)行 var name感憾,沒什么實際意義,因為一開始引擎就找到了var聲明進行了處理令花,繼續(xù)執(zhí)行后面console.log(name)結(jié)果仍然是undefined阻桅,這里就是為了對比下面let代碼的結(jié)果。
  • 第四部
    執(zhí)行后面的console.log(name)結(jié)果是yanhaoqi
屏幕快照 2017-09-13 下午4.39.20.png
let聲明的變量

上面第二段代碼兼都,js引擎編譯和解釋過程如下:

  • 第一步
    在執(zhí)行任何語句之前嫂沉,先找到這段代碼中所有的聲明let age進行處理,age變量在任何代碼執(zhí)行前先在作用域頂部通過了 聲明階段 扮碧,在作用域中注冊了變量name输瓜。不會緊接著進行初始化階段。這算不算let聲明的變量的提升我查到的資料上說法不一芬萍,但本質(zhì)的過程就是這樣的尤揣。
  • 第二部
    開始執(zhí)行第一句代碼console.log(age),因為age還沒有經(jīng)歷初始化階段柬祠,沒有被分配內(nèi)存和初始化為undefined北戏,所以會報錯ReferenceError
  • 第三步
    執(zhí)行代碼let age漫蛔。此時age變量才會進行初始化嗜愈。接著執(zhí)行console.log(age)結(jié)果是undefined
  • 第四部
    執(zhí)行代碼age = 18莽龟。此時完成 賦值階段 蠕嫁。

變量提升的問題,弄清楚變量的聲明和訪問的過程就ok了毯盈,至于有人說let聲明的變量沒有變量提升剃毒,有人說let聲明的變量是不完全提升,說法不同而已,管他呢赘阀,本質(zhì)就是這樣的益缠。

屏幕快照 2017-09-13 下午5.54.49.png

var聲明的變量在一開始就完成了 聲明階段初始化階段,兩個階段是連在一起的基公,而let聲明的變量要執(zhí)行到let時候才會完成 初始化階段幅慌。let聲明的變量完成了聲明階段還沒有到達初始化階段的時候如果訪問該變量就會報錯ReferenceError,我們稱變量此時處在臨時死區(qū)(Temporal Dead Zone轰豆,簡稱TDZ)胰伍。

函數(shù)聲明的提升

既然上面詳細解釋了變量的聲明和訪問的過程,順便接著說一下函數(shù)聲明的提升酸休。首先要搞清楚函數(shù)聲明和函數(shù)表達式的區(qū)別喇辽,如果關(guān)鍵字function是函數(shù)定義的第一個詞,那這就是一個函數(shù)聲明雨席,否則就是一個函數(shù)表達式。

function foo(){
  console.log(123)
}
函數(shù)聲明
var foo = function(){
  console.log(123)
} 
函數(shù)表達式
(function foo(){
  console.log(123)
})
函數(shù)表達式

明確了什么是函數(shù)聲明后吠式,下面我們討論下函數(shù)聲明的訪問陡厘。

[2,3,4,5].reduce(multiplier); // 120
function multiplier(a,b){
  return a * b;
}

在定義multiplier函數(shù)之前就把它作為參數(shù)傳入了reduce()函數(shù),為什么函數(shù)在定義之前就可以使用特占?我們看下js引擎執(zhí)行這段代碼的具體編譯和解釋的過程糙置。

  • 第一步
    js引擎在執(zhí)行任何代碼之前先找到函數(shù)聲明,并在對應的作用域的頂部完成 聲明階段 是目、 初始化階段 谤饭、 賦值階段
  • 第二步
    開始執(zhí)行第一句代碼[2,3,4,5].reduce(multiplier);懊纳,函數(shù)multiplier作為reduce()的參數(shù)揉抵。
屏幕快照 2017-09-13 下午6.34.14.png

最后,關(guān)于js語言的設(shè)計中的變量提升和函數(shù)提升的規(guī)則嗤疯,為什么有變量提升的設(shè)計冤今,這要問js作者Brendan Eich,網(wǎng)上這方面信息較少茂缚,我在這里給一篇我覺得解釋的不錯的博客點擊查看戏罢。

總結(jié)var let聲明的變量和函數(shù)聲明的訪問的區(qū)別就是,var聲明的變量脚囊,聲明階段龟糕、初始化階段2個階段是耦合的,let聲明的變量悔耘,聲明階段和初始化階段是解耦的讲岁,而函數(shù)聲明的聲明階段、初始化階段、賦值階段三個階段都是耦合的催首。

點擊查看上一篇我眼中的js編程(1)
點擊查看下一篇我眼中的js編程(3)
我眼中的js編程系列是我個人的學習總結(jié)扶踊,如有錯誤,煩請包涵郎任、不吝賜教秧耗,O(∩_∩)O謝謝

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市舶治,隨后出現(xiàn)的幾起案子分井,更是在濱河造成了極大的恐慌,老刑警劉巖霉猛,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尺锚,死亡現(xiàn)場離奇詭異,居然都是意外死亡惜浅,警方通過查閱死者的電腦和手機瘫辩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坛悉,“玉大人伐厌,你說我怎么就攤上這事÷阌埃” “怎么了挣轨?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長轩猩。 經(jīng)常有香客問我卷扮,道長,這世上最難降的妖魔是什么均践? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任晤锹,我火速辦了婚禮,結(jié)果婚禮上彤委,老公的妹妹穿的比我還像新娘抖甘。我一直安慰自己,他們只是感情好葫慎,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布衔彻。 她就那樣靜靜地躺著,像睡著了一般偷办。 火紅的嫁衣襯著肌膚如雪艰额。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天椒涯,我揣著相機與錄音柄沮,去河邊找鬼。 笑死,一個胖子當著我的面吹牛祖搓,可吹牛的內(nèi)容都是我干的狱意。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼拯欧,長吁一口氣:“原來是場噩夢啊……” “哼详囤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起镐作,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤藏姐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后该贾,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體羔杨,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年杨蛋,在試婚紗的時候發(fā)現(xiàn)自己被綠了兜材。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡逞力,死狀恐怖曙寡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情掏击,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布秩铆,位于F島的核電站砚亭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏殴玛。R本人自食惡果不足惜捅膘,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望滚粟。 院中可真熱鬧寻仗,春花似錦、人聲如沸凡壤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽亚侠。三九已至曹体,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間硝烂,已是汗流浹背箕别。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人串稀。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓除抛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親母截。 傳聞我的和親對象是個殘疾皇子到忽,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容