目錄
- 閉包和作用域
- 變數宣告
- 變數和函式的宣告提升
- 作用域和作用域鏈
- 執行背景關系
- 閉包
- 垃圾回識訓制
閉包和作用域
變數宣告
var 宣告特點
- 在使用
var宣告變數時,變數會被自動添加到最接近的背景關系 - var存在宣告提升,
var宣告會被拿到函式或全域作用域的頂部,位于作用域中所有代碼之前, - 可多次重復宣告,而重復的
var宣告則會被忽略
let 宣告特點
-
let宣告存在塊級作用域 -
let宣告(創建程序)存在提升,但由于暫時性死區(temporal dead zone),無法在let宣告之前去使用變數 -
在同一作用域內無法重復宣告,重復的
let宣告會拋出SyntaxError錯誤
const 宣告特點
const宣告存在塊級作用域const一旦宣告后在其生命周期內都無法重新賦予新值- 其余與
let宣告一致
變數和函式的宣告提升
變數宣告與函式宣告都存在提升,可以記住以下幾個點:
- 變數宣告中由
var定義的變數會提升到其所在作用域的頂部, - 變數宣告中
let和const提升效果一致,即由其定義的變數都會在創建程序被提升,但在初始化階段被暫時性死區所扼殺, - 函式宣告優先于變數宣告,而函式運算式則會作為一個變數提升,其提升效果取決于用
let還是var定義,
變數和函式的具體宣告情況如下:
let的「創建」程序被提升了,但是初始化沒有提升,var的「創建」和「初始化」都被提升了,function的「創建」「初始化」和「賦值」都被提升了,
來看這樣三段代碼:
第一段:var 變數宣告效果
// 第一段
console.log(a) // 輸出:undefined
var a = 10
上面代碼運行后的實際情況如下:
var a // y 變數宣告提升到其所在作用域的頂部
console.log(a)
a = 10
第二段:let變數宣告效果(const與其一致)
// 第二段
{
console.log(x) // 產生暫時性死區,無法訪問變數,
// 報錯內容:Uncaught ReferenceError: Cannot access 'x' before initialization
// 在值初始化之前無法訪問 x ,即變數在初始化階段被暫時性死區所扼殺
let x = 10
}
第三段:函式宣告與函式運算式宣告效果
// 第三段:
var foo = function () {
console.log('我是函式運算式')
}
function foo() {
console.log('我是函式宣告')
}
foo()
// 按照我們常規思維去思考一下,也許會輸出'我是函式宣告',
// 但去執行一下,輸出:'我是函式運算式'
上面代碼運行后的實際情況如下:
function foo() { // foo 作為函式宣告被提升了
console.log('我是函式宣告')
}
var foo // foo 作為 var 變數宣告被提升了
foo = function () {
console.log('我是函式運算式')
}
foo()
// 其中函式宣告優先于變數宣告,這也就解釋了為什么不會輸出'我是函式宣告',
作用域和作用域鏈
作用域
作用域分類:
- 全域作用域
- 函式作用域(
function函式體內 ) - 塊級作用域(
let和const宣告存在塊級作用域)
// 全域作用域
function foo() {
// 函式作用域
}
{
let c = 30
// 塊級作用域
}
詞法作用域:JavaScript會利用詞法分析器分析我們書寫的代碼,從而依據變數和函式的命名位置來動態生成不同的作用域,即我們在定義變數或函式的時候,就已經決定了它們之間在不同作用域上的關系,
作用域鏈
作用域鏈由執行背景關系中的變數物件逐級構成,
學習要點:
- 作用域鏈的用途,是保證對執行環境有權訪問的所有變數和函式的有序訪問,
- 每個環境都可以逐級向上搜索作用域鏈查詢變數和函式名;但任何環境都不能通過向下搜索作用域鏈,
- 自由變數:未在當前作用域定義的變數,自由變數會按照作用域鏈的查找機制,逐級向上查找與之對應的變數
執行背景關系
執行背景關系保存著變數物件,作用域鏈和this,
學習要點:
- 所有通過var定義的全域變數會函式都會成為
window物件的屬性和方法,但使用let和const的宣告的全域變數和函式不會定義在全域背景關系中, - 每個函式被呼叫時都會產生一個執行背景關系,這個執行背景關系會被推入堆疊,在函式執行完畢后,該執行背景關系會在堆疊彈出,將控制權返還給之前的執行背景關系,
- 當前作用域鏈中的第一個變數物件來自上一個背景關系,下一個變數物件來自再上一個背景關系,以此類推直至全域背景關系,
執行背景關系分類:
- 全域背景關系 (
window物件) - 函式背景關系
eval()背景關系
來看這樣一段代碼:
let a = 10
function sum() {
let b = 20
function add() {
let c = 30
console.log(a + b + c)
}
add()
}
sum()
執行背景關系內容如下:
全域執行背景關系:
[ 作用域鏈:[], 變數物件:[ a, sum ], this: window ]
sum 函式執行背景關系:
[ 作用域鏈:[ 全域變數物件:[ a, sum ] ], 變數物件:[ b, add ] , this: window ]
add 函式執行背景關系:
[ 作用域鏈:[ add函式的變數物件: [ b, add ], 全域變數物件:[ a, sum ] ], 變數物件:[c] , this: window ]
入堆疊程序
-
首先呼叫
sum函式將其推入堆疊,產生了第一個函式執行背景關系, -
緊接著
sum函式內部又呼叫add函式,于是又將其函式推入堆疊,產生了第二個函式執行背景關系,
出堆疊程序
-
add函式執行完畢后將其彈出堆疊,控制權交給sum函式, -
sum函式執行完畢后將其彈出堆疊,控制權交給全域背景關系, -
當瀏覽器關閉后,全域背景關系會出堆疊,
閉包
閉包定義:在一個嵌套函式里,內部函式可以訪問外部函式的變數,
閉包應用:封裝物件的私有屬性和方法,即對資料作隱藏和封裝,防止污染全域變數
閉包作用:多個閉包可以共享相同的函式定義,但卻保存了不同的詞法環境,
來看這樣三段代碼:
// 前置知識:setTimeout在事件回圈機制中作為宏任務,for回圈屬于微任務,
// 宏任務會在微任務之后執行,即我們的for回圈會先一步于setTimeout結束,
for (var i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i)
}, 3000)
} // 輸出結果:輸出10次 10 !
// 每回圈一次,都共享了相同的詞法環境(全域作用域),
我們給setTimeout套一個立即執行函式,如下:
for (var i = 0; i < 10; i++) {
(function (i) { // 我們的閉包函式,相對于全域環境
setTimeout(function () {
console.log(i) // 內部函式訪問了外部函式的變數
}, 3000)
})(i)
} // 輸出結果:3秒后輸出 0 1 2 3 4 5 6 8 9
// 每回圈一次,立即執行函式就創建了不同的詞法環境(塊級作用域),
我們換另一種形式去驗證一下:
for (var i = 0; i < 10; i++) {
let a = function (i) { // 我們的閉包函式,相對于全域環境
setTimeout(function () {
console.log(i) // 內部函式訪問了外部函式的變數
}, 3000)
}
a(i)
} // 輸出結果:同樣3秒后輸出 0 1 2 3 4 5 6 8 9
特別注意:不能濫用閉包,因為閉包在處理速度和記憶體消耗方面對腳本性能具有負面影響,
垃圾回識訓制
作用:垃圾回收程式會跟蹤記錄需要使用的變數和不需使用的變數,自動進行記憶體管理實作記憶體分配和閑置資源回收,
記憶體的生命周期:
- 分配你所需要的記憶體
- 使用分配到的記憶體(讀、寫)
- 不需要時將其釋放
在瀏覽器的發展史上,主要有兩種標記策略:參考計數和標記清理,
參考計數
基本原理:當首次宣告變數并賦一個參考型別值時,會將這個值的參考次數設定為1,當這個值被賦給其他變數時,這個值的參考次數會再加1,當這個值被其他值所覆寫時,參考次數會減1,直到參考次數為0時,垃圾回識訓制則會“上門回收”這個值
來看這樣一段代碼:
let a = { name: '小紅' } // 首次值賦變數,參考計數為 1
let b = a // 值賦變數,參考計數 +1 為 2
let c = a // 值賦變數,參考計數 +1 為 3
c = null // 值被覆寫,參考計數 -1 為 2
b = null // 值被覆寫,參考計數 -1 為 1
a = null // 值被覆寫,參考計數 -1 為 0 被垃圾回識訓制回收
回圈參考(參考計數的缺陷問題)
來看這樣一段代碼
function foo() {
let a = { name: '小紅' }
let b = { name: '小明' }
a.name = b // b賦值給a物件中的name,b的參考次數為2
b.name = a // a賦值給b物件中的name,a的參考次數為2
} // 說明:物件屬性值作為變數被賦值
foo()
程序決議:函式的變數物件在函式呼叫完成之后會將每個變數值設為null ,以便垃圾回識訓制進行回收,但在參考計數演算法的策略中,函式在呼叫后,回圈參考的變數a和b依然保留了一次參考次數,也就是說,這兩個參考型別的參考次數為1,不會進行回收,
標記清除
基本原理:標志清除演算法把“物件不再需要”簡化定義為“物件是否可以獲得”,垃圾回收器將定期從根(全域物件)開始,找所有從根開始參考的物件,然后找這些物件參考的物件......直到最終垃圾回收器將找到所有可以獲取的物件和收集所有不能獲取的物件,其中不能獲取的物件則會被回收,
參考
MDN-閉包
我用了兩個月的時間才理解 let
JavaScript高級程式設計(第4版)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/542452.html
標籤:其他
下一篇:雙十一銷量實時統計圖表
