# lodash

Lodash 常用工具函数 / Common Lodash utilities

## Overview / 概述

提供常用的对象操作、函数防抖节流等工具函数。Provide common object manipulation, debounce, throttle utilities.

## Functions

### deepClone

深拷贝 / Deep clone

```ts
function deepClone<T>(obj: T): T
```

**Parameters / 参数**

| Name | Type | Description |
|------|------|-------------|
| `obj` | `T` | 要拷贝的对象 |

**Returns / 返回值**

- `T`: 深拷贝后的对象

**Features / 支持类型**

- 基本类型（string, number, boolean, null, undefined, symbol, bigint）
- 数组 / Array
- 对象 / Object
- Date
- RegExp
- Map
- Set
- 嵌套对象/数组 / Nested objects/arrays

**Example / 示例**

```ts
// 简单对象
const original = { name: 'a', person: { age: 18 } }
const cloned = deepClone(original)
cloned.person.age = 20
console.log(original.person.age) // 18

// 数组
const arr = [1, 2, { a: 1 }]
const arrClone = deepClone(arr)

// Date
const date = new Date()
const dateClone = deepClone(date)

// RegExp
const reg = /test/g
const regClone = deepClone(reg)

// Map
const map = new Map([['key', 'value']])
const mapClone = deepClone(map)

// Set
const set = new Set([1, 2, 3])
const setClone = deepClone(set)
```

---

### omit

删除对象中的某些键值对 / Omit fields from object

```ts
function omit<T, K extends keyof T>(obj: T, fields: K[], ignoreEmpty?: boolean): Omit<T, K>
```

**Parameters / 参数**

| Name | Type | Description |
|------|------|-------------|
| `obj` | `T` | 源对象 |
| `fields` | `K[]` | 要删除的键名数组 |
| `ignoreEmpty` | `boolean` | 是否忽略空值 |

**Example / 示例**

```ts
const obj = { a: 1, b: 2, c: 3 }
omit(obj, ['a'])           // { b: 2, c: 3 }
omit(obj, ['a', 'b'])      // { c: 3 }

// 忽略空值
omit({ a: 1, b: undefined }, ['b'], true) // { a: 1 }
```

---

### pick

从对象中取出指定的键值对 / Pick fields from object

```ts
function pick<T, K extends keyof T>(obj: T, keys: K[], ignoreEmpty?: boolean): Pick<T, K>
```

**Parameters / 参数**

| Name | Type | Description |
|------|------|-------------|
| `obj` | `T` | 源对象 |
| `keys` | `K[]` | 要保留的键名数组 |
| `ignoreEmpty` | `boolean` | 是否忽略空值 |

**Example / 示例**

```ts
const obj = { a: 1, b: 2, c: 3 }
pick(obj, ['a', 'b'])      // { a: 1, b: 2 }
pick(obj, ['a', 'd'])      // { a: 1 } (d 不存在)
```

---

### pickBy

根据断言函数取出键值对 / Pick fields by predicate

```ts
function pickBy<T>(obj: T, predicate: (value: any, key: keyof T) => boolean): Partial<T>
```

**Example / 示例**

```ts
const obj = { a: 1, b: 2, c: 3, d: 4 }
pickBy(obj, (value) => value > 2)  // { c: 3, d: 4 }
pickBy(obj, (_, key) => key !== 'a') // { b: 2, c: 3, d: 4 }
```

---

### omitBy

根据断言函数删除键值对 / Omit fields by predicate

```ts
function omitBy<T>(obj: T, predicate: (value: any, key: keyof T) => boolean): Partial<T>
```

**Example / 示例**

```ts
const obj = { a: 1, b: 2, c: 3, d: 4 }
omitBy(obj, (value) => value > 2)   // { a: 1, b: 2 }
omitBy(obj, (_, key) => key === 'a') // { b: 2, c: 3, d: 4 }
```

---

### debounce

防抖函数 / Debounce function

```ts
function debounce<T extends (...args: any[]) => any>(fn: T, delay: number): T & { cancel: () => void }
```

**Parameters / 参数**

| Name | Type | Description |
|------|------|-------------|
| `fn` | `T` | 要防抖的函数 |
| `delay` | `number` | 延迟时间（毫秒） |

**Returns / 返回值**

- `T & { cancel: () => void }`: 防抖后的函数，包含 cancel 方法

**Example / 示例**

```ts
// 搜索防抖
const debounceSearch = debounce((keyword: string) => {
  console.log('搜索:', keyword)
  // 执行搜索 API
}, 300)

// 模拟输入
input.oninput = (e) => {
  debounceSearch(e.target.value)
}

// 取消
debounceSearch.cancel()
```

**场景说明 / Scenario**

- 搜索框输入防抖：用户输入停止 300ms 后才触发搜索
- 窗口 resize 防抖：窗口调整大小停止后执行
- 表单验证：输入停止后验证

---

### throttle

节流函数 / Throttle function

```ts
function throttle<T extends (...args: any[]) => any>(fn: T, delay: number): T & { cancel: () => void }
```

**Parameters / 参数**

| Name | Type | Description |
|------|------|-------------|
| `fn` | `T` | 要节流的函数 |
| `delay` | `number` | 间隔时间（毫秒） |

**Returns / 返回值**

- `T & { cancel: () => void }`: 节流后的函数，包含 cancel 方法

**Example / 示例**

