10个JSDoc实战技巧,让你的JavaScript代码更健壮

21 阅读8分钟

从基础到进阶,覆盖90%日常开发场景

前言:从一个真实的案例开始

最近Review团队代码时,看到一个典型的"遗留代码困境":

// 一个真实的业务函数(已简化)
function processOrder(data, config, callback) {
  // 200行业务逻辑
  // data是什么结构?config有哪些选项?callback要传什么参数?
  // 不看函数实现完全不知道
}

当我问作者"这个函数怎么用时",他挠了挠头:"让我看看啊...哦,data应该是订单对象,config有这几个属性..."

这就是没有类型注释的代价:即使是你自己写的代码,一个月后也会忘记接口约定。

今天,我分享10个JSDoc实战技巧,帮你从"猜谜代码"转向"明确契约"。每个技巧都有真实场景和具体代码,可以直接复制使用。

技巧1:从"模糊对象"到"明确契约"(基础必备)

场景:处理API返回的不确定结构数据

// ❌ 模糊写法 - 知道是个对象,但不知道具体结构
function handleResponse(response) {
  console.log(response.data.user.name);
  // 如果response结构变化,这里会默默失败
}

// ✅ 明确写法 - 定义清晰的数据契约
/**
 * API用户数据响应
 * @typedef {Object} UserApiResponse
 * @property {boolean} success - 请求是否成功
 * @property {Object} data - 响应数据
 * @property {Object} data.user - 用户数据
 * @property {string} data.user.id - 用户ID
 * @property {string} data.user.name - 用户名
 * @property {string} data.user.email - 邮箱
 * @property {number} [data.user.age] - 年龄(可选)
 */

/**
 * 处理用户数据响应
 * @param {UserApiResponse} response - API响应
 */
function handleResponse(response) {
  // 现在有完整的智能提示
  if (response.success) {
    console.log(`用户 ${response.data.user.name} 的邮箱是 ${response.data.user.email}`);
    // 如果尝试访问不存在的属性,IDE会立即警告
    // console.log(response.data.user.phone); // ❌ Property 'phone' does not exist
  }
}

核心价值:把隐式约定变成显式契约,预防运行时错误。

技巧2:可选参数的"正确打开方式"

场景:配置对象中有大量可选参数

// ❌ 模糊的可选参数
function createUser(options) {
  const name = options.name || '匿名';
  const age = options.age || 18;
  // 问题:options还可能有哪些属性?默认值是什么?
}

// ✅ 明确的可选参数配置
/**
 * 用户创建选项
 * @typedef {Object} CreateUserOptions
 * @property {string} [name='匿名'] - 用户名(默认:匿名)
 * @property {number} [age=18] - 年龄(默认:18)
 * @property {'male'|'female'|'other'} [gender='male'] - 性别(默认:male)
 * @property {string} [email] - 邮箱(可选)
 * @property {boolean} [isActive=true] - 是否激活(默认:true)
 * @property {string[]} [tags=[]] - 标签(默认:空数组)
 */

/**
 * 创建用户
 * @param {CreateUserOptions} options - 用户选项
 * @returns {Object} 创建的用户对象
 */
function createUser(options = {}) {
  // 使用解构赋默认值,清晰明了
  const {
    name = '匿名',
    age = 18,
    gender = 'male',
    email,
    isActive = true,
    tags = []
  } = options;
  
  return {
    id: generateId(),
    name,
    age,
    gender,
    email,
    isActive,
    tags,
    createdAt: new Date()
  };
}

// 调用时有完整提示
const user = createUser({
  name: '张三',
  age: 25,
  // gender: 'female',  // 可选,有默认值
  // email: 'zhangsan@example.com',  // 可选,无默认值
  // isActive: false,  // 可选,有默认值
  // tags: ['vip', 'new']  // 可选,有默认值
});

核心价值:明确每个可选参数的默认值和含义,避免配置混淆。

技巧3:处理"要么A要么B"的联合类型

场景:函数参数可以是不同类型或不同格式

// ❌ 含糊的类型判断
function parseInput(input) {
  if (typeof input === 'string') {
    return JSON.parse(input);
  } else if (Array.isArray(input)) {
    return input;
  }
  // 还有哪些情况?
}

// ✅ 明确的联合类型
/**
 * 解析用户输入
 * @param {string | Array<number> | { data: Array<number> }} input - 输入数据
 * @returns {Array<number>} 数字数组
 * @throws {Error} 当输入格式无法解析时
 */
