淺談JavaScript作用域

javascriptScope

今天我們來談談 JavaScript作用域(javascript scope) ,這是老生常談的話題晒奕,這里我們會從 作用域 開始妄帘,會延伸到 預解析規(guī)則(預編譯)表達式 兄裂、 變量提升 句旱、 函數(shù)提升匿名函數(shù)表達式 晰奖、 具名函數(shù)表達式 等谈撒,徹底搞明白作用域這些事 ??

詳情,可查閱我的博客 lishaoy.net


變量提升和函數(shù)提升

在開始闡述之前匾南,我們來看一段代碼啃匿,看看結果是什么?

alert(a);
function a(){ alter(2); }
alert(a);
var a = 1
alert(a);
var a = 3;
alert(a);
function a(){ alter(4); }
alert(a);
a();

這里先揭曉答案:

  • 第一個 alert(a) 彈出 function a(){ alter(4); } 函數(shù)體
  • 第二個 alter(a) 彈出 function a(){ alter(4); } 函數(shù)體
  • 第三個 alter(a) 彈出 1
  • 第四個 alter(a) 彈出 3
  • 第五個 alter(a) 彈出 3
  • 最后一行報錯 a is not a function

下面來分析一下這段代碼:
其實在 javascript 開始執(zhí)行代碼之前蛆楞,有一個 預解析(預編譯) 的過程溯乒,這個過程會產生 變量提升函數(shù)提升 ,其實整個執(zhí)行過程可以分為兩部分豹爹,方便理解:

  1. 預解析
    這個過程裆悄,會把 關鍵字 varfunction 臂聋、 參數(shù) 提取出來

上面這段代碼 預解析 的過程是:

// 第1行光稼,沒有關鍵字 , 不解析
// 第2行孩等,遇到 function 關鍵字艾君,解析到全局的頭部
a = function a(){ alter(2); }
// 第3行,沒有關鍵字 肄方, 不解析
// 第4行冰垄,遇到關鍵字 var , 解析到全局的頭部
a = undefined
// 第5行扒秸,沒有關鍵字 播演, 不解析
// 第6行,遇到關鍵字 var 伴奥, 解析到全局的頭部
a = undefined
// 第8行写烤,遇到 function 關鍵字,解析到全局的頭部
a = function a(){ alter(4); }
// 第9行拾徙,沒有關鍵字 洲炊, 不解析
// 第10行,a() 函數(shù)調用

此時這里有4個同名變量 a ,依循規(guī)則是:function 優(yōu)先與 var, 同名的后面覆蓋前面的
因此暂衡,a = function a(){ alter(2); } 替換掉下面的2個 a = undefined 询微,a = function a(){ alter(4); } 又替換掉 a = function a(){ alter(2); } ,最終只剩下 a = function a(){ alter(4); }

預解析(預編譯) 后的代碼樣子是這樣的

var a = function a(){ alter(4); }
alert(a);
alert(a);
a = 1
alert(a);
a = 3;
alert(a);
alert(a);
a();
  1. 執(zhí)行代碼,就是執(zhí)行的這段代碼狂巢,依次從上到下執(zhí)行撑毛,最后的 a() 函數(shù)調用,這時的 a 已被 表達式 賦值成 3 唧领,而報錯 a is not a function

全局作用域和局部作用域

再看這段代碼

var a = 1;
function fn1(){
    alert(a);
    var a = 2;
}
fn1();
alert(a);

這里先揭曉答案:

  • 第一個 alert(a) 彈出 undefined
  • 第二個 alert(a) 彈出 1

JavaScript 的作用域只用兩種藻雌,一個是全局的,一個是函數(shù)的斩个,也稱為 全局作用域局部作用域 胯杭;局部作用域 可以訪問 全局作用域 。但是 全局作用域 不能訪問 局部作用域

同樣用 預解析(預編譯) 的方法來分析這段代碼

  1. 預解析(預編譯) 全局作用域
