高阶函数
高级函数是指至少满足下列条件之一的函数。
-
函数可以作为参数被传递
实际应用场景:回调函数(callback)、常见的数组方法,如
sort
filter
map
reduce
等 -
函数可以作为返回值输出
实际应用场景:闭包等各种场景
相关应用
实现AOP
AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑无关的功能抽离出来,通过“动态织入”的方式掺入业务逻辑模块中。这样做的好处首先是可以保持业务逻辑模块的纯净和高内聚性(一个模块只负责一种功能),其次是可以很方便的复用代码。
代码如下:
Function.prototype.before = function (beforefn) {
const __self = this // 保存原函数的引用
return function () { // 返回包含了原函数和新函数的“代理”函数
beforefn.apply(this, arguments) // 执行新函数,修正this
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
函数柯里化
柯里化又称部分求值,柯里化函数会接受一些参数,然后不会立即求值,而是继续返回一个新函数,将传入的参数通过闭包的形式保存,等到被真正求值的时候,再一次性把所有传入的参数进行求值。
代码如下:
var monthTime = 0;
function overtime(time) {
return monthTime += time;
}
overtime(3.5); // 第一天
overtime(4.5); // 第二天
overtime(2.1); // 第三天
//...
console.log(monthTime); // 10.1
延迟执行:利用闭包的特点,缓存积累传入的参数,等到需要的时候再执行函数。举个例子:
// 柯里化
var overtime = (function() {
var args = [];
return function() {
if(arguments.length === 0) { // 当不传入参数时,调用原函数
var time = 0;
for (var i = 0, l = args.length; i < l; i++) {
time += args[i];
}
return time;
}else {
[].push.apply(args, arguments); // 存储每次传入的参数
}
}
})();
overtime(3.5); // 第一天
overtime(4.5); // 第二天
overtime(2.1); // 第三天
//...
console.log( overtime() ); // 10.1
反柯里化
从字面意义上讲,反柯里化的意义和用法和柯里化正好相反,反柯里化的主要作用是使本来只有特定对象才适用的方法,扩展到更多的对象。例如我们经常使用call和apply去借用Array.prototype上的方法,使其适用的范围从数组扩大到类数组对象。
(function () {
Array.prototype.push.call(arguments, 4) // arguments为类数组对象,借用Array.prototype.push方法
console.log(arguments)
})(1, 2, 3)
反柯里化的函数实现:
Function.prototype.uncurrying = function () {
var self = this; return function () {
var obj = Array.prototype.shift.call(arguments);
return self.apply(obj, arguments);
};
};
//case1
var obj1 = { name: 'sven' };
var obj2 = { getName: function () { return this.name; } };
console.log(obj2.getName.call(obj1)); // 输出:sven
//case2 类数组使用push
(function(){
Array.prototype.push.call( arguments, 4 ); // arguments 借用 Array.prototype.push 方法
console.log( arguments ); // 输出:[1, 2, 3, 4]
})( 1, 2, 3 );
//case3
var push = Array.prototype.push.uncurrying();
(function(){
push( arguments, 4 );
console.log( arguments ); // 输出:[1, 2, 3, 4]
})( 1, 2, 3 );
节流与防抖
节流函数:让原先频繁触发的函数在间隔某一段时间内只执行一次。
常见的使用场景:
- window.onresize 事件
- mousemove 事件
- 上传进度
函数实现:
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)
}
}
防抖函数:指在事件被触发的某个时间间隔后再执行相应的回调,如果再这个时间间隔内事件再次被触发,则重新计时。
常见的使用场景:
- input 输入事件所触发的ajax请求
函数实现:
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)
}
}
分时函数
分时函数的主要作用就是避免在短时间内执行太多任务,通常用于优化大量数据渲染的场景。
函数实现:
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)
}
}
惰性加载函数
由于浏览器之间的行为差异,经常会在函数中包含大量的if语句,以检查浏览器的特性,解决不同浏览器的兼容问题。比如,最常见的为dom节点添加事件的函数:
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函数的时候,它都要对浏览器所支持的能力进行检查,但其实,当页面加载完毕时,浏览器已经是一个固定的浏览器了,检查只需要做一次。我们可以使用立即执行函数来优化这个方法:
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的时间。
优化:
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
}
}
}