彩世界开奖app官网-彩世界平台官方网址(彩票平台)
做最好的网站
来自 前端技术 2019-12-07 08:22 的文章
当前位置: 彩世界开奖app官网 > 前端技术 > 正文

浅谈自回想函数彩世界平台官方网址

一个自记忆函数的例子

下面这个例子展现自记忆函数的工作方式:

// 自记忆素数检测函数
function isPrime (value) {
  // 创建缓存
  if (!isPrime.answers) {
    isPrime.answers = {};
  }
  // 检查缓存的值
  if (isPrime.answers[value] !== undefined) {
    return isPrime.answers[value];
  }
  // 0和1不是素数
  var prime = value !== 0 && value !== 1;
  // 检查是否为素数
  for (var i = 2; i < value; i  ) {
    if (value % i === 0) {
      prime = false;
      break;
    }
  }
  // 存储计算值
  return isPrime.answers[value] = prime
}

isPrime函数是一个自记忆素数检测函数,每当它被调用时:

首先,检查它的answers属性来确认是否已经有自记忆的缓存,如果没有,创建一个。

接下来,检查参数之前是否已经被缓存过,如果在缓存中找到该值,直接返回缓存的结果。

如果参数是一个全新的值,进行正常的素数检测。

最后,存储并返回计算值。

函数存储

利用以下代码可以完成函数存储功能。

var store = {
    nextId: 1,
    cache: {},
    add: function(fn) {
        if (!fn.id) {
            fn.id = store.nextId  ;
            return !!(store.cache[fn.id] = fn);
        }
    }
};

使用场景:可以用来存储事件的回调函数,由于在 addEventListenerattachEvent 的解绑过程中都需要原样传入绑定的函数,所以我们可以将绑定的函数存储下来,以供解绑事件时使用。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

简介

何为自记忆函数?书中提到:

记忆化(memoization)是一种构建函数的处理过程,能够记住上次计算结果

通过这句话可以得出,自记忆函数其实就是能够记住上次计算结果的函数。在实现中,我们可以这样进行处理:当函数计算得到结果时,就将该结果按照参数存储起来。采取这种方式时,如果另外一个调用也使用相同的参数,我们则可以直接返回上次存储的结果而不是再计算一遍。

显而易见,像这样避免既重复又复杂的计算可以显著提高性能。对于动画中的计算、搜索不经常变化的数据或任何耗时的数学计算来说,记忆化这种方式是十分有用的。

函数判断

一般而言,要判断一个函数类型,只需要利用 typeof functionName 即可(会返回字符串 function)。不过会有一些特殊情况让我们的判断失效,比如下面几种:

  1. Opera: 在 HTML<object> 元素上使用 typeof的话,会返回 function,而不是我们期望的 object。(书中说在 Firefox 中会出现这个问题,不过我亲自检测之后,发现我电脑上的 Firefox并没有出现上述问题,反而是 Opera 出现了这个问题 )。

  2. Safari: Safari 认为 DOMNodeList 是一个 function,所以 typeof document.body.childNodes == function。(本人未亲自尝试)

基于以上情况,我们需要寻找一种完美的解决方案,不过事实上并不存在完美的解决方案,倒是有一种接近完美的方案,那就是利用 Object.toString() 方法。代码如下:

function isFunction(fn) {
    return Object.prototype.toString.call(fn) === "[object Function]";  
}

利用这项技术,还可以判断 String, RegExp, Date等其它对象。

这里我们不直接调用 fn.toString() 的原因有两个:

  1. 不同的对象可能有自己的 toString() 方法实现。

  2. JavaScript 中的大多数类型都已经有一个预定义的 toString() 方法覆盖了 Object.prototype 提供的 toString() 方法。

从下面可以看出 StringArray 重写了 ObjecttoString() 方法。

var sContent = "Hello World";
console.log(sContent.toString());   // "Hello World"

var aContent = [1, 2, 3];
console.log(aContent.toString());   // "[1, 2, 3]" 

刚才已经提及,上面这个检测的方法只是接近完美,这说明它也有失误的情况,比如在 IE 中会将 DOM 元素的方法报告成 object

Javascript赋予了函数非常多的特性,其中最重要的特性之一就是将函数作为第一型的对象。那就意味着在javascript中函数可以有属性,可以有方法, 可以享有所有对象所拥有的特性。并且最重要的,她还可以直接被调用