```ts
// 滚动节流
const throttleScroll = throttle(() => {
  console.log('滚动位置:', window.scrollY)
}, 100)

window.addEventListener('scroll', throttleScroll)

// 点击节流（防止重复提交）
const throttleSubmit = throttle(() => {
  console.log('提交表单')
  // 执行提交
}, 2000)

button.onclick = () => throttleSubmit()
```

**场景说明 / Scenario**

- 滚动事件：限制触发频率
- 按钮点击：防止重复提交
- 拖拽事件：减少计算次数

---

### merge

合并对象 / Merge objects

```ts
function merge<T>(target: T, source: any): T & any
```

**Example / 示例**

```ts
const target = { a: 1, b: 2 }
const source = { b: 3, c: 4 }
merge(target, source) // { a: 1, b: 3, c: 4 }

// 数组会被替换而非合并
merge({ arr: [1] }, { arr: [2] }) // { arr: [2] }
```

---

### uniqueId

生成浏览器唯一 ID / Generate unique ID

```ts
function uniqueId(length?: number): string
```

**Parameters / 参数**

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `length` | `number` | `16` | ID 长度 |

**Example / 示例**

```ts
uniqueId()    // 16 位: 'a1b2c3d4e5f6g7h8'
uniqueId(8)   // 8 位: 'a1b2c3d4'
uniqueId(32)  // 32 位
```

**Use Cases / 使用场景**

- 生成组件唯一 ID
- 生成缓存 key
- 生成临时文件名

---

### get

从对象中获取指定路径的值 / Get value by path

```ts
function get(obj: any, path: string | string[], defaultValue?: any): any
```

**Parameters / 参数**

| Name | Type | Description |
|------|------|-------------|
| `obj` | `any` | 源对象 |
| `path` | `string \| string[]` | 属性路径，支持点号或数组 |
| `defaultValue` | `any` | 默认值 |

**Example / 示例**

```ts
const obj = { a: { b: { c: 1 } } }

// 点号路径
get(obj, 'a.b.c')           // 1
get(obj, 'a.b.d', 0)        // 0 (默认值)

// 数组路径
get(obj, ['a', 'b', 'c'])   // 1

// 不存在的路径
get(obj, 'a.b.c.d', 'N/A')  // 'N/A'
```

---

### objToQString

对象转 URL 字符串 / Object to query string

```ts
function objToQString(obj: object): string
```

**Example / 示例**

```ts
objToQString({ a: 1, b: 2 })     // 'a=1&b=2'
objToQString({ name: '张三', age: 18 }) // 'name=%E5%BC%A0%E4%B8%89&age=18'
objToQString({ ids: [1, 2, 3] }) // 'ids=1&ids=2&ids=3'
```

---

### qStringToObj

URL 字符串转对象 / Query string to object

```ts
function qStringToObj(queryString: string): object
```

**Example / 示例**

```ts
qStringToObj('a=1&b=2')              // { a: '1', b: '2' }
qStringToObj('name=%E5%BC%A0%E4%B8%89') // { name: '张三' }
qStringToObj('ids=1&ids=2&ids=3')     // { ids: ['1', '2', '3'] }
```

---

### blobToBase64

Blob 转 Base64 / Blob to Base64

```ts
function blobToBase64(blob: Blob, ignorePrefix?: boolean): string
```

**Parameters / 参数**

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `blob` | `Blob` | - | Blob 对象 |
| `ignorePrefix` | `boolean` | `false` | 是否忽略 data URL 前缀 |

**Example / 示例**

```ts
// 完整 Base64 (包含前缀)
blobToBase64(blob)
// 'data:image/png;base64,iVBORw0KGgo...'

// 纯 Base64 (无前缀)
blobToBase64(blob, true)
// 'iVBORw0KGgo...'
```

---

## Use Cases / 使用场景

### 表单数据处理 / Form Data Processing

```ts
// 只提交表单中的部分字段
const formData = { name: '', email: '', password: '', remember: true }
const submitData = pick(formData, ['name', 'email', 'password'])
// { name: '', email: '', password: '' }

// 排除敏感字段
const userData = { id: 1, name: '张三', password: 'xxx', token: 'xxx' }
const publicData = omit(userData, ['password', 'token'])
// { id: 1, name: '张三' }
```

### 防抖搜索 / Debounced Search

```ts
const handleSearch = debounce(async (keyword: string) => {
  const results = await searchAPI(keyword)
  setResults(results)
}, 300)

input.addEventListener('input', (e) => {
  handleSearch(e.target.value)
})
```

### 安全获取嵌套属性 / Safe Nested Property Access

```ts
// 传统方式
const value = obj && obj.a && obj.a.b && obj.a.b.c

// 使用 get
const value = get(obj, 'a.b.c', 'default')
```

### 处理 URL 参数 / Handle URL Parameters

```ts
// 解析 URL 参数
const params = qStringToObj(window.location.search.slice(1))

// 构建 URL 参数
const query = objToQString({ page: 1, size: 10, keyword: 'test' })
// 'page=1&size=10&keyword=test'
```

### 图片上传预览 / Image Upload Preview

```ts
fileInput.addEventListener('change', async (e) => {
  const file = e.target.files[0]
  const base64 = await fileToBase64(file)
  img.src = base64
})

function fileToBase64(file: File): Promise<string> {
  return new Promise((resolve) => {
    const reader = new FileReader()
    reader.onload = () => {
      resolve(reader.result as string)
    }
    reader.readAsDataURL(file)
  })
}
```