function parseInput(input) {
  if (typeof input === 'string') {
    try {
      const parsed = JSON.parse(input);
      // 类型守卫:确保解析后是数组
      if (Array.isArray(parsed)) {
        return parsed.filter(item => typeof item === 'number');
      }
      throw new Error('字符串必须是JSON数组格式');
    } catch (error) {
      throw new Error(`JSON解析失败: ${error.message}`);
    }
  }
  
  if (Array.isArray(input)) {
    return input.filter(item => typeof item === 'number');
  }
  
  if (input && Array.isArray(input.data)) {
    return input.data.filter(item => typeof item === 'number');
  }
  
  throw new Error('输入格式不支持');
}

// 调用示例 - 所有情况都有明确处理
const result1 = parseInput('[1, 2, 3]');          // ✅ 字符串
const result2 = parseInput([1, 2, 3]);           // ✅ 数组
const result3 = parseInput({ data: [1, 2, 3] }); // ✅ 对象
// const result4 = parseInput(123);              // ❌ 类型错误,IDE会提示

核心价值:明确所有可能的输入类型,避免遗漏情况。

技巧4:函数重载的优雅实现

场景:同一个函数支持多种参数组合

// ❌ 冗长的参数判断
function createElement(tag, className, content) {
  // 根据参数数量判断逻辑...
}

// ✅ 使用@overload明确不同调用方式
/**
 * 创建DOM元素
 * @overload
 * @param {string} tag - 标签名
 * @returns {HTMLElement}
 */
/**
 * @overload
 * @param {string} tag - 标签名
 * @param {string} className - CSS类名
 * @returns {HTMLElement}
 */
/**
 * @overload
 * @param {string} tag - 标签名
 * @param {string} className - CSS类名
 * @param {string|HTMLElement|Array<HTMLElement>} content - 内容
 * @returns {HTMLElement}
 */
/**
 * 创建DOM元素
 * @param {string} tag - 标签名
 * @param {string} [className] - CSS类名
 * @param {string|HTMLElement|Array<HTMLElement>} [content] - 内容
 * @returns {HTMLElement}
 */
function createElement(tag, className, content) {
  const element = document.createElement(tag);
  
  if (className) {
    element.className = className;
  }
  
  if (content) {
    if (typeof content === 'string') {
      element.textContent = content;
    } else if (Array.isArray(content)) {
      element.append(...content);
    } else {
      element.appendChild(content);
    }
  }
  
  return element;
}

// 不同的调用方式都有正确的提示
const div1 = createElement('div');                     // ✅ 只传标签
const div2 = createElement('div', 'container');        // ✅ 标签+类名
const div3 = createElement('div', 'container', 'Hello'); // ✅ 标签+类名+文本
const div4 = createElement('div', 'container', document.createElement('span')); // ✅ 标签+类名+元素

核心价值:让IDE为不同的调用方式提供准确的提示。

技巧5:异步操作的完整类型定义

场景:处理Promise和async/await操作

// ❌ 不完整的异步类型
async function fetchData(url) {
  const response = await fetch(url);
  return response.json(); // 返回的是什么类型?
}

// ✅ 完整的异步类型定义
/**
 * API响应包装类型
 * @template T
 * @typedef {Object} ApiResult
 * @property {boolean} success - 是否成功
 * @property {T} [data] - 数据(成功时)
 * @property {string} [message] - 错误信息(失败时)
 * @property {number} code - 状态码
 */

/**
 * 用户数据类型
 * @typedef {Object} UserData
 * @property {string} id - 用户ID
 * @property {string} name - 用户名
 * @property {string} email - 邮箱
 * @property {number} score - 积分
 */

/**
 * 获取用户数据
 * @param {string} userId - 用户ID
 * @returns {Promise<ApiResult<UserData>>} API响应结果
 * @throws {TypeError} 当userId不是字符串时
 * @throws {Error} 当网络请求失败时
 */
async function fetchUserData(userId) {
  if (typeof userId !== 'string') {
    throw new TypeError('userId必须是字符串');
  }
  
  try {
    const response = await fetch(`/api/users/${userId}`);
    
    if (!response.ok) {
      throw new Error(`HTTP错误: ${response.status}`);
    }
    
    /** @type {ApiResult<UserData>} */
    const result = await response.json();
    
    return result;
  } catch (error) {
    // 网络错误或其他异常
    console.error('获取用户数据失败:', error);
    throw new Error(`获取用户数据失败: ${error.message}`);
  }
}

