JavaScript学习教程,从入门到精通, JavaScript 函数全面解析与案例实践(11)
JavaScript 函数全面解析与案例实践
项目导读
JavaScript 函数是编程中的核心概念,是执行特定任务的代码块。本教程将全面讲解函数的定义、参数、返回值及调用方式,并通过实际案例加深理解。
学习目标
- 掌握 JavaScript 函数的定义与调用方法
- 理解函数参数的不同形式和使用场景
- 熟悉函数返回值的应用
- 能够编写实用的函数解决实际问题
素质目标
- 培养模块化编程思维
- 提升代码复用意识
- 增强问题分解能力
一、函数的定义
1. 函数声明
// 函数声明 (具有函数提升特性)
function functionName(parameters) {
// 函数体
return value; // 可选
}
2. 函数表达式
// 函数表达式 (无函数提升)
const functionName = function(parameters) {
// 函数体
};
3. 箭头函数 (ES6)
// 箭头函数
const functionName = (parameters) => {
// 函数体
};
// 单行箭头函数可省略大括号和return
const square = x => x * x;
二、函数参数
1. 形参与实参
function greet(name) { // name是形参
console.log(`Hello, ${name}!`);
}
greet('Alice'); // 'Alice'是实参
2. 默认参数 (ES6)
function createUser(name, role = 'user') {
console.log(`Name: ${name}, Role: ${role}`);
}
createUser('Bob'); // Name: Bob, Role: user
createUser('Alice', 'admin'); // Name: Alice, Role: admin
3. 剩余参数 (…rest)
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
三、函数返回值
1. 基本返回值
function add(a, b) {
return a + b; // 返回两数之和
}
const result = add(3, 5); // 8
2. 多返回值的处理
function getSize(width, height, depth) {
const area = width * height;
const volume = width * height * depth;
return [area, volume]; // 返回数组
}
const [area, volume] = getSize(2, 3, 4); // 解构赋值
3. 无返回值函数
function logMessage(message) {
console.log(`[LOG]: ${message}`);
// 无return语句,默认返回undefined
}
const result = logMessage('Test'); // 输出日志,result为undefined
四、函数调用
1. 直接调用
function sayHello() {
console.log('Hello!');
}
sayHello(); // 直接调用
2. 方法调用
const calculator = {
add: function(a, b) {
return a + b;
}
};
calculator.add(2, 3); // 5
3. 构造函数调用
function Person(name) {
this.name = name;
}
const person = new Person('Alice'); // 构造函数调用
4. 通过call/apply调用
function introduce(lang1, lang2) {
console.log(`My name is ${this.name}, I know ${lang1} and ${lang2}`);
}
const person = { name: 'Bob' };
introduce.call(person, 'JavaScript', 'Python');
introduce.apply(person, ['Java', 'C++']);
【示例】获取手机价格 - 综合案例
/**
* 计算手机总价
* @param {string} model - 手机型号
* @param {number} quantity - 购买数量
* @param {number} [discount=0] - 折扣率(0-1)
* @returns {object} 包含原始总价和折后总价的对象
*/
function calculatePhonePrice(model, quantity, discount = 0) {
// 手机价格表
const priceList = {
'iPhone15': 7999,
'GalaxyS23': 6999,
'Mi14': 3999,
'P60': 5999
};
// 检查型号是否有效
if (!priceList.hasOwnProperty(model)) {
throw new Error(`未知的手机型号: ${model}`);
}
// 检查数量是否有效
if (quantity <= 0 || !Number.isInteger(quantity)) {
throw new Error('购买数量必须是正整数');
}
// 检查折扣是否有效
if (discount < 0 || discount > 1) {
throw new Error('折扣率必须在0-1之间');
}
// 计算价格
const unitPrice = priceList[model];
const originalTotal = unitPrice * quantity;
const discountedTotal = originalTotal * (1 - discount);
// 返回结果对象
return {
model,
unitPrice,
quantity,
originalTotal,
discountRate: discount,
discountedTotal,
savings: originalTotal - discountedTotal
};
}
// 使用示例
try {
const order1 = calculatePhonePrice('iPhone15', 2);
console.log('订单1(无折扣):', order1);
const order2 = calculatePhonePrice('Mi14', 3, 0.1);
console.log('订单2(9折):', order2);
// 测试错误情况
// const order3 = calculatePhonePrice('Unknown', 1); // 会抛出错误
// const order4 = calculatePhonePrice('GalaxyS23', -2); // 会抛出错误
} catch (error) {
console.error('下单失败:', error.message);
}
案例代码解析:
-
参数处理:
- 必需参数:
model
和quantity
- 可选参数:
discount
有默认值0 - 使用对象存储价格表,便于扩展
- 必需参数:
-
输入验证:
- 检查手机型号是否存在
- 检查购买数量是否为正整数
- 检查折扣率是否在合理范围内
-
计算逻辑:
- 计算原始总价和折后总价
- 返回包含详细信息的对象
-
错误处理:
- 使用try-catch捕获可能的错误
- 提供清晰的错误信息
-
文档注释:
- 使用JSDoc标注参数和返回值类型
- 增强代码可读性和可维护性
总结
JavaScript函数是构建复杂应用的基石,掌握函数的各种定义方式、参数处理技巧和返回值应用,能够显著提高代码质量和开发效率。通过实际的手机价格计算案例,我们展示了如何将理论知识转化为实用代码,包括参数验证、业务逻辑和错误处理等关键技能。
案例代码
以下是 5 个具有实际开发价值的 JavaScript 函数案例,涵盖常见业务场景,每个案例都包含详细注释和实现思路。
案例 1:表单验证工具函数
/**
* 表单验证工具
* @param {Object} formData - 表单数据对象 {field1: value1, field2: value2}
* @param {Object} rules - 验证规则 {field1: [rule1, rule2], field2: [rule1]}
* @returns {Object} 验证结果 {isValid: Boolean, errors: {field1: '错误信息'}}
*/
function validateForm(formData, rules) {
const errors = {};
let isValid = true;
// 内置验证规则
const validators = {
required: (value) => !!value || '必填字段',
email: (value) => /.+@.+\..+/.test(value) || '邮箱格式不正确',
minLength: (min) => (value) =>
value.length >= min || `至少需要${min}个字符`,
phone: (value) => /^1[3-9]\d{9}$/.test(value) || '手机号格式不正确'
};
Object.keys(rules).forEach(field => {
const value = formData[field];
rules[field].forEach(rule => {
// 处理字符串规则如 'required'
if (typeof rule === 'string') {
if (!validators[rule](value)) {
errors[field] = validators[rule](value);
isValid = false;
}
}
// 处理对象规则如 {minLength: 6}
else if (typeof rule === 'object') {
const [ruleName, param] = Object.entries(rule)[0];
if (!validators[ruleName](param)(value)) {
errors[field] = validators[ruleName](param)(value);
isValid = false;
}
}
});
});
return { isValid, errors };
}
// 使用示例
const form = {
username: 'test',
email: 'test@',
phone: '13812345678',
password: '123'
};
const rules = {
username: ['required', { minLength: 4 }],
email: ['required', 'email'],
phone: ['required', 'phone'],
password: ['required', { minLength: 6 }]
};
const result = validateForm(form, rules);
console.log(result);
/*
{
isValid: false,
errors: {
username: "至少需要4个字符",
email: "邮箱格式不正确",
password: "至少需要6个字符"
}
}
*/
案例 2:API 请求封装
/**
* 封装fetch请求
* @param {string} url - 请求地址
* @param {string} method - 请求方法(GET/POST等)
* @param {Object} data - 请求数据
* @param {Object} headers - 自定义请求头
* @param {number} timeout - 超时时间(毫秒)
* @returns {Promise} 包含响应数据的Promise
*/
async function request({
url,
method = 'GET',
data = {},
headers = {},
timeout = 5000
}) {
// 处理GET请求的URL参数
if (method === 'GET') {
const params = new URLSearchParams(data).toString();
url = params ? `${url}?${params}` : url;
}
// 创建AbortController用于超时控制
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeout);
try {
const options = {
method,
headers: {
'Content-Type': 'application/json',
...headers
},
signal: controller.signal
};
// POST请求添加请求体
if (method !== 'GET') {
options.body = JSON.stringify(data);
}
const response = await fetch(url, options);
clearTimeout(timer);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
}
return await response.text();
} catch (error) {
clearTimeout(timer);
if (error.name === 'AbortError') {
throw new Error(`请求超时 (${timeout}ms)`);
}
throw error;
}
}
// 使用示例
async function fetchUserData() {
try {
// GET请求
const users = await request({
url: 'https://api.example.com/users',
params: { page: 1, limit: 10 }
});
// POST请求
const newUser = await request({
url: 'https://api.example.com/users',
method: 'POST',
data: { name: 'John', age: 30 }
});
console.log('Users:', users);
console.log('New user:', newUser);
} catch (error) {
console.error('请求失败:', error.message);
}
}
fetchUserData();
案例 3:购物车计算函数
/**
* 计算购物车总价
* @param {Array} cartItems - 购物车商品数组
* @param {Object} discount - 折扣信息
* @returns {Object} 计算结果
*/
function calculateCartTotal(cartItems, discount = { type: 'none', value: 0 }) {
// 计算小计
const subtotal = cartItems.reduce((sum, item) => {
return sum + (item.price * item.quantity);
}, 0);
// 计算折扣
let discountAmount = 0;
switch (discount.type) {
case 'percentage':
discountAmount = subtotal * (discount.value / 100);
break;
case 'fixed':
discountAmount = Math.min(discount.value, subtotal);
break;
case 'coupon':
// 假设优惠券逻辑更复杂,这里简化处理
discountAmount = subtotal >= 100 ? 20 : 0;
break;
}
// 计算运费 (满100免运费)
const shippingFee = subtotal - discountAmount >= 100 ? 0 : 10;
// 计算总价
const total = subtotal - discountAmount + shippingFee;
return {
subtotal: formatCurrency(subtotal),
discount: formatCurrency(discountAmount),
shippingFee: formatCurrency(shippingFee),
total: formatCurrency(total),
breakdown: {
subtotalRaw: subtotal,
discountRaw: discountAmount,
shippingFeeRaw: shippingFee,
totalRaw: total
}
};
}
// 辅助函数:格式化货币
function formatCurrency(amount) {
return '$' + amount.toFixed(2);
}
// 使用示例
const cartItems = [
{ id: 1, name: 'T-Shirt', price: 25.99, quantity: 2 },
{ id: 2, name: 'Jeans', price: 49.99, quantity: 1 },
{ id: 3, name: 'Socks', price: 8.5, quantity: 3 }
];
// 测试不同折扣场景
console.log('无折扣:', calculateCartTotal(cartItems));
console.log('8折优惠:', calculateCartTotal(cartItems, { type: 'percentage', value: 20 }));
console.log('满减优惠:', calculateCartTotal(cartItems, { type: 'fixed', value: 15 }));
console.log('优惠券:', calculateCartTotal(cartItems, { type: 'coupon' }));
案例 4:数据缓存函数
/**
* 带缓存功能的异步数据获取
* @param {string} key - 缓存键
* @param {Function} fetchFn - 数据获取函数(返回Promise)
* @param {number} ttl - 缓存有效期(毫秒)
* @returns {Promise} 数据Promise
*/
function createCachedFetch(key, fetchFn, ttl = 60000) {
const cache = {
data: null,
timestamp: 0,
isFetching: false,
queue: []
};
return async function() {
const now = Date.now();
// 缓存有效且存在,直接返回缓存数据
if (cache.data && now - cache.timestamp < ttl) {
return Promise.resolve(cache.data);
}
// 如果正在请求中,加入队列等待结果
if (cache.isFetching) {
return new Promise((resolve) => {
cache.queue.push(resolve);
});
}
// 设置获取状态
cache.isFetching = true;
try {
const data = await fetchFn();
cache.data = data;
cache.timestamp = Date.now();
// 通知所有等待的请求
cache.queue.forEach(resolve => resolve(data));
cache.queue = [];
return data;
} catch (error) {
// 出错时清除获取状态,但不更新缓存
cache.isFetching = false;
cache.queue = [];
throw error;
} finally {
cache.isFetching = false;
}
};
}
// 使用示例
// 模拟API请求函数
async function fetchUserData() {
console.log('实际发起API请求');
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 1000));
return { id: 1, name: 'John Doe', timestamp: Date.now() };
}
// 创建带缓存的函数
const cachedFetchUser = createCachedFetch('userData', fetchUserData, 5000);
// 测试
async function test() {
// 第一次调用会发起实际请求
console.log('第一次调用:', await cachedFetchUser());
// 5秒内再次调用会使用缓存
console.log('第二次调用(立即):', await cachedFetchUser());
// 模拟6秒后再次调用
await new Promise(resolve => setTimeout(resolve, 6000));
console.log('第三次调用(6秒后):', await cachedFetchUser());
// 测试并发请求
console.log('并发测试:');
await Promise.all([
cachedFetchUser().then(data => console.log('请求1:', data)),
cachedFetchUser().then(data => console.log('请求2:', data)),
cachedFetchUser().then(data => console.log('请求3:', data))
]);
}
test();
案例 5:DOM 操作工具函数
/**
* DOM操作工具集
*/
const dom = {
/**
* 创建元素
* @param {string} tag - 标签名
* @param {Object} attributes - 属性对象
* @param {Array|string} children - 子元素或文本
* @returns {HTMLElement} 创建的元素
*/
create(tag, attributes = {}, children = []) {
const el = document.createElement(tag);
// 设置属性
for (const [key, value] of Object.entries(attributes)) {
if (key === 'className') {
el.className = value;
} else if (key === 'style' && typeof value === 'object') {
Object.assign(el.style, value);
} else if (key.startsWith('on') && typeof value === 'function') {
el.addEventListener(key.substring(2), value);
} else {
el.setAttribute(key, value);
}
}
// 添加子元素
if (typeof children === 'string') {
el.textContent = children;
} else if (Array.isArray(children)) {
children.forEach(child => {
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else if (child instanceof HTMLElement) {
el.appendChild(child);
}
});
}
return el;
},
/**
* 查找元素
* @param {string} selector - CSS选择器
* @param {HTMLElement} parent - 父元素(默认document)
* @returns {HTMLElement|null} 找到的元素
*/
find(selector, parent = document) {
return parent.querySelector(selector);
},
/**
* 查找所有匹配元素
* @param {string} selector - CSS选择器
* @param {HTMLElement} parent - 父元素(默认document)
* @returns {NodeList} 元素列表
*/
findAll(selector, parent = document) {
return parent.querySelectorAll(selector);
},
/**
* 添加事件委托
* @param {string} event - 事件类型
* @param {string} selector - 目标元素选择器
* @param {Function} handler - 事件处理函数
* @param {HTMLElement} parent - 委托父元素(默认document)
*/
delegate(event, selector, handler, parent = document) {
parent.addEventListener(event, (e) => {
let target = e.target;
while (target && target !== parent) {
if (target.matches(selector)) {
handler.call(target, e);
break;
}
target = target.parentNode;
}
});
},
/**
* 切换元素显示/隐藏
* @param {HTMLElement} element - 目标元素
* @param {boolean} [show] - 强制显示或隐藏(可选)
*/
toggle(element, show) {
if (typeof show === 'boolean') {
element.style.display = show ? '' : 'none';
} else {
element.style.display = element.style.display === 'none' ? '' : 'none';
}
}
};
// 使用示例
document.addEventListener('DOMContentLoaded', () => {
// 创建并添加元素
const header = dom.create('header', { className: 'main-header' }, [
dom.create('h1', {}, 'DOM工具演示'),
dom.create('nav', {}, [
dom.create('a', { href: '#', className: 'nav-link' }, '首页'),
dom.create('a', { href: '#', className: 'nav-link' }, '产品'),
dom.create('a', { href: '#', className: 'nav-link' }, '关于')
])
]);
document.body.appendChild(header);
// 添加事件委托
dom.delegate('click', '.nav-link', function(e) {
e.preventDefault();
console.log('导航点击:', this.textContent);
dom.toggle(this); // 点击后隐藏该链接
});
// 查找元素
const firstLink = dom.find('.nav-link');
console.log('第一个链接:', firstLink);
// 切换显示
setTimeout(() => {
dom.toggle(firstLink);
}, 2000);
});
这些案例涵盖了前端开发中的常见场景,包括表单验证、API请求、购物车逻辑、数据缓存和DOM操作。每个案例都遵循了以下原则:
- 清晰的函数职责划分
- 完善的参数验证和错误处理
- 详细的代码注释
- 实际业务场景的模拟
- 可复用的代码结构