# 编写JS代码

## 规则（Rules）

# 编写JS代码 - 规范

## 适用对象和范围

本规范适用于所有使用原生 JavaScript 编写的代码。

**适用范围**：
- 新编写的 JS 功能代码
- 对现有 JS 代码的修改和重构
- 与 DOM 操作、事件处理、数据请求相关的脚本

---

## 1. 命名规范

| 类型 | 规范 | 示例 |
|------|------|------|
| 函数 | 必须使用小驼峰（camelCase） | `getUserData(user_id)` |
| 变量和参数 | 必须使用**小写蛇形（snake_case）** | `let user_name = 'xxx'` |
| 常量 | 必须使用全大写蛇形（SCREAMING_SNAKE_CASE） | `const MAX_COUNT = 100` |
| 类/构造函数 | 必须使用大驼峰（PascalCase） | `class UserManager` |

### 命名原则

- **名称必须有意义**：`let user_list` ✅ / `let arr` ❌
- **布尔值必须用 is/has/can 开头**：`is_active`、`has_permission`、`can_edit`
- **DOM 元素引用变量必须以 `$` 前缀开头**：`const $user_table = document.querySelector('#user-table');`
- **事件处理函数必须用 handle 开头**：`handle_click`、`handle_submit`
- **禁止使用缩写**：`let button` ✅ / `let btn` ❌（通用缩写如 id、url 除外）

**违反后果**：命名不规范导致代码难以理解和维护，代码审查不通过。

---

## 2. 缩进规范

所有代码必须使用 **2个空格** 缩进，禁止使用 Tab 缩进。

```javascript
// ✅ 正确：2空格缩进
function getData() {
  let user_name = '张三';
  if (user_name) {
    console.log(user_name);
  }
}

// ❌ 错误：Tab缩进或4空格缩进
function getData() {
    let user_name = '张三';
    if (user_name) {
        console.log(user_name);
    }
}
```

**违反后果**：缩进不统一导致代码格式混乱，可读性差。

---

## 3. 变量声明规范

- `const`：必须用于值不会改变的变量，以及模块导入导出
- `let`：必须用于函数内部会被重新赋值的变量
- 禁止使用 `var` 声明变量

```javascript
// ✅ 正确
const MAX_SIZE = 100;
let retry_count = 0;
retry_count = 10;

// ❌ 错误：使用 var
var total = 100;
```

**违反后果**：使用 `var` 导致变量提升和作用域污染，引发难以排查的 Bug。

---

## 4. 分号规范

每行语句末尾必须加分号 `;`。

```javascript
// ✅ 正确
let user_name = '张三';
doSomething();

// ❌ 错误：缺少分号
let user_name = '张三'
doSomething()
```

**违反后果**：缺少分号可能导致 ASI 规则产生意外解析结果，引发隐蔽的运行时错误。

---

## 5. 引号规范

- 字符串必须统一使用单引号 `'`
- 模板字符串必须使用反引号 `` ` ``（仅在需要拼接变量或换行时使用）

```javascript
// ✅ 正确
let user_name = '张三';
let message = `你好，${user_name}`;

// ❌ 错误：使用双引号
let user_name = "张三";
```

**违反后果**：引号不统一导致代码风格不一致，代码审查不通过。

---

## 6. 注释规范

函数/方法必须使用 JSDoc 格式注释，说明功能、参数和返回值。

```javascript
/**
 * 获取用户数据
 * @param {number} user_id - 用户ID
 * @returns {Promise<Object>} 用户信息对象
 */
async function getUserData(user_id) { ... }
```

### 注释要求

- **函数/方法**：必须有 JSDoc 注释
- **复杂逻辑**：必须有行内注释说明意图
- **TODO标记**：必须使用 `// TODO: 说明` 格式
- **FIXME标记**：必须使用 `// FIXME: 说明` 格式

**违反后果**：缺少注释导致代码可读性差，后续维护困难。

---

## 7. 代码风格规范

- 每个函数不得超过 50 行（超出必须拆分）
- 每行代码不得超过 120 个字符
- 操作符前后必须加空格：`let total = price + tax;`
- 逗号后面必须加空格：`let num_list = [1, 2, 3];`
- 大括号前必须加空格：`if (condition) {`
- 控制语句（if/for/while）后必须加空格：`if (condition)`

