0%

函数与作用域进阶

目标:彻底吃透函数的声明/调用/作用域/闭包/this,能在真实业务中写出可维护、无坑的函数代码。


1. 函数基础与声明方式

1.1 函数声明(Function Declaration)

1.1.1 基本语法与特性

  • 语法function foo() { ... }
  • 提升规则:整体被提升(hoisting),函数体可在声明前调用。
  • 作用域:函数声明会创建一个绑定在当前作用域的标识符。
  • 适用场景:公共工具函数、需在文件顶部就能调用的函数、需要递归调用的函数。

1.1.2 提升机制详解

函数声明的提升是完整的提升,包括函数名和函数体:

1
2
3
4
5
6
7
8
9
10
11
12
// 在代码执行前,JavaScript 引擎会这样处理:
say(); // ✅ 可以调用,因为声明整体提升

function say() {
console.log('hi');
}

// 等价于(引擎内部处理):
function say() { // 整个函数被提升到作用域顶部
console.log('hi');
}
say(); // 现在可以正常调用

1.1.3 函数声明的条件提升

函数声明在块级作用域中的行为(ES6+):

1
2
3
4
5
6
7
8
9
10
11
12
// 在 if 块中的函数声明
if (true) {
function foo() { console.log('foo'); }
}
foo(); // 行为取决于环境,严格模式下可能报错

// 更安全的做法:使用函数表达式
let foo;
if (true) {
foo = function() { console.log('foo'); };
}
foo(); // ✅ 安全

1.1.4 函数声明的重复声明

1
2
3
4
5
6
7
8
function foo() { return 1; }
function foo() { return 2; } // 后面的会覆盖前面的
console.log(foo()); // 2

// 在严格模式下,重复声明会报错(某些环境)
"use strict";
function bar() { return 1; }
// function bar() { return 2; } // SyntaxError

1.1.5 函数声明的实际应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 场景1:工具函数库
function formatDate(date) {
return new Intl.DateTimeFormat('zh-CN').format(date);
}

function formatCurrency(amount) {
return new Intl.NumberFormat('zh-CN', {
style: 'currency',
currency: 'CNY'
}).format(amount);
}

// 场景2:递归函数
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1); // 函数名在函数体内可用
}

1.2 函数表达式(Function Expression)

1.2.1 基本语法与特性

  • 语法const foo = function() { ... };
  • 提升规则:只提升变量声明(var 提升为 undefinedlet/const 进入暂时性死区),函数值不会提升。
  • 适用场景:需要控制创建时机、避免过度提升导致的调用混乱、条件性创建函数。

1.2.2 变量提升 vs 函数提升

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用 var(不推荐,仅作演示)
console.log(foo); // undefined(变量提升)
// foo(); // TypeError: foo is not a function
var foo = function() {
console.log('later created');
};
foo(); // ✅ 正常调用

// 使用 const/let(推荐)
// console.log(bar); // ReferenceError: Cannot access 'bar' before initialization
const bar = function() {
console.log('later created');
};
bar(); // ✅ 正常调用

1.2.3 函数表达式的优势

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 优势1:条件性创建
let handler;
if (condition) {
handler = function() { console.log('A'); };
} else {
handler = function() { console.log('B'); };
}

// 优势2:避免命名冲突
(function() {
const localFunction = function() { /* ... */ };
// 不会污染外部作用域
})();

// 优势3:动态函数创建
const createValidator = (rule) => {
return function(value) {
return rule.test(value);
};
};

1.2.4 函数表达式的性能考虑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 每次循环都创建新函数(性能开销)
const handlers = [];
for (let i = 0; i < 1000; i++) {
handlers.push(function() { return i; });
}

// 优化:提取函数到外部
function createHandler(i) {
return function() { return i; };
}
const optimizedHandlers = [];
for (let i = 0; i < 1000; i++) {
optimizedHandlers.push(createHandler(i));
}

1.3 命名函数表达式(Named Function Expression, NFE)

1.3.1 基本语法

  • 语法const foo = function bar() { ... };
  • 作用bar 仅在函数内部可见,便于递归或调试堆栈。
  • 调试优势:在调用栈中显示有意义的函数名。

1.3.2 内部名称的作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const foo = function bar() {
console.log(typeof bar); // "function" - 在函数内部可用
// bar(); // 可以递归调用
};

console.log(typeof bar); // "undefined" - 外部不可访问
foo(); // ✅ 正常调用

// 实际应用:递归
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1); // 使用内部名称递归
};
console.log(factorial(5)); // 120

1.3.3 调试堆栈中的表现

1
2
3
4
5
6
7
8
9
10
11
// 匿名函数表达式
const anonymous = function() {
throw new Error('test');
};
// 调用栈显示:anonymous (匿名)

// 命名函数表达式
const named = function myFunction() {
throw new Error('test');
};
// 调用栈显示:myFunction(更有意义)

1.3.4 命名函数表达式的陷阱

1
2
3
4
5
6
7
8
9
10
11
// 陷阱:在严格模式下,内部名称不能与外部变量冲突
"use strict";
const foo = function foo() { // ✅ 允许,但容易混淆
// ...
};

// 更好的做法:使用不同的名称
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1);
};

1.4 参数默认值与剩余参数

1.4.1 默认值的基本规则

默认值只在实参为 undefined 时触发(null0false'' 等都不触发):

1
2
3
4
5
6
7
8
9
10
function f(x = 1) {
return x;
}

f(undefined); // 1 ✅ 使用默认值
f(null); // null ❌ 不使用默认值
f(0); // 0 ❌ 不使用默认值
f(false); // false ❌ 不使用默认值
f(''); // '' ❌ 不使用默认值
f(); // 1 ✅ 使用默认值(等同于 f(undefined))

1.4.2 默认值的惰性求值

默认值是惰性求值:每次调用都会重新计算表达式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 每次调用都得到新的时间戳
function logTime(x = Date.now()) {
console.log(x);
}
logTime(); // 1704067200000
setTimeout(() => logTime(), 1000); // 1704067201000(不同的值)

// 每次调用都创建新数组
function processItems(items = []) {
items.push('new');
return items;
}
const result1 = processItems(); // ['new']
const result2 = processItems(); // ['new'] - 新数组,不是 ['new', 'new']

1.4.3 默认值可以引用前面的参数

1
2
3
4
5
6
7
function createUser(name, displayName = name.toUpperCase()) {
return { name, displayName };
}
createUser('alice'); // { name: 'alice', displayName: 'ALICE' }

// 但不能引用后面的参数
// function bad(a = b, b = 1) {} // ReferenceError: Cannot access 'b' before initialization

1.4.4 解构结合默认值

防止解构 undefined 报错,同时为解构的属性提供默认值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 基础用法
function createUser({ name = '匿名', role = 'guest' } = {}) {
return { name, role };
}
createUser(); // { name: '匿名', role: 'guest' }
createUser({}); // { name: '匿名', role: 'guest' }
createUser({ name: 'Alice' }); // { name: 'Alice', role: 'guest' }

// 嵌套解构
function processConfig({
api: {
baseURL = 'https://api.example.com',
timeout = 5000
} = {},
cache = true
} = {}) {
return { baseURL, timeout, cache };
}

// 复杂场景
function fetchData({
url,
method = 'GET',
headers = {},
body = null,
timeout = 5000
} = {}) {
if (!url) throw new Error('URL is required');
// 使用参数...
}

1.4.5 剩余参数(Rest Parameters)

剩余参数 ...rest 必须在形参末尾,得到一个真数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 基本用法
function sum(base, ...nums) {
return nums.reduce((a, b) => a + b, base);
}
sum(10, 1, 2, 3); // 16

// 剩余参数必须是最后一个
// function bad(...rest, last) {} // SyntaxError

