Javascript 高階函數

高階函數是指至少知足如下條件之一的函數:javascript

  • 函數能夠做爲參數被傳遞
  • 函數能夠做爲返回值輸出

函數做爲參數傳遞

把參數看成參數傳遞, 抽離出一部分容易變化的業務邏輯,將它放在函數參數中,這樣能夠分離業務代碼中變化與不變的部分。其中一個重要的應用場景就是回調函數。java

1. 回調函數

var appendDiv = function() {
	for (var i = 0; i < 100; i++) {
		var div = document.createElement('div')
			div.innerHTML = i
			document.body.appendChild(div)
			div.style.display = 'none'
	}
}
appendDiv()
複製代碼

div.style.display = 'none'這種硬編碼放在appendDiv裏顯然是不合理的,appendDiv未免有點個性化了,成爲一個難複用的函數,因而咱們將div.style.display = 'none這行代碼抽離出來,用回調函數的形式調用node

var appendDiv = function(callback) {
	for (var i = 0; i < 100; i++) {
		var div = document.createElement('div')
			div.innerHTML = i
			document.body.appendChild(div)
			if (typeof callback === 'function') {
				callback(div)
			}
	}
}
appendDiv(function(node){
	node.style.display = 'none'
})
複製代碼

2. Array.prototype.sort

Array.prototype.sort接受一個函數看成參數,這個函數裏封裝裏數組元素的排序規則。 其中數組是不變,而排序規則是可變的,將可變的部分封裝在函數裏。編程

[1,4,5].sort(function(a, b){
    return a - b    
})

// 輸出 [1,3,4]
複製代碼

函數做爲返回值輸出

相比把函數做爲參數傳遞,函數看成返回值輸出的應用場景也許更多,也更能體現出函數式編程的巧妙。讓函數返回一個可執行的函數,意味着運算過程是可延續。數組

判斷數據的類型

var type = function(data) {
	if(arguments.length === 0) return;
	var typeStr = Object.prototype.toString.call(data)
	return typeStr.match(/\[object (.*?)\]/)[1].toLowerCase()
}
console.log(type('Array'))

//輸出 string
複製代碼

高階函數實現AOP

AOP(面向切面編程) 的主要做用是把一些核心業務邏輯模塊無關的功能抽離出來,這些無關的模塊包括日誌統計,安全控制,異常處理。把這些功能抽離以後,再經過動態織入的方式摻入業務邏輯中。這樣作的好處是保持業務邏輯模塊的純淨和高內聚性,其次是能夠很方便的複用次用模塊。在JavaScript中AOP的實現很是簡單,這是與生俱來的能力。瀏覽器

Function.prototype.before = function(beforefn) {
	var _self = this // 保存原函數的引用
	return function() { // 返回包含了原函數和新函數的"代理"函數				
		beforefn.apply(this, arguments) // 執行新函數,修正this
		return _self.apply(this, arguments) // 執行原函數
	}
}

Function.prototype.after = function(afterfn) {
	var _self = this
	return function() {
		var ret = _self.apply(this, arguments) //修正this值,而且執行原函數
		afterfn.apply(this, arguments) //執行新函數
		return ret
	}
}

var fnc = function(){
	console.log(2)
}
fnc = fnc.before(function(){
	console.log(1)
}).after(function(){
	console.log(3)
})

fnc()

// 輸出 1  2  3
複製代碼

高階函數的其餘應用

1. 函數柯里化

函數柯里化(function 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 arguments.callee
		}
	}
}


// 被currying的函數
var cost = (function(){
	var money = 0
	return function() {
		for (var i = 0, l= arguments.length; i < l; i++) {
			money += arguments[i]
		}
		return money
	}
})()

var cost = currying(cost)

cost(100) //未真正求值
cost(200) //未真正求值
cost(300) //未真正求值

console.log(cost()) // 求值並輸出:600
複製代碼

2. uncurrying

>`uncurrying`的目的是將泛化this的過程提取出來,將`fn.call`或者`fn.apply`抽象成通用的函數。


在javascript中,當咱們調用對象的某個方法時,其實不用關心該對象本來是否擁有這個方法,這也是動態類型語言的特色。能夠用`call`和`apply`去借用一個本來不屬於它的方法
複製代碼
var obj1 = {
	name: 'sven'
}

var obj2 = {
	getName: function() {
		return this.name
	}
}

console.log(obj2.getName.call(obj1)) // 輸出: sven
複製代碼

