[js] 函数
(高阶)函数
高阶函数
高阶函数(higher-order function)指操作函数的函数
- 函数可以作为参数被传递
- 函数可以作为返回值输出
常见的 sort,reduce 等函数也算。
AOP
AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。
把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。这样做的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用日志统计等功能模块
通常,在javascript中实现AOP,都是指把一个函数“动态织入”到另外一个函数之中。
Function.prototype.before = function (beforefn) {
var _this = this; // 保存原函数的引用
return function () { // 返回包含了原函数和新函数的"代理"函数
beforefn.apply(this, arguments); // 先执行新函数,修正this
return _this.apply(this, arguments); // 再执行原函数
}
};
Function.prototype.after = function (afterfn) {
var _this = this;
return function () {
var ret = _this.apply(this, arguments); //先执行原函数
afterfn.apply(this, arguments); //再执行新函数
return ret;
}
};
var func = function () {
console.log(2);
};
func = func.before(function () {
console.log(1);
}).after(function () {
console.log(3);
});
func();
//1
//2
//3
not
function not(f) {
return function () {
return !(f.apply(this, arguments));
};
}
//偶数时,返回true;奇数时,返回false
var even = function (x) {
return x % 2 === 0;
}
//偶数时,返回false;奇数时,返回true
var odd = not(even);
console.log([1, 1, 3, 5, 5].every(odd));//true
柯里化
函数柯里化(currying)又称部分求值。一个currying的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。
把接受多个参数的函数变换成接受一个单一参数的函数,并且返回(接受余下的参数而且返回结果的)新函数的技术.
var cost = (function () {
var args = [];
return function () {
//如果没有参数,则计算args数组中的和
if (arguments.length === 0) {
var money = 0;
for (var i = 0, l = args.length; i < l; i++) {
money += args[i];
}
return money;
//如果有参数,则只能是将数据传到args数组中
} else {
[].push.apply(args, arguments);
}
}
})();
cost(100); // 未真正求值
cost(200); // 未真正求值
cost(300); // 未真正求值
console.log(cost()); // 求值并输出:600
通用函数
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;
}
}
};
var cost = (function () {
var money = 0;
return function () {
for (var i = 0, l = arguments.length; i < l; i++) {
money += arguments[i];
}
return money;
}
})();
cost = currying(cost); // 转化成 currying 函数
cost(100); // 未真正求值
cost(200); // 未真正求值
cost(300); // 未真正求值
console.log(cost()); // 求值并输出:600
可传(多个)参函数
var currying = function (fn) {
var args = [];
//储存传到curring函数中的除了fn之外的其他参数,并储存到args函数中
args = args.concat([].slice.call(arguments, 1));//0为function
return function () {
if (arguments.length === 0) {
return fn.apply(this, args);
} else {
//将fn中的参数展开,然后再储存到args数组中
[].push.apply(args, arguments);
}
}
};
var cost = (function () {
var money = 0;
return function () {
for (var i = 0, l = arguments.length; i < l; i++) {
money += arguments[i];
}
return money;
}
})();
cost = currying(cost, 100, 200); // 转化成 currying 函数
cost(100, 200); // 未真正求值
cost(300); // 未真正求值
console.log((cost())); // 求值并输出:900
求值柯里化
var currying = function (fn) {
//获取除了fn之外的其他参数
var args = [].slice.call(arguments, 1);
return function () {
//获取fn里的所有参数
var innerArgs = [].slice.call(arguments);
//最终的参数列表为args和innerArgs的结合
var finalArgs = args.concat(innerArgs);
//将finalArgs里的参数展开,传到fn中执行
return fn.apply(null, finalArgs);
};
};
var cost = (function () {
var money = 0;
return function () {
for (var i = 0, l = arguments.length; i < l; i++) {
money += arguments[i];
}
return money;
}
})();
cost = currying(cost, 100, 200); // 转化成 currying 函数
console.log(cost(300));//100+200+300=600
console.log(cost(100, 100));//(100+200+300)+(100+200+100+100)=1100
function add(a) {
return function(b) {
return a + b
}
}
var add3 = add(3)
add3(4) === 3 + 4 //true
//add 函数 在 es6 里的写法等价为
let add = a => b => a + b
redux-thunk源码
连续箭头函数是多次柯里化函数的 es6 写法
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
反柯里化(uncurrying)?
Function.prototype.uncurrying = function () {
var _this = this;
return function() {
var obj = Array.prototype.shift.call( arguments );
return _this.apply( obj, arguments );
};
};
//或
Function.prototype.uncurrying = function() {
var _this = this;
return function() {
return Function.prototype.call.apply(_this, arguments);
}
}
使用uncurrying
var push = Array.prototype.push.uncurrying();
var obj = {};
push(obj, 'first', 'two');
console.log(obj);//{0: "first", 1: "two", length: 2}
通过uncurrying的方式,Array.prototype.push.call
变成了一个通用的push函数。
这样一来,push函数的作用就跟Array.prototype.push一样了,同样不仅仅局限于只能操作array对象。(但实际生产中似乎很少见人这么用?)
var toUpperCase = String.prototype.toUpperCase.uncurrying();
console.log(toUpperCase('avd')); // AVD
function AryUpper(ary) {
return ary.map(toUpperCase);
}
console.log(AryUpper(['a', 'b', 'c'])); // ["A", "B", "C"]
debounce(函数去抖)
函数防抖的原理是将即将被执行的函数用setTimeout延迟一段时间执行。对于正在执行的函数和新触发的函数冲突问题有两种处理,也分别对应了定时器管理的两种机制
只要当前函数没有执行完成,任何新触发的函数都会被忽略
function debounce(method, context) {
//忽略新函数
if(method.tId){
return false;
}
method.tId = setTimeout(function() {
method.call(context);
}, 1000);
}
只要有新触发的函数,就立即停止执行当前函数,转而执行新函数
function debounce(method, context) {
//停止当前函数
clearTimeout(method.tId);
method.tId = setTimeout(function() {
method.call(context);
}, 1000);
}
var debounce = 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 = debounce(function () {
console.log(1);
}, 500);
throttle(函数节流)
函数节流使得连续的函数执行,变为固定时间段间断地执行。
节流的实现,有两种主流的实现方式.
一种是使用时间戳:
触发事件时,取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行
function throttle(func, wait) {
var context, args;
var previous = 0;
return function () {
var now = +new Date();
context = this;
args = arguments;
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}
}
一种是设置定时器:
触发事件时,设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器
function throttle(func, wait) {
var timeout, args, context;
var previous = 0;
return function () {
context = this;
args = arguments;
if (!timeout) {
timeout = setTimeout(function () {
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
// Produce an array that contains every item shared between all the
// passed-in arrays.
_.intersection = function (array) {
var result = [];
var argsLength = arguments.length;
for (var i = 0, length = getLength(array); i < length; i++) {
var item = array[i];
if (_.contains(result, item)) { continue; }
for (var j = 1; j < argsLength; j++) {
if (!_.contains(arguments[j], item)) { break; }
}
if (j === argsLength) { result.push(item); }
}
return result;
};
数组分块
数组分块是一种使用定时器分割循环的技术,为要处理的项目创建一个队列,然后使用定时器取出下一个要处理的项目进行处理,接着再设置另一个定时器.
在数组分块模式中,array变量本质上就是一个“待办事宜”列表,它包含了要处理的项目。使用shift()方法可以获取队列中下一个要处理的项目,然后将其传递给某个函数。如果在队列中还有其他项目,则设置另一个定时器,并通过arguments.callee调用同一个匿名函数.
数组分块的重要性在于它可以将多个项目的处理在执行队列上分开,在每个项目处理之后,给予其他的浏览器处理机会运行,这样就可能避免长时间运行脚本的错误。一旦某个函数需要花50ms以上的时间完成,那么最好看看能否将任务分割为一系列可以使用定时器的小任务.
function chunk(array, process, context) {
setTimeout(function () {
//取出下一个条目并处理
var item = array.shift();
process.call(context, item);
//若还有条目,再设置另一个定时器
if (array.length > 0) {
setTimeout(arguments.callee, 300);
}
}, 300);
}
var data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
function printValue(item) {
document.body.innerHTML += item + '<br>';
}
chunk(data.concat(), printValue);
第1个参数是创建节点时需要用到的数据,第2个参数是封装了创建节点逻辑的函数,第3个参数表示每一批创建的节点数量
var timeChunk = function (ary, fn, count) {
var obj, t;
var len = ary.length;
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); // 分批执行的时间间隔,也可以用参数的形式传入
};
};
var ary = [];
for (var i = 1; i <= 1000; i++) {
ary.push(i);
}
var renderFriendList = timeChunk(ary, function (n) {
var div = document.createElement('div');
div.innerHTML = n;
document.body.appendChild(div);
}, 8);//每次创建8个
renderFriendList();
惰性函数
惰性函数表示函数执行的分支只会在函数第一次调用的时候执行,在第一次调用过程中,该函数会被覆盖为另一个按照合适方式执行的函数,这样任何对原函数的调用就不用再经过执行的分支了。
产生原因,并不是每次都需要检查。
function addEvent(type, element, fun) {
if (element.addEventListener) {
element.addEventListener(type, fun, false);
}
else if (element.attachEvent) {
element.attachEvent('on' + type, fun);
}
else {
element['on' + type] = fun;
}
}
函数重写
由于一个函数可以返回另一个函数,因此可以用新的函数来覆盖旧的函数。
这样一来,第一次调用该函数时console.log('a')
会被执行;全局变量a被重定义,并被赋予新的函数。当该函数再次被调用时, console.log('b')
会被执行
function a(){
console.log('a');
a = function(){
console.log('b');
}
}
惰性函数的本质就是函数重写。所谓惰性载入,指函数执行的分支只会发生一次,有两种实现惰性载入的方式.
第一种是在函数被调用时,再处理函数。函数在第一次调用时,该函数会被覆盖为另外一个按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行的分支了。
在这个惰性载入的addEvent()中,if语句的每个分支都会为addEvent变量赋值,有效覆盖了原函数。最后一步便是调用了新赋函数。下一次调用addEvent()时,便会直接调用新赋值的函数,这样就不用再执行if语句了.
这种方法有个缺点,如果函数名称有所改变,修改起来比较麻烦.
function addEvent(type, element, fun) {
if (element.addEventListener) {
addEvent = function (type, element, fun) {
element.addEventListener(type, fun, false);
}
} else if (element.attachEvent) {
addEvent = function (type, element, fun) {
element.attachEvent('on' + type, fun);
}
} else {
addEvent = function (type, element, fun) {
element['on' + type] = fun;
}
}
return addEvent(type, element, fun);
}
第二种是声明函数时就指定适当的函数。把嗅探浏览器的操作提前到代码加载的时候,在代码加载的时候就立刻进行一次判断,以便让addEvent返回一个包裹了正确逻辑的函数.
var addEvent = (function () {
if (document.addEventListener) {
return function (type, element, fun) {
element.addEventListener(type, fun, false);
}
}
else if (document.attachEvent) {
return function (type, element, fun) {
element.attachEvent('on' + type, fun);
}
}
else {
return function (type, element, fun) {
element['on' + type] = fun;
}
}
})();