Appearance
函数
函数是 JavaScript 中最重要的概念之一,它是一段可重复使用的代码块,可以接收参数并返回值。本章将详细介绍函数的定义、参数、作用域和闭包等概念。
函数定义
函数声明
javascript
// 函数声明
function greet(name) {
return 'Hello, ' + name + '!';
}
// 调用函数
console.log(greet('张三')); // Hello, 张三!
// 函数声明会被提升(可以在声明前调用)
console.log(add(1, 2)); // 3
function add(a, b) {
return a + b;
}
// 函数声明语法
// function 函数名(参数1, 参数2, ...) {
// 函数体
// return 返回值;
// }函数表达式
javascript
// 函数表达式:将函数赋值给变量
let greet = function(name) {
return 'Hello, ' + name + '!';
};
console.log(greet('李四')); // Hello, 李四!
// 函数表达式不会提升
// console.log(subtract(5, 3)); // 错误:subtract 未定义
let subtract = function(a, b) {
return a - b;
};
// 命名函数表达式
let factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1); // 内部可以使用函数名递归
};
console.log(factorial(5)); // 120
// console.log(fact(5)); // 错误:fact 在外部不可用
// 函数表达式作为回调
let numbers = [1, 2, 3, 4, 5];
let doubled = numbers.map(function(num) {
return num * 2;
});
console.log(doubled); // [2, 4, 6, 8, 10]箭头函数(ES6)
javascript
// 箭头函数:更简洁的函数语法
let greet = (name) => {
return 'Hello, ' + name + '!';
};
// 简写:单个参数可省略括号
let greet2 = name => 'Hello, ' + name + '!';
// 简写:单个表达式可省略花括号和 return
let double = num => num * 2;
console.log(double(5)); // 10
// 多个参数需要括号
let add = (a, b) => a + b;
console.log(add(3, 4)); // 7
// 无参数需要空括号
let sayHi = () => console.log('Hi!');
sayHi(); // Hi!
// 返回对象需要用括号包裹
let createUser = (name, age) => ({ name, age });
console.log(createUser('张三', 25)); // { name: '张三', age: 25 }
// 多行函数体需要花括号和 return
let calculate = (a, b, operation) => {
switch (operation) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/': return a / b;
default: return 0;
}
};
// 箭头函数没有自己的 this
let obj = {
name: '张三',
// 普通函数:this 指向调用者
greetRegular: function() {
console.log('Hello, ' + this.name);
},
// 箭头函数:this 继承自外层作用域
greetArrow: () => {
console.log('Hello, ' + this.name); // this.name 是 undefined
}
};
obj.greetRegular(); // Hello, 张三
obj.greetArrow(); // Hello, undefined
// 箭头函数适合回调函数
let numbers = [1, 2, 3, 4, 5];
let evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4]Function 构造函数(不推荐)
javascript
// 使用 Function 构造函数创建函数(不推荐)
let add = new Function('a', 'b', 'return a + b');
console.log(add(1, 2)); // 3
// 等价于
let add2 = function(a, b) {
return a + b;
};
// 不推荐使用 Function 构造函数:
// 1. 每次调用都会创建新的函数对象
// 2. 代码在全局作用域执行,存在安全风险
// 3. 难以阅读和调试函数参数
基本参数
javascript
// 函数参数
function greet(name, greeting) {
return greeting + ', ' + name + '!';
}
console.log(greet('张三', '你好')); // 你好, 张三!
// 参数数量可以不匹配
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(多余的参数被忽略)
// 检查参数
function greet2(name) {
if (name === undefined) {
name = '游客';
}
return 'Hello, ' + name + '!';
}
console.log(greet2()); // Hello, 游客!
console.log(greet2('张三')); // Hello, 张三!默认参数(ES6)
javascript
// 默认参数
function greet(name = '游客', greeting = 'Hello') {
return greeting + ', ' + name + '!';
}
console.log(greet()); // Hello, 游客!
console.log(greet('张三')); // Hello, 张三!
console.log(greet('张三', '你好')); // 你好, 张三!
// 默认值可以是表达式
function add(a, b = a * 2) {
return a + b;
}
console.log(add(5)); // 15(5 + 5*2)
// 默认值可以是函数调用
function getDefault() {
return '默认值';
}
function test(value = getDefault()) {
console.log(value);
}
test(); // 默认值
test('自定义'); // 自定义
// 默认参数对 arguments 的影响
function testArgs(a = 1, b = 2) {
console.log(a, b); // 3 undefined
console.log(arguments[0], arguments[1]); // 3 undefined
}
testArgs(3);剩余参数(ES6)
javascript
// 剩余参数:收集多余的参数
function sum(...numbers) {
let total = 0;
for (let num of numbers) {
total += num;
}
return total;
}
console.log(sum(1, 2, 3, 4, 5)); // 15
console.log(sum(1, 2)); // 3
// 与普通参数结合
function greetAll(greeting, ...names) {
names.forEach(name => {
console.log(greeting + ', ' + name + '!');
});
}
greetAll('你好', '张三', '李四', '王五');
// 你好, 张三!
// 你好, 李四!
// 你好, 王五!
// 剩余参数是真正的数组
function logArgs(...args) {
console.log(Array.isArray(args)); // true
args.forEach(arg => console.log(arg));
}
// 剩余参数必须是最后一个参数
// function wrong(...rest, last) {} // 错误
// 剩余参数 vs arguments
function compare(a, b, ...rest) {
console.log('a:', a); // 1
console.log('b:', b); // 2
console.log('rest:', rest); // [3, 4, 5]
console.log('arguments:', arguments); // Arguments(5) [1, 2, 3, 4, 5]
}
compare(1, 2, 3, 4, 5);参数解构(ES6)
javascript
// 解构对象参数
function createUser({ name, age, city = '北京' }) {
return { name, age, city };
}
let user = createUser({ name: '张三', age: 25 });
console.log(user); // { name: '张三', age: 25, city: '北京' }
// 解构数组参数
function getFirstTwo([first, second]) {
return [first, second];
}
console.log(getFirstTwo([1, 2, 3, 4])); // [1, 2]
// 混合使用
function process({ name, scores: [first, second] }) {
return `${name}: ${first + second}`;
}
console.log(process({ name: '张三', scores: [80, 90] })); // 张三: 170
// 带默认值的解构
function setup({ host = 'localhost', port = 3000 } = {}) {
console.log(`Server running at ${host}:${port}`);
}
setup(); // Server running at localhost:3000
setup({ port: 8080 }); // Server running at localhost:8080返回值
return 语句
javascript
// return 返回函数结果
function add(a, b) {
return a + b; // 返回计算结果
}
let result = add(1, 2);
console.log(result); // 3
// return 后面的代码不会执行
function test() {
console.log('开始');
return '结果';
console.log('不会执行'); // 不会执行
}
console.log(test()); // 开始,然后输出 结果
// 没有 return 或 return 后面没有值,返回 undefined
function noReturn() {
console.log('执行');
}
console.log(noReturn()); // 执行,然后输出 undefined
function emptyReturn() {
return; // 相当于 return undefined
}
console.log(emptyReturn()); // undefined
// return 只能返回一个值
function getCoords() {
return 10, 20; // 只返回 20(逗号运算符)
}
console.log(getCoords()); // 20
// 返回多个值:使用数组或对象
function getCoords2() {
return [10, 20]; // 返回数组
}
function getCoords3() {
return { x: 10, y: 20 }; // 返回对象
}
let [x, y] = getCoords2();
let { x: x2, y: y2 } = getCoords3();提前返回
javascript
// 使用 return 提前结束函数
function divide(a, b) {
if (b === 0) {
return '除数不能为 0'; // 提前返回
}
return a / b;
}
console.log(divide(10, 2)); // 5
console.log(divide(10, 0)); // 除数不能为 0
// 验证参数
function processUser(user) {
if (!user) {
return '用户不存在';
}
if (!user.name) {
return '用户名不能为空';
}
if (!user.email) {
return '邮箱不能为空';
}
// 处理用户
return `处理用户:${user.name}`;
}
// 多个返回点
function findElement(arr, target) {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === target) {
return i; // 找到后立即返回
}
}
return -1; // 未找到
}
console.log(findElement([1, 2, 3, 4, 5], 3)); // 2
console.log(findElement([1, 2, 3, 4, 5], 6)); // -1作用域
全局作用域
javascript
// 全局变量:在任何地方都可以访问
var globalVar = '全局变量';
function test() {
console.log(globalVar); // 可以访问
}
test();
console.log(globalVar); // 可以访问
// 全局变量会成为全局对象的属性(浏览器中是 window)
console.log(window.globalVar); // 全局变量
// let 和 const 声明的全局变量不会成为 window 的属性
let globalLet = 'let 全局变量';
console.log(window.globalLet); // undefined函数作用域
javascript
// 函数作用域:变量只在函数内部有效
function test() {
var localVar = '局部变量';
console.log(localVar); // 可以访问
}
test();
// console.log(localVar); // 错误:localVar 未定义
// 函数内部声明的变量会覆盖全局变量
var name = '全局';
function showName() {
var name = '局部';
console.log(name); // 局部
}
showName();
console.log(name); // 全局
// 函数参数也是局部变量
function greet(name) {
console.log('Hello, ' + name);
}
greet('张三');
// console.log(name); // 错误:name 未定义块级作用域(ES6)
javascript
// let 和 const 有块级作用域
if (true) {
let blockLet = '块级变量';
const blockConst = '块级常量';
var blockVar = '函数作用域变量';
console.log(blockLet); // 可以访问
console.log(blockConst); // 可以访问
}
// console.log(blockLet); // 错误:未定义
// console.log(blockConst); // 错误:未定义
console.log(blockVar); // 可以访问
// 循环中的块级作用域
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:0, 1, 2
for (var j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100);
}
// 输出:3, 3, 3
// 块级作用域避免变量污染
function test() {
let x = 1;
if (true) {
let x = 2; // 不同的变量
console.log(x); // 2
}
console.log(x); // 1
}作用域链
javascript
// 作用域链:内部函数可以访问外部函数的变量
let global = '全局';
function outer() {
let outerVar = '外部';
function inner() {
let innerVar = '内部';
// 可以访问所有外层作用域的变量
console.log(innerVar); // 内部
console.log(outerVar); // 外部
console.log(global); // 全局
}
inner();
// console.log(innerVar); // 错误:无法访问内部变量
}
outer();
// 变量查找规则:从内到外
let x = 1;
function a() {
let x = 2;
function b() {
let x = 3;
console.log(x); // 3(找到最近的 x)
}
b();
}
a();闭包
什么是闭包
javascript
// 闭包:函数可以访问其外部函数的变量,即使外部函数已经执行完毕
function createCounter() {
let count = 0; // 私有变量
return function() {
count++;
return count;
};
}
let counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count 变量被闭包"记住"了,不会被销毁
// 另一个计数器是独立的
let counter2 = createCounter();
console.log(counter2()); // 1闭包的应用
javascript
// 1. 数据私有化
function createWallet(initialBalance) {
let balance = initialBalance; // 私有变量
return {
deposit: function(amount) {
balance += amount;
console.log(`存入 ${amount},余额 ${balance}`);
},
withdraw: function(amount) {
if (amount > balance) {
console.log('余额不足');
return;
}
balance -= amount;
console.log(`取出 ${amount},余额 ${balance}`);
},
getBalance: function() {
return balance;
}
};
}
let wallet = createWallet(100);
wallet.deposit(50); // 存入 50,余额 150
wallet.withdraw(30); // 取出 30,余额 120
console.log(wallet.getBalance()); // 120
// console.log(balance); // 错误:无法直接访问 balance
// 2. 函数工厂
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
let double = createMultiplier(2);
let triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// 3. 柯里化
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...moreArgs) {
return curried.apply(this, args.concat(moreArgs));
};
};
}
function add(a, b, c) {
return a + b + c;
}
let curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
// 4. 模块模式
let module = (function() {
let privateVar = '私有变量';
function privateMethod() {
console.log('私有方法');
}
return {
publicVar: '公共变量',
publicMethod: function() {
console.log(privateVar);
privateMethod();
}
};
})();
module.publicMethod(); // 可以访问私有成员
// module.privateMethod(); // 错误:无法访问闭包的注意事项
javascript
// 闭包导致的循环问题(经典问题)
for (var i = 1; i <= 5; i++) {
setTimeout(function() {
console.log(i); // 输出 5 次 6
}, 100);
}
// 解决方案 1:使用 let
for (let i = 1; i <= 5; i++) {
setTimeout(function() {
console.log(i); // 输出 1, 2, 3, 4, 5
}, 100);
}
// 解决方案 2:使用 IIFE
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 输出 1, 2, 3, 4, 5
}, 100);
})(i);
}
// 解决方案 3:使用闭包
for (var i = 1; i <= 5; i++) {
setTimeout((function(j) {
return function() {
console.log(j);
};
})(i), 100);
}
// 闭包会占用内存
function createClosures() {
let closures = [];
for (let i = 0; i < 1000; i++) {
closures.push(function() {
console.log(i); // 每个闭包都持有 i 的引用
});
}
return closures;
}
// 不再使用的闭包应该解除引用
let closures = createClosures();
// 使用完后
closures = null; // 允许垃圾回收高阶函数
高阶函数是接收函数作为参数或返回函数的函数。
函数作为参数
javascript
// 回调函数
function processArray(arr, callback) {
let result = [];
for (let item of arr) {
result.push(callback(item));
}
return result;
}
let numbers = [1, 2, 3, 4, 5];
// 传入不同的回调函数实现不同功能
let doubled = processArray(numbers, n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
let squared = processArray(numbers, n => n * n);
console.log(squared); // [1, 4, 9, 16, 25]
// 数组内置的高阶函数
let arr = [1, 2, 3, 4, 5];
// map:映射
let mapped = arr.map(n => n * 2);
console.log(mapped); // [2, 4, 6, 8, 10]
// filter:过滤
let filtered = arr.filter(n => n % 2 === 0);
console.log(filtered); // [2, 4]
// reduce:归约
let sum = arr.reduce((acc, n) => acc + n, 0);
console.log(sum); // 15
// find:查找
let found = arr.find(n => n > 3);
console.log(found); // 4
// some:是否存在满足条件的元素
let hasEven = arr.some(n => n % 2 === 0);
console.log(hasEven); // true
// every:是否所有元素都满足条件
let allPositive = arr.every(n => n > 0);
console.log(allPositive); // true函数作为返回值
javascript
// 返回函数
function createLogger(prefix) {
return function(message) {
console.log(`[${prefix}] ${message}`);
};
}
let infoLogger = createLogger('INFO');
let errorLogger = createLogger('ERROR');
infoLogger('操作成功'); // [INFO] 操作成功
errorLogger('发生错误'); // [ERROR] 发生错误
// 防抖函数
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 节流函数
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
let now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
// 使用示例
let handleScroll = debounce(function() {
console.log('滚动事件处理');
}, 300);
window.addEventListener('scroll', handleScroll);函数组合
javascript
// 函数组合:将多个函数组合成一个
function compose(...fns) {
return function(x) {
return fns.reduceRight((acc, fn) => fn(acc), x);
};
}
let addOne = x => x + 1;
let double = x => x * 2;
let square = x => x * x;
let composed = compose(addOne, double, square);
console.log(composed(3)); // ((3^2) * 2) + 1 = 19
// 管道:从左到右执行
function pipe(...fns) {
return function(x) {
return fns.reduce((acc, fn) => fn(acc), x);
};
}
let piped = pipe(square, double, addOne);
console.log(piped(3)); // ((3^2) * 2) + 1 = 19立即执行函数(IIFE)
javascript
// IIFE:Immediately Invoked Function Expression
// 定义后立即执行的函数
// 基本语法
(function() {
console.log('立即执行');
})();
// 带参数
(function(name) {
console.log('Hello, ' + name);
})('张三');
// 使用箭头函数
(() => {
console.log('箭头函数 IIFE');
})();
// 返回值
let result = (function(a, b) {
return a + b;
})(1, 2);
console.log(result); // 3
// 创建独立作用域
let counter = (function() {
let count = 0;
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
}
};
})();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
// 避免全局变量污染
(function() {
var localVar = '局部变量';
// 这里的变量不会污染全局
})();
// console.log(localVar); // 错误:未定义递归函数
javascript
// 递归:函数调用自身
// 阶乘
function factorial(n) {
// 基准情况
if (n <= 1) return 1;
// 递归调用
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120
// 斐波那契数列
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
console.log(fibonacci(10)); // 55
// 优化:使用缓存
function fibonacciMemo() {
let cache = {};
return function fib(n) {
if (n in cache) return cache[n];
if (n <= 1) return n;
cache[n] = fib(n - 1) + fib(n - 2);
return cache[n];
};
}
let fib = fibonacciMemo();
console.log(fib(50)); // 12586269025
// 遍历树结构
let tree = {
name: 'root',
children: [
{
name: 'child1',
children: [
{ name: 'grandchild1', children: [] },
{ name: 'grandchild2', children: [] }
]
},
{
name: 'child2',
children: []
}
]
};
function traverseTree(node, level = 0) {
console.log(' '.repeat(level) + node.name);
for (let child of node.children) {
traverseTree(child, level + 1);
}
}
traverseTree(tree);
// root
// child1
// grandchild1
// grandchild2
// child2
// 尾递归优化
// 尾递归:递归调用是函数的最后一步操作
function factorialTail(n, acc = 1) {
if (n <= 1) return acc;
return factorialTail(n - 1, n * acc); // 尾递归
}
console.log(factorialTail(5)); // 120小结
本章学习了 JavaScript 函数的核心概念:
- 函数定义:函数声明、函数表达式、箭头函数
- 函数参数:默认参数、剩余参数、参数解构
- 返回值:return 语句、提前返回
- 作用域:全局作用域、函数作用域、块级作用域、作用域链
- 闭包:闭包的原理和应用
- 高阶函数:函数作为参数、函数作为返回值
- IIFE:立即执行函数
- 递归:递归函数的原理和优化
下一章我们将学习 数组,了解数组的创建和操作方法。
