接上篇博客進行學習,通俗易懂,詳細
博客地址: 2023年web前端開發(fā)之JavaScript基礎(五)基礎完結_努力的小周同學的博客-CSDN博客
學習內容
學習 作用域、變量提升、 閉包等語言特征,加深對 JavaScript 的理解,掌握變量賦值、函數(shù)聲明的簡潔語法, 降低代碼的冗余度。
理解作用域對程序執(zhí)行的影響
能夠分析程序執(zhí)行的作用域范圍
理解閉包本質,利用閉包創(chuàng)建隔離作用域 (重要)
了解什么變量提升及函數(shù)提升
掌握箭頭函數(shù)、解析剩余參數(shù)等簡潔語法 ( 重點 )
一.作用域
了解作用域對程序執(zhí)行的影響及作用域鏈的查找機制,使用閉包函數(shù)創(chuàng)建隔離作用域避免全局變量污染。
作用域(scope) 規(guī)定了變量能夠被訪問的"范圍" 離開這個"范圍" 變量便不能被訪問
作用域分為 全局作用域 和 局部作用域.
1.1局部作用域
局部作用域分為函數(shù)作用域和塊作用域.
1.2函數(shù)作用域
在函數(shù)內部聲明的變量只能在函數(shù)內部被訪問,外部無法直接訪問
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
//聲明conunter 函數(shù)
function conunter(x,y){
//函數(shù)內部聲明的變量
const s = x + y
console.log(s) // 輸出18
}
//調用counter 函數(shù)
conunter(10,8) // 傳入實參
//直接在外部訪問 函數(shù)內部的變量s
console.log(s) // 報錯
</script>
</body>
</html>
總結:
函數(shù)內部聲明的變量,在函數(shù)外部無法被訪問
函數(shù)的參數(shù)也是函數(shù)內部的局部變量
不同函數(shù)內部聲明的變量無法互相訪問
函數(shù)執(zhí)行完畢后,函數(shù)內部的變量實際被清空了
1.3 塊作用域
在 JavaScript 中使用 {} 包裹的代碼稱為代碼塊,代碼塊內部聲明的變量外部將【有可能】無法被訪問。
代碼實現(xiàn)效果如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
//代碼塊
{
//內部聲明變量
let age = 12
console.log(age) //12
}
//代碼塊外部使用 變量age
console.log(age) // 報錯
let flag = true
if(flag){
//str 只能在該代碼塊中被訪問
let str = "代碼塊內部"
console.log(str) // 正常輸出 代碼塊內部
}
//外部訪問str 變量
console.log(str) //報錯
for(let t = 1; t<=6;t++){
//t 只能在該代碼塊中被訪問
console.log(t); //正常輸出
}
//變量t 定義在for循環(huán)里
//在外部訪問 t
console.log(t) //報錯
</script>
</body>
</html>
JavaScript中除了變量外還有產量, 常量與變量本質的區(qū)別是 [常量必須要有值且不允許被重新賦值 ], 常量值為對象尤其屬性和方法允許重新賦值
<script>
// 設置常量 必須要有值
const version = '1.0.0';
// 常量 不能重新賦值
// version = '1.0.1';
// 常量值為對象類型
const user = {
name: '小明',
age: 18
}
// 不能重新賦值
user = {};
// 屬性和方法允許被修改
user.name = '小小明';
user.gender = '男';
</script>
總結:
let 聲明的變量會產生塊作用域, var 不會產生塊作用域 當var設置變量外部也能訪問
const 聲明的常量也會產生塊作用域
不同代碼塊之間的變量無法互相訪問
推薦使用 let 或 const
注:開發(fā)中 let 和 const 經常不加區(qū)分的使用,如果擔心某個值會不小被修改時,則只能使用 const 聲明成常量。
1.4 全局作用域
<script> 標簽和 .js 文件的【最外層】就是所謂的全局作用域,在此聲明的變量在函數(shù)內部也可以被訪問。
<script>
// 此處是全局
function sayHi() {
// 此處為局部
}
// 此處為全局
</script>
全局作用域中聲明的變量,任何其它作用域都可以被訪問,如下代碼所示:
<script>
// 全局變量 name
const name = '小明'
// 函數(shù)作用域中訪問全局
function sayHi() {
// 此處為局部
console.log('你好' + name)
}
// 全局變量 flag 和 x
const flag = true
let x = 10
// 塊作用域中訪問全局
if(flag) {
let y = 5
console.log(x + y) // x 是全局的
}
</script>
總結:
為 window 對象動態(tài)添加的屬性默認也是全局的,不推薦!
函數(shù)中未使用任何關鍵字聲明的變量為全局變量,不推薦?。。?/p>
盡可能少的聲明全局變量,防止全局變量被污染
JavaScript 中的作用域是程序被執(zhí)行時的底層機制,了解這一機制有助于規(guī)范代碼書寫習慣,避免因作用域導致的語法錯誤。
1.5 作用域鏈
在解釋什么是作用域鏈前先來看一段代碼:
<script>
// 全局作用域
let a = 1
let b = 2
// 局部作用域
function f() {
let c
// 局部作用域
function g() {
let d = 'yo'
}
}
</script>
函數(shù)內部允許創(chuàng)建新的函數(shù),f 函數(shù)內部創(chuàng)建的新函數(shù) g,會產生新的函數(shù)作用域,由此可知作用域產生了嵌套的關系。
如下圖所示,父子關系的作用域關聯(lián)在一起形成了鏈狀的結構,作用域鏈的名字也由此而來。
作用域鏈本質上是底層的變量查找機制,在函數(shù)被執(zhí)行時,會優(yōu)先查找當前函數(shù)作用域中查找變量,如果當前作用域查找不到則會依次逐級查找父級作用域直到全局作用域,如下代碼所示:
這里多理解實驗
<script>
// 全局作用域
let a = 1
let b = 2
// 局部作用域
function f() {
let c
// let a = 10;
console.log(a) // 1 或 10
console.log(d) // 報錯
// 局部作用域
function g() {
let d = 'yo'
// let b = 20;
console.log(b) // 2 或 20
}
// 調用 g 函數(shù)
g()
}
console.log(c) // 報錯
console.log(d) // 報錯
f();
</script>
總結:
嵌套關系的作用域串聯(lián)起來形成了作用域鏈
相同作用域鏈中按著從小到大的規(guī)則查找變量
子作用域能夠訪問父作用域,父級作用域無法訪問子級作用域
思考:
1. 作用域鏈本質是什么? ? 作用域鏈本質上是底層的變量查找機制 2. 作用域鏈查找的規(guī)則是什么? ? 會優(yōu)先查找當前函數(shù)作用域中查找變量 ? 查找不到則會依次逐級查找父級作用域直到全局作用域
二. JS垃圾回收機制(重點理解)
2.1 什么是垃圾回收機制
垃圾回收機制(Garbage Collection) 簡稱 GC
JS中內存的分配和回收都是自動完成的,內存在不使用的時候會被垃圾回收器自動回收.
正因為垃圾回收器的村寨,許多人認為JS不用太關心內存管理問題
但如果不了解JS內存管理機制,我們同樣容易層內存泄漏(內存無法被回收) 的情況
不在用到的內存,沒有及時釋放,就叫做內存泄漏
2.2內存的生命周期
JS環(huán)境中分配的內存,一般有如下生命周期:
內存分配:當我們聲明變量, 函數(shù), 對象的時候,系統(tǒng)會自動為他們分配內存
內存使用: 及讀寫內存,也就是使用變量,函數(shù)等
內存回收: 使用完畢,由垃圾回收自動回收不再使用的內存
說明:
全局變量一般不會回收(關閉頁面回收)
一般情況下局部變量的值不用了會被自動回收掉

