這幾次都是些的基礎文章,可能好多人會説基礎不太重要,做前端這麼久,也沒用到多少基礎 <font color="gray">(首先恭喜你,已經進提前進入了被優化名單)</font>。
下面我們來詳細解答一下基礎是什麼。
let 知識, 基礎
if (知識 === '🏡') {
基礎 = '地基'
}
if (知識 === '🌲') {
基礎 = '樹根'
}
if (知識 === '天空') {
基礎 = '階梯'
console.log('基礎 makes you up, up, up…… 直到你碰頭')
}
………………
怎麼樣?認識到基礎的重要性了吧,如果沒有了基礎,代碼就好像無根之木,空中樓閣,雖然賞心悦目,但是總是短暫的。
直到你真正領悟了底層是怎麼運作的,你才能夠真正做到 他強任他強,清風拂山崗;他橫自他橫,明月照大江。
扯遠了,收~
回到我們的正題。今天帶大家瞭解以下 JavaScript 中的作用域問題。請拭拭拭拭拭拭目以待。(自己 get 重點)。
先從最基礎的開始講起。
1.什麼是作用域
作用域是什麼這個問題,好多人都回答不好。請注意:通常來説,作用域就是限制一個變量在程序中的使用範圍。
搜嘎,突然有一種 “同行十二年,不知木蘭是女郎” 的趕腳。
1.1 全局和局部
瞭解了作用域的名字來由之後。我們來認識一下它。
在 JavaScript 中作用域的邊界是以函數劃分。有 全局 和 局部 作用域之分。
- 全局作用域:聲明在
<script></script>標籤內的變量或者不使用var聲明的變量在整個程序中都是可用的,所以叫全局作用域。 - 局部作用域:聲明在函數體內的變量,在整個函數執行環境和其子函數內都是可用的,但是在函數外訪問不到,所以叫局部作用域
小栗子🌰同學上場:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>
<script>
var global = "我是全局變量,全局都能看到我";
global = "我也是全局變量,全局都能看到我";
function getName() {
var name = "我是局部變量,只能在getName函數內才能找到我"
}
</script>
1.2 預解析和變量提升
看到標題的同學是不是會稍有一愣。預解析是什麼玩意兒?變量提升又是啥?(知道答案的同學請配合這個無聊的作者一下,假裝一愣神。)
咳咳~不忙,等本大神(經)來解釋一下。
預解析是在程序執行之前,會進行一遍預檢。查找當前作用域內由 function 和 var 。並且每次更換作用域都會在此作用域中執行預解析
變量提升是指,在查找到由 function 和 var 後,首先在當前作用域的頂端定義好並賦給默認值。var的默認值為 undefined, function的默認值為函數本身。
注:
像 var getName = function() {} 這種代碼會被當做變量定義,而不會當做函數定義。
“讓一下,讓一下……” 遠處小栗子攜大量代碼滾滾而來~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>
<script>
console.log(global) // undefined
console.log(getName) // function getName(){}
var global = 1;
function getName () {}
</script>
誒?怎麼這樣?我不是定義了global嗎?怎麼會輸出 undefined 難道是javascript出現了bug?
不要多想,預解析和變量提升的過程中,並不會將變量賦值,而只是定義,等真正執行的時候才會賦值。修改一下代碼,在global變量下方再次打印。
var global = 1;
console.log(global); // 1
解釋:當執行到打印函數的時候,global已經被賦值為 1。此時已經在執行代碼的階段,而不是在預解析階段。
先練練手:
猜想一下,下方程序如何輸出。
console.log(a) // 1
var a = 1;
console.log(a) // 2
getName()
function getName () {
console.log(a) // 3
console.log(b) // 4
a = 2;
console.log(a) // 5
var b = 3;
console.log(b) // 6
function b(){}
}
此時你的答案是什麼呢?
針對上方代碼,通過圖解的方式看下預解析的執行過程。
ps:(上圖畫的太複雜,看完需要耐心)
1.3 var和function的優先級
細心地同學可能會發現一個問題,上面的代碼,在getName函數中,我既定義了 b變量,也定義了 b函數。為什麼在 console.log(b) // 4 的時候會輸出 undefined?
因為在預解析的過程中,會先查找 function 然後再查找 var 所以,function 會被 var 覆蓋。這裏我們會理解為 在預解析過程中,function的優先級高於var。高優先級的會被低優先級的覆蓋 (是不是很繞?沒關係,多想想,加深下理解)
2.作用域鏈
作用域鏈的執行我們在之前就講過了,有沒有人注意到?有沒有?
好吧,沒有人回答我,看來是沒人注意到了。
就在練手代碼中,getName()函數內,向上查找 a 變量的過程,那個就是作用域鏈的查找過程。
閒言少敍,上高清大圖:
<font color="red">特別注意:</font>作用域只能從下向上查找,不可逆向。(從函數外不能訪問函數內的變量)
3. 從全局獲取函數內部的變量
上面講到,在函數外訪問函數內的變量是訪問不到的,如果我堅持要訪問呢?(一般情況下,這種鑽牛角尖的人都容易捱打)。
好吧,既然你要訪問,那也是有方法的,我們可以在函數內將變量返回出來,這樣就可以訪問到函數內的變量了。
多説無益,還是代碼最實在:
console.log(getA()); // 通過這種方式,我們就可以訪問到 a 變量。
function getA() {
var a = 1;
return a;
}
發散一下思維,你還知道其他方式嗎?
4. 塊級作用域
在ES6到來的時候,javascript迎來一個全新的概念,-- 塊級作用域。顧名思義,塊級作用域可以讓變量只在一塊代碼內生效。
舉個栗子:
{
var a = 1
}
console.log(a)
上述栗子中的代碼會正常輸出,但是下方的代碼會拋出 a 變量未定義的錯誤a is not defined。
{
let a = 1;
}
console.log(a)
也就是説a變量只在花括號內生效,在花括號外是訪問不到的。
可以聲明塊級作用域的方式有兩種。let 和 const 。
目前為止,我們看到了三個定義變量的方式,接下來,讓我們瞅瞅他們之間的不同。
4.1 var、let、const的異同
同: 都可以聲明變量。
異:
var 存在局部作用域,可變量提升,聲明的值可更改。
console.log(a)
var a = 1;
a = 2;
// 上述操作都可以
let 存在塊級作用域,不可變量提升,聲明的值可修改。(只可以先聲明變量,然後再使用)
console.log(a) // 會報錯, a is not defined
let a = 1;
a = 2;
const 存在塊級作用域,不可變量提升,聲明的值本身不可修改(只可以先聲明變量,然後再使用)
const a = 1;
a = 2; // 會報錯,a不可修改
// 下述情況可運行
const a = []
a[0] = 1;
注意:
- const聲明的叫做常量,不可以修改其本身,但如果聲明的是複雜類型的對象,對象裏的值是可修改的。
這裏你會發現一個問題,三者的功能是逐步增強的。
4.2 TDZ介紹(暫時性死區)為什麼let和const不能變量提升。
使用let和const聲明的變量,在預解析的時候會將變量放入到一個暫時不可訪問的區間中,此時訪問變量會提示未定義錯誤,在給變量賦值後,將變量放入到正常的執行環境中。使變量可以正常訪問。
console.log(a) // 此時的a在TDZ中
let a = 1; // 將a從TDZ中移出來
console.log(a) // 此時可以正常訪問a變量
相信你現在已經充滿能量,打開你的代碼,學會分析每一步的執行順序吧。