FORMA

函数进阶

this 绑定、高阶函数、纯函数、记忆化、递归与 IIFE 等是日常开发与面试中的核心主题。前置:语言基础作用域与闭包

一、this 绑定规则

this 的值取决于函数被调用的方式,而非定义时的位置。共有四种绑定规则,优先级从低到高。

1. 默认绑定

非严格模式下,独立函数调用时 this 为全局对象(globalThis);严格模式模块顶层中为 undefined

js
function foo() {
    console.log(this);
}
foo(); // 非严格: window; 严格: undefined

2. 隐式绑定

通过对象调用方法时,this 指向该对象。

js
const obj = {
    name: "Alice",
    greet() {
        console.log(this.name);
    },
};
obj.greet(); // 'Alice'

注意:隐式绑定容易丢失,例如 const fn = obj.greet; fn() 恢复为默认绑定。

3. 显式绑定

使用 callapplybind 手动指定 this

  • call(thisArg, arg1, arg2, ...) – 立即执行,参数逐个传递。
  • apply(thisArg, [argsArray]) – 立即执行,参数以数组形式传递。
  • bind(thisArg, ...) – 返回一个新函数,可延迟执行。
js
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

js
function Person(name) {
    this.name = name;
}
const p = new Person("Charlie");
console.log(p.name); // 'Charlie'

优先级new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定。

5. 箭头函数的词法 this

箭头函数没有自己的 this,它捕获外层(定义时)作用域this 值,且无法被 call/apply/bind 改变。

js
const obj = {
    name: "Diana",
    regular: function () {
        console.log(this.name);
    },
    arrow: () => {
        console.log(this.name);
    },
};
obj.regular(); // 'Diana'
obj.arrow(); // undefined (this 指向外层,此处为全局)

二、高阶函数

高阶函数满足至少一个条件:接收函数作为参数返回一个函数

1. 函数作为参数/返回值

js
// 参数
[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)

将一个多参数函数转化为一系列单参数函数。

js
function add(a) {
    return function (b) {
        return a + b;
    };
}
const add5 = add(5);
add5(3); // 8

3. 组合函数(Compose)

将多个函数从右向左组合执行。

js
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 请求)。
js
// 纯函数
function add(a, b) {
    return a + b;
}

// 不纯(有副作用)
let counter = 0;
function increment() {
    counter++;
} // 修改外部变量

纯函数易于测试、缓存、并发执行,是函数式编程的基石。

四、函数记忆(Memoization)

缓存函数的计算结果,避免重复运算。适用于纯函数且开销较大的场景。

js
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); // 极快,因为缓存了中间结果

五、递归与尾调用优化

  • 递归:函数调用自身。可能导致调用栈溢出(递归深度过大)。
  • 尾调用:函数最后一步是调用另一个函数,并且该调用的返回值直接返回。
  • 尾递归:递归调用是尾调用。理论上编译器可以优化,复用当前栈帧,避免栈增长。
js
// 普通递归(非尾递归)
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)定义后立即执行,用于创建独立作用域,避免污染全局。

js
(function () {
    var privateVar = "secret";
    console.log("IIFE runs");
})();

现代替代:ES6 引入块级作用域let / const + {}),无需函数包裹即可隔离变量。

js
{
    let privateVar = "secret";
    // 仅在此块内有效
}
// privateVar 不可访问

另外,模块(ES Modules)天生提供作用域隔离,已成为主流方案。

js
// module.js(`private` 是严格模式保留字,模块顶层严格模式下不能作为变量名,此处改名为 internal)
const internal = 42;
export const publicFn = () => { ... };

除非需要立即执行逻辑且对旧环境兼容,否则推荐使用块级作用域或模块。

参考文献

以下链接在编写时均可正常访问:

资料说明
MDN:this绑定规则
MDN:箭头函数词法 this
MDN:Function.prototype.bind显式绑定
MDN:尾调用优化TCO 说明(英文)
ECMA-262:尾位置调用规范(英文)

Series

js

1 / 14