總結:
1. 什么是垃圾回收機制? ? 簡稱 GC ? JS中內存的分配和回收都是自動完成的,內存在不使用的時候會被垃圾 回收器自動回收
2. 什么是內存泄漏? ? 不再用到的內存,沒有及時釋放,就叫做內存泄漏
3. 內存的生命周期是什么樣的? ? 內存分配、內存使用、內存回收 ? 全局變量一般不會回收; 一般情況下局部變量的值, 不用了, 會被自動 回收掉
拓展-JS垃圾回收機制-算法說明:

堆??臻g分配區(qū)別:
1.1 棧 (操作系統(tǒng)) : 由操作系統(tǒng)自動分配釋放函數(shù)的參數(shù)值,局部變量等,基本數(shù)據(jù)類型放到棧里面
1.2 堆 (操作系統(tǒng)) : 一般由程序員分配釋放,若程序員不釋放,由垃圾回收機制回收. 復雜數(shù)據(jù)類型放到堆里面.
下面介紹兩種常見的瀏覽器垃圾回收算法: 引用計數(shù)法 和 標記清除法
1.1引用計數(shù)法:
IE采用的引用計數(shù)算法, 定義“內存不再使用”,就是看一個對象是否有指向它的引用,沒有引用了就回收對象 算法: 1. 跟蹤記錄被引用的次數(shù) 2. 如果被引用了一次,那么就記錄次數(shù)1,多次引用會累加 ++ 3. 如果減少一個引用就減1 -- 4. 如果引用次數(shù)是0 ,則釋放內存