浅谈自记忆函数

最近阅读《JavaScript忍者秘籍》看到了一种有趣的函数:自记忆函数。

自记忆函数

所谓自记忆函数,就是说函数自己能够记住先前计算的结果,这样就能避免相同的计算执行两次,可以显著的提高性能。比如说下面这个检测是否为素数的函数。

    function isPrime(value) {
        if (!isPrime.results) {
            isPrime.results = {};
        }
        if (isPrime.results[value] !== undefined) {
            return isPrime.results[value];
        }
        var prime = value !== 1;
        for(var i = 2; i < value; i  ) {
            if (value % i === 0) {
                prime = false;
                break;
            }
        }
        return isPrime.results[value] = prime;
    }

缓存记忆有两个优点:

  1. 在函数调用获取之前计算结果的时候,最终用户享有性能优势。
  2. 发生在幕后,完全无缝,最终用户和页面开发人员都无需特殊操作或者为此做任何额外的初始化工作。

将缓存记忆用在 DOM 的获取操作上,可以获得 5 倍的性能提升,如下所示。

function getElements(name) {
    if (!getElements.cache) {
        getElements.cache = {};
    }

    return getElements.cache[name] = 
        getElements.cache[name] ||
        document.getElementsByTagName(name); 
}

上面我们求素数的例子中,其实是在函数中对结果进行了缓存,不过值得注意的一点是,这种实现只有在我们能获取到函数体的时候才可以使用。下面我们就对上面的函数进行改写。

Function.prototype.memoized = function(key) {
    this._values = this._values || {};

    return this._values[key] !== undefined ?
        this._values[key] :
        this._values[key] = this.call(this, key);
};

function isPrime(num) {
    var prime = num != 1;

    for(var i = 2; i < num; i  ) {
        if (num % i === 0) {
            prime = false;
            break;
        }
    }

    return prime;
}

console.log(isPrime.memoized(5));
console.log(isPrime._values[5]);

这种写法可以解决刚才我们提出的无法获取函数体的问题,不过又出现了一个问题,因为上面的函数要求调用者在使用 isPrime() 的时候必须要跟上 .memoized(),不过调用者不可能时刻都能记得这一点,所以对于这个函数我们还可以改写,如下所示:

Function.prototype.memoized = function(key) {
    this._values = this._values || {};

    return this._values[key] !== undefined ?
        this._values[key] :
        this._values[key] = this.call(this, key);
};

Function.prototype.memoize = function(key) {
    var fn = this;
    return function() {
        return fn.memoized.call(fn, key);
    }
};

var isPrime = (function(num) {
    var prime = num !== 1;

    for(var i = 2; i < num; i  ) {
        if (num % i === 0) {
            prime = false;
            break;
        }
    }

    return prime;
}).memoize();

console.log(isPrime(5));

不过,上面的功能都被添加在 Function 上,由所有函数实例共享,如果感觉这么做有所不妥,可以使用下面这种方式。

function memoize(fn) {
    var cache = {};

    return function(key) {
        console.log("before: "   cache[key]);
        return cache[key] !== undefined ?
            cache[key] :
            cache[key] = fn.call(this, key);
    }
}

只需要对要缓存的函数进行包装即可。

通过元素的标签查询DOM的操作的的代价是昂贵的,各位前端大佬肯定都很清楚。我们下面使用缓存记忆的方式来进行这个操作

总结

自记忆函数有两个优点:

  • 由于函数调用时会寻找之前调用所得到的值,所以用户最终会乐于看到所获得的性能收益。
  • 它不需要执行任何特殊请求,也不需要做任何额外初始化,就能顺利进行工作。

但是,自记忆函数并不是完美的,它一样有着缺陷:

  • 任何类型的缓存都必然会为性能牺牲内存。
  • 很多人认为缓存逻辑不应该和业务逻辑混合,函数或方法只需要把一件事情做好。
  • 对自记忆函数很难做负载测试或估算算法复杂度,因为结果依赖于函数之前的输入。

优点

本文中的内容来自于 《JavaScript 忍者秘籍》。

为函数添加属性的这个特性我觉的大家在平时的开发中基本没什么尝试或者是使用过,但是在一些JS库或者是事件回掉管理中都能发挥出很大的用处。下面一起来看几个例子。

伪造数组

出于某种目的(我也不知道),我们可以将对象伪造成一个数组,具体操作如下:

var eles = {
    length: 0,
    add: function(ele) {
        Array.prototype.push.call(this, ele);
    }
};

eles 对象添加了一个 length 属性,当调用 push 方法时,length 属性会自动增加。

前言

这个函数和上面的缓存使用的同一个手法,而且这简单的4句代码能为我们的性能带来大幅度的提升。这也算是一种超能力吧。函数的很多特性都和其上下文有关,接下来我们研究一个和上下文又换的例子。

了解了优缺点我们来看一个简单的计算素数的例子

!!构造是一种可以将任意Javascript表达式转化为其等效布尔值的简单方式。

缓存了之前的结果,最终用户享有性能优势 实际上是发生在幕后,操作无感

在某有一些的情况下我们可以要存储一组相关但是相互又独立的函数。这个需求看起来很easy,实现起来也不复杂。最显而易见的做法是使用一个数组来保存所有的函数,这样不是不可以,但是显然这种做法不是最好的。下面通过为函数属性我们呢来实现这个我们的目的

// 2: 缓存记忆函数function isPrime  {if  isPrime.anwers = {}// 先从缓存里面取if (isPrime.anwers[value] != null ) {return isPrime.anwers[value]}// 开始进行判断和计算let prime = value != 1for (let index = 2; index < value; index  ) {if  {prime = falsebreak;} }// 保存计算出来的值return isPrime.anwers[value] = prime}console.logconsole.log(`从函数记忆中直接读取${isPrime.anwers[5]}`)

我们简单的试验一下就可以发现

总结

内存的牺牲这是肯定的 打破了存粹性 如果方法中有算法,那么很难测量这个算法的性能

在我还对JS懵懵懂懂的时候看到这样的操作被秀了一脸,简直是刺激了我幼小的心灵。

// 4:伪造数组方法// // let elems = {length: 0, //为了保存个数add  {Array.prototype.push.call},gather  {this.add(document.getElementById}}elems.gatherelems.gatherconsole.log; // console.log; // console.log; // 2console.log;/**0: input#add1: input#removeadd: ƒ addgather: ƒ gatherlength: 2*/

我们在add函数中实现了把元素添加到了集合中,而且Array又正好提供push方法, 不用白不用。这种操作也是直白的展示了函数上下文的超强特性。

在举例之前我们先来看看这种方式的优缺点

伪造数组方法

缓存记忆函数

在一些情况下我们想创建一个包含一组数据的对象,但是这个数据包含很多的状态,比如和集合项有关的元数据那么我们用数组来存就不太合适了。那么这里我们就用对象的方式来假扮数组。通过改变上下文来完成一些“不法的行为”

这种函数可以记住之前已经计算过的结果,避免了不必要的计算,这显然是能够提升代码性能的。

缺点

// 3:缓存记忆DOM元素function getElements  {if  getElements.cache = {}return getElements.cache[name] = getElements.cache[name] || document.getElementsByTagName;}console.log // HTMLCollectionconsole.log(getElements.cache['div']) // HTMLCollection

上面的这一段代码逻辑清晰,store对象用来管理我们的缓存,cache属性用来存储函数,nextId属性用来保存当前的缓存Id,add()方法用来设置存储,先来判断当前函数是否已经在缓存中然后再去设置缓存,这样就能限制函数的重复添加,最后返回true。

这里呢 好处是特别明显的我们再次的取用isPrime.anwers[5]的时候不需要经过任何的计算,但是大型的计算要主要内存的使用

// 简单实验 函数作为对象的存在let fn = function () {}fn.prop = 'fnProp'console.log // fnProp

缓存记忆DOM元素

Javascript强大的灵活性, 也带来更多的可能性。 路漫漫其修远兮,吾将上下而求索。

// 1:函数缓存示例let store = {nextId: 1, // idcache: {}, // 缓存add  {// 如果函数中没有id属性那么就缓存if  {console.log(`begin add func ${fn.name}`)fn.id = store.nextId   // 设置完缓存之后返回truereturn !!(store.cache[fn.id] = fn)} else {console.log(`${fn.name} is already in cache`)}}}function storeCache() {}store.add // begin add func storeCachestore.add // storeCache is already in cache

函数缓存

本文由彩世界开奖app官网发布于前端技术,转载请注明出处:浅谈自回想函数彩世界平台官方网址

关键词: 脚本 JavaScript 函数 对象 之家