javascript 不是一门函数式的语言,但是函数却是它的第一类型,函数就像一个普通的数据类型一样,可以随意的引用,返回,声明。这赋予了javascript强大的能力,

函数节流

如果某个函数执行频率特别高,但是,实际上只需要某个低频率来运行。这种限制函数的执行频率的方式就叫做函数节流。 在开发中这样的场景会经常遇到,比如拖动窗口会频繁触发window的resize事件,拖拽是时候会频繁触发onmousemove事件,页面滚动事件,用户打字的时候触发keyup事件,可以延伸出许多的功能场景,模拟画板,搜索推荐,可拖拽组件。

下面是一个简单的实现:

	var throttle = function (fn, delay) {
		var cache = fn, timer;
		return function () {
			var args = arguments, self = this;
			if (timer) {
				return;
			}
			timer = setTimeout(function () {
				clearTimeout(timer);
				timer = null;
				cache.apply(self, args);
			}, delay || 200)
		}
	}

主要是运用了闭包和定时器。关于闭包的原理不再赘述,关键是定时器。

javascript中的定时器

js是单线程执行的(除去web worker),这一点很重要,它会导致阻塞。

假设主代码中又一个DOM事件监听,在某一刻它触发了,但是当前主代码中还有在运行的,那么这个事件就会开始排队,等到主线程空闲了之后才执行。

setTimeout与setInterval的区别
setTimeout会在指定的时间间隔之后才执行,但是如果主线程中还有其他在执行的代码,执行将会向后延迟,一直到线程空闲,所以setTimeout的间隔将会比它指定的间隔延迟更多,不会更少。 setInterval会永远按照指定的时间间隔执行,如果线程中有其他代码运行,那么执行将会进行排队,并且,同一个interval定时器的实例不能同时进行排队,如果js引擎发现排队队列中有同一个interval的实例,它会把这个实例销毁。 看下面这个关于setInterval的例子

{% jsfiddle hangbale/u287c6nr js,result %}

interval执行了一个非常耗时的循环,可以看到虽然指定的间隔是200ms,但是实际上的执行间隔是1.55s。大部分interval进入了排队,然后被销毁。

函数防抖

强制函数以某个时间间隔运行,如果在这个间隔内函数被触发了,那么重新计算时间间隔。
假设这样一个场景,有一个提供搜索推荐的输入框,当用户输入某个字符的时候进行联想(百度或者谷歌都有这个功能),我们绑定一个keyup事件,发送ajax请求,获取推荐结果,但是如果用户快速的输入了一段字符串,那么在这个短时间内,将会触发大量的ajax请求,然而理想中的情况应该是,用户输入完成以后再发出ajax,这时函数防抖就用得上了。 看一下underscore的写法:

	_.debounce = function(func, wait, immediate) {
		    var timeout, args, context, timestamp, result;
		
		    var later = function() {
		        var last = _.now() - timestamp;
		
		        if (last < wait && last >= 0) {
		            timeout = setTimeout(later, wait - last);
		        } else {
		            timeout = null;
		            if (!immediate) {
		                result = func.apply(context, args);
		                if (!timeout) context = args = null;
		            }
		        }
		    };
		    return function() {
		        context = this;
		        args = arguments;
		        timestamp = _.now();
		        var callNow = immediate && !timeout;
		        if (!timeout) timeout = setTimeout(later, wait);
		        if (callNow) {
		            result = func.apply(context, args);
		            context = args = null;
		        }
		
		        return result;
		    };
	};	

惰性加载

经常会有这样的场景,检测浏览器的兼容性,然后通过判断作出不同的设置,为了避免每次调用函数的时候都需要进行判断,可以在判断分支内部将函数重写。在做初始化配置的时候,这个方法特别有用。

	var addEvent = function (ele, type, fn) {
    if (window.addEventListener) {
        addEvent = function () {
            ele.addEventListener(type, fn, false)
        }
    } else if (window.addEvent) {
        ele.attachEvent('on' + type, fn)
    }
}

单例

在某些场景下,某些类只需要实例化一个唯一的类就好,避免多余的内存开销,比如常用的modal组件,因为页面上只需要一个modal节点即可,可以用单例将已经插入到DOM中的节点缓存起来,避免重复的查找和插入。或者用来创建命名空间。

	var singleton = function (fn) {
   		var cache;
    	return function () {
       	return cache || (cache = fn.apply(this, arguments));
    	}
	}

柯里化

把多参数的函数转化成一系列的单参数的函数。
从数学上可以这样理解:
给定一个函数 f(x,y,z) = x + 3y + z*z, 求f(1,2,3)的值。

首先把自变量x换成1,那么原函数变成了 f(y,z) = 1 + 3y + z*z;

然后把自变量y换成2,那么原函数变成了 f(z) = 1 + 6 + z*z ;

最后把自变量z换成3,即可求得值16。

这个过程就叫做柯里化,我们可以用javascript来实现一下:

var curry = function (a) {
    return function (b) {
        return function (c) {
            return a + 3*b + c*c;
        }
    }
}
var addedA = curry(1);
var addedB = addedA(2);
var addedC = addedB(3);
alert(addedC);
alert(curry(1)(2)(3));

柯里化常常会跟偏函数弄混

偏函数

还是上面的函数

	f(x,y,z) = x + 3y + z*z

现在已知x是固定值1,那么原函数变成了

f(y,z) = 1 + 3y + z*z

这个过程用js描述就是

var origin = function (a, b, c) {
    return a + 3*y + c*c;
}

var partial = function (b, c) {
    return origin(1, b, c );
}

偏函数的定义是,固定某个多参数函数的其中几个参数的值,并返回以剩下的参数的值作为参数的函数。
它与柯里化不同的地方在于,柯里化永远是返回的单参数的函数。