// 剩余参数是真正的数组
function logArgs(...args) {
console.log(Array.isArray(args)); // true
console.log(args.map(x => x * 2)); // 可以直接使用数组方法
}

// 实际应用:函数组合
function compose(...fns) {
return function(value) {
return fns.reduceRight((acc, fn) => fn(acc), value);
};
}
const add1 = x => x + 1;
const multiply2 = x => x * 2;
const add1ThenMultiply2 = compose(multiply2, add1);
console.log(add1ThenMultiply2(5)); // 12

1.4.6 默认值与剩余参数的组合

1
2
3
4
5
6
7
8
9
10
// 剩余参数可以有默认值(虽然不常用)
function process(...items = []) {
// items 总是数组,但默认值实际上不会用到
// 因为不传参数时,items 就是 []
}

// 更实用的模式:必需参数 + 可选参数对象
function apiCall(url, { method = 'GET', headers = {}, body = null } = {}) {
// url 是必需的,其他都有默认值
}

1.5 箭头函数 vs 普通函数

1.5.1 箭头函数的完整特性列表

箭头函数特点:

  • **没有自己的 this**:沿用定义处的外层 this(词法绑定),无法通过 call/apply/bind 改变。
  • **没有 arguments**:需用剩余参数 ...args
  • **不能 new**:没有 prototype,也不能做构造函数。
  • **不能用 yield**:不能是生成器函数。
  • **没有 super**:不能用于类方法(除非是静态方法)。
  • **没有 new.target**:无法判断是否通过 new 调用。

1.5.2 this 绑定的差异

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 普通函数:this 取决于调用方式
const obj = {
name: 'Object',
regular: function() {
console.log(this.name);
},
arrow: () => {
console.log(this.name); // this 指向外层(全局或模块)
}
};

obj.regular(); // "Object" ✅
obj.arrow(); // undefined 或 window.name ❌

// 箭头函数捕获外层 this
function Timer() {
this.seconds = 0;

// 普通函数:需要手动绑定
setInterval(function() {
this.seconds++; // ❌ this 指向全局对象
}, 1000);

// 箭头函数:自动捕获
setInterval(() => {
this.seconds++; // ✅ this 指向 Timer 实例
}, 1000);
}

1.5.3 arguments 的差异

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 普通函数:有 arguments
function regular() {
console.log(arguments); // 类数组对象
console.log(arguments[0]); // 第一个参数
}

// 箭头函数:没有 arguments
const arrow = () => {
// console.log(arguments); // ReferenceError
// 必须使用剩余参数
};

const arrowWithRest = (...args) => {
console.log(args); // 真正的数组
console.log(args[0]); // 第一个参数
};

1.5.4 构造函数能力

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 普通函数:可以作为构造函数
function Person(name) {
this.name = name;
}
const person = new Person('Alice'); // ✅

// 箭头函数:不能作为构造函数
const ArrowPerson = (name) => {
this.name = name; // 即使写了,也不能 new
};
// new ArrowPerson('Alice'); // TypeError: ArrowPerson is not a constructor

// 检查
console.log(Person.prototype); // { constructor: Person }
console.log(ArrowPerson.prototype); // undefined

1.5.5 适用场景详解

箭头函数适合:

  • 回调函数(需要捕获外层 this
  • 高阶函数的参数
  • 简短的工具函数
  • 函数式编程风格
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 场景1:数组方法回调
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2); // ✅ 简洁

// 场景2:事件处理器(需要捕获组件 this)
class Component {
constructor() {
this.value = 0;
// 箭头函数自动绑定 this
this.handleClick = () => {
console.log(this.value); // ✅ this 指向组件实例
};
}
}

// 场景3:函数式编程
const pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value);

箭头函数不适合:

  • 对象方法(需要动态 this
  • 构造函数
  • 需要 arguments 的函数
  • 需要 prototype 的函数
  • 生成器函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ❌ 不适合:对象方法
const obj = {
value: 1,
bad: () => {
return this.value; // this 不指向 obj
},
good: function() {
return this.value; // ✅ this 指向 obj
}
};

// ❌ 不适合:需要动态 this
document.addEventListener('click', function(event) {
console.log(this); // ✅ this 指向 document
});

document.addEventListener('click', (event) => {
console.log(this); // ❌ this 指向外层,不是 document
});

1.5.6 返回对象字面量的语法

1
2
3
4
5
6
7
8
9
10
11
12
// 错误:会被解析为函数体
const bad = () => { value: 1 }; // 返回 undefined

// 正确:加括号
const good = () => ({ value: 1 }); // 返回 { value: 1 }

// 多行对象
const createConfig = () => ({
api: 'https://api.example.com',
timeout: 5000,
retries: 3
});

1.5.7 箭头函数的性能考虑

1
2
3
4
5
6
7
8
9
10
11
// 箭头函数通常更轻量(没有 prototype、arguments 等)
// 但在某些情况下,普通函数可能被优化得更好

// 大量调用时,考虑性能
function heavyComputation(x) {
// 复杂计算...
return x * 2;
}

// 如果不需要 this/arguments,箭头函数更简洁
const lightComputation = (x) => x * 2;

1.6 小结与易错点

1.6.1 选择指南

场景 推荐 原因
需要提升 函数声明 整体提升,可在声明前调用
需要延迟创建 函数表达式 控制创建时机
需要递归 函数声明或命名函数表达式 函数名在内部可用
需要动态 this 普通函数 箭头函数无法改变 this
需要捕获外层 this 箭头函数 词法绑定
需要 arguments 普通函数 箭头函数没有 arguments
需要作为构造函数 普通函数 箭头函数不能 new

1.6.2 常见易错点

错误1:混淆提升行为

1
2
3
4
5
6
// ❌ 错误理解
foo(); // 以为会报错
const foo = function() {};

// ✅ 正确理解
// const/let 声明的函数表达式不会提升,会报错

错误2:默认值判断

1
2
3
4
5
6
// ❌ 错误:以为 null 会触发默认值
function f(x = 1) {}
f(null); // null,不是 1

// ✅ 正确:只有 undefined 触发
f(undefined); // 1

错误3:箭头函数作为对象方法

1
2
3
4
5
6
7
8
9
10
11
// ❌ 错误
const obj = {
value: 1,
getValue: () => this.value // this 不指向 obj
};

// ✅ 正确
const obj = {
value: 1,
getValue() { return this.value; } // 方法简写
};

错误4:剩余参数位置

1
2
3
4
5
// ❌ 错误
function bad(first, ...rest, last) {} // SyntaxError

// ✅ 正确
function good(first, ...rest) {} // rest 必须在最后

2. 高阶函数、回调与递归

2.1 高阶函数是什么

2.1.1 定义与分类

定义:接收函数作为参数,或返回一个函数,或两者兼有的函数。

分类

  1. 接收函数作为参数:如 mapfilterforEach
  2. 返回函数:如 bind、函数工厂
  3. 两者兼有:如 composecurry

2.1.2 高阶函数的价值

  • 复用控制逻辑:将通用模式抽象出来
  • 抽象操作:隐藏实现细节,关注做什么而非怎么做
  • 组合行为:通过组合小函数构建复杂功能
  • 提高可读性:声明式编程风格

2.1.3 实际应用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 场景1:抽象异步操作
function withRetry(fn, maxRetries = 3) {
return async function(...args) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return await fn(...args);
} catch (error) {
lastError = error;
if (i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
throw lastError;
};
}

// 场景2:抽象错误处理
function withErrorHandling(fn, errorHandler) {
return function(...args) {
try {
return fn(...args);
} catch (error) {
return errorHandler(error, args);
}
};
}

// 场景3:抽象性能监控
function withPerformanceMonitor(fn, label) {
return function(...args) {
const start = performance.now();
const result = fn(...args);
const end = performance.now();
console.log(`${label} took ${end - start}ms`);
return result;
};
}

2.2 常见内置高阶函数

2.2.1 Array.prototype.map

逐个变换,返回新数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 基本用法
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8]

