# JavaScript/TypeScript 原理详解## 为何用于网页开发、作用及与 Python 的对比---## 目录1. [JavaScript/TypeScript 为何用于网页和前端开发](#javascripttypescript-为何用于网页和前端开发)2. [在前端开发中的具体作用](#在前端开发中的具体作用)3. [与 Python 的详细对比](#与-python-的详细对比)4. [前端自动化开发:两种语言的选择](#前端自动化开发两种语言的选择)5. [实际代码对比](#实际代码对比)6. [环境搭建与第一个程序](#环境搭建与第一个程序)7. [学习建议](#学习建议)---## JavaScript/TypeScript 为何用于网页和前端开发### 1. 历史背景与设计初衷#### 1.1 诞生背景JavaScript 在 **1995年** 由 **Netscape 公司** 的 **Brendan Eich** 在 **10天内** 设计并实现。它的设计初衷非常明确:- **目标**:让网页具有交互性- **定位**:一种运行在浏览器端的脚本语言- **需求**:简单、轻量、易于学习#### 1.2 发展历程```1995年 - JavaScript 诞生(Netscape Navigator 2.0) ↓1996年 - 微软推出 JScript(IE 浏览器) ↓1997年 - ECMAScript 标准化(ES1) ↓2005年 - AJAX 技术出现,Web 2.0 时代开启 ↓2009年 - Node.js 发布,JS 可用于服务端 ↓2012年 - TypeScript 发布(微软) ↓2015年 - ES6/ES2015 重大更新,现代化 JS ↓2022年 - ES2022,持续发展中```### 2. 技术架构原因#### 2.1 浏览器架构现代浏览器由多个核心组件构成:```┌─────────────────────────────────────────────────────┐│ 浏览器主进程 │├─────────────┬─────────────┬─────────────┬───────────┤│ 渲染进程 │ GPU 进程 │ 网络进程 │ 插件进程 ││ │ │ │ ││ ┌─────────┐ │ ┌─────────┐ │ ┌─────────┐ │ ││ │ 渲染引擎 │ │ │ GPU │ │ │ HTTP │ │ ││ │ Blink/ │ │ │ 计算 │ │ │ 请求 │ │ ││ │ Gecko │ │ │ │ │ │ │ │ ││ │ │ │ └─────────┘ │ └─────────┘ │ ││ ├─────────┤ │ │ │ ││ │ JS引擎 │ │ │ │ ││ │ V8/ │ │ │ │ ││ │ Spider │ │ │ │ ││ │ Monkey │ │ │ │ ││ └─────────┘ │ │ │ │└─────────────┴─────────────┴─────────────┴───────────┘```#### 2.2 JavaScript 引擎的作用JavaScript 引擎是浏览器的核心组件之一,负责执行 JavaScript 代码:| 引擎 | 浏览器 | 特点 ||------|--------|------|| **V8** | Chrome、Edge、Node.js | 性能最强,Google 开发 || **SpiderMonkey** | Firefox | Mozilla 开发 || **JavaScriptCore** | Safari | Apple 开发 || **Chakra** | Edge (Legacy) | 微软开发 |**为什么需要 JS 引擎?**1. **HTML/CSS 是声明式的** - HTML 定义页面结构("这是什么") - CSS 定义页面样式("长什么样") - 但两者都无法处理"交互"和"动态"2. **JavaScript 是命令式的** - 定义"如何做" - 可以响应事件、修改状态 - 可以与浏览器 API 通信#### 2.3 浏览器提供的 JavaScript API浏览器为 JavaScript 提供了丰富的 API,使其能与浏览器深度集成:```javascript// 1. DOM API - 操作页面内容document.getElementById('id')document.createElement('div')element.appendChild(child)// 2. CSSOM API - 操作样式element.style.color = 'red'window.getComputedStyle(element)// 3. Event API - 事件处理element.addEventListener('click', handler)window.onclick = handler// 4. Fetch API - 网络请求fetch('https://api.example.com/data')// 5. Storage API - 本地存储localStorage.setItem('key', 'value')sessionStorage.getItem('key')// 6. Geolocation API - 地理位置navigator.geolocation.getCurrentPosition(success, error)// 7. Canvas API - 图形绘制const ctx = canvas.getContext('2d');ctx.fillRect(0, 0, 100, 100);// 8. Web Audio API - 音频处理const audioCtx = new AudioContext();// 9. WebSocket API - 实时通信const ws = new WebSocket('ws://example.com');```### 3. 为什么浏览器只支持 JavaScript?#### 3.1 历史路径依赖- 1995年 JavaScript 诞生后,Netscape 和 IE 都实现了 JS 引擎- 1999年 ECMAScript 第三版成为标准,各大浏览器统一支持- 此后积累了大量网页使用 JS- 更换语言意味着破坏所有现有网页#### 3.2 WebAssembly 的补充近年来浏览器引入了 **WebAssembly (WASM)**,允许运行其他语言编译的二进制代码:```JavaScript ←→ 浏览器原生支持WebAssembly ←→ 编译自 C++/Rust/Go 等语言```但 WebAssembly 不能直接操作 DOM,仍需通过 JS 桥接。### 4. TypeScript 的价值TypeScript 是 JavaScript 的超集,它在 JS 的基础上添加了:```typescript// JavaScript - 动态类型,运行时才报错function calculateTotal(price, quantity, tax) { return (price * quantity) * (1 + tax);}// 调用时可能传入错误类型calculateTotal("100", 5, 0.1); // 结果错误,但不会立即报错// TypeScript - 静态类型,编译时发现错误function calculateTotal( price: number, quantity: number, tax: number): number { return (price * quantity) * (1 + tax);}// 调用时类型检查calculateTotal("100", 5, 0.1); // 编译错误:不能将类型"string"分配给参数```**TypeScript 的优势:**1. **类型安全** - 编译时发现错误2. **IDE 支持** - 自动补全、重构、导航3. **代码可维护性** - 类型即文档4. **团队协作** - 降低沟通成本---## 在前端开发中的具体作用### 1. Web 技术的三层架构```┌───────────────────────────────────────────────────┐│ 用户浏览器 │├───────────────────────────────────────────────────┤│ ││ ┌────────────────────────────────────────────┐ ││ │ HTML (结构层) │ ││ │ - 定义页面内容 │ ││ │ - 语义化标签 │ ││ │ - 示例: │ ││ │ <h1>标题</h1> │ ││ │ <p>段落</p> │ ││ │ <div id="content">内容</div> │ ││ └────────────────────────────────────────────┘ ││ ││ ┌────────────────────────────────────────────┐ ││ │ CSS (表现层) │ ││ │ - 定义页面样式 │ ││ │ - 响应式布局 │ ││ │ - 动画效果 │ ││ │ - 示例: │ ││ │ #content { │ ││ │ color: #333; │ ││ │ font-size: 16px; │ ││ │ animation: fade 1s; │ ││ │ } │ ││ └────────────────────────────────────────────┘ ││ ││ ┌────────────────────────────────────────────┐ ││ │ JavaScript (行为层) │ ││ │ - 实现交互逻辑 │ ││ │ - 处理数据 │ ││ │ - 控制页面行为 │ ││ │ - 示例: │ ││ │ const el = document.getElementById('content') │ ││ │ el.textContent = '新内容' │ ││ └────────────────────────────────────────────┘ ││ │└───────────────────────────────────────────────────┘```### 2. JavaScript/TypeScript 的核心职责#### 2.1 页面交互```javascript// 按钮点击事件document.getElementById('submit-btn').addEventListener('click', function() { const username = document.getElementById('username').value; const password = document.getElementById('password').value; if (!username || !password) { alert('请填写完整信息'); return; } // 提交表单 login(username, password);});// 悬停效果const card = document.querySelector('.card');card.addEventListener('mouseenter', function() { this.classList.add('hover');});card.addEventListener('mouseleave', function() { this.classList.remove('hover');});// 滚动事件window.addEventListener('scroll', function() { const header = document.querySelector('header'); if (window.scrollY > 100) { header.classList.add('fixed'); } else { header.classList.remove('fixed'); }});```#### 2.2 表单验证```javascript// 实时验证用户名function validateUsername(username) { // 长度检查 if (username.length < 3 || username.length > 20) { return { valid: false, message: '用户名长度为 3-20 个字符' }; } // 格式检查 if (!/^[a-zA-Z0-9_]+$/.test(username)) { return { valid: false, message: '只能包含字母、数字和下划线' }; } return { valid: true };}// 邮箱验证function validateEmail(email) { const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return regex.test(email);}// 密码强度验证function validatePassword(password) { let strength = 0; if (password.length >= 8) strength++; if (/[a-z]/.test(password)) strength++; if (/[A-Z]/.test(password)) strength++; if (/[0-9]/.test(password)) strength++; if (/[^a-zA-Z0-9]/.test(password)) strength++; return strength; // 0-5,5 最强}// 使用示例document.getElementById('password').addEventListener('input', function(e) { const strength = validatePassword(e.target.value); const indicator = document.getElementById('strength-indicator'); if (strength >= 4) { indicator.textContent = '强'; indicator.className = 'strength strong'; } else if (strength >= 2) { indicator.textContent = '中'; indicator.className = 'strength medium'; } else { indicator.textContent = '弱'; indicator.className = 'strength weak'; }});```#### 2.3 动态内容加载(AJAX)```javascript// 使用 Fetch API 加载数据async function loadProducts() { try { const response = await fetch('https://api.example.com/products'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const products = await response.json(); // 动态渲染产品列表 const container = document.getElementById('product-list'); container.innerHTML = products.map(product => ` <div class="product-card"> <img src="${product.image}" alt="${product.name}"> <h3>${product.name}</h3> <p class="price">¥${product.price}</p> <button onclick="addToCart(${product.id})">加入购物车</button> </div> `).join(''); } catch (error) { console.error('加载产品失败:', error); document.getElementById('error-message').textContent = '加载失败,请稍后重试'; }}// 页面加载时执行document.addEventListener('DOMContentLoaded', loadProducts);```#### 2.4 单页应用(SPA)路由```javascript// 简单的路由实现const routes = { '/': 'home', '/about': 'about', '/products': 'products', '/contact': 'contact'};function navigate(path) { // 更新 URL history.pushState(null, '', path); // 渲染对应页面 const page = routes[path] || 'home'; renderPage(page);}function renderPage(pageName) { // 隐藏所有页面 document.querySelectorAll('.page').forEach(el => { el.style.display = 'none'; }); // 显示目标页面 document.getElementById(`${pageName}-page`).style.display = 'block';}// 监听浏览器后退/前进window.addEventListener('popstate', function() { const path = window.location.pathname; const page = routes[path] || 'home'; renderPage(page);});// 导航链接点击事件document.querySelectorAll('.nav-link').forEach(link => { link.addEventListener('click', function(e) { e.preventDefault(); const path = this.getAttribute('href'); navigate(path); });});```#### 2.5 状态管理```javascript// 简单的状态管理器class Store { constructor(initialState = {}) { this.state = initialState; this.listeners = []; } // 获取状态 getState() { return this.state; } // 更新状态 setState(newState) { this.state = { ...this.state, ...newState }; this.notify(); } // 订阅状态变化 subscribe(listener) { this.listeners.push(listener); } // 通知所有订阅者 notify() { this.listeners.forEach(listener => listener(this.state)); }}// 使用示例const store = new Store({ user: null, cart: [], isLoading: false});// 订阅状态变化store.subscribe(function(state) { // 更新 UI updateUserDisplay(state.user); updateCartCount(state.cart.length); updateLoadingIndicator(state.isLoading);});// 更新状态store.setState({ user: { name: 'Tom', email: 'tom@example.com' }, isLoading: false});// 添加到购物车function addToCart(product) { const cart = store.getState().cart; store.setState({ cart: [...cart, product] });}```#### 2.6 本地存储```javascript// LocalStorage - 永久存储(除非手动清除)const user = { name: 'Tom', email: 'tom@example.com', preferences: { theme: 'dark', language: 'zh-CN' }};// 保存数据localStorage.setItem('user', JSON.stringify(user));// 读取数据const savedUser = JSON.parse(localStorage.getItem('user'));console.log(savedUser);// SessionStorage - 会话存储(关闭标签页后清除)sessionStorage.setItem('tempData', '临时数据');// IndexedDB - 更强大的数据库// 适合存储大量结构化数据const request = indexedDB.open('myDatabase', 1);request.onerror = function(event) { console.error('Database error:', event.target.error);};request.onsuccess = function(event) { const db = event.target.result; // 使用数据库};request.onupgradeneeded = function(event) { const db = event.target.result; // 创建对象存储 const objectStore = db.createObjectStore('users', { keyPath: 'id' }); // 创建索引 objectStore.createIndex('name', 'name', { unique: false });};```### 3. 现代前端框架中的角色#### 3.1 React```typescriptimport React, { useState, useEffect } from 'react';interface User { id: number; name: string; email: string;}function UserProfile() { // 状态管理 const [user, setUser] = useState<User | null>(null); const [loading, setLoading] = useState(true); // 副作用(数据获取) useEffect(() => { async function fetchUser() { try { const response = await fetch('/api/user/1'); const data = await response.json(); setUser(data); } catch (error) { console.error('Failed to fetch user:', error); } finally { setLoading(false); } } fetchUser(); }, []); // 空依赖数组表示只在挂载时执行 if (loading) { return <div>加载中...</div>; } if (!user) { return <div>用户不存在</div>; } return ( <div className="user-profile"> <h1>{user.name}</h1> <p>{user.email}</p> <button onClick={() => handleEdit(user.id)}>编辑</button> </div> );}```#### 3.2 Vue```typescript<script setup lang="ts">import { ref, onMounted } from 'vue';interface User { id: number; name: string; email: string;}const user = ref<User | null>(null);const loading = ref(true);onMounted(async () => { try { const response = await fetch('/api/user/1'); user.value = await response.json(); } catch (error) { console.error('Failed to fetch user:', error); } finally { loading.value = false; }});function handleEdit(id: number) { // 编辑逻辑}</script><template> <div class="user-profile" v-if="user"> <h1>{{ user.name }}</h1> <p>{{ user.email }}</p> <button @click="handleEdit(user.id)">编辑</button> </div> <div v-else-if="loading">加载中...</div></template>```---## 与 Python 的详细对比### 1. 运行环境对比| 维度 | JavaScript/TypeScript | Python ||------|----------------------|--------|| **原生运行环境** | 浏览器、Node.js | 操作系统、虚拟机 || **浏览器支持** | ✅ 所有浏览器原生支持 | ❌ 不支持 || **服务端运行** | ✅ Node.js | ✅ 原生支持 || **移动端** | ✅ React Native, NativeScript | ❌ 需要桥接或转换 || **桌面应用** | ✅ Electron, Tauri | ✅ Tkinter, PyQt, Kivy || **嵌入式** | ⚠️ 有限 | ✅ MicroPython, CircuitPython |### 2. 语法对比详细版#### 2.1 变量声明```javascript// JavaScript// 声明方式多样let name = "Tom"; // 块级作用域,可变const age = 18; // 块级作用域,不可变var city = "Beijing"; // 函数作用域,不推荐// TypeScript - 带类型注解let username: string = "Tom";let userAge: number = 18;let isAdmin: boolean = true;// 联合类型let value: string | number = "hello";value = 123; // OK``````python# Python# 声明方式统一name = "Tom" # 自动推断为 strage = 18 # 自动推断为 intcity = "Beijing" # 自动推断为 str# 类型提示(可选)username: str = "Tom"user_age: int = 18is_admin: bool = True# 联合类型from typing import Unionvalue: Union[str, int] = "hello"value = 123 # OK```#### 2.2 数据结构```javascript// JavaScript 数组let arr = [1, 2, 3, 4, 5];// 添加元素arr.push(6); // 末尾添加arr.unshift(0); // 开头添加arr.splice(2, 0, 2.5); // 指定位置插入// 删除元素arr.pop(); // 删除末尾arr.shift(); // 删除开头arr.splice(2, 1); // 删除指定位置// 遍历arr.forEach((item, index) => { console.log(index, item);});// 函数式操作const doubled = arr.map(x => x * 2); // 映射const evens = arr.filter(x => x % 2 === 0); // 过滤const sum = arr.reduce((a, b) => a + b, 0); // 累加// JavaScript 对象let obj = { name: "Tom", age: 18, greet: function() { return `Hello, ${this.name}`; }};// 访问属性console.log(obj.name); // 点表示法console.log(obj["age"]); // 方括号表示法// 解构赋值const { name, age } = obj;// 对象展开const obj2 = { ...obj, city: "Beijing" };``````python# Python 列表arr = [1, 2, 3, 4, 5]# 添加元素arr.append(6) # 末尾添加arr.insert(0, 0) # 开头插入arr.insert(2, 2.5) # 指定位置插入# 删除元素arr.pop() # 删除末尾arr.pop(0) # 删除指定位置del arr[2] # 删除指定位置arr.remove(3) # 删除指定值# 列表推导式(函数式操作)doubled = [x * 2 for x in arr]evens = [x for x in arr if x % 2 == 0]sum_value = sum(arr)# 遍历for index, item in enumerate(arr): print(index, item)# Python 字典obj = { "name": "Tom", "age": 18}# 定义方法def greet(self): return f"Hello, {self['name']}"# 访问属性print(obj["name"]) # 方括号表示法print(obj.get("age")) # get 方法(默认 None)# 解构赋值name, age = obj["name"], obj["age"]# 字典展开(Python 3.9+)obj2 = {**obj, "city": "Beijing"}```#### 2.3 函数定义```javascript// JavaScript 函数声明function add(a, b) { return a + b;}// 函数表达式const multiply = function(a, b) { return a * b;};// 箭头函数(ES6)const subtract = (a, b) => a - b;// 带默认参数function greet(name = "Guest") { return `Hello, ${name}`;}// 可变参数function sum(...numbers) { return numbers.reduce((a, b) => a + b, 0);}// 使用sum(1, 2, 3); // 6// TypeScript - 带类型注解function divide(a: number, b: number): number { return a / b;}// 可选参数function createUser(name: string, age?: number): void { console.log(name, age);}// 泛型函数function identity<T>(arg: T): T { return arg;}identity<string>("hello");identity<number>(123);``````python# Python 函数定义def add(a, b): return a + b# lambda 函数multiply = lambda a, b: a * b# 带默认参数def greet(name="Guest"): return f"Hello, {name}"# 可变参数def sum(*numbers): return sum(numbers)# 使用sum(1, 2, 3) # 6# 关键字参数def create_user(name, age=None): print(name, age)# 类型提示def divide(a: int, b: int) -> float: return a / b# 泛型(使用 TypeVar)from typing import TypeVarT = TypeVar('T')def identity(arg: T) -> T: return argidentity(str)("hello")identity(int)(123)```#### 2.4 类和面向对象```javascript// JavaScript 类(ES6)class Animal { constructor(name, age) { this.name = name; this.age = age; } speak() { return `${this.name} makes a sound`; } static species() { return "Animal"; }}// 继承class Dog extends Animal { constructor(name, age, breed) { super(name, age); // 调用父类构造函数 this.breed = breed; } speak() { return `${this.name} barks`; } fetch() { return `${this.name} fetches the ball`; }}// 使用const dog = new Dog("Buddy", 3, "Golden Retriever");console.log(dog.speak()); // "Buddy barks"console.log(dog.fetch()); // "Buddy fetches the ball"// TypeScript - 类型系统interface AnimalInterface { name: string; age: number; speak(): string;}class Cat implements AnimalInterface { private _name: string; // 私有属性 constructor(public name: string, public age: number) { this._name = name; } speak(): string { return `${this.name} meows`; } // getter/setter get description(): string { return `${this.name} is ${this.age} years old`; }}``````python# Python 类class Animal: def __init__(self, name, age): self.name = name self.age = age def speak(self): return f"{self.name} makes a sound" @staticmethod def species(): return "Animal"# 继承class Dog(Animal): def __init__(self, name, age, breed): super().__init__(name, age) # 调用父类构造函数 self.breed = breed def speak(self): return f"{self.name} barks" def fetch(self): return f"{self.name} fetches the ball"# 使用dog = Dog("Buddy", 3, "Golden Retriever")print(dog.speak()) # "Buddy barks"print(dog.fetch()) # "Buddy fetches the ball"# 抽象基类from abc import ABC, abstractmethodclass AnimalInterface(ABC): @abstractmethod def speak(self) -> str: passclass Cat(AnimalInterface): def __init__(self, name: str, age: int): self.name = name self.age = age def speak(self) -> str: return f"{self.name} meows" @property def description(self) -> str: return f"{self.name} is {self.age} years old"```#### 2.5 异步编程```javascript// JavaScript - Promisefunction fetchData() { return new Promise((resolve, reject) => { setTimeout(() => { resolve({ data: "Hello" }); }, 1000); });}// 使用 PromisefetchData() .then(result => console.log(result)) .catch(error => console.error(error));// async/awaitasync function main() { try { const result = await fetchData(); console.log(result); } catch (error) { console.error(error); }}// 并发请求async function fetchMultiple() { const [result1, result2, result3] = await Promise.all([ fetchData(), fetchData(), fetchData() ]); return [result1, result2, result3];}// 顺序执行async function sequential() { const result1 = await fetchData(); const result2 = await fetchData(); const result3 = await fetchData(); return [result1, result2, result3];}``````python# Python - asyncioimport asyncioasync def fetch_data(): await asyncio.sleep(1) return {"data": "Hello"}# 使用协程async def main(): try: result = await fetch_data() print(result) except Exception as error: print(error)# 并发请求async def fetch_multiple(): results = await asyncio.gather( fetch_data(), fetch_data(), fetch_data() ) return results# 顺序执行async def sequential(): result1 = await fetch_data() result2 = await fetch_data() result3 = await fetch_data() return [result1, result2, result3]# 运行异步代码if __name__ == "__main__": asyncio.run(main())```### 3. 类型系统对比| 特性 | JavaScript | TypeScript | Python ||------|-----------|-----------|--------|| **类型检查时间** | 运行时 | 编译时 | 运行时(类型提示可选) || **类型安全性** | 弱类型 | 强类型 | 强类型 || **类型推断** | 部分支持 | 完整支持 | 部分支持 || **错误发现** | 运行时错误 | 编译期错误 | 运行时错误 || **类型定义** | 无 | 接口、类型别名 | 类型提示(PEP 484) |```javascript// JavaScript - 类型错误运行时才发现function add(a, b) { return a + b;}// 可能的问题add(1, 2); // 3 ✓add("1", 2); // "12" ✗ 类型不一致但不会立即报错add(null, 2); // "2null" ✗ 意外行为``````typescript// TypeScript - 编译期发现错误function add(a: number, b: number): number { return a + b;}// 编译检查add(1, 2); // 3 ✓add("1", 2); // 编译错误 ✗add(null, 2); // 编译错误 ✗// 联合类型function combine(a: string | number, b: string | number): string | number { return a + b; // 需要进一步类型检查}``````python# Python - 运行时才发现(类型提示不影响执行)def add(a, b): return a + b# 运行时行为add(1, 2) # 3 ✓add("1", 2) # "12" ✗ 不会立即报错# add(1, None) # TypeError 运行时错误# 类型提示def add_typed(a: int, b: int) -> int: return a + b# 类型检查工具(如 mypy)# 需要额外运行类型检查器```### 4. 生态系统对比#### 4.1 包管理器| 语言 | 包管理器 | 仓库 | 特点 ||------|---------|------|------|| JavaScript | npm, yarn, pnpm | npm registry | 生态最大 || TypeScript | npm, yarn, pnpm | npm registry | 复用 npm 生态 || Python | pip, poetry | PyPI | 稳定成熟 |```bash# JavaScript/TypeScript 安装包npm install axiosnpm install --save-dev @types/axios # TypeScript 类型定义# Python 安装包pip install requests```#### 4.2 测试框架```javascript// JavaScript/TypeScript - Jestdescribe('Calculator', () => { test('adds two numbers', () => { expect(add(1, 2)).toBe(3); }); test('throws error for invalid input', () => { expect(() => add('a', 'b')).toThrow(); });});// 运行测试npm test``````python# Python - pytestdef test_add(): assert add(1, 2) == 3def test_add_invalid(): with pytest.raises(TypeError): add("a", "b")# 运行测试pytest```#### 4.3 热门库对比| 用途 | JavaScript/TypeScript | Python ||------|----------------------|--------|| **HTTP 请求** | axios, fetch | requests, httpx || **Web 框架** | Express, Nest.js | Flask, Django, FastAPI || **数据处理** | Lodash, D3.js | Pandas, NumPy || **数据库** | Sequelize, Prisma | SQLAlchemy, Django ORM || **自动化测试** | Playwright, Cypress | Selenium, pytest-playwright || **机器学习** | TensorFlow.js | TensorFlow, PyTorch, scikit-learn || **爬虫** | Puppeteer, Cheerio | Scrapy, BeautifulSoup |### 5. 性能对比#### 5.1 V8 引擎性能特点JavaScript (V8) 的性能优势:- **JIT 编译**:将热点代码编译为机器码- **优化编译**:对频繁执行的代码进行优化- **内联缓存**:加速属性访问```javascript// V8 会优化这段代码function sumArray(arr) { let sum = 0; for (let i = 0; i < arr.length; i++) { sum += arr[i]; } return sum;}// 首次调用:解释执行// 多次调用后:编译为机器码```#### 5.2 Python 性能特点Python 的性能特点:- **解释执行**:每次解释一行代码- **C 扩展**:关键操作用 C 实现(如 NumPy)- **全局解释器锁 (GIL)**:限制了多线程性能```python# Python 使用 C 扩展加速import numpy as np# 这些操作在 C 层执行,速度很快arr = np.array([1, 2, 3, 4, 5])sum_result = np.sum(arr)```#### 5.3 性能对比示例| 场景 | JavaScript | Python | 说明 ||------|-----------|--------|------|| **字符串操作** | 快 | 较快 | 两者接近 || **数值计算** | 中等 | 慢(标准库) | NumPy 很快 || **数组操作** | 快 | 慢(列表) | NumPy 很快 || **JSON 解析** | 快 | 较快 | 原生 JSON 支持 || **HTTP 请求** | 快 | 较慢 | Node.js 事件循环 || **DOM 操作** | 快 | 不适用 | 浏览器环境 |### 6. 并发模型对比#### 6.1 JavaScript 事件循环```javascriptconsole.log('1');setTimeout(() => { console.log('2');}, 0);console.log('3');// 输出顺序:1, 3, 2// 原因:setTimeout 回调放入任务队列,事件循环执行``````JavaScript 事件循环模型:┌─────────────────────────────────────────┐│ 调用栈 (Call Stack) ││ ┌─────────────────────────────────┐ ││ │ console.log('1') │ ││ │ console.log('3') │ ││ └─────────────────────────────────┘ │└─────────────────────────────────────────┘ ↓┌─────────────────────────────────────────┐│ 任务队列 (Task Queue) ││ ┌─────────────────────────────────┐ ││ │ setTimeout(callback) │ ││ └─────────────────────────────────┘ │└─────────────────────────────────────────┘```#### 6.2 Python 多线程/多进程```pythonimport threadingimport multiprocessing# 多线程(受 GIL 限制)def worker(): print("Thread working")threads = []for i in range(5): t = threading.Thread(target=worker) t.start() threads.append(t)for t in threads: t.join()# 多进程(绕过 GIL)def process_worker(): print("Process working")processes = []for i in range(5): p = multiprocessing.Process(target=process_worker) p.start() processes.append(p)for p in processes: p.join()# 异步 (asyncio)import asyncioasync def async_worker(): print("Async working")async def main(): tasks = [async_worker() for _ in range(5)] await asyncio.gather(*tasks)asyncio.run(main())```---## 前端自动化开发:两种语言的选择### 1. Python 方案(你当前使用)#### 1.1 优势| 优势 | 说明 ||------|------|| **学习曲线平缓** | 语法简单,易于上手 || **测试库丰富** | Selenium, pytest, Robot Framework || **数据处理强** | Pandas, NumPy 方便处理测试数据 || **生态成熟** | 大量第三方库和工具 || **脚本语言** | 快速编写,无需编译 || **调试友好** | 丰富的调试工具 |#### 1.2 主流框架```python# 1. Selenium + pytestfrom selenium import webdriverimport pytestdef test_login(): driver = webdriver.Chrome() driver.get("https://example.com/login") driver.find_element("id", "username").send_keys("admin") driver.find_element("id", "password").send_keys("password") driver.find_element("id", "login-btn").click() assert driver.title == "Dashboard" driver.quit()# 2. Playwright for Pythonfrom playwright.sync_api import sync_playwrightdef test_login(): with sync_playwright() as p: browser = p.chromium.launch(headless=False) page = browser.new_page() page.goto("https://example.com/login") page.fill("#username", "admin") page.fill("#password", "password") page.click("#login-btn") assert page.title() == "Dashboard" browser.close()# 3. Robot Framework (关键字驱动)*** Settings ***Library SeleniumLibrary*** Test Cases ***Login Test Open Browser https://example.com/login Chrome Input Text id=username admin Input Text id=password password Click Button id=login-btn Title Should Be Dashboard Close Browser```#### 1.3 实际项目结构```astro-gui-auto/├── pages/ # 页面对象│ ├── basepage.py # 基础页面│ ├── login_page.py # 登录页│ └── dashboard_page.py # 仪表板页├── tests/ # 测试用例│ ├── test_login.py # 登录测试│ └── test_dashboard.py # 仪表板测试├── data/ # 测试数据│ └── test_data.xlsx # Excel 数据文件├── config/ # 配置文件│ └── config.ini # 配置├── utils/ # 工具类│ └── excel_utils.py # Excel 工具├── conftest.py # pytest 配置└── requirements.txt # 依赖列表```### 2. JavaScript/TypeScript 方案#### 2.1 优势| 优势 | 说明 ||------|------|| **与前端技术栈一致** | 与 React, Vue 等框架无缝集成 || **浏览器原生支持** | 直接操作 DOM,性能更好 || **调试方便** | 浏览器 DevTools 直接调试 || **异步处理** | Promise, async/await 更自然 || **类型安全** | TypeScript 提供编译时检查 || **包管理** | npm 生态庞大 || **工具链成熟** | ESLint, Prettier, Webpack |#### 2.2 主流框架```typescript// 1. Playwright (TypeScript 原生)import { test, expect } from '@playwright/test';test.describe('Login Tests', () => { test('should login successfully', async ({ page }) => { await page.goto('https://example.com/login'); await page.fill('#username', 'admin'); await page.fill('#password', 'password'); await page.click('#login-btn'); await expect(page).toHaveTitle('Dashboard'); });});// 2. Cypress (端到端测试)describe('Login Tests', () => { it('should login successfully', () => { cy.visit('/login'); cy.get('#username').type('admin'); cy.get('#password').type('password'); cy.get('#login-btn').click(); cy.title().should('eq', 'Dashboard'); });});// 3. Puppeteerconst puppeteer = require('puppeteer');const { expect } = require('chai');describe('Login Tests', () => { let browser, page; before(async () => { browser = await puppeteer.launch({ headless: false }); page = await browser.newPage(); }); after(async () => { await browser.close(); }); it('should login successfully', async () => { await page.goto('https://example.com/login'); await page.type('#username', 'admin'); await page.type('#password', 'password'); await page.click('#login-btn'); const title = await page.title(); expect(title).to.equal('Dashboard'); });});```#### 2.3 实际项目结构```e2e-tests/├── src/│ ├── page-objects/ # 页面对象│ │ ├── base-page.ts│ │ ├── login-page.ts│ │ └── dashboard-page.ts│ ├── tests/ # 测试用例│ │ ├── login.spec.ts│ │ └── dashboard.spec.ts│ ├── data/ # 测试数据│ │ └── test-data.ts│ └── utils/ # 工具类│ └── helpers.ts├── playwright.config.ts # Playwright 配置├── package.json # 依赖和脚本├── tsconfig.json # TypeScript 配置└── .eslintrc.json # ESLint 配置```### 3. 对比总结| 维度 | Python 方案 | JavaScript/TypeScript 方案 ||------|------------|--------------------------|| **学习成本** | 低 | 中等 || **调试体验** | 良好 | 优秀(浏览器 DevTools) || **与前端协作** | 需要沟通 | 无缝对接 || **代码可读性** | 高 | 高 || **类型安全** | 需要额外工具 | TypeScript 原生支持 || **性能** | 良好 | 更好(直接操作浏览器) || **生态丰富度** | 高 | 最高 || **社区活跃度** | 高 | 极高 |---## 实际代码对比### 1. 登录功能对比#### Python 实现```python# pages/basepage.pyfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.common.by import Byclass BasePage: def __init__(self, driver): self.driver = driver self.wait = WebDriverWait(driver, 10) def find_element(self, locator): """查找元素""" return self.wait.until( EC.presence_of_element_located(locator) ) def click(self, locator): """点击元素""" element = self.find_element(locator) element.click() def input_text(self, locator, text): """输入文本""" element = self.find_element(locator) element.clear() element.send_keys(text) def wait_for_text(self, locator, text): """等待文本出现""" self.wait.until( EC.text_to_be_present_in_element(locator, text) )# pages/login_page.pyfrom pages.basepage import BasePagefrom selenium.webdriver.common.by import Byclass LoginPage(BasePage): # 定位器 USERNAME_INPUT = (By.ID, "username") PASSWORD_INPUT = (By.ID, "password") LOGIN_BUTTON = (By.ID, "login-btn") def login(self, username, password): """登录操作""" self.input_text(self.USERNAME_INPUT, username) self.input_text(self.PASSWORD_INPUT, password) self.click(self.LOGIN_BUTTON)# tests/test_login.pyimport pytestfrom selenium import webdriverfrom pages.login_page import LoginPage@pytest.fixturedef driver(): """初始化浏览器""" driver = webdriver.Chrome() yield driver driver.quit()def test_login_success(driver): """测试成功登录""" driver.get("https://example.com/login") login_page = LoginPage(driver) login_page.login("admin", "password123") # 验证登录成功 assert "Dashboard" in driver.title```#### TypeScript 实现```typescript// page-objects/base-page.tsimport { Page, Locator } from '@playwright/test';export class BasePage { constructor(protected page: Page) {} async click(selector: string): Promise<void> { await this.page.click(selector); } async fill(selector: string, value: string): Promise<void> { await this.page.fill(selector, value); } async waitForSelector(selector: string): Promise<Locator> { return await this.page.waitForSelector(selector); } async getText(selector: string): Promise<string> { return await this.page.textContent(selector) || ''; } async waitURLContains(text: string): Promise<void> { await this.page.waitForURL(`**/*${text}*`); }}// page-objects/login-page.tsimport { BasePage } from './base-page';export class LoginPage extends BasePage { private readonly usernameInput = '#username'; private readonly passwordInput = '#password'; private readonly loginButton = '#login-btn'; async login(username: string, password: string): Promise<void> { await this.fill(this.usernameInput, username); await this.fill(this.passwordInput, password); await this.click(this.loginButton); }}// tests/login.spec.tsimport { test, expect } from '@playwright/test';import { LoginPage } from '../page-objects/login-page';test.describe('Login Tests', () => { test('should login successfully', async ({ page }) => { const loginPage = new LoginPage(page); await page.goto('https://example.com/login'); await loginPage.login('admin', 'password123'); // 验证登录成功 await expect(page).toHaveTitle(/Dashboard/); }); test('should show error for invalid credentials', async ({ page }) => { const loginPage = new LoginPage(page); await page.goto('https://example.com/login'); await loginPage.login('wrong', 'wrong'); // 验证错误消息 const errorMessage = await page.textContent('.error-message'); expect(errorMessage).toContain('用户名或密码错误'); });});```### 2. 数据驱动测试对比#### Python 实现```python# tests/test_data_driven.pyimport pytestimport openpyxlfrom selenium import webdriverfrom pages.login_page import LoginPagedef read_test_data(): """从 Excel 读取测试数据""" workbook = openpyxl.load_workbook('data/test_cases.xlsx') sheet = workbook.active test_data = [] for row in sheet.iter_rows(min_row=2, values_only=True): test_data.append({ 'username': row[0], 'password': row[1], 'expected': row[2], 'description': row[3] }) return test_data@pytest.fixturedef driver(): driver = webdriver.Chrome() yield driver driver.quit()@pytest.mark.parametrize("data", read_test_data())def test_login_data_driven(driver, data): """数据驱动登录测试""" driver.get("https://example.com/login") login_page = LoginPage(driver) login_page.login(data['username'], data['password']) if data['expected'] == 'success': assert "Dashboard" in driver.title else: error_msg = driver.find_element(By.CLASS_NAME, "error").text assert "错误" in error_msg```#### TypeScript 实现```typescript// data/test-data.tsexport interface LoginTestCase { username: string; password: string; expected: 'success' | 'error'; description: string;}export const loginTestData: LoginTestCase[] = [ { username: 'admin', password: 'password123', expected: 'success', description: '正确用户名密码' }, { username: 'wrong', password: 'wrong', expected: 'error', description: '错误用户名密码' }, { username: '', password: '', expected: 'error', description: '空用户名密码' }];// tests/data-driven-login.spec.tsimport { test, expect } from '@playwright/test';import { LoginPage } from '../page-objects/login-page';import { loginTestData } from '../data/test-data';test.describe('Data Driven Login Tests', () => { for (const data of loginTestData) { test(data.description, async ({ page }) => { const loginPage = new LoginPage(page); await page.goto('https://example.com/login'); await loginPage.login(data.username, data.password); if (data.expected === 'success') { await expect(page).toHaveTitle(/Dashboard/); } else { const errorMessage = await page.textContent('.error-message'); await expect(errorMessage).toContain('错误'); } }); }});```---## 环境搭建与第一个程序### 1. JavaScript 环境搭建#### 1.1 安装 Node.jsNode.js 是 JavaScript 的运行时环境,包含 npm(Node 包管理器)。**步骤:**1. **下载 Node.js** - 访问:https://nodejs.org/ - 选择 LTS 版本(长期支持版本,更稳定) - 根据系统下载对应安装包(Windows/Mac/Linux)2. **安装 Node.js** - Windows:运行安装程序,一路点击"下一步" - Mac:运行 .pkg 安装包 - Linux:使用包管理器安装3. **验证安装** ```bash # 检查 Node.js 版本 node -v # 检查 npm 版本 npm -v ``` 预期输出: ``` v20.10.0 10.2.3 ```#### 1.2 安装代码编辑器 VS CodeVS Code 是最流行的 JavaScript/TypeScript 开发工具。**步骤:**1. 下载 VS Code:https://code.visualstudio.com/2. 安装并启动3. 安装推荐插件: - ESLint(代码检查) - Prettier(代码格式化) - JavaScript (ES6) code snippets(代码片段) - Live Server(实时预览 HTML)#### 1.3 创建第一个 JavaScript 程序**方法一:浏览器中运行(最简单)**1. 创建 HTML 文件 `index.html`: ```html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>第一个 JavaScript 程序</title> </head> <body> <h1>我的第一个 JavaScript 程序</h1> <button id="myButton">点击我</button> <script> // JavaScript 代码 console.log('Hello, JavaScript!'); const button = document.getElementById('myButton'); button.addEventListener('click', function() { alert('你点击了按钮!'); console.log('按钮被点击了'); }); </script> </body> </html> ```2. 双击打开 HTML 文件,在浏览器中运行3. 按 F12 打开开发者工具,查看 Console 控制台**方法二:Node.js 命令行运行**1. 创建 JavaScript 文件 `hello.js`: ```javascript // hello.js console.log('Hello, JavaScript!'); // 变量 let name = '张三'; const age = 25; console.log('姓名:', name); console.log('年龄:', age); // 函数 function greet(name) { return `你好, ${name}!`; } console.log(greet('世界')); // 数组 const fruits = ['苹果', '香蕉', '橙子']; fruits.forEach(fruit => console.log(fruit)); // 对象 const person = { name: '李四', age: 30, sayHello: function() { console.log(`我是 ${this.name}`); } }; person.sayHello(); ```2. 在命令行中运行: ```bash node hello.js ```**方法三:使用 VS Code 运行**1. 在 VS Code 中打开 `hello.js`2. 安装插件 `Code Runner`3. 点击右上角的运行按钮,或按 `Ctrl + Alt + N`#### 1.4 使用 npm 管理项目**初始化项目:**```bash# 创建项目目录mkdir my-js-projectcd my-js-project# 初始化项目(会生成 package.json)npm init -y# 安装依赖npm install axios# 运行脚本npm start```**创建项目结构:**```my-js-project/├── node_modules/ # 依赖包├── src/│ ├── index.js # 主文件│ └── utils.js # 工具函数├── package.json # 项目配置└── README.md```**package.json 示例:**```json{ "name": "my-js-project", "version": "1.0.0", "description": "我的第一个 JavaScript 项目", "main": "src/index.js", "scripts": { "start": "node src/index.js", "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { "axios": "^1.6.0" }}```---### 2. TypeScript 环境搭建#### 2.1 全局安装 TypeScript**步骤:**1. 确保 Node.js 已安装2. 全局安装 TypeScript 编译器: ```bash npm install -g typescript ```3. 验证安装: ```bash tsc -v ``` 预期输出: ``` Version 5.3.0 ```#### 2.2 安装 VS Code TypeScript 插件VS Code 原生支持 TypeScript,但可以安装额外插件增强体验:- TypeScript Vue Plugin (如果使用 Vue)- TSLint(已被 ESLint 替代,不推荐)- Prettier - Code formatter#### 2.3 创建第一个 TypeScript 程序**方法一:直接编译运行**1. 创建 TypeScript 文件 `hello.ts`: ```typescript // hello.ts // 基本类型 let name: string = '张三'; const age: number = 25; const isStudent: boolean = true; console.log('姓名:', name); console.log('年龄:', age); console.log('是否为学生:', isStudent); // 函数 function greet(name: string): string { return `你好, ${name}!`; } console.log(greet('世界')); // 数组 const fruits: string[] = ['苹果', '香蕉', '橙子']; fruits.forEach(fruit => console.log(fruit)); // 对象 interface Person { name: string; age: number; sayHello(): void; } const person: Person = { name: '李四', age: 30, sayHello: function() { console.log(`我是 ${this.name}`); } }; person.sayHello(); // 箭头函数 const add = (a: number, b: number): number => { return a + b; }; console.log('1 + 2 =', add(1, 2)); // 异步函数 async function fetchData(): Promise<void> { console.log('开始获取数据...'); // 模拟异步操作 await new Promise(resolve => setTimeout(resolve, 1000)); console.log('数据获取完成!'); } fetchData(); ```2. 编译 TypeScript 文件: ```bash tsc hello.ts ``` 这会生成 `hello.js` 文件3. 运行编译后的文件: ```bash node hello.js ```**方法二:使用 ts-node 直接运行**1. 安装 ts-node(推荐): ```bash npm install -g ts-node ```2. 直接运行 TypeScript 文件: ```bash ts-node hello.ts ```**方法三:配置自动编译**1. 创建项目目录并初始化: ```bash mkdir my-ts-project cd my-ts-project npm init -y ```2. 安装 TypeScript 和类型定义: ```bash npm install --save-dev typescript @types/node ```3. 创建 `tsconfig.json`: ```json { "compilerOptions": { "target": "ES2020", "module": "commonjs", "lib": ["ES2020"], "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules"] } ```4. 创建项目结构: ``` my-ts-project/ ├── src/ │ ├── index.ts │ └── utils.ts ├── dist/ # 编译输出目录 ├── package.json └── tsconfig.json ```5. 在 `package.json` 中添加脚本: ```json { "scripts": { "build": "tsc", "start": "node dist/index.js", "dev": "ts-node src/index.ts" } } ```6. 运行脚本: ```bash npm run build # 编译 npm start # 运行编译后的代码 npm run dev # 直接运行 TypeScript ```#### 2.4 TypeScript 类型示例```typescript// types-demo.ts// 1. 基本类型let num: number = 42;let str: string = 'Hello';let bool: boolean = true;let nothing: null = null;let notDefined: undefined = undefined;// 2. 数组类型let numbers: number[] = [1, 2, 3];let strings: Array<string> = ['a', 'b', 'c'];let anyArray: any[] = [1, 'hello', true];// 3. 元组类型(固定长度和类型)let person: [string, number] = ['Tom', 18];// 4. 枚举类型enum Color { Red, Green, Blue}let myColor: Color = Color.Red;// 5. 接口interface User { id: number; name: string; age?: number; // 可选属性 readonly email: string; // 只读属性}const user: User = { id: 1, name: 'Tom', email: 'tom@example.com'};// 6. 类class Animal { protected name: string; constructor(name: string) { this.name = name; } speak(): void { console.log(`${this.name} makes a sound`); }}class Dog extends Animal { private breed: string; constructor(name: string, breed: string) { super(name); this.breed = breed; } bark(): void { console.log(`${this.name} barks!`); }}const dog = new Dog('Buddy', 'Golden Retriever');dog.bark();// 7. 泛型function identity<T>(arg: T): T { return arg;}identity<string>('hello');identity<number>(123);// 8. 联合类型let value: string | number = 'hello';value = 123; // OK// 9. 类型断言let someValue: any = 'this is a string';let strLength: number = (someValue as string).length;// 10. 类型守卫function isString(value: any): value is string { return typeof value === 'string';}if (isString(value)) { console.log(value.toUpperCase()); // TypeScript 知道这里是 string}```---### 3. 前端自动化测试环境搭建#### 3.1 Playwright 环境搭建(TypeScript)**步骤:**1. 创建项目: ```bash mkdir playwright-test cd playwright-test npm init -y ```2. 安装 Playwright: ```bash npm init playwright@latest ``` 这会自动: - 安装 Playwright - 生成配置文件 - 创建示例测试 - 安装浏览器3. 项目结构: ``` playwright-test/ ├── tests/ │ ├── example.spec.ts │ └── login.spec.ts ├── tests-examples/ ├── playwright.config.ts ├── package.json └── package-lock.json ```4. 编写第一个测试: ```typescript // tests/hello.spec.ts import { test, expect } from '@playwright/test'; test('基本页面测试', async ({ page }) => { // 访问页面 await page.goto('https://example.com'); // 验证标题 await expect(page).toHaveTitle(/Example Domain/); // 获取元素文本 const h1 = await page.textContent('h1'); console.log('页面标题:', h1); }); ```5. 运行测试: ```bash # 运行所有测试 npx playwright test # 运行特定测试 npx playwright test hello.spec.ts # 显示浏览器 npx playwright test --ui # 调试模式 npx playwright test --debug ```#### 3.2 Playwright 配置**playwright.config.ts:**```typescriptimport { defineConfig, devices } from '@playwright/test';export default defineConfig({ testDir: './tests', fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: [ ['html'], // 生成 HTML 报告 ['json', { outputFile: 'test-results/results.json' }] ], use: { baseURL: 'https://example.com', trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, { name: 'firefox', use: { ...devices['Desktop Firefox'] }, }, { name: 'webkit', use: { ...devices['Desktop Safari'] }, }, ],});```#### 3.3 Playwright 常用操作```typescript// tests/playwright-demo.spec.tsimport { test, expect } from '@playwright/test';test.describe('Playwright 操作示例', () => { test('基本操作', async ({ page }) => { // 导航 await page.goto('https://example.com'); // 等待元素 await page.waitForSelector('h1'); // 输入文本 await page.fill('#input-id', 'Hello World'); // 点击元素 await page.click('#button-id'); // 获取文本 const text = await page.textContent('.element'); console.log(text); // 获取属性 const href = await page.getAttribute('a', 'href'); console.log(href); // 截图 await page.screenshot({ path: 'screenshot.png' }); // 验证 await expect(page.locator('h1')).toBeVisible(); await expect(page).toHaveURL(/example/); }); test('表单操作', async ({ page }) => { await page.goto('https://example.com/form'); // 填写表单 await page.fill('#name', '张三'); await page.fill('#email', 'zhangsan@example.com'); await page.selectOption('#country', 'CN'); // 复选框 await page.check('#agree'); // 单选框 await page.check('#gender-male'); // 下拉框 await page.selectOption('#hobby', ['reading', 'sports']); // 提交 await page.click('#submit'); // 等待导航 await page.waitForURL('**/success'); }); test('异步操作', async ({ page }) => { // 等待 API 请求 await page.goto('https://example.com'); const [response] = await Promise.all([ page.waitForResponse('**/api/data'), page.click('#load-data') ]); const data = await response.json(); console.log('API 响应:', data); }); test('处理弹窗', async ({ page }) => { // 监听弹窗 page.on('dialog', async dialog => { console.log('弹窗消息:', dialog.message()); await dialog.accept(); // 或 dialog.dismiss() }); await page.goto('https://example.com'); await page.click('#show-alert'); });});```---### 4. 调试技巧#### 4.1 浏览器调试1. **打开开发者工具** - Chrome/Edge:`F12` 或 `Ctrl + Shift + I` - Firefox:`F12` 或 `Ctrl + Shift + I`2. **Console 控制台** ```javascript // 输出调试信息 console.log('变量值:', variable); console.warn('警告信息'); console.error('错误信息'); console.table([{name: 'Tom', age: 18}, {name: 'Jerry', age: 20}]); // 断点 debugger; // 程序会在这里暂停 ```3. **Sources 面板** - 设置断点:点击行号 - 单步执行:F10(单步跳过),F11(单步进入) - 查看变量:Watch 面板#### 4.2 VS Code 调试1. **配置 launch.json** ```json { "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "启动程序", "skipFiles": ["<node_internals>/**"], "program": "${workspaceFolder}/src/index.js" }, { "type": "node", "request": "launch", "name": "运行 TypeScript", "runtimeArgs": ["-r", "ts-node/register"], "args": ["${workspaceFolder}/src/index.ts"] } ] } ```2. **设置断点** - 点击行号设置断点 - 按 `F5` 启动调试#### 4.3 Playwright 调试```bash# UI 模式(推荐)npx playwright test --ui# 调试模式npx playwright test --debug# 显示浏览器npx playwright test --headed```**代码中添加断点:**```typescripttest('调试示例', async ({ page }) => { await page.goto('https://example.com'); // 暂停执行 await page.pause(); // 设置断点 await page.pause();});```---### 5. 常见问题#### 5.1 Node.js 安装问题**问题:** `node` 命令找不到**解决:**1. 检查环境变量 PATH 是否包含 Node.js 安装路径2. Windows:重新打开命令行窗口3. Mac/Linux:运行 `source ~/.bash_profile` 或 `source ~/.zshrc`#### 5.2 npm 安装包慢**解决:使用国内镜像**```bash# 淘宝镜像(推荐)npm config set registry https://registry.npmmirror.com# 恢复官方源npm config set registry https://registry.npmjs.org```#### 5.3 TypeScript 编译错误**问题:** 找不到类型定义**解决:**```bash# 安装 @types 包npm install --save-dev @types/node @types/jest @types/react```#### 5.4 Playwright 浏览器下载失败**解决:**```bash# 手动下载浏览器npx playwright install chromiumnpx playwright install firefoxnpx playwright install webkit# 或者设置代理PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright/ npx playwright install```---### 6. 推荐开发工具| 工具 | 用途 | 下载地址 ||------|------|---------|| **VS Code** | 代码编辑器 | https://code.visualstudio.com/ || **Chrome DevTools** | 浏览器调试 | 内置 || **Postman** | API 测试 | https://www.postman.com/ || **Git** | 版本控制 | https://git-scm.com/ |**VS Code 推荐插件:**| 插件名称 | 用途 ||---------|------|| ESLint | JavaScript/TypeScript 代码检查 || Prettier | 代码格式化 || Auto Rename Tag | 自动重命名 HTML 标签 || Path Intellisense | 路径智能提示 || GitLens | Git 增强 || Live Server | HTML 实时预览 || Thunder Client | API 测试(轻量级) |---## 学习建议### 1. 如果你已经掌握 Python**建议学习路径:**```第1周:JavaScript 基础├── 变量和数据类型├── 函数和作用域├── 对象和数组└── 异步编程基础第2周:TypeScript 基础├── 类型系统├── 接口和类├── 泛型└── 类型实用工具第3周:Playwright 实战├── Playwright 基础├── 页面对象模式└── 数据驱动测试第4周:进阶├── CI/CD 集成├── 测试报告└── 性能测试```**学习重点:**1. **理解 JavaScript 运行机制** - 事件循环 - 异步编程 - Promise 和 async/await2. **掌握 TypeScript 类型系统** - 基本类型和类型推断 - 接口定义和使用 - 泛型应用3. **实践自动化测试** - Playwright 框架 - 页面对象模式 - 测试最佳实践### 2. 何时选择 JavaScript/TypeScript**推荐使用的场景:**| 场景 | 推荐语言 | 理由 ||------|---------|------|| **纯前端自动化测试** | JavaScript/TypeScript | 原生支持,调试方便 || **与前端团队协作** | JavaScript/TypeScript | 技术栈一致,沟通顺畅 || **需要调试前端代码** | JavaScript/TypeScript | 浏览器 DevTools 直接调试 || **SPA 应用测试** | JavaScript/TypeScript | 更好的异步处理 || **已有 JavaScript 基础** | JavaScript/TypeScript | 学习成本较低 || 场景 | 推荐语言 | 理由 ||------|---------|------|| **已有 Python 基础** | Python | 学习成本低 || **团队使用 Python** | Python | 保持技术栈统一 || **需要处理大量数据** | Python | Pandas, NumPy 强大 || **后端是 Python** | Python | 技术栈一致 || **需要快速开发** | Python | 开发效率高 |### 3. 技能互补**最佳实践:两种语言都掌握**```Python JavaScript/TypeScript ↓ ↓数据处理 前端交互后端开发 浏览器操作测试框架 前端调试脚本工具 现代前端技术 ↓ ↓ 全栈自动化工程师```---## 总结### 核心要点1. **JavaScript/TypeScript 的独特价值** - 唯一的浏览器原生编程语言 - 直接操作 DOM 和浏览器 API - TypeScript 提供类型安全和更好的开发体验2. **Python 的独特价值** - 简洁易学的语法 - 丰富的数据处理和科学计算库 - 成熟的测试和自动化框架3. **选择建议** - 当前项目使用 Python 是合理选择 - 学习 JavaScript/TypeScript 可拓展技能栈 - 根据团队和项目需求选择### 学习资源**JavaScript/TypeScript:**- [MDN JavaScript 文档](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript)- [TypeScript 官方文档](https://www.typescriptlang.org/zh/docs/)- [JavaScript.info](https://javascript.info/)- [Playwright 官方文档](https://playwright.dev/)**Python:**- [Python 官方文档](https://docs.python.org/zh-cn/3/)- [Selenium 官方文档](https://www.selenium.dev/documentation/)- [Playwright Python 文档](https://playwright.dev/python/)
# JavaScript 和 TypeScript 基础教程## 前端自动化开发入门指南---## 目录1. [JavaScript 基础](#javascript-基础)2. [TypeScript 基础](#typescript-基础)3. [前端自动化开发实战](#前端自动化开发实战)4. [常用工具和框架](#常用工具和框架)5. [学习路线建议](#学习路线建议)---## JavaScript 基础### 1. JavaScript 简介JavaScript 是一种轻量级的编程语言,最初用于网页开发,现在广泛应用于:- 网页前端开发- 后端开发 (Node.js)- 移动应用开发- 自动化测试### 2. 基本语法#### 2.1 变量声明```javascript// ES6+ 推荐方式let name = "张三"; // 可变变量const PI = 3.14159; // 常量// 旧方式(不推荐)var age = 25; // 函数作用域,容易出错```**重要说明:**- `let`: 块级作用域,值可变- `const`: 块级作用域,值不可变- `var`: 函数作用域,容易产生变量提升问题#### 2.2 数据类型```javascript// 原始类型let str = "Hello"; // 字符串let num = 42; // 数字let bool = true; // 布尔值let nothing = null; // nulllet notDefined = undefined; // undefined// 引用类型let arr = [1, 2, 3]; // 数组let obj = { name: "Tom", age: 18 }; // 对象let func = function() { return 1; }; // 函数// ES6 新增let sym = Symbol("id"); // Symbollet bigNum = 9007199254740991n; // BigInt```#### 2.3 运算符```javascript// 算术运算符let a = 10, b = 3;console.log(a + b); // 13 加法console.log(a - b); // 7 减法console.log(a * b); // 30 乘法console.log(a / b); // 3.33 除法console.log(a % b); // 1 取余console.log(a ** b); // 1000 幂运算// 比较运算符console.log(5 == "5"); // true 值相等(类型转换)console.log(5 === "5"); // false 值和类型都相等console.log(5 != "5"); // false 值不相等console.log(5 !== "5"); // true 值或类型不相等// 逻辑运算符console.log(true && false); // false 与console.log(true || false); // true 或console.log(!true); // false 非```### 3. 控制流#### 3.1 条件语句```javascriptlet score = 85;// if-elseif(score >= 90) { console.log("优秀");} else if (score >= 60) { console.log("及格");} else { console.log("不及格");}// 三元运算符(简写)let result = score >= 60 ? "及格" : "不及格";// switchlet day = 3;switch(day) { case 1: console.log("星期一"); break; case 2: console.log("星期二"); break; case 3: console.log("星期三"); break; default: console.log("其他");}```#### 3.2 循环语句```javascript// for 循环for(let i = 0; i < 5; i++) { console.log(i);}// for...of(遍历数组)let fruits = ["苹果", "香蕉", "橙子"];for(let fruit of fruits) { console.log(fruit);}// for...in(遍历对象)let person = { name: "Tom", age: 18 };for(let key in person) { console.log(key + ": " + person[key]);}// while 循环let i = 0;while(i < 5) { console.log(i); i++;}// do-while 循环(至少执行一次)let j = 0;do { console.log(j); j++;} while(j < 5);```### 4. 函数#### 4.1 函数声明```javascript// 函数声明functiongreet(name) { return "Hello, " + name;}// 函数表达式const greet2 = function(name) { return "Hello, " + name;};// 箭头函数(ES6)const greet3 = (name) => "Hello, " + name;// 单参数可省略括号const greet4 = name => "Hello, " + name;// 调用console.log(greet("World")); // Hello, Worldconsole.log(greet3("JavaScript")); // Hello, JavaScript```#### 4.2 默认参数```javascriptfunctionsayHello(name = "Guest") { console.log("Hello, " + name);}sayHello(); // Hello, GuestsayHello("Tom"); // Hello, Tom```#### 4.3 解构参数```javascriptfunctioncreateUser({ name, age, isAdmin = false }) { return { name, age, isAdmin };}const user = createUser({ name: "Tom", age: 18 });console.log(user); // { name: 'Tom', age: 18, isAdmin: false }```### 5. 数组操作```javascriptlet arr = [1, 2, 3, 4, 5];// 添加元素arr.push(6); // [1, 2, 3, 4, 5, 6] 末尾添加arr.unshift(0); // [0, 1, 2, 3, 4, 5, 6] 开头添加// 删除元素arr.pop(); // [0, 1, 2, 3, 4, 5] 删除末尾arr.shift(); // [1, 2, 3, 4, 5] 删除开头// 查找元素console.log(arr.indexOf(3)); // 2 返回索引console.log(arr.includes(4)); // true 是否包含// 遍历arr.forEach((item, index) => { console.log(index + ": " + item);});// 映射(返回新数组)let doubled = arr.map(x => x * 2); // [2, 4, 6, 8, 10]// 过滤(返回新数组)let even = arr.filter(x => x % 2 === 0); // [2, 4]// 归约(累加)let sum = arr.reduce((acc, cur) => acc + cur, 0); // 15```### 6. 对象操作```javascript// 创建对象let person = { name: "Tom", age: 18, greet() { return `Hello, I'm ${this.name}`; }};// 访问属性console.log(person.name); // Tomconsole.log(person["age"]); // 18// 添加/修改属性person.email = "tom@example.com";person.age = 19;// 删除属性delete person.email;// 解构赋值let { name, age } = person;console.log(name, age); // Tom 19// 对象合并(ES6)let person2 = { ...person, city: "Beijing" };// 合并对象let merged = Object.assign({}, person, { score: 90 });```### 7. 字符串操作```javascriptlet str = "Hello World";// 字符串模板(ES6)let name = "Tom";let message = `Hello, ${name}!`;console.log(message); // Hello, Tom!// 常用方法console.log(str.length); // 11console.log(str.toUpperCase()); // HELLO WORLDconsole.log(str.toLowerCase()); // hello worldconsole.log(str.indexOf("World")); // 6console.log(str.includes("lo")); // trueconsole.log(str.slice(0, 5)); // Helloconsole.log(str.split(" ")); // ["Hello", "World"]// 字符串拼接let s1 = "Hello";let s2 = "World";console.log(s1 + " " + s2); // Hello Worldconsole.log(`${s1} ${s2}`); // Hello World```### 8. 异步编程#### 8.1 Promise```javascript// 创建 Promiselet promise = new Promise((resolve, reject) => { setTimeout(() => { resolve("操作成功"); // reject("操作失败"); }, 1000);});// 使用 Promisepromise .then(result => console.log(result)) .catch(error => console.error(error)) .finally(() => console.log("完成"));// Promise 链式调用promise .then(result => result + "!") .then(result => result.toUpperCase()) .then(result => console.log(result)) // 操作成功! .catch(error => console.error(error));```#### 8.2 async/await```javascript// async 函数async function fetchData() { try { let result = await promise; console.log(result); } catch (error) { console.error(error); }}// 多个并发请求async function fetchAll() { let [result1, result2] = await Promise.all([ fetch("https://api1.com"), fetch("https://api2.com") ]); return [result1, result2];}```### 9. DOM 操作(网页自动化核心)```javascript// 等待页面加载完成document.addEventListener('DOMContentLoaded', function() { // 获取元素 let element = document.getElementById("myId"); let elements = document.getElementsByClassName("myClass"); let allDivs = document.querySelectorAll("div"); // 修改元素 element.textContent = "新文本"; element.innerHTML = "<span>HTML内容</span>"; element.style.color = "red"; element.classList.add("active"); // 创建元素 let newDiv = document.createElement("div"); newDiv.textContent = "新元素"; document.body.appendChild(newDiv); // 事件监听 element.addEventListener("click", function(event) { console.log("点击了"); event.preventDefault(); }); // 表单操作 let input = document.getElementById("myInput"); input.value = "默认值"; console.log(input.value);});// 等待元素出现function waitForElement(selector, callback) { let element = document.querySelector(selector); if (element) { callback(element); } else { setTimeout(() => waitForElement(selector, callback), 100); }}// 使用示例waitForElement("#myButton", function(element) { element.click();});```---## TypeScript 基础### 1. TypeScript 简介TypeScript 是 JavaScript 的超集,添加了:- 静态类型检查- 接口- 类- 泛型- 更好的 IDE 支持TypeScript 代码会被编译为 JavaScript 代码运行。### 2. 基本类型```typescript// 基本类型注解let name: string = "张三";let age: number = 25;let isStudent: boolean = true;let nothing: null = null;let notDefined: undefined = undefined;// 数组let numbers: number[] = [1, 2, 3];let strings: Array<string> = ["a", "b", "c"];// 元组(固定长度、固定类型)let person: [string, number] = ["Tom", 18];// 枚举enum Color { Red, Green, Blue}let myColor: Color = Color.Red;// any 类型(不推荐)let anything: any = "可以是任何类型";// void 类型(无返回值)function log(message: string): void { console.log(message);}// never 类型(永不返回)function throwError(message: string): never { throw new Error(message);}```### 3. 接口(Interface)```typescript// 定义对象形状interface User { id: number; name: string; age?: number; // 可选属性 readonly email: string; // 只读属性}let user: User = { id: 1, name: "Tom", email: "tom@example.com"};user.name = "Jerry"; // OK// user.email = "new@email.com"; // Error: 只读```#### 3.1 函数类型```typescriptinterface SearchFunc { (source: string, subString: string): boolean;}let mySearch: SearchFunc = function(source, subString) { return source.indexOf(subString) > -1;};```### 4. 类(Class)```typescript// 定义类class Person { // 属性 private name: string; protected age: number; public email: string; // 构造函数 constructor(name: string, age: number, email: string) { this.name = name; this.age = age; this.email = email; } // 方法 greet(): string { return `Hello, I'm ${this.name}`; } // getter/setter get getName(): string { return this.name; } set setName(newName: string) { this.name = newName; }}// 继承classStudentextendsPerson{ private studentId: number; constructor(name: string, age: number, email: string, studentId: number) { super(name, age, email); this.studentId = studentId; } study(): string { return `${this.getName} is studying`; }}// 使用let student = new Student("Tom", 18, "tom@example.com", 1001);console.log(student.greet());console.log(student.study());```### 5. 泛型```typescript// 泛型函数functionidentity<T>(arg: T): T{ return arg;}let output1 = identity<string>("Hello");let output2 = identity(123); // 类型推断// 泛型接口interfaceBox<T> { value: T;}let numberBox: Box<number> = { value: 100 };let stringBox: Box<string> = { value: "Hello" };// 泛型类classStorage<T> { private items: T[] = []; add(item: T): void { this.items.push(item); } getAll(): T[] { return this.items; }}let numberStorage = new Storage<number>();numberStorage.add(1);numberStorage.add(2);```### 6. 类型推断```typescript// 自动推断let x = 3; // numberlet y = "hello"; // string// 函数返回值推断functionadd(a: number, b: number) { return a + b; // 返回类型自动推断为 number}// 上下文推断window.onclick = function(mouseEvent) { console.log(mouseEvent.clientX); // mouseEvent 类型为 MouseEvent};```### 7. 类型断言```typescript// 类型断言(告诉编译器相信我)let value: any = "Hello World";let length1: number = (value as string).length;let length2: number = (<string>value).length;```### 8. 联合类型和交叉类型```typescript// 联合类型(可以是多种类型之一)let value: string | number;value = "Hello";value = 123;// 交叉类型(同时具备多个类型)interfacePerson{ name: string;}interfaceEmployee{ id: number;}type PersonEmployee = Person & Employee;let pe: PersonEmployee = { name: "Tom", id: 100};```### 9. 字面量类型```typescript// 字符串字面量type Direction = "up" | "down" | "left" | "right";let move: Direction = "up";// move = "diagonal"; // Error// 数字字面量type Age = 18 | 19 | 20;let myAge: Age = 18;// 布尔字面量type Config = { debug: true;};```### 10. 实用类型```typescript// Partial - 所有属性可选interfaceUser{ id: number; name: string; email: string;}type PartialUser = Partial<User>;// Required - 所有属性必填type RequiredUser = Required<PartialUser>;// Readonly - 所有属性只读type ReadonlyUser = Readonly<User>;// Pick - 选择部分属性type UserBasic = Pick<User, "id" | "name">;// Omit - 排除部分属性type UserWithoutEmail = Omit<User, "email">;// Record - 构建对象类型type Pages = Record<string, { title: string; content: string }>;// 使用示例functionupdateUser(user: Partial<User>): void{ // 只需要提供要更新的字段}```---## 前端自动化开发实战### 1. Puppeteer 自动化测试#### 1.1 基础操作```javascriptconst puppeteer = require('puppeteer');(async () => { // 启动浏览器 const browser = await puppeteer.launch({ headless: false // 显示浏览器窗口 }); // 创建新页面 const page = await browser.newPage(); // 访问页面 await page.goto('https://example.com'); // 截图 await page.screenshot({ path: 'screenshot.png' }); // 关闭浏览器 await browser.close();})();```#### 1.2 元素操作```javascriptconst puppeteer = require('puppeteer');(async () => { const browser = await puppeteer.launch({ headless: false }); const page = await browser.newPage(); await page.goto('https://example.com'); // 输入文本 await page.type('#input-id', 'Hello World'); // 点击按钮 await page.click('#button-id'); // 等待元素出现 await page.waitForSelector('#result'); // 获取元素文本 const text = await page.$eval('#result', el => el.textContent); console.log(text); // 获取多个元素 const items = await page.$$eval('.item', els => els.map(el => el.textContent) ); console.log(items); await browser.close();})();```#### 1.3 等待策略```javascript// 等待选择器出现await page.waitForSelector('.loading', { timeout: 5000 });// 等待函数返回 trueawait page.waitForFunction(() => { return document.querySelector('.result') !== null;});// 等待导航完成await page.waitForNavigation();// 等待固定时间await page.waitForTimeout(1000); // 等待 1 秒```### 2. Playwright 自动化测试#### 2.1 基础操作```javascriptconst { chromium } = require('playwright');(async () => { // 启动浏览器 const browser = await chromium.launch({ headless: false }); // 创建上下文和页面 const context = await browser.newContext(); const page = await context.newPage(); // 访问页面 await page.goto('https://example.com'); // 截图 await page.screenshot({ path: 'screenshot.png' }); await browser.close();})();```#### 2.2 元素操作```javascriptconst { chromium } = require('playwright');(async () => { const browser = await chromium.launch({ headless: false }); const page = await browser.newPage(); await page.goto('https://example.com'); // 输入文本 await page.fill('#input-id', 'Hello World'); // 点击按钮 await page.click('#button-id'); // 等待元素 await page.waitForSelector('#result'); // 获取文本 const text = await page.textContent('#result'); console.log(text); // 获取属性 const href = await page.getAttribute('a', 'href'); await browser.close();})();```### 3. Cypress 自动化测试#### 3.1 基础测试```javascriptdescribe('登录测试', () => { it('应该成功登录', () => { // 访问页面 cy.visit('/login'); // 输入用户名 cy.get('#username').type('admin'); // 输入密码 cy.get('#password').type('password123'); // 点击登录按钮 cy.get('#login-btn').click(); // 验证跳转到首页 cy.url().should('include', '/dashboard'); // 验证欢迎消息 cy.contains('欢迎').should('be.visible'); });});```#### 3.2 常用命令```javascriptdescribe('常用命令示例', () => { it('各种操作示例', () => { // 等待 cy.wait(1000); // 断言 cy.get('h1').should('have.text', '标题'); cy.get('button').should('be.visible'); cy.get('input').should('have.value', 'test'); // 断言包含 cy.get('.message').should('contain', '成功'); // 断言数量 cy.get('.item').should('have.length', 3); // 自定义断言 cy.get('.score').invoke('text').then(parseFloat).should('be.gte', 60); });});```### 4. TypeScript 自动化项目结构```typescript// page-objects/base-page.tsimport { Page, Locator } from '@playwright/test';export classBasePage{ constructor(protected page: Page) {} async navigate(url: string): Promise<void> { await this.page.goto(url); } async waitForElement(selector: string, timeout: number = 5000): Promise<Locator> { return this.page.waitForSelector(selector, { timeout }); } async click(selector: string): Promise<void> { await this.page.click(selector); } async fill(selector: string, value: string): Promise<void> { await this.page.fill(selector, value); } async getText(selector: string): Promise<string> { return await this.page.textContent(selector) || ''; }}// page-objects/login-page.tsimport { BasePage } from './base-page';export classLoginPageextendsBasePage{ private readonly usernameInput = '#username'; private readonly passwordInput = '#password'; private readonly loginButton = '#login-button'; async login(username: string, password: string): Promise<void> { await this.fill(this.usernameInput, username); await this.fill(this.passwordInput, password); await this.click(this.loginButton); }}// tests/login.spec.tsimport { test, expect } from '@playwright/test';import { LoginPage } from '../page-objects/login-page';test('登录测试', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.navigate('https://example.com/login'); await loginPage.login('admin', 'password'); await expect(page).toHaveURL(/.*dashboard/);});```---## 常用工具和框架### 1. 自动化测试框架对比| 框架 | 优点 | 缺点 | 适用场景 ||------|------|------|----------|| Puppeteer | 轻量、快速、Google官方 | API相对简单 | 快速测试、截图、爬虫 || Playwright | 跨浏览器、功能强大、现代API | 体积较大 | 全面自动化测试 || Cypress | 开发体验好、实时预览、调试方便 | 只支持Chromium | 前端团队自动化测试 || Selenium | 支持所有语言、生态丰富 | 配置复杂、速度慢 | 跨语言团队 |### 2. 推荐学习路径**初学者路径:**1. JavaScript 基础(1-2周)2. Puppeteer 基础(1周)3. 简单自动化脚本(1周)**进阶路径:**1. TypeScript 基础(1-2周)2. Playwright 进阶(1-2周)3. 页面对象模式(1周)4. 测试框架整合(1周)---## 学习路线建议### 第一周:JavaScript 基础**目标:掌握 JavaScript 基本语法**- [ ] 变量和数据类型- [ ] 函数定义和调用- [ ] 数组和对象操作- [ ] 条件判断和循环- [ ] DOM 基础操作### 第二周:JavaScript 进阶**目标:理解异步编程**- [ ] Promise 基础- [ ] async/await- [ ] 事件处理- [ ] 实践:编写第一个自动化脚本### 第三周:TypeScript 基础**目标:掌握类型系统**- [ ] 基本类型注解- [ ] 接口定义- [ ] 类和继承- [ ] 泛型使用### 第四周:自动化框架入门**目标:开始实际项目**- [ ] 安装和配置 Playwright- [ ] 编写第一个测试用例- [ ] 页面对象模式实践- [ ] 数据驱动测试### 持续提升- [ ] 学习 CI/CD 集成- [ ] 学习性能测试- [ ] 学习 API 测试- [ ] 学习测试报告生成---## 实战练习项目### 项目 1:简单登录自动化```javascriptconst puppeteer = require('puppeteer');(async () => { const browser = await puppeteer.launch({ headless: false }); const page = await browser.newPage(); // 访问登录页面 await page.goto('https://example.com/login'); // 填写表单 await page.type('#username', 'testuser'); await page.type('#password', 'testpass'); // 点击登录 await page.click('#login-button'); // 等待跳转 await page.waitForNavigation(); // 验证登录成功 const welcomeText = await page.$eval('.welcome', el => el.textContent); console.log('登录成功:', welcomeText); await browser.close();})();```### 项目 2:数据抓取```javascriptconst puppeteer = require('puppeteer');const fs = require('fs');(async () => { const browser = await puppeteer.launch({ headless: false }); const page = await browser.newPage(); await page.goto('https://example.com/articles'); // 等待内容加载 await page.waitForSelector('.article'); // 抓取所有文章标题 const articles = await page.$$eval('.article', items => items.map(item => ({ title: item.querySelector('.title').textContent, author: item.querySelector('.author').textContent, date: item.querySelector('.date').textContent })) ); // 保存到文件 fs.writeFileSync('articles.json', JSON.stringify(articles, null, 2)); console.log(`抓取完成,共 ${articles.length} 篇文章`); await browser.close();})();```---## 资源推荐### 官方文档- [MDN JavaScript 文档](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript)- [TypeScript 官方文档](https://www.typescriptlang.org/zh/docs/)- [Playwright 官方文档](https://playwright.dev/zh-hans/)- [Puppeteer 官方文档](https://pptr.dev/)### 在线练习- [JavaScript.info](https://javascript.info/)- [TypeScript 深入浅出](https://jkchao.github.io/typescript-book-chinese/)- [Playwright by Example](https://playwright-by-example.vercel.app/)### 工具推荐- **IDE**: VS Code- **TypeScript 插件**: TypeScript Vue Plugin- **调试工具**: Chrome DevTools- **代码格式化**: Prettier- **代码检查**: ESLint---## 快速参考### JavaScript 常用命令速查```javascript// 变量声明let x = 1;const y = 2;// 数组arr.push(item); // 添加arr.pop(); // 删除末尾arr.filter(x => x > 0); // 过滤arr.map(x => x * 2); // 映射arr.reduce((a, b) => a + b, 0); // 累加// 对象Object.keys(obj); // 获取所有键Object.values(obj); // 获取所有值Object.entries(obj); // 获取所有键值对// 字符串str.includes(sub); // 是否包含str.split(','); // 分割str.trim(); // 去除首尾空格// 异步new Promise((resolve, reject) => {});async function fn() { await promise; }```### TypeScript 类型速查```typescript// 基本类型let num: number = 1;let str: string = "hello";let bool: boolean = true;// 数组let arr: number[] = [1, 2, 3];let arr2: Array<string> = ["a", "b"];// 接口interfaceUser{ id: number; name: string;}// 联合类型let value: string | number;// 泛型functionfn<T>(arg: T): T{ return arg; }```---
# Script 标签详解与前端开发实战## JavaScript/TypeScript 在前端开发中的全面指南---## 目录1. [Script 标签执行时机详解](#script-标签执行时机详解)2. [Script 标签的各种属性](#script-标签的各种属性)3. [前端开发中使用 JavaScript 的场景](#前端开发中使用-javascript-的场景)4. [前端开发中使用 TypeScript 的场景](#前端开发中使用-typescript-的场景)5. [实战示例:完整的页面开发](#实战示例完整的页面开发)6. [JavaScript vs TypeScript 选择指南](#javascript-vs-typescript-选择指南)7. [最佳实践](#最佳实践)---## Script 标签执行时机详解### 1. 浏览器渲染流程理解 script 执行时机,首先需要了解浏览器的渲染流程:```┌─────────────────────────────────────────────────────────┐│ 浏览器渲染流程 │└─────────────────────────────────────────────────────────┘1. 解析 HTML → 构建 DOM 树 ↓2. 解析 CSS → 构建 CSSOM 树 ↓3. 合并 DOM 和 CSSOM → 构建渲染树 ↓4. 布局(Layout)→ 计算元素位置和大小 ↓5. 绘制(Paint)→ 绘制像素到屏幕 ↓6. 合成(Composite)→ 合成各层显示```**关键点:** JavaScript 会阻塞 HTML 解析和页面渲染!---### 2. Script 标签的默认行为#### 2.1 没有任何属性的 script 标签```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>默认行为示例</title> <!-- Script 1:立即执行,阻塞 HTML 解析 --> <script> console.log('Script 1: 开始执行'); // 模拟耗时操作(1秒) const start = Date.now(); while (Date.now() - start < 1000) { // 等待 } console.log('Script 1: 执行完成'); // ❌ 此时 DOM 还没解析到 body,无法访问 const button = document.getElementById('myButton'); console.log('按钮元素:', button); // null </script></head><body> <!-- 注意:上面的 script 执行了 1 秒,页面会白屏 1 秒! --> <h1>我的网页</h1> <buttonid="myButton">点击我</button> <!-- Script 2:此时 DOM 已部分解析 --> <script> console.log('Script 2: body 中执行'); const button = document.getElementById('myButton'); console.log('按钮元素:', button); // ✅ 找到了 </script></body></html>```**执行顺序:**```时间轴:0ms 开始解析 HTML↓50ms 遇到 Script 1 → 暂停 HTML 解析↓50ms 执行 Script 1(耗时 1 秒) - 页面白屏,用户看不到任何内容↓1050ms Script 1 执行完成↓1050ms 继续解析 HTML↓1100ms 解析到 <h1>↓1150ms 解析到 <button>↓1200ms 遇到 Script 2 → 执行 Script 2↓1250ms 继续解析剩余 HTML↓1300ms HTML 解析完成```**问题:**- 脚本执行期间,HTML 解析暂停- 用户会看到白屏或部分内容- 影响页面加载速度和用户体验---### 3. 使用 defer 属性#### 3.1 defer 的执行时机```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>Defer 示例</title> <!-- defer:延迟到 DOM 解析完成后执行 --> <scriptdefersrc="script1.js"></script> <scriptdefersrc="script2.js"></script> <scriptdefer> console.log('内联 defer 脚本'); const button = document.getElementById('myButton'); console.log('按钮元素:', button); // ✅ 可以访问 </script></head><body> <h1>我的网页</h1> <buttonid="myButton">点击我</button></body></html>```**defer 执行特点:**- 脚本延迟到 DOM 解析完成后执行- 在 `DOMContentLoaded` 事件之前执行- 多个 defer 脚本按在 HTML 中的顺序执行- **不阻塞 HTML 解析****执行顺序:**```html<scriptdefersrc="script1.js"></script> <!-- 1️⃣ 按顺序执行 --><scriptdefersrc="script2.js"></script> <!-- 2️⃣ 按顺序执行 --><scriptdefersrc="script3.js"></script> <!-- 3️⃣ 按顺序执行 -->```**完整示例:**```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>Defer 完整示例</title> <script> // 立即执行 console.log('1. 立即执行 - head'); </script> <scriptdefer> // defer:DOM 解析后执行 console.log('2. defer - DOM 解析后'); // 可以安全访问 DOM document.addEventListener('DOMContentLoaded', function() { console.log('3. DOMContentLoaded 回调'); }); </script></head><body> <h1>我的网页</h1> <buttonid="myButton">点击我</button> <script> console.log('4. 立即执行 - body'); </script> <scriptdefer> console.log('5. defer - 最后执行'); </script></body></html>```**输出顺序:**```1. 立即执行 - head4. 立即执行 - body2. defer - DOM 解析后5. defer - 最后执行3. DOMContentLoaded 回调```---### 4. 使用 async 属性#### 4.1 async 的执行时机```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>Async 示例</title> <!-- async:异步加载,加载完成后立即执行 --> <scriptasyncsrc="analytics.js"></script> <scriptasyncsrc="ads.js"></script></head><body> <h1>我的网页</h1> <!-- 页面内容继续加载,不等待脚本 --></body></html>```**async 执行特点:**- 脚本异步加载,不阻塞 HTML 解析- 脚本加载完成后立即执行- 多个 async 脚本执行顺序**不确定**(取决于加载速度)- **适用于独立脚本**:分析工具、广告、社交分享按钮**示例:不按顺序执行**```html<!DOCTYPE html><htmllang="zh-CN"><head> <scriptasyncsrc="script1.js"></script> <!-- 可能在第 3 个执行 --> <scriptasyncsrc="script2.js"></script> <!-- 可能在第 1 个执行 --> <scriptasyncsrc="script3.js"></script> <!-- 可能在第 2 个执行 --></head><body> <h1>我的网页</h1></body></html>```**实际执行顺序取决于网络加载速度!**---### 5. 三种方式对比| 属性 | 执行时机 | 顺序 | 阻塞解析 | 适用场景 ||------|---------|------|----------|---------|| **无属性** | 遇到立即执行 | 按顺序 | ✅ 阻塞 | 需要立即执行且依赖后续代码 || **defer** | DOM 解析完成后 | 按顺序 | ❌ 不阻塞 | 需要操作 DOM,保持执行顺序 || **async** | 加载完成后立即 | 不确定 | ❌ 不阻塞 | 独立脚本(分析、广告) |---### 6. 实际测试示例#### 6.1 创建测试页面```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>Script 执行时机测试</title> <script> console.log('=== 测试开始 ==='); console.log('1. 立即执行 - head'); console.log(' - DOM 解析进度:', document.readyState); </script> <scriptdefer> console.log('2. defer - head'); </script> <scriptasync> console.log('3. async - head'); </script></head><body> <h1>Script 执行时机测试</h1> <buttonid="myButton">点击我</button> <script> console.log('4. 立即执行 - body'); console.log(' - DOM 解析进度:', document.readyState); </script> <scriptdefer> console.log('5. defer - body'); </script> <script> document.addEventListener('DOMContentLoaded', function() { console.log('6. DOMContentLoaded 事件'); console.log(' - DOM 解析进度:', document.readyState); // 获取元素 const button = document.getElementById('myButton'); console.log(' - 按钮元素:', button); }); </script> <script> window.addEventListener('load', function() { console.log('7. load 事件(所有资源加载完成)'); console.log(' - DOM 解析进度:', document.readyState); }); </script> <scriptdefer> console.log('8. defer - body 末尾'); </script></body></html>```**预期输出顺序:**```=== 测试开始 ===1. 立即执行 - head - DOM 解析进度: loading3. async - head (执行顺序不确定)4. 立即执行 - body - DOM 解析进度: interactive2. defer - head5. defer - body8. defer - body 末尾6. DOMContentLoaded 事件 - DOM 解析进度: interactive7. load 事件(所有资源加载完成) - DOM 解析进度: complete```---### 7. 事件监听器的执行时机#### 7.1 事件注册 vs 事件触发```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>事件监听器执行时机</title> <script> console.log('1. 脚本开始执行'); // 注册事件监听器(立即执行) console.log('2. 注册点击事件监听器'); const button = document.getElementById('myButton'); console.log('3. 按钮元素:', button); // null(按钮还不存在) // 如果按钮不存在,这段代码会报错! // button.addEventListener('click', ...); // ❌ TypeError </script></head><body> <h1>事件监听器示例</h1> <buttonid="myButton">点击我</button> <script> console.log('4. 脚本在 body 中执行'); const button = document.getElementById('myButton'); console.log('5. 按钮元素:', button); // ✅ 找到了 // 注册事件监听器(立即注册,但不执行回调) button.addEventListener('click', function() { console.log('7. 按钮被点击了!'); alert('你点击了按钮!'); }); console.log('6. 事件监听器注册完成,等待用户点击...'); </script></body></html>```**执行流程:**```页面加载时: ↓1. 脚本开始执行 ↓2. 注册点击事件监听器(尝试) ↓3. 按钮元素: null(❌ 按钮还不存在) ↓4. 脚本在 body 中执行 ↓5. 按钮元素: <button>(✅ 找到了) ↓6. 事件监听器注册完成,等待用户点击... ↓等待用户操作... ↓用户点击按钮时: ↓7. 按钮被点击了!(触发回调函数)```**关键理解:**- `addEventListener` 的注册过程是**立即执行**的- 但回调函数(`function() { ... }`)只有在**事件触发时**才执行#### 7.2 多种事件类型```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>多种事件类型</title> <style> #demo-box { width: 200px; height: 200px; background-color: lightblue; margin: 20px; display: flex; align-items: center; justify-content: center; } </style></head><body> <h1>事件类型示例</h1> <!-- 点击事件 --> <buttonid="click-btn">点击我</button> <!-- 鼠标悬停事件 --> <divid="hover-box"style="padding: 20px; background: #eee; margin: 10px;"> 鼠标移到这里 </div> <!-- 输入事件 --> <inputtype="text"id="name-input"placeholder="输入你的名字"> <!-- 表单提交事件 --> <formid="my-form"> <inputtype="text"id="username"placeholder="用户名"> <buttontype="submit">提交</button> </form> <!-- 滚动事件 --> <divid="scroll-box"style="height: 100px; overflow-y: auto; border: 1px solid #ccc;"> <divstyle="height: 300px; background: linear-gradient(to bottom, #eee, #ddd);"> 滚动这个区域 </div> </div> <script> // 1. 点击事件 document.getElementById('click-btn').addEventListener('click', function() { console.log('点击事件触发'); alert('按钮被点击了!'); }); // 2. 鼠标移入事件 document.getElementById('hover-box').addEventListener('mouseenter', function() { console.log('鼠标移入'); this.style.backgroundColor = 'lightgreen'; }); // 3. 鼠标移出事件 document.getElementById('hover-box').addEventListener('mouseleave', function() { console.log('鼠标移出'); this.style.backgroundColor = '#eee'; }); // 4. 输入事件(实时监听) document.getElementById('name-input').addEventListener('input', function(event) { console.log('输入值:', event.target.value); }); // 5. 表单提交事件 document.getElementById('my-form').addEventListener('submit', function(event) { event.preventDefault(); // 阻止表单默认提交 console.log('表单提交'); const username = document.getElementById('username').value; console.log('用户名:', username); alert(`表单提交!用户名: ${username}`); }); // 6. 滚动事件 document.getElementById('scroll-box').addEventListener('scroll', function() { console.log('滚动位置:', this.scrollTop); }); </script></body></html>```---## Script 标签的各种属性### 1. src 属性:引用外部脚本```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>外部脚本示例</title> <!-- 引用外部 JavaScript 文件 --> <scriptsrc="js/main.js"></script> <!-- 引用 CDN 上的库 --> <scriptsrc="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <!-- 带版本号的引用 --> <scriptsrc="js/utils.js?v=1.0.0"></script></head><body> <h1>外部脚本示例</h1></body></html>```**外部脚本示例:**```javascript// js/main.jsconsole.log('main.js 加载完成');function greet(name) { return `你好, ${name}!`;}// 暴露到全局window.greet = greet;``````html<!-- 使用外部脚本中定义的函数 --><script> const result = greet('世界'); console.log(result); // 你好, 世界!</script>```---### 2. type 属性:指定脚本类型```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>Type 属性示例</title> <!-- JavaScript(默认) --> <scripttype="text/javascript"> console.log('JavaScript 代码'); </script> <!-- JavaScript 模块 --> <scripttype="module"> import { sayHello } from './module.js'; console.log(sayHello('Module')); </script> <!-- 数据块(不执行) --> <scripttype="application/json"id="data"> { "name": "张三", "age": 25 } </script> <!-- 数据块 --> <scripttype="text/plain"id="template"> <div>这是一个模板</div> </script></head><body> <h1>Type 属性示例</h1> <script> // 读取 JSON 数据 const data = JSON.parse(document.getElementById('data').textContent); console.log('数据:', data); // 读取模板 const template = document.getElementById('template').textContent; console.log('模板:', template); </script></body></html>```---### 3. crossorigin 属性:跨域资源共享```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>Crossorigin 示例</title> <!-- 匿名跨域请求 --> <script src="https://cdn.example.com/library.js" crossorigin="anonymous"> </script> <!-- 使用凭证的跨域请求 --> <script src="https://api.example.com/protected.js" crossorigin="use-credentials"> </script></head><body> <h1>Crossorigin 示例</h1></body></html>```---### 4. integrity 属性:子资源完整性(SRI)```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>Integrity 属性示例</title> <!-- 使用 SRI 验证资源完整性 --> <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"> </script> <!-- 如果文件被篡改,浏览器将拒绝加载 --></head><body> <h1>Integrity 属性示例</h1></body></html>```---### 5. nomodule 属性:不支持模块的浏览器```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>Nomodule 示例</title> <!-- 现代浏览器:使用模块 --> <scripttype="module"src="js/app.js"></script> <!-- 旧浏览器:使用传统脚本 --> <scriptnomodulesrc="js/app-es5.js"></script></head><body> <h1>Nomodule 示例</h1></body></html>```---## 前端开发中使用 JavaScript 的场景### 1. DOM 操作#### 1.1 查找元素```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>DOM 查找元素</title></head><body> <divid="container"> <h1id="title">标题</h1> <pclass="paragraph">第一段</p> <pclass="paragraph">第二段</p> <buttonclass="btn">按钮1</button> <buttonclass="btn">按钮2</button> <ulid="list"> <liclass="item">项目1</li> <liclass="item">项目2</li> <liclass="item">项目3</li> </ul> </div> <script> // 1. 通过 ID 查找 const title = document.getElementById('title'); console.log('标题:', title.textContent); // 2. 通过类名查找 const paragraphs = document.getElementsByClassName('paragraph'); console.log('段落数量:', paragraphs.length); // 3. 通过标签名查找 const buttons = document.getElementsByTagName('button'); console.log('按钮数量:', buttons.length); // 4. 通过 CSS 选择器查找(单个) const firstItem = document.querySelector('.item'); console.log('第一个项目:', firstItem.textContent); // 5. 通过 CSS 选择器查找(多个) const items = document.querySelectorAll('.item'); items.forEach((item, index) => { console.log(`项目 ${index + 1}:`, item.textContent); }); // 6. 在指定范围内查找 const container = document.getElementById('container'); const btns = container.querySelectorAll('.btn'); console.log('容器内的按钮:', btns.length); // 7. 查找子元素 const list = document.getElementById('list'); const listItems = list.children; console.log('列表子元素数量:', listItems.length); // 8. 查找父元素 const item = document.querySelector('.item'); const parent = item.parentElement; console.log('父元素:', parent.id); // 9. 查找兄弟元素 const nextSibling = item.nextElementSibling; const prevSibling = item.previousElementSibling; console.log('下一个兄弟:', nextSibling.textContent); console.log('上一个兄弟:', prevSibling.textContent); </script></body></html>```#### 1.2 修改元素```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>修改 DOM 元素</title> <style> .highlight { background-color: yellow; } .hidden { display: none; } </style></head><body> <divid="content"> <h1id="title">原标题</h1> <pid="description">原描述</p> <buttonid="modify-btn">修改内容</button> <buttonid="style-btn">修改样式</button> <buttonid="class-btn">切换类名</button> <buttonid="attr-btn">修改属性</button> <buttonid="html-btn">修改 HTML</button> </div> <script> const title = document.getElementById('title'); const description = document.getElementById('description'); // 1. 修改文本内容 document.getElementById('modify-btn').addEventListener('click', function() { title.textContent = '修改后的标题'; description.textContent = '修改后的描述'; description.innerText = '使用 innerText(会解析换行)'; }); // 2. 修改样式 document.getElementById('style-btn').addEventListener('click', function() { title.style.color = 'red'; title.style.fontSize = '32px'; title.style.fontWeight = 'bold'; description.style.color = 'blue'; description.style.padding = '20px'; }); // 3. 添加/移除类名 document.getElementById('class-btn').addEventListener('click', function() { title.classList.add('highlight'); title.classList.toggle('highlight'); // 切换 title.classList.remove('highlight'); // 检查类名 console.log('是否有 highlight 类:', title.classList.contains('highlight')); // 替换类名 title.classList.replace('old-class', 'new-class'); }); // 4. 修改属性 document.getElementById('attr-btn').addEventListener('click', function() { title.id = 'new-title'; title.title = '这是一个提示'; title.dataset.custom = '自定义数据'; console.log('自定义数据:', title.dataset.custom); }); // 5. 修改 HTML 内容 document.getElementById('html-btn').addEventListener('click', function() { description.innerHTML = '<strong>加粗文字</strong> <em>斜体文字</em>'; description.innerHTML += '<br><span style="color: green;">新增内容</span>'; }); </script></body></html>```#### 1.3 创建和删除元素```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>创建和删除元素</title> <style> .card { border: 1px solid #ccc; padding: 15px; margin: 10px; background-color: #f9f9f9; } </style></head><body> <divid="container"> <h1>动态元素示例</h1> <buttonid="add-btn">添加卡片</button> <buttonid="add-template-btn">添加模板卡片</button> <buttonid="clear-btn">清空所有</button> <hr> <divid="cards-container"></div> </div> <!-- 模板 --> <templateid="card-template"> <divclass="card"> <h3>卡片标题</h3> <p>卡片内容</p> <buttonclass="delete-btn">删除</button> </div> </template> <script> const container = document.getElementById('cards-container'); let cardCount = 0; // 1. 创建元素并添加 document.getElementById('add-btn').addEventListener('click', function() { cardCount++; // 创建元素 const card = document.createElement('div'); card.className = 'card'; // 创建标题 const title = document.createElement('h3'); title.textContent = `卡片 ${cardCount}`; // 创建内容 const content = document.createElement('p'); content.textContent = `这是第 ${cardCount} 张卡片的内容`; // 创建删除按钮 const deleteBtn = document.createElement('button'); deleteBtn.textContent = '删除'; deleteBtn.className = 'delete-btn'; deleteBtn.addEventListener('click', function() { this.parentElement.remove(); }); // 组装元素 card.appendChild(title); card.appendChild(content); card.appendChild(deleteBtn); // 添加到容器 container.appendChild(card); }); // 2. 使用模板创建元素 document.getElementById('add-template-btn').addEventListener('click', function() { cardCount++; const template = document.getElementById('card-template'); const clone = template.content.cloneNode(true); // 修改克隆的元素 clone.querySelector('h3').textContent = `卡片 ${cardCount}`; clone.querySelector('p').textContent = `这是第 ${cardCount} 张卡片(来自模板)`; // 绑定删除事件 clone.querySelector('.delete-btn').addEventListener('click', function() { this.closest('.card').remove(); }); // 添加到容器 container.appendChild(clone); }); // 3. 清空所有元素 document.getElementById('clear-btn').addEventListener('click', function() { container.innerHTML = ''; // 方法1:设置 HTML 为空 // container.textContent = ''; // 方法2:设置文本为空 // while (container.firstChild) { // 方法3:逐个删除 // container.removeChild(container.firstChild); // } cardCount = 0; }); </script></body></html>```---### 2. 表单处理#### 2.1 表单验证```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>表单验证</title> <style> .form-group { margin: 15px 0; } label { display: block; margin-bottom: 5px; } input { padding: 8px; width: 300px; } .error { color: red; font-size: 14px; margin-top: 5px; } .success { color: green; font-size: 14px; margin-top: 5px; } .invalid { border-color: red; } .valid { border-color: green; } </style></head><body> <h1>表单验证示例</h1> <formid="registration-form"> <divclass="form-group"> <labelfor="username">用户名:</label> <inputtype="text"id="username"name="username"> <divclass="error"id="username-error"></div> </div> <divclass="form-group"> <labelfor="email">邮箱:</label> <inputtype="email"id="email"name="email"> <divclass="error"id="email-error"></div> </div> <divclass="form-group"> <labelfor="password">密码:</label> <inputtype="password"id="password"name="password"> <divclass="error"id="password-error"></div> <divclass="success"id="password-strength"></div> </div> <divclass="form-group"> <labelfor="confirm-password">确认密码:</label> <inputtype="password"id="confirm-password"name="confirm-password"> <divclass="error"id="confirm-password-error"></div> </div> <buttontype="submit">注册</button> </form> <script> const form = document.getElementById('registration-form'); const usernameInput = document.getElementById('username'); const emailInput = document.getElementById('email'); const passwordInput = document.getElementById('password'); const confirmPasswordInput = document.getElementById('confirm-password'); // 验证函数 function validateUsername(username) { if (username.length < 3) { return '用户名至少需要 3 个字符'; } if (username.length > 20) { return '用户名最多 20 个字符'; } if (!/^[a-zA-Z0-9_]+$/.test(username)) { return '用户名只能包含字母、数字和下划线'; } return ''; } function validateEmail(email) { const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!regex.test(email)) { return '请输入有效的邮箱地址'; } return ''; } function validatePassword(password) { if (password.length < 8) { return '密码至少需要 8 个字符'; } return ''; } function getPasswordStrength(password) { let strength = 0; if (password.length >= 8) strength++; if (/[a-z]/.test(password)) strength++; if (/[A-Z]/.test(password)) strength++; if (/[0-9]/.test(password)) strength++; if (/[^a-zA-Z0-9]/.test(password)) strength++; const levels = ['很弱', '弱', '中等', '强', '很强']; return levels[strength - 1] || '很弱'; } // 实时验证用户名 usernameInput.addEventListener('input', function() { const username = this.value; const error = validateUsername(username); const errorElement = document.getElementById('username-error'); if (error) { errorElement.textContent = error; this.classList.add('invalid'); this.classList.remove('valid'); } else { errorElement.textContent = ''; this.classList.remove('invalid'); this.classList.add('valid'); } }); // 实时验证邮箱 emailInput.addEventListener('input', function() { const email = this.value; const error = validateEmail(email); const errorElement = document.getElementById('email-error'); if (error) { errorElement.textContent = error; this.classList.add('invalid'); this.classList.remove('valid'); } else { errorElement.textContent = ''; this.classList.remove('invalid'); this.classList.add('valid'); } }); // 实时验证密码强度 passwordInput.addEventListener('input', function() { const password = this.value; const error = validatePassword(password); const errorElement = document.getElementById('password-error'); const strengthElement = document.getElementById('password-strength'); if (error) { errorElement.textContent = error; strengthElement.textContent = ''; this.classList.add('invalid'); this.classList.remove('valid'); } else { errorElement.textContent = ''; strengthElement.textContent = '密码强度:' + getPasswordStrength(password); this.classList.remove('invalid'); this.classList.add('valid'); } }); // 验证确认密码 confirmPasswordInput.addEventListener('input', function() { const password = passwordInput.value; const confirmPassword = this.value; const errorElement = document.getElementById('confirm-password-error'); if (confirmPassword && password !== confirmPassword) { errorElement.textContent = '两次输入的密码不一致'; this.classList.add('invalid'); this.classList.remove('valid'); } else { errorElement.textContent = ''; this.classList.remove('invalid'); this.classList.add('valid'); } }); // 表单提交验证 form.addEventListener('submit', function(event) { event.preventDefault(); // 验证所有字段 const usernameError = validateUsername(usernameInput.value); const emailError = validateEmail(emailInput.value); const passwordError = validatePassword(passwordInput.value); const confirmError = confirmPasswordInput.value !== passwordInput.value ? '两次输入的密码不一致' : ''; // 显示所有错误 if (usernameError) { document.getElementById('username-error').textContent = usernameError; } if (emailError) { document.getElementById('email-error').textContent = emailError; } if (passwordError) { document.getElementById('password-error').textContent = passwordError; } if (confirmError) { document.getElementById('confirm-password-error').textContent = confirmError; } // 如果没有错误,提交表单 if (!usernameError && !emailError && !passwordError && !confirmError) { console.log('表单验证通过,准备提交'); console.log('用户名:', usernameInput.value); console.log('邮箱:', emailInput.value); console.log('密码:', passwordInput.value); // 实际项目中,这里会发送 AJAX 请求 alert('注册成功!'); } }); </script></body></html>```---### 3. 异步数据请求#### 3.1 Fetch API```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>异步数据请求</title> <style> #user-list { margin-top: 20px; } .user-card { border: 1px solid #ddd; padding: 15px; margin: 10px 0; background-color: #f9f9f9; } #loading { display: none; color: blue; } #error { color: red; display: none; } </style></head><body> <h1>异步数据请求示例</h1> <buttonid="fetch-btn">获取用户数据</button> <divid="loading">加载中...</div> <divid="error"></div> <divid="user-list"></div> <script> const fetchBtn = document.getElementById('fetch-btn'); const loading = document.getElementById('loading'); const error = document.getElementById('error'); const userList = document.getElementById('user-list'); // 使用 Fetch API 获取数据 fetchBtn.addEventListener('click', async function() { // 显示加载状态 loading.style.display = 'block'; error.style.display = 'none'; userList.innerHTML = ''; fetchBtn.disabled = true; try { // 发送 GET 请求 const response = await fetch('https://jsonplaceholder.typicode.com/users'); // 检查响应状态 if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } // 解析 JSON 数据 const users = await response.json(); // 渲染数据 users.forEach(user => { const card = document.createElement('div'); card.className = 'user-card'; card.innerHTML = ` <h3>${user.name}</h3> <p><strong>邮箱:</strong> ${user.email}</p> <p><strong>电话:</strong> ${user.phone}</p> <p><strong>网站:</strong> ${user.website}</p> <p><strong>公司:</strong> ${user.company.name}</p> `; userList.appendChild(card); }); } catch (err) { // 显示错误信息 console.error('获取数据失败:', err); error.textContent = '获取数据失败:' + err.message; error.style.display = 'block'; } finally { // 恢复按钮状态 loading.style.display = 'none'; fetchBtn.disabled = false; } }); </script></body></html>```#### 3.2 POST 请求```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>POST 请求示例</title></head><body> <h1>创建新用户</h1> <formid="create-user-form"> <div> <labelfor="name">姓名:</label> <inputtype="text"id="name"name="name"required> </div> <br> <div> <labelfor="username">用户名:</label> <inputtype="text"id="username"name="username"required> </div> <br> <div> <labelfor="email">邮箱:</label> <inputtype="email"id="email"name="email"required> </div> <br> <buttontype="submit">创建</button> </form> <divid="result"></div> <script> const form = document.getElementById('create-user-form'); const result = document.getElementById('result'); form.addEventListener('submit', async function(event) { event.preventDefault(); // 获取表单数据 const formData = new FormData(form); const data = Object.fromEntries(formData.entries()); try { // 发送 POST 请求 const response = await fetch('https://jsonplaceholder.typicode.com/users', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const resultData = await response.json(); // 显示结果 result.innerHTML = ` <h3>用户创建成功!</h3> <p>ID: ${resultData.id}</p> <p>姓名: ${resultData.name}</p> <p>用户名: ${resultData.username}</p> <p>邮箱: ${resultData.email}</p> `; console.log('创建成功:', resultData); } catch (err) { console.error('创建失败:', err); result.innerHTML = `<p style="color: red;">创建失败:${err.message}</p>`; } }); </script></body></html>```---### 4. 本地存储```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>本地存储示例</title></head><body> <h1>本地存储示例</h1> <h2>LocalStorage(永久存储)</h2> <inputtype="text"id="local-key"placeholder="键名"> <inputtype="text"id="local-value"placeholder="值"> <buttonid="local-set">设置</button> <buttonid="local-get">获取</button> <buttonid="local-remove">删除</button> <buttonid="local-clear">清空所有</button> <divid="local-result"></div> <h2>SessionStorage(会话存储)</h2> <inputtype="text"id="session-key"placeholder="键名"> <inputtype="text"id="session-value"placeholder="值"> <buttonid="session-set">设置</button> <buttonid="session-get">获取</button> <buttonid="session-remove">删除</button> <buttonid="session-clear">清空所有</button> <divid="session-result"></div> <script> // ============ LocalStorage ============ const localSetBtn = document.getElementById('local-set'); const localGetBtn = document.getElementById('local-get'); const localRemoveBtn = document.getElementById('local-remove'); const localClearBtn = document.getElementById('local-clear'); const localResult = document.getElementById('local-result'); // 设置值 localSetBtn.addEventListener('click', function() { const key = document.getElementById('local-key').value; const value = document.getElementById('local-value').value; if (key && value) { localStorage.setItem(key, value); localResult.textContent = `已设置: ${key} = ${value}`; } }); // 获取值 localGetBtn.addEventListener('click', function() { const key = document.getElementById('local-key').value; const value = localStorage.getItem(key); if (value !== null) { localResult.textContent = `获取到: ${key} = ${value}`; } else { localResult.textContent = `键 ${key} 不存在`; } }); // 删除值 localRemoveBtn.addEventListener('click', function() { const key = document.getElementById('local-key').value; localStorage.removeItem(key); localResult.textContent = `已删除: ${key}`; }); // 清空所有 localClearBtn.addEventListener('click', function() { localStorage.clear(); localResult.textContent = '已清空所有 LocalStorage 数据'; }); // ============ SessionStorage ============ const sessionSetBtn = document.getElementById('session-set'); const sessionGetBtn = document.getElementById('session-get'); const sessionRemoveBtn = document.getElementById('session-remove'); const sessionClearBtn = document.getElementById('session-clear'); const sessionResult = document.getElementById('session-result'); // 设置值 sessionSetBtn.addEventListener('click', function() { const key = document.getElementById('session-key').value; const value = document.getElementById('session-value').value; if (key && value) { sessionStorage.setItem(key, value); sessionResult.textContent = `已设置: ${key} = ${value}`; } }); // 获取值 sessionGetBtn.addEventListener('click', function() { const key = document.getElementById('session-key').value; const value = sessionStorage.getItem(key); if (value !== null) { sessionResult.textContent = `获取到: ${key} = ${value}`; } else { sessionResult.textContent = `键 ${key} 不存在`; } }); // 删除值 sessionRemoveBtn.addEventListener('click', function() { const key = document.getElementById('session-key').value; sessionStorage.removeItem(key); sessionResult.textContent = `已删除: ${key}`; }); // 清空所有 sessionClearBtn.addEventListener('click', function() { sessionStorage.clear(); sessionResult.textContent = '已清空所有 SessionStorage 数据'; }); // 存储对象(需要序列化) const user = { name: '张三', age: 25, email: 'zhangsan@example.com' }; // 存储 localStorage.setItem('user', JSON.stringify(user)); // 读取 const savedUser = JSON.parse(localStorage.getItem('user')); console.log('保存的用户:', savedUser); </script></body></html>```---### 5. 动画效果```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>动画效果示例</title> <style> #animated-box { width: 100px; height: 100px; background-color: blue; position: relative; left: 0; transition: all 0.5s ease; } .moved { left: 200px; background-color: red; transform: rotate(180deg); } .fade-in { animation: fadeIn 1s ease-in; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } </style></head><body> <h1>动画效果示例</h1> <divid="animated-box"></div> <br> <buttonid="move-btn">移动盒子</button> <buttonid="reset-btn">重置</button> <buttonid="fade-btn">淡入效果</button> <script> const box = document.getElementById('animated-box'); const moveBtn = document.getElementById('move-btn'); const resetBtn = document.getElementById('reset-btn'); const fadeBtn = document.getElementById('fade-btn'); // 使用 CSS 类实现动画 moveBtn.addEventListener('click', function() { box.classList.toggle('moved'); }); // 重置 resetBtn.addEventListener('click', function() { box.classList.remove('moved'); box.classList.remove('fade-in'); }); // 使用 requestAnimationFrame 实现平滑动画 fadeBtn.addEventListener('click', function() { box.style.opacity = '0'; box.classList.add('fade-in'); }); // 使用 JavaScript 动画 let isAnimating = false; box.addEventListener('click', function() { if (isAnimating) return; isAnimating = true; let position = 0; const target = 300; const duration = 1000; // 1 秒 const startTime = performance.now(); function animate(currentTime) { const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); // 缓动函数 const easeOutQuad = t => t * (2 - t); const easedProgress = easeOutQuad(progress); position = easedProgress * target; box.style.left = position + 'px'; if (progress < 1) { requestAnimationFrame(animate); } else { isAnimating = false; } } requestAnimationFrame(animate); }); </script></body></html>```---## 前端开发中使用 TypeScript 的场景### 1. 为什么使用 TypeScript| 场景 | JavaScript | TypeScript ||------|-----------|-------------|| **小型项目** | ✅ 足够 | 可能过度 || **快速原型** | ✅ 更快 | 需要额外配置 || **团队项目** | ⚠️ 容易出错 | ✅ 类型安全 || **大型应用** | ❌ 难以维护 | ✅ 易于维护 || **重构项目** | ⚠️ 容易出错 | ✅ 类型保护 |---### 2. TypeScript 基础示例#### 2.1 类型注解```typescript// 基本类型let username: string = '张三';let age: number = 25;let isStudent: boolean = true;let nothing: null = null;let notDefined: undefined = undefined;// 数组let numbers: number[] = [1, 2, 3];let names: string[] = ['Alice', 'Bob', 'Charlie'];let anyArray: any[] = [1, 'hello', true];// 对象interface User { id: number; name: string; age: number; email?: string; // 可选属性}const user: User = { id: 1, name: '张三', age: 25};// 函数function greet(name: string): string { return `你好, ${name}!`;}function add(a: number, b: number): number { return a + b;}// 箭头函数const multiply = (a: number, b: number): number => a * b;// 可选参数function createUser(name: string, age?: number): void { console.log(`创建用户: ${name}, 年龄: ${age || '未知'}`);}```---### 3. TypeScript 实际应用示例#### 3.1 类型安全的 DOM 操作```typescript// 前端 TypeScript 项目// src/dom-operations.ts// 定义元素选择器const SELECTORS = { container: '#container', button: '.action-btn', input: '#username',} as const;// 获取元素(类型安全)function getElement<TextendsHTMLElement>(selector: string): T { const element = document.querySelector(selector); if (!element) { throw new Error(`元素 ${selector} 不存在`); } return element as T;}// 创建元素(类型安全)function createElement<KextendskeyofHTMLElementTagNameMap>( tag: K, className?: string): HTMLElementTagNameMap[K] { const element = document.createElement(tag); if (className) { element.className = className; } return element;}// 使用示例function initApp() { const container = getElement<HTMLDivElement>(SELECTORS.container); const button = getElement<HTMLButtonElement>(SELECTORS.button); const input = getElement<HTMLInputElement>(SELECTORS.input); // 类型安全的事件监听 button.addEventListener('click', (event: MouseEvent) => { const target = event.target as HTMLButtonElement; console.log('按钮点击:', target.textContent); }); input.addEventListener('input', (event: Event) => { const target = event.target as HTMLInputElement; console.log('输入值:', target.value); }); // 创建新元素 const card = createElement<HTMLDivElement>('div', 'card'); const title = createElement<HTMLHeadingElement>('h2'); title.textContent = '新卡片'; card.appendChild(title); container.appendChild(card);}// 页面加载后初始化document.addEventListener('DOMContentLoaded', initApp);```#### 3.2 类型安全的表单处理```typescript// src/form-handler.ts// 定义表单数据接口interface RegistrationForm { username: string; email: string; password: string; confirmPassword: string; agree: boolean;}// 定义验证错误接口interface ValidationError { field: keyof RegistrationForm; message: string;}// 表单验证器class FormValidator { private errors: ValidationError[] = []; // 验证用户名 validateUsername(username: string): boolean { if (username.length < 3) { this.addError('username', '用户名至少需要 3 个字符'); return false; } if (username.length > 20) { this.addError('username', '用户名最多 20 个字符'); return false; } return true; } // 验证邮箱 validateEmail(email: string): boolean { const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!regex.test(email)) { this.addError('email', '请输入有效的邮箱地址'); return false; } return true; } // 验证密码 validatePassword(password: string): boolean { if (password.length < 8) { this.addError('password', '密码至少需要 8 个字符'); return false; } return true; } // 添加错误 private addError(field: keyof RegistrationForm, message: string): void { this.errors.push({ field, message }); } // 获取所有错误 getErrors(): ValidationError[] { return this.errors; } // 清空错误 clearErrors(): void { this.errors = []; } // 是否有错误 hasErrors(): boolean { return this.errors.length > 0; }}// 表单处理类class FormHandler { private form: HTMLFormElement; private validator: FormValidator; constructor(formId: string) { const formElement = document.getElementById(formId); if (!formElement || !(formElement instanceof HTMLFormElement)) { throw new Error(`表单 ${formId} 不存在`); } this.form = formElement; this.validator = new FormValidator(); this.init(); } private init(): void { this.form.addEventListener('submit', this.handleSubmit.bind(this)); // 实时验证 const usernameInput = this.form.querySelector('#username') as HTMLInputElement; usernameInput?.addEventListener('input', (e: Event) => { const target = e.target as HTMLInputElement; this.validateField('username', target.value); }); } private async handleSubmit(event: Event): Promise<void> { event.preventDefault(); // 获取表单数据 const formData = new FormData(this.form); const data = Object.fromEntries(formData.entries()) as RegistrationForm; // 验证表单 this.validator.clearErrors(); this.validator.validateUsername(data.username); this.validator.validateEmail(data.email); this.validator.validatePassword(data.password); if (data.password !== data.confirmPassword) { this.validator['addError']('confirmPassword', '两次输入的密码不一致'); } // 显示错误 if (this.validator.hasErrors()) { this.displayErrors(); return; } // 提交表单 try { const response = await this.submitForm(data); console.log('提交成功:', response); alert('注册成功!'); } catch (error) { console.error('提交失败:', error); alert('注册失败,请重试'); } } private validateField(field: keyof RegistrationForm, value: string): void { // 字段级验证逻辑 } private displayErrors(): void { const errors = this.validator.getErrors(); console.log('表单错误:', errors); // 显示错误到 UI } private async submitForm(data: RegistrationForm): Promise<Response> { return fetch('/api/register', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), }); }}// 使用document.addEventListener('DOMContentLoaded', () => { new FormHandler('registration-form');});```#### 3.3 类型安全的 API 请求```typescript// src/api-client.ts// 定义 API 响应类型interface User { id: number; name: string; email: string; phone: string; website: string; company: { name: string; catchPhrase: string; };}interface ApiResponse<T> { data: T; message: string; code: number;}interface ApiError { message: string; code: number;}// API 客户端类class ApiClient { private baseUrl: string; constructor(baseUrl: string = 'https://api.example.com') { this.baseUrl = baseUrl; } // 通用请求方法 private async request<T>( endpoint: string, options: RequestInit = {} ): Promise<T> { const url = `${this.baseUrl}${endpoint}`; const response = await fetch(url, { headers: { 'Content-Type': 'application/json', ...options.headers, }, ...options, }); if (!response.ok) { const error: ApiError = await response.json(); throw new Error(error.message); } return response.json(); } // GET 请求 async get<T>(endpoint: string): Promise<T> { return this.request<T>(endpoint, { method: 'GET', }); } // POST 请求 async post<T>(endpoint: string, data: unknown): Promise<T> { return this.request<T>(endpoint, { method: 'POST', body: JSON.stringify(data), }); } // PUT 请求 async put<T>(endpoint: string, data: unknown): Promise<T> { return this.request<T>(endpoint, { method: 'PUT', body: JSON.stringify(data), }); } // DELETE 请求 async delete<T>(endpoint: string): Promise<T> { return this.request<T>(endpoint, { method: 'DELETE', }); }}// 用户 APIclass UserApi { private client: ApiClient; constructor() { this.client = new ApiClient('https://jsonplaceholder.typicode.com'); } // 获取所有用户 async getAllUsers(): Promise<User[]> { return this.client.get<User[]>('/users'); } // 获取单个用户 async getUser(id: number): Promise<User> { return this.client.get<User>(`/users/${id}`); } // 创建用户 async createUser(user: Partial<User>): Promise<User> { return this.client.post<User>('/users', user); } // 更新用户 async updateUser(id: number, user: Partial<User>): Promise<User> { return this.client.put<User>(`/users/${id}`, user); } // 删除用户 async deleteUser(id: number): Promise<void> { return this.client.delete<void>(`/users/${id}`); }}// 使用示例async function main() { const userApi = new UserApi(); try { // 获取所有用户 const users = await userApi.getAllUsers(); console.log('用户列表:', users); // 获取单个用户 const user = await userApi.getUser(1); console.log('用户详情:', user); // 创建新用户 const newUser = await userApi.createUser({ name: '张三', email: 'zhangsan@example.com', phone: '1234567890', }); console.log('新用户:', newUser); } catch (error) { console.error('API 请求失败:', error); }}main();```---### 4. 实战:完整的应用示例```typescript// src/app.ts// 应用状态类型interface AppState { users: User[]; loading: boolean; error: string | null; currentPage: number;}// 应用类class TodoApp { private state: AppState; constructor() { this.state = { users: [], loading: false, error: null, currentPage: 1, }; this.init(); } private init(): void { this.loadUsers(); this.bindEvents(); } private async loadUsers(): Promise<void> { this.setState({ loading: true, error: null }); try { const userApi = new UserApi(); const users = await userApi.getAllUsers(); this.setState({ users, loading: false }); } catch (error) { this.setState({ error: error instanceof Error ? error.message : '加载失败', loading: false, }); } } private setState(newState: Partial<AppState>): void { this.state = { ...this.state, ...newState }; this.render(); } private bindEvents(): void { const loadBtn = document.getElementById('load-btn') as HTMLButtonElement; loadBtn?.addEventListener('click', () => this.loadUsers()); const prevBtn = document.getElementById('prev-btn') as HTMLButtonElement; prevBtn?.addEventListener('click', () => { this.setState({ currentPage: Math.max(1, this.state.currentPage - 1) }); }); const nextBtn = document.getElementById('next-btn') as HTMLButtonElement; nextBtn?.addEventListener('click', () => { this.setState({ currentPage: this.state.currentPage + 1 }); }); } private render(): void { const container = document.getElementById('app-container') as HTMLDivElement; if (!container) return; // 渲染加载状态 if (this.state.loading) { container.innerHTML = '<p>加载中...</p>'; return; } // 渲染错误状态 if (this.state.error) { container.innerHTML = `<pclass="error">${this.state.error}</p>`; return; } // 渲染用户列表 const usersHtml = this.state.users.map(user => ` <divclass="user-card"> <h3>${user.name}</h3> <p>邮箱: ${user.email}</p> <p>电话: ${user.phone}</p> </div> `).join(''); container.innerHTML = ` <buttonid="load-btn">重新加载</button> <buttonid="prev-btn">上一页</button> <buttonid="next-btn">下一页</button> <p>当前页: ${this.state.currentPage}</p> <divclass="user-list"> ${usersHtml} </div> `; // 重新绑定事件 this.bindEvents(); }}// 启动应用document.addEventListener('DOMContentLoaded', () => { new TodoApp();});```---## JavaScript vs TypeScript 选择指南### 1. 决策树```是否需要使用 TypeScript?项目规模├── 小型(< 500 行代码)│ └── JavaScript ✅ 足够├── 中型(500 - 5000 行)│ ├── 需要长期维护?│ │ ├── 是 → TypeScript ✅│ │ └── 否 → JavaScript ✅└── 大型(> 5000 行) └── TypeScript ✅ 强烈推荐团队情况├── 个人项目│ └── 根据个人偏好选择├── 小团队(1-3 人)│ ├── 团队成员熟悉 TypeScript?│ │ ├── 是 → TypeScript ✅│ │ └── 否 → JavaScript ✅└── 大型团队 └── TypeScript ✅ 类型安全、降低沟通成本项目类型├── 快速原型│ └── JavaScript ✅ 开发速度快├── 生产环境应用│ └── TypeScript ✅ 减少运行时错误├── 库/框架开发│ └── TypeScript ✅ 提供类型定义└── 自动化测试 ├── 简单脚本 → JavaScript ✅ └── 复杂测试框架 → TypeScript ✅```---### 2. 详细使用场景对比#### 2.1 场景一:简单的页面交互**需求:** 一个简单的登录页面,有表单验证功能**JavaScript 实现:**```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>登录 - JavaScript 版本</title></head><body> <h1>登录</h1> <formid="login-form"> <inputtype="text"id="username"placeholder="用户名"> <inputtype="password"id="password"placeholder="密码"> <buttontype="submit">登录</button> </form> <divid="error"></div> <script> const form = document.getElementById('login-form'); const errorDiv = document.getElementById('error'); form.addEventListener('submit', function(event) { event.preventDefault(); const username = document.getElementById('username').value; const password = document.getElementById('password').value; // 简单验证 if (!username || !password) { errorDiv.textContent = '请填写完整信息'; return; } // 提交 fetch('/api/login', { method: 'POST', body: JSON.stringify({ username, password }) }); }); </script></body></html>```**TypeScript 实现:**```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>登录 - TypeScript 版本</title></head><body> <h1>登录</h1> <formid="login-form"> <inputtype="text"id="username"placeholder="用户名"> <inputtype="password"id="password"placeholder="密码"> <buttontype="submit">登录</button> </form> <divid="error"></div> <scripttype="module"> // 定义类型 interface LoginData { username: string; password: string; } interface ApiResponse { success: boolean; message: string; token?: string; } const form = document.getElementById('login-form') as HTMLFormElement; const errorDiv = document.getElementById('error') as HTMLDivElement; form.addEventListener('submit', async (event: Event) => { event.preventDefault(); const usernameInput = document.getElementById('username') as HTMLInputElement; const passwordInput = document.getElementById('password') as HTMLInputElement; const username = usernameInput.value; const password = passwordInput.value; // 类型安全的验证 if (!username || !password) { errorDiv.textContent = '请填写完整信息'; return; } // 类型安全的提交 const loginData: LoginData = { username, password }; try { const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(loginData), }); const result: ApiResponse = await response.json(); if (result.success && result.token) { localStorage.setItem('token', result.token); // 跳转到首页 window.location.href = '/home'; } else { errorDiv.textContent = result.message; } } catch (error) { errorDiv.textContent = '登录失败,请稍后重试'; } }); </script></body></html>```**对比:**| 方面 | JavaScript | TypeScript ||------|-----------|-------------|| **代码量** | 约 30 行 | 约 50 行(含类型) || **类型安全** | ❌ 无 | ✅ 有 || **IDE 支持** | ⚠️ 基础 | ✅ 完整 || **运行时错误** | 可能发现较晚 | 编译时发现 || **推荐场景** | ✅ 简单页面 | ⚠️ 可能过度 |**结论:** 简单的登录页面使用 **JavaScript** 即可,TypeScript 类型定义增加了复杂度,但收益不大。---#### 2.2 场景二:复杂的数据管理**需求:** 用户管理系统,包含增删改查、分页、筛选等功能**JavaScript 实现:**```javascript// 用户管理 - JavaScript 版本class UserManager { constructor() { this.users = []; this.page = 1; this.pageSize = 10; this.filters = {}; } // 获取用户 async getUsers() { const response = await fetch(`/api/users?page=${this.page}&size=${this.pageSize}`); this.users = await response.json(); return this.users; } // 添加用户 async addUser(user) { const response = await fetch('/api/users', { method: 'POST', body: JSON.stringify(user), }); const newUser = await response.json(); this.users.push(newUser); return newUser; } // 更新用户 async updateUser(id, updates) { const response = await fetch(`/api/users/${id}`, { method: 'PUT', body: JSON.stringify(updates), }); const updatedUser = await response.json(); const index = this.users.findIndex(u => u.id === id); if (index !== -1) { this.users[index] = updatedUser; } return updatedUser; } // 删除用户 async deleteUser(id) { const response = await fetch(`/api/users/${id}`, { method: 'DELETE', }); if (response.ok) { this.users = this.users.filter(u => u.id !== id); } } // 筛选用户 filterUsers(filters) { this.filters = filters; return this.users.filter(user => { return Object.entries(filters).every(([key, value]) => { return user[key] === value; }); }); }}// ❌ 问题:// 1. user 的类型不确定,可能出现拼写错误// 2. 返回值类型不明确// 3. 过滤器键可能不存在于 user 中```**TypeScript 实现:**```typescript// 用户管理 - TypeScript 版本// 定义类型interface User { id: number; username: string; email: string; age: number; role: 'admin' | 'user' | 'guest'; createdAt: string; updatedAt?: string;}interface UserFilters { username?: string; email?: string; role?: 'admin' | 'user' | 'guest'; minAge?: number; maxAge?: number;}interface PaginationParams { page: number; pageSize: number;}interface PaginatedResponse<T> { data: T[]; total: number; page: number; pageSize: number;}interface CreateUserDto { username: string; email: string; age: number; role: 'admin' | 'user' | 'guest'; password: string;}interface UpdateUserDto { username?: string; email?: string; age?: number; role?: 'admin' | 'user' | 'guest';}class UserManager { private users: User[]; private page: number; private pageSize: number; private filters: UserFilters; constructor() { this.users = []; this.page = 1; this.pageSize = 10; this.filters = {}; } // ✅ 类型安全的获取用户 async getUsers(params?: Partial<PaginationParams>): Promise<PaginatedResponse<User>> { const response = await fetch( `/api/users?page=${this.page}&size=${this.pageSize}` ); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result: PaginatedResponse<User> = await response.json(); this.users = result.data; return result; } // ✅ 类型安全的添加用户 async addUser(userDto: CreateUserDto): Promise<User> { const response = await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(userDto), }); if (!response.ok) { throw new Error('创建用户失败'); } const newUser: User = await response.json(); this.users.push(newUser); return newUser; } // ✅ 类型安全的更新用户 async updateUser(id: number, updates: UpdateUserDto): Promise<User> { const response = await fetch(`/api/users/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updates), }); if (!response.ok) { throw new Error('更新用户失败'); } const updatedUser: User = await response.json(); const index = this.users.findIndex(u => u.id === id); if (index !== -1) { this.users[index] = updatedUser; } return updatedUser; } // ✅ 类型安全的删除用户 async deleteUser(id: number): Promise<void> { const response = await fetch(`/api/users/${id}`, { method: 'DELETE', }); if (!response.ok) { throw new Error('删除用户失败'); } this.users = this.users.filter(u => u.id !== id); } // ✅ 类型安全的筛选用户 filterUsers(filters: Partial<UserFilters>): User[] { this.filters = { ...this.filters, ...filters }; return this.users.filter(user => { return Object.entries(this.filters).every(([key, value]) => { // TypeScript 确保键存在于 UserFilters 中 if (key === 'minAge') { return user.age >= (value as number); } if (key === 'maxAge') { return user.age <= (value as number); } return user[key as keyof User] === value; }); }); } // ✅ 类型安全的分页 getTotalPages(): number { return Math.ceil(this.users.length / this.pageSize); } // ✅ 类型安全的排序 sortUsers(field: keyof User, order: 'asc' | 'desc'): User[] { return [...this.users].sort((a, b) => { const aValue = a[field]; const bValue = b[field]; if (order === 'asc') { return aValue > bValue ? 1 : -1; } else { return aValue < bValue ? 1 : -1; } }); }}// 使用示例async function main() { const manager = new UserManager(); try { // 获取用户列表 const result = await manager.getUsers({ page: 1, pageSize: 10 }); console.log(`共 ${result.total} 个用户`); // 添加用户 const newUser = await manager.addUser({ username: 'zhangsan', email: 'zhangsan@example.com', age: 25, role: 'user', password: 'password123', }); // 更新用户 await manager.updateUser(newUser.id, { age: 26, role: 'admin', }); // 筛选用户 const admins = manager.filterUsers({ role: 'admin' }); console.log('管理员:', admins); // 排序用户 const sortedUsers = manager.sortUsers('age', 'asc'); console.log('按年龄排序:', sortedUsers); } catch (error) { console.error('操作失败:', error); }}```**对比:**| 方面 | JavaScript | TypeScript ||------|-----------|-------------|| **类型安全** | ❌ 无类型检查 | ✅ 完整类型定义 || **重构安全性** | ⚠️ 容易出错 | ✅ 编译器检查 || **代码可读性** | ⚠️ 需要注释 | ✅ 类型即文档 || **IDE 支持** | ⚠️ 基础 | ✅ 完整自动补全 || **运行时错误** | 可能才发现 | 编译时发现 || **推荐场景** | ❌ 不推荐 | ✅ 强烈推荐 |**结论:** 复杂数据管理系统使用 **TypeScript**,类型安全大幅减少错误,提高代码可维护性。---#### 2.3 场景三:前端自动化测试**需求:** 编写 Playwright 自动化测试**JavaScript 实现:**```javascript// 登录测试 - JavaScript 版本const { test, expect } = require('@playwright/test');test('should login successfully', async ({ page }) => { // 访问登录页 await page.goto('https://example.com/login'); // 填写表单 await page.fill('#username', 'admin'); await page.fill('#password', 'password'); // 点击登录按钮 await page.click('#login-btn'); // 验证跳转 await expect(page).toHaveURL('/dashboard'); // 验证元素可见 await expect(page.locator('.welcome-message')).toBeVisible();});test('should show error for invalid credentials', async ({ page }) => { await page.goto('https://example.com/login'); await page.fill('#username', 'wrong'); await page.fill('#password', 'wrong'); await page.click('#login-btn'); // 验证错误消息 const errorMessage = await page.textContent('.error-message'); expect(errorMessage).toContain('用户名或密码错误');});test('should validate form', async ({ page }) => { await page.goto('https://example.com/login'); // 尝试空提交 await page.click('#login-btn'); // ❌ 验证错误消息 - 如果选择器错误,运行时才发现 await expect(page.locator('.error-msg')).toBeVisible();});```**TypeScript 实现:**```typescript// 登录测试 - TypeScript 版本import { test, expect, Page } from '@playwright/test';// 定义测试数据类型interface LoginCredentials { username: string; password: string;}interface TestCase { name: string; credentials: LoginCredentials; expectedUrl: string; shouldSucceed: boolean;}// 定义页面对象class LoginPage { constructor(private page: Page) {} private readonly selectors = { usernameInput: '#username', passwordInput: '#password', loginButton: '#login-btn', errorMessage: '.error-message', welcomeMessage: '.welcome-message', }; async navigate(): Promise<void> { await this.page.goto('https://example.com/login'); } async fillCredentials(credentials: LoginCredentials): Promise<void> { await this.page.fill(this.selectors.usernameInput, credentials.username); await this.page.fill(this.selectors.passwordInput, credentials.password); } async clickLogin(): Promise<void> { await this.page.click(this.selectors.loginButton); } async getErrorMessage(): Promise<string> { return await this.page.textContent(this.selectors.errorMessage) || ''; } async isWelcomeVisible(): Promise<boolean> { return await this.page.locator(this.selectors.welcomeMessage).isVisible(); }}// 定义测试用例const testCases: TestCase[] = [ { name: '管理员登录', credentials: { username: 'admin', password: 'admin123' }, expectedUrl: '/dashboard', shouldSucceed: true, }, { name: '普通用户登录', credentials: { username: 'user', password: 'user123' }, expectedUrl: '/home', shouldSucceed: true, }, { name: '错误用户名', credentials: { username: 'wrong', password: 'password' }, expectedUrl: '/login', shouldSucceed: false, }, { name: '错误密码', credentials: { username: 'admin', password: 'wrong' }, expectedUrl: '/login', shouldSucceed: false, },];// 使用页面对象的测试test.describe('登录测试(TypeScript)', () => { let loginPage: LoginPage; test.beforeEach(async ({ page }) => { loginPage = new LoginPage(page); await loginPage.navigate(); }); testCases.forEach(({ name, credentials, expectedUrl, shouldSucceed }) => { test(`should ${shouldSucceed ? 'succeed' : 'fail'} with ${name}`, async ({ page }) => { // ✅ 类型安全的填充 await loginPage.fillCredentials(credentials); // 点击登录 await loginPage.clickLogin(); if (shouldSucceed) { // 验证跳转 await expect(page).toHaveURL(expectedUrl); // 验证欢迎消息 expect(await loginPage.isWelcomeVisible()).toBe(true); } else { // ✅ 类型安全的获取错误消息 const errorMessage = await loginPage.getErrorMessage(); expect(errorMessage).toBeTruthy(); expect(errorMessage).toContain('错误'); } }); });});// 表单验证测试test.describe('表单验证', () => { test('应该验证必填字段', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.navigate(); // 直接点击登录(不填写) await loginPage.clickLogin(); // ✅ TypeScript 确保选择器正确 await expect(page.locator('.validation-error')).toBeVisible(); }); test('应该验证邮箱格式', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.navigate(); await loginPage.fillCredentials({ username: 'admin', password: '' }); await loginPage.clickLogin(); const errorMessage = await loginPage.getErrorMessage(); expect(errorMessage).toContain('密码不能为空'); });});```**对比:**| 方面 | JavaScript | TypeScript ||------|-----------|-------------|| **页面对象** | ⚠️ 可能有拼写错误 | ✅ 类型安全 || **测试数据** | ⚠️ 结构不明确 | ✅ 类型定义明确 || **选择器** | ⚠️ 字符串容易出错 | ✅ 集中管理,类型检查 || **重构** | ⚠️ 容易遗漏 | ✅ 编译器强制检查 || **维护成本** | ⚠️ 较高 | ✅ 较低 || **推荐场景** | ⚠️ 简单脚本 | ✅ 复杂测试框架 |**结论:** 复杂的自动化测试框架使用 **TypeScript**,页面对象模式配合类型系统极大提高可维护性。---#### 2.4 场景四:快速原型和概念验证**需求:** 快速验证一个新功能 idea**JavaScript 实现(推荐):**```html<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <title>快速原型 - JavaScript</title></head><body> <h1>快速原型:拖拽功能</h1> <divid="draggable"style="width: 100px; height: 100px; background: blue; position: absolute;"> 拖拽我 </div> <script> // 简单的拖拽实现 const draggable = document.getElementById('draggable'); let isDragging = false; let offsetX, offsetY; draggable.addEventListener('mousedown', (e) => { isDragging = true; offsetX = e.clientX - draggable.offsetLeft; offsetY = e.clientY - draggable.offsetTop; }); document.addEventListener('mousemove', (e) => { if (isDragging) { draggable.style.left = (e.clientX - offsetX) + 'px'; draggable.style.top = (e.clientY - offsetY) + 'px'; } }); document.addEventListener('mouseup', () => { isDragging = false; }); </script></body></html>```**优点:**- 快速实现,无需配置- 直接在浏览器运行- 适合快速验证 idea**TypeScript 实现(不推荐用于快速原型):**```typescript// 需要配置 tsconfig.json// 需要编译步骤// 增加了不必要的复杂度```**结论:** 快速原型和概念验证使用 **JavaScript**,速度最重要,类型系统是累赘。---#### 2.5 场景五:长期维护的企业应用**需求:** 企业级管理系统,需要长期维护和多团队协作**TypeScript 实现(强烈推荐):**```typescript// 用户服务// src/services/user.service.tsinterface User { id: number; name: string; email: string; role: UserRole; status: UserStatus;}type UserRole = 'admin' | 'manager' | 'employee';type UserStatus = 'active' | 'inactive' | 'suspended';interface CreateUserDto { name: string; email: string; role: UserRole; password: string;}interface UpdateUserDto { name?: string; email?: string; role?: UserRole; status?: UserStatus;}interface UserListParams { page: number; pageSize: number; role?: UserRole; status?: UserStatus; search?: string;}class UserService { private baseUrl: string = '/api/users'; async getAll(params: UserListParams): Promise<{ data: User[]; total: number }> { const query = new URLSearchParams(params as any).toString(); const response = await fetch(`${this.baseUrl}?${query}`); if (!response.ok) { throw new Error(`获取用户列表失败: ${response.statusText}`); } return await response.json(); } async getById(id: number): Promise<User> { const response = await fetch(`${this.baseUrl}/${id}`); if (!response.ok) { throw new Error(`获取用户失败: ${response.statusText}`); } return await response.json(); } async create(dto: CreateUserDto): Promise<User> { const response = await fetch(this.baseUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(dto), }); if (!response.ok) { throw new Error(`创建用户失败: ${response.statusText}`); } return await response.json(); } async update(id: number, dto: UpdateUserDto): Promise<User> { const response = await fetch(`${this.baseUrl}/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(dto), }); if (!response.ok) { throw new Error(`更新用户失败: ${response.statusText}`); } return await response.json(); } async delete(id: number): Promise<void> { const response = await fetch(`${this.baseUrl}/${id}`, { method: 'DELETE', }); if (!response.ok) { throw new Error(`删除用户失败: ${response.statusText}`); } }}// 导出单例export const userService = new UserService();```**使用 TypeScript 的优势:**```typescript// 在其他文件中使用import { userService } from './services/user.service';// ✅ IDE 会自动补全所有方法userService.getAll({ page: 1, pageSize: 10 });// ✅ 编译器检查参数类型userService.create({ name: '张三', email: 'zhangsan@example.com', role: 'admin', // ✅ 只能选择 'admin' | 'manager' | 'employee' password: '123456',});// ❌ 编译错误:缺少必需字段// userService.create({// name: '张三',// });// ❌ 编译错误:错误的角色// userService.create({// name: '张三',// email: 'zhangsan@example.com',// role: 'super-admin', // Error// password: '123456',// });```**JavaScript 的风险:**```javascript// ❌ 拼写错误,运行时才发现userService.getAll({ page: 1, pageSize: 10, rol: 'admin', // 拼写错误!});// ❌ 传递了错误的字段类型userService.create({ name: '张三', email: 'zhangsan@example.com', role: 12345, // 应该是字符串! password: '123456',});```**结论:** 长期维护的企业应用使用 **TypeScript**,类型系统是投资,回报是代码质量。---### 3. 迁移建议#### 从 JavaScript 迁移到 TypeScript**步骤 1:逐步迁移**```typescript// 1. 保持 .js 文件// script.jsfunction greet(name) { return `Hello, ${name}!`;}``````typescript// 2. 创建同名的 .ts 文件// script.tsfunction greet(name: string): string { return `Hello, ${name}!`;}```**步骤 2:添加类型定义**```typescript// 定义接口interface User { id: number; name: string; email: string;}// 使用接口function getUser(id: number): User { // ...}```**步骤 3:启用严格模式**```json// tsconfig.json{ "compilerOptions": { "strict": true, "noImplicitAny": true, "strictNullChecks": true }}```---## 最佳实践### 1. Script 标签最佳实践```html<!-- ✅ 推荐:放在 body 末尾 --><body> <divid="app"> <!-- 页面内容 --> </div> <scriptsrc="js/app.js"></script></body><!-- ✅ 推荐:使用 defer --><head> <scriptdefersrc="js/app.js"></script></head><!-- ✅ 推荐:使用模块 --><scripttype="module"src="js/app.js"></script><!-- ❌ 不推荐:放在 head 中且无 defer --><head> <scriptsrc="js/app.js"></script> <!-- 阻塞页面加载 --></head>```### 2. JavaScript 最佳实践```javascript// ✅ 使用 const 和 letconst API_URL = 'https://api.example.com';let currentPage = 1;// ❌ 避免使用 varvar api_url = 'https://api.example.com'; // 不推荐// ✅ 使用箭头函数const addUser = (user) => { console.log(user);};// ✅ 使用模板字符串const message = `你好, ${name}!`;// ✅ 使用解构赋值const { id, name, email } = user;// ✅ 使用展开运算符const newUser = { ...user, age: 25 };// ✅ 使用 Promise 和 async/awaitasync function fetchData() { try { const response = await fetch(url); const data = await response.json(); return data; } catch (error) { console.error(error); }}```### 3. TypeScript 最佳实践```typescript// ✅ 使用接口定义类型interface User { id: number; name: string; email: string;}// ✅ 使用类型别名type UserID = number;type UserRole = 'admin' | 'user' | 'guest';// ✅ 使用枚举enum Status { Pending = 0, Active = 1, Inactive = 2,}// ✅ 使用泛型function identity<T>(arg: T): T { return arg;}// ✅ 使用类型守卫function isString(value: unknown): value is string { return typeof value === 'string';}// ✅ 使用 readonly 防止意外修改interface Config { readonly apiUrl: string; readonly timeout: number;}// ✅ 使用可选链const email = user?.contact?.email;// ✅ 使用空值合并const name = userName ?? '匿名';```---## 总结### Script 执行时机总结| 场景 | 推荐方式 | 原因 ||------|---------|------|| **操作 DOM** | defer 或 body 末尾 | 确保 DOM 已加载 || **独立脚本** | async | 不阻塞页面加载 || **立即执行** | 无属性 | 需要立即执行的代码 || **模块化** | type="module" | 支持 ES6 模块 || **向后兼容** | nomodule | 兼容旧浏览器 |### JavaScript vs TypeScript**选择 JavaScript:**- 快速原型开发- 学习 JavaScript 基础- 小型项目- 团队不熟悉 TypeScript**选择 TypeScript:**- 大型项目- 需要长期维护- 团队协作- 库/框架开发- 自动化测试---
# JavaScript/TypeScript 后端自动化实战## 详细分析与实操示例---## 目录1. [后端自动化概览](#后端自动化概览)2. [Node.js 环境搭建](#nodejs-环境搭建)3. [Python 环境搭建](#python-环境搭建)4. [场景一:自动化 API 测试](#场景一自动化-api-测试)4. [场景二:数据爬取和处理](#场景二数据爬取和处理)5. [场景三:文件系统自动化](#场景三文件系统自动化)6. [场景四:数据库自动化操作](#场景四数据库自动化操作)7. [场景五:定时任务和调度](#场景五定时任务和调度)8. [场景六:邮件自动化](#场景六邮件自动化)9. [场景七:CI/CD 自动化](#场景七cicd-自动化)10. [场景八:服务器监控和日志](#场景八服务器监控和日志)11. [对比总结](#对比总结)---## 后端自动化概览### JavaScript/TypeScript 在后端自动化的能力| 能力 | 描述 | 常用库 ||------|------|---------|| **API 测试** | 自动测试 REST API | Jest, Supertest || **数据爬取** | 爬取网页数据 | Puppeteer, Playwright, Cheerio || **文件操作** | 自动处理文件 | fs-extra, glob || **数据库操作** | 自动 CRUD 操作 | Sequelize, TypeORM || **定时任务** | 定时执行任务 | node-cron, Agenda || **邮件发送** | 自动发送邮件 | Nodemailer || **CI/CD** | 自动构建部署 | GitHub Actions, GitLab CI || **监控告警** | 服务监控告警 | Winston, Prometheus || **报表生成** | 自动生成报表 | ExcelJS, PDFKit || **自动化部署** | 自动部署应用 | PM2, Docker |---## Node.js 环境搭建### 1. 创建后端自动化项目```bash# 创建项目目录mkdir backend-automationcd backend-automation# 初始化项目npm init -y# 安装常用依赖npm install axios cheerio puppeteer express fs-extra node-cronnpm install --save-dev @types/node typescript ts-node jest supertest```### 2. 项目结构```backend-automation/├── src/│ ├── api/ # API 相关│ │ ├── api-client.ts│ │ └── api-tests.ts│ ├── crawler/ # 爬虫相关│ │ ├── web-crawler.ts│ │ └── data-processor.ts│ ├── file/ # 文件操作│ │ ├── file-manager.ts│ │ └── report-generator.ts│ ├── database/ # 数据库相关│ │ ├── db-connection.ts│ │ └── repository.ts│ ├── scheduler/ # 定时任务│ │ └── jobs.ts│ ├── email/ # 邮件相关│ │ └── mailer.ts│ ├── monitor/ # 监控相关│ │ └── logger.ts│ └── index.ts # 入口文件├── data/ # 数据文件├── output/ # 输出文件├── logs/ # 日志文件├── tests/ # 测试文件├── package.json└── tsconfig.json```---## Python 环境搭建### 1.1 安装 Python 依赖```bash# 创建项目目录mkdir backend-automationcd backend-automation# 创建虚拟环境python -m venv venv# 激活虚拟环境# Windows:venv\Scripts\activate# Mac/Linux:source venv/bin/activate# 安装依赖pip install requests pytest playwright-openpyxl openpyxl pandaspip install --save-dev pytest-playwright```### 1.2 项目结构```backend-automation/├── src/│ ├── api/ # API 相关│ │ ├── api_client.py│ │ └── api_tests.py│ ├── crawler/ # 爬虫相关│ │ └── web_crawler.py│ ├── database/ # 数据库相关│ │ ├── db_connection.py│ │ └── operations.py│ ├── scheduler/ # 定时任务│ │ └── jobs.py│ ├── email/ # 邮件相关│ │ └── mailer.py│ └── monitor/ # 监控相关│ └── logger.py├── tests/ # 测试文件├── data/ # 数据文件├── output/ # 输出文件├── logs/ # 日志文件├── requirements.txt└── README.md```---## 场景一:自动化 API 测试### Python 版本:API 客户端封装```python# src/api/api_client.pyimport requestsfrom typing import Any, Dict, List, Optionalfrom datetime import datetime# 响应类型定义class ApiResponse: def __init__(self, code: int = 0, message: str = '', data: Any = None): self.code = code self.message = message self.data = data @classmethod def from_dict(cls, data: Dict): return cls( code=data.get('code', 0), message=data.get('message', ''), data=data.get('data') ) @property def is_success(self) -> bool: return self.code == 200# API 客户端类class ApiClient: def __init__(self, base_url: str = 'http://localhost:3000/api'): self.base_url = base_url self.token: Optional[str] = None self.session = requests.Session() def set_token(self, token: str) -> None: """设置认证令牌""" self.token = token def get_headers(self) -> Dict[str, str]: """获取请求头""" headers = { 'Content-Type': 'application/json', 'User-Agent': 'Python API Client' } if self.token: headers['Authorization'] = f'Bearer {self.token}' return headers def request(self, method: str, endpoint: str, **kwargs) -> ApiResponse: """通用请求方法""" url = f'{self.base_url}{endpoint}' try: response = self.session.request( method=method, url=url, headers=self.get_headers(), **kwargs ) response.raise_for_status() return ApiResponse.from_dict(response.json()) except requests.exceptions.RequestException as e: print(f'请求失败: {e}') return ApiResponse(code=500, message=str(e)) def get(self, endpoint: str, params: Dict = None) -> ApiResponse: """GET 请求""" return self.request('GET', endpoint, params=params) def post(self, endpoint: str, data: Any = None) -> ApiResponse: """POST 请求""" return self.request('POST', endpoint, json=data) def put(self, endpoint: str, data: Any = None) -> ApiResponse: """PUT 请求""" return self.request('PUT', endpoint, json=data) def delete(self, endpoint: str) -> ApiResponse: """DELETE 请求""" return self.request('DELETE', endpoint) # ========== 用户 API ========== def login(self, username: str, password: str) -> ApiResponse: """登录""" response = self.post('/auth/login', { 'username': username, 'password': password }) if response.is_success and 'token' in response.data: self.set_token(response.data['token']) return response def get_current_user(self) -> ApiResponse: """获取当前用户""" return self.get('/user/me') def get_users(self, page: int = 1, page_size: int = 10) -> ApiResponse: """获取用户列表""" return self.get('/users', params={ 'page': page, 'pageSize': page_size }) def create_user(self, user: Dict) -> ApiResponse: """创建用户""" return self.post('/users', user) def update_user(self, user_id: int, user: Dict) -> ApiResponse: """更新用户""" return self.put(f'/users/{user_id}', user) def delete_user(self, user_id: int) -> ApiResponse: """删除用户""" return self.delete(f'/users/{user_id}')# 使用示例if __name__ == '__main__': client = ApiClient() # 登录 login_result = client.login('admin', 'admin123') print(f'登录结果: {login_result.is_success}') # 获取用户列表 users_result = client.get_users(1, 10) print(f'用户列表: {users_result.data}')```### Python 版本:自动化测试(使用 pytest)```python# tests/api_tests.pyimport pytestimport requestsfrom typing import Dict, Listfrom datetime import datetime, timedeltaimport json# API 客户端(单例模式)api_client = None@pytest.fixture(scope='session')def api_client_fixture(): """创建 API 客户端""" global api_client api_client = ApiClient('http://localhost:3000/api') return api_client# ========== 认证测试 ==========def test_login_success(api_client_fixture): """测试成功登录""" response = api_client_fixture.login('admin', 'admin123') assert response.is_success assert 'token' in response.data print('✅ 登录测试通过')def test_login_failure(api_client_fixture): """测试失败登录""" response = api_client_fixture.login('wrong', 'wrong') assert not response.is_success assert 'token' not in response.data print('✅ 登录失败测试通过')# ========== 用户管理测试 ==========def test_get_users(api_client_fixture): """测试获取用户列表""" response = api_client_fixture.get_users(1, 10) assert response.is_success assert response.data is not None assert len(response.data) <= 10 print(f'✅ 获取用户列表测试通过,共 {len(response.data)} 个用户')def test_create_user(api_client_fixture): """测试创建用户""" new_user = { 'username': 'testuser', 'email': 'test@example.com', 'password': 'hashed_password', 'role': 'user' } response = api_client_fixture.create_user(new_user) assert response.is_success assert response.data['username'] == 'testuser' print('✅ 创建用户测试通过')def test_update_user(api_client_fixture): """测试更新用户""" updates = {'email': 'updated@example.com'} response = api_client_fixture.update_user(1, updates) assert response.is_success assert response.data['email'] == 'updated@example.com' print('✅ 更新用户测试通过')def test_delete_user(api_client_fixture): """测试删除用户""" response = api_client_fixture.delete_user(999) assert response.code == 404 print('✅ 删除用户测试通过')# ========== 并发测试 ==========@pytest.mark.parametrize('count', [1, 5, 10])def test_concurrent_requests(api_client_fixture, count): """测试并发请求""" import concurrent.futures def get_users_request(): return api_client_fixture.get_users(1, 10) start_time = datetime.now() with concurrent.futures.ThreadPoolExecutor(max_workers=count) as executor: futures = [executor.submit(get_users_request) for _ in range(count)] results = [f.result() for f in concurrent.futures.as_completed(futures)] duration = (datetime.now() - start_time).total_seconds() print(f'✅ 并发测试通过:{count} 个请求,耗时 {duration:.2f}s') assert duration < 5.0 # 响应时间应小于5秒# ========== 数据驱动测试 ==========@pytest.mark.parametrize('username,password,expected', [ ('admin', 'admin123', True), ('wrong', 'wrong', False), ('', '', False),])def test_login_with_credentials(username, password, expected): """数据驱动的登录测试""" from src.api.api_client import ApiClient client = ApiClient() response = client.login(username, password) if expected: assert response.is_success assert 'token' in response.data else: assert not response.is_success print(f'✅ 登录参数化测试通过: {username}, 期望={expected}')# ========== 性能测试 ==========def test_api_performance(api_client_fixture): """测试 API 性能""" response_times = [] for i in range(10): start = datetime.now() api_client_fixture.get_users(1, 10) duration = (datetime.now() - start_time).total_seconds() response_times.append(duration) avg_time = sum(response_times) / len(response_times) max_time = max(response_times) print(f'✅ API 性能测试通过') print(f' 平均响应时间: {avg_time:.3f}s') print(f' 最大响应时间: {max_time:.3f}s') assert avg_time < 1.0 # 平均响应时间应小于1秒# ========== 测试报告生成 ==========@pytest.hookimpl(autouse=True)def generate_report(request): """生成测试报告""" yield # 测试执行 # 测试完成后生成报告 test_results = { 'timestamp': datetime.now().isoformat(), 'total_tests': len([node for node in request.node.items]), 'passed': 0, 'failed': 0, 'results': [] } for node in request.node.items: for result in node.result: if hasattr(result, 'call_result'): test_results['results'].append({ 'test_name': node.name, 'passed': result.passed, 'duration': result.call_result.duration if hasattr(result.call_result, 'duration') else 0 }) if result.passed: test_results['passed'] += 1 else: test_results['failed'] += 1 # 生成 HTML 报告 html = f"""<!DOCTYPE html><html lang="zh-CN"><head> <meta charset="UTF-8"> <title>Python API 测试报告</title> <style> body {{ font-family: Arial, sans-serif; padding: 20px; }} .summary {{ background: #f0f0f0; padding: 20px; margin-bottom: 20px; border-radius: 5px; }} .test-item {{ margin: 10px 0; padding: 10px; border-left: 4px solid #ddd; }} .pass {{ border-left-color: #4CAF50; background: #e8f5e9; }} .fail {{ border-left-color: #f44336; background: #ffebee; }} .duration {{ color: #666; font-size: 12px; }} </style></head><body> <h1>Python API 自动化测试报告</h1> <p>生成时间: {test_results['timestamp']}</p> <div class="summary"> <h2>测试摘要</h2> <p>总测试数: {test_results['total']}</p> <p>通过: {test_results['passed']}</p> <p>失败: {test_results['failed']}</p> <p>通过率: {(test_results['passed'] / test_results['total'] * 100 if test_results['total'] > 0 else 0:.1f}%</p> </div> <h2>详细结果</h2> {"".join([f""" <div class="test-item {''}{'pass' if r['passed'] else 'fail'}}"> <strong>{r['test_name']}</strong> <span class="duration">({r['duration']}ms)</span> </div> """ for r in test_results['results']])}</body></html> """ # 保存报告 import os output_dir = '../output/tests' os.makedirs(output_dir, exist_ok=True) report_path = f'{output_dir}/test-report-{datetime.now().strftime("%Y%m%d_%H%M%S")}.html' with open(report_path, 'w', encoding='utf-8') as f: f.write(html) print(f'测试报告已生成: {report_path}')# ========== 主程序入口 ==========def main(): """运行所有测试并生成报告""" # 导入客户端 from src.api.api_client import ApiClient # 创建测试客户端 client = ApiClient('http://localhost:3000/api') print('开始 Python API 自动化测试...') # 简单的测试循环 test_cases = [ ('登录测试', client.login('admin', 'admin123')), ('获取用户列表', client.get_users(1, 10)), ('创建用户', client.create_user({ 'username': 'testuser', 'email': 'test@example.com', 'password': 'pass123', 'role': 'user' })), ] for test_name, test_fn in test_cases: try: print(f'执行: {test_name}...') result = test_fn() if result.is_success: print(f' ✅ 成功') else: print(f' ❌ 失败: {result.message}') except Exception as e: print(f' ❌ 错误: {e}') print('\n所有测试完成!')if __name__ == '__main__': main()```### Python 版本:Playwright 自动化测试```python# tests/playwright_tests.pyfrom playwright.sync_api import sync_playwrightfrom typing import List, Dictclass LoginPage: """登录页面对象""" def __init__(self, page): self.page = page self.url = 'http://localhost:3000/login' self.selectors = { 'username': '#username', 'password': '#password', 'login_button': '#login-btn', 'error_message': '.error-message', 'welcome_message': '.welcome-message', } def navigate(self): """导航到登录页""" self.page.goto(self.url) def fill_credentials(self, username: str, password: str): """填写凭据""" self.page.fill(self.selectors['username'], username) self.page.fill(self.selectors['password'], password) def click_login(self): """点击登录按钮""" self.page.click(self.selectors['login_button']) def get_error_message(self) -> str: """获取错误消息""" return self.page.text_content(self.selectors['error_message']) or '' def is_welcome_visible(self) -> bool: """检查欢迎消息是否可见""" return self.page.is_visible(self.selectors['welcome_message'])def test_login_with_valid_credentials(page): """测试有效凭据登录""" login_page = LoginPage(page) login_page.navigate() login_page.fill_credentials('admin', 'admin123') login_page.click_login() # 验证跳转 page.wait_for_url('**/dashboard', timeout=5000) assert '/dashboard' in page.url print('✅ 有效凭据登录测试通过')def test_login_with_invalid_credentials(page): """测试无效凭据登录""" login_page = LoginPage(page) login_page.navigate() login_page.fill_credentials('wrong', 'wrong') login_page.click_login() # 等待错误消息 page.wait_for_selector('.error-message', timeout=5000) error_msg = login_page.get_error_message() assert error_msg != '' print('✅ 无效凭据登录测试通过')def test_form_validation(page): """测试表单验证""" login_page = LoginPage(page) login_page.navigate() # 直接点击登录(不填写) login_page.click_login() # 等待验证错误 page.wait_for_selector('.validation-error', timeout=5000) assert page.is_visible('.validation-error') print('✅ 表单验证测试通过')# ========== 数据驱动测试 ==========test_data = [ ('admin', 'admin123', True, '成功登录'), ('user', 'user123', True, '成功登录'), ('wrong', 'wrong', False, '登录失败'), ('', '', False, '空凭据'),]@pytest.mark.parametrize('username,password,should_succeed,description', test_data)def test_login_scenarios(page, username, password, should_succeed, description): """数据驱动登录测试""" login_page = LoginPage(page) login_page.navigate() login_page.fill_credentials(username, password) login_page.click_login() if should_succeed: page.wait_for_url('**/dashboard', timeout=5000) assert '/dashboard' in page.url else: page.wait_for_selector('.error-message', timeout=5000) error_msg = login_page.get_error_message() assert '错误' in error_msg print(f'✅ 场景测试通过: {description}')def test_multiple_scenarios(page): """测试多个场景""" scenarios = [ { 'name': '管理员登录', 'username': 'admin', 'password': 'admin123', 'should_succeed': True }, { 'name': '普通用户登录', 'username': 'user', 'password': 'user123', 'should_succeed': True }, { 'name': '错误密码', 'username': 'admin', 'password': 'wrong', 'should_succeed': False }, ] for scenario in scenarios: page.goto('http://localhost:3000/login') page.fill('#username', scenario['username']) page.fill('#password', scenario['password']) page.click('#login-btn') if scenario['should_succeed']: page.wait_for_url('**/dashboard') assert '/dashboard' in page.url else: page.wait_for_selector('.error-message') error_msg = page.text_content('.error-message') assert '错误' in error_msg print(f'✅ 场景测试通过: {scenario["name"]}')if __name__ == '__main__': # 直接运行测试 with sync_playwright() as p: browser = p.chromium.launch(headless=False) page = browser.new_page() # 运行单个测试 test_login_with_valid_credentials(page) page.reload() # 运行数据驱动测试 test_login_scenarios(page) browser.close()```---## 场景一:自动化 API 测试### 1.1 API 客户端封装```typescript// src/api/api-client.ts// 响应类型定义interface ApiResponse<T = any> { code: number; message: string; data: T;}interface User { id: number; username: string; email: string; role: string; createdAt: string;}interface LoginData { username: string; password: string;}interface LoginResponse { token: string; user: User;}// API 客户端类class ApiClient { private baseURL: string; private token: string | null = null; constructor(baseURL: string = 'http://localhost:3000/api') { this.baseURL = baseURL; } // 设置认证令牌 setToken(token: string): void { this.token = token; } // 获取请求头 private getHeaders(): Record<string, string> { const headers: Record<string, string> = { 'Content-Type': 'application/json', }; if (this.token) { headers['Authorization'] = `Bearer ${this.token}`; } return headers; } // 通用请求方法 private async request<T>( endpoint: string, options: RequestInit = {} ): Promise<ApiResponse<T>> { const url = `${this.baseURL}${endpoint}`; const response = await fetch(url, { headers: this.getHeaders(), ...options, }); if (!response.ok) { const errorData: ApiResponse = await response.json(); throw new Error(errorData.message || '请求失败'); } return response.json(); } // GET 请求 async get<T>(endpoint: string): Promise<ApiResponse<T>> { return this.request<T>(endpoint, { method: 'GET' }); } // POST 请求 async post<T>(endpoint: string, data: any): Promise<ApiResponse<T>> { return this.request<T>(endpoint, { method: 'POST', body: JSON.stringify(data), }); } // PUT 请求 async put<T>(endpoint: string, data: any): Promise<ApiResponse<T>> { return this.request<T>(endpoint, { method: 'PUT', body: JSON.stringify(data), }); } // DELETE 请求 async delete<T>(endpoint: string): Promise<ApiResponse<T>> { return this.request<T>(endpoint, { method: 'DELETE' }); } // ========== 用户 API ========== // 登录 async login(data: LoginData): Promise<ApiResponse<LoginResponse>> { const response = await this.post<LoginResponse>('/auth/login', data); if (response.code === 200 && response.data.token) { this.setToken(response.data.token); } return response; } // 获取当前用户 async getCurrentUser(): Promise<ApiResponse<User>> { return this.get<User>('/user/me'); } // 获取用户列表 async getUsers(page: number = 1, pageSize: number = 10): Promise<ApiResponse<User[]>> { return this.get<User[]>(`/users?page=${page}&pageSize=${pageSize}`); } // 创建用户 async createUser(user: Partial<User>): Promise<ApiResponse<User>> { return this.post<User>('/users', user); } // 更新用户 async updateUser(id: number, user: Partial<User>): Promise<ApiResponse<User>> { return this.put<User>(`/users/${id}`, user); } // 删除用户 async deleteUser(id: number): Promise<ApiResponse<void>> { return this.delete<void>(`/users/${id}`); }}// 导出单例export const apiClient = new ApiClient('http://localhost:3000/api');```### 1.2 自动化 API 测试```typescript// src/api/api-tests.tsimport { apiClient } from './api-client';import * as fs from 'fs';import * as path from 'path';// 测试结果接口interface TestResult { testCase: string; success: boolean; duration: number; error?: string;}// 测试套件class ApiTestSuite { private results: TestResult[] = []; // 记录测试结果 private recordResult( testCase: string, success: boolean, duration: number, error?: string ): void { this.results.push({ testCase, success, duration, error }); } // 执行测试并计时 private async executeTest( testCase: string, testFn: () => Promise<void> ): Promise<void> { const startTime = Date.now(); try { await testFn(); const duration = Date.now() - startTime; this.recordResult(testCase, true, duration); console.log(`✅ ${testCase} - 通过 (${duration}ms)`); } catch (error) { const duration = Date.now() - startTime; this.recordResult(testCase, false, duration, String(error)); console.error(`❌ ${testCase} - 失败: ${error}`); } } // ========== 用户认证测试 ========== async testAuth(): Promise<void> { await this.executeTest('登录 - 正确的凭据', async () => { const result = await apiClient.login({ username: 'admin', password: 'admin123', }); if (result.code !== 200) { throw new Error('登录失败'); } }); await this.executeTest('登录 - 错误的凭据', async () => { try { await apiClient.login({ username: 'wrong', password: 'wrong', }); throw new Error('应该抛出错误'); } catch (error) { if (!String(error).includes('请求失败')) { throw new Error('错误类型不正确'); } } }); } // ========== 用户管理测试 ========== async testUserManagement(): Promise<void> { await this.executeTest('获取用户列表', async () => { const result = await apiClient.getUsers(1, 10); if (result.code !== 200 || !result.data) { throw new Error('获取用户列表失败'); } }); await this.executeTest('创建用户', async () => { const result = await apiClient.createUser({ username: 'testuser', email: 'test@example.com', role: 'user', }); if (result.code !== 201) { throw new Error('创建用户失败'); } }); await this.executeTest('更新用户', async () => { const result = await apiClient.updateUser(1, { email: 'updated@example.com', }); if (result.code !== 200) { throw new Error('更新用户失败'); } }); await this.executeTest('删除用户', async () => { const result = await apiClient.deleteUser(999); // 999 不存在的用户 if (result.code !== 404) { throw new Error('删除用户应该返回 404'); } }); } // ========== 并发测试 ========== async testConcurrency(): Promise<void> { await this.executeTest('并发请求测试', async () => { const requests = Array(10).fill(0).map((_, i) => apiClient.getUsers(i + 1, 10) ); const startTime = Date.now(); await Promise.all(requests); const duration = Date.now() - startTime; console.log(`10 个并发请求完成时间: ${duration}ms`); if (duration > 5000) { throw new Error('并发性能不达标'); } }); } // ========== 错误处理测试 ========== async testErrorHandling(): Promise<void> { await this.executeTest('无效的请求参数', async () => { try { await apiClient.getUsers(-1, -1); throw new Error('应该处理无效参数'); } catch (error) { // 预期会抛出错误 if (!String(error).includes('请求失败')) { throw new Error('错误处理不正确'); } } }); await this.executeTest('不存在的资源', async () => { const result = await apiClient.getUser(99999); if (result.code !== 404) { throw new Error('应该返回 404'); } }); } // 生成测试报告 generateReport(): void { const reportPath = path.join(__dirname, '../../output/api-test-report.html'); const totalTests = this.results.length; const passedTests = this.results.filter(r => r.success).length; const failedTests = totalTests - passedTests; const passRate = ((passedTests / totalTests) * 100).toFixed(2); const avgDuration = ( this.results.reduce((sum, r) => sum + r.duration, 0) / totalTests ).toFixed(2); const html = `<!DOCTYPE html><html lang="zh-CN"><head> <meta charset="UTF-8"> <title>API 测试报告</title> <style> body { font-family: Arial, sans-serif; padding: 20px; } .summary { background: #f0f0f0; padding: 20px; margin-bottom: 20px; } .test-item { margin: 10px 0; padding: 10px; border-left: 4px solid #ddd; } .pass { border-left-color: #4CAF50; background: #e8f5e9; } .fail { border-left-color: #f44336; background: #ffebee; } .error { color: #f44336; margin-top: 5px; } .duration { color: #666; font-size: 12px; } </style></head><body> <h1>API 自动化测试报告</h1> <p>生成时间: ${new Date().toLocaleString()}</p> <div class="summary"> <h2>测试摘要</h2> <p>总测试数: ${totalTests}</p> <p>通过: ${passedTests}</p> <p>失败: ${failedTests}</p> <p>通过率: ${passRate}%</p> <p>平均耗时: ${avgDuration}ms</p> </div> <h2>详细结果</h2> ${this.results.map(result => ` <div class="test-item ${result.success ? 'pass' : 'fail'}"> <strong>${result.testCase}</strong> <span class="duration">(${result.duration}ms)</span> ${!result.success ? `<div class="error">错误: ${result.error}</div>` : ''} </div> `).join('')}</body></html> `; // 确保输出目录存在 const outputDir = path.join(__dirname, '../../output'); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } fs.writeFileSync(reportPath, html); console.log(`报告已生成: ${reportPath}`); } // 运行所有测试 async runAll(): Promise<void> { console.log('开始 API 自动化测试...\n'); await this.testAuth(); await this.testUserManagement(); await this.testConcurrency(); await this.testErrorHandling(); console.log('\n所有测试完成!'); this.generateReport(); }}// 使用示例async function main() { const testSuite = new ApiTestSuite(); await testSuite.runAll();}main().catch(console.error);```### 1.3 使用 Supertest 进行 API 测试```typescript// tests/api.test.tsimport request from 'supertest';import express from 'express';import { expect } from 'chai';// 模拟 Express 应用const app = express();app.use(express.json());// 模拟用户数据const users: any[] = [ { id: 1, name: '张三', email: 'zhangsan@example.com' }, { id: 2, name: '李四', email: 'lisi@example.com' },];// API 路由app.get('/api/users', (req, res) => { res.json({ code: 200, data: users });});app.get('/api/users/:id', (req, res) => { const user = users.find(u => u.id === parseInt(req.params.id)); if (user) { res.json({ code: 200, data: user }); } else { res.status(404).json({ code: 404, message: '用户不存在' }); }});app.post('/api/users', (req, res) => { const newUser = { id: users.length + 1, ...req.body }; users.push(newUser); res.status(201).json({ code: 201, data: newUser });});app.put('/api/users/:id', (req, res) => { const index = users.findIndex(u => u.id === parseInt(req.params.id)); if (index !== -1) { users[index] = { ...users[index], ...req.body }; res.json({ code: 200, data: users[index] }); } else { res.status(404).json({ code: 404, message: '用户不存在' }); }});app.delete('/api/users/:id', (req, res) => { const index = users.findIndex(u => u.id === parseInt(req.params.id)); if (index !== -1) { users.splice(index, 1); res.json({ code: 200, message: '删除成功' }); } else { res.status(404).json({ code: 404, message: '用户不存在' }); }});// 测试套件describe('User API 自动化测试', () => { // 获取用户列表 it('应该返回用户列表', async () => { const response = await request(app) .get('/api/users') .expect(200); expect(response.body.code).to.equal(200); expect(response.body.data).to.be.an('array'); expect(response.body.data).to.have.lengthOf(2); }); // 获取单个用户 it('应该返回指定用户', async () => { const response = await request(app) .get('/api/users/1') .expect(200); expect(response.body.code).to.equal(200); expect(response.body.data.name).to.equal('张三'); }); // 创建用户 it('应该成功创建用户', async () => { const newUser = { name: '王五', email: 'wangwu@example.com' }; const response = await request(app) .post('/api/users') .send(newUser) .expect(201); expect(response.body.code).to.equal(201); expect(response.body.data.name).to.equal('王五'); }); // 更新用户 it('应该成功更新用户', async () => { const updates = { name: '赵六' }; const response = await request(app) .put('/api/users/1') .send(updates) .expect(200); expect(response.body.code).to.equal(200); expect(response.body.data.name).to.equal('赵六'); }); // 删除用户 it('应该成功删除用户', async () => { await request(app) .delete('/api/users/2') .expect(200); const response = await request(app) .get('/api/users/2') .expect(404); }); // 错误处理 it('应该返回 404 对于不存在的用户', async () => { await request(app) .get('/api/users/999') .expect(404); }); // 参数验证 it('应该验证必填字段', async () => { const response = await request(app) .post('/api/users') .send({ name: '测试' }) // 缺少 email .expect(400); // 假设有参数验证中间件 }); // 并发请求 it('应该处理并发请求', async () => { const requests = [ request(app).get('/api/users').expect(200), request(app).get('/api/users').expect(200), request(app).get('/api/users').expect(200), ]; await Promise.all(requests); });});```---## 场景二:数据爬取和处理### 2.1 使用 Cheerio 爬取静态网页```typescript// src/crawler/web-crawler.tsimport * as cheerio from 'cheerio';import axios from 'axios';import * as fs from 'fs';import * as path from 'path';// 爬取的数据类型interface Article { title: string; url: string; author: string; date: string; summary: string;}// 网页爬虫类class WebCrawler { private results: Article[] = []; // 爬取网页 async crawl(url: string): Promise<Article[]> { console.log(`正在爬取: ${url}`); try { const response = await axios.get(url, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', }, }); const $ = cheerio.load(response.data); const articles: Article[] = []; // 解析文章列表 $('.article').each((index, element) => { const $element = $(element); const article: Article = { title: $element.find('h2').text().trim(), url: new URL($element.find('a').attr('href') || '', url).href, author: $element.find('.author').text().trim(), date: $element.find('.date').text().trim(), summary: $element.find('.summary').text().trim(), }; articles.push(article); }); this.results = articles; console.log(`爬取完成,共 ${articles.length} 篇文章`); return articles; } catch (error) { console.error('爬取失败:', error); throw error; } } // 批量爬取多个页面 async crawlMultiple(urls: string[]): Promise<Article[]> { console.log(`开始爬取 ${urls.length} 个页面...`); const allArticles: Article[] = []; for (let i = 0; i < urls.length; i++) { try { const articles = await this.crawl(urls[i]); allArticles.push(...articles); // 延迟,避免请求过快 if (i < urls.length - 1) { await this.sleep(1000); } } catch (error) { console.error(`爬取 ${urls[i]} 失败:`, error); } } console.log(`批量爬取完成,共 ${allArticles.length} 篇文章`); return allArticles; } // 延迟函数 private sleep(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } // 保存结果到 JSON saveToJson(filePath: string): void { fs.writeFileSync( filePath, JSON.stringify(this.results, null, 2), 'utf-8' ); console.log(`数据已保存到: ${filePath}`); } // 保存结果到 CSV saveToCsv(filePath: string): void { const header = ['title', 'url', 'author', 'date', 'summary'].join(','); const rows = this.results.map(article => [ `"${article.title.replace(/"/g, '""')}"`, `"${article.url}"`, `"${article.author}"`, `"${article.date}"`, `"${article.summary.replace(/"/g, '""')}"` ].join(',') ); const csv = [header, ...rows].join('\n'); fs.writeFileSync(filePath, csv, 'utf-8'); console.log(`CSV 已保存到: ${filePath}`); }}// 使用示例async function main() { const crawler = new WebCrawler(); // 爬取单个页面 const articles = await crawler.crawl('https://example.com/articles'); // 保存结果 const outputDir = path.join(__dirname, '../../output'); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } crawler.saveToJson(path.join(outputDir, 'articles.json')); crawler.saveToCsv(path.join(outputDir, 'articles.csv')); // 批量爬取 const urls = [ 'https://example.com/articles?page=1', 'https://example.com/articles?page=2', 'https://example.com/articles?page=3', ]; const allArticles = await crawler.crawlMultiple(urls);}main().catch(console.error);```### 2.2 使用 Puppeteer 爬取动态网页```typescript// src/crawler/dynamic-crawler.tsimport puppeteer from 'puppeteer';import * as fs from 'fs';import * as path from 'path';interface Product { name: string; price: string; rating: string; reviews: number; imageUrl: string;}// 动态网页爬虫class DynamicCrawler { async crawlProductPage(url: string): Promise<Product[]> { console.log(`启动浏览器,访问: ${url}`); const browser = await puppeteer.launch({ headless: false, // 显示浏览器,方便调试 }); const page = await browser.newPage(); // 设置视口 await page.setViewport({ width: 1920, height: 1080 }); // 拦截请求(可选) await page.setRequestInterception(true); page.on('request', (request) => { if (['image', 'font'].includes(request.resourceType())) { request.abort(); } else { request.continue(); } }); try { // 导航到页面 await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000, }); // 滚动到底部,加载更多内容 await page.evaluate(() => { window.scrollTo(0, document.body.scrollHeight); }); // 等待动态内容加载 await page.waitForTimeout(2000); // 提取产品数据 const products = await page.evaluate(() => { const items = document.querySelectorAll('.product-card'); const data: Product[] = []; items.forEach((item) => { const nameElement = item.querySelector('.product-name'); const priceElement = item.querySelector('.product-price'); const ratingElement = item.querySelector('.rating'); const reviewsElement = item.querySelector('.reviews-count'); const imageElement = item.querySelector('img'); if (nameElement && priceElement) { data.push({ name: nameElement.textContent?.trim() || '', price: priceElement.textContent?.trim() || '', rating: ratingElement?.getAttribute('data-rating') || '', reviews: parseInt(reviewsElement?.textContent || '0'), imageUrl: imageElement?.src || '', }); } }); return data; }); console.log(`提取到 ${products.length} 个产品`); return products; } catch (error) { console.error('爬取失败:', error); throw error; } finally { await browser.close(); } } // 截图页面 async screenshotPage(url: string, outputPath: string): Promise<void> { const browser = await puppeteer.launch(); const page = await browser.newPage(); try { await page.goto(url, { waitUntil: 'networkidle2' }); await page.screenshot({ path: outputPath, fullPage: true, }); console.log(`截图已保存: ${outputPath}`); } finally { await browser.close(); } } // 生成 PDF async generatePdf(url: string, outputPath: string): Promise<void> { const browser = await puppeteer.launch(); const page = await browser.newPage(); try { await page.goto(url, { waitUntil: 'networkidle2' }); await page.pdf({ path: outputPath, format: 'A4', printBackground: true, }); console.log(`PDF 已生成: ${outputPath}`); } finally { await browser.close(); } } // 自动填写表单并提交 async fillAndSubmitForm( url: string, formData: Record<string, string> ): Promise<void> { const browser = await puppeteer.launch({ headless: false }); const page = await browser.newPage(); try { await page.goto(url); // 填写表单 for (const [selector, value] of Object.entries(formData)) { await page.fill(selector, value); await page.waitForTimeout(500); } // 提交表单 await page.click('button[type="submit"]'); // 等待响应 await page.waitForNavigation({ waitUntil: 'networkidle0' }); console.log('表单提交成功'); } catch (error) { console.error('表单操作失败:', error); throw error; } finally { await browser.close(); } }}// 使用示例async function main() { const crawler = new DynamicCrawler(); const outputDir = path.join(__dirname, '../../output'); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } // 爬取产品页面 const products = await crawler.crawlProductPage('https://example.com/products'); // 保存产品数据 fs.writeFileSync( path.join(outputDir, 'products.json'), JSON.stringify(products, null, 2) ); // 截图页面 await crawler.screenshotPage( 'https://example.com/products', path.join(outputDir, 'screenshot.png') ); // 生成 PDF await crawler.generatePdf( 'https://example.com/products', path.join(outputDir, 'page.pdf') ); // 自动填写表单 await crawler.fillAndSubmitForm('https://example.com/contact', { '#name': '张三', '#email': 'zhangsan@example.com', '#message': '这是测试消息', });}main().catch(console.error);```---## 场景三:文件系统自动化### 3.1 文件管理器```typescript// src/file/file-manager.tsimport * as fs from 'fs';import * as path from 'path';// 文件操作工具类class FileManager { // ========== 读取文件 ========== // 读取文本文件 readTextFile(filePath: string): string { if (!fs.existsSync(filePath)) { throw new Error(`文件不存在: ${filePath}`); } return fs.readFileSync(filePath, 'utf-8'); } // 读取 JSON 文件 readJsonFile<T>(filePath: string): T { const content = this.readTextFile(filePath); return JSON.parse(content); } // 读取目录中的所有文件 readDirectory(dirPath: string): string[] { if (!fs.existsSync(dirPath)) { throw new Error(`目录不存在: ${dirPath}`); } return fs.readdirSync(dirPath); } // 递归读取目录 readDirectoryRecursive(dirPath: string): string[] { const files: string[] = []; const read = (dir: string) => { const items = fs.readdirSync(dir); items.forEach(item => { const fullPath = path.join(dir, item); const stat = fs.statSync(fullPath); if (stat.isDirectory()) { read(fullPath); } else { files.push(fullPath); } }); }; read(dirPath); return files; } // ========== 写入文件 ========== // 写入文本文件 writeTextFile(filePath: string, content: string): void { const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(filePath, content, 'utf-8'); console.log(`文件已写入: ${filePath}`); } // 写入 JSON 文件 writeJsonFile(filePath: string, data: any): void { const content = JSON.stringify(data, null, 2); this.writeTextFile(filePath, content); } // 追加内容到文件 appendFile(filePath: string, content: string): void { const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.appendFileSync(filePath, content + '\n', 'utf-8'); } // ========== 文件操作 ========== // 复制文件 copyFile(srcPath: string, destPath: string): void { if (!fs.existsSync(srcPath)) { throw new Error(`源文件不存在: ${srcPath}`); } const destDir = path.dirname(destPath); if (!fs.existsSync(destDir)) { fs.mkdirSync(destDir, { recursive: true }); } fs.copyFileSync(srcPath, destPath); console.log(`文件已复制: ${srcPath} -> ${destPath}`); } // 移动文件 moveFile(srcPath: string, destPath: string): void { if (!fs.existsSync(srcPath)) { throw new Error(`源文件不存在: ${srcPath}`); } const destDir = path.dirname(destPath); if (!fs.existsSync(destDir)) { fs.mkdirSync(destDir, { recursive: true }); } fs.renameSync(srcPath, destPath); console.log(`文件已移动: ${srcPath} -> ${destPath}`); } // 删除文件 deleteFile(filePath: string): void { if (!fs.existsSync(filePath)) { throw new Error(`文件不存在: ${filePath}`); } fs.unlinkSync(filePath); console.log(`文件已删除: ${filePath}`); } // ========== 目录操作 ========== // 创建目录 createDirectory(dirPath: string): void { if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); console.log(`目录已创建: ${dirPath}`); } else { console.log(`目录已存在: ${dirPath}`); } } // 删除目录 deleteDirectory(dirPath: string): void { if (!fs.existsSync(dirPath)) { throw new Error(`目录不存在: ${dirPath}`); } fs.rmSync(dirPath, { recursive: true, force: true }); console.log(`目录已删除: ${dirPath}`); } // 清空目录 clearDirectory(dirPath: string): void { if (!fs.existsSync(dirPath)) { throw new Error(`目录不存在: ${dirPath}`); } const files = fs.readdirSync(dirPath); files.forEach(file => { const filePath = path.join(dirPath, file); const stat = fs.statSync(filePath); if (stat.isDirectory()) { this.deleteDirectory(filePath); } else { this.deleteFile(filePath); } }); console.log(`目录已清空: ${dirPath}`); } // ========== 文件信息 ========== // 获取文件信息 getFileInfo(filePath: string) { if (!fs.existsSync(filePath)) { throw new Error(`文件不存在: ${filePath}`); } const stats = fs.statSync(filePath); return { path: filePath, size: stats.size, created: stats.birthtime, modified: stats.mtime, isDirectory: stats.isDirectory(), isFile: stats.isFile(), }; } // 获取目录大小 getDirectorySize(dirPath: string): number { let totalSize = 0; const calculate = (dir: string) => { const files = fs.readdirSync(dir); files.forEach(file => { const filePath = path.join(dir, file); const stats = fs.statSync(filePath); if (stats.isDirectory()) { calculate(filePath); } else { totalSize += stats.size; } }); }; calculate(dirPath); return totalSize; } // 格式化文件大小 formatFileSize(bytes: number): string { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]; } // ========== 文件搜索 ========== // 按名称搜索文件 searchByName(dirPath: string, pattern: string): string[] { const files: string[] = []; const regex = new RegExp(pattern, 'i'); const search = (dir: string) => { const items = fs.readdirSync(dir); items.forEach(item => { const fullPath = path.join(dir, item); const stat = fs.statSync(fullPath); if (stat.isDirectory()) { search(fullPath); } else if (regex.test(item)) { files.push(fullPath); } }); }; search(dirPath); return files; } // 按扩展名搜索文件 searchByExtension(dirPath: string, extension: string): string[] { const files: string[] = []; const search = (dir: string) => { const items = fs.readdirSync(dir); items.forEach(item => { const fullPath = path.join(dir, item); const stat = fs.statSync(fullPath); if (stat.isDirectory()) { search(fullPath); } else if (item.endsWith(extension)) { files.push(fullPath); } }); }; search(dirPath); return files; } // 按内容搜索文件 searchByContent(dirPath: string, content: string): string[] { const files: string[] = []; const search = (dir: string) => { const items = fs.readdirSync(dir); items.forEach(item => { const fullPath = path.join(dir, item); const stat = fs.statSync(fullPath); if (stat.isDirectory()) { search(fullPath); } else if (stat.isFile()) { const fileContent = fs.readFileSync(fullPath, 'utf-8'); if (fileContent.includes(content)) { files.push(fullPath); } } }); }; search(dirPath); return files; } // ========== 批量操作 ========== // 批量重命名 batchRename(dirPath: string, oldPattern: string, newPattern: string): void { const files = this.readDirectory(dirPath); let renamed = 0; files.forEach(file => { if (file.includes(oldPattern)) { const oldPath = path.join(dirPath, file); const newFile = file.replace(oldPattern, newPattern); const newPath = path.join(dirPath, newFile); fs.renameSync(oldPath, newPath); renamed++; } }); console.log(`批量重命名完成,共 ${renamed} 个文件`); } // 批量复制 batchCopy(srcFiles: string[], destDir: string): void { if (!fs.existsSync(destDir)) { fs.mkdirSync(destDir, { recursive: true }); } srcFiles.forEach((srcFile, index) => { const destFile = path.join(destDir, path.basename(srcFile)); this.copyFile(srcFile, destFile); }); console.log(`批量复制完成,共 ${srcFiles.length} 个文件`); }}// 使用示例async function main() { const fileManager = new FileManager(); const outputDir = path.join(__dirname, '../../output/file-ops'); // 创建目录 fileManager.createDirectory(outputDir); // 写入测试文件 fileManager.writeJsonFile(path.join(outputDir, 'data.json'), { users: [ { id: 1, name: '张三' }, { id: 2, name: '李四' }, ] }); // 读取文件 const data = fileManager.readJsonFile(path.join(outputDir, 'data.json')); console.log('读取到的数据:', data); // 搜索文件 const jsonFiles = fileManager.searchByExtension(outputDir, '.json'); console.log('JSON 文件:', jsonFiles); // 获取文件信息 const fileInfo = fileManager.getFileInfo(path.join(outputDir, 'data.json')); console.log('文件信息:', { ...fileInfo, size: fileManager.formatFileSize(fileInfo.size) });}main().catch(console.error);```### 3.2 报表生成器```typescript// src/file/report-generator.tsimport ExcelJS from 'exceljs';import * as fs from 'fs';import * as path from 'path';interface ReportData { [key: string]: any;}// Excel 报表生成器class ExcelReportGenerator { private workbook: ExcelJS.Workbook; constructor() { this.workbook = new ExcelJS.Workbook(); } // 创建工作表 createSheet(name: string, data: ReportData[]): void { const worksheet = this.workbook.addWorksheet(name); // 添加标题 if (data.length > 0) { worksheet.columns = Object.keys(data[0]).map(key => ({ header: key, key: key, width: 20, })); } // 添加数据 worksheet.addRows(data); // 样式 worksheet.getRow(1).font = { bold: true }; worksheet.getRow(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF4F81BD' } }; } // 添加图表 async addChart( sheetName: string, type: ExcelJS.ChartType, dataRange: string, title: string ): Promise<void> { const worksheet = this.workbook.getWorksheet(sheetName); if (!worksheet) { throw new Error(`工作表不存在: ${sheetName}`); } const chart = worksheet.addChart(type, { name: title, displayBlanksAs: 'gap', title: { name: title, }, dataRange, }); return chart; } // 保存 Excel 文件 async save(filePath: string): Promise<void> { const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } await this.workbook.xlsx.writeFile(filePath); console.log(`Excel 报表已生成: ${filePath}`); }}// 使用示例async function main() { const generator = new ExcelReportGenerator(); const outputDir = path.join(__dirname, '../../output/reports'); // 生成测试数据 const testData: ReportData[] = [ { 姓名: '张三', 部门: '销售', 销售额: 10000, 绩成率: '120%' }, { 姓名: '李四', 部门: '销售', 销售额: 15000, 绩成率: '150%' }, { 姓名: '王五', 部门: '技术', 销售额: 8000, 绩成率: '95%' }, { 姓名: '赵六', 部门: '技术', 销售额: 12000, 绩成率: '105%' }, ]; // 创建工作表 generator.createSheet('员工绩效', testData); // 保存 await generator.save(path.join(outputDir, 'performance-report.xlsx'));}main().catch(console.error);```---## 场景四:数据库自动化操作### 4.1 数据库连接和 CRUD```typescript// src/database/db-connection.tsimport sqlite3 from 'sqlite3';import { promisify } from 'util';// 数据库类型定义interface User { id?: number; username: string; email: string; password: string; role: string; created_at?: string;}interface Product { id?: number; name: string; price: number; stock: number; category: string;}// SQLite 数据库类class Database { private db: sqlite3.Database; constructor(dbPath: string = './data/database.db') { this.db = new sqlite3.Database(dbPath, (err) => { if (err) { console.error('数据库连接失败:', err); } else { console.log('数据库连接成功'); this.initTables(); } }); } // 初始化表 private initTables(): void { this.db.serialize(() => { // 用户表 this.db.run(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, email TEXT UNIQUE NOT NULL, password TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'user', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); // 产品表 this.db.run(` CREATE TABLE IF NOT EXISTS products ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, price REAL NOT NULL, stock INTEGER NOT NULL DEFAULT 0, category TEXT NOT NULL ) `); // 订单表 this.db.run(` CREATE TABLE IF NOT EXISTS orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, product_id INTEGER NOT NULL, quantity INTEGER NOT NULL, total REAL NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (product_id) REFERENCES products(id) ) `); console.log('数据库表初始化完成'); }); } // 执行查询(Promise 包装) private query<T>(sql: string, params: any[] = []): Promise<T[]> { return new Promise((resolve, reject) => { this.db.all(sql, params, (err, rows) => { if (err) { reject(err); } else { resolve(rows as T[]); } }); }); } private run(sql: string, params: any[] = []): Promise<sqlite3.RunResult> { return new Promise((resolve, reject) => { this.db.run(sql, params, function(err) { if (err) { reject(err); } else { resolve(this); } }); }); } // ========== 用户操作 ========== // 创建用户 async createUser(user: User): Promise<number> { const result = await this.run( 'INSERT INTO users (username, email, password, role) VALUES (?, ?, ?, ?)', [user.username, user.email, user.password, user.role || 'user'] ); console.log(`用户创建成功,ID: ${result.lastID}`); return result.lastID; } // 获取用户 async getUser(id: number): Promise<User | null> { const users = await this.query<User>('SELECT * FROM users WHERE id = ?', [id]); return users[0] || null; } // 获取所有用户 async getAllUsers(): Promise<User[]> { return this.query<User>('SELECT * FROM users ORDER BY created_at DESC'); } // 更新用户 async updateUser(id: number, updates: Partial<User>): Promise<boolean> { const fields = Object.keys(updates); const values = Object.values(updates); const setClause = fields.map(f => `${f} = ?`).join(', '); const result = await this.run( `UPDATE users SET ${setClause} WHERE id = ?`, [...values, id] ); console.log(`用户更新成功: ${result.changes} 行受影响`); return result.changes > 0; } // 删除用户 async deleteUser(id: number): Promise<boolean> { const result = await this.run('DELETE FROM users WHERE id = ?', [id]); console.log(`用户删除成功: ${result.changes} 行受影响`); return result.changes > 0; } // ========== 产品操作 ========== // 创建产品 async createProduct(product: Product): Promise<number> { const result = await this.run( 'INSERT INTO products (name, price, stock, category) VALUES (?, ?, ?, ?)', [product.name, product.price, product.stock, product.category] ); console.log(`产品创建成功,ID: ${result.lastID}`); return result.lastID; } // 获取产品 async getProduct(id: number): Promise<Product | null> { const products = await this.query<Product>('SELECT * FROM products WHERE id = ?', [id]); return products[0] || null; } // 按分类获取产品 async getProductsByCategory(category: string): Promise<Product[]> { return this.query<Product>('SELECT * FROM products WHERE category = ?', [category]); } // 更新库存 async updateStock(productId: number, quantity: number): Promise<boolean> { const result = await this.run( 'UPDATE products SET stock = stock + ? WHERE id = ?', [quantity, productId] ); console.log(`库存更新成功: ${result.changes} 行受影响`); return result.changes > 0; } // ========== 统计查询 ========== // 获取用户统计 async getUserStats() Promise<any> { const totalUsers = await this.query<any>('SELECT COUNT(*) as count FROM users'); const usersByRole = await this.query<any>('SELECT role, COUNT(*) as count FROM users GROUP BY role'); return { total: totalUsers[0]?.count || 0, byRole: usersByRole, }; } // 获取产品统计 async getProductStats(): Promise<any> { const totalProducts = await this.query<any>('SELECT COUNT(*) as count FROM products'); const byCategory = await this.query<any>('SELECT category, COUNT(*) as count FROM products GROUP BY category'); const lowStock = await this.query<any>('SELECT * FROM products WHERE stock < 10'); return { total: totalProducts[0]?.count || 0, byCategory, lowStock, }; } // ========== 批量操作 ========== // 批量导入数据 async batchImportUsers(users: User[]): Promise<number> { let successCount = 0; for (const user of users) { try { await this.createUser(user); successCount++; } catch (error) { console.error(`导入用户失败: ${user.username}`, error); } } console.log(`批量导入完成,成功: ${successCount}/${users.length}`); return successCount; } // 清空表 async clearTable(tableName: string): Promise<void> { await this.run(`DELETE FROM ${tableName}`); console.log(`表 ${tableName} 已清空`); } // 关闭数据库 close(): void { this.db.close(); console.log('数据库连接已关闭'); }}// 使用示例async function main() { const db = new Database(); try { // 创建用户 const userId1 = await db.createUser({ username: 'zhangsan', email: 'zhangsan@example.com', password: 'hashed_password', role: 'user', }); const userId2 = await db.createUser({ username: 'admin', email: 'admin@example.com', password: 'hashed_password', role: 'admin', }); // 创建产品 await db.createProduct({ name: '商品1', price: 99.99, stock: 100, category: '电子产品', }); // 获取统计 const userStats = await db.getUserStats(); console.log('用户统计:', userStats); const productStats = await db.getProductStats(); console.log('产品统计:', productStats); // 批量导入 const newUsers: User[] = [ { username: 'user1', email: 'user1@example.com', password: 'pass1', role: 'user' }, { username: 'user2', email: 'user2@example.com', password: 'pass2', role: 'user' }, { username: 'user3', email: 'user3@example.com', password: 'pass3', role: 'user' }, ]; await db.batchImportUsers(newUsers); } finally { db.close(); }}main().catch(console.error);```---## 场景五:定时任务和调度```typescript// src/scheduler/jobs.tsimport cron from 'node-cron';import * as fs from 'fs';import * as path from 'path';// 定时任务管理器class TaskScheduler { private tasks: Map<string, cron.ScheduledTask> = new Map(); // ========== 数据备份任务 ========== // 定时备份数据库 startDatabaseBackup(): void { const task = cron.schedule('0 2 * * *', async () => { console.log('开始数据库备份...'); const sourceDir = './data'; const backupDir = './backups'; const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupPath = path.join(backupDir, `backup-${timestamp}`); // 创建备份目录 if (!fs.existsSync(backupDir)) { fs.mkdirSync(backupDir, { recursive: true }); } // 复制数据库文件 const dbFiles = fs.readdirSync(sourceDir).filter(f => f.endsWith('.db')); for (const file of dbFiles) { fs.copyFileSync( path.join(sourceDir, file), path.join(backupPath, file) ); } console.log(`数据库备份完成: ${backupPath}`); }); this.tasks.set('database-backup', task); } // ========== 数据清理任务 ========== // 定时清理临时文件 startTempFileCleanup(): void { const task = cron.schedule('0 3 * * *', async () => { console.log('开始清理临时文件...'); const tempDir = './temp'; const retentionDays = 7; const cutoffTime = Date.now() - retentionDays * 24 * 60 * 60 * 1000; if (fs.existsSync(tempDir)) { const files = fs.readdirSync(tempDir); let deletedCount = 0; files.forEach(file => { const filePath = path.join(tempDir, file); const stats = fs.statSync(filePath); if (stats.mtimeMs < cutoffTime) { fs.unlinkSync(filePath); deletedCount++; } }); console.log(`清理完成,删除了 ${deletedCount} 个文件`); } }); this.tasks.set('temp-cleanup', task); } // ========== 数据同步任务 ========== // 定时同步数据 startDataSync(): void { const task = cron.schedule('*/30 * * * *', async () => { console.log('开始数据同步...'); try { // 模拟数据同步逻辑 const response = await fetch('https://api.example.com/sync'); const data = await response.json(); // 保存同步的数据 const syncDir = './data/sync'; if (!fs.existsSync(syncDir)) { fs.mkdirSync(syncDir, { recursive: true }); } const timestamp = new Date().toISOString(); fs.writeFileSync( path.join(syncDir, `sync-${timestamp}.json`), JSON.stringify(data, null, 2) ); console.log('数据同步完成'); } catch (error) { console.error('数据同步失败:', error); } }); this.tasks.set('data-sync', task); } // ========== 报表生成任务 ========== // 定时生成日报表 startDailyReport(): void { const task = cron.schedule('0 8 * * *', async () => { console.log('开始生成日报表...'); // 这里可以调用报表生成逻辑 const reportDate = new Date().toISOString().split('T')[0]; console.log(`日报表生成完成: ${reportDate}`); }); this.tasks.set('daily-report', task); } // ========== 健康检查任务 ========== // 定时健康检查 startHealthCheck(): void { const task = cron.schedule('*/5 * * * *', async () => { console.log('执行健康检查...'); const services = [ { name: '数据库', url: 'http://localhost:3000/health/db' }, { name: 'API', url: 'http://localhost:3000/health/api' }, { name: '缓存', url: 'http://localhost:3000/health/cache' }, ]; for (const service of services) { try { const startTime = Date.now(); const response = await fetch(service.url); const duration = Date.now() - startTime; if (response.ok) { console.log(`✅ ${service.name}: 正常 (${duration}ms)`); } else { console.log(`⚠️ ${service.name}: 响应异常 (${response.status})`); // 这里可以发送告警 } } catch (error) { console.log(`❌ ${service.name}: 不可用 (${error})`); // 这里可以发送告警 } } }); this.tasks.set('health-check', task); } // ========== 执行一次的任务 ========== // 立即执行一次备份 async runBackupNow(): Promise<void> { console.log('立即执行数据库备份...'); // 备份逻辑... } // 停止所有任务 stopAll(): void { this.tasks.forEach((task, name) => { task.stop(); console.log(`任务已停止: ${name}`); }); }}// 使用示例async function main() { const scheduler = new TaskScheduler(); // 启动定时任务 scheduler.startDatabaseBackup(); // 每天凌晨 2 点备份 scheduler.startTempFileCleanup(); // 每天凌晨 3 点清理临时文件 scheduler.startDataSync(); // 每 30 分钟同步数据 scheduler.startDailyReport(); // 每天早上 8 点生成报表 scheduler.startHealthCheck(); // 每 5 分钟健康检查 console.log('所有定时任务已启动'); // 监听退出信号 process.on('SIGINT', () => { console.log('\n正在停止所有任务...'); scheduler.stopAll(); process.exit(0); });}main().catch(console.error);```---## 场景六:邮件自动化```typescript// src/email/mailer.tsimport nodemailer from 'nodemailer';import * as fs from 'fs';import * as path from 'path';interface EmailConfig { host: string; port: number; secure: boolean; auth: { user: string; pass: string; };}interface EmailOptions { from: string; to: string | string[]; subject: string; html?: string; text?: string; attachments?: string[];}// 邮件发送器class EmailMailer { private transporter: nodemailer.Transporter; private config: EmailConfig; constructor(config: EmailConfig) { this.config = config; this.transporter = nodemailer.createTransport({ host: config.host, port: config.port, secure: config.secure, auth: config.auth, }); } // 发送简单文本邮件 async sendTextEmail(options: EmailOptions): Promise<void> { const mailOptions = { from: options.from, to: options.to, subject: options.subject, text: options.text || '', }; await this.transporter.sendMail(mailOptions); console.log(`文本邮件已发送: ${options.subject}`); } // 发送 HTML 邮件 async sendHtmlEmail(options: EmailOptions): Promise<void> { const mailOptions = { from: options.from, to: options.to, subject: options.subject, html: options.html || '', }; await this.transporter.sendMail(mailOptions); console.log(`HTML 邮件已发送: ${options.subject}`); } // 发送带附件的邮件 async sendEmailWithAttachments(options: EmailOptions): Promise<void> { const attachments = (options.attachments || []).map(file => ({ filename: path.basename(file), path: file, })); const mailOptions = { from: options.from, to: options.to, subject: options.subject, html: options.html || '', text: options.text || '', attachments, }; await this.transporter.sendMail(mailOptions); console.log(`带附件的邮件已发送: ${options.subject}`); } // 批量发送邮件 async sendBatchEmail(recipients: string[], subject: string, html: string): Promise<void> { const batchSize = 10; const batches: string[][] = []; for (let i = 0; i < recipients.length; i += batchSize) { batches.push(recipients.slice(i, i + batchSize)); } console.log(`准备发送邮件,共 ${recipients.length} 个收件人,分 ${batches.length} 批`); for (let i = 0; i < batches.length; i++) { await this.sendHtmlEmail({ from: 'noreply@example.com', to: batches[i], subject, html, }); console.log(`已发送第 ${i + 1}/${batches.length} 批`); // 延迟,避免被邮件服务器限制 if (i < batches.length - 1) { await new Promise(resolve => setTimeout(resolve, 5000)); } } console.log('所有邮件发送完成'); } // 生成 HTML 邮件模板 generateHtmlEmail(template: string, data: any): string { const templates: Record<string, string> = { 'welcome': ` <h1>欢迎,${data.name}!</h1> <p>感谢您注册我们的服务。</p> <p>您的账户已激活,可以开始使用了。</p> <p>如有问题,请联系我们。</p> `, 'alert': ` <h1>系统告警:${data.title}</h1> <p><strong>时间:</strong>${data.time}</p> <p><strong>级别:</strong>${data.level}</p> <p><strong>详情:</strong>${data.message}</p> `, 'report': ` <h1>${data.reportTitle}</h1> <p><strong>生成时间:</strong>${data.generateTime}</p> <hr> <h2>数据概览</h2> <ul> ${data.summary.map((item: any) => `<li>${item.key}: ${item.value}</li>`).join('')} </ul> `, }; return templates[template] || ''; } // 发送报告邮件 async sendReportEmail( recipients: string[], reportData: any ): Promise<void> { const html = this.generateHtmlEmail('report', { reportTitle: '系统运行报告', generateTime: new Date().toLocaleString(), summary: reportData, }); await this.sendBatchEmail(recipients, '系统运行报告', html); }}// 使用示例async function main() { const config: EmailConfig = { host: 'smtp.example.com', port: 587, secure: false, auth: { user: 'your-email@example.com', pass: 'your-password', }, }; const mailer = new EmailMailer(config); // 发送欢迎邮件 await mailer.sendHtmlEmail({ from: 'noreply@example.com', to: 'user@example.com', subject: '欢迎注册', html: mailer.generateHtmlEmail('welcome', { name: '张三', }), }); // 发送告警邮件 await mailer.sendHtmlEmail({ from: 'alert@example.com', to: 'admin@example.com', subject: '系统告警', html: mailer.generateHtmlEmail('alert', { title: '数据库连接失败', level: 'ERROR', time: new Date().toLocaleString(), message: '数据库连接超时,请及时处理', }), }); // 发送报告邮件 await mailer.sendReportEmail(['admin@example.com'], { reportTitle: '系统运行报告', generateTime: new Date().toLocaleString(), summary: [ { key: '活跃用户数', value: '100' }, { key: '今日订单数', value: '50' }, { key: '错误数', value: '5' }, ], });}main().catch(console.error);```---## 场景七:CI/CD 自动化### 7.1 GitHub Actions 工作流```yaml# .github/workflows/ci-cd.ymlname: CI/CD Pipelineon: push: branches: [ main, develop ] pull_request: branches: [ main ]jobs: # ========== 测试阶段 ========== test: runs-on: ubuntu-latest steps: - name: 检出代码 uses: actions/checkout@v3 - name: 设置 Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: 安装依赖 run: npm ci - name: 运行测试 run: npm test - name: 代码覆盖率 run: npm run test:coverage - name: 上传覆盖率 uses: codecov/codecov-action@v3 with: files: ./coverage/lcov.info # ========== 构建阶段 ========== build: needs: test runs-on: ubuntu-latest steps: - name: 检出代码 uses: actions/checkout@v3 - name: 设置 Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: 安装依赖 run: npm ci - name: 构建项目 run: npm run build - name: 上传构建产物 uses: actions/upload-artifact@v3 with: name: build-artifacts path: dist/ # ========== 部署阶段 ========== deploy: needs: build runs-on: ubuntu-latest steps: - name: 检出代码 uses: actions/checkout@v3 - name: 部署到服务器 uses: appleboy/ssh-action@master with: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SERVER_USER }} key: ${{ secrets.SSH_KEY }} script: | cd /var/www/app git pull origin main npm ci npm run build pm2 restart app```### 7.2 自动化部署脚本```typescript// src/ci-cd/deploy.tsimport * as fs from 'fs';import * as path from 'path';interface DeployConfig { appName: string; buildDir: string; remoteDir: string; host: string; port: number; username: string; keyPath: string;}// 自动化部署器class AutoDeployer { private config: DeployConfig; constructor(config: DeployConfig) { this.config = config; } // 检查部署前置条件 async preDeployCheck(): Promise<boolean> { console.log('执行部署前置检查...'); // 检查构建目录是否存在 if (!fs.existsSync(this.config.buildDir)) { console.error('构建目录不存在,请先执行构建'); return false; } // 检查必要的文件 const requiredFiles = ['package.json', 'main.js']; for (const file of requiredFiles) { const filePath = path.join(this.config.buildDir, file); if (!fs.existsSync(filePath)) { console.error(`缺少必需文件: ${file}`); return false; } } console.log('✅ 前置检查通过'); return true; } // 生成部署清单 generateDeploymentChecklist(): string { const checklist = `部署检查清单=============应用信息--------应用名称: ${this.config.appName}构建目录: ${this.config.buildDir}部署目录: ${this.config.remoteDir}服务器: ${this.config.host}:${this.config.port}文件检查--------${fs.existsSync(path.join(this.config.buildDir, 'package.json')) ? '✅' : '❌'} package.json${fs.existsSync(path.join(this.config.buildDir, 'main.js')) ? '✅' : '❌'} main.js${fs.existsSync(path.join(this.config.buildDir, 'assets')) ? '✅' : '❌'} assets/部署时间--------时间: ${new Date().toLocaleString()} `; return checklist; } // 生成部署日志 generateDeploymentLog(): void { const logDir = './logs/deployments'; const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const logFile = path.join(logDir, `deploy-${timestamp}.log`); if (!fs.existsSync(logDir)) { fs.mkdirSync(logDir, { recursive: true }); } const logContent = `部署开始时间: ${new Date().toLocaleString()}\n`; fs.writeFileSync(logFile, logContent, 'utf-8'); console.log(`部署日志: ${logFile}`); } // 生成部署回滚脚本 generateRollbackScript(): void { const rollbackScript = `#!/bin/bash# 自动生成的回滚脚本APP_NAME="${this.config.appName}"BACKUP_DIR="/var/backups/${this.config.appName}"CURRENT_VERSION=$(date +%Y%m%d_%H%M%S)echo "开始回滚..."# 备份当前版本cd /var/www/${this.config.appName}tar -czf ${BACKUP_DIR}/${APP_NAME}-${CURRENT_VERSION}.tar.gz .# 回滚到上一版本PREV_VERSION=$(ls -t ${BACKUP_DIR} | head -2 | tail -1)tar -xzf ${BACKUP_DIR}/${PREV_VERSION} -C /var/www/${this.config.appName}echo "回滚完成: ${PREV_VERSION}" `; const scriptPath = './scripts/rollback.sh'; const scriptDir = path.dirname(scriptPath); if (!fs.existsSync(scriptDir)) { fs.mkdirSync(scriptDir, { recursive: true }); } fs.writeFileSync(scriptPath, rollbackScript, 'utf-8'); console.log(`回滚脚本已生成: ${scriptPath}`); } // 执行部署 async deploy(): Promise<void> { console.log('开始部署流程...'); // 前置检查 const checkPassed = await this.preDeployCheck(); if (!checkPassed) { throw new Error('前置检查失败,取消部署'); } // 生成部署文件 this.generateDeploymentChecklist(); this.generateDeploymentLog(); this.generateRollbackScript(); console.log('部署准备完成,请确认后执行部署命令'); console.log(`部署命令:---------1. 压缩构建产物 tar -czf deploy.tar.gz -C ${this.config.buildDir} .2. 上传到服务器 scp deploy.tar.gz ${this.config.username}@${this.config.host}:/tmp/3. 解压并部署 ssh ${this.config.username}@${this.config.host} cd /tmp tar -xzf deploy.tar.gz -C ${this.config.remoteDir} pm2 restart ${this.config.appName}4. 验证部署 curl http://${this.config.host}:${this.config.port}/health `); }}// 使用示例async function main() { const config: DeployConfig = { appName: 'my-app', buildDir: './dist', remoteDir: '/var/www/my-app', host: 'example.com', port: 3000, username: 'deploy-user', keyPath: '~/.ssh/id_rsa', }; const deployer = new AutoDeployer(config); await deployer.deploy();}main().catch(console.error);```---## 场景八:服务器监控和日志```typescript// src/monitor/logger.tsimport winston from 'winston';import * as fs from 'fs';import * as path from 'path';interface LogConfig { level: string; logDir: string; maxFiles: number; maxSize: string;}// 日志记录器class Logger { private logger: winston.Logger; constructor(config: LogConfig) { // 确保日志目录存在 if (!fs.existsSync(config.logDir)) { fs.mkdirSync(config.logDir, { recursive: true }); } this.logger = winston.createLogger({ level: config.level, format: winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.printf(({ timestamp, level, message, ...meta }) => { return `[${timestamp}] [${level.toUpperCase()}] ${message} ${ meta ? JSON.stringify(meta) : '' }`; }) ), transports: [ // 控制台输出 new winston.transports.Console({ format: winston.format.simple(), }), // 文件输出 - 所有日志 new winston.transports.File({ filename: path.join(config.logDir, 'combined.log'), maxsize: config.maxSize, maxFiles: config.maxFiles, }), // 文件输出 - 错误日志 new winston.transports.File({ filename: path.join(config.logDir, 'error.log'), level: 'error', maxsize: config.maxSize, maxFiles: config.maxFiles, }), ], }); } // 日志方法 info(message: string, meta?: any): void { this.logger.info(message, meta); } error(message: string, error?: Error): void { this.logger.error(message, error); } warn(message: string, meta?: any): void { this.logger.warn(message, meta); } debug(message: string, meta?: any): void { this.logger.debug(message, meta); }}// 使用示例const logger = new Logger({ level: 'info', logDir: './logs', maxFiles: 30, maxSize: '10m',});logger.info('应用启动');logger.error('数据库连接失败', new Error('Connection timeout'));logger.warn('内存使用率高', { memory: '90%' });```### 8.2 系统监控```typescript// src/monitor/system-monitor.tsimport os from 'os';import * as fs from 'fs';import { Logger } from './logger';const logger = new Logger({ level: 'info', logDir: './logs', maxFiles: 30, maxSize: '10m',});interface SystemMetrics { timestamp: string; cpu: { usage: number; loadAvg: number[]; }; memory: { total: number; used: number; free: number; usage: number; }; disk: { total: number; used: number; free: number; usage: number; }; uptime: number;}// 系统监控器class SystemMonitor { private metrics: SystemMetrics[] = []; private maxMetrics: number = 100; // 获取 CPU 使用率 getCpuUsage(): number { const cpus = os.cpus(); let totalIdle = 0; let totalTick = 0; cpus.forEach(cpu => { for (const type in cpu.times) { totalIdle += cpu.times[type]; totalTick += cpu.times[type]; } }); return ((totalTick - totalIdle) / totalTick) * 100; } // 获取系统负载 getCpuLoadAvg(): number[] { return os.loadavg(); } // 获取内存使用率 getMemoryUsage() { const total = os.totalmem(); const free = os.freemem(); const used = total - free; return { total, used, free, usage: (used / total) * 100, }; } // 获取磁盘使用率 getDiskUsage(path: string) { const stats = fs.statSync(path); const used = stats.size; return { total: used, // 简化处理 used, free: 0, usage: 100, }; } // 获取系统运行时间 getUptime(): number { return os.uptime(); } // 收集所有指标 collectMetrics(): SystemMetrics { const metrics: SystemMetrics = { timestamp: new Date().toISOString(), cpu: { usage: this.getCpuUsage(), loadAvg: this.getCpuLoadAvg(), }, memory: this.getMemoryUsage(), disk: this.getDiskUsage('/'), uptime: this.getUptime(), }; this.metrics.push(metrics); // 保持最近的数据 if (this.metrics.length > this.maxMetrics) { this.metrics.shift(); } return metrics; } // 检查是否触发告警 checkAlerts(metrics: SystemMetrics): string[] { const alerts: string[] = []; // CPU 告警 if (metrics.cpu.usage > 80) { alerts.push(`CPU 使用率过高: ${metrics.cpu.usage.toFixed(2)}%`); } // 内存告警 if (metrics.memory.usage > 85) { alerts.push(`内存使用率过高: ${metrics.memory.usage.toFixed(2)}%`); } // 系统负载告警 if (metrics.cpu.loadAvg[0] > 2.0) { alerts.push(`系统负载过高: ${metrics.cpu.loadAvg[0].toFixed(2)}`); } return alerts; } // 生成监控报告 generateReport(): void { const avgCpuUsage = this.metrics.reduce((sum, m) => sum + m.cpu.usage, 0) / this.metrics.length; const avgMemoryUsage = this.metrics.reduce((sum, m) => sum + m.memory.usage, 0) / this.metrics.length; const report = `系统监控报告==================时间范围: ${this.metrics[0]?.timestamp} ~ ${this.metrics[this.metrics.length - 1]?.timestamp}CPU 使用率----------平均值: ${avgCpuUsage.toFixed(2)}%峰值: ${Math.max(...this.metrics.map(m => m.cpu.usage)).toFixed(2)}%内存使用率----------平均值: ${avgMemoryUsage.toFixed(2)}%峰值: ${Math.max(...this.metrics.map(m => m.memory.usage)).toFixed(2)}%告警统计----------${this.metrics.filter(m => this.checkAlerts(m).length > 0).length} 条告警 `; logger.info('系统监控报告', { report }); const outputDir = './output/monitor'; if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } fs.writeFileSync( path.join(outputDir, 'system-report.txt'), report, 'utf-8' ); } // 启动监控 startMonitoring(interval: number = 5000): void { logger.info('系统监控已启动'); setInterval(() => { const metrics = this.collectMetrics(); logger.info('系统指标', { cpu: `${metrics.cpu.usage.toFixed(2)}%`, memory: `${metrics.memory.usage.toFixed(2)}%`, uptime: `${(metrics.uptime / 60).toFixed(1)}min`, }); const alerts = this.checkAlerts(metrics); alerts.forEach(alert => logger.warn(alert)); }, interval); }}// 使用示例async function main() { const monitor = new SystemMonitor(); // 立即收集一次指标 const metrics = monitor.collectMetrics(); console.log('当前系统指标:', metrics); // 启动持续监控(每 5 秒) monitor.startMonitoring(5000); // 每分钟生成报告 setInterval(() => { monitor.generateReport(); }, 60000);}main().catch(console.error);```---## 对比总结### JavaScript vs TypeScript vs Python 在后端自动化中的选择| 场景 | JavaScript | TypeScript | Python | 推荐 ||------|-----------|-------------|---------|------|| **简单脚本** | ✅ 足够 | ⚠️ 可能过度 | ✅ 非常适合 | Python || **API 测试** | ⚠️ 类型不明确 | ✅ 类型安全 | ✅ pytest生态 | Python || **数据库操作** | ⚠️ 容易出错 | ✅ 类型安全 | ✅ SQLAlchemy等 | Python || **文件处理** | ✅ 快速 | ⚠️ 增加复杂度 | ✅ 内置强大 | Python || **爬虫** | ✅ 快速原型 | ⚠️ 可选 | ✅ Scrapy/requests | Python || **定时任务** | ⚠️ 逻辑复杂 | ✅ 类型安全 | ✅ APScheduler | Python || **邮件发送** | ✅ 简单够用 | ⚠️ 收益不大 | ✅ smtplib | Python || **CI/CD** | ✅ 配置简单 | ✅ 类型安全 | ⚠️ 通常用JS | JavaScript || **监控系统** | ⚠️ 复杂逻辑 | ✅ 类型安全 | ✅ Prometheus等 | Python || **数据科学/分析** | ❌ 不适合 | ❌ 不适合 | ✅ Pandas/NumPy | Python || **前端相关** | ✅ 语言统一 | ✅ 类型安全 | ❌ 需桥接 | TypeScript || **Web自动化** | ✅ Playwright | ✅ Playwright | ✅ Selenium | TypeScript || **并发性能** | ⚠️ Node限制 | ⚠️ Node限制 | ⚠️ GIL限制 | 视场景 || **学习曲线** | ⚠️ 中等 | ❌ 较陡 | ✅ 简单 | Python |### 详细对比分析#### JavaScript**优势:**- 与前端代码语言统一- npm 生态系统庞大- 异步编程模型适合 I/O 密集型任务- Playwright/Puppeteer 等工具原生支持**劣势:**- 类型系统薄弱,容易出错- 数据处理库不如 Python 丰富- 数值计算性能不如 Python**适用场景:**- 需要与前端代码集成的自动化- 快速原型和简单脚本- CI/CD 配置脚本- Web 自动化(Playwright)#### TypeScript**优势:**- 类型安全减少运行时错误- 接口和类型定义让代码更清晰- 更好的 IDE 支持- 适合大型项目维护**劣势:**- 编译步骤增加复杂度- 学习曲线较陡- 对简单任务可能过度设计**适用场景:**- 复杂的自动化测试框架- 需要长期维护的项目- 团队协作开发- 企业级自动化系统#### Python**优势:**- 语法简洁易学- 生态极其丰富(requests, pytest, pandas, numpy)- 数据处理和分析能力强- 测试框架成熟(pytest, unittest)- 科学计算和机器学习生态**劣势:**- 运行速度相对较慢- 与前端集成需要额外桥接- Web 自动化工具(Selenium)不如 Playwright 优秀- 全局解释器锁(GIL)限制多线程性能**适用场景:**- API 测试和集成测试- 数据爬取和处理- 数据分析和报表生成- 复杂的测试场景(pytest 生态)- 需要数据科学支持的项目- 数据库操作和 ORM### 选择建议**使用 JavaScript 的情况:**- 快速脚本和原型(简单场景)- 文件操作自动化- 爬虫原型验证- CI/CD 配置脚本**使用 TypeScript 的情况:**- 复杂的 API 测试框架- 需要与前端代码统一语言- 数据库操作和 ORM- 定时任务调度系统- 监控和告警系统- 长期维护的企业项目**使用 Python 的情况:**- API 测试(pytest + requests)- 数据爬取和处理- 数据分析和报表- 数据库操作(SQLAlchemy)- 需要数据科学支持- 测试框架开发- 复杂的测试场景### 快速决策指南```问题1:项目是否涉及前端自动化测试? └─ 是 → 推荐 TypeScript (Playwright) └─ 否 → 继续问题2问题2:是否需要大量数据处理或分析? └─ 是 → 推荐 Python └─ 否 → 继续问题3问题3:项目复杂度和维护周期? └─ 简单/短期 → JavaScript 或 Python └─ 复杂/长期 → TypeScript问题4:团队技术栈偏好? └─ 前端团队 → TypeScript/JavaScript └─ 后端/测试团队 → Python```