函数进阶
this 绑定、高阶函数、纯函数、记忆化、递归与 IIFE 等是日常开发与面试中的核心主题。前置:语言基础、作用域与闭包。
一、this 绑定规则
this 的值取决于函数被调用的方式,而非定义时的位置。共有四种绑定规则,优先级从低到高。
1. 默认绑定
非严格模式下,独立函数调用时 this 为全局对象(globalThis);严格模式或模块顶层中为 undefined。
function foo() {
console.log(this);
}
foo(); // 非严格: window; 严格: undefined
2. 隐式绑定
通过对象调用方法时,this 指向该对象。
const obj = {
name: "Alice",
greet() {
console.log(this.name);
},
};
obj.greet(); // 'Alice'
注意:隐式绑定容易丢失,例如 const fn = obj.greet; fn() 恢复为默认绑定。
3. 显式绑定
使用 call、apply、bind 手动指定 this。
call(thisArg, arg1, arg2, ...)– 立即执行,参数逐个传递。apply(thisArg, [argsArray])– 立即执行,参数以数组形式传递。bind(thisArg, ...)– 返回一个新函数,可延迟执行。
function say(age) {
console.log(this.name, age);
}
const person = { name: "Bob" };
say.call(person, 25); // Bob 25
say.apply(person, [25]); // Bob 25
const bound = say.bind(person, 25);
bound(); // Bob 25
4. new 绑定
使用 new 调用函数时,会创建一个新对象,并将该对象绑定为 this。
function Person(name) {
this.name = name;
}
const p = new Person("Charlie");
console.log(p.name); // 'Charlie'
优先级:new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定。
5. 箭头函数的词法 this
箭头函数没有自己的 this,它捕获外层(定义时)作用域的 this 值,且无法被 call/apply/bind 改变。
const obj = {
name: "Diana",
regular: function () {
console.log(this.name);
},
arrow: () => {
console.log(this.name);
},
};
obj.regular(); // 'Diana'
obj.arrow(); // undefined (this 指向外层,此处为全局)
二、高阶函数
高阶函数满足至少一个条件:接收函数作为参数、返回一个函数。
1. 函数作为参数/返回值
// 参数
[1, 2, 3].map(x => x * 2);
// 返回值
function multiplyBy(factor) {
return function (x) {
return x * factor;
};
}
const double = multiplyBy(2);
double(5); // 10
2. 柯里化(Currying)
将一个多参数函数转化为一系列单参数函数。
function add(a) {
return function (b) {
return a + b;
};
}
const add5 = add(5);
add5(3); // 8
3. 组合函数(Compose)
将多个函数从右向左组合执行。
const compose =
(...fns) =>
init =>
fns.reduceRight((val, fn) => fn(val), init);
const add1 = x => x + 1;
const double = x => x * 2;
const add1ThenDouble = compose(double, add1);
add1ThenDouble(3); // (3+1)*2 = 8
三、纯函数与副作用
- 纯函数:相同的输入永远产生相同的输出,且不产生任何副作用(不修改外部状态、不依赖外部可变变量)。
- 副作用:函数在执行过程中修改了外部环境(如修改全局变量、DOM 操作、读写文件、发送 HTTP 请求)。
// 纯函数
function add(a, b) {
return a + b;
}
// 不纯(有副作用)
let counter = 0;
function increment() {
counter++;
} // 修改外部变量
纯函数易于测试、缓存、并发执行,是函数式编程的基石。
四、函数记忆(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(...args);
cache.set(key, result);
return result;
};
}
const fib = memoize(function (n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
});
fib(40); // 极快,因为缓存了中间结果
五、递归与尾调用优化
- 递归:函数调用自身。可能导致调用栈溢出(递归深度过大)。
- 尾调用:函数最后一步是调用另一个函数,并且该调用的返回值直接返回。
- 尾递归:递归调用是尾调用。理论上编译器可以优化,复用当前栈帧,避免栈增长。
// 普通递归(非尾递归)
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1); // 乘法是最后一步,不是尾调用
}
// 尾递归版本
function factorialTail(n, acc = 1) {
if (n === 1) return acc;
return factorialTail(n - 1, n * acc);
}
引擎支持:ECMAScript 规定尾调用优化为可选实现。V8(Chrome、Node.js)未实现 PTC(proper tail calls);Safari 的 JavaScriptCore 在严格模式下对部分尾递归有优化。工程上应用循环或**蹦床(trampoline)**保证栈深度可控。
六、IIFE 及其现代替代
IIFE(Immediately Invoked Function Expression)定义后立即执行,用于创建独立作用域,避免污染全局。
(function () {
var privateVar = "secret";
console.log("IIFE runs");
})();
现代替代:ES6 引入块级作用域(let / const + {}),无需函数包裹即可隔离变量。
{
let privateVar = "secret";
// 仅在此块内有效
}
// privateVar 不可访问
另外,模块(ES Modules)天生提供作用域隔离,已成为主流方案。
// module.js(`private` 是严格模式保留字,模块顶层严格模式下不能作为变量名,此处改名为 internal)
const internal = 42;
export const publicFn = () => { ... };
除非需要立即执行逻辑且对旧环境兼容,否则推荐使用块级作用域或模块。
参考文献
以下链接在编写时均可正常访问:
| 资料 | 说明 |
|---|---|
| MDN:this | 绑定规则 |
| MDN:箭头函数 | 词法 this |
| MDN:Function.prototype.bind | 显式绑定 |
| MDN:尾调用优化 | TCO 说明(英文) |
| ECMA-262:尾位置调用 | 规范(英文) |
相关文章
错误处理与调试
结构化错误类型、try/catch 与全局未捕获处理是健壮应用的基础。
浏览器相关 API
DOM 事件、存储、fetch、Worker、Observer 等由浏览器提供的 Web API(非 ECMAScript 核心)。HTML 结构见 标签。
性能优化
防抖/节流、渲染与内存、资源预加载及引擎行为是前端性能的主要抓手。相关 CSS 见 视觉效果、Head 资源提示。
模块化与工程化
ESM、CommonJS 与打包工具(Tree Shaking、代码分割)是前端工程化的基础。入门见 JavaScript 基础 · 模块。
执行模型与作用域
执行上下文与词法作用域决定变量在何处可访问、闭包如何形成,以及 var / let / const 的差异。语言概览见 JavaScript 基础。
数据结构与集合增强
Map / Set、WeakMap / WeakSet、数组高阶方法、TypedArray 与 Symbol 等扩展了内置数据结构能力。见 语言基础。
Series
js
1 / 14