// 带索引
const indexed = numbers.map((n, i) => `${i}: ${n}`); // ["0: 1", "1: 2", ...]

// 转换对象数组
const users = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 }
];
const names = users.map(user => user.name); // ["Alice", "Bob"]

// 链式调用
const result = numbers
.map(n => n * 2)
.map(n => n + 1)
.filter(n => n > 5); // [7, 9]

2.2.2 Array.prototype.filter

按条件保留,返回新数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 基本用法
const numbers = [1, 2, 3, 4, 5, 6];
const evens = numbers.filter(n => n % 2 === 0); // [2, 4, 6]

// 过滤对象数组
const users = [
{ name: 'Alice', age: 25, active: true },
{ name: 'Bob', age: 30, active: false },
{ name: 'Charlie', age: 20, active: true }
];
const activeUsers = users.filter(user => user.active);

// 过滤掉 falsy 值
const mixed = [0, 1, false, 2, '', 3, null, undefined];
const truthy = mixed.filter(Boolean); // [1, 2, 3]

2.2.3 Array.prototype.reduce

累积器模式,可做求和、分组、统计:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 基本用法:求和
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, n) => acc + n, 0); // 15

// 分组
const users = [
{ name: 'Alice', role: 'admin' },
{ name: 'Bob', role: 'user' },
{ name: 'Charlie', role: 'admin' }
];
const grouped = users.reduce((acc, user) => {
if (!acc[user.role]) acc[user.role] = [];
acc[user.role].push(user);
return acc;
}, {}); // { admin: [...], user: [...] }

// 统计
const words = ['apple', 'banana', 'apple', 'cherry', 'banana'];
const count = words.reduce((acc, word) => {
acc[word] = (acc[word] || 0) + 1;
return acc;
}, {}); // { apple: 2, banana: 2, cherry: 1 }

// 扁平化
const nested = [[1, 2], [3, 4], [5, 6]];
const flat = nested.reduce((acc, arr) => acc.concat(arr), []); // [1, 2, 3, 4, 5, 6]

// 管道操作
const pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value);

2.2.4 其他常见高阶函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// forEach:遍历执行副作用
const numbers = [1, 2, 3];
numbers.forEach(n => console.log(n)); // 1, 2, 3

// find:查找第一个匹配项
const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
const user = users.find(u => u.id === 2); // { id: 2, name: 'Bob' }

// some:至少一个满足条件
const hasEven = numbers.some(n => n % 2 === 0); // true

// every:全部满足条件
const allPositive = numbers.every(n => n > 0); // true

// sort:排序(会修改原数组)
const unsorted = [3, 1, 4, 1, 5];
const sorted = [...unsorted].sort((a, b) => a - b); // [1, 1, 3, 4, 5]

2.2.5 手写高阶函数示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 示例1:带日志的高阶函数
function withLog(fn) {
return function (...args) {
console.log('start', args);
const res = fn.apply(this, args);
console.log('end', res);
return res;
};
}
const add = (a, b) => a + b;
const addWithLog = withLog(add);
addWithLog(1, 2); // start [1,2]; end 3

// 示例2:带缓存的高阶函数
function withCache(fn, keyGenerator = JSON.stringify) {
const cache = new Map();
return function(...args) {
const key = keyGenerator(args);
if (cache.has(key)) {
console.log('Cache hit');
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}

// 示例3:带重试的高阶函数
function withRetry(fn, maxRetries = 3) {
return async function(...args) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return await fn.apply(this, args);
} catch (error) {
lastError = error;
if (i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
throw lastError;
};
}

// 示例4:函数组合
function compose(...fns) {
return function(value) {
return fns.reduceRight((acc, fn) => fn(acc), value);
};
}
const add1 = x => x + 1;
const multiply2 = x => x * 2;
const add1ThenMultiply2 = compose(multiply2, add1);
console.log(add1ThenMultiply2(5)); // 12

2.3 递归函数

2.3.1 递归的三要素

  1. 终止条件(不写会栈溢出)
  2. 缩小问题规模(向终止条件逼近)
  3. 递归调用自身

2.3.2 基础递归示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 示例1:阶乘
function factorial(n) {
if (n <= 1) return 1; // 终止条件
return n * factorial(n - 1); // 缩小规模 + 递归调用
}
console.log(factorial(5)); // 120

// 示例2:斐波那契数列(低效版本)
function fibonacci(n) {
if (n <= 1) return n; // 终止条件
return fibonacci(n - 1) + fibonacci(n - 2); // 递归调用
}
// 注意:这个版本效率很低,会重复计算

// 示例3:数组求和
function sumArray(arr) {
if (arr.length === 0) return 0; // 终止条件
return arr[0] + sumArray(arr.slice(1)); // 缩小规模 + 递归调用
}
console.log(sumArray([1, 2, 3, 4])); // 10

2.3.3 尾递归优化

1
2
3
4
5
6
7
8
9
10
11
12
13
// 非尾递归:需要保存调用栈
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1); // 需要等待递归结果才能计算
}

// 尾递归:可以优化
function factorialTail(n, acc = 1) {
if (n <= 1) return acc;
return factorialTail(n - 1, n * acc); // 直接返回递归结果
}

// 注意:大多数 JS 引擎未默认开启 TCO,不要依赖

2.3.4 递归转迭代

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 递归版本:深度遍历
function traverseRecursive(node, callback) {
callback(node);
if (node.children) {
node.children.forEach(child => {
traverseRecursive(child, callback);
});
}
}

// 迭代版本:使用显式栈
function traverseIterative(root, callback) {
const stack = [root];
while (stack.length) {
const node = stack.pop();
callback(node);
if (node.children) {
// 反向添加,保持顺序
for (let i = node.children.length - 1; i >= 0; i--) {
stack.push(node.children[i]);
}
}
}
}

// 迭代版本:使用队列(广度优先)
function traverseBFS(root, callback) {
const queue = [root];
while (queue.length) {
const node = queue.shift();
callback(node);
if (node.children) {
queue.push(...node.children);
}
}
}

2.3.5 递归的常见模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 模式1:分治
function quickSort(arr) {
if (arr.length <= 1) return arr;
const pivot = arr[0];
const left = arr.slice(1).filter(x => x < pivot);
const right = arr.slice(1).filter(x => x >= pivot);
return [...quickSort(left), pivot, ...quickSort(right)];
}

// 模式2:回溯
function findPath(maze, start, end) {
const visited = new Set();

function dfs(x, y, path) {
if (x === end.x && y === end.y) return path;
if (visited.has(`${x},${y}`) || maze[x][y] === 1) return null;

visited.add(`${x},${y}`);
const directions = [[0, 1], [1, 0], [0, -1], [-1, 0]];

for (const [dx, dy] of directions) {
const newPath = dfs(x + dx, y + dy, [...path, [x + dx, y + dy]]);
if (newPath) return newPath;
}

visited.delete(`${x},${y}`);
return null;
}

return dfs(start.x, start.y, [[start.x, start.y]]);
}

// 模式3:树遍历
function treeMap(node, fn) {
const newNode = fn(node);
if (node.children) {
newNode.children = node.children.map(child => treeMap(child, fn));
}
return newNode;
}

2.3.6 递归的性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 问题:重复计算
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2); // 大量重复计算
}

// 优化1:记忆化
function fibonacciMemo(n, memo = {}) {
if (n in memo) return memo[n];
if (n <= 1) return n;
memo[n] = fibonacciMemo(n - 1, memo) + fibonacciMemo(n - 2, memo);
return memo[n];
}

