javascript
JS基础篇--函数声明与定义,作用域,函数声明与表达式的区别
Scoping & Hoisting
例:
var a = 1;function foo() {if (!a) {var a = 2;}alert(a); };foo();上面這段代碼在運(yùn)行時會產(chǎn)生什么結(jié)果?
盡管對于有經(jīng)驗(yàn)的程序員來說這只是小菜一碟,不過我還是順著初學(xué)者常見的思路做一番描述:
嗯,看起來無懈可擊的推理啊,但讓人驚訝的是:答案竟然是 2!為什么?
別著急,我會解釋給你聽。首先我要告訴你這不是什么錯誤,而是 JavaScript 語言解釋器的一個(非官方的)特性,某人(Ben Cherry)把這個特性叫做:Hoisting(目前尚未有標(biāo)準(zhǔn)的翻譯,比較常見的是提升)。
聲明與定義
為了理解 Hoisting,我們先來看一個簡單的情況:
var a = 1;你是否想過,上面這句代碼在運(yùn)行的時候到底發(fā)生了什么?
你是否知道,就這句代碼而言,“聲明變量 a” 和 “定義變量 a”這兩個說法哪一個才是正確的?
下例叫做 “聲明變量”:
var a;下例叫做 “定義變量”:
var a = 1;聲明:是指你聲稱某樣?xùn)|西的存在,比如一個變量或一個函數(shù);但你沒有說明這樣?xùn)|西到底是什么,僅僅是告訴解釋器這樣?xùn)|西存在而已;
定義:是指你指明了某樣?xùn)|西的具體實(shí)現(xiàn),比如一個變量的值是多少,一個函數(shù)的函數(shù)體是什么,確切的表達(dá)了這樣?xùn)|西的意義。
總結(jié)一下:
重點(diǎn)來了:當(dāng)你以為你只做了一件事情的時候(var a = 1),實(shí)際上解釋器把這件事情分解成了兩個步驟,一個是聲明(var a),另一個是定義(a = 1)。
這和 Hoisting 有何關(guān)系?
回到最開始的那個令人困惑的例子,我告訴你解釋器是如何分析你的代碼的:
var a; a = 1;function foo() {var a; // 關(guān)鍵在這里if (!a) {a = 2;}alert(a); // 此時的 a 并非函數(shù)體外的那個全局變量 }如代碼所示,在進(jìn)入函數(shù)體后解釋器聲明了新的變量 a,而無論 if 語句的條件如何,都將為新的變量 a 賦值為 2。你若不相信可以在函數(shù)體外面 alert(a),然后再執(zhí)行 foo() 對比一下結(jié)果就知道了。
Scoping(作用域)
有人可能會問了:“為什么不是在 if 語句內(nèi)聲明變量 a?”
因?yàn)?JavaScript 沒有塊級作用域(Block Scoping),只有函數(shù)作用域(Function Scoping),所以說不是看見一對花括號{} 就代表產(chǎn)生了新的作用域,和 C 不一樣!
當(dāng)解析器讀到 if 語句的時候,它發(fā)現(xiàn)此處有一個變量聲明和賦值,于是解析器會將其聲明提升至當(dāng)前作用域的頂部(這是默認(rèn)行為,并且無法更改),這個行為就叫做 Hoisting。
OK,大家都懂了,你懂了嗎……
懂了不代表就會用了,就拿最開始的例子來說,如果我就是想要 alert(a) 出那個 1 可咋整呢?
創(chuàng)建新的作用域
alert(a) 在執(zhí)行的時候,會去尋找變量 a 的位置,它從當(dāng)前作用域開始向上(或者說向外)一直查找到頂層作用域?yàn)橹?#xff0c;若是找不到就報(bào) undefined。
因?yàn)樵?alert(a) 的同級作用域里,我們再次聲明了本地變量 a,所以它報(bào) 2;所以我們可以把本地變量 a 的聲明向下(或者說向內(nèi))移動,這樣 alert(a) 就找不到它了。
記住:JavaScript 只有函數(shù)作用域!
var a = 1;function foo() {if (!a) {(function() { // 這是上一篇說到過的 IIFE,它會創(chuàng)建一個新的函數(shù)作用域var a = 2; // 并且該作用域在 foo() 的內(nèi)部,所以 alert 訪問不到}()); // 不過這個作用域可以訪問上層作用域哦,這就叫:“閉包”};alert(a); };foo();你或許在無數(shù)的 JavaScript 書籍和文章里讀到過:“請始終保持作用域內(nèi)所有變量的聲明放置在作用域的頂部”,現(xiàn)在你應(yīng)該明白為什么有此一說了吧?因?yàn)檫@樣可以避免 Hoisting 特性給你帶來的困擾(我不是很情愿這么說,因?yàn)?Hoisting 本身并沒有什么錯),也可以很明確的告訴所有閱讀代碼的人(包括你自己)在當(dāng)前作用域內(nèi)有哪些變量可以訪問。但是,變量聲明的提升并非 Hoisting 的全部。在 JavaScript 中,有四種方式可以讓命名進(jìn)入到作用域中(按優(yōu)先級):
另外,還記得之前我們討論過 聲明 和 定義 的區(qū)別吧?當(dāng)時我并沒有說為什么要理解這個區(qū)別,不過現(xiàn)在是時候了,記住:
Hosting 只提升了命名,沒有提升定義
這一點(diǎn)和我們接下來要講到的東西息息相關(guān),請看:
函數(shù)聲明與函數(shù)表達(dá)式的差別
先看兩個例子:
function test() {foo();function foo() {alert("我是會出現(xiàn)的啦……");} }test(); function test() {foo();var foo = function() {alert("我不會出現(xiàn)的哦……");} }test();同學(xué),在了解了 Scoping & Hoisting 之后,你知道怎么解釋這一切了吧?
在第一個例子里,函數(shù) foo 是一個聲明,既然是聲明就會被提升(我特意包裹了一個外層作用域,因?yàn)槿肿饔糜蛐枰愕南胂?#xff0c;不是那么直觀,但是道理是一樣的),所以在執(zhí)行 foo() 之前,作用域就知道函數(shù) foo 的存在了。這叫做函數(shù)聲明(Function Declaration),函數(shù)聲明會連通命名和函數(shù)體一起被提升至作用域頂部。
然而在第二個例子里,被提升的僅僅是變量名 foo,至于它的定義依然停留在原處。因此在執(zhí)行 foo() 之前,作用域只知道 foo 的命名,不知道它到底是什么,所以執(zhí)行會報(bào)錯(通常會是:undefined is not a function)。這叫做函數(shù)表達(dá)式(Function Expression),函數(shù)表達(dá)式只有命名會被提升,定義的函數(shù)體則不會。
尾記:Ben Cherry 的原文解釋的更加詳細(xì),只不過是英文而已。我這篇是借花獻(xiàn)佛,主要是更淺顯的解釋給初學(xué)者聽,若要看更多的示例,請移步原作,謝謝。
轉(zhuǎn)載地址:http://segmentfault.com/a/119...
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的JS基础篇--函数声明与定义,作用域,函数声明与表达式的区别的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怪物猎人世界怎么解禁
- 下一篇: 如何设置XMind思维导图线条