```javascript
// ✅ 正确
function calculateTotal(item_price, quantity) {
  let total_price = item_price * quantity;
  if (total > 100) {
    return total * 0.9;
  }
  return total;
}

// ❌ 错误
function calculateTotal(item_price,quantity){
  let total_price=item_price*quantity;
  if(total>100){
    return total*0.9;
  }
  return total;
}
```

**违反后果**：代码风格不一致导致可读性差，代码审查不通过。

---

## 8. 错误处理规范

- 所有 API 调用必须使用 `try...catch` 包裹
- 异步操作必须处理 reject 路径（使用 `try...catch` 或 `.catch()`）
- 用户操作反馈必须显示友好的错误提示，禁止直接抛出原始错误对象

```javascript
// ✅ 正确
async function loadData() {
  try {
    let result_data = await fetchData();
    renderData(result_data);
  } catch (error) {
    showError('数据加载失败，请稍后重试');
    console.error('加载数据出错:', error);
  }
}

// ❌ 错误：未处理错误
async function loadData() {
  let result_data = await fetchData();
  renderData(result_data);
}
```

**违反后果**：未处理的错误导致页面白屏、功能不可用，用户体验极差。

---

## 9. 异步规范

- 必须优先使用 `async/await` 而非原始 `Promise.then()`
- 多个无依赖的异步操作必须使用 `Promise.all()` 并行执行
- 禁止使用回调嵌套（回调地狱）

```javascript
// ✅ 正确：async/await
async function loadAll() {
  let [user_list, post_list] = await Promise.all([
    fetchUsers(),
    fetchPosts()
  ]);
}

// ❌ 错误：回调嵌套
function loadAll() {
  fetchUsers().then(user_list => {
    fetchPosts().then(post_list => { ... });
  });
}
```

**违反后果**：回调嵌套导致代码难以阅读和维护，串行执行多个无依赖的异步操作降低性能。

---

## 10. 严格比较规范

- 必须始终使用 `===` 和 `!==`，禁止使用 `==` 和 `!=`

```javascript
// ✅ 正确
if (user_age === 18) { ... }
if (user_name !== '') { ... }

// ❌ 错误：使用宽松比较
if (user_age == 18) { ... }
if (user_name != '') { ... }
```

**违反后果**：使用 `==` 和 `!=` 导致类型转换引发意外比较结果，产生隐蔽 Bug。

---

## 11. 文件编码规范

所有 JS 源文件必须使用 **UTF-8 编码**。

```javascript
// 文件头部可添加编码声明（可选）
// -*- coding: utf-8 -*-
```

**违反后果**：编码不一致导致中文等非 ASCII 字符出现乱码，跨平台协作时文件无法正常读取。

---

## 例外情况

以下情况允许破例：

- 在需要兼容 IE11 及以下版本浏览器时，允许使用 `var` 声明变量
- 在第三方库的 polyfill 或垫片代码中，允许使用 `==` 进行类型检查
- 在性能敏感的循环体内，允许函数超过 50 行，但不得超过 100 行

## 方法（Methods）

# 编写JS代码 - 方法

## 前置条件

开始编写 JS 代码前，请确认以下条件已满足：

- [ ] 需要实现的功能需求已明确
- [ ] 依赖的 API 接口文档已就绪（或已确定使用模拟数据）

---

## 流程概览

```
理解需求 → 设计数据结构 → 编写功能代码 → 验证功能
```

---

## 详细步骤

### 步骤1：理解需求

明确需要实现的交互功能。分析需求文档，明确以下内容：

- 需要实现的交互功能有哪些（点击、输入、滚动、拖拽等）
- 需要调用哪些 API 接口，接口的入参和出参是什么
- 数据展示方式（列表、表格、图表等）
- 用户操作反馈方式（弹窗提示、页面跳转、局部刷新等）
- 页面状态有哪些（加载中、空数据、错误状态、正常状态）

## 技巧（Tips）

# 编写JS代码 - 技巧

## 1. 异常处理技巧

### 1.1 API 接口未定义时用模拟数据占位

**适用场景**：后端 API 还未开发完成，但前端需要先行开发。

**具体做法**：在代码中定义模拟数据对象，用模拟数据代替真实 API 调用。

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