經過uncurrying的方式,咱們能夠把Array.prototype上的方法"複製"到array對象上,一樣這些方法可操做的對象也不只僅只是array對象bash

// uncurrying實現
Function.prototype.uncurrying = function() {
    var self = this;
    return function() {
        return Function.prototype.call.apply(self, arguments);
    }
};

// 將Array.prototype.push進行uncurrying,此時push函數的做用就跟Array.prototype.push同樣了,且不只僅侷限於只能操做array對象。
var push = Array.prototype.push.uncurrying();

var obj = {
    "length": 1,
    "0": 1
};

push(obj, 2);
console.log(obj);   // 輸出:{0: 1, 1: 2, length: 2}
複製代碼

3. 函數節流

當一個函數被頻繁調用時,若是會形成很大的性能問題的時候,這個時候能夠考慮函數節流,下降函數被調用的頻率。閉包

throttle函數的原理是,將即將被執行的函數用setTimeout延遲一段時間執行。若是該次延遲執行尚未完成,則忽略接下來調用該函數的請求。throttle函數接受2個參數,第一個參數爲須要被延遲執行的函數,第二個參數爲延遲執行的時間。app

var throttle = function(fn, interval) {
	var _self = fn, // 保存須要被延時執行的函數引用
		timer,		// 定時器
		firstTime = true // 是不是第一次調用
	
	return function() {
		var args = arguments,
			_me = this
		
		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(1)
}, 500)
複製代碼

4. 分時函數

當一次的用戶操做會嚴重地影響頁面性能,如在短期內往頁面中大量添加DOM節點顯然也會讓瀏覽器吃不消,咱們看到的結果每每就是瀏覽器的卡頓甚至假死。

這個問題的解決方案之一是下面的timeChunk函數,timeChunk函數讓建立節點的工做分批進行,好比把1秒鐘建立1000個節點,改成每隔200毫秒建立8個節點。

var timeChunk = function(ary, fn, count) {
    var t;
    
    var start = function() {
        for ( var i = 0; i < Math.min( count || 1, ary.length ); i++ ){
            var obj = ary.shift();
            fn( obj );
        }
     };
    
     return function() {
        t = setInterval(function() {
          if (ary.length === 0) {  // 若是所有節點都已經被建立好
              return clearInterval(t);
          }
          start();
        }, 200);    // 分批執行的時間間隔,也能夠用參數的形式傳入
    };
};
複製代碼

5. 惰性加載

在Web開發中,由於瀏覽器之間的實現差別,一些嗅探工做老是不可避免。好比咱們須要一個在各個瀏覽器中可以通用的事件綁定函數addEvent,常見的寫法以下:
複製代碼

方案一:

var addEvent = function(elem, type, handler) {
    if (window.addEventListener) {
       return elem.addEventListener(type, handler, false)
    }
    
    if (window.attachEvent) {
          return elem.attachEvent('on' + type, handler)
    }
}
複製代碼

缺點:當它每次被調用的時候都會執行裏面的if條件分支,雖然執行這些if分支的開銷不算大,但也許有一些方法可讓程序避免這些重複的執行過程。

方案二:

var addEvent = (function() {
    if (window.addEventListener) {
        return function(elem, type, handler) {
            elem.addEventListener(type, handler, false)
        }
    }
    if (window.attachEvent) {
        return function(elem, type, handler) {
            elem.attachEvent('on' + type, handler)
        }
    }
})()
複製代碼

缺點:也許咱們從頭至尾都沒有使用過addEvent函數,這樣看來,一開始的瀏覽器嗅探就是徹底多餘的操做,並且這也會稍稍延長頁面ready的時間。

方案三:

var addEvent = function(elem, type, handler) {
    if (window.addEventListener) {
       addEvent = function(elem, type, handler) {
           elem.addEventListener(type, handler, false)
       }
    } else if (window.attachEvent) {
        addEvent = function(elem, type, handler) {
            elem.attachEvent('on' + type, handler)
        }
    }
    addEvent(elem, type, handler)
}
複製代碼

此時addEvent依然被聲明爲一個普通函數,在函數裏依然有一些分支判斷。可是在第一次進入條件分支以後,在函數內部會重寫這個函數,重寫以後的函數就是咱們指望的addEvent函數,在下一次進入addEvent函數的時候,addEvent函數裏再也不存在條件分支語句。

相關文章
相關標籤/搜索