当前位置: 首页 > news >正文

JavaScript学习教程,从入门到精通, JavaScript 函数全面解析与案例实践(11)

JavaScript 函数全面解析与案例实践

项目导读

JavaScript 函数是编程中的核心概念,是执行特定任务的代码块。本教程将全面讲解函数的定义、参数、返回值及调用方式,并通过实际案例加深理解。

学习目标

  1. 掌握 JavaScript 函数的定义与调用方法
  2. 理解函数参数的不同形式和使用场景
  3. 熟悉函数返回值的应用
  4. 能够编写实用的函数解决实际问题

素质目标

  1. 培养模块化编程思维
  2. 提升代码复用意识
  3. 增强问题分解能力

一、函数的定义

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);
}

案例代码解析:

  1. 参数处理

    • 必需参数:modelquantity
    • 可选参数:discount有默认值0
    • 使用对象存储价格表,便于扩展
  2. 输入验证

    • 检查手机型号是否存在
    • 检查购买数量是否为正整数
    • 检查折扣率是否在合理范围内
  3. 计算逻辑

    • 计算原始总价和折后总价
    • 返回包含详细信息的对象
  4. 错误处理

    • 使用try-catch捕获可能的错误
    • 提供清晰的错误信息
  5. 文档注释

    • 使用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操作。每个案例都遵循了以下原则:

  1. 清晰的函数职责划分
  2. 完善的参数验证和错误处理
  3. 详细的代码注释
  4. 实际业务场景的模拟
  5. 可复用的代码结构

相关文章:

  • 浏览器与网络模块
  • 大模型——mcp-ui基于MCP协议的简洁AI聊天界面
  • Transformer模型的自注意机制原理、作用、优缺点,通俗易懂
  • qt mapFrom返回的QPoint和event->pos()区别和globalPos区别
  • Python爬虫第12节-解析库Beautiful Soup的使用下篇
  • AF3 ProteinDataset类的_get_masked_sequence方法解读
  • Linux Kernel 1
  • gazebo 启动卡死的解决方法汇总
  • transformers的 pipeline是什么:将模型加载、数据预处理、推理等步骤进行了封装
  • Linux下Docker安装超详细教程(以CentOS为例)
  • transformer 规范化层
  • Linux 进程基础(一):冯诺依曼结构
  • Java设计模式实战:策略模式在SimUDuck问题中的应用
  • 使用Fortran读取HDF5数据
  • 若依前后端分离版运行教程、打包教程、部署教程
  • Linux-内核驱动
  • Window 10使用WSL2搭建Linux版Android Studio应用开发环境
  • Redis集群模式学习
  • Kubernetes nodeName Manual Scheduling practice (K8S节点名称绑定以及手工调度)
  • 【高性能缓存Redis_中间件】一、快速上手redis缓存中间件
  • 用自己电脑配置服务器做网站/网络营销的网站建设
  • 做家政建网站/网站制作平台
  • 南京网站建设优化/上海牛巨仁seo
  • 网站源码下载地址是什么/外贸推广引流
  • 扬州网站建设制作/网站内部优化有哪些内容
  • 网站建设项目中标通知/长沙有实力的关键词优化价格