当前位置:首页 > 日记 > 正文

JavaScript学习笔记之函数记忆

JavaScript学习笔记之函数记忆

本文讲解函数记忆与菲波那切数列的实现,分享给大家,具体如下

定义

函数记忆是指将上次的计算结果缓存起来,当下次调用时,如果遇到相同的参数,就直接返回缓存中的数据。

举个例子:

function add(a, b) {  return a + b;}// 假设 memorize 可以实现函数记忆var memoizedAdd = memorize(add);memoizedAdd(1, 2) // 3memoizedAdd(1, 2) // 相同的参数,第二次调用时,从缓存中取出数据,而非重新计算一次

原理

实现这样一个 memorize 函数很简单,原理上只用把参数和对应的结果数据存到一个对象中,调用时,判断参数对应的数据是否存在,存在就返回对应的结果数据。

第一版

我们来写一版:

// 第一版 (来自《JavaScript权威指南》)function memoize(f) {  var cache = {};  return function(){    var key = arguments.length + Array.prototype.join.call(arguments, ",");    if (key in cache) {      return cache[key]    }    else return cache[key] = f.apply(this, arguments)  }}

我们来测试一下:

var add = function(a, b, c) { return a + b + c}var memoizedAdd = memorize(add)console.time('use memorize')for(var i = 0; i < 100000; i++) {  memoizedAdd(1, 2, 3)}console.timeEnd('use memorize')console.time('not use memorize')for(var i = 0; i < 100000; i++) {  add(1, 2, 3)}console.timeEnd('not use memorize')

在 Chrome 中,使用 memorize 大约耗时 60ms,如果我们不使用函数记忆,大约耗时 1.3 ms 左右。

注意

什么,我们使用了看似高大上的函数记忆,结果却更加耗时,这个例子近乎有 60 倍呢!

所以,函数记忆也并不是万能的,你看这个简单的场景,其实并不适合用函数记忆。

需要注意的是,函数记忆只是一种编程技巧,本质上是牺牲算法的空间复杂度以换取更优的时间复杂度,在客户端 JavaScript 中代码的执行时间复杂度往往成为瓶颈,因此在大多数场景下,这种牺牲空间换取时间的做法以提升程序执行效率的做法是非常可取的。

第二版

因为第一版使用了 join 方法,我们很容易想到当参数是对象的时候,就会自动调用 toString 方法转换成 [Object object],再拼接字符串作为 key 值。我们写个 demo 验证一下这个问题:

var propValue = function(obj){  return obj.value}var memoizedAdd = memorize(propValue)console.log(memoizedAdd({value: 1})) // 1console.log(memoizedAdd({value: 2})) // 1

两者都返回了 1,显然是有问题的,所以我们看看 underscore 的 memoize 函数是如何实现的:

// 第二版 (来自 underscore 的实现)var memorize = function(func, hasher) {  var memoize = function(key) {    var cache = memoize.cache;    var address = '' + (hasher ? hasher.apply(this, arguments) : key);    if (!cache[address]) {      cache[address] = func.apply(this, arguments);    }    return cache[address];  };  memoize.cache = {};  return memoize;};

从这个实现可以看出,underscore 默认使用 function 的第一个参数作为 key,所以如果直接使用

var add = function(a, b, c) { return a + b + c}var memoizedAdd = memorize(add)memoizedAdd(1, 2, 3) // 6memoizedAdd(1, 2, 4) // 6

肯定是有问题的,如果要支持多参数,我们就需要传入 hasher 函数,自定义存储的 key 值。所以我们考虑使用 JSON.stringify:

var memoizedAdd = memorize(add, function(){  var args = Array.prototype.slice.call(arguments)  return JSON.stringify(args)})console.log(memoizedAdd(1, 2, 3)) // 6console.log(memoizedAdd(1, 2, 4)) // 7

如果使用 JSON.stringify,参数是对象的问题也可以得到解决,因为存储的是对象序列化后的字符串。

适用场景

我们以斐波那契数列为例:

