刚接触编程时,我经常把同样的代码复制粘贴到各处。直到有一天,我需要修改一个计算逻辑,结果发现这个逻辑在项目里出现了几十次——那天我加班到凌晨三点。这就是函数存在的意义:它让我们能把代码封装成可重复使用的模块。
想象一下,如果你每次喝水都要重新发明杯子,那得多累?函数就是编程世界里的"杯子",一次定义,无限次使用。在JavaScript中,函数不仅能避免代码重复,还能让程序结构更清晰。比如计算商品价格的代码,如果不用函数,你可能要在购物车、订单、支付等十几个地方重复写相同的计算逻辑。
实际开发中,一个中等规模的Web应用通常包含300-500个函数调用。没有函数,代码维护将是一场噩梦。
在JS中,我最常用的是函数声明式,这是最传统的方式:
javascript复制function greet(name) {
return `Hello, ${name}!`;
}
但现代JS项目中,箭头函数越来越流行,特别是在React等框架中:
javascript复制const greet = (name) => `Hello, ${name}!`;
还有一种不太推荐的方式是函数表达式:
javascript复制const greet = function(name) {
return `Hello, ${name}!`;
};
这三种方式的主要区别在于:
让我们封装一个计算商品折扣的函数:
javascript复制function calculateDiscount(price, discountRate = 0.1) {
if (price <= 0) throw new Error('价格必须大于0');
if (discountRate < 0 || discountRate > 1) {
throw new Error('折扣率必须在0-1之间');
}
return price * (1 - discountRate);
}
这个函数展示了几个最佳实践:
初学者常被要求写累加函数,这里有个优化版本:
javascript复制function sumToN(n) {
if (typeof n !== 'number' || n < 1) {
throw new Error('请输入大于0的整数');
}
return (n * (n + 1)) / 2; // 数学公式替代循环
}
这个实现:
形参(parameters)是函数定义时声明的变量,实参(arguments)是调用时传入的值。JS的传参机制很灵活:
javascript复制function logUser(name, age, country = 'China') {
console.log(`姓名:${name}, 年龄:${age}, 国家:${country}`);
}
logUser('张三', 25); // 使用默认国家
logUser('李四', 30, 'USA'); // 覆盖默认值
实际项目中,建议为所有可选参数设置默认值,这样可以避免undefined导致的意外行为。
JS对参数数量非常宽容:
javascript复制function test(a, b) {
console.log(a, b);
}
test(1); // 1 undefined
test(1, 2, 3); // 1 2 (多余的参数被忽略)
这种灵活性有利有弊:
建议的解决方案:
return不只是返回结果,它还能控制函数流程:
javascript复制function login(username, password) {
if (!username) return; // 提前退出
if (!password) return false; // 返回特定值
// 验证逻辑...
return { token: 'xxxx', expires: 3600 };
}
return result;javascript复制const double = x => x * 2; // 自动返回计算结果
javascript复制function Person(name) {
this.name = name;
return 123; // 会被忽略,仍然返回新对象
}
虽然arguments类似数组,但它没有数组方法。现代JS推荐使用剩余参数:
javascript复制function maxNumber(...numbers) {
if (numbers.length === 0) return NaN;
return Math.max(...numbers);
}
maxNumber(3, 1, 4, 1, 5, 9); // 9
剩余参数的优势:
把多个小函数组合成更复杂的功能:
javascript复制const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const add5 = x => x + 5;
const double = x => x * 2;
const square = x => x * x;
const transform = compose(square, double, add5);
transform(2); // ((2 + 5) * 2)^2 = 196
缓存函数结果提升性能:
javascript复制function memoize(fn) {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
}
const factorial = memoize(n =>
n <= 1 ? 1 : n * factorial(n - 1));
把多参数函数转化为一系列单参数函数:
javascript复制function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return (...moreArgs) =>
curried.apply(this, args.concat(moreArgs));
};
}
const add = curry((a, b, c) => a + b + c);
add(1)(2)(3); // 6
javascript复制// 方法1:使用reverse()
function reverseArray(arr) {
return [...arr].reverse(); // 避免修改原数组
}
// 方法2:reduce实现
function reverseArray(arr) {
return arr.reduce((acc, val) => [val, ...acc], []);
}
// 方法3:for循环
function reverseArray(arr) {
const result = [];
for (let i = arr.length - 1; i >= 0; i--) {
result.push(arr[i]);
}
return result;
}
javascript复制function bubbleSort(arr, compareFn = (a, b) => a - b) {
let swapped;
do {
swapped = false;
for (let i = 0; i < arr.length - 1; i++) {
if (compareFn(arr[i], arr[i + 1]) > 0) {
[arr[i], arr[i + 1]] = [arr[i + 1], arr[i]];
swapped = true;
}
}
} while (swapped);
return arr;
}
优化点:
javascript复制function isLeapYear(year) {
if (typeof year !== 'number' || !Number.isInteger(year)) {
throw new Error('请输入整数年份');
}
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
}
常见误区:
javascript复制function fetchData(url, onSuccess, onError) {
fetch(url)
.then(response => {
if (!response.ok) throw new Error('请求失败');
return response.json();
})
.then(onSuccess)
.catch(onError);
}
fetchData(
'/api/user',
data => console.log('获取成功:', data),
err => console.error('错误:', err.message)
);
javascript复制function createCounter(initial = 0) {
let count = initial;
return {
increment() { return ++count; },
decrement() { return --count; },
get() { return count; },
reset() { count = initial; }
};
}
const counter = createCounter(5);
counter.increment(); // 6
counter.get(); // 6
计算某年某月的天数:
javascript复制function getDaysInMonth(year, month) {
if (month < 1 || month > 12) throw new Error('月份无效');
return new Date(year, month, 0).getDate();
}
function getFebruaryDays(year) {
return getDaysInMonth(year, 2);
}
这个实现:
关键区别:
javascript复制// 函数声明 - 会提升
console.log(sum(1, 2)); // 可以正常调用
function sum(a, b) { return a + b; }
// 函数表达式 - 不会提升
console.log(multiply(1, 2)); // 报错
const multiply = function(a, b) { return a * b; };
箭头函数不只是语法糖,它有重要特性:
javascript复制const obj = {
value: 42,
getValue: function() {
return this.value;
},
getValueArrow: () => {
return this.value; // undefined,因为this指向外层
}
};
javascript复制/**
* 计算两个数的和
* @param {number} a - 第一个加数
* @param {number} b - 第二个加数
* @returns {number} 两数之和
* @throws {TypeError} 当参数不是数字时抛出
*/
function add(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('参数必须是数字');
}
return a + b;
}
javascript复制const obj = {
name: 'Object',
printName: function() {
console.log(this.name);
},
printNameArrow: () => {
console.log(this.name);
}
};
obj.printName(); // 'Object'
obj.printNameArrow(); // undefined (严格模式下)
解决方案:
javascript复制function setupHandler() {
const data = getHugeData(); // 大数据
document.getElementById('btn').addEventListener('click', () => {
console.log(data); // data一直被引用无法释放
});
}
解决方法:
javascript复制async function fetchUser(userId) {
try {
const response = await fetch(`/users/${userId}`);
if (!response.ok) throw new Error('请求失败');
return await response.json();
} catch (error) {
console.error('获取用户失败:', error);
throw error; // 重新抛出供上层处理
}
}
避免不必要的函数创建:
javascript复制// 不好:每次渲染都创建新函数
<button onClick={() => doSomething(id)}>Click</button>
// 更好:使用useCallback或类方法
const handleClick = useCallback(() => doSomething(id), [id]);
<button onClick={handleClick}>Click</button>
节流与防抖:
javascript复制function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
const handleResize = debounce(() => {
console.log(window.innerWidth);
}, 300);
window.addEventListener('resize', handleResize);
使用Web Workers处理CPU密集型任务
javascript复制function createUser(name, { age = 18, verified = false } = {}) {
return { name, age, verified };
}
javascript复制function join(separator, ...strings) {
return strings.join(separator);
}
join('-', 'a', 'b', 'c'); // 'a-b-c'
javascript复制function renderUser({ name, age, location: { city } }) {
console.log(`${name}, ${age}岁, 来自${city}`);
}
javascript复制function factorial(n, acc = 1) {
if (n <= 1) return acc;
return factorial(n - 1, n * acc); // 尾调用形式
}
javascript复制function map(array, transform) {
const result = [];
for (const item of array) {
result.push(transform(item));
}
return result;
}
map([1, 2, 3], x => x * 2); // [2, 4, 6]
特点:
javascript复制// 不纯
let taxRate = 0.1;
function calculateTax(amount) {
return amount * taxRate; // 依赖外部变量
}
// 纯函数
function calculateTax(amount, rate) {
return amount * rate;
}
javascript复制const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
const add1 = x => x + 1;
const double = x => x * 2;
const square = x => x * x;
const transform = pipe(add1, double, square);
transform(2); // ((2 + 1) * 2)^2 = 36
javascript复制const utils = {
// 节流函数
throttle(fn, delay) {
let lastCall = 0;
return (...args) => {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
return fn(...args);
}
};
},
// 深拷贝
deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
const result = Array.isArray(obj) ? [] : {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = utils.deepClone(obj[key]);
}
}
return result;
},
// 生成随机ID
generateId(length = 8) {
return Math.random().toString(36).substr(2, length);
}
};
javascript复制function testSum() {
console.assert(sum(1, 2) === 3, '1+2应该等于3');
console.assert(sum(-1, 1) === 0, '-1+1应该等于0');
try {
sum('1', 2);
console.error('字符串参数应该抛出错误');
} catch (e) {
console.log('测试通过:成功捕获类型错误');
}
}
javascript复制describe('sum函数测试', () => {
test('正常数字相加', () => {
expect(sum(1, 2)).toBe(3);
});
test('非法输入抛出错误', () => {
expect(() => sum('1', 2)).toThrow(TypeError);
});
});
javascript复制function complexCalculation(a, b) {
debugger; // 执行到这里会暂停
const result = a * b;
// 更多计算...
return result;
}
javascript复制function processData(data) {
console.time('processData');
// 处理数据...
console.log('中间结果:', intermediateResult);
console.timeEnd('processData');
}
javascript复制function expensiveOperation() {
performance.mark('start');
// 耗时操作...
performance.mark('end');
performance.measure('耗时', 'start', 'end');
const measure = performance.getEntriesByName('耗时')[0];
console.log(`耗时: ${measure.duration}ms`);
}
javascript复制function createUser(name, role) {
return {
name,
role,
permissions: getPermissions(role),
lastLogin: null,
login() {
this.lastLogin = new Date();
}
};
}
javascript复制const strategies = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b
};
function calculate(strategy, a, b) {
return strategies[strategy](a, b);
}
javascript复制function createObservable() {
const observers = [];
return {
subscribe(fn) {
observers.push(fn);
return () => {
const index = observers.indexOf(fn);
if (index !== -1) observers.splice(index, 1);
};
},
notify(data) {
observers.forEach(fn => fn(data));
}
};
}
随着项目增长,应该把相关函数组织成模块:
javascript复制// math.js
export function sum(a, b) { return a + b; }
export function multiply(a, b) { return a * b; }
// app.js
import { sum, multiply } from './math.js';
模块化的好处:
函数在面向对象编程中扮演重要角色:
javascript复制class Person {
constructor(name) {
this.name = name;
}
// 类方法本质上也是函数
greet() {
return `Hello, ${this.name}!`;
}
// 静态方法
static createAnonymous() {
return new Person('Anonymous');
}
}
从回调地狱到async/await:
javascript复制// 回调地狱
getUser(id, user => {
getPosts(user, posts => {
getComments(posts[0], comments => {
// 更多嵌套...
});
});
});
// Promise链
getUser(id)
.then(user => getPosts(user))
.then(posts => getComments(posts[0]))
.then(comments => { /* 处理结果 */ })
.catch(error => console.error(error));
// async/await
async function fetchData() {
try {
const user = await getUser(id);
const posts = await getPosts(user);
const comments = await getComments(posts[0]);
// 处理结果
} catch (error) {
console.error(error);
}
}
现代JS开发常混合两种范式:
javascript复制class ShoppingCart {
constructor(items = []) {
this.items = items;
}
// 面向对象方法
addItem(item) {
return new ShoppingCart([...this.items, item]);
}
// 函数式方法
mapItems(fn) {
return new ShoppingCart(this.items.map(fn));
}
}
javascript复制// 优化前:每次调用都创建新函数
function processItems(items) {
return items.map(item => expensiveOperation(item));
}
// 优化后:预先定义函数
const processItem = item => expensiveOperation(item);
function processItems(items) {
return items.map(processItem);
}
javascript复制function getUserRole(user) {
if (!user.roleCache) {
user.roleCache = calculateRole(user);
}
return user.roleCache;
}
javascript复制// 不好:创建了不必要的闭包
function setup() {
const data = getData();
return function() {
console.log(data);
};
}
// 更好:直接传递参数
function createLogger(data) {
return function() {
console.log(data);
};
}
javascript复制// 传统方式
const result = exclaim(capitalize(trim(' hello world ')));
// 管道提案
const result = ' hello world '
|> trim
|> capitalize
|> exclaim;
javascript复制function handleResponse(response) {
return match(response) {
case { status: 200, data } => processData(data),
case { status: 404 } => 'Not found',
case { status: 500 } => 'Server error',
case _ => 'Unknown error'
};
}
javascript复制function safeMerge(target, source) {
return Object.keys(source).reduce((obj, key) => {
if (key !== '__proto__') {
obj[key] = source[key];
}
return obj;
}, { ...target });
}
javascript复制function safeEval(code) {
return new Function('"use strict";' + code)();
}
javascript复制function executeQuery(query) {
if (typeof query !== 'string') {
throw new Error('查询必须是字符串');
}
if (query.length > 1000) {
throw new Error('查询过长');
}
// 执行查询...
}
javascript复制/**
* 计算两个向量的点积
* @param {number[]} v1 - 第一个向量
* @param {number[]} v2 - 第二个向量
* @returns {number} 点积结果
* @throws {Error} 当向量长度不一致时抛出
*/
function dotProduct(v1, v2) {
if (v1.length !== v2.length) {
throw new Error('向量长度必须相同');
}
return v1.reduce((sum, val, i) => sum + val * v2[i], 0);
}
typescript复制interface User {
id: number;
name: string;
}
function getUserName(user: User): string {
return user.name;
}
Don't Repeat Yourself的典型应用:
javascript复制// 重复代码
function calculateCircleArea(r) {
return Math.PI * r * r;
}
function calculateCircleCircumference(r) {
return 2 * Math.PI * r;
}
// DRY版本
function calculateCircle(r, formula) {
return formula(r);
}
const area = r => Math.PI * r * r;
const circumference = r => 2 * Math.PI * r;
现代React大量使用函数组件:
jsx复制function Counter({ initial = 0 }) {
const [count, setCount] = useState(initial);
const increment = useCallback(() => {
setCount(c => c + 1);
}, []);
return (
<div>
<p>当前计数: {count}</p>
<button onClick={increment}>增加</button>
</div>
);
}
掌握JavaScript函数需要不断实践和思考。我建议:
记住,好的函数就像好的故事:有明确的开始(输入)、发展(处理)和结局(输出),并且能独立存在而不依赖上下文。