在js中閉包有兩個緊密度很是高的概念與之關聯:一.變量的做用域,二.變量的生存週期。那麼什麼是閉包,咱們先看一個閉包的函數: `java
function closure () {
var num = 0;
return function () {
if (arguments.length) {
for (var i = 0, len = arguments.length; i < len; i ++) {
num += arguments[i]
}
return num;
}
}
}
var runClosure = closure();
console.log(runClosure(1,3,5,7,9)); // 輸出: 25
console.log(runClosure(11,13,15,17,19)); // 輸出: 100
複製代碼
`ajax
這是一個很典型且簡單閉包函數,closure
函數裏的匿名函數被返回出來,也就是runClosure
,runClosure
在外部進行調用,而runClosure
所運行的環境是closure
這個函數體裏的做用域裏,而closure
這個函數體裏的做用域是個封閉的空間,而這樣的調用關係就是閉包。簡言之:就是一個函數裏,包含了另一個函數,且被外部調用執行,這就造成了閉包。設計模式
使用閉包是個很天然的過程,並無什麼特別的,重點是閉包的知識點,文章開頭有說到,閉包有兩個關聯度很是高的概念:變量的做用域與變量的生存週期,先說明,這句話並非我說的,這句話出自 《javaScript設計模式》一書中,我很是承認這句話,因此照搬了過來。數組
那麼爲何說變量的做用域與變量的生存週期呢,從上面的代碼咱們能夠看出,runClosure
是運行在closure
這個函數的的局部做用域裏,num
這個變量也一樣生存在這個做用域裏面,從我麼執行兩次runClosure
就能夠看出,num
變量是一直存在的,即使咱們在執行一次,它也是在現有結果下進行累加的。咱們都知道,在js中存在着全局做用域與局部做用域,全局做用域的變量生存週期是永久的,除非你的頁面關閉,而局部做用域裏的變量,通常函數體裏則是局部做用域,局部做用域裏的變量通常跟隨調用的函數執行的結束而結束,再次調用就又將是個新的。而閉包裏的變量,由於被外包訪問到,因此閉包環境裏的變量就不能被銷燬,便就繼續存活着,而這些就是閉包裏的知識點,掌握這些知識點,咱們就能夠利用閉包的特性完成許多奇妙的工做了,好比下面要說的高階函數。而閉包常見的應用有哪些呢?咱們從上面的這個函數裏,也能夠得出兩條結論:瀏覽器
什麼是高階函數呢,在《javaScript設計模式》一書中一樣有說明:閉包
知足這些條件之一的均可以稱之爲高階函數了,做爲參數傳遞的應用場景就是咱們常見的回調函數了:app
`函數
var arr = [13, 25, 10, 17, 8]
arr.sort(function(a, b){
return a - b;
})
複製代碼
`性能
像上面數組裏sort
方法裏的這個比較函數就屬於高階函數。而函數做爲返回值輸出,咱們閉包的那個例子裏的runClosure
就是個高階函數了。那麼爲何會有高階函數這個概念呢:通俗的講,高階函數的應用都是爲了解決咱們實際開發當中遇到的問題的,高階函數便所以而誕生的。優化
那麼有哪些常見的高階函數,它們又解決了咱們的什麼問題呢?下面咱們看幾個例子:
currying
。柯里化函數又稱部分求值,一個 currying 的函數首先會接受一些參數,接受了這些參數以後, 該函數並不會當即求值,而是繼續返回另一個函數,剛纔傳入的參數在函數造成的閉包中被保 存起來。待到函數被真正須要求值的時候,以前傳入的全部參數都會被一次性用於求值。`
咱們先構建一個需求,假設咱們須要對一些數據進行計算總和,咱們的目的呢,是獲得最後總和得結果,若是咱們每一次咱們每次把數據輸入進去就進行計算,那麼明顯是浪費計算機資源得,而咱們若是把全部得數據先進行存儲,在發現後面沒有數據了,就把數據計算出總和返回出來,經過這樣得操做,咱們就優化了性能。下面看代碼:
var currying = function (fn) {
var args = [];
return function () {
if (arguments.length) {
[].push.apply(args, arguments);
return arguments.callee; // 意思是返回當前這個匿名函數
} else { // 若是這個匿名函數參數裏沒有數字,則進行計算,並返回結果
return fn.apply(this, args)
}
}
}
var total = (function () {
var num = 0;
return function () {
for (var i = 0, l = arguments.length; i < l; i++) {
num+=arguments[i];
}
return num;
}
})()
var cont = currying(total)
cont(1500); cont(3000, 6000); cont(12000); // 這些都未真正計算,只是存儲
console.log(cont()); // 真正計算,並返回結果 輸出:22500
複製代碼
`
throttle
與函數防抖動debounce
。函數節流與函數防抖動都有一個共同特色,就是不但願頻繁的觸發函數運行,好比咱們用的onresize
事件,onmousemove
事件,這些事件都會不經意的被頻繁觸發,由於頻繁的觸發函數就要運行函數體,運行函數體就要佔用計算資源,還有一些ajax
請求也是,若是用戶頻繁的觸發ajax
,就會形成沒必要要的ajax
通訊,進而佔用資源,浪費性能。所以咱們就須要封裝這樣的方法,對於這些方法就是防抖動函數與節流函數了。那麼節流與防抖動函數的差異,你們自行百度一下,這裏暫不作贅述,咱們先看代碼實現:`
// 函數節流
// fn是咱們須要包裝的事件回調, interval是時間間隔的閾值
function throttle(fn, interval) {
// last爲上一次觸發回調的時間
let last = 0
// 將throttle處理結果看成函數返回
return function () {
// 保留調用時的this上下文
let context = this
// 保留調用時傳入的參數
let args = arguments
// 記錄本次觸發回調的時間
let now = +new Date()
// 判斷上次觸發的時間和本次觸發的時間差是否小於時間間隔的閾值
if (now - last >= interval) {
// 若是時間間隔大於咱們設定的時間間隔閾值,則執行回調
last = now;
fn.apply(context, args);
}
}
}
// 用throttle來包裝scroll的回調
const better_scroll = throttle(() => console.log('throttle函數節流'), 1000)
document.addEventListener('scroll', better_scroll);
// 函數防抖
// fn是咱們須要包裝的事件回調, delay是每次推遲執行的等待時間
function debounce(fn, delay) {
// 定時器
let timer = null
// 將debounce處理結果看成函數返回
return function () {
// 保留調用時的this上下文
let context = this
// 保留調用時傳入的參數
let args = arguments
// 每次事件被觸發時,都去清除以前的舊定時器
if(timer) {
clearTimeout(timer)
}
// 設立新定時器
timer = setTimeout(function () {
fn.apply(context, args)
}, delay)
}
}
// 用debounce來包裝scroll的回調
const better_scroll = debounce(() => console.log('debounce函數防抖'), 1000)
document.addEventListener('scroll', better_scroll)
複製代碼
`
`
<button id="crearte-btn">開始建立</button>
// 分時函數
var friend = []
for (let n = 1; n < 1000; n++) {
friend.push('好友:'+ n + '^_^');
}
var timeChunk = function (data, fn, count) {
var obj, time;
var len = data.length;
var start = function () {
for (let i = 0; i < Math.min(count || 1, data.length); i++) { // 小於10或者1
var obj = data.shift(); // 刪除自身一個元素
fn(obj);
}
}
return function () {
time = setInterval(function () { // 每一個250毫秒執行一次start方法
if (!data.length) {
clearInterval(time)
}
start();
}, 250)
}
}
var renderElement = timeChunk(friend, function (n) {
var div = document.createElement('div');
div.innerHTML = n;
document.getElementById('friend-div').appendChild(div);
}, 10);
document.getElementById('crearte-btn').onclick = function () {
renderElement();
}
複製代碼
`
在這個章節裏,介紹了閉包與高階函數,並展現了三種常見的高階函數的應用,而高階函數的應用,歸根揭底,是用來處理咱們平常開發中的一些問題,更多的目的是爲了優化性能的操做。今天分享就到這,喜歡的朋友點個贊,謝謝。