摘要:我們在面試時,總會碰到一些奇奇怪怪的關(guān)于作用域的面試題,其實弄清楚原理,萬變不離其宗,大部分的面試題都可以得姐。
我們在面試時,總會碰到一些奇奇怪怪的關(guān)于 作用域 的面試題,其實弄清楚原理,萬變不離其宗,大部分的面試題都可以得 ‘姐’。
所以,今天我們來談談 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í)行過程可以分為兩部分,方便理解:
預解析
這個過程,會把 關(guān)鍵字 var 、 function 、 參數(shù) 提取出來
上面這段代碼 預解析 的過程是:
// 第1行,沒有關(guān)鍵字 , 不解析 // 第2行,遇到 function 關(guān)鍵字,解析到全局的頭部 a = function a(){ alter(2); } // 第3行,沒有關(guān)鍵字 , 不解析 // 第4行,遇到關(guān)鍵字 var , 解析到全局的頭部 a = undefined // 第5行,沒有關(guān)鍵字 , 不解析 // 第6行,遇到關(guān)鍵字 var , 解析到全局的頭部 a = undefined // 第8行,遇到 function 關(guān)鍵字,解析到全局的頭部 a = function a(){ alter(4); } // 第9行,沒有關(guān)鍵字 , 不解析 // 第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();
執(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行,遇到 var 關(guān)鍵字,解析到全局的頭部 a = undefined // 第2行,遇到 function 關(guān)鍵字,解析到全局的頭部 fn1 = function fn1(){ alert(a); var a = 2; } // 第3行,沒有遇到關(guān)鍵字,不解析 // 第4行,沒有遇到關(guān)鍵字,不解析
開始執(zhí)行代碼
第1行,遇到表達式 a = 1, a 被賦值成 1
第6行,遇到函數(shù)調(diào)用 fn1() ,開始 預解析(預編譯) 局部
預解析(預編譯) 局部作用域
// 第3行,沒有遇到關(guān)鍵字,不解析 // 第4行,遇到 var 關(guān)鍵字,解析到局部 a = undefined
開始執(zhí)行 局部 代碼
第3行,彈出 undefined
第4行,遇到表達式,把局部 a 改成 2
局部執(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
最后行,遇到函數(shù)調(diào)用,重新 預解析 局部
局部預解析
// 第4行,遇到 function 關(guān)鍵字,解析到局部的頭部 foo = function(){ console.log(1); } // 第8行,遇到 var 關(guān)鍵字,解析到局部的頭部 foo = undefined // 第12行,遇到 var 關(guān)鍵字,解析到局部的頭部 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í)行局部代碼
第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嚴格模式禁用)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/93951.html
摘要:理解作用域作用域負責收集并維護由所有聲明的變量組成的集合,等待引擎的查找。當遇到時,編譯器會詢問作用域是否存在變量。詞法作用域就是定義在詞法階段的作用域。但函數(shù)不是唯一的作用域單元。塊作用域?qū)儆谀硞€代碼塊通常指內(nèi)部。 理解作用域 作用域負責收集并維護由所有聲明的變量組成的集合,等待引擎的查找。 var a = 2; console.log(a); // 2 console.log(b...
摘要:將作用域賦值給變量這里的作用域是,而不是將作用域賦值給一個變量閉包返回瀏覽器中內(nèi)存泄漏問題大家都知道,閉包會使變量駐留在內(nèi)存中,這也就導致了內(nèi)存泄漏。 上一章我們講了匿名函數(shù)和閉包,這次我們來談談閉包中作用域this的問題。 大家都知道,this對象是在運行時基于函數(shù)的執(zhí)行環(huán)境綁定的,如果this在全局就是[object window],如果在對象內(nèi)部就是指向這個對象,而閉包卻是在運行...
摘要:但是函數(shù)返回了內(nèi)部函數(shù),內(nèi)部函數(shù)會隨時訪問變量所以垃圾回收機制是不會回收函數(shù)的內(nèi)部作用域的,這就是閉包的含義。也就是函數(shù)在定義的詞法作用域以外的地方被調(diào)用,閉包使得函數(shù)可以繼續(xù)訪問定義時的詞法作用域。 初學JavaScript閉包時,閉包這個概念在我眼里及其的神秘,也不知道這個東西在講什么,尤其某些地方的閉包概念定義的非常抽象,屬于那種本來你可能明白這個概念,看了反而又把你給繞糊涂...
摘要:在內(nèi)部,理所當然能訪問到局部變量,但當作為的返回值賦給外的全局變量時,神奇的事情發(fā)生了在全局作用域中訪問到了,這就是閉包。而閉包最神奇的地方就是能在一個函數(shù)外訪問函數(shù)中的局部變量,把這些變量用閉包的形式放在函數(shù)中便能避免污染。 一、閉包是什么? 《JavaScript高級程序設(shè)計》中寫道:閉包是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù),如果用下定義的觀點看,這句話就是說閉包是函數(shù),我...
閱讀 2581·2021-11-22 13:53
閱讀 4091·2021-09-28 09:47
閱讀 877·2021-09-22 15:33
閱讀 824·2020-12-03 17:17
閱讀 3322·2019-08-30 13:13
閱讀 2129·2019-08-29 16:09
閱讀 1184·2019-08-29 12:24
閱讀 2456·2019-08-28 18:14