第一章 簡介
1.1 ES6概述
1.1.1 ES6定義與發展歷程
1. ES6 定義
ES6 全稱 ECMAScript 6.0,它是 JavaScript 語言的下一代標準,對 JavaScript 語言進行了許多增強和擴展,帶來了更簡潔、更強大的語法特性。可以把 ES6 想象成是 JavaScript 的一次“超級進化”,就像游戲里角色升級后擁有了更厲害的技能一樣😎。
2. 發展歷程
- 起源:JavaScript 早期缺乏統一標準,不同瀏覽器實現存在差異。為了規范和發展 JavaScript,ECMA 國際標準化組織開始制定 ECMAScript 標準。
- ES6 誕生:2015 年 6 月,ECMAScript 6.0 正式發布,它包含了許多新的語法特性和 API,如箭頭函數、模板字符串、Promise 對象等。此后,為了更好地適應技術發展和需求,ECMA 采用了每年發布一次新版本的策略,所以 ES6 也被稱為 ECMAScript 2015。
- 后續版本:在 ES6 之后,陸續推出了 ECMAScript 2016、2017 等版本,不斷對語言進行完善和擴展,但 ES6 是其中具有里程碑意義的版本,很多新特性被廣泛應用和使用🎉。
1.1.2 ES6在前端開發中的重要性
1. 提高開發效率
ES6 提供了許多簡潔的語法糖,例如箭頭函數可以讓函數的定義更加簡潔,減少了代碼量。就像給開發者配備了一把超級鋒利的“代碼剪刀”,能夠快速裁剪出所需的代碼片段??。再比如模板字符串,讓字符串的拼接變得更加直觀和方便,避免了繁瑣的加號拼接操作。
2. 增強代碼可讀性和可維護性
新的語法特性使得代碼結構更加清晰,例如使用 let
和 const
代替 var
來聲明變量,能夠更好地控制變量的作用域,減少變量污染和意外賦值的問題。類和繼承的語法讓面向對象編程在 JavaScript 中更加直觀和易于理解,就像給代碼穿上了一件整潔的“外衣”,讓人一目了然👕。
3. 推動前端框架發展
許多現代前端框架,如 React、Vue、Angular 等,都廣泛使用了 ES6 的特性。ES6 的出現為這些框架的發展提供了強大的支持,使得開發者能夠使用更先進的編程思想和技術來構建復雜的前端應用。可以說 ES6 是前端框架發展的“助推器”🚀。
1.2 環境搭建
1.2.1 Babel的安裝與配置
1. 什么是 Babel
Babel 是一個 JavaScript 編譯器,它的主要作用是將 ES6+ 代碼轉換為向后兼容的 JavaScript 代碼,以便在舊版本的瀏覽器或環境中運行。簡單來說,Babel 就像是一個“翻譯官”,把新的 ES6 代碼“翻譯”成舊瀏覽器能“聽懂”的代碼👨?🏫。
2. 安裝 Babel
首先,確保你已經安裝了 Node.js 和 npm(Node 包管理器)。然后在項目根目錄下打開終端,執行以下命令來安裝 Babel 的核心庫和命令行工具:
npm install --save-dev @babel/core @babel/cli
這里的 @babel/core
是 Babel 的核心編譯庫,@babel/cli
是命令行工具,用于在命令行中執行 Babel 編譯操作。
3. 配置 Babel
在項目根目錄下創建一個 .babelrc
文件,用于配置 Babel 的轉換規則。例如,如果你想將 ES6+ 代碼轉換為 ES5 代碼,可以安裝 @babel/preset-env
預設,并在 .babelrc
文件中進行配置:
npm install --save-dev @babel/preset-env
在 .babelrc
文件中添加以下內容:
{"presets": ["@babel/preset-env"]
}
這樣,Babel 就會根據 @babel/preset-env
預設來進行代碼轉換。
4. 執行編譯
安裝和配置完成后,就可以使用 Babel 來編譯 ES6 代碼了。假設你的 ES6 代碼文件名為 index.js
,可以在終端中執行以下命令進行編譯:
npx babel index.js --out-file output.js
這會將 index.js
文件中的 ES6 代碼編譯成 ES5 代碼,并輸出到 output.js
文件中。
1.2.2 使用Webpack打包ES6代碼
1. 什么是 Webpack
Webpack 是一個模塊打包工具,它可以將多個模塊打包成一個或多個文件,優化資源加載,提高應用性能。可以把 Webpack 想象成一個“打包工人”,它會把項目中的各種資源(如 JavaScript、CSS、圖片等)打包成一個或幾個文件,方便瀏覽器加載📦。
2. 安裝 Webpack
同樣,在項目根目錄下打開終端,執行以下命令來安裝 Webpack 和 Webpack CLI:
npm install --save-dev webpack webpack-cli
3. 配置 Webpack
在項目根目錄下創建一個 webpack.config.js
文件,用于配置 Webpack 的打包規則。以下是一個簡單的配置示例:
const path = require('path');module.exports = {entry: './src/index.js', // 入口文件output: {filename: 'bundle.js', // 輸出文件名path: path.resolve(__dirname, 'dist') // 輸出目錄},module: {rules: [{test: /\.js$/, // 匹配 .js 文件exclude: /node_modules/, // 排除 node_modules 目錄use: {loader: 'babel-loader', // 使用 babel-loader 處理 .js 文件options: {presets: ['@babel/preset-env']}}}]}
};
這個配置文件指定了入口文件、輸出文件和目錄,以及使用 babel-loader
來處理 .js
文件,確保 ES6 代碼能夠被正確編譯。
4. 執行打包
安裝和配置完成后,在終端中執行以下命令來進行打包:
npx webpack --mode production
這里的 --mode production
表示以生產模式進行打包,Webpack 會對代碼進行壓縮和優化。打包完成后,會在 dist
目錄下生成一個 bundle.js
文件,這個文件就是打包后的文件,可以在 HTML 文件中引入使用。
🎉 通過以上步驟,你就完成了 ES6 開發環境的搭建,可以愉快地使用 ES6 進行前端開發啦!
第二章 變量聲明
2.1 let 關鍵字
2.1.1 let 的塊級作用域
在 JavaScript 中,let
關鍵字具有塊級作用域的特性。塊級作用域是指由一對花括號 {}
所包含的代碼區域。使用 let
聲明的變量只在當前的塊級作用域內有效。
🌰 示例代碼如下:
{let blockVariable = '我在塊級作用域內';console.log(blockVariable); // 輸出: 我在塊級作用域內
}
console.log(blockVariable); // 報錯,blockVariable 未定義
在這個例子中,blockVariable
是使用 let
聲明在塊級作用域內的變量。在塊級作用域內部可以正常訪問該變量,但在塊級作用域外部嘗試訪問時就會報錯,因為它超出了作用域范圍。
2.1.2 let 不存在變量提升
在 JavaScript 中,使用 var
聲明的變量會存在變量提升的現象,即變量可以在聲明之前被訪問,只是值為 undefined
。而使用 let
聲明的變量不存在變量提升。
🌰 示例代碼如下:
console.log(letVariable); // 報錯,letVariable 未定義
let letVariable = '我是用 let 聲明的變量';
在這個例子中,由于 let
不存在變量提升,所以在聲明 letVariable
之前嘗試訪問它會直接報錯,而不是像 var
那樣返回 undefined
。
2.1.3 let 不允許重復聲明
使用 let
聲明變量時,不允許在同一作用域內對同一個變量進行重復聲明。
🌰 示例代碼如下:
let singleVariable = '第一次聲明';
let singleVariable = '重復聲明'; // 報錯,SyntaxError: Identifier 'singleVariable' has already been declared
在這個例子中,嘗試對 singleVariable
進行重復聲明時會拋出語法錯誤,這體現了 let
不允許重復聲明的特性。
2.2 const 關鍵字
2.2.1 const 聲明常量
const
關鍵字用于聲明常量,一旦聲明就必須賦值,并且在后續代碼中不能再重新賦值。
🌰 示例代碼如下:
const constantValue = 10;
constantValue = 20; // 報錯,TypeError: Assignment to constant variable.
在這個例子中,constantValue
被聲明為常量,嘗試對其重新賦值時會拋出類型錯誤,因為常量的值是不可變的。
2.2.2 const 的塊級作用域
和 let
一樣,const
也具有塊級作用域的特性。使用 const
聲明的常量只在當前的塊級作用域內有效。
🌰 示例代碼如下:
{const blockConstant = '我是塊級作用域內的常量';console.log(blockConstant); // 輸出: 我是塊級作用域內的常量
}
console.log(blockConstant); // 報錯,blockConstant 未定義
在這個例子中,blockConstant
是使用 const
聲明在塊級作用域內的常量。在塊級作用域內部可以正常訪問該常量,但在塊級作用域外部嘗試訪問時就會報錯,因為它超出了作用域范圍。
2.2.3 const 聲明引用類型
雖然 const
聲明的常量不能重新賦值,但如果聲明的是引用類型(如對象、數組等),可以修改其內部的屬性或元素。
🌰 示例代碼如下:
const person = {name: '張三',age: 20
};
person.age = 21; // 可以修改對象的屬性
console.log(person.age); // 輸出: 21const numberArray = [1, 2, 3];
numberArray.push(4); // 可以修改數組的元素
console.log(numberArray); // 輸出: [1, 2, 3, 4]
在這個例子中,person
是一個對象,numberArray
是一個數組,雖然它們是使用 const
聲明的常量,但可以對其內部的屬性和元素進行修改,因為 const
只是保證引用的地址不變,而不是對象或數組的內容不變。
2.3 var 與 let、const 對比
2.3.1 作用域區別
- var:使用
var
聲明的變量具有函數作用域,即變量在整個函數內部都有效,而不是塊級作用域。
🌰 示例代碼如下:
function varScope() {if (true) {var varVariable = '我是用 var 聲明的變量';}console.log(varVariable); // 輸出: 我是用 var 聲明的變量
}
varScope();
在這個例子中,varVariable
是在 if
語句塊內使用 var
聲明的變量,但在 if
語句塊外部仍然可以訪問,因為 var
具有函數作用域。
- let 和 const:使用
let
和const
聲明的變量具有塊級作用域,只在當前的塊級作用域內有效。前面已經有相關示例,這里不再贅述。
2.3.2 變量提升區別
- var:使用
var
聲明的變量會存在變量提升的現象,變量可以在聲明之前被訪問,只是值為undefined
。
🌰 示例代碼如下:
console.log(varLifted); // 輸出: undefined
var varLifted = '我是用 var 聲明的變量';
在這個例子中,在聲明 varLifted
之前嘗試訪問它,會返回 undefined
,這體現了 var
的變量提升特性。
- let 和 const:使用
let
和const
聲明的變量不存在變量提升,在聲明之前訪問會報錯。前面已經有相關示例,這里不再贅述。
2.3.3 重復聲明區別
- var:使用
var
聲明變量時,允許在同一作用域內對同一個變量進行重復聲明。
🌰 示例代碼如下:
var repeatedVar = '第一次聲明';
var repeatedVar = '重復聲明';
console.log(repeatedVar); // 輸出: 重復聲明
在這個例子中,對 repeatedVar
進行重復聲明是允許的,并且后面的聲明會覆蓋前面的聲明。
- let 和 const:使用
let
和const
聲明變量時,不允許在同一作用域內對同一個變量進行重復聲明。前面已經有相關示例,這里不再贅述。
綜上所述,var
、let
和 const
在作用域、變量提升和重復聲明方面存在明顯的區別,在實際開發中需要根據具體需求選擇合適的關鍵字來聲明變量。💡
第三章 箭頭函數
箭頭函數是 ES6 中引入的一種簡潔的函數定義方式,它為 JavaScript 開發者提供了更加便捷的編碼體驗。下面我們來詳細了解一下箭頭函數的相關知識。
3.1 箭頭函數語法
3.1.1 基本語法形式
箭頭函數的基本語法形式根據參數的數量和函數體的復雜程度有所不同:
- 單個參數:當函數只有一個參數時,可以直接寫參數名,后面緊跟箭頭
=>
,再跟上函數體。
// 示例
const square = num => num * num;
console.log(square(5)); // 輸出 25
在這個例子中,num
是參數,num * num
是函數體,箭頭函數會自動返回函數體的計算結果。
- 多個參數:如果函數有多個參數,需要用括號將參數括起來,再緊跟箭頭和函數體。
// 示例
const add = (a, b) => a + b;
console.log(add(3, 4)); // 輸出 7
這里 (a, b)
表示有兩個參數,a + b
是函數體,同樣會自動返回計算結果。
- 無參數:當函數沒有參數時,需要使用一對空括號表示。
// 示例
const greet = () => 'Hello!';
console.log(greet()); // 輸出 Hello!
這里 ()
表示沒有參數,'Hello!'
是函數體,會返回這個字符串。
- 復雜函數體:如果函數體包含多條語句,需要用花括號
{}
將函數體括起來,并且使用return
語句來返回結果。
// 示例
const calculate = (x, y) => {let sum = x + y;let product = x * y;return {sum: sum, product: product};
};
console.log(calculate(2, 3)); // 輸出 { sum: 5, product: 6 }
在這個例子中,函數體有兩條語句,所以用花括號括起來,并且使用 return
語句返回一個對象。
3.1.2 省略括號和花括號的情況
- 省略括號:當函數只有一個參數時,可以省略參數的括號。
// 示例
const double = num => num * 2;
這里 num
作為單個參數,沒有使用括號。
- 省略花括號:當函數體只有一條語句時,可以省略花括號,并且該語句的計算結果會自動作為返回值。
// 示例
const multiply = (a, b) => a * b;
這里函數體只有 a * b
這一條語句,省略了花括號,并且會自動返回計算結果。
3.2 箭頭函數特點
3.2.1 沒有自己的this
普通函數的 this
值取決于函數的調用方式,而箭頭函數沒有自己的 this
,它的 this
值繼承自外層函數。
// 示例
const person = {name: 'John',sayHello: function() {// 普通函數的 this 指向 person 對象console.log(this.name); const arrowFunc = () => {// 箭頭函數的 this 繼承自外層函數 sayHello,也指向 person 對象console.log(this.name); };arrowFunc();}
};
person.sayHello();
在這個例子中,箭頭函數 arrowFunc
的 this
繼承自外層的 sayHello
函數,所以 this
指向 person
對象。
3.2.2 不能使用arguments對象
arguments
對象是一個類數組對象,它包含了函數調用時的所有參數。但是箭頭函數沒有自己的 arguments
對象。
// 示例
const normalFunc = function() {console.log(arguments);
};
normalFunc(1, 2, 3); const arrowFunc = () => {// 這里會報錯,因為箭頭函數沒有 arguments 對象console.log(arguments);
};
arrowFunc(4, 5, 6);
在普通函數 normalFunc
中可以使用 arguments
對象獲取所有參數,而在箭頭函數 arrowFunc
中使用會報錯。
3.2.3 不能使用yield關鍵字
yield
關鍵字用于生成器函數,它可以暫停和恢復函數的執行。但是箭頭函數不能使用 yield
關鍵字,所以箭頭函數不能作為生成器函數。
// 示例
// 普通生成器函數
function* normalGenerator() {yield 1;yield 2;
}
const gen = normalGenerator();
console.log(gen.next().value); // 輸出 1// 箭頭函數不能使用 yield 關鍵字,下面的代碼會報錯
// const arrowGenerator = () => {
// yield 3;
// };
這里普通生成器函數 normalGenerator
可以正常使用 yield
關鍵字,而如果嘗試在箭頭函數中使用 yield
會報錯。
3.3 箭頭函數應用場景
3.3.1 簡單的回調函數
在處理數組的方法(如 map
、filter
、reduce
等)時,經常會使用回調函數,箭頭函數可以讓回調函數的代碼更加簡潔。
// 示例
const numbers = [1, 2, 3, 4, 5];
// 使用箭頭函數作為 map 方法的回調函數
const squaredNumbers = numbers.map(num => num * num);
console.log(squaredNumbers); // 輸出 [1, 4, 9, 16, 25]
這里使用箭頭函數作為 map
方法的回調函數,簡潔地實現了對數組元素的平方操作。
3.3.2 簡化對象方法
當對象的方法邏輯比較簡單時,可以使用箭頭函數來簡化代碼。
// 示例
const calculator = {add: (a, b) => a + b,subtract: (a, b) => a - b
};
console.log(calculator.add(5, 3)); // 輸出 8
console.log(calculator.subtract(5, 3)); // 輸出 2
在這個例子中,使用箭頭函數定義了 add
和 subtract
方法,讓對象的代碼更加簡潔。
總之,箭頭函數以其簡潔的語法和獨特的特點,在很多場景下都能提高代碼的可讀性和開發效率,但也需要注意它的一些限制哦😃。
第四章 模板字符串
4.1 模板字符串語法
4.1.1 使用反引號定義
在 JavaScript 中,傳統的字符串通常使用單引號('
)或雙引號("
)來定義😃。而模板字符串則是使用反引號(`
)來定義。這種定義方式為字符串的創建帶來了更多的靈活性。
下面是一個簡單的示例:
// 使用單引號定義字符串
let str1 = '這是一個普通字符串';
// 使用雙引號定義字符串
let str2 = "這也是一個普通字符串";
// 使用反引號定義模板字符串
let templateStr = `這是一個模板字符串`;console.log(str1);
console.log(str2);
console.log(templateStr);
在這個示例中,我們可以看到使用反引號定義的模板字符串和傳統字符串在基本使用上的區別。模板字符串的使用讓代碼在處理字符串時更加直觀和方便👍。
4.1.2 嵌入變量和表達式
模板字符串的一個強大特性就是可以嵌入變量和表達式。我們可以使用 ${}
語法來實現這一點。在 ${}
中,我們可以放入變量、函數調用或者更復雜的表達式。
以下是具體示例:
let name = '張三';
let age = 20;
// 嵌入變量
let info = `我的名字是 ${name},今年 ${age} 歲。`;
console.log(info);// 嵌入表達式
let num1 = 5;
let num2 = 3;
let result = `5 加 3 的結果是 ${num1 + num2}。`;
console.log(result);
在上述代碼中,我們可以看到 ${}
語法的強大之處。它讓我們可以很方便地將變量和表達式的結果插入到字符串中,避免了傳統字符串拼接時的繁瑣操作👏。
4.2 模板字符串功能
4.2.1 多行字符串
在傳統的 JavaScript 中,要創建多行字符串是比較麻煩的,通常需要使用換行符(\n
)來實現。而模板字符串可以很輕松地處理多行字符串,只需要在反引號內直接換行即可。
示例如下:
// 傳統方式創建多行字符串
let multiLineStr1 = '這是第一行。\n這是第二行。';
console.log(multiLineStr1);// 使用模板字符串創建多行字符串
let multiLineStr2 = `這是第一行。
這是第二行。`;
console.log(multiLineStr2);
通過對比可以發現,使用模板字符串創建多行字符串更加直觀和簡潔,代碼的可讀性也大大提高了😀。
4.2.2 標簽模板
標簽模板是模板字符串的一個高級特性。它允許我們使用一個函數來處理模板字符串。這個函數可以對模板字符串的各個部分進行自定義處理。
下面是一個簡單的標簽模板示例:
function myTag(strings, ...values) {let result = '';strings.forEach((string, index) => {result += string;if (index < values.length) {result += values[index];}});return result;
}let name = '李四';
let message = myTag`你好,${name},歡迎來到我們的世界!`;
console.log(message);
在這個示例中,myTag
函數就是一個標簽函數。它接收兩個參數:strings
是一個包含模板字符串中普通字符串部分的數組,values
是一個包含模板字符串中嵌入的變量或表達式結果的數組。通過這種方式,我們可以對模板字符串進行更靈活的處理🧐。
4.3 模板字符串應用場景
4.3.1 動態生成 HTML 片段
在前端開發中,我們經常需要動態生成 HTML 片段。模板字符串可以讓這個過程變得非常簡單。
示例如下:
let users = [{ name: '王五', age: 25 },{ name: '趙六', age: 30 }
];let html = `<ul>${users.map(user => `<li>姓名:${user.name},年齡:${user.age}</li>`).join('')}</ul>
`;document.body.innerHTML = html;
在這個示例中,我們使用模板字符串動態生成了一個包含用戶信息的無序列表。通過嵌入變量和使用數組的 map
方法,我們可以很方便地處理多個數據項,并且生成對應的 HTML 結構。這種方式讓代碼更加簡潔和易于維護🎉。
4.3.2 格式化字符串
模板字符串還可以用于格式化字符串,特別是在需要將數據按照一定格式輸出時非常有用。
示例如下:
let price = 19.99;
let formattedStr = `商品價格:${price.toFixed(2)} 元`;
console.log(formattedStr);
在這個示例中,我們使用 toFixed(2)
方法將價格保留兩位小數,并將結果嵌入到模板字符串中。這樣就可以很方便地將數據按照我們需要的格式輸出,提高了代碼的可讀性和用戶體驗😎。
第五章 解構賦值
解構賦值是 JavaScript 中一種非常實用的語法,它允許你從數組或對象中提取值,并賦值給變量。下面我們將詳細介紹數組解構賦值、對象解構賦值以及它們的應用場景。
5.1 數組解構賦值
5.1.1 基本解構形式
數組解構賦值的基本形式是通過方括號 []
來匹配數組中的元素,并將其賦值給對應的變量。
示例代碼如下:
const arr = [1, 2, 3];
const [a, b, c] = arr;
console.log(a); // 輸出: 1
console.log(b); // 輸出: 2
console.log(c); // 輸出: 3
在這個例子中,我們定義了一個數組 arr
,然后使用數組解構賦值將數組中的元素依次賦值給變量 a
、b
、c
。😎
5.1.2 剩余參數解構
當我們只需要提取數組中的部分元素,而將剩余的元素收集到一個新的數組中時,可以使用剩余參數解構。使用三個點 ...
來表示剩余參數。
示例代碼如下:
const arr = [1, 2, 3, 4, 5];
const [a, b, ...rest] = arr;
console.log(a); // 輸出: 1
console.log(b); // 輸出: 2
console.log(rest); // 輸出: [3, 4, 5]
在這個例子中,我們將數組 arr
的前兩個元素賦值給變量 a
和 b
,然后將剩余的元素收集到數組 rest
中。🤩
5.1.3 解構默認值
當數組中的元素不存在或為 undefined
時,我們可以為解構的變量設置默認值。
示例代碼如下:
const arr = [1];
const [a, b = 2] = arr;
console.log(a); // 輸出: 1
console.log(b); // 輸出: 2
在這個例子中,數組 arr
只有一個元素,變量 a
被賦值為 1,由于數組中沒有第二個元素,變量 b
使用了默認值 2。🥳
5.2 對象解構賦值
5.2.1 基本解構形式
對象解構賦值的基本形式是通過花括號 {}
來匹配對象中的屬性,并將其賦值給對應的變量。變量名必須與對象的屬性名相同。
示例代碼如下:
const obj = { name: 'John', age: 30 };
const { name, age } = obj;
console.log(name); // 輸出: 'John'
console.log(age); // 輸出: 30
在這個例子中,我們定義了一個對象 obj
,然后使用對象解構賦值將對象的屬性 name
和 age
賦值給對應的變量。😃
5.2.2 嵌套對象解構
當對象中包含嵌套對象時,我們可以使用嵌套的解構語法來提取嵌套對象的屬性。
示例代碼如下:
const obj = {person: {name: 'John',age: 30}
};
const { person: { name, age } } = obj;
console.log(name); // 輸出: 'John'
console.log(age); // 輸出: 30
在這個例子中,對象 obj
包含一個嵌套對象 person
,我們使用嵌套的解構語法提取了 person
對象的 name
和 age
屬性。🤓
5.2.3 解構重命名
當我們想要使用不同的變量名來接收對象的屬性值時,可以使用解構重命名。使用冒號 :
來指定新的變量名。
示例代碼如下:
const obj = { name: 'John', age: 30 };
const { name: fullName, age: personAge } = obj;
console.log(fullName); // 輸出: 'John'
console.log(personAge); // 輸出: 30
在這個例子中,我們將對象 obj
的 name
屬性賦值給變量 fullName
,age
屬性賦值給變量 personAge
。😜
5.3 解構賦值應用場景
5.3.1 交換變量值
使用解構賦值可以很方便地交換兩個變量的值,而不需要使用臨時變量。
示例代碼如下:
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a); // 輸出: 2
console.log(b); // 輸出: 1
在這個例子中,我們使用數組解構賦值交換了變量 a
和 b
的值。🎉
5.3.2 函數參數解構
當函數的參數是一個對象時,使用解構賦值可以使函數的參數更加清晰和靈活。
示例代碼如下:
function printPerson({ name, age }) {console.log(`Name: ${name}, Age: ${age}`);
}
const person = { name: 'John', age: 30 };
printPerson(person); // 輸出: 'Name: John, Age: 30'
在這個例子中,函數 printPerson
的參數使用了對象解構賦值,這樣在調用函數時,只需要傳遞一個包含 name
和 age
屬性的對象即可。👏
通過以上介紹,我們了解了數組解構賦值、對象解構賦值以及它們的應用場景,解構賦值可以讓我們的代碼更加簡潔和易讀。🤗
第六章 擴展運算符
擴展運算符(Spread Operator)是 ES6 引入的一個非常實用的特性,它使用三個連續的點(...
)表示。擴展運算符可以將一個可迭代對象(如數組、對象等)展開為多個元素,在很多場景下能讓代碼變得更加簡潔和直觀。接下來我們將詳細介紹它在數組和對象中的應用以及一些常見的應用場景。
6.1 數組擴展運算符
6.1.1 復制數組
在 JavaScript 中,有時候我們需要復制一個數組,而不是僅僅復制它的引用。使用擴展運算符可以很方便地實現數組的復制。
示例代碼:
const originalArray = [1, 2, 3];
const copiedArray = [...originalArray];console.log(copiedArray); // 輸出: [1, 2, 3]
在這個例子中,...originalArray
將 originalArray
中的元素展開,然后將這些元素作為新數組 copiedArray
的元素。這樣就實現了數組的復制,并且 copiedArray
和 originalArray
是兩個獨立的數組,修改其中一個不會影響另一個😎。
6.1.2 合并數組
擴展運算符還可以用于合并多個數組。
示例代碼:
const array1 = [1, 2, 3];
const array2 = [4, 5, 6];
const mergedArray = [...array1, ...array2];console.log(mergedArray); // 輸出: [1, 2, 3, 4, 5, 6]
這里,...array1
和 ...array2
分別將兩個數組的元素展開,然后將這些元素合并到 mergedArray
中。我們還可以合并更多的數組,只需要在新數組中依次添加擴展運算符展開的數組即可👏。
6.1.3 展開數組作為函數參數
當我們需要將數組的元素作為參數傳遞給函數時,擴展運算符可以派上用場。
示例代碼:
function sum(a, b, c) {return a + b + c;
}const numbers = [1, 2, 3];
const result = sum(...numbers);console.log(result); // 輸出: 6
在這個例子中,...numbers
將 numbers
數組中的元素展開,分別作為 sum
函數的參數 a
、b
、c
。這樣就避免了手動一個一個地傳遞數組元素,讓代碼更加簡潔😃。
6.2 對象擴展運算符
6.2.2 復制對象
和復制數組類似,擴展運算符也可以用于復制對象。
示例代碼:
const originalObject = { name: 'John', age: 30 };
const copiedObject = {...originalObject };console.log(copiedObject); // 輸出: { name: 'John', age: 30 }
這里,...originalObject
將 originalObject
的屬性展開,然后將這些屬性復制到 copiedObject
中。同樣,copiedObject
和 originalObject
是兩個獨立的對象,修改其中一個不會影響另一個🤗。
6.2.2 合并對象
擴展運算符還可以用于合并多個對象。
示例代碼:
const object1 = { name: 'John' };
const object2 = { age: 30 };
const mergedObject = {...object1, ...object2 };console.log(mergedObject); // 輸出: { name: 'John', age: 30 }
在這個例子中,...object1
和 ...object2
分別將兩個對象的屬性展開,然后將這些屬性合并到 mergedObject
中。如果有相同的屬性名,后面的對象屬性會覆蓋前面的對象屬性😉。
6.2.3 展開對象屬性
在創建新對象時,我們可以使用擴展運算符展開一個對象的屬性,然后再添加新的屬性。
示例代碼:
const person = { name: 'John', age: 30 };
const newPerson = {...person, occupation: 'Engineer' };console.log(newPerson); // 輸出: { name: 'John', age: 30, occupation: 'Engineer' }
這里,...person
將 person
對象的屬性展開,然后在新對象 newPerson
中添加了一個新的屬性 occupation
。
6.3 擴展運算符應用場景
6.3.1 函數參數收集
在函數定義時,我們可以使用擴展運算符將剩余的參數收集到一個數組中。
示例代碼:
function collectParams(firstParam, ...restParams) {console.log(firstParam); // 輸出第一個參數console.log(restParams); // 輸出剩余的參數組成的數組
}collectParams(1, 2, 3, 4);
// 輸出:
// 1
// [2, 3, 4]
在這個例子中,firstParam
接收第一個參數,...restParams
將剩余的參數收集到一個數組中。這樣可以處理不定數量的參數,讓函數更加靈活👍。
6.3.2 淺拷貝對象和數組
前面我們已經介紹了使用擴展運算符復制數組和對象,這種復制方式實際上是淺拷貝。淺拷貝會復制對象或數組的一層屬性或元素,如果屬性或元素是引用類型,復制的只是引用,而不是對象本身。
示例代碼:
const originalArray = [1, [2, 3]];
const copiedArray = [...originalArray];copiedArray[1][0] = 99;
console.log(originalArray); // 輸出: [1, [99, 3]]
在這個例子中,copiedArray
是 originalArray
的淺拷貝,originalArray
中的第二個元素是一個數組,修改 copiedArray
中這個數組的元素會影響到 originalArray
中的對應元素。所以在使用淺拷貝時需要注意這一點😜。
通過以上介紹,我們可以看到擴展運算符在數組和對象操作以及一些常見場景中都非常有用,它讓代碼更加簡潔和易讀。希望大家能熟練掌握它的使用👏。
第七章 類與繼承
7.1 類的定義與使用
7.1.1 類的基本語法
在面向對象編程中,類是對象的抽象模板,它定義了對象的屬性和方法。在不同的編程語言中,類的定義語法可能會有所不同,下面以 Python 和 JavaScript 為例進行說明。
Python 中的類定義
# 定義一個簡單的類
class Person:pass# 創建類的實例
p = Person()
在 Python 中,使用 class
關鍵字來定義類,類名通常采用大寫字母開頭的駝峰命名法。pass
是一個占位語句,表示類的定義暫時為空。
JavaScript 中的類定義
// 定義一個簡單的類
class Person {// 類的主體
}// 創建類的實例
let p = new Person();
在 JavaScript 中,同樣使用 class
關鍵字來定義類,創建實例時需要使用 new
關鍵字。
7.1.2 類的構造函數
構造函數是類中的一個特殊方法,用于在創建對象時初始化對象的屬性。
Python 中的構造函數
class Person:def __init__(self, name, age):self.name = nameself.age = age# 創建實例并初始化屬性
p = Person("Alice", 25)
print(p.name) # 輸出: Alice
print(p.age) # 輸出: 25
在 Python 中,構造函數的名稱是 __init__
,它接受 self
參數,代表類的實例本身,后面可以跟其他參數用于初始化屬性。
JavaScript 中的構造函數
class Person {constructor(name, age) {this.name = name;this.age = age;}
}// 創建實例并初始化屬性
let p = new Person("Alice", 25);
console.log(p.name); // 輸出: Alice
console.log(p.age); // 輸出: 25
在 JavaScript 中,構造函數的名稱是 constructor
,同樣使用 this
來引用實例對象。
7.1.3 類的實例方法
實例方法是定義在類中的函數,它可以訪問和修改實例的屬性。
Python 中的實例方法
class Person:def __init__(self, name, age):self.name = nameself.age = agedef introduce(self):print(f"Hello, my name is {self.name} and I'm {self.age} years old.")p = Person("Alice", 25)
p.introduce() # 輸出: Hello, my name is Alice and I'm 25 years old.
在 Python 中,實例方法的第一個參數必須是 self
,通過 self
可以訪問實例的屬性。
JavaScript 中的實例方法
class Person {constructor(name, age) {this.name = name;this.age = age;}introduce() {console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);}
}let p = new Person("Alice", 25);
p.introduce(); // 輸出: Hello, my name is Alice and I'm 25 years old.
在 JavaScript 中,實例方法直接定義在類的主體中,同樣使用 this
來訪問實例的屬性。
7.2 類的繼承
7.2.1 extends 關鍵字
extends
關鍵字用于實現類的繼承,它允許一個類繼承另一個類的屬性和方法。
Python 中的繼承
class Animal:def speak(self):print("Animal speaks")class Dog(Animal):passd = Dog()
d.speak() # 輸出: Animal speaks
在 Python 中,通過在類名后面的括號中指定父類名來實現繼承。
JavaScript 中的繼承
class Animal {speak() {console.log("Animal speaks");}
}class Dog extends Animal {// 子類的主體
}let d = new Dog();
d.speak(); // 輸出: Animal speaks
在 JavaScript 中,使用 extends
關鍵字來指定父類。
7.2.2 super 關鍵字
super
關鍵字用于調用父類的構造函數或方法。
Python 中的 super()
class Animal:def __init__(self, name):self.name = namedef speak(self):print(f"{self.name} makes a sound.")class Dog(Animal):def __init__(self, name, breed):super().__init__(name)self.breed = breeddef speak(self):super().speak()print(f"{self.name} barks.")d = Dog("Buddy", "Golden Retriever")
d.speak()
在 Python 中,super()
用于調用父類的方法,在子類的構造函數中可以使用 super().__init__()
來調用父類的構造函數。
JavaScript 中的 super
class Animal {constructor(name) {this.name = name;}speak() {console.log(`${this.name} makes a sound.`);}
}class Dog extends Animal {constructor(name, breed) {super(name);this.breed = breed;}speak() {super.speak();console.log(`${this.name} barks.`);}
}let d = new Dog("Buddy", "Golden Retriever");
d.speak();
在 JavaScript 中,super
同樣用于調用父類的構造函數和方法。
7.2.3 子類與父類的方法重寫
子類可以重寫父類的方法,以實現不同的行為。
Python 中的方法重寫
class Animal:def speak(self):print("Animal speaks")class Dog(Animal):def speak(self):print("Dog barks")d = Dog()
d.speak() # 輸出: Dog barks
在 Python 中,子類定義與父類同名的方法即可實現方法重寫。
JavaScript 中的方法重寫
class Animal {speak() {console.log("Animal speaks");}
}class Dog extends Animal {speak() {console.log("Dog barks");}
}let d = new Dog();
d.speak(); // 輸出: Dog barks
在 JavaScript 中,同樣通過在子類中定義與父類同名的方法來實現方法重寫。
7.3 類的靜態方法和屬性
7.3.1 靜態方法的定義與使用
靜態方法是屬于類本身的方法,不需要創建類的實例就可以調用。
Python 中的靜態方法
class MathUtils:@staticmethoddef add(a, b):return a + bresult = MathUtils.add(3, 5)
print(result) # 輸出: 8
在 Python 中,使用 @staticmethod
裝飾器來定義靜態方法。
JavaScript 中的靜態方法
class MathUtils {static add(a, b) {return a + b;}
}let result = MathUtils.add(3, 5);
console.log(result); // 輸出: 8
在 JavaScript 中,使用 static
關鍵字來定義靜態方法。
7.3.2 靜態屬性的定義與使用
靜態屬性是屬于類本身的屬性,不需要創建類的實例就可以訪問。
Python 中的靜態屬性
class MyClass:static_attr = 10print(MyClass.static_attr) # 輸出: 10
在 Python 中,直接在類的主體中定義變量即可作為靜態屬性。
JavaScript 中的靜態屬性
class MyClass {static staticAttr = 10;
}console.log(MyClass.staticAttr); // 輸出: 10
在 JavaScript 中,使用 static
關鍵字來定義靜態屬性。
🎉 通過以上內容,我們詳細了解了類的定義與使用、類的繼承以及類的靜態方法和屬性,這些都是面向對象編程中非常重要的概念。
第八章 Promise對象
8.1 Promise概述
8.1.1 異步編程問題
在傳統的異步編程中,常常會遇到一些棘手的問題😫。比如說回調地獄問題,當有多個異步操作嵌套時,代碼會變得非常復雜,難以閱讀和維護。
asyncOperation1(function(result1) {asyncOperation2(result1, function(result2) {asyncOperation3(result2, function(result3) {// 更多嵌套...});});
});
這樣的代碼就像層層嵌套的迷宮🧩,一旦出現問題,調試起來會非常困難。而且,錯誤處理也變得很復雜,很難清晰地知道哪個異步操作出現了錯誤。
8.1.2 Promise的概念和狀態
1. 概念
Promise 是一種異步編程的解決方案🤗,它可以避免回調地獄問題,讓異步代碼的結構更加清晰。簡單來說,Promise 就像是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。
2. 狀態
Promise 有三種狀態:
- pending(進行中):這是 Promise 的初始狀態,當一個 Promise 被創建時,它就處于這個狀態。就像你點了一份外賣,外賣還在制作和配送的過程中,此時就是 pending 狀態🚚。
- fulfilled(已成功):當異步操作成功完成時,Promise 的狀態就會從 pending 變為 fulfilled。相當于外賣已經送到你手中,你可以開心地享用美食啦😋。
- rejected(已失敗):如果異步操作出現了錯誤,Promise 的狀態就會從 pending 變為 rejected。這就好比外賣在配送過程中出了問題,沒辦法送到你手上了😢。
Promise 的狀態一旦改變,就不會再變,而且只能從 pending 變為 fulfilled 或者從 pending 變為 rejected。
8.2 Promise的使用
8.2.1 創建Promise對象
創建一個 Promise 對象需要使用 new Promise()
構造函數,它接收一個執行器函數作為參數,這個執行器函數有兩個參數:resolve
和 reject
。
const promise = new Promise((resolve, reject) => {// 模擬一個異步操作setTimeout(() => {const randomNumber = Math.random();if (randomNumber > 0.5) {resolve(randomNumber); // 操作成功,調用 resolve 方法} else {reject(new Error('隨機數小于等于 0.5')); // 操作失敗,調用 reject 方法}}, 1000);
});
8.2.2 then方法處理成功結果
then
方法用于處理 Promise 成功的結果。它接收一個回調函數作為參數,這個回調函數會在 Promise 的狀態變為 fulfilled 時被調用。
promise.then((result) => {console.log('操作成功,結果是:', result);
});
8.2.3 catch方法處理失敗結果
catch
方法用于處理 Promise 失敗的結果。它接收一個回調函數作為參數,這個回調函數會在 Promise 的狀態變為 rejected 時被調用。
promise.catch((error) => {console.log('操作失敗,錯誤信息是:', error.message);
});
8.2.4 finally方法無論結果如何都會執行
finally
方法無論 Promise 的狀態是 fulfilled 還是 rejected 都會執行。它接收一個回調函數作為參數。
promise.finally(() => {console.log('無論操作成功還是失敗,我都會執行');
});
8.3 Promise的鏈式調用
8.3.1 鏈式調用的原理
Promise 的鏈式調用是基于 then
方法會返回一個新的 Promise 對象。當一個 then
方法中的回調函數執行完畢后,會根據回調函數的返回值來決定新 Promise 的狀態。
如果回調函數返回一個普通值,那么新 Promise 的狀態會變為 fulfilled,并且這個普通值會作為新 Promise 的結果;如果回調函數拋出一個錯誤,那么新 Promise 的狀態會變為 rejected。
const promise = new Promise((resolve, reject) => {resolve(1);
});promise.then((result) => {return result + 1;
}).then((newResult) => {console.log('最終結果是:', newResult);
});
8.3.2 鏈式調用的應用場景
鏈式調用可以讓我們按順序執行多個異步操作,避免了回調地獄。比如,我們要依次讀取三個文件的內容:
function readFile(filePath) {return new Promise((resolve, reject) => {// 模擬讀取文件操作setTimeout(() => {const content = `文件 ${filePath} 的內容`;resolve(content);}, 1000);});
}readFile('file1.txt').then((content1) => {console.log(content1);return readFile('file2.txt');}).then((content2) => {console.log(content2);return readFile('file3.txt');}).then((content3) => {console.log(content3);}).catch((error) => {console.log('讀取文件出錯:', error.message);});
8.4 Promise的靜態方法
8.4.1 Promise.all
Promise.all
方法接收一個 Promise 數組作為參數,它會返回一個新的 Promise。當數組中的所有 Promise 都變為 fulfilled 狀態時,新 Promise 才會變為 fulfilled 狀態,并且它的結果是一個包含所有 Promise 結果的數組;如果數組中有一個 Promise 變為 rejected 狀態,那么新 Promise 就會立即變為 rejected 狀態,并且它的結果是第一個變為 rejected 狀態的 Promise 的錯誤信息。
const promise1 = new Promise((resolve) => {setTimeout(() => {resolve('結果 1');}, 1000);
});const promise2 = new Promise((resolve) => {setTimeout(() => {resolve('結果 2');}, 2000);
});Promise.all([promise1, promise2]).then((results) => {console.log('所有 Promise 都成功,結果是:', results);
}).catch((error) => {console.log('有 Promise 失敗,錯誤信息是:', error.message);
});
8.4.2 Promise.race
Promise.race
方法也接收一個 Promise 數組作為參數,它會返回一個新的 Promise。當數組中的任何一個 Promise 變為 fulfilled 或 rejected 狀態時,新 Promise 就會立即變為相同的狀態,并且它的結果就是第一個改變狀態的 Promise 的結果。
const promise3 = new Promise((resolve) => {setTimeout(() => {resolve('結果 3');}, 1500);
});const promise4 = new Promise((resolve) => {setTimeout(() => {resolve('結果 4');}, 500);
});Promise.race([promise3, promise4]).then((result) => {console.log('第一個完成的 Promise 的結果是:', result);
}).catch((error) => {console.log('第一個失敗的 Promise 的錯誤信息是:', error.message);
});
8.4.3 Promise.resolve
Promise.resolve
方法可以將一個值轉換為一個狀態為 fulfilled 的 Promise 對象。
const resolvedPromise = Promise.resolve('直接創建一個已成功的 Promise');
resolvedPromise.then((result) => {console.log('結果是:', result);
});
8.4.4 Promise.reject
Promise.reject
方法可以將一個值轉換為一個狀態為 rejected 的 Promise 對象。
const rejectedPromise = Promise.reject(new Error('直接創建一個已失敗的 Promise'));
rejectedPromise.catch((error) => {console.log('錯誤信息是:', error.message);
});
第九章 async/await
9.1 async函數
9.1.1 async函數的定義與返回值
1. 定義
在 JavaScript 中,async
函數是一種特殊的函數,它的定義方式很簡單,只需要在普通函數定義的前面加上 async
關鍵字就可以啦😎。以下是幾種不同形式的 async
函數定義示例:
- 函數聲明形式
async function myAsyncFunction() {return 'Hello, async!';
}
- 函數表達式形式
const myAsyncFunction = async function() {return 'Hello, async!';
};
- 箭頭函數形式
const myAsyncFunction = async () => {return 'Hello, async!';
};
2. 返回值
async
函數總是返回一個 Promise
對象。無論你在 async
函數中返回的是什么值,它都會被自動包裝成一個 Promise
對象。如果 async
函數內部沒有顯式地返回一個 Promise
,那么它會返回一個狀態為 resolved
的 Promise
,其值就是函數的返回值。
async function myAsyncFunction() {return 'Hello, async!';
}myAsyncFunction().then(result => {console.log(result); // 輸出: Hello, async!
});
如果 async
函數內部拋出了一個錯誤,那么返回的 Promise
的狀態會變為 rejected
,錯誤信息會作為 Promise
的拒絕理由。
async function myAsyncFunction() {throw new Error('Something went wrong!');
}myAsyncFunction().catch(error => {console.error(error.message); // 輸出: Something went wrong!
});
9.1.2 async函數內部使用Promise
在 async
函數內部,可以像使用普通函數一樣使用 Promise
。async
函數的強大之處在于它可以讓我們以同步的方式編寫異步代碼。
function fetchData() {return new Promise((resolve, reject) => {setTimeout(() => {resolve('Data fetched successfully!');}, 2000);});
}async function getData() {console.log('Fetching data...');const data = await fetchData();console.log(data);return data;
}getData().then(() => {console.log('Data processing completed.');
});
在上面的代碼中,getData
是一個 async
函數,它內部調用了 fetchData
這個返回 Promise
的函數。使用 await
關鍵字可以暫停 async
函數的執行,直到 Promise
被解決(resolved
)或被拒絕(rejected
),然后再繼續執行后續的代碼。
9.2 await關鍵字
9.2.1 await的使用方法
await
關鍵字只能在 async
函數內部使用,它的作用是暫停 async
函數的執行,等待一個 Promise
被解決(resolved
)或被拒絕(rejected
),然后返回 Promise
的解決值。
function delay(ms) {return new Promise(resolve => setTimeout(resolve, ms));
}async function main() {console.log('Start');await delay(2000);console.log('After 2 seconds');
}main();
在上面的代碼中,await delay(2000)
會暫停 main
函數的執行,直到 delay
函數返回的 Promise
被解決,也就是 2 秒后,才會繼續執行后續的代碼,輸出 After 2 seconds
。
9.2.2 await只能在async函數中使用
這是一個非常重要的規則??,如果在非 async
函數中使用 await
關鍵字,會導致語法錯誤。
// 錯誤示例
function normalFunction() {await delay(1000); // 這里會報錯console.log('This will not work.');
}// 正確示例
async function asyncFunction() {await delay(1000);console.log('This works!');
}
9.3 async/await的優勢
9.3.1 代碼可讀性提升
使用 async/await
可以讓異步代碼看起來更像是同步代碼,避免了傳統的回調地獄和復雜的 Promise
鏈式調用,大大提高了代碼的可讀性。
傳統 Promise 鏈式調用示例
function step1() {return new Promise(resolve => setTimeout(() => resolve('Step 1 completed'), 1000));
}function step2() {return new Promise(resolve => setTimeout(() => resolve('Step 2 completed'), 1000));
}function step3() {return new Promise(resolve => setTimeout(() => resolve('Step 3 completed'), 1000));
}step1().then(result1 => {console.log(result1);return step2();}).then(result2 => {console.log(result2);return step3();}).then(result3 => {console.log(result3);});
使用 async/await 示例
async function runSteps() {const result1 = await step1();console.log(result1);const result2 = await step2();console.log(result2);const result3 = await step3();console.log(result3);
}runSteps();
可以看到,使用 async/await
后的代碼更加簡潔、直觀,就像在編寫同步代碼一樣。
9.3.2 錯誤處理更方便
在 async/await
中,錯誤處理可以使用傳統的 try...catch
語句,比 Promise
的 catch
方法更加直觀和方便。
function mightFail() {return new Promise((resolve, reject) => {setTimeout(() => {const random = Math.random();if (random < 0.5) {resolve('Success!');} else {reject(new Error('Something went wrong!'));}}, 1000);});
}async function main() {try {const result = await mightFail();console.log(result);} catch (error) {console.error(error.message);}
}main();
在上面的代碼中,使用 try...catch
語句可以很方便地捕獲 await
操作中可能拋出的錯誤,代碼的錯誤處理邏輯更加清晰。
第十章 模塊化
在現代編程中,模塊化是一種非常重要的編程思想,它可以將一個大的程序拆分成多個小的、獨立的模塊,提高代碼的可維護性、可復用性和可測試性。下面我們就來詳細了解一下 JavaScript 中的模塊化相關知識。
10.1 ES6模塊語法
ES6 引入了一套標準的模塊語法,使得 JavaScript 可以更好地進行模塊化開發。
10.1.1 export導出模塊成員
在 ES6 中,使用 export
關鍵字可以將模塊中的變量、函數、類等成員導出,以便其他模塊使用。export
有兩種導出方式:
1. 命名導出
可以在聲明變量、函數或類時直接導出,也可以在聲明之后統一導出。
直接導出示例:
// math.js
// 導出一個常量
export const PI = 3.14;// 導出一個函數
export function add(a, b) {return a + b;
}// 導出一個類
export class Person {constructor(name) {this.name = name;}sayHello() {console.log(`Hello, my name is ${this.name}`);}
}
統一導出示例:
// math.js
const PI = 3.14;
function add(a, b) {return a + b;
}
class Person {constructor(name) {this.name = name;}sayHello() {console.log(`Hello, my name is ${this.name}`);}
}// 統一導出
export { PI, add, Person };
2. 重命名導出
如果想在導出時給成員起一個不同的名字,可以使用 as
關鍵字。
// math.js
const PI = 3.14;
function add(a, b) {return a + b;
}// 重命名導出
export { PI as MY_PI, add as sum };
10.1.2 import導入模塊成員
使用 import
關鍵字可以從其他模塊導入導出的成員。導入時需要指定模塊的路徑。
1. 命名導入
與命名導出相對應,使用命名導入可以導入指定名稱的成員。
// main.js
// 導入 math.js 模塊中的 PI 和 add 成員
import { PI, add } from './math.js';console.log(PI); // 3.14
console.log(add(1, 2)); // 3
2. 重命名導入
如果導入時想給成員起一個不同的名字,也可以使用 as
關鍵字。
// main.js
// 重命名導入
import { PI as MY_PI, add as sum } from './math.js';console.log(MY_PI); // 3.14
console.log(sum(1, 2)); // 3
3. 導入所有成員
使用 * as
語法可以將模塊中的所有導出成員作為一個對象導入。
// main.js
// 導入 math.js 模塊的所有導出成員
import * as math from './math.js';console.log(math.PI); // 3.14
console.log(math.add(1, 2)); // 3
10.1.3 默認導出與默認導入
除了命名導出,ES6 還支持默認導出。每個模塊只能有一個默認導出。
1. 默認導出
使用 export default
關鍵字進行默認導出。
// person.js
// 默認導出一個類
export default class Person {constructor(name) {this.name = name;}sayHello() {console.log(`Hello, my name is ${this.name}`);}
}
2. 默認導入
在導入默認導出的成員時,不需要使用花括號。
// main.js
// 導入 person.js 模塊的默認導出成員
import Person from './person.js';const p = new Person('John');
p.sayHello(); // Hello, my name is John
10.2 模塊加載機制
10.2.1 靜態導入與動態導入
1. 靜態導入
前面介紹的 import
語句都是靜態導入,靜態導入在模塊加載時就會確定導入的模塊,并且會阻塞后續代碼的執行,直到模塊加載完成。
// main.js
import { add } from './math.js';console.log(add(1, 2));
2. 動態導入
動態導入使用 import()
函數,它返回一個 Promise,可以在需要的時候異步加載模塊。
// main.js
// 動態導入 math.js 模塊
import('./math.js').then((math) => {console.log(math.add(1, 2));}).catch((error) => {console.error('Failed to load module:', error);});
動態導入的好處是可以根據條件加載模塊,減少初始加載時間,提高應用的性能。
10.2.2 模塊的循環依賴問題
模塊的循環依賴是指兩個或多個模塊相互依賴的情況。例如,模塊 A 依賴于模塊 B,而模塊 B 又依賴于模塊 A。
1. 問題示例
// a.js
import { bFunction } from './b.js';export function aFunction() {console.log('aFunction');bFunction();
}
// b.js
import { aFunction } from './a.js';export function bFunction() {console.log('bFunction');aFunction();
}
2. 解決方法
在 ES6 模塊中,循環依賴是可以正常處理的。當模塊 A 導入模塊 B 時,模塊 B 可能還沒有完全加載完成,但 ES6 模塊會在模塊加載完成后正確解析依賴關系。
在實際開發中,盡量避免循環依賴的出現,因為它會使代碼的邏輯變得復雜,難以維護。如果無法避免,可以通過重構代碼,將公共的部分提取到一個新的模塊中,減少模塊之間的直接依賴。
10.3 模塊應用場景
10.3.1 項目代碼的模塊化組織
在大型項目中,將代碼進行模塊化組織可以提高代碼的可維護性和可復用性。可以按照功能、業務邏輯等將代碼拆分成多個模塊。
例如,一個電商項目可以拆分成以下模塊:
- 用戶模塊:負責用戶的注冊、登錄、信息管理等功能。
- 商品模塊:負責商品的展示、搜索、詳情等功能。
- 購物車模塊:負責購物車的添加、刪除、結算等功能。
每個模塊都有自己獨立的功能和職責,模塊之間通過導入導出的方式進行交互。
10.3.2 第三方模塊的使用
在開發過程中,我們經常會使用到第三方模塊,這些模塊可以幫助我們快速實現一些功能,提高開發效率。
1. 安裝第三方模塊
可以使用包管理工具(如 npm 或 yarn)來安裝第三方模塊。
npm install lodash
2. 導入和使用第三方模塊
安裝完成后,就可以在項目中導入和使用這些模塊了。
// main.js
import _ from 'lodash';const array = [1, 2, 3, 4, 5];
const sum = _.sum(array);
console.log(sum); // 15
通過使用第三方模塊,我們可以避免重復造輪子,專注于項目的核心業務邏輯。🎉
第十一章 其他新特性
11.1 新的數據類型 Symbol
11.1.1 Symbol 的創建與使用
1. 創建 Symbol
Symbol 是 ES6 引入的一種新的原始數據類型,表示獨一無二的值。可以使用 Symbol()
函數來創建一個 Symbol。
// 創建一個 Symbol
let sym1 = Symbol();
let sym2 = Symbol('description'); // 可以傳入一個描述字符串,方便調試
這里的描述字符串只是一個標識,不會影響 Symbol 的唯一性。即使傳入相同的描述字符串,創建的 Symbol 也是不同的。
let sym3 = Symbol('hello');
let sym4 = Symbol('hello');
console.log(sym3 === sym4); // false
2. 使用 Symbol
Symbol 可以像其他數據類型一樣被使用,例如作為變量的值。
let mySymbol = Symbol();
let obj = {};
obj[mySymbol] = 'This is a value associated with a Symbol';
console.log(obj[mySymbol]); // 'This is a value associated with a Symbol'
11.1.2 Symbol 作為對象屬性名
1. 避免屬性名沖突
在 JavaScript 中,對象的屬性名通常是字符串。使用 Symbol 作為屬性名可以避免屬性名沖突,因為 Symbol 是獨一無二的。
let nameSymbol = Symbol('name');
let person = {[nameSymbol]: 'John',age: 30
};
console.log(person[nameSymbol]); // 'John'
2. 遍歷對象時的特殊性
使用 for...in
循環和 Object.keys()
方法遍歷對象時,Symbol 類型的屬性名不會被遍歷到。
let sym = Symbol('prop');
let obj = {[sym]: 'value',normalProp: 'normal'
};for (let key in obj) {console.log(key); // 只會輸出 'normalProp'
}console.log(Object.keys(obj)); // ['normalProp']
要獲取對象的 Symbol 類型的屬性名,可以使用 Object.getOwnPropertySymbols()
方法。
let symbols = Object.getOwnPropertySymbols(obj);
console.log(symbols); // [Symbol(prop)]
console.log(obj[symbols[0]]); // 'value'
11.2 迭代器與生成器
11.2.1 迭代器協議與可迭代對象
1. 迭代器協議
迭代器是一個對象,它實現了一個 next()
方法。next()
方法返回一個對象,該對象包含兩個屬性:value
和 done
。value
表示當前迭代的值,done
是一個布爾值,表示迭代是否結束。
// 自定義一個迭代器
let myIterator = {index: 0,next: function() {if (this.index < 3) {return { value: this.index++, done: false };} else {return { value: undefined, done: true };}}
};console.log(myIterator.next()); // { value: 0, done: false }
console.log(myIterator.next()); // { value: 1, done: false }
console.log(myIterator.next()); // { value: 2, done: false }
console.log(myIterator.next()); // { value: undefined, done: true }
2. 可迭代對象
可迭代對象是實現了迭代器協議的對象。在 JavaScript 中,數組、字符串、Set、Map 等都是可迭代對象。可以使用 for...of
循環來遍歷可迭代對象。
let arr = [1, 2, 3];
for (let value of arr) {console.log(value); // 依次輸出 1, 2, 3
}
11.2.2 生成器函數與 yield 關鍵字
1. 生成器函數
生成器函數是一種特殊的函數,使用 function*
來定義。生成器函數在執行時會返回一個生成器對象,生成器對象是一個迭代器。
function* myGenerator() {yield 1;yield 2;yield 3;
}let gen = myGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
2. yield 關鍵字
yield
關鍵字用于暫停和恢復生成器函數的執行。當生成器函數執行到 yield
語句時,會暫停執行,并返回 yield
后面的值。下次調用 next()
方法時,生成器函數會從暫停的位置繼續執行。
function* countGenerator() {let count = 0;while (true) {yield count++;}
}let counter = countGenerator();
console.log(counter.next().value); // 0
console.log(counter.next().value); // 1
console.log(counter.next().value); // 2
11.3 Proxy 代理
11.3.1 Proxy 的基本用法
Proxy 是 ES6 引入的一個新特性,用于創建一個對象的代理,從而可以對該對象的基本操作進行攔截和自定義。
let target = {name: 'John',age: 30
};let handler = {get: function(target, property) {console.log(`Getting property ${property}`);return target[property];},set: function(target, property, value) {console.log(`Setting property ${property} to ${value}`);target[property] = value;return true;}
};let proxy = new Proxy(target, handler);console.log(proxy.name); // 輸出 'Getting property name',然后輸出 'John'
proxy.age = 31; // 輸出 'Setting property age to 31'
11.3.2 Proxy 的攔截方法
Proxy 可以攔截多種對象的基本操作,常見的攔截方法有:
- get(target, property, receiver):攔截對象屬性的讀取操作。
- set(target, property, value, receiver):攔截對象屬性的設置操作。
- has(target, property):攔截
in
操作符。 - deleteProperty(target, property):攔截
delete
操作。
let target = {name: 'John',age: 30
};let handler = {has: function(target, property) {console.log(`Checking if ${property} exists`);return property in target;},deleteProperty: function(target, property) {console.log(`Deleting property ${property}`);delete target[property];return true;}
};let proxy = new Proxy(target, handler);console.log('name' in proxy); // 輸出 'Checking if name exists',然后輸出 true
delete proxy.age; // 輸出 'Deleting property age'
11.4 Reflect 對象
11.4.1 Reflect 對象的方法
Reflect 是 ES6 引入的一個內置對象,它提供了一系列與 Proxy 攔截方法相對應的方法。這些方法可以用來執行對象的基本操作。
- Reflect.get(target, property, receiver):獲取對象的屬性值。
- Reflect.set(target, property, value, receiver):設置對象的屬性值。
- Reflect.has(target, property):檢查對象是否具有某個屬性。
- Reflect.deleteProperty(target, property):刪除對象的屬性。
let obj = {name: 'John',age: 30
};console.log(Reflect.get(obj, 'name')); // 'John'
Reflect.set(obj, 'age', 31);
console.log(obj.age); // 31
console.log(Reflect.has(obj, 'name')); // true
Reflect.deleteProperty(obj, 'age');
console.log(obj.age); // undefined
11.4.2 Reflect 與 Proxy 的配合使用
Reflect 對象的方法可以與 Proxy 的攔截方法配合使用,使代碼更加簡潔和規范。
let target = {name: 'John',age: 30
};let handler = {get: function(target, property, receiver) {console.log(`Getting property ${property}`);return Reflect.get(target, property, receiver);},set: function(target, property, value, receiver) {console.log(`Setting property ${property} to ${value}`);return Reflect.set(target, property, value, receiver);}
};let proxy = new Proxy(target, handler);console.log(proxy.name); // 輸出 'Getting property name',然后輸出 'John'
proxy.age = 31; // 輸出 'Setting property age to 31'
通過這種方式,我們可以在攔截操作的同時,利用 Reflect 對象的方法來執行原始的操作。🎉