JavaScript 函数详解

函数是 JavaScript 的一等公民,理解函数的各种特性是掌握 JavaScript 的关键。本文将从基础到高级,全面讲解 JavaScript 函数的各种知识点。


一、函数的定义方式

JavaScript 有多种定义函数的方式,每种方式都有其特点和适用场景。

1.1 函数声明(Function Declaration)

1
2
3
4
5
function add(a, b) {
return a + b;
}

console.log(add(1, 2)); // 3

特点:

  • 有函数名
  • 函数提升(hoisting),可以在声明前调用
  • 适合作为工具函数或构造函数
1
2
3
4
5
6
// 函数提升示例
console.log(multiply(2, 3)); // 6,不会报错

function multiply(a, b) {
return a * b;
}

1.2 函数表达式(Function Expression)

1
2
3
4
5
const subtract = function(a, b) {
return a - b;
};

console.log(subtract(5, 3)); // 2

特点:

  • 将匿名函数赋值给变量
  • 不会提升,必须在定义后调用
  • 可以是命名函数表达式(便于调试)
1
2
3
4
5
6
7
// 命名函数表达式
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1); // 可以递归调用自身
};

console.log(factorial(5)); // 120

1.3 箭头函数(Arrow Function)

ES6 新增的语法糖,更简洁。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 基本写法
const add = (a, b) => {
return a + b;
};

// 简写:单个表达式可以省略大括号和 return
const multiply = (a, b) => a * b;

// 简写:单个参数可以省略括号
const square = x => x * x;

// 简写:无参数需要括号
const sayHello = () => console.log('Hello');

箭头函数的特点:

特性说明
没有自己的 thisthis 继承自外层作用域
没有 arguments可以用剩余参数 ...args 代替
不能用作构造函数不能用 new 调用
没有 prototype没有原型对象
不能用作 Generator 函数不能使用 yield

this 绑定对比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const obj = {
name: 'Alice',
sayHi: function() {
console.log(`Hi, I'm ${this.name}`);

// 普通函数作为 setTimeout 回调,this 会变成 global/window
setTimeout(function() {
console.log(`普通函数:${this.name}`); // undefined
}, 100);

// 箭头函数,this 继承自外层 sayHi 的 this
setTimeout(() => {
console.log(`箭头函数:${this.name}`); // Alice
}, 200);
}
};

obj.sayHi();

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 写法一
(function() {
console.log('立即执行');
})();

// 写法二
(function() {
console.log('立即执行');
}());

// 传参
(function(name) {
console.log(`Hello, ${name}`);
})('Alice');

// 返回值赋值给变量
const result = (function(a, b) {
return a + b;
})(1, 2);
console.log(result); // 3

作用:

  • 创建私有作用域,避免变量污染
  • 模块化编程(ES6 模块出现前)

二、函数参数

2.1 参数的基本使用

1
2
3
4
5
6
7
// 固定参数
function add(a, b) {
return a + b;
}
console.log(add(1, 2)); // 3
console.log(add(1)); // NaN (b 是 undefined)
console.log(add(1, 2, 3)); // 3 (忽略第三个参数)

2.2 arguments 对象

1
2
3
4
5
6
7
8
9
10
function sum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}

console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15

注意:

  • arguments 是类数组对象,不是真正的数组
  • 可以用 Array.from(arguments)[...arguments] 转成数组
  • 箭头函数没有自己的 arguments
1
2
3
4
5
6
7
8
9
10
11
// 转成数组的方法
function sum() {
// 方法一:Array.from
const args = Array.from(arguments);
// 方法二:展开运算符
const args2 = [...arguments];
// 方法三:slice
const args3 = Array.prototype.slice.call(arguments);

return args.reduce((acc, cur) => acc + cur, 0);
}

2.3 剩余参数(Rest Parameters)

ES6 推荐使用剩余参数代替 arguments

1
2
3
4
5
6
7
8
9
10
11
12
function sum(...nums) {
return nums.reduce((acc, cur) => acc + cur, 0);
}

console.log(sum(1, 2, 3)); // 6

// 与普通参数配合使用
function concat(separator, ...strings) {
return strings.join(separator);
}

console.log(concat('-', 'a', 'b', 'c')); // a-b-c

2.4 参数默认值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ES5 写法
function greet(name) {
name = name || 'Guest';
console.log(`Hello, ${name}`);
}

// ES6 写法
function greet(name = 'Guest') {
console.log(`Hello, ${name}`);
}

greet(); // Hello, Guest
greet('Alice'); // Hello, Alice

// 默认值可以是表达式
function add(a, b = a * 2) {
return a + b;
}
console.log(add(3)); // 3 + 6 = 9

2.5 解构参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 对象解构
function greet({ name = 'Guest', age = 18 }) {
console.log(`Hello, ${name}, you're ${age} years old`);
}

const person = { name: 'Alice', age: 25 };
greet(person); // Hello, Alice, you're 25 years old
greet({}); // Hello, Guest, you're 18 years old