let a = new Object() // 引用次數(shù)初始化為1
let b = a // 引用次數(shù)2,即obj被a和b引用
a=null // 引用次數(shù)1
b=null // 引用次數(shù)0,
... // GC回收此引用類型在堆空間中所占的內存
但是這種方法也存在一些問題,就是嵌套引用(循環(huán)引用)
如果兩個對象相互引用,盡管他們已不再使用,垃圾回收器不會進行回收,導致內存泄漏
function fn(){ // fn引用次數(shù)為1,因為window.fn = fn,會在window=null即瀏覽器關閉時回收
let A = new Object() // A: 1
let B = new Object() // B: 1
A.b = B // B: 2
B.a = A // A: 2
}
// A對象中引用了B,B對象中引用了A,兩者引用計數(shù)都不為0,永遠不會被回收。
// 若執(zhí)行無限多次fn,那么內存將會被占滿,程序宕機
fn();
// 還有就是這種方法需要一個計數(shù)器,這個計數(shù)器可能要占據(jù)很大的位置,因為我們無法知道被引用數(shù)量多少。
因為他們的引用次數(shù)永遠不會是0。這樣的相互引用如果說很大量的存在就會導致大量的內存泄露
1.2 標記清除法
該策略分為Mark(標記)和Sweep(清除)兩個階段。
Mark階段:
運行時,將內存中的所有變量標記為0(垃圾)
從各個根對象遍歷,將非垃圾變量標記為1
Sweep階段:
將所有標記為0的變量的內存釋放
function fn(a){ // 開始執(zhí)行此函數(shù)時,將其作用域中a、B以及匿名函數(shù)標記為0
alert(a) // 0
let B = new Object() // 0
return function (){ // 由于這里return出去會被其他變量引用,故標記變?yōu)?
altert(B) // 由于這里的閉包,B的標記變?yōu)?
}
... // 執(zhí)行函數(shù)完畢,銷毀作用域,在某個GC回收循環(huán)時會清理標記為0的變量a,B和匿名函數(shù)被保留了下來即非垃圾變量
}
let fn2 = fn(new Object())
// 補充一下:fn和fn2作為window.fn和window.fn2,標記一直為1,僅僅當手動設置fn=null和fn2=null才會標記為0
三. 閉包(重要)
閉包是一種比較特殊和函數(shù),使用閉包能夠訪問函數(shù)作用域中的變量。從代碼形式上看閉包是一個做為返回值的函數(shù),如下代碼所示:
閉包含義 : 內層函數(shù) + 外層函數(shù)變量
閉包的應用: 實現(xiàn)數(shù)據(jù)的私有。統(tǒng)計函數(shù)的調用次數(shù)
閉包的寫法 統(tǒng)計函數(shù)的調用次數(shù)
<body>
<script>
// 1. 閉包含義 : 內層函數(shù) + 外層函數(shù)變量
// function outer() {
// const a = 1 //外層函數(shù)變量
//
// function f() { //內層函數(shù)
// console.log(a)
// }
//
// f()
//
// }
// outer()
// 2. 閉包的應用: 實現(xiàn)數(shù)據(jù)的私有.統(tǒng)計函數(shù)的調用次數(shù)
// let count = 1
// function fn() {
// count++
// console.log(`函數(shù)被調用${count}次`)
// }
// 不影響外部count = 1 的值,實現(xiàn)數(shù)據(jù)的私有
// 3. 閉包的寫法 統(tǒng)計函數(shù)的調用次數(shù)
function outer() {
let count = 1
function fn() {
count++
console.log(`函數(shù)被調用${count}次`)
}
return fn
}
const re = outer()
// const re = function fn() {
// count++
// console.log(`函數(shù)被調用${count}次`)
// }
re()
re()
// const fn = function() { } 函數(shù)表達式
// 4. 閉包存在的問題: 可能會造成內存泄漏
</script>
</body>
總結:
1.怎么理解閉包?
閉包 = 內層函數(shù) + 外層函數(shù)的變量
恍然大悟如下:

2.閉包的作用?
封閉數(shù)據(jù),提供操作,外部也可以訪問函數(shù)內部的變量

閉包很有用,因為它允許將函數(shù)與其所操作的某些數(shù)據(jù)(環(huán)境)關聯(lián)起來, 實現(xiàn)數(shù)據(jù)私有

3.閉包可能引起的問題?
內存泄漏
四. 變量提升
目標:了解什么是變量提升 說明:
JS初學者經?;ê芏鄷r間才能習慣變量提升,還經常出現(xiàn)一些意想不到的bug,正因為如此,ES6 引入了塊級作用域, 用let 或者 const聲明變量,讓代碼寫法更加規(guī)范和人性化
變量提升是 JavaScript 中比較“奇怪”的現(xiàn)象,它允許在變量聲明之前即被訪問,(僅存在于var聲明變量)
<script>
// 訪問變量 str
console.log(str + 'world!');
// 聲明變量 str
var str = 'hello ';
</script>
注意: 1. 變量在未聲明即被訪問時會報語法錯誤 2. 變量在var聲明之前即被訪問,變量的值為 undefined 3. let / const 聲明的變量不存在變量提升 4. 變量提升出現(xiàn)在相同作用域當中 5. 實際開發(fā)中推薦先聲明再訪問變量
提問:
1. 用哪個關鍵字聲明變量會有變量提升?
var
2. 變量提升是什么流程?
先把var 變量提升到當前作用域于最前面
只提升變量聲明, 不提升變量賦值
然后依次執(zhí)行代碼
我們不建議使用var聲明變量
五.函數(shù)進階
知道函數(shù)參數(shù)默認值、動態(tài)參數(shù)、剩余參數(shù)的使用細節(jié),提升函數(shù)應用的靈活度,知道箭頭函數(shù)的語法及與普通函數(shù)的差異
1.函數(shù)提升
函數(shù)提升與變量提升比較類似,是指函數(shù)在聲明之前即可被調用

<script>
// 調用函數(shù)
foo()
// 聲明函數(shù)
function foo() {
console.log('聲明之前即被調用...')
}
// 不存在提升現(xiàn)象
bar() // 錯誤
var bar = function () {
console.log('函數(shù)表達式不存在提升現(xiàn)象...')
}
</script>
總結: 1. 函數(shù)提升能夠使函數(shù)的聲明調用更靈活 2. 函數(shù)表達式不存在提升的現(xiàn)象 3. 函數(shù)提升出現(xiàn)在相同作用域當中
2.函數(shù)參數(shù)
函數(shù)參數(shù)的使用細節(jié),能夠提升函數(shù)應用的靈活度。

2.1默認值
<script>
// 設置參數(shù)默認值
function sayHi(name="小明", age=18) {
document.write(`<p>大家好,我叫${name},我今年${age}歲了。</p>`);
}
// 調用函數(shù)
sayHi();
sayHi('小紅');
sayHi('小剛', 21);
</script>
總結:
聲明函數(shù)時為形參賦值即為參數(shù)的默認值
如果參數(shù)未自定義默認值時,參數(shù)的默認值為 undefined
調用函數(shù)時沒有傳入對應實參時,參數(shù)的默認值被當做實參傳入
2.2動態(tài)參數(shù)
arguments 是函數(shù)內部內置的偽數(shù)組變量,它包含了調用函數(shù)時傳入的所有實參。
<script>
// 求生函數(shù),計算所有參數(shù)的和
function sum() {
// console.log(arguments)
let s = 0
for(let i = 0; i < arguments.length; i++) {
s += arguments[i]
}
console.log(s)
}
// 調用求和函數(shù)
sum(5, 10)// 兩個參數(shù)
sum(1, 2, 4) // 三個參數(shù)
</script>
總結:
arguments 是一個偽數(shù)組
arguments 的作用是動態(tài)獲取函數(shù)的實參
2.3 剩余參數(shù)
<script>
function config(baseURL, ...other) {
console.log(baseURL) // 得到 'http://baidu.com'
console.log(other) // other 得到 ['get', 'json']
}
// 調用函數(shù)
config('http://baidu.com', 'get', 'json');
</script>
總結:
... 是語法符號,置于最末函數(shù)形參之前,用于獲取多余的實參
借助 ... 獲取的剩余實參,是個真數(shù)組
3. 展開運算符
展開運算符( … ),將一個數(shù)組進行展開 典型運用場景: 求數(shù)組最大值(最小值)、合并數(shù)組等

