英文: Understanding Memoization in JavaScript to Improve Performance

中文: 了解JavaScript中的Memoization以提高性能--react的应用(欢迎star)

我们渴望提高应用程序的性能,MemoizationJavaScript中的一种技术,通过缓存结果并在下一个操作中重新使用缓存来加速查找费时的操作。

在这里,我们将看到memoization的用法以及它如何帮助优化应用的性能。

Memoization: 基本理念

如果我们有CPU密集型操作,我们可以通过将初始操作的结果存储在缓存中来优化使用。如果操作必然会再次执行,我们将不再麻烦再次使用我们的CPU,因为相同结果的结果存储在某个地方,我们只是简单地返回结果。

可以看下面的例子:

function longOp(arg) {if( cache has operation result for arg) {return the cache}else {假设执行一个耗时30分钟的操作把结果存在`cache`缓存里}return the result
}
longOp('lp') // 因为第一次执行这个参数的操作,所以需要耗时30分钟
// 接下来会把结果缓存起来
longOp('bp') // 同样的第一次执行bp参数的操作,也需要耗时30分钟
// 同样会把结果缓存起来
longOp('bp') // 第二次出现了
// 会很快的把结果从缓存里取出来
longOp('lp') //也同样出现过了
// 快速的取出结果
复制代码

就CPU使用而言,上面的伪函数longOp是一种耗时的功能。上面的代码会把第一次的结果给缓存起来,后面具有相同输入的调用都会从缓存中提取结果,这样就会绕过时间和资源消耗。

下面看一个平方根的例子:

function sqrt(arg) {return Math.sqrt(arg);
}
log(sqrt(4)) // 2
log(sqrt(9)) // 3
复制代码

现在我们可以使用memoize来处理这个函数:

function sqrt(arg) {if (!sqrt.cache) {sqrt.cache = {}}if (!sqrt.cache[arg]) {return sqrt.cache[arg] = Math.sqrt(arg)}return sqrt.cache[arg]
}
复制代码

可以看到,结果会缓存在cache的属性里。

Memoization:履行

在上面部分,我们为函数添加了memoization

现在,我们可以创建一个独立的函数来记忆任何函数。我们将此函数称为memoize

function memoize(fn) {return function () {var args = Array.prototype.slice.call(arguments)fn.cache = fn.cache || {};return fn.cache[args] ? fn.cache[args] : (fn.cache[args] = fn.apply(this,args))}
}
复制代码

我们可以看到这段代码接收另外一个函数作为参数并返回。

要使用此函数,我们调用memoize将要缓存的函数作为参数传递。

memoizedFunction = memoize(funtionToMemoize)
memoizedFunction(args)
复制代码

我们现在把上面的例子加入到这个里面:

function sqrt(arg) {return Math.sqrt(arg);
}
const memoizedSqrt = memoize(sqrt)
复制代码

返回的函数memoizedSqrt现在是sqrtmemoized版本。

我们来调用下:

//...
memoizedSqrt(4) // 2 calculated(计算)
memoizedSqrt(4) // 2 cached
memoizedSqrt(9) // 3 calculated
memoizedSqrt(9) // 3 cached
memoizedSqrt(25) // 5 calculated
memoizedSqrt(25) // 5 cached
复制代码

我们可以将memoize函数添加到Function原型中,以便我们的应用程序中定义的每个函数都继承memoize函数并可以调用它。

Function.prototype.memoize = function() {var self = thisreturn function () {var args = Array.prototype.slice.call(arguments)self.cache = self.cache || {};return self.cache[args] ? self.cache[args] : (self.cache[args] = self(args))}
}
复制代码

我们知道JS中定义的所有函数都是从Function.prototype继承的。因此,添加到Function.prototype的任何内容都可用于我们定义的所有函数。

我们现在再来试试:

function sqrt(arg) {return Math.sqrt(arg);
}
// ...
const memoizedSqrt = sqrt.memoize()
log(memoizedSqrt(4)) // 2, calculated
log(memoizedSqrt(4)) // 2, returns result from cache
log(memoizedSqrt(9)) // 3, calculated
log(memoizedSqrt(9)) // 3, returns result from cache
log(memoizedSqrt(25)) // 5, calculated
log(memoizedSqrt(25)) // 5, returns result from cache
复制代码

Memoization: Speed and Benchmarking

memoization的目标是速度,他通过内存来提升速度。

看下面的对比: 文件名: memo.js:

function memoize(fn) {return function () {var args = Array.prototype.slice.call(arguments)fn.cache = fn.cache || {};return fn.cache[args] ? fn.cache[args] : (fn.cache[args] = fn.apply(this,args))}
}function sqrt(arg) {return Math.sqrt(arg);
}
const memoizedSqrt = memoize(sqrt)
console.time("non-memoized call")
console.log(sqrt(4))
console.timeEnd("non-memoized call")
console.time("memoized call")
console.log(sqrt(4))
console.timeEnd("memoized call")
复制代码

然后node memo.js可以发现输出,我这里是:

2
non-memoized call: 2.210ms
2
memoized call: 0.054ms
复制代码

可以发现,速度还是提升了不少。

Memoization: 该什么时候使用

在这里,memoization通常会缩短执行时间并影响我们应用程序的性能。当我们知道一组输入将产生某个输出时,memoization最有效。

遵循最佳实践,应该在纯函数上实现memoization。纯函数输入什么就返回什么,不存在副作用。

记住这个是以空间换速度,所以最好确定你是否值得那么做,有些场景很有必要使用。

在处理递归函数时,Memoization最有效,递归函数用于执行诸如GUI渲染,Sprite和动画物理等繁重操作。

Memoization: 什么时候不要使用

不是纯函数的时候(输出不完全依赖于输入)。

使用案例:斐波那契系列(Fibonacci)

Fibonacci是许多复杂算法中的一种,使用memoization优化的作用很明显。

1,1,2,3,5,8,13,21,34,55,89 每个数字是前面两个数字的和。 现在我们用js实现:

function fibonacci(num) {if (num == 1 || num == 2) {return 1}return fibonacci(num-1) + fibonacci(num-2)
}
复制代码

如果num超过2,则此函数是递归的。它以递减方式递归调用自身。

log(fibonacci(4)) // 3
复制代码

让我们根据memoized版本对运行斐波那契的有效性进行测试。 memo.js文件:

function memoize(fn) {return function () {var args = Array.prototype.slice.call(arguments)fn.cache = fn.cache || {};return fn.cache[args] ? fn.cache[args] : (fn.cache[args] = fn.apply(this,args))}
}function fibonacci(num) {if (num == 1 || num == 2) {return 1}return fibonacci(num-1) + fibonacci(num-2)
}const memFib = memoize(fibonacci)
console.log('profiling tests for fibonacci')
console.time("non-memoized call")
console.log(memFib(6))
console.timeEnd("non-memoized call")
console.time("memoized call")
console.log(memFib(6))
console.timeEnd("memoized call")
复制代码

接下来调用:

$ node memo.js
profiling tests for fibonacci
8
non-memoized call: 1.027ms
8
memoized call: 0.046ms
复制代码

可以发现,很小的一个数字,时间差距就那么大了。

上面是参考原文,下面是个人感想。

咋说呢, 第一时间想到了reactmemo组件(注意 这里,现版本(16.6.3)有两个memo,一个是React.memo,还有一个是React.useMemo, 我们这里说的是useMemo),相信关注react动态的都知道useMemo是新出来的hooks api,并且这个api是作用于function组件,官方文档写的是这个可以优化用以优化每次渲染的耗时工作。

看文档这里介绍的也挺明白。今天看到medium的这篇文章,感觉和react memo有关系,就去看了下源码,发现的确是和本文所述一样。

export function useMemo<T>(nextCreate: () => T,inputs: Array<mixed> | void | null,
): T {currentlyRenderingFiber = resolveCurrentlyRenderingFiber(); //返回一个变量workInProgressHook = createWorkInProgressHook(); // 返回包含memoizedState的hook对象const nextInputs =inputs !== undefined && inputs !== null ? inputs : [nextCreate]; // 需要保存下来的inputs,用作下次取用的keyconst prevState = workInProgressHook.memoizedState; // 获取之前缓存的值if (prevState !== null) {const prevInputs = prevState[1];// prevState不为空,并且取出上次存的`key`, 然后下面判断(前后的`key`是不是同一个),如果是就直接返回,否则继续向下if (areHookInputsEqual(nextInputs, prevInputs)) {return prevState[0];}}const nextValue = nextCreate(); //执行useMemo传入的第一个参数(函数)workInProgressHook.memoizedState = [nextValue, nextInputs]; // 存入memoizedState以便下次对比使用return nextValue;
}
复制代码

进行了缓存(workInProgressHook.memoizedState就是hook返回的对象并且包含memoizedState,进行对比前后的inputs是否相同,然后再次进行操作),并且支持传递第二个数组参数作为key

果然, useMemo就是用的本文提到的memoization来提高性能的。

其实从官方文档就知道这个两个有关系了 :cry: :

Pass a “create” function and an array of inputs. useMemo will only recompute the memoized value when one of the inputs has changed. This optimization helps to avoid expensive calculations on every render.

个人学习记录--欢迎star&watch 一起学习哦

了解JavaScript中的Memoization以提高性能,再看React的应用相关推荐

  1. ubuntu 装在ssd_如何在Ubuntu中调整SSD以提高性能

    ubuntu 装在ssd There are lots of tips out there for tweaking your SSD in Linux and lots of anecdotal r ...

  2. 工作了 需要学OC 写博客来总结学习中的重点 忘记了可以再看一看

    教学视频推荐stanford ios7 应用开发这个课程,往后的版本就是swift语言的ios开发了 入门objective-c基础教程 进阶书记<<Effective 0bjective ...

  3. SQL中使用WITH AS提高性能-使用公用表表达式(CTE)简化嵌套SQL

    一.WITH AS的含义     WITH AS短语,也叫做子查询部分(subquery factoring),可以让你做很多事情,定义一个SQL片断,该SQL片断会被整个SQL语句所用到.有的时候, ...

  4. javascript php 性能,JavaScript知识点总结之如何提高性能_javascript技巧

    JavaScript的性能问题不容小觑,这就需要我们开发人员在编写JavaScript程序时多注意一些细节,本文非常详细的介绍了一下JavaScript性能优化方面的知识点,绝对是干货. 先给大家巩固 ...

  5. 配置内存中OLTP文件组提高性能

    在今天的文章里,我想谈下使用内存中OLTP的内存优化文件组来获得持久性,还有如何配置它来获得高性能.在进入正题前,我想简单介绍下使用你数据库里这个特定文件组,内存OLTP是如何获得持久性的. 内存中O ...

  6. Mysql中“饮鸠止渴“提高性能的方法(临时性提升)

    该文章为<MySQL实战45讲>课程学习笔记及部分摘抄,原课程链接MySQL 实战 45 讲 (geekbang.org) 不知道你在实际运维过程中有没有碰到这样的情景:业务高峰期,生产环 ...

  7. 从源码的角度再看 React JS 中的 setState

    在这一篇文章中,我们从源码的角度再次理解下 setState 的更新机制,供深入研究学习之用. 在上一篇手记「深入理解 React JS 中的 setState」中,我们简单地理解了 React 中 ...

  8. javascript中的异步 macrotask 和 microtask 简介

    什么是macrotask?什么是microtask? 在理解什么是macrotask?什么是microtask之前,我们先来看看javascript中的事件循环机制,先看如下面一段代码: consol ...

  9. JavaScript 中的函数式编程:函数,组合和柯里化

    作者:Fernando Doglio 译者:前端小智 来源:medium 移动端阅读:点这里 点赞再看,微信搜索 [大迁世界] 关注这个没有大厂背景,但有着一股向上积极心态人.本文 GitHub ht ...

最新文章

  1. Give root password for maintenance 问题解决.
  2. shell 做加法运算_C语言探索之旅 | 第一部分第七课:运算那点事
  3. 分享实录 | 企业CICD规模化落地浅析
  4. asp.net实现无刷新,无须AJAX
  5. bzoj1013 [JSOI2008]球形空间产生器sphere
  6. 【codevs1073】家族,胡写并查集
  7. 私人订制,一份专属你的数据分析课程!
  8. node.js 设置 淘宝 镜像
  9. mysql sql语句 编辑器_三个非常实用的开源SQL编辑器
  10. 【百度地图】在百度地图上框出边界线
  11. Windows下安装Nutch
  12. 微信加人的108种方法
  13. 自动化和半自动矢量化提取地物矢量轮廓
  14. Sass Module 介绍
  15. Qt入门教程【Core篇】Layout布局(布局管理器、手动布局)
  16. 计算机桌面排列,如何进行桌面图标排列 让你的桌面一秒变酷炫【图文教程】...
  17. 常见的概率公式及其推导(马尔科夫HMM系列课程拓展)
  18. 实现多数据源混合计算的方案之一
  19. 解决Angular里的报错:ERROR Error: Uncaught (in promise): NullInjectorError: R3InjectorError()
  20. IntelliJ IDEA中文注释字体更换最佳方法

热门文章

  1. 329. 矩阵中的最长递增路径
  2. Gazebo仿真平台
  3. 操作系统实践(四/五)
  4. ThreadLocal实现线程范围内的共享变量
  5. 数据结构期末复习(に)--链式栈定义及使用
  6. Netty构建游戏服务器(一)--基本概念与原理
  7. [原]openstack-kilo--issue(十八) Error parsing template file: Template format version not found.
  8. mysql学习笔记03 mysql数据类型
  9. SCSF 系列:Smart Client Software Factory 与 ObjectBuilder
  10. 手把手教你用Matplotlib进行数据可视化