JavaScript 設計模式與開發實踐讀書筆記
JavaScript 設計模式與開發實踐讀書筆記
最近利用碎片時間在 Kindle 上面閱讀《JavaScript 設計模式與開發實踐讀書》這本書,剛開始閱讀前兩章內容,和大家分享下我覺得可以在項目中用的上的一些筆記。
我的 github 項目會不定時更新,有需要的同學可以移步到我的 github 中去查看源碼: https://github.com/lichenbuliren/design-mode-notes
1、currying 函數柯里化
currying 又稱 部分求值 。一個 currying 的函數首先會接受一些參數,接受了這些參數之后,該函數并不會立即求值,而是繼續返回另外一個函數,將剛才傳入的參數在函數形成的閉包中被保存起來。待到函數被真正需要求值的時候,之前傳入的所有參數都會被一次性的用于求值。
假設我們需要編寫一個計算每個月開銷的函數,在每天結束之前,我們要記錄每天花掉了多少錢。
通用 currying 函數:
var currying = function(fn) {
var args = [];
return function() {
if (arguments.length === 0) {
return fn.apply(this, args);
} else {
[].push.apply(args, arguments);
// 返回函數本身,這里指向 return 后面的匿名函數!
return arguments.callee;
}
}
};
var cost = (function() {
// 閉包存儲最后的值
var money = 0;
return function() {
for (var i = 0, len = arguments.length; i < len; i++) {
money += arguments[i];
}
return money;
}
})();
// 轉化成 currying 函數
// 這個時候,閉包內部的 fn 指向真正的求值函數
// 也就是 cost 自運行的時候返回的匿名函數
var cost = currying(cost);
cost(200);
cost(300);
cost(500);
// 求值輸出
console.log(cost());</code></pre>
2、uncurrying 函數
Function.prototype.uncurrying = function() {
// 此時 selft 是后面例子中的 Array.prototype.push;
var self = this;
return function() {
// arguments: { '0': { '0': 1, length: 1 }, '1': 2 }
var obj = Array.prototype.shift.call(arguments);
return self.apply(obj, arguments);
}
};
// 另外一種實現方式
Function.prototype.uncurrying = function() {
var self = this;
return function() {
return Function.prototype.call.apply(self, arguments);
}
};
var push = Array.prototype.push.uncurrying();
var obj = {
"length": 1,
"0": 1
};
push(obj, 2);
console.log(obj);</code></pre>
3、函數節流
JavaScript 中的函數大多數情況下都是由用戶主動調用觸發的,除非是函數本身的實現不合理,否則我們一般不會遇到跟性能相關的問題。但是在一些少數情況下,函數的觸發不是有由用戶直接控制的。在這些場景下,函數有可能被非常頻繁的調用,而造成大的性能問題。
函數被頻繁調用的場景:
-
window.onresize 事件
-
mousemove 事件
-
上傳進度
函數節流原理
上面三個提到的場景,可以發現它們面臨的共同問題是函數被觸發的頻率太高。
比如我們在 window.onresize 事件中要打印當前瀏覽器窗口大小,在我們拖拽改變窗口大小的時候,控制臺1秒鐘進行了 10 次。而我們實際上只需要 2 次或者 3 次。這就需要我們按時間段來忽略掉一些事件請求,比如確保在 500ms 內打印一次。很顯然,我們可以借助 setTimeout 來完成。
函數節流實現
/**
- 函數節流實現
- @param {Function} fn 需要節流執行的函數
- @param {[type]} interval 事件執行間隔時間,單位 ms
@return {[type]} [description]
*/
var throttle = function(fn, interval) {
var _self = fn,
timer,
firstTime = true;
console.log(_self);
return function() {
var args = arguments,
_me = this; // 這里代表當前的匿名函數
console.log(_me);
if (firstTime) {
_self.apply(_me, args);
return firstTime = false;
}
if (timer) {
return false;
}
timer = setTimeout(function() {
clearTimeout(timer);
timer = null;
_self.apply(_me, args);
}, interval || 500);
};
};
window.onresize = throttle(function() {
console.log('test');
}, 500);</code></pre>
4、分時函數
我們經常會遇到這么一種情況,某些函數確實是用戶主動調用的,但是因為一些客觀原因,這些函數會嚴重地影響頁面性能。
一個例子就是創建 WebQQ 的 QQ 好友列表。列表中通常會有成百上千個好友,如果一個好友用一個節點來表示,當我們在頁面中渲染這個列表的時候,可能要一次性往頁面中創建成百上千個節點。
在短時間內往頁面中大量添加 DOM 節點顯然也會讓瀏覽器吃不消,我們看到的結果往往就是瀏覽器的卡頓甚至假死。所以我們需要一個分時函數來解決這個問題
/**
- 分時函數例子
- 以創建 WebQQ 列表為例
- @param {[type]} data 函數執行需要用到的數據
- @param {Function} fn 真正需要分時執行的函數
- @param {[type]} count 每次創建一批節點的數量
- @param {[type]} interval 函數執行間隔
@return {[type]} [description]
*/
var timeChunk = function(data, fn, count, interval) {
var t;
var len = data.length;
var start = function() {
for (var i = 0; i < Math.min(count || 1, data.length); i++) {
var obj = data.shift();
fn(obj);
}
}
return function() {
t = setInterval(function() {
if (data.length === 0) {
return clearInterval(t);
}
start();
}, interval);
}
}</code></pre>
5、惰性加載函數
以創建事件綁定函數為例:
在進入第一個條件分支之后,在函數內部重寫這個函數,重寫之后,就是我們所需要的函數,在下一次進入的時候,就不再需要判斷了。
/**
- 事件綁定
- @param {[type]} el [description]
- @param {[type]} type [description]
@param {[type]} handler [description]
*/
var addEvent = function(el, type, handler) {
if (window.addEventListener) {
addEvent = function(el, type, handler) {
el.addEventListener(type, handler, false);
}
} else if (window.attachEvent) {
addEvent = function(el, type, handler) {
el.attachEvent('on' + type, handler);
}
}
addEvent(el, type, handler);
}</code></pre>
Q&A
暫時這么多,以后會不定期更新一些關于我讀這本書的筆記內容!