JavaScript 中的间谍依赖于作为对象属性的函数。
They work by replacing the object property with a new function that wraps and tracks calls to the original.
如果递归函数直接调用自身,则无法监视这些调用,因为它们直接引用该函数。
为了监视递归调用,它们必须引用可以被监视的函数。幸运的是,这是可能的,并且可以通过以下两种方式之一完成。
第一种解决方案是将递归函数包装在一个对象中,并引用对象属性进行递归:
fib.js
const wrappingObject = {
memoization: (num, hash = { '0':0, '1':1 }) => {
if (hash[num-1] === undefined) {
hash[num-1] = wrappingObject.memoization(num-1, hash);
}
return hash[num-1] + hash[num-2];
}
};
export default wrappingObject;
fib.test.js
import fib from './fib';
describe('memoization', () => {
it('should memoize correctly', () => {
const mock = jest.spyOn(fib, 'memoization');
const result = fib.memoization(50);
expect(result).toBe(12586269025);
expect(mock).toHaveBeenCalledTimes(49);
mock.mockRestore();
});
});
第二种解决方案是将递归函数导入回自己的模块中,并使用导入的函数进行递归:
fib.js
import * as fib from './fib'; // <= import the module into itself
export function memoization(num, hash = { '0':0, '1':1 }) {
if (hash[num-1] === undefined) {
hash[num-1] = fib.memoization(num-1, hash); // <= call memoization using the module
}
return hash[num-1] + hash[num-2];
}
fib.test.js
import * as fib from './fib';
describe('memoization', () => {
it('should memoize correctly', () => {
const mock = jest.spyOn(fib, 'memoization');
const result = fib.memoization(50);
expect(result).toBe(12586269025);
expect(mock).toHaveBeenCalledTimes(49);
mock.mockRestore();
});
});
上面的测试使用的是 Jest,但是这些想法扩展到了其他测试框架。例如,这里是使用 Jasmine 的第二种解决方案的测试:
// ---- fib.test.js ----
import * as fib from './fib';
describe('memoization', () => {
it('should memoize correctly', () => {
const spy = spyOn(fib, 'memoization').and.callThrough();
const result = fib.memoization(50);
expect(result).toBe(12586269025);
expect(spy.calls.count()).toBe(49);
});
});
(我优化了记忆以要求最少的调用次数)