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 ...
随机推荐
- HCNA网工笔记Day2 - IP编址
IP编址 主机地址子网掩码:区分主机位和网络位网络地址:主机位全部为0,比如 127.0.0.0/8广播地址:主机位全部为1,比如 127.255.255.255/8 一般网络地址和广播地址不能pin ...
- PHP gd 库添加 freetype
gd 库未安装 freetype 可能导致验证码图片加载不出来等问题. 先到 PHP 的扩展目录里面找到 gd 扩展文件夹 最好先 make clean 清除之前编译的可执行文件及配置文件,运行/us ...
- wamp2.5版本配置多端口虚拟主机
1.保证httpd.conf下 LoadModule php5_module "D:/E/php/wamp/bin/php/php5.5.12/php5apache2_4.dll" ...
- 在img标签上尽量不要使用onerror事件
在img标签上尽量不要使用onerror事件 因为在之前的时候,我在本地对用户头像修改发现,如果图片加载失败, 使用onerror事件去获取一个默认地址的图片虽然这是可行的,但是如果刚好onerror ...
- css为什么要定最小宽度和最大宽度,最小宽度和最大宽度有什么用
设最小和最大宽度,主要是为了防止页面变形而已如,如果一个页面的宽度设置为百分比,这时此页面的宽度会根据浏览器的宽度而定但如果浏览器的宽度过小,页面就会变形,例如,你做的用百分比设置的页面,而用户端的浏 ...
- FZu Problem 2233 ~APTX4869 (并查集 + sort)
题目链接: FZu Problem 2233 ~APTX4869 题目描述: 给一个n*n的矩阵,(i, j)表示第 i 种材料 和 第 j 种材料的影响值,这个矩阵代表这n个物品之间的影响值.当把这 ...
- BZOJ4653(区间离散化+线段树+决策单调尺取)
写得很好的题解 一眼过去很像是:排序,然后从前向后扫,有这个区间时插到树里,过去以后再删除.然后事实也是这样做的…… 具体起来: 1.如果考虑暴力的话,一种想法是枚举左端和右端要选取的区间(如果我们按 ...
- 微信小程序消息推送,前端操作
<form bindsubmit="getFormId" report-submit="true"> <button form-type=&q ...
- LookupError: unknown encoding: idna 的处理方法
写了一个脚本,想把它打包成exe文件,在python编译器中运行正常,但是打包成.exe文件运行报错 LookupError: unknown encoding: idna 找遍资料终于找到了解决方法 ...
- 你不知道的border-radius
对于border-radius这个属性,我们知道它可以用来设置边框圆角,利用它我们可以画出很多形状 这就需要了解到border-radius的各式写法: border-radius的写法: 1.只设置 ...