高阶函数是指至少知足如下条件之一的函数:javascript
把参数看成参数传递, 抽离出一部分容易变化的业务逻辑,将它放在函数参数中,这样能够分离业务代码中变化与不变的部分。其中一个重要的应用场景就是回调函数。
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
这行代码抽离出来,用回调函数的形式调用java
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' })
Array.prototype.sort接受一个函数看成参数,这个函数里封装里数组元素的排序规则。
其中数组是不变,而排序规则是可变的,将可变的部分封装在函数里。node
[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(面向切面编程) 的主要做用是把一些核心业务逻辑模块无关的功能抽离出来,这些无关的模块包括日志统计,安全控制,异常处理。把这些功能抽离以后,再经过
动态织入
的方式掺入业务逻辑中。这样作的好处是保持业务逻辑模块的纯净和高内聚性,其次是能够很方便的复用次用模块。在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
函数柯里化(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
>`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对象数组
// 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}
当一个函数被频繁调用时,若是会形成很大的性能问题的时候,这个时候能够考虑函数节流,下降函数被调用的频率。
throttle函数的原理是,将即将被执行的函数用setTimeout延迟一段时间执行。若是该次延迟执行尚未完成,则忽略接下来调用该函数的请求。throttle函数接受2个参数,第一个参数为须要被延迟执行的函数,第二个参数为延迟执行的时间。浏览器
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)
当一次的用户操做会严重地影响页面性能,如在短期内往页面中大量添加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); // 分批执行的时间间隔,也能够用参数的形式传入 }; };
在Web开发中,由于浏览器之间的实现差别,一些嗅探工做老是不可避免。好比咱们须要一个在各个浏览器中可以通用的事件绑定函数addEvent,常见的写法以下:
方案一:app
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函数里再也不存在条件分支语句。