0x01 深淺複製
-
開發中經常需要複製(複製)一個物件,如果直接賦值,則對複製物件的修改會影響到源物件
const o1 = { a: 1, b: 2 } const o2 = o1 console.log(o2) // { a: 1, b: 2 } o2.a = 3 console.log(o1) // { a: 3, b: 2 } console.log(o2) // { a: 3, b: 2 }
原因在於,直接賦值的方法是在複製物件資料在棧中的地址,即兩個變數操作同一個位置的資料
-
深複製與淺複製只針對引用型別
(1)淺複製
- 淺複製只將物件或陣列的第一層進行復制,其他層級複製的是所儲存的記憶體地址
a. 物件
-
方法:
Object.assign(to, from)
或{...obj}
-
案例:
const o1 = { a: 1, b: 2 } const o2 = {} Object.assign(o2, o1) console.log(o2) // { a: 1, b: 2 } o2.a = 3 console.log(o1) // { a: 1, b: 2 } console.log(o2) // { a: 3, b: 2 } const o3 = {...o1} console.log(o3) // { a: 1, b: 2 }
-
淺複製僅複製第一層資料,而不會深入
const o1 = { a: 1, b: { c: 2 } } const o2 = {} Object.assign(o2, o1) o2.b.c = 3 console.log(o1) // { a: 1, b: { c: 3 } } console.log(o2) // { a: 1, b: { c: 3 } }
b. 陣列
-
方法:
Array.prototype.concat()
或[...array]
-
案例:
const a1 = [1, 2, 3] const a2 = a1.concat([]) console.log(a2) // [ 1, 2, 3 ] const a3 = [...a1] console.log(a3) // [ 1, 2, 3 ]
(2)深複製
-
深複製會構造一個新的複合陣列或物件,遇到引用所指向的引用資料型別會繼續執行複製
-
常見方法:
a. 遞迴方法
-
遞回應用舉例:透過
setTimeout
模擬setInterval
效果實現頁面中的時鐘每秒重新整理document.body.appendChild(document.createElement('div')) function getTime() { document.querySelector('div').innerHTML = new Date().toLocaleString() setTimeout(getTime, 1000) } getTime()
-
基於遞迴方法的深複製案例:
const o1 = { a: 1, b: { c: 2 } } const o2 = {} function deepCopy(newObj, oldObj) { // 遍歷oldObj中的所有屬性 for (let key in oldObj) { // 如果屬性值是物件,則遞迴進行深度複製 if (typeof oldObj[key] === 'object') { newObj[key] = {} // 為newObj建立一個空物件作為屬性值 deepCopy(newObj[key], oldObj[key]) // 遞迴呼叫deepCopy函式複製屬性值中的所有屬性 } else { // 如果屬性值不是物件,直接複製 newObj[key] = oldObj[key] } } } deepCopy(o2, o1) console.log(o2) // { a: 1, b: { c: 2 } } o2.b.c = 3 console.log(o1) // { a: 1, b: { c: 2 } } console.log(o2) // { a: 1, b: { c: 3 } }
b. Lodash
- Lodash 是第三方 JS 庫,官網連結
- 引入 Lodash:https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js
const o1 = {
a: 1,
b: {
c: 2
}
}
const o2 = _.cloneDeep(o1)
console.log(o2)
c. JSON 方法
const o1 = {
a: 1,
b: {
c: 2
}
}
const o2 = JSON.parse(JSON.stringify(o1))
console.log(o2)
0x02 異常處理
- 定義:預估程式碼執行過程中可能發生的錯誤,使用特定的方法對這些錯誤進行合適的處理
- 意義:有助於提高程式碼健壯性
(1)throw 丟擲異常
function division(x, y) {
if(!x || !y) {
throw "The parameter cannot be empty"
}
if(y === 0) {
throw new Error("Divisor cannot be zero")
}
}
division()
throw
丟擲異常資訊,程式也會中止Error
物件常配合throw
使用,能夠設定更詳細的錯誤訊息
(2)try...catch 捕獲異常
document.body.appendChild(document.createElement('p'))
function fun() {
try {
// Correct: document.querySelector('p').style.color = 'red'
document.querySelector('.p').style.color = 'red'
} catch (e) {
console.log("Catch a error: ", e.message)
} finally {
console.log("Finally")
}
}
fun()
- 用於捕獲錯誤資訊
try
中寫入可能會發生錯誤的程式碼catch
中寫入捕獲錯誤後的處理finally
中寫入無論是否出錯都會執行的程式碼
(3)debugger
const btn = document.createElement('button')
btn.onclick = function () {
debugger
}
btn.textContent = 'Debug'
document.body.appendChild(btn)
- 使用
debugger
可以進入逐步除錯
0x03 處理 this
(1)this 指向
a. 普通函式
-
普通函式的呼叫方式決定了
this
的值const obj = { a: function fun() { console.log(this); } } obj.a() // {a:f}
-
沒有明確的呼叫方法時,
this
的值為window
function fun() { console.log(this) // [object Window] } fun()
-
嚴格模式下沒有明確的呼叫方法時,
this
的值為undefined
'use strict' function fun() { console.log(this) // undefined } fun()
b. 箭頭函式
-
箭頭函式不存在
this
- 箭頭函式會預設繫結外層
this
的值 - 箭頭函式中的
this
引用的是最近作用域中的this
- 箭頭函式會向外層作用域中一層層查詢
this
,直至找到有this
的定義
const obj = { a: () => { console.log(this) } } obj.a() // [object Window]
- 箭頭函式會預設繫結外層
-
在開發中,使用箭頭函式前需要考慮函式中
this
的值const btn = document.createElement("button") btn.textContent = "Click" btn.addEventListener("click", function() { console.log(this) // <button>Click</button> }) btn.addEventListener("click", () => { console.log(this) // [object Window] }) document.body.appendChild(btn)
-
基於原型的物件導向不推薦採用箭頭函式
function Obj() {} Obj.prototype.a = () => { console.log(this) } const obj = new Obj() obj.a() // [object Window]
(2)改變 this
- 有三個方法可以動態指定普通函式中
this
的指向
a. call()
-
語法:
call(thisArg, arg1, arg2, ...)
thisArg
:在函式執行時,指定this
的值arg1, arg2, ...
:傳參- 返回值就是函式的返回值
-
舉例
const obj = { a: 0 } function fun(x, y) { console.log(x, y, this) // 1 2 {a: 0} } fun.call(obj, 1, 2)
b. apply()
-
語法:
fun.apply(thisArg, [argsArray])
thisArg
:在函式執行時,指定this
的值argsArray
:傳參,必須包含在陣列裡面- 返回值就是函式的返回值
- 因此
apply
主要跟陣列有關係
-
舉例
function sum(x, y) { console.log(this) // [object Window] return x + y } console.log(sum.apply(null, [1, 2])) // 3 console.log(Math.max.apply(Math, [1, 2, 3])) // 3
c. bind()
-
bind
方法不會呼叫函式,但是也可以改變函式內部的this
指向 -
語法:
bind(thisArg, arg1, arg2, ...)
thisArg
:在函式執行時,指定this
的值arg1, arg2, ...
:傳參,必須包含在陣列裡面- 返回值由指定的
this
值和初始化引數改造的原函式複製
-
舉例
const obj = { a: 0 } function fun(x, y) { console.log(x, y, this) } fun.bind(obj, 1, 2)() // 1 2 {a: 0}
0x04 效能最佳化
(1)防抖
-
防抖(debounce):單位時間內,頻繁觸發事件,只執行最後一次
- 觸發事件後,在 \(n\) 秒內函式只能執行一次,如果在 \(n\) 秒內再次被觸發,則重新計算函式執行時間
-
使用場景:常用於輸入事件的處理中,以減少不必要的計算或操作
-
舉例:滑鼠在盒子上移動,每 500ms 盒內數字加一
const box = document.createElement('div') document.body.appendChild(box) let cnt = 1 function add() { box.innerHTML = cnt++ } function debounce(func, timeMs) { let timer return function () { if (timer) clearTimeout(timer) timer = setTimeout(function () { func() }, timeMs) } } box.addEventListener("mousemove", debounce(add, 500))
-
防抖函式的封裝說明
/** * @param {Function} func 要執行的函式。 * @param {number} timeMs 延遲的時間,單位為毫秒。 * @returns {Function} 返回一個新的函式,該函式具有防抖功能。 */ function debounce(func, timeMs) { let timer // 用於儲存定時器的變數 // 返回一個新的函式,該函式會延遲執行傳入的func函式 return function () { if (timer) clearTimeout(timer) // 如果存在定時器,則清除,以防止之前設定的執行被觸發 // 設定一個新的定時器,當延遲時間過去後,執行func函式 timer = setTimeout(function () { func() }, timeMs) } }
(2)節流
-
節流(throttle):單位時間內,頻繁觸發事件,只執行一次
- 連續觸發事件,但在 \(n\) 秒內僅執行一次函式
-
使用場景:常用於高頻事件的處理中,以減少不必要的效能消耗
-
舉例:滑鼠在盒子上移動,每隔 500ms 盒內數字加一
const box = document.createElement('div') document.body.appendChild(box) let cnt = 1 function add() { box.innerHTML = cnt++ } function throttle(func, timeMs) { let timer = null return function () { if (!timer) { timer = setTimeout(function() { func() timer = null }, timeMs) } } } box.addEventListener("mousemove", throttle(add, 500))
-
節流函式封裝說明
/** * @param {Function} func 要節流的函式 * @param {number} timeMs 節流的時間間隔(毫秒) * @returns {Function} 返回一個新函式,新函式將控制原函式在指定時間間隔內只執行一次 */ function throttle(func, timeMs) { let timer = null // 利用閉包儲存一個定時器變數 return function () { // 如果定時器不存在,則設定定時器 if (!timer) { timer = setTimeout(function() { func() // 在指定時間間隔後執行原函式 timer = null // 執行後重置定時器變數 }, timeMs) } } }
-End-