Skip to content

函数和方法

概述

函数在 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 和第三方服务集成。

实践练习

  1. 创建一个包含 pipe 和 compose 工具的函数组合库
  2. 为数据处理管道实现中间件系统
  3. 构建带有 TTL 和大小限制的函数记忆化系统
  4. 为不可靠的函数调用创建熔断器模式

关键要点

  • 函数在 JavaScript 和 Node.js 中是一等公民
  • 高阶函数启用强大的抽象模式
  • 闭包提供封装和状态管理
  • Async/await 简化了异步函数组合
  • 中间件模式启用灵活的请求处理
  • 函数装饰器添加横切关注点
  • 性能监控有助于优化关键函数
  • 函数式编程模式提高代码可维护性

本站内容仅供学习和研究使用。