一、惰性函數(shù)
惰性函數(shù):顧名思義就是懶惰的函數(shù),舉一個(gè)簡(jiǎn)單的例子,如果我們需要判斷當(dāng)前瀏覽器是哪一種瀏覽器,我們需要封裝一個(gè)函數(shù),根據(jù)判斷來進(jìn)行返回瀏覽器類型,但是如果你在同一個(gè)瀏覽器中調(diào)用2次這個(gè)函數(shù),那么這個(gè)函數(shù)中就需要做2次判斷。其實(shí)這是一種無用的性能消耗,因此我們的惰性函數(shù)就登場(chǎng)了
原理:根據(jù)第一次調(diào)用的判斷重寫當(dāng)前函數(shù)
function getStyle(obj,attr){
if(obj.currentStyle) {
return obj.currentStyle[attr]
} else {
return getComputedStyle(obj,false)[attr];
}
}
上述方法我們知道用來獲取元素的屬性值,但是問題是我們每次調(diào)用該函數(shù)都要進(jìn)行一次判斷,如果在同一個(gè)瀏覽器中連續(xù)調(diào)用2次,每次都進(jìn)行一次判斷明顯這是無用功,因此我們可以根據(jù)惰性函數(shù)原理進(jìn)行優(yōu)化
function getStyle(obj,attr){
if(obj.currentStyle) {
getStyle = function(obj,attr) {
return obj.currentStyle[attr]
}
} else {
getStyle = function(obj,attr) {
return getComputedStyle(obj,false)[attr];
}
}
return getStyle(obj,attr);
}
例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
#box{width: 200px;}
#box1{width:300px;}
</style>
</head>
<body>
<div id="box"></div>
<div id="box1"></div>
</body>
</html>
<script>
var div = document.getElementsByTagName("div")[0];
function getStyle(obj,attr){
if(obj.currentStyle){
getStyle = function(obj,attr){
return obj.currentStyle[attr]
}
}else{
getStyle = function(obj,attr){
return getComputedStyle(obj,false)[attr]
}
}
return getStyle(obj,attr)
}
console.log(getStyle)
/*
function getStyle(obj,attr){
if(obj.currentStyle){
getStyle = function(obj,attr){
return obj.currentStyle[attr]
}
}else{
getStyle = function(obj,attr){
return getComputedStyle(obj,false)[attr]
}
}
return getStyle(obj,attr)
}
*/
console.log(getStyle(box,"width"))
console.log(getStyle)
/*
chrome瀏覽器
(obj,attr){
return getComputedStyle(obj,false)[attr]
}
*/
</script>
二、記憶函數(shù)
如果熟悉Vue的同學(xué)應(yīng)該知道vue中有一個(gè)屬性叫做
computed
它可以緩存計(jì)算后的結(jié)果。記憶函數(shù)的功能也類似于computed
。它可以將上次的結(jié)果緩存起來,當(dāng)下次調(diào)用的時(shí)候,如果傳遞的參數(shù)相同,則直接返回緩存中的數(shù)據(jù)
舉個(gè)例子:
function add(a, b) {
return a + b;
}
// 假設(shè) memoize 可以實(shí)現(xiàn)函數(shù)記憶
var memoizedAdd = memoize(add);
memoizedAdd(1, 2) // 3
memoizedAdd(1, 2) // 相同的參數(shù),第二次調(diào)用時(shí),從緩存中取出數(shù)據(jù),而非重新計(jì)算一次
原理
我們只需要把參數(shù)和對(duì)應(yīng)的結(jié)果存到一個(gè)對(duì)象中,調(diào)用時(shí),判斷參數(shù)對(duì)應(yīng)的數(shù)據(jù)是否存在,如果存在則直接返回對(duì)應(yīng)的結(jié)果
第一版
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)
}
}
}
// 調(diào)用
var add = function(a, b, c) {
return a + b + c
}
var memoizedAdd = memoize(add)
memoizedAdd(1, 2, 3) // 5
因?yàn)榈谝话媸褂昧?code>join方法,我們很容易想到當(dāng)參數(shù)是對(duì)象的時(shí)候,就會(huì)自動(dòng)調(diào)用
toString
方法轉(zhuǎn)換成[Object object]
,再拼接字符串作為 key 值。這樣就很容易造成key值相同
var propValue = function(obj){
return obj.value
}
var memoizedAdd = memoize(propValue)
console.log(memoizedAdd({value: 1})) // 1
console.log(memoizedAdd({value: 2})) // 1
兩者都返回了 1,顯然是有問題的,所以我們看看 underscore 的 memoize 函數(shù)是如何實(shí)現(xiàn)的:
// 第二版 (來自 underscore 的實(shí)現(xiàn))
var memoize = 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
默認(rèn)使用 function 的第一個(gè)參數(shù)作為 key,所以如果直接使用
var add = function(a, b, c) {
return a + b + c
}
var memoizedAdd = memoize(add)
memoizedAdd(1, 2, 3) // 6
memoizedAdd(1, 2, 4) // 6
肯定是有問題的,如果要支持多參數(shù),我們就需要傳入 hasher 函數(shù),自定義存儲(chǔ)的 key 值。所以我們考慮使用 JSON.stringify:
var memoizedAdd = memoize(add, function(){
var args = Array.prototype.slice.call(arguments)
return JSON.stringify(args)
})
console.log(memoizedAdd(1, 2, 3)) // 6
console.log(memoizedAdd(1, 2, 4)) // 7
總結(jié):
使用場(chǎng)景:當(dāng)有需要大量重復(fù)計(jì)算或者計(jì)算依賴之前的結(jié)果的函數(shù),我們都可以使用記憶函數(shù)。