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 );
}
偏函数的定义是,固定某个多参数函数的其中几个参数的值,并返回以剩下的参数的值作为参数的函数。
它与柯里化不同的地方在于,柯里化永远是返回的单参数的函数。