函数与作用域
约 1608 字大约 5 分钟
2025-11-24
函数定义
问题
定义函数有哪些方法,它们有什么区别?
1. 函数声明
console.log(sum(1, 2)) // 可以提前调用
function sum(a, b) {
console.log(arguments) // [Arguments] { '0': 1, '1': 2 }
return a + b;
}
// 特点:
console.log(sum(1, 2)); // 3
console.log(sum.name); // "sum"关键特性:
- 函数提升:可以在声明前调用
- 有自己的
name属性 - 拥有
arguments对象 - 有自己的
this绑定(根据调用方式动态绑定)
2. 函数表达式
// 匿名函数表达式
const multiply = function(a, b) {
return a * b;
};
// 命名函数表达式(推荐)
const divide = function div(a, b) {
return a / b;
};关键特性:
- 无函数提升:必须先定义后使用
- 可匿名也可命名
- 适合作为回调函数、IIFE(立即调用函数表达式)
// console.log(square(5)); // ReferenceError - 不能提前调用
const square = function(n) {
return n * n;
};
console.log(square(5)); // 25 - 必须先定义
// 命名函数表达式的优势(调试友好)
const factorial = function fact(n) {
return n <= 1 ? 1 : n * fact(n - 1); // 可以在内部递归调用
};3. 箭头函数 (ES6+)
// 基本语法
const add = (a, b) => a + b;
// 多参数需要括号
const greet = (name, age) => `Hello ${name}, age ${age}`;
// 单参数可省略括号
const double = n => n * 2;
// 函数体多条语句需要大括号和 return
const calculate = (a, b) => {
const sum = a + b;
const product = a * b;
return { sum, product };
};关键特性:
- 无函数提升
- 没有自己的
this(继承自父作用域) - 没有
arguments对象 - 不能作为构造函数(不能用
new) - 没有
prototype属性
// this 绑定差异
const obj = {
value: 42,
regular: function() {
console.log(this.value); // 42 - this 指向 obj
},
arrow: () => {
console.log(this.value); // undefined - this 指向外层(全局)
}
};
obj.regular(); // 42
obj.arrow(); // undefined4. Function 构造函数
const dynamicFunc = new Function('a', 'b', 'return a + b');
console.log(dynamicFunc(2, 3)); // 5
// 从字符串动态创建
const operation = 'multiply';
const funcBody = operation === 'add' ? 'return a + b' : 'return a * b';
const customFunc = new Function('a', 'b', funcBody);关键特性:
- 完全动态创建
- 性能差(每次都会解析字符串)
- 安全问题(可能执行恶意代码)
- 无法访问外层作用域变量(只能在全局作用域运行)
5. 生成器函数
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = numberGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2关键特性:
- 可暂停和恢复执行
- 返回生成器对象,不是直接返回值
- 使用
yield关键字
什么是闭包
问题
什么是闭包?
闭包有什么用途?
闭包是指一个函数能够记住并访问它的词法作用域,即使这个函数在其词法作用域之外被调用。 它发生在当一个函数嵌套在另一个函数内部时,内部函数可以访问外部函数的变量和参数,即使外部函数已经执行完毕并返回。
简单来说,闭包就是“函数 + 函数声明时的词法作用域”组合形成的一个整体。
闭包的特性
- 内部函数可以访问外部函数的变量。
- 外部函数执行完成后,其作用域不会立即销毁,而是被闭包保留。
- 闭包可以让变量的生命周期延长,直到不再有任何引用为止。
闭包的作用
- 数据封装与信息隐藏:通过闭包可以创建私有变量,避免全局污染。
- 回调函数和事件处理:在异步编程中,闭包常用于保存状态。
- 工厂函数:通过闭包返回特定功能的对象。
示例代码
function outerFunction() {
let outerVariable = "I'm outside!";
function innerFunction() {
console.log(outerVariable); // 访问外部函数的变量
}
return innerFunction;
}
const closure = outerFunction();
closure(); // 输出: I'm outside!在这个例子中,innerFunction 是一个闭包,它记住了 outerFunction 的作用域,并能够在 outerFunction 执行完毕后继续访问 outerVariable。
闭包的注意事项
- 内存泄漏:由于闭包会持有对外部作用域的引用,如果未正确释放,可能导致内存占用过高。
- 性能问题:过多使用闭包可能会影响程序性能,尤其是在循环或大量嵌套的情况下。
作用域(Scope)
问题
什么是作用域?
作用域是变量、函数或对象的可访问范围,决定了代码中哪些部分可以访问这些标识符。JavaScript 中有以下作用域类型:
1. 全局作用域
- 定义在函数或代码块外的变量
- 可在代码任何位置访问
let globalVar = "全局变量"; // 全局作用域 function show() { console.log(globalVar); // 可以访问 }
2. 函数作用域
- 通过
var在函数内定义的变量 - 只能在函数内部访问
function func() { var localVar = "局部变量"; // 函数作用域 console.log(localVar); // 可以访问 } console.log(localVar); // 报错:localVar未定义
3. 块级作用域(ES6+)
- 通过
let/const在代码块({})内定义的变量 - 只能在代码块内访问
if (true) { let blockVar = "块级变量"; // 块级作用域 console.log(blockVar); // 可以访问 } console.log(blockVar); // 报错:blockVar未定义
作用域链(Scope Chain)
问题
什么是作用域链?
作用域链是 JavaScript 执行上下文中的核心机制,它定义了标识符解析的静态规则。作用域链在函数创建时基于词法环境确立,体现了 JavaScript 的词法作用域特性。
从实现角度看,作用域链是一个由变量对象组成的链表结构,规定了引擎在访问变量时的查找路径。当在某个执行上下文中访问标识符时,引擎会遵循以下解析顺序:
- 首先在当前执行上下文的变量对象(活动对象)中查找
- 若未找到,则沿作用域链向上在外层执行环境的变量对象中查找
- 递归执行此过程,直至抵达全局执行上下文的变量对象
- 若全局上下文中仍未找到该标识符的定义,则抛出 ReferenceError
这一机制确保了变量访问的确定性和可预测性,同时也是闭包实现的理论基础——函数通过维护其定义时的作用域链,得以在创建它的执行环境销毁后仍能访问其中的变量。
拓展阅读:JavaScript 中的作用域链