// 第1行受啥,遇到 var 關鍵字做个,解析到全局的頭部
a = undefined
// 第2行,遇到 function 關鍵字滚局,解析到全局的頭部
fn1 = function fn1(){
    alert(a);
    var a = 2;
}
// 第3行居暖,沒有遇到關鍵字,不解析
// 第4行藤肢,沒有遇到關鍵字膝但,不解析
  1. 開始執(zhí)行代碼

第1行,遇到表達式 a = 1, a 被賦值成 1 </br>
第6行谤草,遇到函數(shù)調用 fn1() ,開始 預解析(預編譯) 局部

  1. 預解析(預編譯) 局部作用域
// 第3行,沒有遇到關鍵字莺奸,不解析
// 第4行丑孩,遇到 var 關鍵字,解析到局部
a = undefined
  1. 開始執(zhí)行 局部 代碼

第3行灭贷,彈出 undefined
第4行温学,遇到表達式,把局部 a 改成 2

  1. 局部執(zhí)行完成甚疟,繼續(xù)執(zhí)行全局

第7行仗岖,彈出 1 ,因為全局和局部是兩個獨立的作用域

作用域鏈

如果览妖,把上面??代碼轧拄,稍作修改

var a = 1;
function fn1(){
    alert(a);
    a = 2;
}
fn1();
alert(a);

去掉了 function 里的 var ,結果就會不一樣
這次讽膏,輸出的是:

  • 第一個 alert 彈出 1
  • 第二個 alert 彈出 2
    因為在解析局部是沒有發(fā)現(xiàn) var a 檩电,如是在執(zhí)行時,就會去全局查找,找到了全局的 a = 1 俐末,所以 第一個 alert 彈出 1 料按,而不是 undefined ,這個就是 作用域連

匿名函數(shù)表達式、具名函數(shù)表達式

在來看看這段代碼??

var a = 3;
function fn() {
    foo();
    function foo() {
        console.log(1);
    }
    foo();
    var foo = function() {
        console.log(2);
    };
    foo();
    var bar = function foo() {
        if(a > 3) return;
        console.log(++a);
        foo();
    };
    foo();
    bar();
}
fn();

先揭曉答案:

  • 第1個 foo() 輸出的是 1
  • 第2個 foo() 輸出的是 1
  • 第3個 foo() 輸出的是 2
  • 第4個 foo() 輸出的是 2
  • 最后的 bar() 輸出的是 4

以上代碼包含了 函數(shù)聲明 卓箫、 匿名函數(shù)表達式 载矿、 具名函數(shù)表達式匿名函數(shù)表達式 烹卒、 具名函數(shù)表達式 是把函數(shù)體賦值給一個變量闷盔,因此擁有和變量相同的特性 變量提升 ,而 具名函數(shù)表達式 的函數(shù)名只能在函數(shù)內部使用甫题。

了解了這些馁筐,再來分析段代碼

  • 全局預解析
a = undefined
fn = function fn(){
    ...
}
  • 執(zhí)行代碼
    第1行,遇到表達式,把 a 的值改變成3 </br>
    最后行坠非,遇到函數(shù)調用敏沉,重新 預解析 局部

  • 局部預解析

// 第4行,遇到 function 關鍵字炎码,解析到局部的頭部
foo = function(){
    console.log(1);
}
// 第8行盟迟,遇到 var 關鍵字,解析到局部的頭部
foo = undefined
// 第12行潦闲,遇到 var 關鍵字攒菠,解析到局部的頭部
bar = undefined

由于有兩個同名變量 foo ,遵循 function 優(yōu)先 var 因此歉闰, foo = undefined 被干掉

局部預解析 完之后的代碼應該是這個樣子??