// 使用时的完整类型支持
async function displayUser(userId) {
  try {
    const result = await fetchUserData(userId);
    
    if (result.success && result.data) {
      // result.data是UserData类型,有完整提示
      console.log(`用户: ${result.data.name}`);
      console.log(`邮箱: ${result.data.email}`);
      console.log(`积分: ${result.data.score}`);
    } else {
      console.error(`获取失败: ${result.message}`);
    }
  } catch (error) {
    console.error('操作失败:', error);
  }
}

// 错误的调用方式会被提示
// fetchUserData(123); // ❌ Argument of type 'number' is not assignable to parameter of type 'string'

核心价值:让异步操作的输入输出和异常都有明确的类型定义。

技巧6:泛型工具函数的实战应用

场景:编写可复用的工具函数,支持多种数据类型

// ❌ 类型固定的工具函数
function getFirstItem(items) {
  return items[0]; // items是什么类型?返回什么类型?
}

// ✅ 泛型工具函数
/**
 * 获取数组的第一个元素
 * @template T
 * @param {T[]} array - 任意类型的数组
 * @returns {T | undefined} 第一个元素或undefined
 */
function getFirstItem(array) {
  return array.length > 0 ? array[0] : undefined;
}

/**
 * 将对象数组转换为键值映射
 * @template T
 * @param {T[]} array - 对象数组
 * @param {keyof T} keyProp - 作为键的属性名
 * @returns {Record<string, T>} 键值映射
 * @example
 * const users = [{ id: '1', name: '张三' }, { id: '2', name: '李四' }];
 * const userMap = arrayToMap(users, 'id');
 * // userMap的类型是 Record<string, { id: string, name: string }>
 */
function arrayToMap(array, keyProp) {
  /** @type {Record<string, T>} */
  const map = {};
  
  for (const item of array) {
    const key = item[keyProp];
    if (typeof key === 'string' || typeof key === 'number') {
      map[String(key)] = item;
    }
  }
  
  return map;
}

/**
 * 深度合并多个对象
 * @template T
 * @param {...Partial<T>} objects - 要合并的对象
 * @returns {T} 合并后的对象
 */
function deepMerge(...objects) {
  /** @type {any} */
  const result = {};
  
  for (const obj of objects) {
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        if (typeof obj[key] === 'object' && obj[key] !== null &&
            typeof result[key] === 'object' && result[key] !== null) {
          // 递归合并对象
          result[key] = deepMerge(result[key], obj[key]);
        } else {
          result[key] = obj[key];
        }
      }
    }
  }
  
  return /** @type {T} */ (result);
}

// 使用示例
const numbers = [1, 2, 3];
const firstNumber = getFirstItem(numbers); // number | undefined

const strings = ['a', 'b', 'c'];
const firstString = getFirstItem(strings); // string | undefined

const users = [
  { id: '1', name: '张三', age: 25 },
  { id: '2', name: '李四', age: 30 }
];

const userMap = arrayToMap(users, 'id');
// userMap['1'] 的类型是 { id: string, name: string, age: number }
console.log(userMap['1'].name); // ✅ 正确的类型提示

const merged = deepMerge(
  { name: '张三', settings: { theme: 'dark' } },
  { age: 25, settings: { fontSize: 14 } }
);
// merged 的类型是 { name: string, age: number, settings: { theme: string, fontSize: number } }

核心价值:编写灵活且类型安全的工具函数,提升代码复用性。

技巧7:DOM操作的类型安全

场景:操作DOM元素时避免常见的类型错误

// ❌ 不安全的DOM操作
function updateElement(elementId, content) {
  const element = document.getElementById(elementId);
  element.innerHTML = content; // element可能是null!
}

// ✅ 类型安全的DOM操作
/**
 * 安全地获取DOM元素
 * @template {keyof HTMLElementTagNameMap} K
 * @param {string} id - 元素ID
 * @param {K} [expectedTag] - 期望的标签名
 * @returns {HTMLElementTagNameMap[K] | null}
 */
function getElementSafe(id, expectedTag) {
  const element = document.getElementById(id);
  
  if (!element) {
    console.warn(`元素 #${id} 不存在`);
    return null;
  }
  
  if (expectedTag && element.tagName.toLowerCase() !== expectedTag.toLowerCase()) {
    console.warn(`元素 #${id} 不是 <${expectedTag}>,而是 <${element.tagName.toLowerCase()}>`);
    return null;
  }
  
  return /** @type {HTMLElementTagNameMap[K]} */ (element);
}

