为什么 D.Flanagan 的 "JS: Definitive Guide" 中的 memoize 功能需要 arguments.length?

Why does memoize function in D. Flanagan's "JS: Definitive Guide" need arguments.length?

我正在浏览 David Flanagan 的 "Javascript: The Definitive Guide"。

在第 8.8.4 段中,他展示了一个高阶函数,memoize() 接受一个函数作为其参数,returns 该函数的记忆版本:

 //Return a memoized version of f.
// It only works if arguments to f all have distinct string representations.
 function memoize(f) {
      var cache = {}; // Value cache stored in the closure.

      return function() {
          // Create a string version of the arguments to use as a cache key.
          var key = arguments.length + Array.prototype.join.call(arguments,",");
          if (key in cache) return cache[key];
          else return cache[key] = f.apply(this, arguments);
      }
 }

解释中有:"The returned function converts its arguments array to a string"。

如果我们只需要参数,为什么他将 arguments.lengthArray.prototype.join.call(arguments, ",") 连接起来,而不是仅将参数数组转换为字符串?

否则这两个调用将使用相同的密钥存储:

memoizedFunc('', '');
memoizedFunc(',');

在这两种情况下,连接参数的结果都是相同的字符串:,

此功能已损坏。即使所有参数都是字符串,它也不起作用。看这个例子:

 //Return a memoized version of f.
// It only works if arguments to f all have distinct string representations.
 function memoize(f) {
      var cache = {}; // Value cache stored in the closure.

      return function() {
          // Create a string version of the arguments to use as a cache key.
          var key = arguments.length + Array.prototype.join.call(arguments,",");
          if (key in cache) return cache[key];
          else return cache[key] = f.apply(this, arguments);
      }
 }

const f = memoize(function(...args) {
  console.log('f was called')
  return args
})

console.log(f(',', ''))
console.log(f('', ','))

第二次使用不同的参数调用该函数,因此它不应该 return return 缓存值。但是,'f was called' 只记录一次,因此它没有按预期工作。


要创建适用于所有情况的函数,您必须将所有参数存储在缓存中,并迭代它们以检查它们是否相同。可以这样实现:

const memoize = function(f) {
  const cache = []
  return (...args) => {
    for (const element of cache) {
      let hasSameArguments = true
      for (const i of args.keys()) {
        if (args[i] !== element.args[i]) {
          hasSameArguments = false
          break
        }
      }
      if (hasSameArguments) {
        return element.value
      }
    }
    const value = f(...args)
    cache.push({value, args})
    return value
  }
}

const f = memoize(function(...args) {
  console.log('f was called')
  return args
})

console.log(f(',', ''))
console.log(f('', ',')) // different arguments, f is called again

console.log(f(true))
console.log(f(true)) // from cache

const someObj = {}
     ,otherObj = {}

console.log(f(someObj))
console.log(f(someObj)) // the same object, result from cache
console.log(f(otherObj)) // different object, f is called again
console.log(f(otherObj))

console.log(f([1, 2, 3]))
console.log(f([1, 2, 3])) // different object, f is called again
                          // (because [1, 2, 3] !== [1, 2, 3])

请注意,它使用 === 运算符比较参数,例如,如果您使用包含相同值的数组调用该函数两次,它不会 return 缓存结果。您可以通过深入迭代参数并检查所有属性是否相同来更改此行为。