var a = 3
function fn() {
    var foo = function foo() {
        console.log(1);
    }
    var bar;
    foo();
    foo();
    foo = function foo() {
        console.log(2);
    };
    foo();
    bar = function foo() {
        if(a > 3) return;
        console.log(++a);
        foo();
    };
    foo();
    bar();
}
fn();
  • 執(zhí)行局部代碼 </br>
    第1個 foo() 輸出的是 1
    第2個 foo() 輸出的是 1
    第3個 foo() 輸出的是 2
    第4個 foo() 輸出的是 2 辖众,注意這個 foo() 輸出的是上面 foo = function foo() {console.log(2);} 的內容,因為 具名函數(shù)表達式 的函數(shù)名只能在函數(shù)內部使用和敬,在外部無法訪問凹炸。
    最后的 bar() 輸出的是 4 ,這里才是輸出 function foo() {if(a > 3) return;console.log(++a);foo();} 里的內容昼弟,而且啤它,這個函數(shù)體內也有自身的調用,結果 a 變量 +1 舱痘,說明可以調用变骡,其實,可以用 bar.name 輸出的就是 foo

所以芭逝,注意:

  • bar = function foo() , 不要用這種寫法
  • 不推薦使用 匿名函數(shù)表達式 塌碌,有以下 ?? 幾個缺點
    • 在追蹤棧中沒函數(shù)名,調試困難
    • 如果需要引用自身铝耻,只能用非標準的 arguments.callee(ES5嚴格模式禁用)
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末誊爹,一起剝皮案震驚了整個濱河市蹬刷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌频丘,老刑警劉巖办成,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異搂漠,居然都是意外死亡迂卢,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門桐汤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來而克,“玉大人,你說我怎么就攤上這事怔毛≡逼迹” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵拣度,是天一觀的道長碎绎。 經(jīng)常有香客問我,道長抗果,這世上最難降的妖魔是什么筋帖? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮冤馏,結果婚禮上日麸,老公的妹妹穿的比我還像新娘。我一直安慰自己逮光,他們只是感情好代箭,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涕刚,像睡著了一般梢卸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上副女,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音蚣旱,去河邊找鬼碑幅。 笑死,一個胖子當著我的面吹牛塞绿,可吹牛的內容都是我干的沟涨。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼异吻,長吁一口氣:“原來是場噩夢啊……” “哼裹赴!你這毒婦竟也來了喜庞?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤棋返,失蹤者是張志新(化名)和其女友劉穎延都,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體睛竣,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡晰房,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了射沟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片殊者。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖验夯,靈堂內的尸體忽然破棺而出猖吴,到底是詐尸還是另有隱情,我是刑警寧澤挥转,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布海蔽,位于F島的核電站,受9級特大地震影響扁位,放射性物質發(fā)生泄漏准潭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一域仇、第九天 我趴在偏房一處隱蔽的房頂上張望刑然。 院中可真熱鬧,春花似錦暇务、人聲如沸泼掠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽择镇。三九已至,卻和暖如春括改,著一層夾襖步出監(jiān)牢的瞬間腻豌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工嘱能, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吝梅,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓惹骂,卻偏偏與公主長得像苏携,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子对粪,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容

  • Lua 5.1 參考手冊 by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 13,743評論 0 38
  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line)右冻,也就是一...
    悟名先生閱讀 4,118評論 0 13
  • JavaScript中的函數(shù)擁有自己的執(zhí)行環(huán)境装蓬,每一個執(zhí)行環(huán)境中都有一個與之關聯(lián)的變量對象,環(huán)境中所有定義的變量和...
    饑人谷_Jackie閱讀 159評論 0 0
  • 首先履羞,JavaScript確實是一門編譯型語言,與C等典型編譯型語言相比屡久,區(qū)別在于JavaScript的編譯過程(...
    環(huán)零弦閱讀 5,558評論 0 18
  • 可憐的人們啊 為何一生都在找尋 那座叫作世外的夢境 走走停停 像夜空中漫步的星星 彼此之間眨眼睛 仿佛距離很近 卻...
    fotanade閱讀 153評論 0 0