一、JavaScript 簡介
JavaScript(簡稱 JS)是一種高級的、解釋型的編程語言,由 Netscape 公司的 Brendan Eich 在 1995 年開發,最初命名為 LiveScript,后因與 Java 的合作關系而改名為 JavaScript。作為 Web 開發的三大核心技術之一(其他兩個是 HTML 和 CSS),JavaScript 在現代網頁中扮演著至關重要的角色。
JS 的主要特點包括:
- 客戶端腳本語言:直接在用戶的瀏覽器中執行,無需服務器處理
- 動態類型:變量類型在運行時確定
- 基于原型的面向對象特性
- 函數是一等公民
- 支持異步編程(通過 Promise 和 async/await)
與 HTML(超文本標記語言)負責頁面結構、CSS(層疊樣式表)負責頁面樣式不同,JavaScript 主要負責頁面的行為和交互功能。典型的應用場景包括:
- 表單驗證:檢查用戶輸入是否符合要求(如郵箱格式驗證)
- 動態內容更新:無需刷新頁面即可加載新內容(如社交媒體信息流)
- 動畫效果:創建平滑的頁面過渡和交互效果
- 用戶交互響應:處理點擊、滾動等用戶事件
- 與后端 API 通信:通過 AJAX 或 Fetch API 獲取數據
現代 JavaScript 已經發展出多個框架和庫(如 React、Vue、Angular),使得開發復雜的前端應用變得更加高效。根據 Stack Overflow 2022 開發者調查,JavaScript 已連續10年成為最常用的編程語言。
示例代碼展示基本的 DOM 操作:
// 獲取頁面元素
const button = document.getElementById('myButton');
// 添加點擊事件監聽器
button.addEventListener('click', function() {// 修改元素內容this.textContent = '已點擊';// 改變樣式this.style.backgroundColor = 'blue';
});
二、JavaScript 的引入方式
1. 內嵌式 JavaScript
內嵌式是最基礎的 JavaScript 引入方式,直接在 HTML 文件中使用 <script>
標簽包裹 JavaScript 代碼。這種方式的優點是簡單直接,適合快速測試和小型腳本。
典型應用場景:
- 快速原型開發
- 小型網站或單頁應用
- 需要立即執行的初始化代碼
示例代碼詳解:
<!DOCTYPE html>
<html>
<head><title>內嵌式JS</title><script>// 這里是JavaScript代碼function init() {alert("頁面加載完成,這是內嵌式JavaScript");console.log("調試信息輸出到控制臺");}// 當DOM加載完成后執行document.addEventListener('DOMContentLoaded', init);</script>
</head>
<body><!-- 頁面內容 -->
</body>
</html>
特性說明:
- 代碼會按照在HTML中的出現順序執行
- 可以放在
<head>
或<body>
中,位置不同會影響執行時機 - 適合少量腳本,但不利于代碼維護和復用
2. 外鏈式 JavaScript
外鏈式是將 JavaScript 代碼寫在單獨的.js文件中,然后在 HTML 中通過<script>
標簽引入。這是現代Web開發推薦的方式。
典型應用場景:
- 大中型項目開發
- 需要復用的組件或庫
- 需要代碼分離和模塊化的項目
示例代碼詳解:
HTML文件:
<!DOCTYPE html>
<html>
<head><title>外鏈式JS</title><!-- 推薦放在body結束前,避免阻塞渲染 --><script src="assets/js/myScript.js" defer></script><!-- 或者使用async屬性實現異步加載 -->
</head>
<body><!-- 頁面內容 -->
</body>
</html>
myScript.js文件內容:
// 使用嚴格模式
'use strict';// 定義模塊
const App = {init: function() {alert("這是外鏈式JavaScript");this.bindEvents();},bindEvents: function() {// 事件綁定代碼}
};// 當DOM加載完成后初始化應用
document.addEventListener('DOMContentLoaded', function() {App.init();
});
最佳實踐:
- 文件命名要有意義,如
main.js
、app.js
等 - 使用
defer
或async
屬性優化加載性能 - 合理組織目錄結構,如
js/
或assets/js/
- 考慮使用模塊化開發工具(如Webpack、Rollup)
3. 行內式 JavaScript
行內式是將JavaScript代碼直接寫在HTML標簽的事件屬性中,這種方式雖然簡單但不推薦大量使用。
典型應用場景:
- 快速測試某個事件處理
- 簡單的交互效果
- 傳統老式網站維護
示例代碼詳解:
<!DOCTYPE html>
<html>
<head><title>行內式JS</title><style>.btn {padding: 10px 20px;background-color: #4CAF50;color: white;border: none;cursor: pointer;}</style>
</head>
<body><!-- 簡單的事件處理 --><button class="btn" onclick="alert('這是行內式JavaScript'); this.textContent='已點擊'">點擊我</button><!-- 表單驗證示例 --><form onsubmit="return validateForm()"><input type="text" id="username" required><button type="submit">提交</button></form><script>// 可以配合使用的函數function validateForm() {const input = document.getElementById('username');if(input.value.length < 3) {alert('用戶名至少3個字符');return false;}return true;}</script>
</body>
</html>
注意事項:
- 這種方式使HTML和JavaScript高度耦合,不利于維護
- 違反了關注點分離(Separation of Concerns)原則
- 現代開發中推薦使用事件監聽器而非行內事件
- 在React等框架中的"行內事件"實際上是語法糖,原理不同
三、變量
變量是用于存儲數據的容器
在編程中,變量是存儲數據的基本單元,相當于一個帶標簽的盒子,我們可以把數據放進去,需要時再取出來使用。JavaScript 提供了三種聲明變量的方式:var
、let
和 const
。
1. 聲明和賦值詳解
1.1 使用 var
聲明變量(ES5及之前的方式)
var
是 JavaScript 最早使用的變量聲明方式,具有函數作用域:
// 1. 先聲明后賦值
var a; // 聲明變量a,此時a的值為undefined
a = 10; // 給變量a賦值為10// 2. 聲明同時賦值
var b = 20; // 聲明變量b并賦值為20// 3. 變量提升現象
console.log(c); // 輸出undefined,不會報錯
var c = 30;
1.2 使用 let
聲明變量(ES6新增)
let
是 ES6 引入的聲明方式,具有塊級作用域,解決了 var
的一些問題:
// 1. 基本用法
let x = 100; // 聲明并賦值// 2. 塊級作用域示例
{let y = 200;console.log(y); // 200
}
// console.log(y); // 報錯,y不在作用域內// 3. 不存在變量提升
console.log(z); // 報錯
let z = 300;
1.3 使用 const
聲明常量(ES6新增)
const
用于聲明不可變的常量,必須初始化時就賦值:
// 1. 基本用法
const PI = 3.14159; // 聲明常量PI// 2. 不可重新賦值
// PI = 3.14; // 報錯,不能修改常量// 3. 對于對象和數組
const colors = ['red', 'green'];
colors.push('blue'); // 允許,因為修改的是數組內容
// colors = ['yellow']; // 報錯,不能重新賦值
2. 變量命名規則詳解
JavaScript 變量命名需要遵循以下規則:
組成字符:
- 允許使用字母(a-z, A-Z)
- 數字(0-9),但不能以數字開頭
- 下劃線(_)
- 美元符號($)
命名示例:
- 合法:
userName
,_count
,$price
,num1
- 非法:
1stPlace
(數字開頭),my-name
(含連字符)
- 合法:
關鍵字限制:
- 不能使用 JavaScript 保留的關鍵字,如:
// let if = 10; // 報錯,if是關鍵字 // let for = 20; // 報錯,for是關鍵字
- 不能使用 JavaScript 保留的關鍵字,如:
大小寫敏感:
firstName
和FirstName
是兩個不同的變量age
和AGE
也是不同的變量
命名建議:
- 使用駝峰命名法(如
userAge
) - 常量通常全大寫(如
MAX_SIZE
) - 使用有意義的名稱(避免單字母變量名)
- 使用駝峰命名法(如
舉例說明:
let userAge = 25; // 正確
const MAX_USERS = 100; // 正確
let $price = 99.9; // 正確
// let 2ndPlace = "John"; // 錯誤:數字開頭
// let first-name = "Tom";// 錯誤:包含連字符
四、數據類型
1. 基本數據類型
字符串(String)
字符串是表示文本的數據類型,可以用單引號('')、雙引號("")或反引號(``)定義。ES6引入的模板字符串(反引號)支持多行文本和字符串插值。
let str1 = "Hello"; // 雙引號
let str2 = 'World'; // 單引號
let str3 = `Hello ${str2}`; // 模板字符串,輸出"Hello World"
let multiLine = `這是
多行
字符串`; // 支持換行
數字(Number)
JavaScript 只有一種數字類型,包括整數和浮點數,采用IEEE 754標準的64位雙精度浮點數格式表示。
let num1 = 100; // 整數
let num2 = 3.14; // 浮點數
let num3 = 0.1 + 0.2; // 0.30000000000000004(浮點數精度問題)
let hex = 0xff; // 十六進制
let oct = 0o10; // 八進制
let bigNum = 1e6; // 科學計數法,表示1000000
布爾值(Boolean)
布爾值表示邏輯實體,只有兩個值:true和false。常用于條件判斷。
let isTrue = true; // 真值
let isFalse = false; // 假值
let isGreater = 10 > 5; // 比較運算返回布爾值
空值(Null)
Null類型只有一個值null,表示一個空對象引用。通常用于表示變量有意為空值。
let nullValue = null; // 明確賦值為空
let element = document.getElementById('not-exist'); // 返回null
未定義(Undefined)
Undefined類型表示變量已聲明但未賦值,或訪問對象不存在的屬性。
let undefinedValue; // 只聲明未賦值
let obj = {};
console.log(obj.noProperty); // 訪問不存在的屬性返回undefined
Symbol(ES6新增)
Symbol表示唯一的、不可變的值,主要用作對象屬性的標識符。
let sym1 = Symbol('description');
let sym2 = Symbol('description');
console.log(sym1 === sym2); // false,每個Symbol都是唯一的
BigInt(ES2020新增)
BigInt表示任意精度的整數,可以表示大于2^53的整數。
let bigInt = 9007199254740991n; // 末尾加n表示BigInt
let bigInt2 = BigInt("9007199254740991"); // 使用BigInt函數
2. 引用數據類型
對象(Object)
對象是鍵值對的集合,用于存儲復雜數據結構。對象屬性可以是基本類型值或其他對象。
let person = {name: "張三",age: 20,address: {city: "北京",street: "長安街"},sayHello: function() {console.log("你好!");}
};// 訪問屬性
console.log(person.name); // 點表示法
console.log(person['age']); // 方括號表示法
person.sayHello(); // 調用方法
數組(Array)
數組是有序的值集合,可以包含不同類型的元素,長度動態可變。
let arr = [1, "two", true, {name: "數組元素"}];
arr.push(5); // 添加元素到末尾
arr.unshift(0); // 添加元素到開頭
let first = arr[0]; // 通過索引訪問
let length = arr.length; // 獲取數組長度// 數組遍歷
arr.forEach(function(item) {console.log(item);
});
函數(Function)
函數是可執行代碼塊,可以接收參數并返回值。JavaScript中函數是一等公民。
// 函數聲明
function add(a, b) {return a + b;
}// 函數表達式
let multiply = function(x, y) {return x * y;
};// 箭頭函數(ES6)
let divide = (x, y) => x / y;// 調用函數
let sum = add(5, 3); // 8
let product = multiply(4, 6); // 24
let quotient = divide(10, 2); // 5
其他引用類型
還包括Date、RegExp、Map、Set等特殊對象類型。
// Date對象
let now = new Date();
console.log(now.getFullYear());// RegExp正則表達式
let pattern = /hello/i;
console.log(pattern.test("Hello World")); // true// Map集合
let map = new Map();
map.set('name', '李四');
console.log(map.get('name'));// Set集合
let set = new Set([1, 2, 3, 3, 4]);
console.log(set.size); // 4(自動去重)
3. 數據類型檢測與轉換
類型檢測
// typeof運算符
console.log(typeof "Hello"); // "string"
console.log(typeof 100); // "number"
console.log(typeof true); // "boolean"
console.log(typeof null); // "object"(歷史遺留問題)
console.log(typeof undefined); // "undefined"
console.log(typeof {name: "張三"}); // "object"
console.log(typeof [1, 2, 3]); // "object"(數組也是對象)
console.log(typeof function(){}); // "function"// instanceof運算符(檢測對象類型)
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(function(){} instanceof Function); // true// Object.prototype.toString方法(更準確的類型檢測)
console.log(Object.prototype.toString.call([])); // "[object Array]"
console.log(Object.prototype.toString.call(null)); // "[object Null]"
類型轉換
// 顯式類型轉換
let numStr = "123";
let num = Number(numStr); // 字符串轉數字
let str = String(123); // 數字轉字符串
let bool = Boolean(1); // 真值轉換// 隱式類型轉換
let result = "5" + 2; // "52"(字符串拼接)
let sum = "5" - 2; // 3(數字運算)
let isTrue = !!1; // true(布爾轉換)// 特殊轉換案例
console.log(Number("")); // 0
console.log(Number("123abc")); // NaN
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN
五、運算符
1. 算術運算符
JavaScript 提供了多種算術運算符用于數值計算,包括:
+
加法運算-
減法運算*
乘法運算/
除法運算%
取余運算(求模)++
自增運算(分為前自增和后自增)--
自減運算(分為前自減和后自減)
示例代碼
let a = 10;
let b = 5;// 基本運算
console.log(a + b); // 15,加法運算
console.log(a - b); // 5,減法運算
console.log(a * b); // 50,乘法運算
console.log(a / b); // 2,除法運算
console.log(a % b); // 0,取余運算(10除以5余0)// 自增運算(后自增)
a++; // 等價于 a = a + 1
console.log(a); // 11// 自減運算(后自減)
b--; // 等價于 b = b - 1
console.log(b); // 4// 前自增與前自減
let x = 5;
let y = ++x; // 先自增再賦值
console.log(y); // 6let m = 5;
let n = m++; // 先賦值再自增
console.log(n); // 5
console.log(m); // 6
2. 賦值運算符
賦值運算符用于給變量賦值,包括:
=
基本賦值+=
加后賦值-=
減后賦值*=
乘后賦值/=
除后賦值%=
取余后賦值
示例代碼
let c = 10; // 基本賦值// 復合賦值運算
c += 5; // 相當于 c = c + 5,結果為15
console.log(c); // 15c -= 3; // 相當于 c = c - 3,結果為12
console.log(c); // 12c *= 2; // 相當于 c = c * 2,結果為24
console.log(c); // 24c /= 4; // 相當于 c = c / 4,結果為6
console.log(c); // 6c %= 2; // 相當于 c = c % 2,結果為0
console.log(c); // 0// 字符串連接賦值
let str = "Hello";
str += " World"; // 相當于 str = str + " World"
console.log(str); // "Hello World"
3. 比較運算符
比較運算符用于比較兩個值的大小或相等性,返回布爾值:
==
等于(會自動類型轉換)===
嚴格等于(不進行類型轉換)!=
不等于!==
嚴格不等于>
大于<
小于>=
大于等于<=
小于等于
示例代碼
// 松散比較(會進行類型轉換)
console.log(10 == "10"); // true,因為字符串"10"會被轉換為數字10
console.log(1 == true); // true,true會被轉換為1// 嚴格比較(不進行類型轉換)
console.log(10 === "10"); // false,類型不同
console.log(10 === 10); // true,值和類型都相同// 數值比較
console.log(10 > 5); // true
console.log(10 < 5); // false
console.log(10 >= 10); // true
console.log(10 <= 9); // false// 不等于比較
console.log(10 != "10"); // false,因為松散比較會轉換類型
console.log(10 !== "10"); // true,因為嚴格比較類型不同// 特殊比較
console.log(null == undefined); // true
console.log(null === undefined); // false
console.log(NaN == NaN); // false,NaN不等于任何值包括它自己
4. 邏輯運算符
邏輯運算符用于處理布爾值,包括:
&&
邏輯與(兩個操作數都為true時返回true)||
邏輯或(至少一個操作數為true時返回true)!
邏輯非(反轉操作數的布爾值)
示例代碼
// 邏輯與
console.log(true && false); // false
console.log(true && true); // true
console.log(false && false); // false// 邏輯或
console.log(true || false); // true
console.log(false || false); // false
console.log(true || true); // true// 邏輯非
console.log(!true); // false
console.log(!false); // true// 短路求值特性
let x = 10;
(x > 5) && console.log("x大于5"); // 會輸出,因為第一個條件為true
(x < 5) && console.log("這不會輸出"); // 不會輸出,因為第一個條件為false// 實際應用:條件賦值
let name = "";
let displayName = name || "匿名用戶";
console.log(displayName); // "匿名用戶"let user = { name: "張三" };
let userName = user && user.name;
console.log(userName); // "張三"
六、流程控制
JavaScript 條件語句和循環語句詳解
1. 條件語句
if 語句
if 語句是最基本的條件控制結構,用于根據條件執行不同的代碼塊。
let score = 80;if (score >= 60) {console.log("及格了"); // 當score>=60時執行
} else {console.log("沒及格"); // 當score<60時執行
}
應用場景:判斷用戶是否登錄、表單驗證、成績評定等。
if-else if-else 語句
當需要判斷多個條件時,可以使用多分支結構。
let grade = 85;if (grade >= 90) {console.log("優秀"); // 90分以上
} else if (grade >= 80) {console.log("良好"); // 80-89分
} else if (grade >= 60) {console.log("及格"); // 60-79分
} else {console.log("不及格"); // 60分以下
}
執行流程:從上到下依次判斷條件,當某個條件滿足時執行對應代碼塊并退出整個判斷結構。
switch 語句
switch 語句適用于多條件等值判斷的場景,比多個if-else更清晰。
let day = 3;switch (day) {case 1:console.log("星期一");break; // 必須使用break退出switch結構case 2:console.log("星期二");break;case 3:console.log("星期三"); // 本例會輸出這個結果break;default:console.log("其他星期"); // 當所有case都不匹配時執行
}
注意事項:
- 每個case后面必須加break,否則會繼續執行下一個case
- default分支是可選的,用于處理未匹配的情況
- switch使用嚴格比較(===)
2. 循環語句
for 循環
for循環是最常用的循環結構,適合已知循環次數的場景。
for (let i = 0; i < 5; i++) {console.log(i); // 依次輸出0、1、2、3、4
}
循環步驟:
- 初始化變量(let i = 0)
- 檢查條件(i < 5)
- 執行循環體
- 更新變量(i++)
- 重復步驟2-4直到條件不滿足
while 循環
while循環在不確定循環次數時使用,先判斷條件再執行循環體。
let j = 0;
while (j < 5) {console.log(j); // 依次輸出0、1、2、3、4j++;
}
應用場景:讀取文件直到結束、處理用戶輸入直到滿足條件等。
do-while 循環
do-while循環至少執行一次循環體,然后再判斷條件。
let k = 0;
do {console.log(k); // 先輸出0,然后輸出1、2、3、4k++;
} while (k < 5);
特點:無論條件是否成立,循環體至少執行一次。適合需要先執行操作再檢查條件的場景,如菜單顯示和用戶交互。
七、函數
函數的概念與重要性
函數是編程中的基本構建塊,它是一段可重復使用的代碼,用于執行特定的任務。合理使用函數可以帶來多重好處:
- 提高代碼可維護性:將復雜邏輯分解為多個小函數,便于理解和修改
- 增強代碼復用性:避免重復代碼,一處定義多處調用
- 降低耦合度:函數間通過明確接口通信,減少相互依賴
- 便于調試:可以單獨測試每個函數的功能
1. 函數的聲明和調用
在JavaScript中,函數可以通過多種方式聲明和調用,每種方式都有其適用場景。
基本函數聲明
// 聲明一個名為sayHello的函數
// 它接受一個name參數,用于個性化問候
function sayHello(name) {// 使用console.log輸出問候語// 字符串拼接可以使用+運算符console.log("Hello, " + name + "!");// 也可以使用ES6的模板字符串console.log(`Hello, ${name}!`);
}// 調用函數并傳入參數"張三"
sayHello("張三");
// 輸出:
// Hello, 張三!
// Hello, 張三!// 再次調用傳入不同參數
sayHello("李四");
// 輸出:
// Hello, 李四!
// Hello, 李四!
帶多個參數的函數
// 聲明一個包含多個參數的函數
// 參數可以是任意類型:字符串、數字、布爾值、對象等
function introduce(name, age, job) {// 使用模板字符串構建自我介紹console.log(`大家好,我是${name},今年${age}歲,職業是${job}。`);// 可以添加更多邏輯if (age < 18) {console.log("我還是個未成年人~");}
}// 調用函數并傳入三個參數
introduce("王五", 28, "工程師");
// 輸出:
// 大家好,我是王五,今年28歲,職業是工程師。introduce("小明", 16, "學生");
// 輸出:
// 大家好,我是小明,今年16歲,職業是學生。
// 我還是個未成年人~
2. 函數的返回值
函數可以使用return語句返回一個值,這個值可以被其他代碼使用。返回值可以是任何JavaScript數據類型。
簡單返回值
// 聲明一個求兩數之和的函數
function sum(a, b) {// 返回a和b的和// return語句會立即結束函數執行return a + b;// 這行代碼永遠不會執行console.log("This won't be printed");
}// 調用函數并將返回值賦給result變量
let result = sum(3, 5);
console.log(result); // 輸出8// 可以直接在表達式中使用函數調用
console.log(sum(10, 20) * 2); // 輸出60// 也可以作為其他函數的參數
console.log(sum(sum(2,3), sum(4,5))); // 輸出14
復雜返回值
// 返回復雜數據結構的函數
function createUser(name, age) {// 返回一個對象// 可以根據參數動態計算屬性值return {username: name,userAge: age,isAdult: age >= 18,registeredAt: new Date(), // 添加注冊時間// 方法也可以作為返回值greet: function() {return `Hi, I'm ${this.username}`;}};
}let user = createUser("趙六", 25);
console.log(user);
/* 輸出:
{username: "趙六",userAge: 25,isAdult: true,registeredAt: [當前日期時間],greet: [Function: greet]
}
*/
console.log(user.greet()); // 輸出: Hi, I'm 趙六let teen = createUser("小紅", 16);
console.log(teen.isAdult); // false
3. 函數表達式
函數也可以作為表達式賦值給變量,這種形式更加靈活。
匿名函數表達式
// 將匿名函數賦值給multiply變量
// 函數表達式不會被提升,必須先定義后使用
let multiply = function(a, b) {return a * b;
};// 調用函數表達式
let product = multiply(4, 5);
console.log(product); // 輸出20// 立即調用的函數表達式(IIFE)
// 常用于創建獨立作用域
let result = (function(x, y) {return x / y;
})(10, 2);
console.log(result); // 輸出5// 帶參數的IIFE
(function(config) {console.log(`App starting with ${config.env} mode`);
})({ env: 'development', debug: true });
箭頭函數(ES6)
// 使用箭頭函數簡化語法
// 當只有一個參數和一條語句時最簡潔
let square = x => x * x;
console.log(square(5)); // 輸出25// 多參數箭頭函數
// 需要括號包裹參數
let greet = (name, time) => {return `Good ${time}, ${name}!`;
};
console.log(greet("張三", "morning")); // 輸出"Good morning, 張三!"// 箭頭函數與this綁定
const counter = {count: 0,increment: function() {setInterval(() => {// 箭頭函數不綁定自己的this,繼承自外圍this.count++;console.log(this.count);}, 1000);}
};
counter.increment();
實際應用場景
數據處理
// 格式化貨幣金額
function formatCurrency(amount) {// 添加貨幣符號并保留兩位小數return "¥" + amount.toFixed(2);
}
console.log(formatCurrency(29.9)); // 輸出"¥29.90"
console.log(formatCurrency(123.456)); // 輸出"¥123.46"// 更復雜的格式化函數
function formatPrice(amount, currency = '¥', decimal = 2) {const formatted = amount.toFixed(decimal);if (currency === '¥') {return currency + formatted;} else {return formatted + ' ' + currency;}
}
console.log(formatPrice(29.9)); // "¥29.90"
console.log(formatPrice(99.99, '$')); // "99.99 $"
表單驗證
// 驗證郵箱格式
function validateEmail(email) {// 使用正則表達式驗證基本郵箱格式const re = /\S+@\S+\.\S+/;return re.test(email);
}
console.log(validateEmail("test@example.com")); // true
console.log(validateEmail("invalid.email")); // false// 更全面的驗證函數
function validateForm(data) {const errors = {};if (!data.username) {errors.username = '用戶名不能為空';}if (!validateEmail(data.email)) {errors.email = '郵箱格式不正確';}if (data.password.length < 6) {errors.password = '密碼至少需要6個字符';}return Object.keys(errors).length === 0 ? null : errors;
}const formData = {username: '張三',email: 'test@',password: '123'
};
console.log(validateForm(formData));
/* 輸出:
{email: '郵箱格式不正確',password: '密碼至少需要6個字符'
}
*/
業務邏輯封裝
// 計算訂單總金額
function calculateTotal(items, taxRate) {// 計算小計let subtotal = items.reduce((sum, item) => sum + item.price, 0);// 計算稅費const tax = subtotal * taxRate;// 返回包含明細的對象return {subtotal: subtotal,tax: tax,total: subtotal + tax,itemsCount: items.length};
}const cartItems = [{ id: 1, name: '商品A', price: 100 },{ id: 2, name: '商品B', price: 200 },{ id: 3, name: '商品C', price: 150 }
];const orderTotal = calculateTotal(cartItems, 0.1);
console.log(orderTotal);
/* 輸出:
{subtotal: 450,tax: 45,total: 495,itemsCount: 3
}
*/// 更復雜的業務邏輯函數
function processOrder(order, paymentMethod) {// 驗證訂單if (!order.items || order.items.length === 0) {throw new Error('訂單中沒有商品');}// 計算總金額const total = calculateTotal(order.items, order.taxRate || 0.1);// 處理支付const paymentResult = processPayment(total.total, paymentMethod);// 記錄訂單const orderRecord = createOrderRecord(order, total, paymentResult);// 發送確認郵件sendConfirmationEmail(order.customerEmail, orderRecord);return orderRecord;
}
通過合理使用函數,可以使代碼更加模塊化、易于維護和測試。
八、數組
數組基礎概念
數組是編程中最常用的數據結構之一,它是一種用于存儲多個值的有序集合。數組中的每個元素都有一個對應的索引(從0開始的整數),通過這個索引可以快速訪問或修改特定位置的元素。數組在內存中是連續存儲的,這使得它的訪問效率非常高。
數組的特點
- 有序集合:數組中的元素按照插入順序排列
- 索引訪問:每個元素都有對應的從0開始的整數索引
- 連續內存:數組元素在內存中是連續存儲的
- 動態大小:在JavaScript中數組長度可以動態變化
- 混合類型:可以存儲不同類型的數據
數組的創建方式
1. 字面量方式(推薦)
// 創建數字數組
let numbers = [1, 2, 3, 4, 5]; // 創建字符串數組
let fruits = ['apple', 'banana', 'orange'];// 創建混合類型數組
let mixed = [1, 'text', true, null, {name: 'John'}];// 創建空數組
let empty = [];
2. 構造函數方式
// 創建包含元素的數組
let arr = new Array(6, 7, 8, 9, 10);// 創建指定長度的空數組
let emptyArr = new Array(5); // 長度為5,元素都是undefined// 創建單元素數組時要特別注意
let single = new Array(5); // 創建的是長度為5的空數組
let singleCorrect = [5]; // 這才是包含數字5的數組
數組的訪問和修改
let products = ['手機', '電腦', '平板', '耳機', '鼠標'];// 訪問元素
console.log(products[0]); // 輸出:"手機"
console.log(products[2]); // 輸出:"平板"// 訪問不存在的索引
console.log(products[10]); // 輸出:undefined// 修改元素
products[1] = '筆記本電腦'; // 修改第二個元素
products[3] = '藍牙耳機'; // 修改第四個元素// 添加新元素
products[5] = '鍵盤'; // 添加新元素
products[10] = '顯示器'; // 這會創建空位(empty items)console.log(products);
// 輸出:['手機', '筆記本電腦', '平板', '藍牙耳機', '鼠標', '鍵盤', 空 × 4, '顯示器']
數組的常用方法和屬性
1. 添加/刪除元素
// 初始化數組
let tasks = ['學習', '運動'];// push() - 末尾添加
tasks.push('閱讀'); // 添加單個
tasks.push('購物', '做飯'); // 添加多個// pop() - 末尾刪除
let lastTask = tasks.pop(); // 刪除并返回'做飯'// unshift() - 開頭添加
tasks.unshift('起床'); // 添加單個
tasks.unshift('刷牙', '洗臉'); // 添加多個// shift() - 開頭刪除
let firstTask = tasks.shift(); // 刪除并返回'刷牙'console.log(tasks); // 輸出:['洗臉', '起床', '學習', '運動', '閱讀']
2. 數組長度
let colors = ['red', 'green', 'blue'];// 獲取長度
console.log(colors.length); // 輸出:3// 修改長度
colors.length = 5; // 擴展數組
console.log(colors); // 輸出:['red', 'green', 'blue', empty × 2]colors.length = 2; // 截斷數組
console.log(colors); // 輸出:['red', 'green']// 清空數組
colors.length = 0;
console.log(colors); // 輸出:[]
3. 其他常用方法
// 初始化數組
let numbers = [1, 2, 3, 4, 5];// slice() - 提取子數組
let subArr = numbers.slice(1, 4); // [2, 3, 4]
let lastTwo = numbers.slice(-2); // [4, 5]// splice() - 修改數組
// 刪除
numbers.splice(2, 1); // 從索引2開始刪除1個 → [1, 2, 4, 5]
// 插入
numbers.splice(2, 0, 3); // 在索引2插入3 → [1, 2, 3, 4, 5]
// 替換
numbers.splice(1, 2, 'a', 'b'); // [1, 'a', 'b', 4, 5]// concat() - 合并數組
let arr1 = [1, 2];
let arr2 = [3, 4];
let combined = arr1.concat(arr2, [5, 6]); // [1, 2, 3, 4, 5, 6]
實際應用示例
示例1:購物車商品管理
// 初始化購物車
let cart = [];// 添加商品
function addProduct(id, name, price) {cart.push({id, name, price});console.log(`已添加: ${name}`);
}// 移除商品
function removeProduct(index) {if (index >= 0 && index < cart.length) {let removed = cart.splice(index, 1);console.log(`已移除: ${removed[0].name}`);} else {console.log('無效的索引');}
}// 計算總價
function calculateTotal() {return cart.reduce((total, item) => total + item.price, 0);
}// 使用示例
addProduct(1, 'iPhone 13', 5999);
addProduct(2, 'AirPods Pro', 1499);
addProduct(3, 'MacBook Pro', 12999);console.log('當前購物車:');
cart.forEach((item, index) => {console.log(`${index + 1}. ${item.name} - ¥${item.price}`);
});removeProduct(1); // 移除AirPods Proconsole.log(`總價: ¥${calculateTotal()}`);
示例2:成績處理系統
// 初始化成績數組
let scores = [85, 90, 78, 92, 88];// 計算統計信息
function calculateStats(scores) {if (scores.length === 0) return null;// 計算總分和平均分let sum = scores.reduce((a, b) => a + b, 0);let average = sum / scores.length;// 計算最高分和最低分let max = Math.max(...scores);let min = Math.min(...scores);// 找出不及格的成績let failed = scores.filter(score => score < 60);return {count: scores.length,sum,average: average.toFixed(2),max,min,failedCount: failed.length,failedScores: failed};
}// 添加新成績
function addScore(newScore) {scores.push(newScore);console.log(`已添加成績: ${newScore}`);
}// 刪除最低分
function removeLowest() {let minIndex = scores.indexOf(Math.min(...scores));if (minIndex !== -1) {let removed = scores.splice(minIndex, 1);console.log(`已移除最低分: ${removed[0]}`);}
}// 使用示例
console.log('初始成績:', scores.join(', '));
addScore(95);
removeLowest();let stats = calculateStats(scores);
console.log('統計信息:');
console.log(`數量: ${stats.count}`);
console.log(`總分: ${stats.sum}`);
console.log(`平均分: ${stats.average}`);
console.log(`最高分: ${stats.max}`);
console.log(`最低分: ${stats.min}`);
console.log(`不及格數量: ${stats.failedCount}`);
九、對象
對象的基本概念
對象是JavaScript中最重要的數據類型之一,它是一個無序的鍵值對集合,用于存儲和表示復雜的數據結構。在對象中:
鍵(key):稱為屬性(property),必須是字符串或Symbol類型。屬性名遵循標識符命名規則,但也可以使用引號包裹特殊字符作為屬性名。
值(value):稱為屬性值,可以是任意JavaScript數據類型,包括:
- 基本類型:字符串、數字、布爾值、null、undefined
- 復雜類型:數組、函數、其他對象
- 特殊類型:Symbol、BigInt
屬性描述符:每個屬性還有一組描述其特性的屬性描述符,包括:
value
:屬性的值writable
:是否可修改(默認true)enumerable
:是否可枚舉(默認true)configurable
:是否可配置(默認true)get
/set
:訪問器函數
例如,在瀏覽器環境中,document
對象就是一個典型的JavaScript對象,它包含了大量屬性和方法來操作DOM。
對象的創建方式
1. 對象字面量方式
這是最常用的創建對象方式,使用大括號{}語法,適合創建簡單的、一次性的對象:
let person = {// 基本類型屬性name: "張三", // 字符串屬性age: 20, // 數字屬性isStudent: true, // 布爾屬性// 復雜類型屬性courses: ["數學", "語文", "英語"], // 數組屬性address: { // 嵌套對象city: "北京",street: "中關村大街",getFullAddress() {return `${this.city} ${this.street}`}},// 方法屬性sayHello: function() {console.log("Hello, I'm " + this.name);},// ES6簡寫方法introduce() {console.log(`我叫${this.name},今年${this.age}歲`);},// 計算屬性名["id_" + Math.random().toString(36).substr(2)]: "隨機ID"
};// 使用對象
person.sayHello(); // 輸出: Hello, I'm 張三
console.log(person.address.getFullAddress()); // 輸出: 北京 中關村大街
2. 使用Object構造函數
通過new Object()
創建空對象,再動態添加屬性,適合需要動態構建對象的場景:
let student = new Object(); // 創建一個空對象// 添加屬性
student.name = "李四";
student.age = 18;// 添加方法
student.study = function(subject) {console.log(`${this.name}正在學習${subject}`);
};// 添加嵌套對象
student.scores = new Object();
student.scores.math = 90;
student.scores.english = 85;// 使用對象
student.study("數學"); // 輸出: 李四正在學習數學
console.log(student.scores.math); // 輸出: 90
3. 使用Object.create()方法
可以指定原型對象創建新對象,適合需要繼承的場景:
let animal = {type: "動物",makeSound() {console.log("發出聲音");}
};let dog = Object.create(animal);
dog.type = "狗";
dog.breed = "金毛";
dog.bark = function() {console.log("汪汪!");
};dog.makeSound(); // 繼承自animal: 發出聲音
dog.bark(); // 輸出: 汪汪!
4. 使用構造函數和new操作符
適合需要創建多個相似對象的場景:
function Car(make, model, year) {this.make = make;this.model = model;this.year = year;this.displayInfo = function() {console.log(`${this.year} ${this.make} ${this.model}`);};
}let myCar = new Car("Toyota", "Camry", 2020);
myCar.displayInfo(); // 輸出: 2020 Toyota Camry
對象屬性的訪問和操作
1. 點表示法訪問
最常用的屬性訪問方式,簡潔直觀:
let person = {name: "張三",age: 20,"first-name": "張" // 包含特殊字符的屬性名
};// 訪問屬性
console.log(person.name); // 輸出"張三"// 修改屬性
person.age = 21; // 修改age屬性
console.log(person.age); // 輸出21// 添加新屬性
person.gender = "男";
console.log(person.gender); // 輸出"男"// 無法訪問特殊字符屬性
// console.log(person.first-name); // 報錯
2. 方括號表示法訪問
當屬性名包含特殊字符或需要動態計算時使用:
let person = {"first-name": "張","last-name": "三",1: "數字作為屬性名",[Symbol("id")]: "symbol作為屬性名"
};// 訪問特殊字符屬性
console.log(person["first-name"]); // 輸出"張"// 動態訪問屬性
let propName = "last-name";
console.log(person[propName]); // 輸出"三"// 數字屬性名
console.log(person[1]); // 輸出"數字作為屬性名"
console.log(person["1"]); // 同上// Symbol屬性名
let sym = Symbol("id");
console.log(person[sym]); // 輸出"symbol作為屬性名"// 添加計算屬性
let dynamicProp = "score_" + Math.floor(Math.random() * 10);
person[dynamicProp] = 95;
console.log(person[dynamicProp]);
3. 屬性操作
刪除屬性
使用delete
操作符刪除對象屬性:
let person = {name: "張三",age: 20,gender: "男"
};console.log("age" in person); // 輸出true
delete person.age;
console.log(person.age); // 輸出undefined
console.log("age" in person); // 輸出false
檢查屬性存在
let person = {name: "張三"};// in操作符檢查屬性是否存在(包括原型鏈)
console.log("name" in person); // true
console.log("toString" in person); // true (繼承自Object.prototype)// hasOwnProperty檢查自有屬性
console.log(person.hasOwnProperty("name")); // true
console.log(person.hasOwnProperty("toString")); // false
遍歷屬性
let person = {name: "張三",age: 20,gender: "男"
};// for...in循環(包含繼承的可枚舉屬性)
for (let key in person) {console.log(key + ": " + person[key]);
}// Object.keys()獲取自有可枚舉屬性
let keys = Object.keys(person);
console.log(keys); // ["name", "age", "gender"]// Object.getOwnPropertyNames()獲取所有自有屬性
let allProps = Object.getOwnPropertyNames(person);
console.log(allProps);// Object.getOwnPropertySymbols()獲取Symbol屬性
let symbols = Object.getOwnPropertySymbols(person);
console.log(symbols);
對象方法的定義和調用
1. 定義方法
方法本質上是一個函數類型的屬性,有多種定義方式:
let calculator = {// 傳統函數表達式add: function(a, b) {return a + b;},// ES6方法簡寫subtract(a, b) {return a - b;},// 箭頭函數(注意this的指向問題)multiply: (a, b) => a * b,// 生成器方法*generateSequence(start, end) {for (let i = start; i <= end; i++) {yield i;}},// 異步方法async fetchData(url) {let response = await fetch(url);return await response.json();}
};
2. 調用方法
使用點表示法或方括號表示法調用對象方法:
let person = {name: "張三",age: 20,// 方法定義sayHello() {console.log(`你好,我是${this.name},今年${this.age}歲`);},// 另一個方法celebrateBirthday() {this.age++;console.log(`慶祝生日!現在${this.age}歲了`);}
};// 直接調用
person.sayHello(); // 輸出: 你好,我是張三,今年20歲// 動態調用
let methodName = "sayHello";
person[methodName](); // 同上// 鏈式調用
person.celebrateBirthday().sayHello(); // 先增加年齡,再打招呼
3. 方法中的this
在對象方法中,this
指向調用該方法的對象,但需要注意一些特殊情況:
let person = {name: "Alice",greet: function() {console.log("Hi, I'm " + this.name);},greetArrow: () => {console.log("Hi, I'm " + this.name); // 箭頭函數沒有自己的this}
};// 直接調用
person.greet(); // 輸出: Hi, I'm Alice
person.greetArrow(); // 輸出: Hi, I'm undefined (或全局name)// 方法賦值給變量
let greetFunc = person.greet;
greetFunc(); // 輸出: Hi, I'm undefined (this丟失)// 使用bind綁定this
let boundGreet = person.greet.bind(person);
boundGreet(); // 輸出: Hi, I'm Alice// 作為回調函數
setTimeout(person.greet, 1000); // this丟失
setTimeout(person.greet.bind(person), 1000); // 正確綁定
4. Getter和Setter
可以使用getter和setter定義訪問器屬性:
let user = {firstName: "張",lastName: "三",// getterget fullName() {return `${this.firstName} ${this.lastName}`;},// setterset fullName(value) {[this.firstName, this.lastName] = value.split(" ");}
};console.log(user.fullName); // 輸出: 張 三
user.fullName = "李 四";
console.log(user.firstName); // 輸出: 李
console.log(user.lastName); // 輸出: 四
對象的高級特性
1. 屬性描述符
可以使用Object.defineProperty
定義或修改屬性特性:
let obj = {};Object.defineProperty(obj, "readOnlyProp", {value: 42,writable: false,enumerable: true,configurable: false
});console.log(obj.readOnlyProp); // 42
obj.readOnlyProp = 100; // 靜默失敗(嚴格模式下報錯)
console.log(obj.readOnlyProp); // 仍然是42// 獲取屬性描述符
let descriptor = Object.getOwnPropertyDescriptor(obj, "readOnlyProp");
console.log(descriptor);
2. 對象凍結
可以限制對象的修改:
let person = {name: "張三",age: 20
};// 1. Object.preventExtensions: 禁止添加新屬性
Object.preventExtensions(person);
person.gender = "男"; // 靜默失敗(嚴格模式下報錯)// 2. Object.seal: 禁止添加/刪除屬性
Object.seal(person);
delete person.name; // 靜默失敗// 3. Object.freeze: 完全凍結對象
Object.freeze(person);
person.age = 21; // 靜默失敗// 檢查對象狀態
console.log(Object.isExtensible(person)); // false
console.log(Object.isSealed(person)); // true
console.log(Object.isFrozen(person)); // true
3. 原型和繼承
JavaScript使用原型鏈實現繼承:
// 父類
function Animal(name) {this.name = name;
}
Animal.prototype.speak = function() {console.log(`${this.name} makes a noise.`);
};// 子類
function Dog(name) {Animal.call(this, name); // 調用父類構造函數
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function() {console.log(`${this.name} barks.`);
};let d = new Dog("Rex");
d.speak(); // 輸出: Rex barks.
4. ES6類語法
ES6引入了更簡潔的類語法:
class Person {constructor(name, age) {this.name = name;this.age = age;}greet() {console.log(`Hello, I'm ${this.name}`);}static createAnonymous() {return new Person("Anonymous", 0);}
}class Student extends Person {constructor(name, age, grade) {super(name, age); // 調用父類構造函數this.grade = grade;}study() {console.log(`${this.name} is studying`);}
}let student = new Student("張三", 20, "A");
student.greet(); // 輸出: Hello, I'm 張三
student.study(); // 輸出: 張三 is studying