展開運算符 or 剩余參數(shù)
剩余參數(shù):函數(shù)參數(shù)使用,得到真數(shù)組 展開運算符:數(shù)組中使用,數(shù)組展開

3.箭頭函數(shù)( 重點 )
箭頭函數(shù)是一種聲明函數(shù)的簡潔語法,它與普通函數(shù)并無本質的區(qū)別,差異性更多體現(xiàn)在語法格式上。
3.1基本寫法

<body>
<script>
// const fn = function () {
// console.log(123)
// }
// 1. 箭頭函數(shù) 基本語法
// const fn = () => {
// console.log(123)
// }
// fn()
// const fn = (x) => {
// console.log(x)
// }
// fn(1)
// 2. 只有一個形參的時候,可以省略小括號
// const fn = x => {
// console.log(x)
// }
// fn(1)
// // 3. 只有一行代碼的時候,我們可以省略大括號
// const fn = x => console.log(x)
// fn(1)
// 4. 只有一行代碼的時候,可以省略return
// const fn = x => x + x
// console.log(fn(1))
// 5. 箭頭函數(shù)可以直接返回一個對象
// const fn = (uname) => ({ uname: uname })
// console.log(fn('劉德華'))
</script>
</body>
總結:
箭頭函數(shù)屬于表達式函數(shù),因此不存在函數(shù)提升
箭頭函數(shù)只有一個參數(shù)時可以省略圓括號 ( )

箭頭函數(shù)函數(shù)體只有一行代碼時可以省略花括號{ }, 并自動作為返回值被返回

如圖所示:

代碼如下:
<body>
<script>
// const fn = function () {
// console.log(123)
// }
// 1. 箭頭函數(shù) 基本語法
// const fn = () => {
// console.log(123)
// }
// fn()
// const fn = (x) => {
// console.log(x)
// }
// fn(1)
// 2. 只有一個形參的時候,可以省略小括號
// const fn = x => {
// console.log(x)
// }
// fn(1)
// // 3. 只有一行代碼的時候,我們可以省略大括號
// const fn = x => console.log(x)
// fn(1)
// 4. 只有一行代碼的時候,可以省略return
// const fn = x => x + x
// console.log(fn(1))
// 5. 箭頭函數(shù)可以直接返回一個對象
// const fn = (uname) => ({ uname: uname })
// console.log(fn('劉德華'))
</script>
</body>
3.2箭頭函數(shù)參數(shù)
箭頭函數(shù)中沒有 arguments,只能使用 ... 動態(tài)獲取實參
<body>
<script>
// 1. 利用箭頭函數(shù)來求和
const getSum = (...arr) => {
let sum = 0
for (let i = 0; i < arr.length; i++) {
sum += arr[i]
}
return sum
}
const result = getSum(2, 3, 4)
console.log(result) // 9
</script>
3.3箭頭函數(shù) this
箭頭函數(shù)不會創(chuàng)建自己的this,它只會從自己的作用域鏈的上一層沿用this。文章來源:http://www.zghlxwxcb.cn/news/detail-436980.html
<script>
// 以前this的指向: 誰調用的這個函數(shù),this 就指向誰
// console.log(this) // window
// // 普通函數(shù)
// function fn() {
// console.log(this) // window
// }
// window.fn()
// // 對象方法里面的this
// const obj = {
// name: 'andy',
// sayHi: function () {
// console.log(this) // obj
// }
// }
// obj.sayHi()
// 2. 箭頭函數(shù)的this 是上一層作用域的this 指向
// const fn = () => {
// console.log(this) // window
// }
// fn()
// 對象方法箭頭函數(shù) this
// const obj = {
// uname: 'pink老師',
// sayHi: () => {
// console.log(this) // this 指向誰? window
// }
// }
// obj.sayHi()
const obj = {
uname: 'pink老師',
sayHi: function () {
console.log(this) // obj
let i = 10
const count = () => {
console.log(this) // obj
}
count()
}
}
obj.sayHi()
</script>

箭頭函數(shù)不會創(chuàng)建自己的this ,他只會從自己的作用域鏈的上一層沿用this文章來源地址http://www.zghlxwxcb.cn/news/detail-436980.html
到了這里,關于2023年web前端開發(fā)之JavaScript進階(一)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!