高阶函数
高级函数是指至少满足下列条件之一的函数。
相关应用
实现AOP
AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑无关的功能抽离出来,通过“动态织入”的方式掺入业务逻辑模块中。这样做的好处首先是可以保持业务逻辑模块的纯净和高内聚性(一个模块只负责一种功能),其次是可以很方便的复用代码。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| Function.prototype.before = function (beforefn) { const __self = this return function () { beforefn.apply(this, arguments) return __self.apply(this, arguments) } }
Function.prototype.after = function (afterfn) { const __self = this return function () { const ret = __self.apply(this, arguments) afterfn.apply(this, arguments) return ret } }
let func = function () { console.log(2) }
func = func.before(() => { console.log(1) }).after(() => { console.log(3) })
func()
|
函数柯里化
柯里化又称部分求值,柯里化函数会接受一些参数,然后不会立即求值,而是继续返回一个新函数,将传入的参数通过闭包的形式保存,等到被真正求值的时候,再一次性把所有传入的参数进行求值。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| function add (x, y) { return x + y }
add(3, 4)
let add = function (x) { return function (y) { return x + y } }
add(3)(4)
function curry (fn) { function _c(restNum, args) { return restNum === 0 ? fn.apply(null, args) : function (x) { return _c(restNum - 1, [...args, x]) } } return _c(fn.length, []) }
let add = curry((x, y) => { return x + y })
add(3)(4)
|
函数柯里化在业务中的应用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function square(i) { return i * i }
function double(i) { return i *= 2; }
function map(handler, list) { return list.map(handler) }
map(square, [1, 2, 3, 4, 5]) map(square, [6, 7, 8, 9, 10]) map(square, [10, 20, 30, 40, 50])
map(double, [1, 2, 3, 4, 5]) map(double, [6, 7, 8, 9, 10]) map(double, [10, 20, 30, 40, 50])
|
在以上例子中,创建了一个map通用函数,用于适应不同的应用场景,但是在例子中,反复的传入了相同的处理函数:square和double。
我们利用柯里化将其改造一下:
1 2 3 4 5 6 7 8 9 10 11 12
| function curry (fn) { const args = [].slice.call(arguments, 1) return function () { return fn.apply(null, [...args, ...arguments]) } }
const mapSQ = curry(map, square) mapSQ([1, 2, 3, 4, 5])
const mapDB = curry(map, double) mapDB([1, 2, 3, 4, 5])
|
如此,便降低了代码的重复性。
- 延迟执行:利用闭包的特点,缓存积累传入的参数,等到需要的时候再执行函数。举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function curry (fn) { let args = [] return function cb () { if (arguments.length === 0) { return fn.apply(this, args) } else { args.push(...arguments) return cb } } }
const curryAdd = curry((...args) => args.reduce((sum, single) => sum += single))
curryAdd(1) curryAdd(2) curryAdd(3) curryAdd(4) curryAdd()
|
- Function.prototype.bind 方法的实现
1 2 3 4 5 6
| Function.prototype.bind = function (scope) { const fn = this return function () { fn.apply(scope, arguments) } }
|
反柯里化
从字面意义上讲,反柯里化的意义和用法和柯里化正好相反,反柯里化的主要作用是使本来只有特定对象才适用的方法,扩展到更多的对象。例如我们经常使用call和apply去借用Array.prototype上的方法,使其适用的范围从数组扩大到类数组对象。
1 2 3 4
| (function () { Array.prototype.push.call(arguments, 4) console.log(arguments) })(1, 2, 3)
|
反柯里化的函数实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Function.prototype.uncurrying = function () { const self = this return function () { return Function.prototype.call.apply(self, arguments) } }
const push = Array.prototype.push.uncurrying()
const obj = {} push(obj, 'first') console.log('obj', obj)
|
节流与防抖
节流函数:让原先频繁触发的函数在间隔某一段时间内只执行一次。
常见的使用场景:
- window.onresize 事件
- mousemove 事件
- 上传进度
函数实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const throttle = function (fn, interval) { const __self = fn let timer, firstTime = true return function () { const args = arguments, __me = this if (firstTime) { __self.apply(__me, args) return firstTime = false } if (timer) { return false } timer = setTimeout(() => { clearTimeout(timer) timer = null __self.apply(__me, args) }, interval || 500) } }
|
防抖函数:指在事件被触发的某个时间间隔后再执行相应的回调,如果再这个时间间隔内事件再次被触发,则重新计时。
常见的使用场景:
函数实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const debounce = function (fn, interval) { const __self = fn let timer return function () { const args = arguments, __me = this if(timer !== null) { clearTimeout(timer) timer = null } timer = setTimeout(() => { __self.apply(__me, args) }, interval || 500) } }
|
分时函数
分时函数的主要作用就是避免在短时间内执行太多任务,通常用于优化大量数据渲染的场景。
函数实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const timeChunk = function (ary, fn, count, duration) { let timer const start = function () { for (let i = 0; i < Math.min(count || 1, ary.length); i ++) { const obj = ary.shift() fn(obj) } } return function () { timer = setInterval(() => { if (ary.length === 0) { return clearInterval(timer) } start() }, duration || 500) } }
const render = timeChunk(new Array(10), () => { console.log('render') }, 5, 1000)
render()
|
惰性加载函数
由于浏览器之间的行为差异,经常会在函数中包含大量的if语句,以检查浏览器的特性,解决不同浏览器的兼容问题。比如,最常见的为dom节点添加事件的函数:
1 2 3 4 5 6 7 8 9
| function addEvent (elem, type, handler) { if (window.addEventListener) { elem.addEventListener(type, handler, false) } else if (window.attachEvent) { elem.attachEvent('on' + type, handler) } else { elem['on' + type] = handler } }
|
每次调用addEvent函数的时候,它都要对浏览器所支持的能力进行检查,但其实,当页面加载完毕时,浏览器已经是一个固定的浏览器了,检查只需要做一次。我们可以使用立即执行函数来优化这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const addEvent = (function () { if (window.addEventListener) { return function (elem, type, handler) { elem.addEventListener(type, handler, false) } } else if (window.attachEvent) { return function (elem, type, handler) { elem.attachEvent('on' + type, handler) } } else { return function (elem, type, handler) { elem['on' + type] = handler } } })()
|
以上就是惰性加载的一种方式。所谓的惰性加载,就是函数的if分支只会执行一次。
不过上述函数也存在一个缺点,也许我们从头到尾都没有使用过addEvent函数,那么执行这个函数就是一个多余的操作,并且也会延长页面ready的时间。
优化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| let 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) } } else { addEvent = function (elem, type, handler) { elem['on' + type] = handler } } }
|
文章参考:
《JavaScript设计模式与开发实践》