JavaScript 高级技巧 Memoization
memoization 来源于拉丁语 memorandum ("to be remembered"),不要与 memorization 混淆了。
首先来看一下维基百科的描述:
In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.
简单来说,memoization 是一种优化技术,主要用于通过存储昂贵的函数调用的结果来加速计算机程序,并在再次发生相同的输入时返回缓存的结果。
本文首先介绍一个简单的使用 memoization 优化技术的例子,然后解读 underscore 和 reselect 库中使用 memoization 的源码,加深理解。
阶乘
不使用 memoization
不假思索,我们会立即写下如下的代码:
const factorial = n => {
if (n === 1) {
return 1
} else {
return factorial(n - 1) * n
}
};
使用 memoization
const cache = []
const factorial = n => {
if (n === 1) {
return 1
} else if (cache[n - 1]) {
return cache[n - 1]
} else {
let result = factorial(n - 1) * n
cache[n - 1] = result
return result
}
};
使用 闭包 和 memoization
常见的方式是 闭包 和 memoization 一起搭配使用:
const factorialMemo = () => {
const cache = []
const factorial = n => {
if (n === 1) {
return 1
} else if (cache[n - 1]) {
console.log(`get factorial(${n}) from cache...`)
return cache[n - 1]
} else {
let result = factorial(n - 1) * n
cache[n - 1] = result
return result
}
}
return factorial
};
const factorial = factorialMemo();
继续变形,下面这种编写方式是最常见的形式。
const factorialMemo = func => {
const cache = []
return function(n) {
if (cache[n - 1]) {
console.log(`get factorial(${n}) from cache...`)
return cache[n - 1]
} else {
const result = func.apply(null, arguments)
cache[n - 1] = result
return result
}
}
}
const factorial = factorialMemo(function(n) {
return n === 1 ? 1 : factorial(n - 1) * n
});
从阶乘的这个例子可以知道 memoization 是一个空间换时间的方式,存储执行结果,下次再次发生相同的输入会直接输出结果,提高了执行的速度。
underscore 源码中的 memoization
// Memoize an expensive function by storing its results.
_.memoize = function(func, hasher) {
var memoize = function(key) {
var cache = memoize.cache;
var address = '' + (hasher ? hasher.apply(this, arguments) : key);
if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
return cache[address];
};
memoize.cache = {};
return memoize;
};
代码一目了然,使用 _.memoize 来实现阶乘如下:
const factorial = _.memoize(function(n) {
return n === 1 ? 1 : factorial(n - 1) * n
});
参照这个源码,上面的阶乘继续可以变形如下:
const factorialMemo = func => {
const memoize = function(n) {
const cache = memoize.cache
if (cache[n - 1]) {
console.log(`get factorial(${n}) from cache...`)
return cache[n - 1]
} else {
const result = func.apply(null, arguments)
cache[n - 1] = result
return result
}
}
memoize.cache = []
return memoize
}
const factorial = factorialMemo(function(n) {
return n === 1 ? 1 : factorial(n - 1) * n
});
reselect 源码中的 memoization
export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
let lastArgs = null
let lastResult = null
// we reference arguments instead of spreading them for performance reasons
return function () {
if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
// apply arguments instead of spreading for performance.
lastResult = func.apply(null, arguments)
}
lastArgs = arguments
return lastResult
}
};
从源码可以知道当 lastArgs 与 arguments 相同的时候,就不会再执行 func。
总结
memoization 是一种优化技术,避免一些不必要的重复计算,可以提高计算速度。
参考
- Memoization wiki
- Understanding JavaScript Memoization In 3 Minutes
- Underscore
- reselect
- Implementing Memoization in JavaScript
原文地址:https://segmentfault.com/a/1190000016703106
JavaScript 高级技巧 Memoization的更多相关文章
- javascript实用技巧、javascript高级技巧
字号+作者:H5之家 来源:H5之家 2016-10-31 11:00 我要评论( ) 三零网提供网络编程. JavaScript 的技术文章javascript实用技巧.javascript高级技巧 ...
- JavaScript: 高级技巧: window 对象也可以添加自定义属性
JavaScript: 高级技巧: window 对象也可以添加自定义属性 例如 window.ntName = 'a';例如 window.ntXw = top; 优点是, window 无须等加载 ...
- 前端常用的库和实用技术之JavaScript高级技巧
javascript高级技巧 变量作用域和闭包 <!DOCTYPE html> <html lang="en"> <head> <meta ...
- Javascript高级技巧
上次整理了Ajax部分,这周看完了高级技巧部分,也整理下吧. 1.类型检测 使用Object.prototype.toString.call(obj)的方式. 因为无论typeof还是instance ...
- 读书笔记:javascript高级技巧(二)
四.惰性载入函数 因为浏览器兼容的原因,我们的javascript代码会有大量的if语句,将执行引导到正确的代码中,看如下函数: function createXHR(){ if (typeof XM ...
- 读书笔记:javascript高级技巧(一)
一.安全的类型检测 javascript内置的类型检测机制并非完全可靠,由于浏览器或者作用域等原因,经常会发生错误.大家知道,在任何值调用toString()方法都会返回一个[object Nativ ...
- 《前端之路》之 JavaScript 高级技巧、高阶函数(一)
目录 一.高级函数 1-1 安全的类型检测 1-2 作用域安全的构造函数 1-3 惰性载入函数 1-4 函数绑定 1-5 函数柯里化 1-6 反函数柯里化 一.高级函数 1-1 安全的类型检测 想到类 ...
- javascript高级技巧篇(作用域安全、防篡改、惰性载入、节流、自定义事件,拖放)
安全的类型检测 在任何值上调用Object原生的toString()方法,都会返回一个[object NativeConstructorName]格式字符串.每个类在内部都有一个[[Class]]属性 ...
- 每个JavaScript工程师都应懂的33个概念
摘要: 基础很重要啊! 原文:33 concepts every JavaScript developer should know 译文:每个 JavaScript 工程师都应懂的33个概念 作者:s ...
随机推荐
- bzoj 1176 [Balkan2007]Mokia 【CDQ分治】
W过大,很难在线维护,考虑离线算法 给每个操作加一个时间属性n,显然,对于n=i的询问,对它有影响的修改只在n<i中,所以可以CDQ(因为是按时间序读进来的,所以不用排序了 对于统计矩形和,可以 ...
- 51NOD 1134 最长上升子序列
给出长度为N的数组,找出这个数组的最长递增子序列.(递增子序列是指,子序列的元素是递增的) 例如:5 1 6 8 2 4 5 10,最长递增子序列是1 2 4 5 10. 输入 第1行:1个数N, ...
- python 处理时间和日期
转自: https://www.cnblogs.com/65702708/archive/2011/04/17/2018936.html Python格式化日期时间的函数为datetime.datet ...
- scanf()和scanf_s()
在最初的C语言中,原版的输入就是scanf("<格式化字符串>",<地址表>) ANSI C中没有scanf_s(),只有scanf(),scanf()在读 ...
- Json----Jackson 下载地址
下载地址: http://repo1.maven.org/maven2/com/fasterxml/jackson/
- 跟我一起玩Win32开发(3):窗口的重绘
c可以编译#include <Windows.h> //先声明一下消息处理函数 LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT msg, WPA ...
- python实现判断素数
import math def is_prime_1(n): if n <= 1: return False for i in range(2, int(math.sqrt(n) + 1)): ...
- Query on a tree again! SPOJ - QTREE3
https://vjudge.net/problem/SPOJ-QTREE3 https://www.luogu.org/problemnew/show/P4116 一个log(LCT)比两个log( ...
- 1-3方法的重载(overload)
之前已经写了一个方法sumInt用来计算两个int类型数字的和,如果要是想计算两个float类型数字的和呢?ok,那就再来写一个sumFloat方法,除此之外,还有long类型,double类型,如果 ...
- HDU 4565 So Easy! 数学 + 矩阵 + 整体思路化简
http://acm.hdu.edu.cn/showproblem.php?pid=4565 首先知道里面那个东西,是肯定有小数的,就是说小数部分是约不走的,(因为b限定了不是一个完全平方数). 因为 ...