摘要:我們在面試時,總會碰到一些奇奇怪怪的關于作用域的面試題,其實弄清楚原理,萬變不離其宗,大部分的面試題都可以得姐。
我們在面試時,總會碰到一些奇奇怪怪的關于 作用域 的面試題,其實弄清楚原理,萬變不離其宗,大部分的面試題都可以得 ‘姐’。
所以,今天我們來談談 JavaScript 的 作用域(javascript scope) ,這是老生常談的話題,這里我們會從 作用域 開始,會延伸到 預解析規(guī)則(預編譯) 、 表達式 、 變量提升 、 函數提升 、 匿名函數表達式 、 具名函數表達式 等,徹底搞明白作用域這些事 ?
詳情,可查看我的博客 lishaoy.net
變量提升和函數提升在開始闡述之前,我們來看一段代碼,看看結果是什么?
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); } 函數體
第二個 alter(a) 彈出 function a(){ alter(4); } 函數體
第三個 alter(a) 彈出 1
第四個 alter(a) 彈出 3
第五個 alter(a) 彈出 3
最后一行報錯 a is not a function
下面來分析一下這段代碼:
其實在 javascript 開始執(zhí)行代碼之前,有一個 預解析(預編譯) 的過程,這個過程會產生 變量提升 和 函數提升 ,其實整個執(zhí)行過程可以分為兩部分,方便理解:
預解析
這個過程,會把 關鍵字 var 、 function 、 參數 提取出來
上面這段代碼 預解析 的過程是:
// 第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() 函數調用
此時這里有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() 函數調用,這時的 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 的作用域只用兩種,一個是全局的,一個是函數的,也稱為 全局作用域 和 局部作用域 ;局部作用域 可以訪問 全局作用域 。但是 全局作用域 不能訪問 局部作用域
同樣用 預解析(預編譯) 的方法來分析這段代碼
預解析(預編譯) 全局作用域
// 第1行,遇到 var 關鍵字,解析到全局的頭部 a = undefined // 第2行,遇到 function 關鍵字,解析到全局的頭部 fn1 = function fn1(){ alert(a); var a = 2; } // 第3行,沒有遇到關鍵字,不解析 // 第4行,沒有遇到關鍵字,不解析
開始執(zhí)行代碼
第1行,遇到表達式 a = 1, a 被賦值成 1
第6行,遇到函數調用 fn1() ,開始 預解析(預編譯) 局部
預解析(預編譯) 局部作用域
// 第3行,沒有遇到關鍵字,不解析 // 第4行,遇到 var 關鍵字,解析到局部 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 ,結果就會不一樣
這次,輸出的是:
第一個 alert 彈出 1
第二個 alert 彈出 2
因為在解析局部是沒有發(fā)現 var a ,如是在執(zhí)行時,就會去全局查找,找到了全局的 a = 1 ,所以 第一個 alert 彈出 1 ,而不是 undefined ,這個就是 作用域連
匿名函數表達式、具名函數表達式在來看看這段代碼?
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
以上代碼包含了 函數聲明 、 匿名函數表達式 、 具名函數表達式 ,匿名函數表達式 、 具名函數表達式 是把函數體賦值給一個變量,因此擁有和變量相同的特性 變量提升 ,而 具名函數表達式 的函數名只能在函數內部使用。
了解了這些,再來分析段代碼
全局預解析
a = undefined fn = function fn(){ ... }
執(zhí)行代碼
第1行,遇到表達式,把 a 的值改變成3
最后行,遇到函數調用,重新 預解析 局部
局部預解析
// 第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í)行局部代碼
第1個 foo() 輸出的是 1
第2個 foo() 輸出的是 1
第3個 foo() 輸出的是 2
第4個 foo() 輸出的是 2
注意 這個 foo() 輸出的是上面 foo = function foo() {console.log(2);} 的內容,因為 具名函數表達式 的函數名只能在函數內部使用,在外部無法訪問。
最后的 bar() 輸出的是 4
這里才是輸出 function foo() {if(a > 3) return;console.log(++a);foo();} 里的內容,而且,這個函數體內也有自身的調用,結果 a 變量 +1 ,說明可以調用,其實,可以用 bar.name 輸出的就是 foo
所以,注意:
bar = function foo() , 不要用這種寫法
不推薦使用 匿名函數表達式 ,有以下 ? 幾個缺點
在追蹤棧中沒函數名,調試困難
如果需要引用自身,只能用非標準的 arguments.callee(ES5嚴格模式禁用)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93951.html
摘要:理解作用域作用域負責收集并維護由所有聲明的變量組成的集合,等待引擎的查找。當遇到時,編譯器會詢問作用域是否存在變量。詞法作用域就是定義在詞法階段的作用域。但函數不是唯一的作用域單元。塊作用域屬于某個代碼塊通常指內部。 理解作用域 作用域負責收集并維護由所有聲明的變量組成的集合,等待引擎的查找。 var a = 2; console.log(a); // 2 console.log(b...
摘要:將作用域賦值給變量這里的作用域是,而不是將作用域賦值給一個變量閉包返回瀏覽器中內存泄漏問題大家都知道,閉包會使變量駐留在內存中,這也就導致了內存泄漏。 上一章我們講了匿名函數和閉包,這次我們來談談閉包中作用域this的問題。 大家都知道,this對象是在運行時基于函數的執(zhí)行環(huán)境綁定的,如果this在全局就是[object window],如果在對象內部就是指向這個對象,而閉包卻是在運行...
摘要:但是函數返回了內部函數,內部函數會隨時訪問變量所以垃圾回收機制是不會回收函數的內部作用域的,這就是閉包的含義。也就是函數在定義的詞法作用域以外的地方被調用,閉包使得函數可以繼續(xù)訪問定義時的詞法作用域。 初學JavaScript閉包時,閉包這個概念在我眼里及其的神秘,也不知道這個東西在講什么,尤其某些地方的閉包概念定義的非常抽象,屬于那種本來你可能明白這個概念,看了反而又把你給繞糊涂...
摘要:在內部,理所當然能訪問到局部變量,但當作為的返回值賦給外的全局變量時,神奇的事情發(fā)生了在全局作用域中訪問到了,這就是閉包。而閉包最神奇的地方就是能在一個函數外訪問函數中的局部變量,把這些變量用閉包的形式放在函數中便能避免污染。 一、閉包是什么? 《JavaScript高級程序設計》中寫道:閉包是指有權訪問另一個函數作用域中的變量的函數,如果用下定義的觀點看,這句話就是說閉包是函數,我...
閱讀 2566·2021-11-22 13:53
閱讀 4069·2021-09-28 09:47
閱讀 858·2021-09-22 15:33
閱讀 808·2020-12-03 17:17
閱讀 3315·2019-08-30 13:13
閱讀 2121·2019-08-29 16:09
閱讀 1176·2019-08-29 12:24
閱讀 2452·2019-08-28 18:14