var count = 0;var fibonacci = function(n){  count++;  return n < 2? n : fibonacci(n-1) + fibonacci(n-2);};for (var i = 0; i <= 10; i++){  fibonacci(i)}console.log(count) // 453

我们会发现最后的 count 数为 453,也就是说 fibonacci 函数被调用了 453 次!也许你会想,我只是循环到了 10,为什么就被调用了这么多次,所以我们来具体分析下:

当执行 fib(0) 时,调用 1 次

当执行 fib(1) 时,调用 1 次

当执行 fib(2) 时,相当于 fib(1) + fib(0) 加上 fib(2) 本身这一次,共 1 + 1 + 1 = 3 次

当执行 fib(3) 时,相当于 fib(2) + fib(1) 加上 fib(3) 本身这一次,共 3 + 1 + 1 = 5 次

当执行 fib(4) 时,相当于 fib(3) + fib(2) 加上 fib(4) 本身这一次,共 5 + 3 + 1 = 9 次

当执行 fib(5) 时,相当于 fib(4) + fib(3) 加上 fib(5) 本身这一次,共 9 + 5 + 1 = 15 次

当执行 fib(6) 时,相当于 fib(5) + fib(4) 加上 fib(6) 本身这一次,共 15 + 9 + 1 = 25 次

当执行 fib(7) 时,相当于 fib(6) + fib(5) 加上 fib(7) 本身这一次,共 25 + 15 + 1 = 41 次

当执行 fib(8) 时,相当于 fib(7) + fib(6) 加上 fib(8) 本身这一次,共 41 + 25 + 1 = 67 次

当执行 fib(9) 时,相当于 fib(8) + fib(7) 加上 fib(9) 本身这一次,共 67 + 41 + 1 = 109 次

当执行 fib(10) 时,相当于 fib(9) + fib(8) 加上 fib(10) 本身这一次,共 109 + 67 + 1 = 177 次
所以执行的总次数为:177 + 109 + 67 + 41 + 25 + 15 + 9 + 5 + 3 + 1 + 1 = 453 次!

如果我们使用函数记忆呢?

var count = 0;var fibonacci = function(n) {  count++;  return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);};fibonacci = memorize(fibonacci)for (var i = 0; i <= 10; i++) {  fibonacci(i)}console.log(count) // 12

我们会发现最后的总次数为 12 次,因为使用了函数记忆,调用次数从 453 次降低为了 12 次!

兴奋的同时不要忘记思考:为什么会是 12 次呢?

从 0 到 10 的结果各储存一遍,应该是 11 次呐?咦,那多出来的一次是从哪里来的?

所以我们还需要认真看下我们的写法,在我们的写法中,其实我们用生成的 fibonacci 函数覆盖了原本了 fibonacci 函数,当我们执行 fibonacci(0) 时,执行一次函数,cache 为 {0: 0},但是当我们执行 fibonacci(2) 的时候,执行 fibonacci(1) + fibonacci(0),因为 fibonacci(0) 的值为 0, !cache[address] 的结果为 true,又会执行一次 fibonacci 函数。原来,多出来的那一次是在这里!

多说一句

也许你会觉得在日常开发中又用不到 fibonacci,这个例子感觉实用价值不高呐,其实,这个例子是用来表明一种使用的场景,也就是如果需要大量重复的计算,或者大量计算又依赖于之前的结果,便可以考虑使用函数记忆。而这种场景,当你遇到的时候,你就会知道的。

相关文章

怎样用PS制作个性化的签名水印用PS

怎样用PS制作个性化的签名水印用PS

方法,水印,电脑软件,PS,  很多人都喜欢在作品上加入水印,用以保障相片的版权。下面是PS制作个性化签名水印的方法,希望小编整理的对你有用,欢迎阅读。用PS制作个性化的签名水印的方法用PS制作个性化的签名水印的方法图1  1、先签个大名真…

详解如何使用webpack在vue项目中写

详解如何使用webpack在vue项目中写

语法,项目,如何使用,详解,电脑软件,本文介绍了如何使用webpack在vue项目中写jsx语法,分享给大家,具体如下:我们知道Vue 2.0中对虚拟DOM的支持。我们可以通过JavaScript动态的创建元素,而不用在template中写HTML代码。虚拟DOM最终将被渲染为真…

