阿川私房教材:
學 JavaScript 前端,帶作品集去面試!

63 個專案實戰,寫出作品集,讓面試官眼前一亮!

立即開始免費試讀!

我們將深入探討以下主題:

  • 提升 (Hoisting)
  • 時間死區 (Temporal Dead Zone)
  • 函數與函數表達式的差異 (Diff b/w Function & Function Exp.)
  • 淺拷貝與深拷貝 (Shallow Copy vs Deep Copy)
  • Object.assign
  • Slice與Splice (Slice vs Splice)
  • forEach與Map (forEach vs Map)
  • 全局執行上下文 (Global Execution Context)
  • Polyfill
  • Map深入分析 (Map Deep Dive)
  • 類型強制轉換 (Type Coercion)

1. 提升 (Hoisting)

這是一種 JavaScript 機制,其中變量和函數的聲明在代碼執行之前被移動到其作用域的頂部

  • var 被提升並初始化為 undefined。
    console.log(a) // undefined
    var a = 10;
  • let 和 const 是被提升但未初始化。
    console.log(a) // 引用錯誤,時間死區。
    let a = 10;
  • 函數聲明是被提升的,我們可以在代碼的任何地方訪問它們。
    console.log(getNum());
    function getNum(){
    return 2*2;
    }
  • 函數表達式是未完全提升的
    console.log(getNum()); // 引用錯誤,getNum2 未定義:對 let 也一樣,TDZ
    const getNum = () => {
    return 2*2;
    }
    console.log(getNum4()); // 型別錯誤:getNum4 不是一個函數
    var getNum4 = () => {
    return 2*2;
    }

以上僅變量被提升,而不是函數。

2. 時間死區 (Temporal Dead Zone)

這是變量被定義和初始化之間的作用域/區塊的期間。

何時發生?

  • 僅適用於使用 let 和 const 聲明的變量。
  • 它基本上防止了在初始化之前訪問它們。
  • 在 TDZ 中訪問變量會導致引用錯誤。
  • TDZ 也適用於分配給 let 和 const 的函數表達式。

Javascript

3. 函數聲明與函數表達式的差異

函數聲明 (Function Dec.):使用 function 關鍵字定義的函數。

  • 它們是被提升的。
  • 在整個作用域內可用。

函數表達式 (Function Exp.):當一個函數被賦值給一個變量。

  • 不被提升。
  • 只有在聲明之後可用。

當您需要在作用域的任何地方訪問函數時,使用函數聲明。<br>
當需要更好地控制函數的使用時,使用函數表達式。

4. 淺拷貝與深拷貝 (Shallow Copy Vs Deep Copy)

淺拷貝 (Shallow Copy):僅複製頂層屬性,對於嵌套對象則傳遞引用。

let obj1 = { name: "Jayant", address: { city: "Delhi" } };
let obj2 = {...obj1};

obj2.address.city = "Gurugram";

console.log(obj1);
console.log(obj2);

// 兩者輸出相同 
// {
//     "name": "Jayant",
//     "address": {
//         "city": "Gurugram"
//     }
// }

淺拷貝的其他方法

  • Object.assign()
  • 展開運算子
  • 切片運算子 [新的陣列在嵌套對象的情況下將包含引用]
  • 連接運算子

深拷貝 (Deep Copy):複製所有屬性。

let obj1 = { name: "Jayant", address: { city: "Delhi" } };

// 創建深拷貝
let obj2 = JSON.parse(JSON.stringify(obj1));

obj2.address.city = "Mumbai"; // ✅ 只改變 obj2

console.log(obj1); // { name: "Jayant", address: { city: "Delhi" } }
console.log(obj2); // { name: "Jayant", address: { city: "Mumbai" } }

深拷貝的其他方法

  • 基於遞歸的自定義深拷貝函數

5. Object.assign()

這是一個 JavaScript 方法,用於將一個或多個對象的屬性複製到另一個對象中。

我們可以使用展開運算子來做相同的事情

// 語法:創建淺拷貝
Object.assign(<target>,...sources)

// 示例 
let obj1 = {
    name:"Jayant"
}

let obj2 = {
    status:"single"
}
// 如果兩個對象有相同的屬性,最後一個對象的值將覆蓋先前的值。
let obj3 = Object.assign({},obj1,obj2);

6. 切片與插入 (Slice Vs Splice)

切片 (Slice) 用於提取陣列的一部分。<br>
切片會創建淺拷貝。<br>
插入 (Splice) 用於添加/移除陣列中的元素。

Slice vs Splice

// 切片:arr.slice(start,end);
// 結尾是排除的,不會包含
const arr = [1,2,3,4,5,6,7];
const newArray = arr.slice(1,4); 
console.log(newArray); // [2,3,4 ]

console.log(arr.slice(-2)); // [6,7] , 獲取最後 2 個元素 

// 插入:arr.splice(start,deleteCount,...items);

const itemsToAdd = [2,3,4,5,6,7];
const newArr = [1,2,3,4,5,6,7];
console.log(newArr.splice(1,7,...itemsToAdd)); //[2,3,4,5,6,7]
console.log(newArr); // [1,2,3,4,5,6,7];

const newArr2 = [1,2,3,4,5,6,7];
console.log(newArr2.splice(-2));  // [6,7] // 移除最後 2 個元素
console.log(newArr2); // [1,2,3,4,5]

7. forEach與Map (ForEach Vs Map)

ForEach 和 Map 不支持提前返回,如果您想提前返回,可以使用 some、every、reduce 或 for of 迴圈。

[1,2,3,4,5,6,7].forEach((el)=>{
    if(!el){
        break;  // 這會報錯,對於 Map 也是如此
    }
    console.log(el)
})

ForEach Loop

8. 執行上下文 (Execution Context)

這是一個代碼執行的環境。<br>
它有 2 種類型:<br>
1) 全局執行上下文 (Global EC)
2) 函數執行上下文 (Function EC)

全局執行上下文 - 當 JS 文件運行時,首先運行的執行上下文。它包含全局變量和函數以及詞法環境。當程序停止時將被移除。

9. Polyfilling

這是編寫代碼以使舊瀏覽器兼容新功能的行為。

舊版瀏覽器(如 Internet Explorer)不支持 Array.includes()。為了添加這一功能,我們可以編寫一個 polyfill。

Array.includes() 的 Polyfill

if (!Array.prototype.includes) {
  Array.prototype.includes = function (element) {
    return this.indexOf(element) !== -1;
  };
}

10. Map深入分析

通常我們只給回調傳遞一個參數,但內部來說,map() 提供 4 個參數給回調函數。

// 在 forEach 的情況下,陣列也可以訪問
arr.map((value,index,array,this)=>{})

const arr = [1,2,3,4,5,65];

console.log(arr.map((val)=>{
    console.log(arr[0]);
    return val;
}));   // arr 已經在作用域中,因此有什麼需求?

稀疏陣列 - 缺少元素的陣列

const arr = [1,2,,5,6,,7,8,9] // 稀疏陣列 

// 預設情況下,map 跳過這些並且不會調用回調函數。
// 空位保留在陣列中

console.log(arr.map((val)=>{
    if(!val){
        return 0;
    }
    return val*2
})) // [2, 4, 6, 8, 10, empty, 130]

如果您想在稀疏陣列中提供缺省值而不是空值,可以利用 forEach 或展開運算子進行。

const arr = [1,2,,5,6,,7,8,9];

// 使用展開運算子將稀疏陣列轉換成包含明確 undefined 值的密集陣列 || 也可以使用 forEach。
console.log([...arr].map((val,index,arr)=>{
    if(!val){
        return arr[index-1] || 0;
    }
    return val*2
}))  // [2, 4, 2, 10, 12, 6, 14, 16, 18]

map() 在執行之前設置元素的範圍:指在第一次回調執行之前,正在被回調處理的元素是確定的。

  • 在執行過程中新增的元素將被忽略。
  • 被處理前刪除的元素將被跳過。

11. 類型強制轉換 (Type coercion)

自動將一種類型轉換為另一種類型。<br>
這主要以三種方式發生:

隱式強制轉換 → JS 自動轉換類型。<br>

console.log(10+"2"); // [串接]
console.log(10-"2"); // [轉換] - 轉為數字
console.log(10*"2"); // [轉換] - 轉為數字
console.log(10/"2"); // [轉換] - 轉為數字

console.log(true+1) // true 轉換為數字得到 1
// false -> 0

console.log(null+undefined) // NaN
// null -> 0 
// undefined -> NaN

console.log(null + 10);      // 10  (null → 0)
console.log(undefined + 10); // NaN (undefined → NaN)

const obj = { name: "John" };

console.log(String(obj)); // "[object Object]"
console.log(obj + "");    // "[object Object]"

// [] -> ""
console.log([] + 2); // "2" (陣列 → 空字串 → 串接)
console.log([] - 2); // -2 (陣列 → 空字串 → 0 - 2 = -2)

console.log([1] + [2]); // "12"

顯式強制轉換 → 我們手動使用函數轉換類型。<br>
抽象操作 → JS 中類型轉換的內部規則。


原文出處:https://dev.to/jay818/javascript-basics-for-a-senior-dev-1lob


共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。

阿川私房教材:
學 JavaScript 前端,帶作品集去面試!

63 個專案實戰,寫出作品集,讓面試官眼前一亮!

立即開始免費試讀!