函数是 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));
|
特点:
- 有函数名
- 会函数提升(hoisting),可以在声明前调用
- 适合作为工具函数或构造函数
1 2 3 4 5 6
| console.log(multiply(2, 3));
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));
|
特点:
- 将匿名函数赋值给变量
- 不会提升,必须在定义后调用
- 可以是命名函数表达式(便于调试)
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));
|
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; };
const multiply = (a, b) => a * b;
const square = x => x * x;
const sayHello = () => console.log('Hello');
|
箭头函数的特点:
| 特性 | 说明 |
|---|
| 没有自己的 this | this 继承自外层作用域 |
| 没有 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(function() { console.log(`普通函数:${this.name}`); }, 100); setTimeout(() => { console.log(`箭头函数:${this.name}`); }, 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);
|
作用:
- 创建私有作用域,避免变量污染
- 模块化编程(ES6 模块出现前)
二、函数参数
2.1 参数的基本使用
1 2 3 4 5 6 7
| function add(a, b) { return a + b; } console.log(add(1, 2)); console.log(add(1)); console.log(add(1, 2, 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)); console.log(sum(1, 2, 3, 4, 5));
|
注意:
arguments 是类数组对象,不是真正的数组- 可以用
Array.from(arguments) 或 [...arguments] 转成数组 - 箭头函数没有自己的
arguments
1 2 3 4 5 6 7 8 9 10 11
| function sum() { const args = Array.from(arguments); const args2 = [...arguments]; 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));
function concat(separator, ...strings) { return strings.join(separator); }
console.log(concat('-', 'a', 'b', 'c'));
|
2.4 参数默认值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function greet(name) { name = name || 'Guest'; console.log(`Hello, ${name}`); }
function greet(name = 'Guest') { console.log(`Hello, ${name}`); }
greet(); greet('Alice');
function add(a, b = a * 2) { return a + b; } console.log(add(3));
|
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); greet({});
function sum([a, b, c]) { return a + b + c; }
console.log(sum([1, 2, 3]));
|
三、函数返回值
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();
|
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()); console.log(counter.increment()); console.log(counter.getCount()); console.log(counter.decrement());
|
四、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();
'use strict'; foo();
|
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();
const bar = obj.sayHi; bar();
|
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' };
greet.call(alice, 'Hello', '!');
greet.apply(bob, ['Hi', '?']);
|
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();
|
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)); console.log(add5(10));
|
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); console.log(alice.age);
|
new 操作符做了什么?
- 创建一个空对象
{} - 将这个对象的
__proto__ 指向构造函数的 prototype - 将构造函数的 this 绑定到这个对象
- 如果构造函数没有返回对象,则返回这个对象
1 2 3 4 5 6 7 8 9 10 11 12 13
| function myNew(constructor, ...args) { const obj = {}; obj.__proto__ = constructor.prototype; const result = constructor.apply(obj, args); 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(); counter(); counter();
|
关键点:
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); wallet.withdraw(30); console.log(wallet.money);
|
用途二:函数柯里化
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)); console.log(triple(5));
|
用途三:循环中的闭包
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); }, 100); }
for (var i = 0; i < 3; i++) { (function(j) { setTimeout(function() { console.log(j); }, 100); })(i); }
for (let i = 0; i < 3; i++) { setTimeout(function() { console.log(i); }, 100); }
|
六、高级函数(Higher-Order Functions)
高级函数是指接受函数作为参数,或返回函数的函数。
6.1 接受函数作为参数
1 2 3 4 5 6 7 8 9 10 11 12
| 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);
|
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')); console.log(isArray([1, 2, 3]));
|
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));
|
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];
const doubled = numbers.map(x => x * 2);
const evens = numbers.filter(x => x % 2 === 0);
const sum = numbers.reduce((acc, x) => acc + x, 0);
numbers.forEach(x => console.log(x));
const hasEven = numbers.some(x => x % 2 === 0);
const allEven = numbers.every(x => x % 2 === 0);
|
七、常见面试题
面试题 1:什么是闭包?
回答要点:
- 闭包 = 函数 + 自由变量
- 函数可以访问外层作用域的变量
- 即使外层函数执行完,变量仍然存在
- 用途:数据私有化、函数柯里化等
面试题 2:this 的绑定规则?
回答要点:
- new 绑定(优先级最高)
- 显式绑定(call/apply/bind)
- 隐式绑定(对象方法调用)
- 默认绑定(直接调用)
面试题 3:箭头函数和普通函数的区别?
回答要点:
- 箭头函数没有自己的 this,继承外层
- 箭头函数不能用 new 调用
- 箭头函数没有 arguments
- 箭头函数没有 prototype
- 写法更简洁
面试题 4:new 操作符做了什么?
回答要点:
- 创建空对象
- 链接原型
- 绑定 this
- 返回对象
八、最佳实践
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
| const obj = { items: [1, 2, 3], double: function() { return this.items.map(item => item * 2); } };
|
九、总结
9.1 核心知识点
- 函数定义方式:声明、表达式、箭头函数、IIFE
- 函数参数:arguments、剩余参数、默认值、解构
- this 绑定:4 种规则,优先级
- 闭包:概念、用途
- 高级函数:接受或返回函数
- 数组方法:map、filter、reduce 等
9.2 记忆要点
- this 绑定:new > 显式 > 隐式 > 默认
- 闭包:函数 + 自由变量
- 箭头函数:没有自己的 this、arguments、prototype
函数是 JavaScript 的灵魂,掌握了函数,就掌握了 JavaScript 的核心!