// 数组解构
function sum([a, b, c]) {
return a + b + c;
}

console.log(sum([1, 2, 3])); // 6

三、函数返回值

3.1 返回基本数据类型

1
2
3
4
5
6
7
function add(a, b) {
return a + b;
}

function isEven(n) {
return n % 2 === 0;
}

3.2 返回对象

1
2
3
4
5
6
7
8
9
10
11
12
function createUser(name, age) {
return {
name: name,
age: age,
sayHi: function() {
console.log(`Hi, I'm ${this.name}`);
}
};
}

const user = createUser('Alice', 25);
user.sayHi(); // Hi, I'm Alice

3.3 返回函数(闭包)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function createCounter() {
let count = 0;

return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
}
};
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
console.log(counter.decrement()); // 1

四、this 绑定

this 的指向是 JavaScript 中最容易混淆的概念之一,掌握 this 的绑定规则非常重要。

4.1 this 绑定的 4 种规则

规则优先级说明
new 绑定1使用 new 调用构造函数
显式绑定2使用 call、apply、bind
隐式绑定3作为对象方法调用
默认绑定4直接调用(非严格模式指向全局,严格模式指向 undefined)

4.2 默认绑定

1
2
3
4
5
6
7
8
9
10
function foo() {
console.log(this);
}

// 非严格模式
foo(); // window (浏览器) 或 global (Node.js)

// 严格模式
'use strict';
foo(); // undefined

4.3 隐式绑定

1
2
3
4
5
6
7
8
9
10
11
12
const obj = {
name: 'Alice',
sayHi: function() {
console.log(`Hi, I'm ${this.name}`);
}
};

obj.sayHi(); // Hi, I'm Alice

// 注意:隐式丢失
const bar = obj.sayHi;
bar(); // 非严格模式下 this 是全局,输出 undefined

4.4 显式绑定

call 和 apply

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

const alice = { name: 'Alice' };
const bob = { name: 'Bob' };

// call:参数逐个传入
greet.call(alice, 'Hello', '!'); // Hello, Alice!

// apply:参数作为数组传入
greet.apply(bob, ['Hi', '?']); // Hi, Bob?

bind

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

const alice = { name: 'Alice' };
const greetAlice = greet.bind(alice);

greetAlice(); // Hello, Alice

bind 的特点:

  • 返回一个新函数
  • 新函数的 this 永久绑定
  • 可以进行柯里化
1
2
3
4
5
6
7
8
// 柯里化示例
function add(a, b) {
return a + b;
}

const add5 = add.bind(null, 5);
console.log(add5(3)); // 8
console.log(add5(10)); // 15

4.5 new 绑定

1
2
3
4
5
6
7
8
function Person(name, age) {
this.name = name;
this.age = age;
}

const alice = new Person('Alice', 25);
console.log(alice.name); // Alice
console.log(alice.age); // 25

new 操作符做了什么?

  1. 创建一个空对象 {}
  2. 将这个对象的 __proto__ 指向构造函数的 prototype
  3. 将构造函数的 this 绑定到这个对象
  4. 如果构造函数没有返回对象,则返回这个对象
1
2
3
4
5
6
7
8
9
10
11
12
13
// 模拟 new
function myNew(constructor, ...args) {
// 1. 创建空对象
const obj = {};
// 2. 链接原型
obj.__proto__ = constructor.prototype;
// 3. 绑定 this,执行构造函数
const result = constructor.apply(obj, args);
// 4. 返回
return typeof result === 'object' ? result : obj;
}

const bob = myNew(Person, 'Bob', 30);

五、闭包(Closure)

闭包是 JavaScript 中最强大的特性之一。

5.1 什么是闭包?

闭包 = 函数 + 函数能访问的自由变量

1
2
3
4
5
6
7
8
9
10
11
12
13
function outer() {
let count = 0; // 自由变量

return function inner() {
count++;
console.log(count);
};
}

const counter = outer();
counter(); // 1
counter(); // 2
counter(); // 3

关键点:

  • inner 函数可以访问 outer 函数的变量
  • 即使 outer 已经执行完,count 变量仍然存在

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
25
26
function createWallet(initialMoney) {
let money = initialMoney; // 私有变量

return {
deposit: function(amount) {
money += amount;
console.log(`存入 ${amount},余额 ${money}`);
},
withdraw: function(amount) {
if (amount > money) {
console.log('余额不足');
return;
}
money -= amount;
console.log(`取出 ${amount},余额 ${money}`);
},
getBalance: function() {
return money;
}
};
}

const wallet = createWallet(100);
wallet.deposit(50); // 存入 50,余额 150
wallet.withdraw(30); // 取出 30,余额 120
console.log(wallet.money); // undefined(无法直接访问)

用途二:函数柯里化

1
2
3
4
5
6
7
8
9
10
11
function multiply(a) {
return function(b) {
return a * b;
};
}

const double = multiply(2);
const triple = multiply(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

用途三:循环中的闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ❌ 错误写法
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 输出 3, 3, 3
}, 100);
}

// ✅ 正确写法一:使用闭包
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 输出 0, 1, 2
}, 100);
})(i);
}

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

六、高级函数(Higher-Order Functions)

高级函数是指接受函数作为参数,或返回函数的函数。

6.1 接受函数作为参数

1
2
3
4
5
6
7
8
9
10
11
12
// map:对数组每个元素应用函数
function map(arr, fn) {
const result = [];
for (let i = 0; i < arr.length; i++) {
result.push(fn(arr[i]));
}
return result;
}

const numbers = [1, 2, 3];
const doubled = map(numbers, x => x * 2);
console.log(doubled); // [2, 4, 6]

6.2 返回函数

1
2
3
4
5
6
7
8
9
10
11
12
// 偏函数应用
function isType(type) {
return function(obj) {
return Object.prototype.toString.call(obj) === `[object ${type}]`;
};
}

const isString = isType('String');
const isArray = isType('Array');

console.log(isString('hello')); // true
console.log(isArray([1, 2, 3])); // true

6.3 函数组合

1
2
3
4
5
6
7
8
9
10
11
function compose(...fns) {
return function(x) {
return fns.reduceRight((acc, fn) => fn(acc), x);
};
}

const add = x => x + 1;
const multiply = x => x * 2;

const addThenMultiply = compose(multiply, add);
console.log(addThenMultiply(5)); // (5 + 1) * 2 = 12

6.4 内置的高级函数

JavaScript 数组提供了很多内置的高级函数:

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, 5];

// map:映射
const doubled = numbers.map(x => x * 2); // [2, 4, 6, 8, 10]

// filter:过滤
const evens = numbers.filter(x => x % 2 === 0); // [2, 4]

// reduce:累计
const sum = numbers.reduce((acc, x) => acc + x, 0); // 15

// forEach:遍历
numbers.forEach(x => console.log(x));

// some:是否有一个满足
const hasEven = numbers.some(x => x % 2 === 0); // true

// every:是否全部满足
const allEven = numbers.every(x => x % 2 === 0); // false

七、常见面试题

面试题 1:什么是闭包?

回答要点:

  1. 闭包 = 函数 + 自由变量
  2. 函数可以访问外层作用域的变量
  3. 即使外层函数执行完,变量仍然存在
  4. 用途:数据私有化、函数柯里化等

面试题 2:this 的绑定规则?

回答要点:

  1. new 绑定(优先级最高)
  2. 显式绑定(call/apply/bind)
  3. 隐式绑定(对象方法调用)
  4. 默认绑定(直接调用)

面试题 3:箭头函数和普通函数的区别?

回答要点:

  1. 箭头函数没有自己的 this,继承外层
  2. 箭头函数不能用 new 调用
  3. 箭头函数没有 arguments
  4. 箭头函数没有 prototype
  5. 写法更简洁

面试题 4:new 操作符做了什么?

回答要点:

  1. 创建空对象
  2. 链接原型
  3. 绑定 this
  4. 返回对象

八、最佳实践

8.1 函数命名

1
2
3
4
5
6
7
8
9
// ✅ 好的命名:动词开头,清晰表达意图
function getUser(id) {}
function calculateTotal(prices) {}
function validateEmail(email) {}

// ❌ 不好的命名
function a() {}
function doStuff() {}
function handle() {}

8.2 函数参数不宜过多

1
2
3
4
5
// ❌ 不好:参数太多
function createUser(name, age, address, phone, email, gender) {}

// ✅ 好:使用对象参数
function createUser({ name, age, address, phone, email, gender }) {}

8.3 一个函数只做一件事

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ❌ 不好:一个函数做了太多事
function processUserData(user) {
validateUser(user);
saveUser(user);
sendEmail(user);
logAudit(user);
}

// ✅ 好:每个函数只做一件事
function validateUser(user) {}
function saveUser(user) {}
function sendEmail(user) {}
function logAudit(user) {}
function processUserData(user) {
validateUser(user);
saveUser(user);
sendEmail(user);
logAudit(user);
}

8.4 优先使用箭头函数(回调中)

1
2
3
4
5
6
7
// ✅ 箭头函数避免 this 问题
const obj = {
items: [1, 2, 3],
double: function() {
return this.items.map(item => item * 2);
}
};

九、总结

9.1 核心知识点

  1. 函数定义方式:声明、表达式、箭头函数、IIFE
  2. 函数参数:arguments、剩余参数、默认值、解构
  3. this 绑定:4 种规则,优先级
  4. 闭包:概念、用途
  5. 高级函数:接受或返回函数
  6. 数组方法:map、filter、reduce 等

9.2 记忆要点

  • this 绑定:new > 显式 > 隐式 > 默认
  • 闭包:函数 + 自由变量
  • 箭头函数:没有自己的 this、arguments、prototype

函数是 JavaScript 的灵魂,掌握了函数,就掌握了 JavaScript 的核心!