微信小程序 空白页重定向解决办法

微信小程序 空白页重定向解决办法

重定向,解决办法,程序,空白页,电脑软件,微信小程序 空白页重定向解决办法 在刚开始的时候将小程序的入口文件直接指向tabbar 的首页,此时出现问题:二维码扫描,第一次不关闭首页,第二次进入时;不会经过onLoad过程解析scene参数;官方中解释:tabbar…

excel表格自定义公式的教程excel表

excel表格自定义公式的教程excel表

自定义,教程,公式,表格,电脑软件,  Excel中经常需要自定义公式进行计算,公式具体该如何自定义公式呢?下面是小编带来的关于excel表格自定义公式的教程,希望阅读过后对你有所启发!excel表格自定义公式教程自定义公式步骤1:点击&ldquo;工具&rd…

ps如何修改文字的颜色

ps如何修改文字的颜色

修改,文字,方法,颜色,电脑软件,  文字的颜色可以随意修改,只要大家觉得文字颜色合适。那么大家知道ps如何修改文字的颜色吗?下面是小编整理的ps如何修改文字的颜色的方法,希望能帮到大家!ps修改文字的颜色的方法选中文字图层,单击鼠标右键,选…

bootstrap表格内容过长时用省略号

bootstrap表格内容过长时用省略号

解决方法,省略号,时用,表格,内容,首先 ,bootstrap中当td内容超过我给的固定宽度时,省略号代替的代码如下:<table class="table table-bordered"> <thead> <tr> <th class="center" style='width:38%;'>商品名称</th> …

cdr怎么导出透明背景的?

cdr怎么导出透明背景的?

透明背景,电脑软件,cdr,cdr中想要制作透明背景的图片,该怎么制作呢?下面我们就来看看详细的制作方法。软件名称:CorelDRAW X8 64位 简体中文安装免费版(附序列号)软件大小:547.9MB更新时间:2016-11-231、打开要导出透明图片的文件。如果背景不是…

2010版ppt怎么嵌入特殊字体

2010版ppt怎么嵌入特殊字体

字体,方法,电脑软件,ppt,  之前说了如何嵌入特殊字体以方便在没有该字体的电脑上显示完整的ppt样式,那么问题来了,有的情况会提示无法嵌入字体,要怎么解决呢?其实小编有方法解决,下面给大家分享ppt2010嵌入特殊字体的方法,不懂的朋友会请多多…

echarts学习笔记之箱线图的分析与

echarts学习笔记之箱线图的分析与

绘制,线图,详解,学习笔记,电脑软件,一、箱线图 Box-plot箱线图(Boxplot)也称箱须图(Box-whisker Plot),它是用一组数据中的最小值、第一四分位数、中位数、第三四分位数和最大值来反映数据分布的中心位置和散布范围,可以粗略地看出数据是否具有对…

js调用刷新界面的几种方式

js调用刷新界面的几种方式

刷新,调用,界面,几种,方式,有的时候需要手动调用来刷新界面,比如非动态切换的语言模式啊,风格样式啊什么的。<!DOCTYPE html><html> <head> <meta charset='utf-8'> </head> <body> <button onclick="click1()">location.reload();</but…

JS获取一个表单字段中多条数据并转

JS获取一个表单字段中多条数据并转

表单,字段,数据,多条,格式,如图需要获取下面两个li标签里面的数据,然后传给后台;而后台接收的数据格式是json的,所以需要把两个li里面的信息转化为以下格式的。{recieverName:小红,recieverPhone:12341234,recieverAddress:中国湖南},{recieve…

经典令人心痛伤感个性签名集锦

经典令人心痛伤感个性签名集锦

心痛,个性签名,经典,伤感,集锦,  有些人的经历多了,看到很多心痛伤感的事情,用一些文字表达内心的心痛伤感的情绪,以下是小编为大家收集整理的《经典令人心痛伤感个性签名》全部内容,如果您喜欢小编的推荐,请继续关注。经典令人心痛伤感个性签…