// 优化2:迭代版本
function fibonacciIterative(n) {
if (n <= 1) return n;
let a = 0, b = 1;
for (let i = 2; i <= n; i++) {
[a, b] = [b, a + b];
}
return b;
}

2.3.7 递归的注意事项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 注意1:栈溢出
function infiniteRecursion() {
return infiniteRecursion(); // RangeError: Maximum call stack size exceeded
}

// 注意2:确保终止条件
function badFactorial(n) {
// 缺少终止条件检查
return n * badFactorial(n - 1); // 会一直递归直到栈溢出
}

// 注意3:参数验证
function safeFactorial(n) {
if (typeof n !== 'number' || n < 0 || !Number.isInteger(n)) {
throw new Error('Invalid input');
}
if (n <= 1) return 1;
return n * safeFactorial(n - 1);
}

3. 作用域、执行上下文与闭包

3.1 作用域类型

3.1.1 全局作用域

在任意地方可见(浏览器全局是 window,Node.js 是 global):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 全局变量
var globalVar = 'global';
let globalLet = 'global';
const globalConst = 'global';

function test() {
console.log(globalVar); // 'global'
console.log(globalLet); // 'global'
console.log(globalConst); // 'global'
}

// 在浏览器中
console.log(window.globalVar); // 'global'(var 声明的会挂到 window)
// console.log(window.globalLet); // undefined(let/const 不会挂到 window)

3.1.2 函数作用域

var 声明的变量仅在函数内可见:

1
2
3
4
5
6
7
8
9
10
function test() {
var functionScoped = 'inside function';
if (true) {
var alsoFunctionScoped = 'also inside function';
}
console.log(functionScoped); // 'inside function'
console.log(alsoFunctionScoped); // 'also inside function'(var 无视块)
}

// console.log(functionScoped); // ReferenceError

3.1.3 块级作用域

let/const 声明在最近的花括号 {} 内有效;有暂时性死区:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 块级作用域
{
let blockScoped = 'inside block';
const alsoBlockScoped = 'also inside block';
console.log(blockScoped); // 'inside block'
console.log(alsoBlockScoped); // 'also inside block'
}
// console.log(blockScoped); // ReferenceError

// 暂时性死区(Temporal Dead Zone, TDZ)
// console.log(tdz); // ReferenceError: Cannot access 'tdz' before initialization
let tdz = 'value';

// 循环中的块级作用域
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 0, 1, 2(每次循环创建新的 i)
}

// var 的问题
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 3, 3, 3(共享同一个 i)
}

3.1.4 作用域链

查找变量时,先看当前上下文的作用域,再沿着作用域链向外层父作用域查找:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const global = 'global';

function outer() {
const outerVar = 'outer';

function inner() {
const innerVar = 'inner';
console.log(innerVar); // 'inner' - 当前作用域
console.log(outerVar); // 'outer' - 外层作用域
console.log(global); // 'global' - 全局作用域
}

inner();
}

outer();

3.2 执行上下文与调用栈

3.2.1 执行上下文的概念

每次调用函数都会创建执行上下文(Execution Context),包含:

  • 变量环境(Variable Environment):var 声明
  • 词法环境(Lexical Environment):let/const 声明
  • this 绑定
  • 外部环境引用(Outer Environment Reference):形成作用域链

3.2.2 调用栈(Call Stack)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function first() {
console.log('first');
second();
console.log('first end');
}

function second() {
console.log('second');
third();
console.log('second end');
}

function third() {
console.log('third');
}

first();
// 调用栈变化:
// [first] -> [first, second] -> [first, second, third]
// -> [first, second] -> [first] -> []

3.2.3 变量查找过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const globalVar = 'global';

function outer() {
const outerVar = 'outer';

function inner() {
// 查找过程:
// 1. 当前作用域(inner)-> 找不到 innerVar
// 2. 外层作用域(outer)-> 找到 outerVar
// 3. 如果还找不到,继续向上到全局作用域
console.log(outerVar); // 'outer'
}

inner();
}

outer();

3.2.4 执行上下文的创建阶段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 在函数执行前,会进行"提升"(Hoisting)
function example() {
// 创建阶段:
// 1. 创建变量环境(var 声明提升,值为 undefined)
// 2. 创建词法环境(let/const 进入 TDZ)
// 3. 绑定 this
// 4. 设置外部环境引用

console.log(a); // undefined(var 提升)
// console.log(b); // ReferenceError(let 在 TDZ)

var a = 1;
let b = 2;
}

example();

3.3 闭包(Closure)是什么

3.3.1 闭包的定义

定义:函数”记住”了它被定义时的词法作用域环境,而不是调用时的环境。

直观理解:函数把外层变量”打包带走”,即使外层函数早已返回。

3.3.2 基础闭包示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 示例1:计数器
function createCounter(start = 0) {
let count = start; // 被闭包捕获
return function inc(step = 1) { // 返回的函数形成闭包
count += step;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

// 示例2:多个闭包共享变量
function createSharedState() {
let state = 0;
return {
increment: () => ++state,
decrement: () => --state,
getValue: () => state
};
}
const shared = createSharedState();
console.log(shared.increment()); // 1
console.log(shared.increment()); // 2
console.log(shared.getValue()); // 2

3.3.3 闭包的形成条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 条件1:内部函数引用外部变量
function outer() {
const outerVar = 'outer';
function inner() {
console.log(outerVar); // 引用外部变量,形成闭包
}
return inner;
}

// 条件2:内部函数被返回或传递
function createClosure() {
const secret = 'secret';
return function() {
return secret; // 闭包保持 secret 的引用
};
}

// 即使外层函数执行完毕,闭包仍然持有变量引用
const getSecret = createClosure();
console.log(getSecret()); // 'secret' - 外层函数已返回,但变量仍可访问

3.3.4 闭包的实际应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 应用1:数据私有化
function createBankAccount(initialBalance) {
let balance = initialBalance; // 私有变量

return {
deposit(amount) {
if (amount > 0) balance += amount;
return balance;
},
withdraw(amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return balance;
}
throw new Error('Insufficient funds');
},
getBalance() {
return balance;
}
};
}

const account = createBankAccount(100);
console.log(account.getBalance()); // 100
account.deposit(50);
console.log(account.getBalance()); // 150
// console.log(balance); // ReferenceError - 无法直接访问

// 应用2:函数工厂
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15

// 应用3:模块模式(早期实现)
const module = (function() {
let privateVar = 0;

function privateFunction() {
return privateVar;
}

return {
publicMethod() {
return privateFunction();
},
setValue(val) {
privateVar = val;
}
};
})();

3.4 闭包的常见用途

3.4.1 数据私有化

外部只能通过函数访问内部状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function createPerson(name) {
let age = 0; // 私有变量

return {
getName() { return name; },
getAge() { return age; },
haveBirthday() { age++; },
// 无法直接修改 name 和 age
};
}

const person = createPerson('Alice');
console.log(person.getName()); // 'Alice'
person.haveBirthday();
console.log(person.getAge()); // 1
// person.age = 100; // 无效,age 是私有的

3.4.2 惰性计算/缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 惰性计算
function createLazyValue(computeFn) {
let cachedValue = null;
let computed = false;

return {
get() {
if (!computed) {
cachedValue = computeFn();
computed = true;
}
return cachedValue;
},
reset() {
computed = false;
cachedValue = null;
}
};
}

const expensive = createLazyValue(() => {
console.log('Computing...');
return Math.random() * 1000;
});
console.log(expensive.get()); // Computing... 0.123
console.log(expensive.get()); // 0.123(不再计算)

// 缓存(Memoize)
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}

3.4.3 函数工厂

根据配置返回行为不同的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function createValidator(rules) {
return function(value) {
for (const rule of rules) {
if (!rule.test(value)) {
return { valid: false, error: rule.message };
}
}
return { valid: true };
};
}

const emailValidator = createValidator([
{ test: v => v.includes('@'), message: 'Must contain @' },
{ test: v => v.length > 5, message: 'Too short' }
]);

console.log(emailValidator('test@example.com')); // { valid: true }
console.log(emailValidator('test')); // { valid: false, error: 'Must contain @' }

3.4.4 模拟模块

早期 IIFE + 闭包实现私有变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const MyModule = (function() {
// 私有变量
let privateVar = 0;
const privateConst = 'secret';

// 私有函数
function privateFunction() {
return privateVar;
}

// 公共 API
return {
publicMethod() {
return privateFunction();
},
setValue(val) {
privateVar = val;
},
getConstant() {
return privateConst;
}
};
})();

console.log(MyModule.publicMethod()); // 0
MyModule.setValue(42);
console.log(MyModule.publicMethod()); // 42
// console.log(MyModule.privateVar); // undefined

3.5 闭包常见坑

3.5.1 循环与 var

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 问题:var 共享一个函数作用域
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 3, 3, 3
}

// 解决1:使用 let(每次循环创建新的块级作用域)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0, 1, 2
}

// 解决2:使用 IIFE
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 100); // 0, 1, 2
})(i);
}

// 解决3:使用 bind
for (var i = 0; i < 3; i++) {
setTimeout(function(j) {
console.log(j);
}.bind(null, i), 100); // 0, 1, 2
}

3.5.2 内存占用

闭包长时间存在会持有外部变量引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 问题:大量闭包持有大对象引用
function createHandlers() {
const largeData = new Array(1000000).fill(0); // 大对象
return Array.from({ length: 1000 }, (_, i) => {
return function() {
// 即使不使用 largeData,闭包也会持有引用
console.log(i);
};
});
}

// 解决:不需要的变量不要引用
function createHandlersOptimized() {
return Array.from({ length: 1000 }, (_, i) => {
return function() {
console.log(i); // 只引用需要的变量
};
});
}

// 或者:不再需要时将引用置为 null
let handlers = createHandlers();
// 使用完毕后
handlers = null; // 帮助垃圾回收

3.5.3 意外的闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 问题:意外的闭包捕获
const buttons = document.querySelectorAll('button');
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function() {
console.log(i); // 如果使用 var,所有按钮都打印最后一个 i
});
}

// 解决:使用 let 或立即绑定
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', (function(index) {
return function() {
console.log(index);
};
})(i));
}

3.5.4 闭包与 this 的混淆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 问题:闭包中的 this
const obj = {
value: 1,
getValue: function() {
return function() {
return this.value; // this 不是 obj
};
}
};
console.log(obj.getValue()()); // undefined(严格模式)或 window.value

// 解决1:箭头函数
const obj2 = {
value: 1,
getValue: function() {
return () => {
return this.value; // this 是 obj2
};
}
};
console.log(obj2.getValue()()); // 1

// 解决2:保存 this
const obj3 = {
value: 1,
getValue: function() {
const self = this;
return function() {
return self.value;
};
}
};
console.log(obj3.getValue()()); // 1

4. 特殊函数特性

4.1 IIFE(立即执行函数表达式)

4.1.1 基本概念

IIFE(Immediately Invoked Function Expression)立即执行函数表达式,作用:立刻创建一个独立作用域,避免全局变量污染。

4.1.2 语法形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 形式1:经典形式
(function () {
const hidden = 1;
console.log('init');
})();

// 形式2:箭头函数
(() => {
console.log('arrow iife');
})();

// 形式3:一元运算符
!function() {
console.log('negation');
}();

+function() {
console.log('unary plus');
}();

// 形式4:void 运算符
void function() {
console.log('void');
}();

4.1.3 实际应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 应用1:避免全局污染
(function() {
const privateVar = 'private';
function privateFunction() {
return privateVar;
}
// 只暴露需要的
window.MyLibrary = {
publicMethod: privateFunction
};
})();

// 应用2:模块化(早期)
const Module = (function() {
let state = 0;
return {
increment() { state++; },
getState() { return state; }
};
})();

// 应用3:循环中的闭包
for (var i = 0; i < 3; i++) {
(function(index) {
setTimeout(() => console.log(index), 100);
})(i); // 0, 1, 2
}

// 应用4:初始化代码
(function() {
// 初始化逻辑
const config = loadConfig();
setupApp(config);
})();

4.1.4 带参数的 IIFE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 传递参数
(function(global, $) {
// 使用 global 和 $,避免查找全局变量
global.MyApp = {
init: function() {
$('.button').on('click', this.handleClick);
}
};
})(window, jQuery);

// 传递依赖
(function(deps) {
const { api, logger } = deps;
// 使用依赖
})({
api: MyAPI,
logger: MyLogger
});

4.2 arguments 对象

4.2.1 基本特性

arguments类数组对象,有 length 和按索引访问,但没有数组原型方法:

1
2
3
4
5
6
7
8
9
function example() {
console.log(arguments.length); // 实参个数
console.log(arguments[0]); // 第一个参数
console.log(arguments[1]); // 第二个参数
console.log(Array.isArray(arguments)); // false
// arguments.map(...); // TypeError: arguments.map is not a function
}

example(1, 2, 3);

4.2.2 转换为数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function convertArguments() {
// 方法1:Array.from
const arr1 = Array.from(arguments);

// 方法2:展开运算符
const arr2 = [...arguments];

// 方法3:Array.prototype.slice
const arr3 = Array.prototype.slice.call(arguments);

// 方法4:旧式方法(不推荐)
const arr4 = Array.prototype.slice.apply(arguments);

return { arr1, arr2, arr3, arr4 };
}

4.2.3 箭头函数中的 arguments

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 箭头函数没有自己的 arguments
const arrow = () => {
// console.log(arguments); // ReferenceError
};

// 箭头函数会从外层捕获 arguments
function outer() {
const inner = () => {
console.log(arguments); // 使用外层的 arguments
};
inner();
}
outer(1, 2, 3); // [1, 2, 3]

// 推荐:使用剩余参数
const arrowWithRest = (...args) => {
console.log(args); // 真正的数组
console.log(args.map(x => x * 2)); // 可以直接使用数组方法
};

4.2.4 arguments 与剩余参数的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// arguments 包含所有实参
function withArguments(a, b) {
console.log(arguments.length); // 3(包含 c)
console.log(arguments[0], arguments[1], arguments[2]); // 1, 2, 3
}
withArguments(1, 2, 3);

// 剩余参数只收集未被单独声明的那部分
function withRest(a, b, ...rest) {
console.log(rest.length); // 1(只有 c)
console.log(rest[0]); // 3
}
withRest(1, 2, 3);

// 实际应用:兼容旧代码
function legacyFunction() {
const args = Array.from(arguments);
// 处理参数...
}

// 现代写法
function modernFunction(...args) {
// 直接使用 args 数组
}

4.2.5 arguments 的特殊行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// arguments 与形参的关联(非严格模式,不推荐)
function linked(a, b) {
arguments[0] = 100;
console.log(a); // 100(非严格模式)
}
linked(1, 2);

// 严格模式下,arguments 与形参解耦
function strictLinked(a, b) {
'use strict';
arguments[0] = 100;
console.log(a); // 1(严格模式,不改变形参)
}
strictLinked(1, 2);

4.3 严格模式 "use strict"

4.3.1 开启方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 方式1:文件顶部
"use strict";
function strictFunction() {
// 整个文件都是严格模式
}

// 方式2:函数内部
function nonStrict() {
// 非严格模式
}

function strictFunction() {
"use strict";
// 只有这个函数是严格模式
}

// 方式3:ESM/模块脚本默认严格模式
// export function moduleFunction() {
// // 自动严格模式
// }

4.3.2 主要影响

1. 禁止隐式创建全局变量

1
2
3
4
"use strict";
function test() {
undeclaredVar = 1; // ReferenceError(非严格模式会创建全局变量)
}

2. 普通函数独立调用时 thisundefined

1
2
3
4
5
6
7
8
9
10
11
"use strict";
function test() {
console.log(this); // undefined(非严格模式是全局对象)
}
test();

// 非严格模式
function nonStrict() {
console.log(this); // window(浏览器)或 global(Node.js)
}
nonStrict();

3. 删除不可删属性会抛错

1
2
"use strict";
delete Object.prototype; // TypeError(非严格模式静默失败)

4. 重复参数名会抛错

1
2
3
4
"use strict";
function bad(a, a) { // SyntaxError
// ...
}

5. with 语句被禁用

1
2
3
4
5
"use strict";
const obj = { x: 1 };
// with (obj) { // SyntaxError
// console.log(x);
// }

6. 八进制字面量被禁用

1
2
3
"use strict";
// const oct = 0123; // SyntaxError(非严格模式允许)
const oct = 0o123; // 使用 0o 前缀

7. evalarguments 不能作为标识符

1
2
3
"use strict";
// const eval = 1; // SyntaxError
// const arguments = 1; // SyntaxError

4.3.3 严格模式的实际应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 推荐:始终使用严格模式
"use strict";

// 好处1:避免意外创建全局变量
function processData(data) {
result = data.map(x => x * 2); // ReferenceError,提醒你声明变量
return result;
}

// 好处2:更安全的 this
function EventHandler() {
this.value = 1;
document.addEventListener('click', function() {
console.log(this); // undefined,提醒你需要绑定
});
}

// 好处3:更好的错误提示
function calculate(a, b) {
// delete a; // TypeError,提醒你参数不能被删除
return a + b;
}

5. this 的绑定规则(逐条对照记忆)

5.1 决定 this 的唯一因素:调用位置

按优先级从高到低:

5.1.1 new 调用

this 指向新创建的实例。箭头函数不能 new

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name) {
this.name = name; // this 指向新创建的实例
}

const person = new Person('Alice');
console.log(person.name); // 'Alice'

// 箭头函数不能 new
const ArrowPerson = (name) => {
this.name = name; // 即使写了,也不能 new
};
// new ArrowPerson('Alice'); // TypeError

5.1.2 显式绑定

call / apply / bind 指定的对象。

1
2
3
4
5
6
7
8
9
10
11
12
function greet() {
console.log(`Hello, ${this.name}`);
}

const obj1 = { name: 'Alice' };
const obj2 = { name: 'Bob' };

greet.call(obj1); // "Hello, Alice"
greet.apply(obj2); // "Hello, Bob"

const bound = greet.bind(obj1);
bound(); // "Hello, Alice"

5.1.3 隐式绑定

作为对象的方法被调用,obj.fn()thisobj

1
2
3
4
5
6
7
8
9
10
11
12
const obj = {
name: 'Object',
greet() {
console.log(`Hello, ${this.name}`);
}
};

obj.greet(); // "Hello, Object"(this 是 obj)

// 丢失隐式绑定
const fn = obj.greet;
fn(); // "Hello, undefined"(严格模式)或 "Hello, "(非严格模式)

5.1.4 默认绑定

独立函数调用,非严格模式下是全局对象,严格模式下是 undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function test() {
console.log(this);
}

test(); // window(非严格模式)或 undefined(严格模式)

// 非严格模式
function nonStrict() {
console.log(this === window); // true(浏览器)
}
nonStrict();

// 严格模式
"use strict";
function strict() {
console.log(this); // undefined
}
strict();

5.1.5 箭头函数

没有自己的 this,直接用定义处外层this,且无法被以上方式改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const obj = {
name: 'Object',
regular: function() {
console.log(this.name); // 'Object'
},
arrow: () => {
console.log(this.name); // undefined(this 是外层,可能是全局)
}
};

obj.regular(); // "Object"
obj.arrow(); // undefined

// 箭头函数的 this 无法改变
const arrow = () => console.log(this);
arrow.call({ x: 1 }); // 仍然是外层 this,不是 { x: 1 }

5.2 事件处理中的 this

5.2.1 DOM 事件处理器

DOM 事件处理器(非箭头)默认指向触发事件的元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// HTML: <button id="btn">Click</button>

const btn = document.getElementById('btn');

// 普通函数:this 指向元素
btn.addEventListener('click', function() {
console.log(this); // <button id="btn">
console.log(this.id); // "btn"
});

// 箭头函数:this 指向外层
btn.addEventListener('click', () => {
console.log(this); // window 或外层 this
// console.log(this.id); // undefined
});

5.2.2 类/组件中的事件处理

在类/组件中用箭头函数做回调,可捕获实例 this,避免手动绑定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// React 类组件示例
class Button extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}

// 方法1:箭头函数(推荐)
handleClick = () => {
this.setState({ count: this.state.count + 1 }); // this 指向实例
};

// 方法2:普通函数 + bind
handleClick2() {
this.setState({ count: this.state.count + 1 });
}
// 在构造函数中:this.handleClick2 = this.handleClick2.bind(this);

render() {
return <button onClick={this.handleClick}>Click</button>;
}
}

// 普通类示例
class Counter {
constructor() {
this.count = 0;
// 箭头函数自动绑定 this
this.increment = () => {
this.count++;
};
}
}

5.3 call / apply / bind

5.3.1 call

即时调用,参数逐个传:

1
2
3
4
5
6
7
8
9
10
11
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}

const obj = { name: 'Alice' };
greet.call(obj, 'Hello', '!'); // "Hello, Alice!"

// 实际应用:借用方法
const numbers = [5, 6, 2, 3, 7];
const max = Math.max.call(null, ...numbers); // 7
// 或直接:Math.max(...numbers)

5.3.2 apply

即时调用,参数数组传:

1
2
3
4
5
6
7
8
9
10
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}

const obj = { name: 'Alice' };
greet.apply(obj, ['Hello', '!']); // "Hello, Alice!"

// 实际应用:传递参数数组
const numbers = [5, 6, 2, 3, 7];
const max = Math.max.apply(null, numbers); // 7(在展开运算符之前常用)

5.3.3 bind

返回一个永久绑定 this 与部分参数的新函数(常用于事件回调、定时器):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}

const obj = { name: 'Alice' };
const bound = greet.bind(obj, 'Hello'); // 绑定 this 和第一个参数
bound('!'); // "Hello, Alice!"

// 实际应用1:事件处理器
class Component {
constructor() {
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(this); // Component 实例
}
}

// 实际应用2:部分应用(Partial Application)
function multiply(a, b, c) {
return a * b * c;
}
const double = multiply.bind(null, 2); // 固定第一个参数
console.log(double(3, 4)); // 24 (2 * 3 * 4)

// 实际应用3:定时器
const obj = {
value: 0,
start() {
setInterval(function() {
this.value++; // this 不是 obj
}, 1000);
},
startFixed() {
setInterval(function() {
this.value++; // this 是 obj
}.bind(this), 1000);
}
};

5.3.4 三者对比

方法 调用时机 参数形式 返回值 用途
call 立即调用 逐个传递 函数返回值 立即调用并指定 this
apply 立即调用 数组传递 函数返回值 立即调用并指定 this(参数是数组)
bind 不调用 逐个传递 新函数 创建绑定 this 的新函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function example(a, b, c) {
console.log(this.name, a, b, c);
}

const obj = { name: 'Test' };

// call
example.call(obj, 1, 2, 3); // Test 1 2 3

// apply
example.apply(obj, [1, 2, 3]); // Test 1 2 3

// bind
const bound = example.bind(obj, 1, 2);
bound(3); // Test 1 2 3

5.4 函数借用(Function Borrowing)

5.4.1 基本概念

借用数组方法处理类数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 借用 Array.prototype.slice
function example() {
const arr = Array.prototype.slice.call(arguments);
console.log(Array.isArray(arr)); // true
return arr.map(x => x * 2);
}
example(1, 2, 3); // [2, 4, 6]

// 借用 Array.prototype.forEach
const nodeList = document.querySelectorAll('div');
Array.prototype.forEach.call(nodeList, (node, index) => {
console.log(`Node ${index}:`, node);
});

5.4.2 常见应用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 场景1:处理 arguments
function sum() {
return Array.prototype.reduce.call(
arguments,
(acc, val) => acc + val,
0
);
}
sum(1, 2, 3, 4); // 10

// 场景2:处理 NodeList
const buttons = document.querySelectorAll('button');
Array.prototype.map.call(buttons, btn => btn.textContent);

// 场景3:处理字符串(类数组)
const str = 'hello';
Array.prototype.forEach.call(str, (char, index) => {
console.log(`${index}: ${char}`);
});

// 场景4:借用其他对象的方法
const obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
const arr = Array.prototype.slice.call(obj); // ['a', 'b', 'c']

5.4.3 现代替代方案

1
2
3
4
5
6
7
8
9
10
11
12
13
// 旧方式:函数借用
const nodeList = document.querySelectorAll('div');
Array.prototype.forEach.call(nodeList, callback);

// 新方式1:Array.from
const nodes = Array.from(nodeList);
nodes.forEach(callback);

// 新方式2:展开运算符
[...nodeList].forEach(callback);

// 新方式3:Array.from 的映射功能
const texts = Array.from(nodeList, node => node.textContent);

5.5 this 绑定的复杂场景

5.5.1 多层调用链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const obj1 = {
name: 'obj1',
obj2: {
name: 'obj2',
greet() {
console.log(this.name); // this 是 obj2,不是 obj1
}
}
};
obj1.obj2.greet(); // "obj2"

// 丢失 this 的情况
const fn = obj1.obj2.greet;
fn(); // undefined(严格模式)

5.5.2 回调函数中的 this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const obj = {
name: 'Object',
data: [1, 2, 3],
process() {
// 问题:回调中的 this 不是 obj
this.data.forEach(function(item) {
console.log(this.name, item); // this 是全局对象或 undefined
});

// 解决1:保存 this
const self = this;
this.data.forEach(function(item) {
console.log(self.name, item); // "Object 1", "Object 2", "Object 3"
});

// 解决2:箭头函数
this.data.forEach(item => {
console.log(this.name, item); // "Object 1", "Object 2", "Object 3"
});

// 解决3:bind
this.data.forEach(function(item) {
console.log(this.name, item);
}.bind(this));
}
};
obj.process();

5.5.3 构造函数中的 this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function Person(name) {
this.name = name;

// 问题:setTimeout 中的 this 不是实例
setTimeout(function() {
console.log(this.name); // undefined 或 window.name
}, 100);

// 解决1:箭头函数
setTimeout(() => {
console.log(this.name); // 正确的 name
}, 100);

// 解决2:bind
setTimeout(function() {
console.log(this.name);
}.bind(this), 100);

// 解决3:保存 this
const self = this;
setTimeout(function() {
console.log(self.name);
}, 100);
}

const person = new Person('Alice');

6. 代码片段(可直接粘贴试验)

6.1 闭包计数器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function createCounter(start = 0) {
let count = start; // 被闭包捕获
return {
inc(step = 1) {
count += step;
return count;
},
reset() {
count = start;
},
value() {
return count;
},
setValue(newValue) {
count = newValue;
}
};
}

const c = createCounter(10);
console.log(c.value()); // 10
c.inc(); // 11
c.inc(5); // 16
c.reset(); // 10
c.setValue(100);
console.log(c.value()); // 100

6.2 节流与防抖(完整版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 节流:固定时间间隔执行
function throttle(fn, wait = 100, options = {}) {
let last = 0;
let timer = null;
const { leading = true, trailing = true } = options;

return function (...args) {
const now = Date.now();

// 首次调用
if (!last && !leading) {
last = now;
}

if (now - last >= wait) {
if (timer) {
clearTimeout(timer);
timer = null;
}
last = now;
return fn.apply(this, args);
} else if (trailing && !timer) {
timer = setTimeout(() => {
last = Date.now();
timer = null;
fn.apply(this, args);
}, wait - (now - last));
}
};
}

// 防抖:延迟执行,频繁调用时重置计时器
function debounce(fn, wait = 200, immediate = false) {
let timer = null;

return function (...args) {
const callNow = immediate && !timer;

clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
if (!immediate) {
fn.apply(this, args);
}
}, wait);

if (callNow) {
fn.apply(this, args);
}
};
}

// 使用示例
const handleScroll = throttle(() => {
console.log('Scrolling...');
}, 100);

const handleInput = debounce((value) => {
console.log('Searching for:', value);
}, 300);

6.3 用闭包做简单缓存(Memoize)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function memoize(fn, keyGenerator = JSON.stringify) {
const cache = new Map();

return function (...args) {
const key = keyGenerator(args);

if (cache.has(key)) {
console.log('Cache hit');
return cache.get(key);
}

console.log('Cache miss');
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}

// 使用示例
const slowAdd = (a, b) => {
// 模拟耗时操作
let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += i;
}
return a + b;
};

const fastAdd = memoize(slowAdd);
console.log(fastAdd(1, 2)); // 首次计算
console.log(fastAdd(1, 2)); // 直接命中缓存

// 带自定义 key 生成器
const memoizedFetch = memoize(
async (url) => {
const response = await fetch(url);
return response.json();
},
(args) => args[0] // 只用 URL 作为 key
);

6.4 arguments 与剩余参数对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 使用 arguments
function demo(a, b) {
console.log('Arguments length:', arguments.length); // 实参个数
console.log('Arguments:', arguments[0], arguments[1]);
console.log('Is array:', Array.isArray(arguments)); // false

// arguments 不是数组,不能直接 map
// arguments.map(x => x * 2); // TypeError

// 需要转换
const arr = Array.from(arguments);
console.log('Converted array:', arr.map(x => x * 2));
}

demo(1, 2, 3); // Arguments length: 3

// 使用剩余参数
function demoRest(a, b, ...args) {
console.log('Rest length:', args.length); // 剩余参数个数
console.log('Rest:', args);
console.log('Is array:', Array.isArray(args)); // true

// 剩余参数是真数组,可以直接使用数组方法
console.log('Mapped:', args.map(x => x * 2));
}

demoRest(1, 2, 3, 4, 5); // Rest length: 3, Rest: [3, 4, 5]

// 实际应用:兼容性处理
function legacySum() {
const args = Array.from(arguments);
return args.reduce((a, b) => a + b, 0);
}

function modernSum(...args) {
return args.reduce((a, b) => a + b, 0);
}

6.5 this 绑定示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const obj = {
x: 1,
show() {
console.log(this.x);
},
arrow: () => {
console.log(this.x); // this 是外层,不是 obj
}
};

// 默认绑定
const fn = obj.show;
fn(); // 严格模式下 undefined;非严格模式下 window.x

// 显式绑定
fn.call({ x: 2 }); // 2
fn.apply({ x: 3 }); // 3

// 隐式绑定
obj.show(); // 1

// bind 绑定
const bound = obj.show.bind({ x: 4 });
bound(); // 4

// 箭头函数
obj.arrow(); // undefined(this 是外层,不是 obj)

// 复杂场景
const obj2 = {
x: 10,
nested: {
x: 20,
show() {
console.log(this.x);
}
}
};
obj2.nested.show(); // 20(this 是 nested,不是 obj2)

// 回调中的 this
setTimeout(obj.show, 100); // undefined(this 丢失)
setTimeout(() => obj.show(), 100); // 1(通过闭包调用)
setTimeout(obj.show.bind(obj), 100); // 1(通过 bind 绑定)

6.6 函数组合与管道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 函数组合(从右到左)
function compose(...fns) {
return function(value) {
return fns.reduceRight((acc, fn) => fn(acc), value);
};
}

// 管道(从左到右)
function pipe(...fns) {
return function(value) {
return fns.reduce((acc, fn) => fn(acc), value);
};
}

// 使用示例
const add1 = x => x + 1;
const multiply2 = x => x * 2;
const square = x => x * x;

const composed = compose(square, multiply2, add1);
console.log(composed(5)); // 144 = square(multiply2(add1(5))) = square(12)

const piped = pipe(add1, multiply2, square);
console.log(piped(5)); // 144 = square(multiply2(add1(5))) = square(12)

6.7 柯里化(Currying)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 基础柯里化
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));
};
}
};
}

// 使用示例
function add(a, b, c) {
return a + b + c;
}

const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6

// 实际应用
const multiply = (a, b) => a * b;
const curriedMultiply = curry(multiply);
const double = curriedMultiply(2);
console.log(double(5)); // 10

6.8 函数重载模拟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 使用函数重载模式
function createOverload() {
const overloads = [];

function overload(...args) {
for (const { condition, fn } of overloads) {
if (condition(...args)) {
return fn.apply(this, args);
}
}
throw new Error('No matching overload');
}

overload.add = function(condition, fn) {
overloads.push({ condition, fn });
return overload;
};

return overload;
}

// 使用示例
const process = createOverload()
.add(
(arg) => typeof arg === 'string',
(str) => `String: ${str}`
)
.add(
(arg) => typeof arg === 'number',
(num) => `Number: ${num * 2}`
)
.add(
(arg) => Array.isArray(arg),
(arr) => `Array: ${arr.join(', ')}`
);

console.log(process('hello')); // "String: hello"
console.log(process(5)); // "Number: 10"
console.log(process([1, 2, 3])); // "Array: 1, 2, 3"

7. 高级主题

7.1 生成器函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 生成器函数(虽然不能是箭头函数,但属于函数主题)
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}

const gen = numberGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

// 实际应用:惰性序列
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}

const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2

7.2 异步函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 异步函数(async/await)
async function fetchData(url) {
try {
const response = await fetch(url);
const data = await response.json();
return data;
} catch (error) {
console.error('Error:', error);
throw error;
}
}

// 异步函数总是返回 Promise
const data = fetchData('https://api.example.com/data');
console.log(data instanceof Promise); // true

// 组合异步函数
async function processData() {
const data1 = await fetchData('url1');
const data2 = await fetchData('url2');
return { data1, data2 };
}

7.3 函数式编程模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 不可变数据操作
const numbers = [1, 2, 3, 4, 5];

// 函数式风格
const doubled = numbers.map(n => n * 2);
const evens = doubled.filter(n => n % 2 === 0);
const sum = evens.reduce((a, b) => a + b, 0);

// 链式调用
const result = numbers
.map(n => n * 2)
.filter(n => n % 2 === 0)
.reduce((a, b) => a + b, 0);

// 纯函数
function pureAdd(a, b) {
return a + b; // 不修改外部状态,相同输入总是相同输出
}

// 高阶函数组合
const operations = [
x => x * 2,
x => x + 1,
x => x ** 2
];

const applyOperations = (value) =>
operations.reduce((acc, op) => op(acc), value);

console.log(applyOperations(5)); // 121 = ((5 * 2) + 1) ** 2

8. 速记口诀与最佳实践

8.1 速记口诀

  • **”声明整体升,表达式不升值”**:声明能提前用,表达式要先定义。
  • **”默认只认 undefinednull 不顶班”**:默认值只在 undefined 时触发。
  • **”箭头不 new、无 this、无 arguments”**:想要动态 this 用普通函数。
  • **”闭包记定义环境,不记调用环境”**:this 看调用,变量查词法。
  • **”this 先 new,后 call/apply/bind,再看点号,最后默认,箭头例外”**。

8.2 最佳实践

8.2.1 函数声明选择

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ✅ 推荐:工具函数用声明
function formatDate(date) {
return new Intl.DateTimeFormat().format(date);
}

// ✅ 推荐:需要条件创建用表达式
const handler = condition
? function() { /* A */ }
: function() { /* B */ };

// ✅ 推荐:回调用箭头函数
array.map(item => item * 2);

// ❌ 避免:对象方法用箭头函数
const obj = {
value: 1,
bad: () => this.value, // this 不对
good() { return this.value; } // ✅
};

8.2.2 参数处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ✅ 推荐:使用默认值和剩余参数
function apiCall(url, { method = 'GET', headers = {} } = {}) {
// ...
}

// ✅ 推荐:参数验证
function safeDivide(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('Arguments must be numbers');
}
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}

8.2.3 闭包使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ✅ 推荐:明确闭包用途
function createCounter() {
let count = 0; // 明确这是要封装的变量
return { /* ... */ };
}

// ❌ 避免:意外的闭包
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 3, 3, 3
}

// ✅ 正确:使用 let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0, 1, 2
}

8.2.4 this 绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
// ✅ 推荐:类方法用箭头函数或 bind
class Component {
handleClick = () => {
// this 自动绑定
};
}

// ✅ 推荐:明确 this 绑定
const bound = obj.method.bind(obj);

// ❌ 避免:丢失 this
const fn = obj.method;
fn(); // this 丢失

8.3 调试技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 1. 使用命名函数表达式便于调试
const process = function processData(data) {
// 调用栈会显示 processData
return data.map(/* ... */);
};

// 2. 使用 console.trace 查看调用栈
function debugFunction() {
console.trace('Function called');
}

// 3. 使用断点调试
function complexFunction() {
debugger; // 浏览器会在此暂停
// ...
}

// 4. 使用性能分析
function measurePerformance(fn) {
return function(...args) {
const start = performance.now();
const result = fn(...args);
const end = performance.now();
console.log(`Function took ${end - start}ms`);
return result;
};
}

9. 总结

9.1 核心概念回顾

  1. 函数声明 vs 表达式:提升行为不同,选择取决于使用场景
  2. 参数处理:默认值、剩余参数、解构的灵活运用
  3. 箭头函数:词法 this 绑定,适合回调,不适合方法
  4. 高阶函数:函数作为一等公民,实现抽象和组合
  5. 递归:三要素,注意栈溢出和性能优化
  6. 作用域:全局、函数、块级作用域,作用域链查找
  7. 闭包:函数”记住”定义环境,实现私有化和函数工厂
  8. this 绑定:new > 显式 > 隐式 > 默认,箭头函数例外

9.2 实践建议

  • 优先使用函数声明用于工具函数和需要提升的场景
  • 使用箭头函数用于回调和需要捕获外层 this 的场景
  • 使用剩余参数替代 arguments,获得真正的数组
  • 理解闭包但避免意外的闭包和内存泄漏
  • 明确 this 绑定,必要时使用 bind 或箭头函数
  • 使用严格模式,获得更好的错误提示和安全性
  • 合理使用递归,注意性能和栈溢出问题

9.3 进一步学习

  • ES6+ 新特性:生成器、异步函数、类方法
  • 函数式编程:纯函数、不可变性、函数组合
  • 设计模式:工厂模式、模块模式、装饰器模式
  • 性能优化:记忆化、惰性求值、尾调用优化
  • 测试:单元测试、函数测试、边界情况测试

记住:函数是 JavaScript 的核心,深入理解函数机制是写出高质量代码的基础。多实践、多思考、多调试,才能真正掌握函数的精髓。