一、作用域
1、什麼是作用域(Scope)
通常來說,一段程序代碼中所用到的名字不總是有效和可用的,而限定這個名字的可用性的代碼範圍就是這個名字的作用域。
JS作用域:就是代碼名字(變量)作用的範圍
作用域的目的:是爲了提高程序的可靠性,更重要的是減少命名衝突
2、JS的作用域的分類(ES6之前)
JS作用域可以分爲兩大類:全局作用域 、局部作用域(函數作用域)
(一)全局作用域:
直接編寫在 script 標籤之中的JS代碼,都是全局作用域;
或者是一個單獨的 JS 文件中的。
全局作用域在頁面打開時創建,頁面關閉時銷燬;
在全局作用域中有一個全局對象 window(代表的是一個瀏覽器的窗口,由瀏覽器創建),可以直接使用。
在全局作用域中,
- 所有創建的變量都會作爲 window 對象的屬性保存。
- 所有創建的函數都會作爲 window 對象的方法保存。
(二)局部作用域(函數作用域):
在函數內部就是局部作用域,這個代碼的名字只在函數的內部起作用
調用函數時創建函數作用域,函數執行完畢之後,函數作用域銷燬;
每調用一次函數就會創建一個新的函數作用域,它們之間是相互獨立的。
實例分析:
在這個例子裏面 un函數裏面的 局部作用域中 有一個 num 變量,script 標籤的全局作用域中也有一個 num變量。
(一個在全局作用域下,另一個在局部作用域下,雖然兩個變量的變量名相沖突,但是並沒有影響。)
所以,在不同的作用域下,變量名相同也不受影響,這樣就很有效的減少了命名衝突。
<script>
var num = 10;
function nu(){
var num = 20;
console.log(num);
}
nu();
console.log(num);
</script>
JS現階段(ES6之前)沒有塊級作用域,被塊級作用域就是用大括號({})包含的就是塊級作用域。
二、變量的作用域
在JavaScript中,根據作用域的不同,變量可以分爲兩種:全局變量 和 局部變量
(一)全局變量
1、在全局作用域下聲明的變量叫做 全局變量(在函數外部定義的變量)
2、全局變量在全局(代碼的任何位置)下都可以使用;全局作用域中無法訪問到局部作用域中的變量。
3、全局變量第一種創建方式:在全局作用域下 var聲明的變量是全局變量
4、全局變量第二種創建方式:如果在函數內部,沒有使用 var關鍵字聲明直接賦值的變量也屬於 全局變量。(不建議使用)
(變量 num 直接寫在 script標籤下,所以 num是全局變量。)
<script>
var num = 10;
function nu(){
console.log(num);
}
nu();
console.log(num);
</script>
(二)局部變量:
1、在局部作用域下聲明的變量叫做局部變量(在函數內部定義的變量)
2、局部變量只能在函數內部使用,在局部作用域中可以訪問到全局變量。
3、在函數內部 var 聲明的變量就是局部變量;
4、函數的形參實際上就是局部變量;
<script>
function nu(){
var num1 = 10;
num2 = 20;
console.log(num1);
}
nu();
console.log(num2);
</script>
(三)全局變量和局部變量的區別:
全局變量:在任何一個地方都可以使用,全局變量只有在瀏覽器關閉的時候纔會銷燬,比較佔用內存資源
局部變量:只能在函數內部使用,當其所在代碼塊被執行時,會被初始化;當代碼塊執行完畢就會銷燬,因此更節省節約內存空間;
三、變量的聲明提前和函數的聲明提前
(一) 變量的聲明提前
使用 var 關鍵字聲明的變量,會在所有的代碼執行之前被聲明。(但是不會賦值)
全局變量即使是寫在最下面,也相當於在所有代碼之前的最上面聲明的變量。
等價於
(在這個例子中最終結果返回的是 undefined,這是因爲 變量a 就相當於在所有代碼最上面被聲明,但下面才被賦值,所以結果是 undefined未定義)
如果聲明變量的時候不使用 var 關鍵字,那麼變量就不會被聲明提前。
(如果不寫 var 關鍵字,變量聲明就無法提前,所以在 console.log前面就找不到 變量,所以返回結果報錯)
(二) 函數的聲明提前
使用函數聲明形式創建的函數 :function 函數名() {};
它會在所有代碼執行之前就被創建。所以可以在函數聲明之前被調用
等價於
使用函數表達式創建的函數:var 變量名 = function(){};
不會被聲明提前,所以不能再聲明前調用。
四、作用域鏈
只要是代碼,就有一個作用域,寫在函數內部的就叫做局部作用域;
如果函數中還有函數,那麼在這個作用域中又可以誕生一個作用域;
當在函數作用域中操作一個變量的時候,會先在自身作用域中查找,如果有就直接使用,如果沒有就向上級作用域中尋找。如果全局作用域中也沒有,那麼就報錯。
根據內部函數可以訪問可以訪問外部函數變量的這種機制,用鏈式查找決定哪些數據能被內部函數訪問,就稱爲函數作用域鏈。
作用域鏈:內部函數訪問外部函數的變量,採取的是鏈式查找的方法來決定取那個結構,這種結構稱之爲作用域鏈。
作用域鏈的原則:就近原則
(作用域鏈採用鏈式查找的方式,一層一層向上查找,先查找外面的嵌套的函數是否有所需內容,找到就輸出相應的結果,如果沒有再向上查找。就近原則)
實例一:
下面代碼最終輸出的結果是多少?
思路分析:
按照鏈式查找先到上一級查找,輸出內容在2級鏈,向上到 1級鏈去查找,如果 1級鏈也沒有就繼續向上查找。如果都找不到就會返回 undefined(未定義)。
因爲1級鏈中有 unm 值,所以輸出num結果就是 123。
實例二:
下面代碼最終輸出的結果?
注意:在更長的結構中畫圖分析太過於麻煩,可以從輸出目標console.log(); 位置向外層的結構看,尋找最近的變量。
var a = 1;
function fn1(){
var a=2;
var b='22';
fn2();
function fn2(){
var a =3;
fn3();
function fn3(){
var a=4;
console.log('a= ' + a); //求 a的值
console.log('b= ' + b); //求 b的值
}
}
}
fn1();
最終結果是: