函数和方法
概述
函数在 JavaScript 和 Node.js 中是一等公民。本章介绍高级函数模式、闭包、高阶函数、函数式编程概念以及用于构建健壮服务端应用程序的 Node.js 特定函数模式。
函数基础
函数声明和表达式
javascript
// function-basics.js
// Function declaration (hoisted)
function greet(name) {
return `Hello, ${name}!`;
}
// Function expression (not hoisted)
const farewell = function(name) {
return `Goodbye, ${name}!`;
};
// Arrow function (concise syntax)
const welcome = (name) => `Welcome, ${name}!`;
// Arrow function with block body
const processUser = (user) => {
const processed = {
...user,
fullName: `${user.firstName} ${user.lastName}`,
processedAt: new Date()
};
return processed;
};
// Immediately Invoked Function Expression (IIFE)
const config = (function() {
const privateKey = 'secret-key';
return {
getKey: () => privateKey,
encrypt: (data) => `encrypted-${data}-${privateKey}`
};
})();
console.log(greet('Alice'));
console.log(farewell('Bob'));
console.log(welcome('Charlie'));
console.log(config.encrypt('data'));函数参数和参数传递
javascript
// function-parameters.js
// Default parameters
function createUser(name, role = 'user', isActive = true) {
return {
name,
role,
isActive,
createdAt: new Date()
};
}
// Rest parameters
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
// Destructuring parameters
function updateUser({ id, name, email, ...otherProps }) {
return {
id,
name: name || 'Unknown',
email: email || 'no-email@example.com',
...otherProps,
updatedAt: new Date()
};
}
// Function with options object
function fetchData(url, options = {}) {
const {
method = 'GET',
headers = {},
timeout = 5000,
retries = 3,
...otherOptions
} = options;
return {
url,
method,
headers,
timeout,
retries,
...otherOptions
};
}
// Usage examples
console.log(createUser('John'));
console.log(createUser('Jane', 'admin'));
console.log(sum(1, 2, 3, 4, 5));
console.log(updateUser({ id: 1, name: 'Alice', age: 30 }));
console.log(fetchData('/api/users', { method: 'POST', timeout: 10000 }));高阶函数
函数作为参数
javascript
// higher-order-functions.js
// Function that accepts other functions
function processArray(array, processor) {
return array.map(processor);
}
// Function that returns a function
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
// Function composition
function compose(...functions) {
return function(value) {
return functions.reduceRight((acc, fn) => fn(acc), value);
};
}
// Pipe function (left-to-right composition)
function pipe(...functions) {
return function(value) {
return functions.reduce((acc, fn) => fn(acc), value);
};
}
// Utility functions
const double = x => x * 2;
const addTen = x => x + 10;
const square = x => x * x;
// Usage examples
const numbers = [1, 2, 3, 4, 5];
const doubleAll = processArray(numbers, double);
console.log('Doubled:', doubleAll);
const multiplyByThree = createMultiplier(3);
console.log('3 * 7 =', multiplyByThree(7));
// Function composition
const transform = compose(square, addTen, double);
console.log('Transform 5:', transform(5)); // ((5 * 2) + 10)^2 = 400
const pipeline = pipe(double, addTen, square);
console.log('Pipeline 5:', pipeline(5)); // ((5 * 2) + 10)^2 = 400函数式编程模式
javascript
// functional-patterns.js
// Currying
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...nextArgs) {
return curried.apply(this, args.concat(nextArgs));
};
}
};
}
// Partial application
function partial(fn, ...presetArgs) {
return function(...laterArgs) {
return fn(...presetArgs, ...laterArgs);
};
}
// Memoization
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log('Cache hit for:', key);
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
console.log('Cache miss for:', key);
return result;
};
}
// Debounce
function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}
// Throttle
function throttle(fn, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Usage examples
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log('Curried add:', curriedAdd(1)(2)(3));
const addFive = partial(add, 5);
console.log('Partial add:', addFive(3, 2));
// Expensive function for memoization
const fibonacci = memoize(function(n) {
if (n < 2) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});
console.log('Fibonacci 10:', fibonacci(10));
console.log('Fibonacci 10 again:', fibonacci(10)); // Should hit cache
// Debounced function
const debouncedLog = debounce((message) => {
console.log('Debounced:', message);
}, 1000);
debouncedLog('First call');
debouncedLog('Second call'); // Only this will execute after 1 second闭包和作用域
实用闭包示例
javascript
// closures.js
// Module pattern using closures
function createCounter() {
let count = 0;
return {
increment: () => ++count,
decrement: () => --count,
getValue: () => count,
reset: () => { count = 0; return count; }
};
}
// Factory function with private methods
function createUser(name, email) {
let isActive = true;
const createdAt = new Date();
// Private method
function validateEmail(email) {
return email.includes('@') && email.includes('.');
}
return {
getName: () => name,
getEmail: () => email,
setEmail: (newEmail) => {
if (validateEmail(newEmail)) {
email = newEmail;
return true;
}
return false;
},
isUserActive: () => isActive,
activate: () => { isActive = true; },
deactivate: () => { isActive = false; },
getCreatedAt: () => createdAt,
getInfo: () => ({
name,
email,
isActive,
createdAt
})
};
}
// Configuration manager with closures
function createConfig() {
const config = new Map();
const listeners = new Map();
return {
set(key, value) {
const oldValue = config.get(key);
config.set(key, value);
// Notify listeners
if (listeners.has(key)) {
listeners.get(key).forEach(callback => {
callback(value, oldValue);
});
}
},
get(key) {
return config.get(key);
},
subscribe(key, callback) {
if (!listeners.has(key)) {
listeners.set(key, []);
}
listeners.get(key).push(callback);
// Return unsubscribe function
return () => {
const callbacks = listeners.get(key);
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
};
},
getAll() {
return Object.fromEntries(config);
}
};
}
// Usage examples
const counter = createCounter();
console.log('Counter:', counter.increment()); // 1
console.log('Counter:', counter.increment()); // 2
console.log('Counter value:', counter.getValue()); // 2
const user = createUser('John Doe', 'john@example.com');
console.log('User info:', user.getInfo());
console.log('Email update:', user.setEmail('john.doe@company.com'));
const appConfig = createConfig();
const unsubscribe = appConfig.subscribe('theme', (newValue, oldValue) => {
console.log(`Theme changed from ${oldValue} to ${newValue}`);
});
appConfig.set('theme', 'dark');
appConfig.set('theme', 'light');
unsubscribe();
appConfig.set('theme', 'auto'); // No notification异步函数模式
Async/Await 模式
javascript
// async-patterns.js
const fs = require('fs').promises;
const path = require('path');
// Sequential async operations
async function processFilesSequentially(filePaths) {
const results = [];
for (const filePath of filePaths) {
try {
const content = await fs.readFile(filePath, 'utf8');
results.push({
path: filePath,
size: content.length,
success: true
});
} catch (error) {
results.push({
path: filePath,
error: error.message,
success: false
});
}
}
return results;
}
// Parallel async operations
async function processFilesParallel(filePaths) {
const promises = filePaths.map(async (filePath) => {
try {
const content = await fs.readFile(filePath, 'utf8');
return {
path: filePath,
size: content.length,
success: true
};
} catch (error) {
return {
path: filePath,
error: error.message,
success: false
};
}
});
return await Promise.all(promises);
}
// Controlled concurrency
async function processWithConcurrencyLimit(items, processor, limit = 3) {
const results = [];
for (let i = 0; i < items.length; i += limit) {
const batch = items.slice(i, i + limit);
const batchPromises = batch.map(processor);
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
}
return results;
}
// Retry mechanism
async function withRetry(asyncFn, maxRetries = 3, delay = 1000) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await asyncFn();
} catch (error) {
lastError = error;
if (attempt === maxRetries) {
throw error;
}
console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
delay *= 2; // Exponential backoff
}
}
}
// Timeout wrapper
async function withTimeout(asyncFn, timeoutMs) {
return Promise.race([
asyncFn(),
new Promise((_, reject) => {
setTimeout(() => reject(new Error('Operation timed out')), timeoutMs);
})
]);
}
// Circuit breaker pattern
function createCircuitBreaker(asyncFn, options = {}) {
const {
failureThreshold = 5,
resetTimeout = 60000,
monitoringPeriod = 10000
} = options;
let state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
let failureCount = 0;
let lastFailureTime = null;
let successCount = 0;
return async function(...args) {
if (state === 'OPEN') {
if (Date.now() - lastFailureTime >= resetTimeout) {
state = 'HALF_OPEN';
successCount = 0;
} else {
throw new Error('Circuit breaker is OPEN');
}
}
try {
const result = await asyncFn(...args);
if (state === 'HALF_OPEN') {
successCount++;
if (successCount >= 3) {
state = 'CLOSED';
failureCount = 0;
}
} else {
failureCount = 0;
}
return result;
} catch (error) {
failureCount++;
lastFailureTime = Date.now();
if (failureCount >= failureThreshold) {
state = 'OPEN';
}
throw error;
}
};
}
// Usage examples
async function demonstrateAsyncPatterns() {
const files = ['package.json', 'README.md', 'nonexistent.txt'];
console.log('Sequential processing:');
const sequentialResults = await processFilesSequentially(files);
console.log(sequentialResults);
console.log('\nParallel processing:');
const parallelResults = await processFilesParallel(files);
console.log(parallelResults);
// Retry example
const unreliableFunction = async () => {
if (Math.random() < 0.7) {
throw new Error('Random failure');
}
return 'Success!';
};
try {
const result = await withRetry(unreliableFunction, 5, 500);
console.log('Retry result:', result);
} catch (error) {
console.log('All retries failed:', error.message);
}
}
// demonstrateAsyncPatterns();Node.js 特定函数模式
事件驱动函数
javascript
// event-driven-functions.js
const EventEmitter = require('events');
// Function that returns an EventEmitter
function createDataProcessor() {
const emitter = new EventEmitter();
emitter.process = function(data) {
this.emit('start', { data });
// Simulate async processing
setTimeout(() => {
try {
const result = data.toUpperCase();
this.emit('progress', { progress: 50 });
setTimeout(() => {
this.emit('progress', { progress: 100 });
this.emit('complete', { result });
}, 500);
} catch (error) {
this.emit('error', error);
}
}, 1000);
return this;
};
return emitter;
}
// Promisify event-based functions
function promisifyEventEmitter(emitter, successEvent, errorEvent = 'error') {
return new Promise((resolve, reject) => {
emitter.once(successEvent, resolve);
emitter.once(errorEvent, reject);
});
}
// Stream processing function
function createStreamProcessor(transform) {
const { Transform } = require('stream');
return new Transform({
objectMode: true,
transform(chunk, encoding, callback) {
try {
const result = transform(chunk);
callback(null, result);
} catch (error) {
callback(error);
}
}
});
}
// Usage examples
const processor = createDataProcessor();
processor.on('start', ({ data }) => {
console.log('Processing started for:', data);
});
processor.on('progress', ({ progress }) => {
console.log('Progress:', progress + '%');
});
processor.on('complete', ({ result }) => {
console.log('Processing complete:', result);
});
processor.on('error', (error) => {
console.error('Processing error:', error);
});
processor.process('hello world');
// Promisify example
async function processWithPromise(data) {
const processor = createDataProcessor();
processor.process(data);
try {
const result = await promisifyEventEmitter(processor, 'complete');
return result;
} catch (error) {
throw error;
}
}中间件函数
javascript
// middleware-functions.js
// Generic middleware runner
function createMiddlewareRunner() {
const middlewares = [];
return {
use(middleware) {
middlewares.push(middleware);
},
async run(context) {
let index = 0;
async function next() {
if (index >= middlewares.length) {
return;
}
const middleware = middlewares[index++];
await middleware(context, next);
}
await next();
return context;
}
};
}
// Express-style middleware
function createExpressStyleMiddleware() {
const middlewares = [];
return {
use(middleware) {
middlewares.push(middleware);
},
async execute(req, res) {
let index = 0;
async function next(error) {
if (error) {
// Error handling middleware
const errorMiddleware = middlewares.find(mw => mw.length === 4);
if (errorMiddleware) {
return errorMiddleware(error, req, res, () => {});
}
throw error;
}
if (index >= middlewares.length) {
return;
}
const middleware = middlewares[index++];
if (middleware.length === 4) {
// Skip error middleware
return next();
}
await middleware(req, res, next);
}
await next();
}
};
}
// Validation middleware factory
function createValidator(schema) {
return function validate(data, next) {
const errors = [];
for (const [field, rules] of Object.entries(schema)) {
const value = data[field];
for (const rule of rules) {
if (!rule.test(value)) {
errors.push({
field,
message: rule.message,
value
});
}
}
}
if (errors.length > 0) {
const error = new Error('Validation failed');
error.validationErrors = errors;
return next(error);
}
next();
};
}
// Usage examples
const runner = createMiddlewareRunner();
// Add middlewares
runner.use(async (context, next) => {
console.log('Middleware 1: Before');
context.step1 = 'completed';
await next();
console.log('Middleware 1: After');
});
runner.use(async (context, next) => {
console.log('Middleware 2: Before');
context.step2 = 'completed';
await next();
console.log('Middleware 2: After');
});
runner.use(async (context, next) => {
console.log('Middleware 3: Processing');
context.result = 'All steps completed';
await next();
});
// Run middleware chain
async function runMiddlewareExample() {
const context = { id: 1 };
const result = await runner.run(context);
console.log('Final context:', result);
}
// Validation example
const userSchema = {
name: [
{ test: (value) => typeof value === 'string', message: 'Name must be a string' },
{ test: (value) => value && value.length > 0, message: 'Name is required' }
],
email: [
{ test: (value) => typeof value === 'string', message: 'Email must be a string' },
{ test: (value) => value && value.includes('@'), message: 'Email must be valid' }
]
};
const validateUser = createValidator(userSchema);
// runMiddlewareExample();函数性能和优化
性能监控
javascript
// function-performance.js
// Performance measurement decorator
function measurePerformance(fn, name = fn.name) {
return function(...args) {
const start = process.hrtime.bigint();
const result = fn.apply(this, args);
// Handle both sync and async functions
if (result && typeof result.then === 'function') {
return result.finally(() => {
const end = process.hrtime.bigint();
const duration = Number(end - start) / 1000000; // Convert to milliseconds
console.log(`${name} took ${duration.toFixed(2)}ms`);
});
} else {
const end = process.hrtime.bigint();
const duration = Number(end - start) / 1000000;
console.log(`${name} took ${duration.toFixed(2)}ms`);
return result;
}
};
}
// Memory usage tracker
function trackMemoryUsage(fn, name = fn.name) {
return function(...args) {
const beforeMemory = process.memoryUsage();
const result = fn.apply(this, args);
const afterMemory = process.memoryUsage();
const memoryDiff = {
rss: afterMemory.rss - beforeMemory.rss,
heapTotal: afterMemory.heapTotal - beforeMemory.heapTotal,
heapUsed: afterMemory.heapUsed - beforeMemory.heapUsed,
external: afterMemory.external - beforeMemory.external
};
console.log(`${name} memory usage:`, memoryDiff);
return result;
};
}
// Function call counter
function createCallCounter() {
const counts = new Map();
return function countCalls(fn, name = fn.name) {
return function(...args) {
counts.set(name, (counts.get(name) || 0) + 1);
console.log(`${name} called ${counts.get(name)} times`);
return fn.apply(this, args);
};
};
}
// Performance profiler
class FunctionProfiler {
constructor() {
this.profiles = new Map();
}
profile(fn, name = fn.name) {
if (!this.profiles.has(name)) {
this.profiles.set(name, {
calls: 0,
totalTime: 0,
minTime: Infinity,
maxTime: 0,
errors: 0
});
}
return (...args) => {
const profile = this.profiles.get(name);
const start = process.hrtime.bigint();
try {
const result = fn.apply(this, args);
if (result && typeof result.then === 'function') {
return result
.then(value => {
this.recordTime(name, start);
return value;
})
.catch(error => {
this.recordTime(name, start);
profile.errors++;
throw error;
});
} else {
this.recordTime(name, start);
return result;
}
} catch (error) {
this.recordTime(name, start);
profile.errors++;
throw error;
}
};
}
recordTime(name, start) {
const end = process.hrtime.bigint();
const duration = Number(end - start) / 1000000;
const profile = this.profiles.get(name);
profile.calls++;
profile.totalTime += duration;
profile.minTime = Math.min(profile.minTime, duration);
profile.maxTime = Math.max(profile.maxTime, duration);
}
getReport() {
const report = {};
for (const [name, profile] of this.profiles) {
report[name] = {
calls: profile.calls,
totalTime: profile.totalTime.toFixed(2) + 'ms',
averageTime: (profile.totalTime / profile.calls).toFixed(2) + 'ms',
minTime: profile.minTime.toFixed(2) + 'ms',
maxTime: profile.maxTime.toFixed(2) + 'ms',
errorRate: ((profile.errors / profile.calls) * 100).toFixed(2) + '%'
};
}
return report;
}
reset() {
this.profiles.clear();
}
}
// Usage examples
const profiler = new FunctionProfiler();
const counter = createCallCounter();
// Example functions to profile
const slowFunction = (n) => {
let result = 0;
for (let i = 0; i < n * 1000000; i++) {
result += i;
}
return result;
};
const asyncFunction = async (delay) => {
await new Promise(resolve => setTimeout(resolve, delay));
return 'Async result';
};
// Apply decorators
const measuredSlow = measurePerformance(slowFunction, 'slowFunction');
const profiledSlow = profiler.profile(slowFunction, 'slowFunction');
const countedSlow = counter(slowFunction, 'slowFunction');
// Test functions
console.log('Testing performance measurement...');
measuredSlow(100);
profiledSlow(50);
countedSlow(75);
// Generate report
setTimeout(() => {
console.log('\nPerformance Report:');
console.log(profiler.getReport());
}, 1000);下一步
在下一章中,我们将探索 Node.js 中的 API 和集成模式,包括 REST API、GraphQL 和第三方服务集成。
实践练习
- 创建一个包含 pipe 和 compose 工具的函数组合库
- 为数据处理管道实现中间件系统
- 构建带有 TTL 和大小限制的函数记忆化系统
- 为不可靠的函数调用创建熔断器模式
关键要点
- 函数在 JavaScript 和 Node.js 中是一等公民
- 高阶函数启用强大的抽象模式
- 闭包提供封装和状态管理
- Async/await 简化了异步函数组合
- 中间件模式启用灵活的请求处理
- 函数装饰器添加横切关注点
- 性能监控有助于优化关键函数
- 函数式编程模式提高代码可维护性