/**
 * 安全地更新元素内容
 * @param {string} elementId - 元素ID
 * @param {string|HTMLElement} content - 要设置的内容
 * @returns {boolean} 是否更新成功
 */
function updateElementSafe(elementId, content) {
  const element = getElementSafe(elementId);
  
  if (!element) {
    return false;
  }
  
  try {
    if (typeof content === 'string') {
      // 安全设置innerHTML,避免XSS
      element.textContent = content;
    } else {
      element.innerHTML = '';
      element.appendChild(content);
    }
    return true;
  } catch (error) {
    console.error(`更新元素 #${elementId} 失败:`, error);
    return false;
  }
}

/**
 * 获取表单值(类型安全版)
 * @param {string} formId - 表单ID
 * @returns {Record<string, string> | null} 表单数据或null
 */
function getFormDataSafe(formId) {
  const form = getElementSafe(formId, 'form');
  if (!form) return null;
  
  /** @type {Record<string, string>} */
  const data = {};
  const formData = new FormData(form);
  
  for (const [key, value] of formData.entries()) {
    data[key] = typeof value === 'string' ? value : '';
  }
  
  return data;
}

// 使用示例
const button = getElementSafe('submit-btn', 'button');
if (button) {
  // button 是 HTMLButtonElement 类型,有完整的方法提示
  button.addEventListener('click', handleClick);
}

const success = updateElementSafe('message', '操作成功');
if (success) {
  console.log('内容更新成功');
}

const formData = getFormDataSafe('user-form');
if (formData) {
  console.log('表单数据:', formData);
  // formData.username 是 string 类型
}

核心价值:避免常见的DOM操作错误,如null引用和类型转换错误。

技巧8:第三方库的完美类型集成

场景:在JavaScript项目中使用TypeScript编写的第三方库

// 安装类型定义
// npm install --save-dev @types/lodash

// ✅ 完整的第三方库类型支持
/**
 * 深度合并配置对象
 * @param {Object} defaultConfig - 默认配置
 * @param {Object} userConfig - 用户配置
 * @returns {Object} 合并后的配置
 */
function mergeConfig(defaultConfig, userConfig) {
  // 使用lodash的merge函数,有完整类型提示
  /** @type {import('lodash').MergeWithCustomizer} */
  const customizer = (objValue, srcValue) => {
    if (Array.isArray(objValue)) {
      return objValue.concat(srcValue);
    }
  };
  
  // lodash的merge函数有完整的类型提示
  return _.mergeWith({}, defaultConfig, userConfig, customizer);
}

/**
 * 防抖函数包装器
 * @template T
 * @param {T} func - 要防抖的函数
 * @param {number} wait - 等待时间(毫秒)
 * @returns {T & { cancel: () => void }} 防抖后的函数
 */
function debounceWithTypes(func, wait) {
  // lodash的debounce函数有完整类型提示
  const debounced = _.debounce(func, wait);
  return /** @type {T & { cancel: () => void }} */ (debounced);
}

/**
 * 使用Axios进行API调用
 * @param {string} endpoint - API端点
 * @param {Object} data - 请求数据
 * @returns {Promise<any>} 响应数据
 */
async function apiCall(endpoint, data) {
  // 导入Axios实例类型
  /** @type {import('axios').AxiosInstance} */
  const apiClient = axios.create({
    baseURL: '/api',
    timeout: 5000
  });
  
  try {
    const response = await apiClient.post(endpoint, data);
    return response.data;
  } catch (error) {
    // error 是 AxiosError 类型,有完整属性提示
    if (error.response) {
      console.error('API错误响应:', error.response.status, error.response.data);
    } else if (error.request) {
      console.error('无响应:', error.request);
    } else {
      console.error('请求错误:', error.message);
    }
    throw error;
  }
}

// 使用示例
const searchHandler = debounceWithTypes((query) => {
  console.log('搜索:', query);
}, 300);

// searchHandler 有完整的类型提示
searchHandler('关键词');
searchHandler.cancel(); // 来自lodash的cancel方法

核心价值:即使在不使用TypeScript的项目中,也能享受类型化第三方库的完整支持。

技巧9:枚举和常量组的类型安全

场景:避免魔法字符串和数字,使用类型安全的常量

// ❌ 魔法字符串和数字
function getStatusText(status) {
  if (status === 1) return '待处理';
  if (status === 2) return '处理中';
  if (status === 3) return '已完成';
  return '未知状态';
}

