一、作用域
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();
最終結果是:

