JavaScript高级程序设计笔记 10
第10章 函数 — 快速复习笔记
1. 函数定义
1.1 函数声明与函数表达式
- 函数声明:
function sum(a, b) { return a + b; }- 特点:会被提升(hoisting),可以在声明前调用。
- 函数表达式:
let sum = function(a, b) { return a + b; };- 特点:不会被提升,必须在赋值后才能调用。
1.2 箭头函数(ES6)
let sum = (a, b) => a + b;
let square = x => x * x; // 单个参数可省略括号
let log = () => console.log('hi'); // 无参数需括号
let getObj = () => ({ name: 'a' }); // 返回对象需加括号
- 特点:
- 没有自己的
this、arguments、super、new.target - 不能用作构造函数(不能
new) - 没有
prototype属性 - 不能作为生成器函数(不能用
yield)
- 没有自己的
1.3 函数构造函数(不推荐)
let sum = new Function('a', 'b', 'return a + b');
- 作用域:总是全局作用域,会多次解析代码,性能差。
2. 函数参数
2.1 参数传递
- 所有参数按值传递(原始值拷值,引用值拷地址)。
- 参数数量不限,没有类型检查。
- 未传递的参数值为
undefined。
2.2 arguments 对象
- 类数组对象(不是 Array),可在函数内访问所有传入的参数。
- 与命名参数联动(修改
arguments[0]会同步修改对应的命名参数,严格模式下不联动)。 arguments.callee指向函数自身(严格模式禁用)。
function foo(a, b) {
console.log(arguments[0], arguments[1]); // 0, undefined
arguments[0] = 10;
console.log(a); // 10(非严格模式)
}
foo(0);
2.3 默认参数值(ES6)
function greet(name = 'Guest', age = 18) {
console.log(`${name}, ${age}`);
}
greet(); // Guest, 18
greet('Tom'); // Tom, 18
- 默认值可以是表达式/函数调用(惰性求值,每次调用时重新计算)。
- 默认值可以使用前面的参数值。
function add(a, b = a) { return a + b; } // b 默认等于 a
add(5); // 10
2.4 剩余参数(rest 参数,ES6)
function sum(...nums) {
return nums.reduce((total, n) => total + n, 0);
}
console.log(sum(1,2,3,4)); // 10
- 剩余参数是真正的数组。
- 只能有一个剩余参数,且必须在参数列表最后。
3. 函数调用与内部属性
3.1 函数内部的 this
- 默认绑定:非严格模式下,独立函数调用
this指向全局对象(浏览器window),严格模式为undefined。 - 方法调用:
obj.method()→this指向obj。 - 构造函数:
new Fn()→this指向新创建的实例。 - 箭头函数:
this继承自外层函数的this(词法作用域),不可改变。
let obj = {
name: 'obj',
say() {
setTimeout(() => {
console.log(this.name); // 'obj'(箭头函数继承外层 this)
}, 0);
}
};
3.2 caller 属性
arguments.callee.caller或函数自身.caller指向调用该函数的函数。- 严格模式禁止使用。
3.3 new.target(ES6)
- 用于检测函数是否通过
new调用。 - 在构造函数中:被
new调用时返回构造函数引用,否则返回undefined。
function Person(name) {
if (!new.target) throw new Error('必须使用 new 调用');
this.name = name;
}
4. 函数属性与方法
4.1 属性
length:命名参数的个数prototype:指向原型对象(不可枚举,不可配置)
4.2 方法
call(thisArg, arg1, arg2, ...):改变this,逐个传参apply(thisArg, [argsArray]):改变this,数组传参bind(thisArg, ...args):返回新函数,this绑定后不会改变(硬绑定)
function add(a, b) { return a + b; }
let obj = {};
console.log(add.call(obj, 1, 2)); // 3
console.log(add.apply(obj, [1,2])); // 3
let bound = add.bind(obj, 1);
console.log(bound(2)); // 3
5. 闭包
5.1 定义
- 闭包是指有权访问另一个函数作用域中变量的函数(即函数嵌套函数,内部函数引用了外部函数的变量)。
function outer() {
let name = 'closure';
function inner() {
console.log(name); // 引用外部变量
}
return inner;
}
let fn = outer(); // 执行后,inner 仍然持有对 name 的引用
fn(); // 'closure'
5.2 闭包的作用与问题
- 作用:数据封装/私有变量、模块模式、函数工厂、回调中保存状态。
- 内存影响:闭包会保存外部函数的活动对象,导致外部函数执行完后无法立即释放内存(除非闭包被垃圾回收)。合理使用,避免过度闭包造成内存泄漏。
5.3 循环中的闭包陷阱(经典)
// 错误示例:期望输出 0,1,2,但输出 3,3,3
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 解决方案1:使用 let(块作用域)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 解决方案2:使用 IIFE 捕获 i 的值
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 0);
})(i);
}
6. 立即调用的函数表达式(IIFE)
6.1 语法
(function() {
console.log('IIFE');
})();
// 或
(function() {
console.log('IIFE');
}());
- 作用:创建一个独立的作用域,避免污染全局;常用于封装模块、提供私有变量。
7. 递归
7.1 递归函数
- 函数调用自身。为避免硬编码函数名,建议使用
arguments.callee(非严格模式)或使用命名函数表达式。
// 命名函数表达式
let factorial = function f(n) {
return n <= 1 ? 1 : n * f(n - 1);
};
7.2 尾递归优化(部分浏览器实现)
- 递归最后一步调用自身,可能避免栈溢出,但目前支持有限。
8. 函数记忆(memoization)
- 缓存函数的计算结果,避免重复计算。
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;
};
}
9. 常见面试题速查
-
函数声明与函数表达式的区别?
→ 声明会提升,表达式不会;表达式只能在定义后调用。 -
箭头函数与普通函数的区别?
→ 箭头函数无this、arguments、prototype,不能new,this由外层词法环境决定。 -
arguments是什么?有什么特点?
→ 类数组对象,包含所有传入参数;与命名参数在非严格模式下联动;arguments.callee可递归调用自身。 -
默认参数和剩余参数的使用场景?
→ 默认参数用于可选参数;剩余参数用于收集不定数量的参数为数组。 -
call、apply、bind的异同?
→ 都用来改变this;call和apply立即执行函数,传参方式不同;bind返回新函数,不执行。 -
闭包是什么?有什么优缺点?
→ 能访问外部作用域的函数。优点:数据私有、模块化;缺点:可能造成内存泄漏(外部变量无法释放)。 -
如何解决循环中闭包的问题?
→ 使用let(块作用域)或 IIFE 捕获循环变量当前值。 -
IIFE 的作用是什么?
→ 创建独立作用域,避免变量污染,常用于隔离代码块。 -
递归函数如何避免函数名耦合?
→ 使用命名函数表达式或arguments.callee(非严格模式)。 -
new.target的作用?
→ 检测函数是否被new调用,用于强制构造函数调用。

浙公网安备 33010602011771号