個人自學前端15-JS8-作用域和作用域鏈,var變數提升,閉包
作用域和作用域鏈
一 作用域
什麼是作用域?
作用域就是宣告變數的'地點'。程式執行過程中,遇到函式呼叫,程式會被推入函式作用域中。
作用域的作用?
區分不同的變數,對資料進行保護。對變數具有儲存和保護的作用。
1.1 作用域分類:
1:全域性作用域 (大括號外面的區域)
2:區域性作用域(大括號內部的區域)
其中,區域性作用域又分為:
1:函式作用域 (函式{}內部的區域)
2:塊級作用域 (非函式{}的其他{}的內部區域)
1.2 不同作用域內的變數:
全域性變數:宣告在全域性作用域中的變數叫全域性變數。
區域性變數:宣告在區域性作用域中的變數叫區域性變數。
其中:函式的形參屬於區域性變數
1.3 全域性變數和區域性變數的可見性:
1:全域性變數在程式的任何地方都可以訪問。
2:區域性變數只能在區域性作用域中可以訪問。
擴充套件:
任何函式內都可以訪問全域性變數,全域性作用域不能訪問區域性變數,區域性作用域A不能訪問區域性作用域B中的變數
1.4 作用域的生命週期
1:全域性作用域在程式關閉前一直存在,全域性變數在程式關閉前一直都可以訪問。全域性變數一致都佔據記憶體。
2:區域性作用域在程式退出區域性作用域時,區域性作用域會被銷燬,區域性變數會隨著區域性作用域的銷燬而銷燬。
函式內的區域性變數,會在函式結束呼叫之後被銷燬。(被銷燬後就無法訪問了)
1.5:塊級作用域
ES6新增的概念。
除了函式大括號之外的其他大括號內的區域就是塊級作用域。
塊級作用域內只有通過let和const宣告的變數才會變成區域性變數。
var 宣告的變數不會變成塊級作用域內的區域性變數。(變數提升)
二 作用域鏈
若程式中出現多個同名變數,如何區分他們呢?使用作用域鏈機制進行變數查詢可以區分。
如果區域性作用域內又存在其他作用域,則這些作用域構成了一個 '作用域鏈'。
// 以下程式出現的作用域鏈是:add => fn => 塊級作用域 => 全域性作用域 { function fn(){ function add(){ consle.log(x) } } }
如果作用域鏈內出現了多個同名變數,則需要進行變數查詢來區分他們。
// 以下程式出現的作用域鏈是:add => fn => 塊級作用域 => 全域性作用域
// 以下程式如果呼叫add列印x,列印的是10.不會列印20
{
let x = 20;
function fn(){
var x = 10;
function add(){
consle.log(x)
}
}
}
變數查詢步驟:
1:寫出訪問變數程式碼所在的作用域鏈
2:沿著這個作用域鏈從裡到外逐層查詢變數宣告,如果查詢到,就停止查詢(就近原則)
三 變數提升 (預解析)
var 宣告的變數和函式宣告在程式的解析階段會被提升到本作用域的頂端
只提升宣告部分!不提升賦值部分!
預解析完成之後,程式按照解析之後的js進行執行。
console.log(x);
var x = 10;
show();
function show(){
console.log(100)
}
// 以上程式碼預解析之後變成下面的的順序,最後執行的是下面的程式碼順序。
var x;
function show(){
console.log(100)
}
console.log(x);
x = 10;
show();
let 和 const 宣告的變數不會有提升現象!
函式表示式只有宣告的變數提升了!
var 在塊級作用域中宣告變數會提升到全域性作用域中成為全域性變數!
如果變數提升和函式提升衝突了,(識別符號一致),以函式宣告為準!
如果var變數和函式同名,則提升後,函式覆蓋變數
迴圈中var和let:
迴圈用let宣告i,則這個i是一個區域性變數.
迴圈多少次,就有多少個塊級作用域,每個作用域內有一個不同的i.
第一個i是0,第二個i是1,以此類推...
迴圈用var宣告i,則這個i是一個全域性變數.
不管迴圈多少次,修改的都是全域性變數的i.
迴圈時操作元素,但凡在事件中使用了i,如果這個i是var宣告的,則事件中訪問到的i必定是迴圈結束之後的i.
迴圈結束之後的i,必定超出了陣列的最大下標.
四 閉包
1.1 什麼是閉包?
閉包有很多不同的描述。
1:閉包可以在函式外面訪問閉包內部的變數。
2:閉包建立一個私有的不會被銷燬的作用域,用於儲存區域性變數。
3:閉包就是函式套函式(最白痴的說法)
閉包表現形式:
// 這裡的add作用域構成了一個閉包.
function add(){
let x = 10;
return function(){
console.log(x)
}
}
// 閉包返回子函式
let fn = add();
// 通過呼叫子函式來訪問區域性變數x
fn();
// ------------------------------------------------------------------------------
// 這裡的add作用域構成了一個閉包。
function add(){
let x = 10;
oBtn.onclick = function(){
console.log(x)
}
}
// 通過點選oBtn來觸發子函式,以此來訪問區域性變數x。
add();
oBtn.click()
1.2 閉包的作用
閉包有兩個主要作用:1:儲存。2:保護
閉包可以把資料儲存到區域性作用域中,在函式呼叫結束後區域性變數也不會被銷燬,可以一直訪問。
閉包儲存的變數本質上還是區域性變數,在函式外部無法直接訪問,但是可以通過閉包的子函式訪問。
總結:閉包可以讓變數同時具有全域性變數任何時間都可以訪問的特性,又具有區域性變數不能在外部直接訪問的特性
1.3 閉包的原理
閉包是如何讓區域性作用域在函式呼叫結束之後不會被銷燬的?
這裡需要先了解 js 的垃圾回收機制。
js 垃圾回收機制:如果一個變數無法再訪問,則 js 會自動將其回收(銷燬),以釋放記憶體。
例如,函式呼叫結束,函式作用域被銷燬,沒有了函式作用域,則無法訪問該作用域中的變數,
因此 js 垃圾回收機制會自動銷燬區域性變數。
只要讓 js 認為區域性變數還有可能繼續得到訪問,則 js 垃圾回收機制就不會銷燬區域性變數。
閉包通過呼叫子函式來訪問區域性變數,如果子函式可以任意時間呼叫,則子函式引用的區域性變數就是可以在任何時間得到訪問的,因此 js 垃圾回收機制就不會銷燬區域性變數。
只要閉包內的子函式可以在任意時間呼叫,則子函式內引入的變數就不會被 js 垃圾回收機制銷燬.
1.4 閉包構成條件
1:父作用域套子函式作用域。
2:子函式引用父作用域內宣告的區域性變數。
3:子函式還可以在任意時間呼叫。
1.5 如何銷燬閉包
銷燬閉包的目的就是銷燬閉包儲存的變數。
知道閉包原理後,要讓閉包失效,可以讓子函式不再可以訪問呼叫。
子函式不再可以呼叫,則子函式內部引入的變數就無法再訪問,因此就會被垃圾回收機制回收以釋放記憶體。
// 這裡的add作用域構成了一個閉包。
function add(){
let x = 10;
oBtn.onclick = function(){
console.log(x)
}
}
// 通過點選oBtn來觸發子函式,以此來訪問區域性變數x。
add();
// 這樣,點選按鈕不再觸發子函式,達到銷燬閉包目的。
oBtn.onclick = null;
五 var變數提升
變數提升(預解析):
程式執行前,瀏覽器會把var宣告和函式宣告提升到本作用域頂端.提升之後程式碼再執行.
let 和 const 沒有提升.
只提升宣告部分,賦值部分不提升.
同名的變數和函式同時提升,以函式為準.
var x;
function x() {}
console.log(x);
x = 10;
console.log(x);
if (false) {
var x = 30;
}