手搓一个 Function.prototype.once()

一个只执行一次的函数

Posted by MurphyChen on April 3, 2022

一道面试题:定义一个 once 方法,该函数方法只执行一次,具体要求和描述如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义一个函数方法,满足下面要求
function f(x) {
  console.log(x)
  return x * 2
}

const fOnce = f.once() // 你要定义的 once 方法
let ans1 = fOnce(3) // 打印 3,返回结果为 6
let ans2 = fOnce(2) // 什么都不打印,返回 6
let ans3 = fOnce(1) // 什么都不打印,返回 6
console.log(ans1, ans2, ans3) // 6 6 6

let ans4 = f(10) // 打印 10,返回 20
console.log(ans4) // 打印 20

这个 once() 函数方法就是在原函数基础上,创建一个新函数(闭包),这个新函数只会执行一次。而且后续继续执行会返回第一次执行时的结果(需要缓存结果)。而且对原函数不造成影响。

看到 f.once(),就知道是要在构造函数对象原型上定义 once 方法,也就是标题所述定义一个 Function.prototype.once()

该方法返回一个新的函数 fOnce,说明要在方法内部 return function() {},这很难不让我们想到要使用 闭包。回忆下红宝书中对于闭包的定义:

闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。——《JS 高程》

执行了一次 fOnce(3) 后,对此函数的后续调用都返回的是第一次调用的结果,这说明我们要 对结果进行缓存

1
2
3
fOnce(3) // 6
fOnce(2) // 6
fOnce(1) // 6

然后就是传参,内部函数执行需要传参我们可以使用 fn.apply(null, ...arguments) 的形式。

有了整体的思路,现在我们来看一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
Function.prototype.once = function () {
  let ans = null // ans缓存结果,同时判断该函数是否执行过
  let that = this // that保存原函数中的this,避免this丢失,this指向原函数
  // 返回一个函数闭包
  return function () {
    // 闭包特性,内部可以访问到外部的ans和that
    if (ans) return ans // 根据ans判断函数是否首次次调用;后续调用,直接返回缓存结果
    let res = that.call(that, ...arguments) // 执行原函数,同时传入参数,缓存返回结果res,以赋值给ans
    ans = res // ans被赋值,于是对于后续调用,函数都会直接 return ans
    return ans // 第一次调用,返回结果
  }
}

函数执行结果:

image.png


实际上,至此我们已经手写了一个 TC39 的最新提案:

image.png

该提案的具体描述见下面链接:

https://github.com/tc39/proposal-function-once#description