什麼是作用域:作用域是可通路變量的集合。
- 在 JavaScript 中, 對象和函數同樣也是變量。
- 在 JavaScript 中, 作用域為可通路變量,對象,函數的集合。
- JavaScript 函數作用域: 作用域在函數内修改。
全局作用域:
全局作用域貫穿整個javascript文檔,在所有函數聲明或者大括号之外定義的變量,都在全局作用域裡。一旦你聲明了一個全局變量,那麼你在任何地方都可以使用它,包括函數内部。事實上,JavaScript預設擁有一個全局對象window,聲明一個全局變量,就是為window對象的同名屬性指派。如下面代碼所示。 注:若變量在函數内部沒有聲明(未使用var關鍵字),該變量為全局變量。全局變量在頁面關閉後銷毀。
function zxxFn () {
zxxs = 2
}
var zxx = 1
console.log(window.zxx) // 1
// console.log(zxxs) 未定義
zxxFn() // 執行後zxxs成為全局變量
console.log(zxxs) // 2
局部作用域(函數作用域和塊級作用域)
在JavaScript中,任何定義在函數體内的變量或者函數都将處于函數作用域中,這些變量也無法被在函數外部使用。(當然使用閉包也可以通路)
- 變量在函數内聲明,變量為局部作用域。
- 局部變量:隻能在函數内部通路。
- 局部變量隻作用于函數内,是以不同的函數可以使用相同名稱的變量。
- 局部變量在函數開始執行時建立,函數執行完後局部變量會自動銷毀。
局部作用域外部無法通路示例:
function zxxFn () {
var zxx = 'zxx is a great girl'
console.log(zxx) // zxx is a great girl
}
zxxFn() // zxx is a great girl
console.log(zxx) // Uncaught ReferenceError: zxx is not defined 函數内部定義的局部變量外部無法通路
當函數體内局部變量和函數體外的變量重名的話,内部局部變量将會遮蓋同名的全局變量。(部分知識涉及變量提升)
var zxx = 'zxx is a good girl'
function zxxFn () {
console.log(zxx)
var zxx = 'zxx is a great girl'
console.log(zxx)
zxx = 'very good'
console.log(zxx)
}
console.log(zxx)
zxxFn()
console.log(zxx)
// 依次輸出 1. zxx is a good girl 2. undefined 3. zxx is a great girl 4. very good 5.zxx is a good girl
var zxx = 'zxx is a girl'
var zxxs = 'yes'
function zxxFn () {
console.log(zxx) // undefined
console.log(zxxs) // yes
zxx = 'very good'
zxxs = 'not'
console.log(zxx) // very good
console.log(zxxs) // not
var zxx = 'zxx is a great girl'
console.log(zxx) // zxx is a great girl
console.log(zxxs) // not
}
console.log(zxx) // zxx is a girl
console.log(zxxs) // yes
zxxFn()
console.log(zxx) // zxx is a girl
console.log(zxxs) // not
作用域鍊:
周遊嵌套作用域鍊的規則很簡單:引擎從目前的執行作用域開始查找變量,如果找不到, 就向上一級繼續查找。當抵達最外層的全局作用域時,無論找到還是沒找到,查找過程都會停止。
簡單來講,局部作用域(如函數作用域)可以通路到全局作用域中的變量和方法,而全局作用域不能通路局部作用域的變量和方法。
出現函數嵌套函數,則就會出現作用域鍊。
zxxAge = 'zxx is 18 years old'
function zxxFn () {
console.log(zxxAge)
var zxxAge = 'zxx is 8 years old'
var zxxSub = 'zxx is 25 years old'
console.log(zxxAge) // zxx is 8 years old
// console.log(zxxSub) // zxxSub is not defined
function zxxFnSub () {
var zxxSub = 'zxx is 18 years old'
console.log(zxxSub) // zxx is 18 years old
console.log(zxxAge) // zxxSub is not defined
}
zxxFnSub()
}
zxxFn()
如圖:在執行變量列印的時候,會先在第一作用域尋找該變量是否存在,存在則使用該作用域的變量,不存在則向上尋找第二作用域,以此類推一直找到全局作用域。如果找不到則會抛出異常。
例:
function zxxFn (a) {
console.log(a + b)
}
var b = 2
zxxFn(2) // 4
對變量b查詢無法在函數zxxFn内部,但可以在上一級作用域中完成(全局作用域)
注:代碼中隐式的 a=2 操作可能很容易被你忽略掉。
比較下列兩個代碼
function zxxFn (a) {
console.log(a + b) // b變量在任何相關的作用域都找不到,引擎就會抛出ReferenceError異常
b = a
}
zxxFn(2)
=================
function zxxFn (a) {
b = a
console.log(a + b)
}
zxxFn(2) // 4
函數被調用之前作用域鍊已經存在:
zxxAge = 'zxx is 18 years old'
function zxxFn () {
console.log(zxxAge)
var zxxAge = 'zxx is 8 years old'
var zxxSub = 'zxx is 25 years old'
console.log(zxxAge) // zxx is 8 years old
// console.log(zxxSub) // zxxSub is not defined
function zxxFnSub () {
console.log(zxxSub) // zxx is 25 years old
console.log(zxxAge) // zxxSub is not defined
}
return zxxFnSub;
}
var zxx = zxxFn()
zxx()
上述代碼,在函數被調用之前作用域鍊已經存在:
-
全局作用域 -> zxxFn函數作用域 -> zxxFnSub函數作用域
當執行【zxx();】時,由于其代指的是zxxFnSub函數,此函數的作用域鍊在執行之前已經被定義為:全局作用域 -> zxxFn函數作用域 -> zxxFnSub函數作用域,是以,在執行【zxx();】時,會根據已經存在的作用域鍊去尋找變量。
var zxx = 'zxx is a great girl'
function zxxFn() {
console.log(zxx)
}
function zxxFnSub() {
var zxx = 'zxx is 18 years old'
return zxxFn
}
var myZxx = zxxFnSub()
myZxx() // zxx is a great girl
上述代碼,在函數被執行之前已經建立了兩條作用域鍊:
- 全局作用域 -> zxxFn函數作用域
- 全局作用域 -> zxxFnSub函數作用域
當執行【myZxx();】時,myZxx代指的zxxFn函數,而zxxFn函數的作用域鍊已經存在:全局作用域 -> zxxFn函數作用域,是以,執行時會根據已經存在的作用域鍊去尋找。
最小特權原則:
也叫最小授權或最小暴露原則,這個原則是指在軟體設計中,應該最小限度地暴露必要内容,而将其他内容都“隐藏”起來,比如某個子產品或對象的 API 設計。這個原則可以延伸到如何選擇作用域來包含變量和函數。如果所有變量和函數都在全局作用域中,當然可以在所有的内部嵌套作用域中通路到它們。但這樣會破壞前面提到的最小特權原則,因為可能會暴漏過多的變量或函數,而這些變量或函數本應該是私有的,正确的代碼應該是可以阻止對這些變量或函數進行通路的。
例:
function doSomething (a) {
b = a + doSomethingElse(a * 2)
console.log(b * 3)
}
function doSomethingElse (a) {
return a - 1
}
var b
doSomething(2) // 15
在這個代碼片段中,變量 b 和函數 doSomethingElse(..) 應該是 doSomething(..) 内部具體 實作的“私有”内容。給予外部作用域對 b 和 doSomethingElse(..) 的“通路權限”不僅 沒有必要,而且可能是“危險”的,因為它們可能被有意或無意地以非預期的方式使用, 進而導緻超出了 doSomething(..) 的适用條件。更“合理”的設計會将這些私有的具體内 容隐藏在 doSomething(..) 内部,例如:
function doSomething (a) {
function doSomethingElse (a) {
return a - 1
}
var b
b = a + doSomethingElse(a * 2)
console.log(b * 3)
}
doSomething(2) // 15
現在,b 和 doSomethingElse(..) 都無法從外部被通路,而隻能被 doSomething(..) 所控制。 功能性和最終效果都沒有受影響,但是設計上将具體内容私有化了,設計良好的軟體都會 依此進行實作。
塊級作用域