async function getUsers() {
  // TODO: 联调后替换为真实API
  return MOCK_DATA.users;
}
```

**对比说明**：不用模拟数据时，前端需等后端完成才能开发，开发进度被阻塞；用模拟数据可并行开发，联调时只需换接口。

**注意事项**：模拟数据的数据结构必须与真实 API 返回结构一致，否则联调时仍需修改。

### 1.2 网络请求超时处理

**适用场景**：网络不稳定或服务响应慢时，需要给用户明确的超时提示。

**具体做法**：用 AbortController 实现带超时的 fetch 请求。

```javascript
async function fetchWithTimeout(url, options = {}, timeout = 10000) {
  let controller = new AbortController();
  let timeout_id = setTimeout(() => controller.abort(), timeout);

  try {
    let response = await fetch(url, {
      ...options,
      signal: controller.signal
    });
    clearTimeout(timeout_id);
    return await response.json();
  } catch (error) {
    clearTimeout(timeout_id);
    if (error.name === 'AbortError') {
      throw new Error('请求超时，请稍后重试');
    }
    throw error;
  }
}
```

**对比说明**：不用超时处理时，请求一直转圈，用户不知道要等多久；用超时处理后，超时明确提示，用户知道该怎么做。

**注意事项**：超时时间建议设置为 10 秒。太长用户无法忍受，太短可能因网络波动频繁超时。

---

## 2. 性能优化技巧

### 2.1 批量 DOM 操作用 DocumentFragment

**适用场景**：需要一次性向页面中插入大量 DOM 元素时。

**具体做法**：用 DocumentFragment 先构建 DOM 树，再一次性插入。

```javascript
// ❌ 不用：多次DOM操作（性能差）
for (let item of items) {
  list.innerHTML += `<li>${item}</li>`;
}

// ✅ 用：使用DocumentFragment（性能好）
let fragment = document.createDocumentFragment();
for (let item of items) {
  let li = document.createElement('li');
  li.textContent = item;
  fragment.appendChild(li);
}
list.appendChild(fragment);
```

**对比说明**：不用时每次循环都触发浏览器重排重绘；用 DocumentFragment 只触发一次重排，元素越多性能提升越明显。

### 2.2 事件委托代替逐个绑定

**适用场景**：列表或表格中大量子元素需要绑定相同的事件。

**具体做法**：将事件绑定到父元素，通过事件冒泡捕获子元素。

```javascript
// ❌ 不用：每个元素都绑定事件
items.forEach(item => {
  item.addEventListener('click', handleClick);
});