// ✅ 类型安全的枚举和常量
/**
 * 订单状态枚举
 * @enum {number}
 */
const OrderStatus = {
  PENDING: 1,
  PROCESSING: 2,
  COMPLETED: 3,
  CANCELLED: 4
};

/**
 * 状态显示文本映射
 * @type {Record<OrderStatus[keyof OrderStatus], string>}
 */
const StatusText = {
  [OrderStatus.PENDING]: '待处理',
  [OrderStatus.PROCESSING]: '处理中',
  [OrderStatus.COMPLETED]: '已完成',
  [OrderStatus.CANCELLED]: '已取消'
};

/**
 * 根据状态码获取状态文本
 * @param {OrderStatus[keyof OrderStatus]} status - 状态码
 * @returns {string} 状态文本
 */
function getStatusText(status) {
  return StatusText[status] || '未知状态';
}

/**
 * 权限常量组
 * @readonly
 * @enum {string}
 */
const Permissions = {
  READ: 'read',
  WRITE: 'write',
  DELETE: 'delete',
  ADMIN: 'admin'
};

/**
 * 检查用户权限
 * @param {string[]} userPermissions - 用户拥有的权限
 * @param {Permissions[keyof Permissions]} requiredPermission - 需要的权限
 * @returns {boolean} 是否拥有权限
 */
function hasPermission(userPermissions, requiredPermission) {
  return userPermissions.includes(requiredPermission);
}

// 使用示例
const currentStatus = OrderStatus.PROCESSING;
console.log(getStatusText(currentStatus)); // '处理中'

// 错误的用法会被提示
// console.log(getStatusText(99)); // ❌ Argument of type '99' is not assignable to parameter...

const userPerms = [Permissions.READ, Permissions.WRITE];
const canDelete = hasPermission(userPerms, Permissions.DELETE); // false
const canWrite = hasPermission(userPerms, Permissions.WRITE); // true

核心价值:消除魔法值,提高代码可读性和可维护性。

技巧10:复杂的业务逻辑类型建模

场景:为复杂业务领域建立完整的类型模型

// ✅ 电商订单系统的完整类型建模
/**
 * 商品SKU
 * @typedef {Object} ProductSku
 * @property {string} id - SKU ID
 * @property {string} name - 商品名称
 * @property {number} price - 价格(分)
 * @property {number} stock - 库存
 * @property {Record<string, string>} attributes - 属性(如颜色、尺寸)
 */

/**
 * 购物车商品项
 * @typedef {Object} CartItem
 * @property {ProductSku} sku - 商品SKU
 * @property {number} quantity - 数量
 * @property {number} [selectedPrice] - 选中时的价格(用于促销)
 */

/**
 * 促销规则
 * @typedef {Object} PromotionRule
 * @property {string} id - 规则ID
 * @property {string} name - 规则名称
 * @property {'discount'|'gift'|'coupon'} type - 规则类型
 * @property {number} [discountRate] - 折扣率(0-1)
 * @property {number} [discountAmount] - 折扣金额(分)
 * @property {ProductSku} [giftProduct] - 赠品
 * @property {Function} condition - 应用条件
 * @property {Function} apply - 应用规则
 */

/**
 * 订单计算上下文
 * @typedef {Object} OrderCalculationContext
 * @property {CartItem[]} items - 购物车商品
 * @property {PromotionRule[]} promotions - 可用促销
 * @property {number} shippingFee - 运费(分)
 * @property {number} [pointsUsed] - 使用的积分
 */

/**
 * 订单计算结果
 * @typedef {Object} OrderCalculationResult
 * @property {number} totalAmount - 商品总金额(分)
 * @property {number} discountAmount - 折扣金额(分)
 * @property {number} shippingFee - 运费(分)
 * @property {number} finalAmount - 最终支付金额(分)
 * @property {Array<{rule: PromotionRule, discount: number}>} appliedPromotions - 应用的促销
 */

/**
 * 计算订单金额
 * @param {OrderCalculationContext} context - 计算上下文
 * @returns {OrderCalculationResult} 计算结果
 */
