什么是作用域?
作用域定義了變量的可見(jiàn)性或可訪問(wèn)性川慌。大白話來(lái)說(shuō)吃嘿,就是一個(gè)變量能不能被訪問(wèn)或引用,是由它的作用域決定的梦重。
在 JavaScript 中有三種作用域兑燥。
- 全局作用域
- 函數(shù)作用域(局部作用域)
- 塊作用域
let globalVariable = "我是全局作用域下的變量"
function func() {
let localVariable = "我是局部作用域下的變量"
}
if (true) {
let blockVariable = "我是塊作用域下的變量"
}
全局作用域 Global Scope
一個(gè)在最外層定義的變量便處于全局作用域,全局作用域內(nèi)的變量可以在程序的任意地方訪問(wèn)琴拧。
var globalVariable = "全局作用域變量"
function func() {
// 在函數(shù)內(nèi)訪問(wèn)全局作用域的變量
console.log("函數(shù)內(nèi)訪問(wèn):", globalVariable)
}
func()
console.log("函數(shù)外訪問(wèn):", globalVariable)
輸出:
函數(shù)內(nèi)訪問(wèn): 全局作用域變量
函數(shù)外訪問(wèn): 全局作用域變量
使用 var 關(guān)鍵字
在大括號(hào)內(nèi)(包括純粹的大括號(hào)降瞳、if、while蚓胸、for)定義的變量仍然`屬于全局作用域挣饥。
if (true) {
var globalVariable = "全局作用域變量"
}
console.log("外部訪問(wèn):", globalVariable)
輸出:
外部訪問(wèn): 全局作用域變量
函數(shù)作用域(局部作用域) Function Scope(Local Scope)
在函數(shù)內(nèi)定義的變量則屬于函數(shù)作用域,又稱局部作用域沛膳。局部作用域內(nèi)的變量只能在自身作用域內(nèi)被訪問(wèn)扔枫。
function func(params) {
var localVariable = "局部作用域變量"
console.log("函數(shù)內(nèi)訪問(wèn):", localVariable)
}
func()
console.log("外部訪問(wèn):", localVariable) // Uncaught ReferenceError: localVariable is not defined
輸出:
函數(shù)內(nèi)訪問(wèn): 局部作用域變量
Uncaught ReferenceError: localVariable is not defined
例子中,我們嘗試在外部訪問(wèn)局部作用域中定義的變量锹安,報(bào)了 變量未定義
的錯(cuò)誤短荐。
塊作用域 Block Scope
ES6 中引入了 let 與 const,與 var 不同的是叹哭。之前的例子中忍宋,在大括號(hào)(包括純粹的大括號(hào)、if风罩、while糠排、for)間用 var 定義的變量處在全局作用域。如果我們用 let 與 const 在大括號(hào)中定義超升,變量將處于塊作用域入宦。
塊作用域內(nèi)的變量只能在自身作用域內(nèi)被訪問(wèn)哺徊。
let 與 const 的不同點(diǎn)在于, const 定義的是一個(gè)常量云石,無(wú)法修改定義后的值唉工。
{
let blockVariable = "塊作用域變量"
console.log("塊內(nèi)訪問(wèn):", blockVariable)
}
console.log("外部訪問(wèn):", blockVariable) // Uncaught ReferenceError: blockVariable is not defined
輸出:
塊內(nèi)訪問(wèn): 塊作用域變量
Uncaught ReferenceError: blockVariable is not defined
例子中,我們嘗試在外部訪問(wèn)塊作用域中定義的變量汹忠,報(bào)了 變量未定義
的錯(cuò)誤淋硝。
什么是作用域鏈? Scope Chain
當(dāng)一個(gè)變量在當(dāng)前作用域無(wú)法找到時(shí)宽菜,便會(huì)嘗試尋找其外層的作用域谣膳,如果還找不到,再繼續(xù)往外尋找(只會(huì)往外尋找铅乡,不會(huì)尋找兄弟作用域继谚,更不會(huì)往內(nèi)尋找)。
這種如同鏈條一樣的尋找規(guī)則便被稱為作用域鏈阵幸。
let variable1 = "我是變量 1花履,外部的"
let variable2 = "我是變量 2"
function func() {
let variable1 = "我是變量 1,內(nèi)部的"
{
let variable3 = "我是變量 3"
}
{
// 往外尋找挚赊,在上一層函數(shù)內(nèi)找到了
console.log(variable1)
// 往外尋找诡壁,直到全局作用域
console.log(variable2)
// 找不到,報(bào)錯(cuò)
console.log(variable3) // Uncaught ReferenceError: variable3 is not defined
}
}
func()
輸出:
我是變量 1荠割,內(nèi)部的
我是變量 2
Uncaught ReferenceError: variable3 is not defined
在例子中妹卿,打印 variable1 變量時(shí),由于在上層作用域也就是函數(shù)中就找到了 variable1 變量蔑鹦,便停止了尋找夺克,不會(huì)找到全局作用域下的 variable1 變量。
尋找 variable2 變量時(shí)嚎朽,在上層作用域中未找到铺纽,便一直找到了上上層作用域,也就是全局作用域下的 variable2 變量哟忍。
尋找 variable3 變量時(shí)室囊,由于 variable3 變量被定義在兄弟作用域中,并不會(huì)被尋找到魁索,因?yàn)樽饔糜蜴湹囊?guī)則是只會(huì)往上層作用域?qū)ふ遥⒉粫?huì)尋找兄弟作用域盼铁。因此這里報(bào)了變量未定義的錯(cuò)誤粗蔚。
函數(shù)的作用域是它定義時(shí)的作用域,而不是調(diào)用時(shí)
function func() {
let variable = "我是 func 內(nèi)的變量"
function func2() {
console.log(variable)
}
return func2
}
{
let variable = "我是大括號(hào)內(nèi)的變量"
let func2 = func()
func2()
}
輸出:
我是 func 內(nèi)的變量
在例子中饶火,執(zhí)行 func2 函數(shù)時(shí)往上尋找的作用域是在 func2 定義時(shí)的作用域鹏控,而不是調(diào)用時(shí)的作用域致扯。
如果找不到變量會(huì)怎樣?
如果一個(gè)變量直到全局作用域也找不到便會(huì)執(zhí)行以下操作当辐。
- 非嚴(yán)格模式:隱式聲明全局變量
- 嚴(yán)格模式:報(bào)錯(cuò)
非嚴(yán)格模式
非嚴(yán)格模式下抖僵,嘗試賦值一個(gè)變量時(shí),如果找不到則會(huì)隱性聲明成全局域的變量缘揪。
{
variable = "我是一個(gè)隱性聲明的變量"
}
console.log(variable)
輸出:
我是一個(gè)隱性聲明的變量
以上的例子中耍群,variable 由于未聲明,因此被隱性聲明成了全局作用域下的變量找筝,這使得在最外部也能打印出 variable 變量的值蹈垢。
非嚴(yán)格模式下,嘗試使用一個(gè)變量的值時(shí)袖裕,如果找不到同樣會(huì)報(bào)錯(cuò)曹抬。
{
console.log(variable) // Uncaught ReferenceError: variable is not defined
}
輸出:
Uncaught ReferenceError: variable is not defined
以上的例子中,由于使用 variable 時(shí)未定義急鳄,因此報(bào)了未定義的錯(cuò)誤谤民。
嚴(yán)格模式
加入 "use strict"
表明是嚴(yán)格模式,嚴(yán)格模式下不論賦值還是使用未事先聲明的變量都會(huì)報(bào)錯(cuò)疾宏。
"use strict"
{
variable = "我是一個(gè)隱性聲明的變量" // Uncaught ReferenceError: variable is not defined
}
輸出:
Uncaught ReferenceError: variable is not defined
作用域的好處张足?
-
防止命名沖突
:你寫(xiě)了一萬(wàn)行的代碼文件,如果沒(méi)有作用域灾锯,你要給每個(gè)變量取獨(dú)一無(wú)二的名字兢榨,屁股想想也知道是種折磨。 -
安全性
: 變量不會(huì)被外部訪問(wèn)顺饮,保證了變量值不會(huì)被隨意修改吵聪。你定義在函數(shù)內(nèi)的變量,如果能在幾千行之后不小心被修改兼雄,腳趾頭想想也知道是種折磨吟逝。 -
更高級(jí)的語(yǔ)法
:封裝、面向?qū)ο蟮鹊膶?shí)現(xiàn)離不開(kāi)對(duì)變量的隔離赦肋,這是依靠作用域所達(dá)到的块攒。
說(shuō)人話!
寫(xiě)代碼時(shí)不用區(qū)分它什么全局使用域佃乘、局部作用域囱井、塊作用域啥的概念。只用記得大括號(hào)就是一個(gè)作用域趣避,尋找變量永遠(yuǎn)是從內(nèi)往外找∨优唬現(xiàn)在我們的編輯器基本都有縮進(jìn)格式化, 從當(dāng)前代碼塊的位置一層一層往左,就是它所能引用到的所有變量住练。
打個(gè)比方地啰,就像我們每個(gè)家庭就是一個(gè)作用域,當(dāng)我們需要一筆手術(shù)費(fèi)掏不出錢的時(shí)候讲逛,肯定是先在家里找亏吝,問(wèn)問(wèn)父母兄弟姐妹啥的,不會(huì)去求助其他陌生的家庭盏混。還沒(méi)有的話就往外到熟人關(guān)系這個(gè)作用域里問(wèn)問(wèn)蔚鸥。還不行就向街道居委會(huì)求助。居委會(huì)也沒(méi)辦法再向國(guó)家求援括饶。從最親近的關(guān)系找起株茶,一層一層圈子往外,這就是作用域與作用域鏈图焰。
最后強(qiáng)烈建議大家使用 let 命名變量启盛,放棄 var!