js閉包與高階函數

閉包

在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就是個高階函數了。那麼爲何會有高階函數這個概念呢:通俗的講,高階函數的應用都是爲了解決咱們實際開發當中遇到的問題的,高階函數便所以而誕生的。優化

那麼有哪些常見的高階函數,它們又解決了咱們的什麼問題呢?下面咱們看幾個例子:

  1. 柯里化函數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
複製代碼

`

  1. 函數節流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)
複製代碼

`

  1. 分時函數,分時函數的目的其實很簡單,當咱們有大量的數據須要進行處理的時候,好比咱們要給頁面建立1000個div標籤,若是一次性添加,就會讓瀏覽器承受不住,會讓瀏覽器顯得卡頓。那麼咱們該怎麼辦呢,分時函數就是用來應對這些場景的,分時函數的應用有點相似咱們的懶加載,懶加載是不知足條件是不觸發,分時函數是,不管怎樣都要處理完,只是分批次進行的,下面咱們以頁面添加1000個div爲案例演示:

`

<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();
	}
複製代碼

`

總結

在這個章節裏,介紹了閉包與高階函數,並展現了三種常見的高階函數的應用,而高階函數的應用,歸根揭底,是用來處理咱們平常開發中的一些問題,更多的目的是爲了優化性能的操做。今天分享就到這,喜歡的朋友點個贊,謝謝。

相關文章
相關標籤/搜索