一、什麼是作用域?
作用域是你的代碼在運行時,各個變量、函數和對象的可訪問性。(可產生作用的區域)
二、JavaScript中的作用域
在 JavaScript 中有兩種作用域
全局作用域
局部作用域
當變量定義在一個函數中時,變量就在局部作用域
中,而定義在函數之外的變量則從屬於全局作用域
。每個函數在調用的時候會創建一個新的作用域。
1.全局作用域
當你在文檔中(document)編寫 JavaScript 時,你就已經在全局作用域中了。JavaScript
文檔中(document)只有一個全局作用域。定義在函數之外的變量會被保存在全局作用域中
。
// 作用域默認爲全局作用域
var name = 'andy';
全局作用域裏的變量能夠在其他作用域中被訪問和修改
。
var name = 'andy';
console.log(name); // 輸出 'andy'
function logName() {
console.log(name); // 'name' 變量可以在這裏和其他地方訪問
}
logName(); // 輸出 'andy'
2.局部作用域
定義在函數中的變量就在局部作用域中。並且函數在每次調用時都有一個不同的作用域。這意味着同名變量可以用在不同的函數中。因爲這些變量綁定在不同的函數中,擁有不同作用域,彼此之間不能訪問。
// 全局作用域
function someFunction() {
// 局部作用域 ##1
function someOtherFunction() {
// 局部作用域 ##2
}
}
// 全局作用域
function anotherFunction() {
//局部作用域 ##3
}
3.塊語句(JS沒有塊級作用域)
塊級聲明包括if和switch,以及for和while循環,和函數不同,它們不會創建新的作用域。在塊級聲明中定義的變量從屬於該塊所在的作用域。也就是說在for、if、while等語句內部的聲明的變量與在外部聲明是一樣的,在這些語句外部也可以訪問和修改這些變量的值。
if (true) {
//這裏的if條件不會創建一個新的作用域
var name = 'Hammad'; // name 這個變量仍在全局作用域
}
console.log(name); // logs 'Hammad'
ECMAScript 6 引入了let和const關鍵字。這些關鍵字可以代替var
。
var name = 'Hammad';
let likes = 'Coding';
const skills = 'Javascript and PHP';
和var關鍵字不同,let和const關鍵字支持在塊級聲明中創建使用局部作用域
。(塊級作用域)
if (true)
// 這個 'if' 塊語句沒有創建一個塊級作用域
// name 變量處於全局作用域,因爲由var關鍵字聲明
var name = 'Hammad';
// likes 變量處於塊級作用域因爲由let關鍵字聲明
let likes = 'Coding';
// skills 變量處於塊級作用域因爲由const關鍵字聲明
const skills = 'JavaScript and PHP';
}
console.log(name); // 輸出 'Hammad'
console.log(likes); // Uncaught ReferenceError: likes is not defined
console.log(skills); // Uncaught ReferenceError: skills is not defined
一個應用中全局作用域的生存週期與該應用相同。局部作用域只在該函數調用執行期間存在
。
4.詞法作用域
所謂的 詞法( 代碼 )作用域, 就是代碼在編寫過程中體現出來的作用範圍. 代碼一旦寫好, 不用執行, 作用範圍就已經確定好了.
這個就是所謂詞法作用域.這意味着函數運行在定義它的作用域中,而不是在調用它的作用域中
。
在 js 中詞法作用域規則:
- 函數允許訪問函數外的數據.
- 整個代碼結構中只有函數可以限定作用域.
- 作用規則首先使用提升規則分析
- 如果當前作用規則中有名字了, 就不考慮外面的名字
詞法作用域
var
用來將變量定義在詞法作用域中(也就是function中)
function someFunc(){
var a;
}
a
就被函數someFunc
框在了詞法作用域中
塊級作用域
let
和const
用來將變量定義在塊級作用域中(也就是花括號中)
if(true){
let b;
}
b
就被if
的花括號框在了塊級作用域中
5.作用域鏈
可以發現只有函數可以製造作用域結構. 那麼只要是代碼, 至少有一個作用域, 即全局作用域. 凡是代碼中有函數,那麼這個函數就構成另一個作用域. 如果函數中還有函數, 那麼再這個作用域中就 又可以誕生一個作用域. 那麼將這樣的所有的作用域列出來,可以有一個結構: 函數內指向函數外的鏈式結構
.
作用域嵌套
作用域是可以嵌套的,任務一中提到的詞法作用域和塊級作用域都可以嵌套其他作用域
(塊級作用域僅對ES6而言)
function someFunc(){
function inner(){
}
}
inner
就是嵌套在someFunc
(詞法作用域)中的詞法作用域
if(true){
while(false){
}
}
while
就是嵌套在if
(塊級作用域)中的塊級作用域
function someFunc(){
if(true){
}
}
if
就是嵌套在someFunc
(詞法作用域)中的塊級作用域
作用域中的變量訪問
所有的嵌套作用域都遵循以下規則:內部作用域有權訪問外部作用域
,反之不成立。
栗子:
function someFunc(){
var outerVar = 1;
function inner(){
var innerVar = 2;
}
}inner
有權訪問innerVar
和outerVar
,但是someFunc
只能訪問到outerVar
多重嵌套作用域
作用域是可以任意嵌套的,但是都要遵循上面的規則。
再附加一個規則:
兄弟作用域不可相互訪問
栗子:
function someFunc(){
function inner(){
}
function inner2(){
}
}
inner
和inner2
都是someFunc
中的作用域,正如someFunc
不能訪問inner
們的作用域一樣,inner
們之間也不能相互訪問。
作用域樹
從上向下看這個嵌套作用域,就是棵樹!
看代碼:
function someFunc() {
function inner() {
}
function inner2() {
function foo() {
}
}
}
看樹:
someFunc()
|
/ \
/ \
/ \
↓ ↓
inner() inner2()
|
↓
foo()
要記住的是:inner
作用域可以訪問外部作用域,但是反之不成立; foo()
可以訪問inner2()
中的變量,inner2()
可以訪問someFunc()
中的變量,這棵樹倒過來似乎更有意義,就成了鏈!!
作用域鏈
從最裏面看到最外面就構成了作用域鏈
someFunc()
↑
\
\
\
inner2()
↑
|
foo()