淺談JavaScript作用域

javascriptScope

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

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


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

在開始闡述之前,我們來看一段代碼,看看結(jié)果是什么?

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í)行代碼之前,有一個 預解析(預編譯) 的過程,這個過程會產(chǎn)生 變量提升函數(shù)提升 ,其實整個執(zhí)行過程可以分為兩部分,方便理解:

  1. 預解析
    這個過程,會把 關鍵字 var 、 function 、 參數(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ù)調(diào)用

此時這里有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ù)調(diào)用,這時的 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ù)調(diào)用 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 ,結(jié)果就會不一樣
這次,輸出的是:

  • 第一個 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ù)內(nèi)部使用。

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

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

  • 局部預解析

// 第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);} 的內(nèi)容,因為 具名函數(shù)表達式 的函數(shù)名只能在函數(shù)內(nèi)部使用,在外部無法訪問。
    最后的 bar() 輸出的是 4 ,這里才是輸出 function foo() {if(a > 3) return;console.log(++a);foo();} 里的內(nèi)容,而且,這個函數(shù)體內(nèi)也有自身的調(diào)用,結(jié)果 a 變量 +1 ,說明可以調(diào)用,其實,可以用 bar.name 輸出的就是 foo

所以,注意:

  • bar = function foo() , 不要用這種寫法
  • 不推薦使用 匿名函數(shù)表達式 ,有以下 ?? 幾個缺點
    • 在追蹤棧中沒函數(shù)名,調(diào)試困難
    • 如果需要引用自身,只能用非標準的 arguments.callee(ES5嚴格模式禁用)
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容