function calculateOrder(context) {
  const { items, promotions = [], shippingFee = 0, pointsUsed = 0 } = context;
  
  // 计算商品总金额
  const totalAmount = items.reduce((sum, item) => {
    const price = item.selectedPrice || item.sku.price;
    return sum + price * item.quantity;
  }, 0);
  
  // 应用促销规则
  let discountAmount = 0;
  /** @type {Array<{rule: PromotionRule, discount: number}>} */
  const appliedPromotions = [];
  
  for (const promotion of promotions) {
    if (promotion.condition(items)) {
      const discount = promotion.apply(items, totalAmount);
      discountAmount += discount;
      appliedPromotions.push({ rule: promotion, discount });
    }
  }
  
  // 计算最终金额
  const pointsDiscount = pointsUsed * 100; // 假设1积分=1分钱
  const finalAmount = Math.max(0, totalAmount - discountAmount - pointsDiscount + shippingFee);
  
  return {
    totalAmount,
    discountAmount,
    shippingFee,
    finalAmount,
    appliedPromotions
  };
}

// 使用示例
/** @type {ProductSku} */
const productSku = {
  id: 'sku-001',
  name: '无线耳机',
  price: 29900, // 299元
  stock: 100,
  attributes: { color: '白色', version: '标准版' }
};

/** @type {CartItem} */
const cartItem = {
  sku: productSku,
  quantity: 2
};

/** @type {PromotionRule} */
const discountRule = {
  id: 'promo-001',
  name: '满500减50',
  type: 'discount',
  condition: (items) => {
    const total = items.reduce((sum, item) => sum + item.sku.price * item.quantity, 0);
    return total >= 50000; // 满500元
  },
  apply: (items) => 5000 // 减50元
};

/** @type {OrderCalculationContext} */
const context = {
  items: [cartItem],
  promotions: [discountRule],
  shippingFee: 1000, // 10元运费
  pointsUsed: 100 // 使用100积分
};

const result = calculateOrder(context);
console.log(`商品总额: ${(result.totalAmount / 100).toFixed(2)}元`);
console.log(`折扣: ${(result.discountAmount / 100).toFixed(2)}元`);
console.log(`运费: ${(result.shippingFee / 100).toFixed(2)}元`);
console.log(`最终支付: ${(result.finalAmount / 100).toFixed(2)}元`);

核心价值:为复杂业务建立清晰的类型模型,使代码结构更清晰,减少业务逻辑错误。

实战总结:JSDoc的最佳实践原则

  1. 渐进式采用原则

· 从新代码开始:不必立即改造所有旧代码 · 从公共API开始:优先为模块导出的函数和类添加注释 · 从简单到复杂:先掌握@param和@returns,再学习高级特性

  1. 一致性原则

· 团队统一规范:制定并遵守JSDoc编写规范 · 命名一致:类型命名要有意义且一致(如UserData、ApiResponse) · 格式一致:保持注释格式统一,提高可读性

  1. 实用性原则

· 不为注释而注释:重点是提升代码质量,不是形式主义 · 关注核心契约:优先明确函数输入输出,再完善细节 · 保持注释更新:代码变更时同步更新注释

  1. 工具化原则

· 善用IDE支持:利用智能提示和错误检查 · 配置自动化检查:使用ESLint确保注释质量 · 生成文档:使用工具从JSDoc生成API文档

你的JSDoc实践路线图

阶段 目标 关键实践 第1周 基础入门 为所有新函数添加@param和@returns 第1个月 熟练应用 使用@typedef定义复杂类型,配置ESLint检查 第3个月 进阶掌握 使用泛型、函数重载等高级特性 第6个月 领域建模 为业务领域建立完整的类型模型 长期 团队影响 推动团队类型思维,提升整体代码质量

立即开始:本周实战挑战

挑战1:选择一个你最近写的复杂函数,用JSDoc为其添加完整类型注释。

挑战2:为你负责的模块创建公共类型定义文件(types.js)。

挑战3:配置团队的ESLint,添加JSDoc检查规则。

记住:类型思维的价值不在于你写了多少注释,而在于你通过注释理清了多少业务逻辑。


系列回顾:

· 第一篇:学了TypeScript却用不起来?用JSDoc在JavaScript中立即学以致用 · 第二篇:如何让团队平滑拥抱类型思维,避免TypeScript迁移阵痛 · 第三篇:本文

你已经掌握了JSDoc的核心技巧,接下来就是在实际项目中持续实践。真正的成长,发生在你把知识应用到代码中的那一刻。

你在使用JSDoc过程中遇到过哪些有趣的挑战或收获?欢迎在评论区分享你的实战经验!

如果这个系列对你有帮助,请点赞收藏,让更多开发者看到类型思维的魅力。