// ✅ 用：事件委托到父元素
list.addEventListener('click', function(event) {
  let target = event.target.closest('li');
  if (target) {
    handleClick(target);
  }
});
```

**对比说明**：不用时 1000 个元素绑定 1000 个事件监听器，内存占用高；用事件委托只绑定 1 个事件监听器，新增子元素自动生效。

**注意事项**：事件委托依赖事件冒泡，`focus`、`blur` 等不冒泡的事件不适合用委托。

### 2.3 防抖减少频繁请求

**适用场景**：搜索输入框、窗口 resize 等频繁触发的事件。

**具体做法**：用防抖函数将连续触发合并为最后一次执行。

```javascript
function debounce(fn, delay = 300) {
  let timer = null;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

searchInput.addEventListener('input', debounce(function(event) {
  // 发送搜索请求
}, 500));
```

**对比说明**：不用防抖时输入"hello"发 5 次请求；用防抖只发 1 次。

**注意事项**：防抖延迟建议设为 300-500ms。太短起不到效果，太长让用户感觉响应迟钝。

### 2.4 节流控制执行频率

**适用场景**：滚动事件、鼠标移动等需要持续响应但不需要每帧都触发的场景。

**具体做法**：用节流函数控制固定时间间隔内只执行一次。

```javascript
function throttle(fn, interval = 200) {
  let lastTime = 0;
  return function(...args) {
    let now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

window.addEventListener('scroll', throttle(function() {
  // 处理滚动逻辑
}, 200));
```

**对比说明**：不用节流时滚动一次可能触发几十次事件；用节流控制在每 200ms 只执行一次。

**注意事项**：节流适合需要持续反馈的场景（如滚动加载），防抖适合只需要最后一次的场景（如搜索）。

---

## 3. 调试技巧

### 3.1 用 console.table 展示数组

**适用场景**：需要查看数组或对象列表的结构时。

**具体做法**：用 `console.table()` 以表格形式展示数据，比 `console.log()` 更直观。

```javascript
console.table(array);                  // 表格形式展示数组
console.time('操作名称');              // 性能计时开始
// ...执行操作...
console.timeEnd('操作名称');           // 输出耗时
console.group('分组名称');             // 分组查看
console.log('信息1');
console.groupEnd();
```

### 3.2 用 debugger 快速断点

**适用场景**：需要调试某段代码的执行过程。

**具体做法**：在代码中直接写 `debugger;`，浏览器执行到此处会自动暂停。

```javascript
function complexLogic(data) {
  debugger;  // 浏览器执行到这里会自动暂停
  // ...后续逻辑
}
```

**注意事项**：调试完成后必须删除 `debugger;` 语句，否则会影响生产环境运行。

---

## 4. 安全技巧

### 4.1 用 textContent 替代 innerHTML 防 XSS

**适用场景**：需要将用户输入的内容显示在页面上时。

**具体做法**：用 `textContent` 设置文本内容，避免用 `innerHTML` 插入用户输入。

```javascript
// 设置用户输入时用textContent
element.textContent = userInput;   // ✅ 安全
element.innerHTML = userInput;     // ❌ XSS风险
```

**对比说明**：使用 `innerHTML` 插入用户输入时，如果输入包含 `<script>` 标签，会被执行导致 XSS 攻击；使用 `textContent` 会自动转义 HTML 特殊字符。

### 4.2 避免在前端硬编码敏感信息

**适用场景**：需要在前端代码中使用 API 密钥、Token 等敏感信息时。

**具体做法**：敏感信息通过后端接口获取，使用 HTTPS 传输，前端不存储明文密钥。

```javascript
// ❌ 禁止：在前端硬编码API密钥
const API_KEY = 'sk-abc123...';

// ✅ 正确：通过后端接口获取
async function getApiKey() {
  let response = await fetch('/api/config');
  let config = await response.json();
  return config.apiKey;
}
```

---

## 5. 代码组织技巧

### 5.1 用提前 return 减少嵌套

**适用场景**：函数中有多层条件判断时。

**具体做法**：先处理异常/边界情况并提前 return，主体逻辑放在最外层。

```javascript
// ❌ 不用：多层嵌套
function processUser(user) {
  if (user) {
    if (user.is_active) {
      if (user.age >= 18) {
        // 处理逻辑
      }
    }
  }
}

// ✅ 用：提前return
function processUser(user) {
  if (!user) return;
  if (!user.is_active) return;
  if (user.age < 18) return;
  // 处理逻辑
}
```

**对比说明**：不用时嵌套 3 层，代码可读性差；用提前 return 后扁平化，逻辑清晰。

### 5.2 配置与逻辑分离

**适用场景**：代码中有多处硬编码的配置值（API 地址、超时时间、提示文案等）。

**具体做法**：将所有配置值集中到一个配置对象中管理。

```javascript
const CONFIG = {
  api: {
    base_url: '/api',
    timeout: 10000
  },
  ui: {
    page_size: 20,
    debounce_delay: 300
  },
  messages: {
    loading: '加载中...',
    empty: '暂无数据',
    error: '系统繁忙，请稍后重试'
  }
};
```

**对比说明**：不用时配置值散落在代码各处，修改一个配置要全文搜索；用配置对象后所有配置集中一处，改配置只需改一处。

---

## 常见问题与解决方案

| 问题 | 原因 | 解决方案 |
|------|------|---------|
| `xxx is not a function` | 变量名与函数名冲突 | 检查变量命名，避免覆盖函数 |
| `xxx is not defined` | 变量未声明或作用域问题 | 检查变量声明和作用域 |
| `Cannot read property 'xxx' of undefined` | 访问了 undefined 的属性 | 使用可选链 `?.` 或提前判断 |
| 异步数据未更新 | 忘记 await 或 then | 检查异步操作是否正确等待 |
| 事件绑定不生效 | DOM 元素还未加载 | 将 script 放在 body 末尾或用 DOMContentLoaded 事件 |
| 循环中闭包问题 | 变量共享 | 使用 let 或闭包捕获当前值 |
