高阶函数也称算子(运算符)或泛函。作为函数式编程的最显著的特点,是对函数运算进一步的抽象。高阶函数有以下条件:
把函数作为值传入另一个函数,达能传入函数时,就成称为回调函数,即异步调用已绑定的函数。
jQuery 最大的亮点就是它的链式语法。在 JavaScript 中,很多方法没有返回值,一些设置或修改对象的某个状态却不返回任何值的方法就是最典型的例子。如果让这些方法返回 this ,而不是 undefined ,那么就要启用级联功能,即所谓的链式语法。在一个级联中,单独语句可以连续调用同一个对象的很多方法。
链式语法可以产生出具备很强的表现力接口,以打造出试图一次做很多事的接口。
Function.prototype.method = function (name, func) {
if (!this.prototype[name]) {
this.prototype[name] = func;
return this;
}
};
String.method('trim', function () {
return this.replace(/^\s+|\s$/g, '');
});
String.method('writeln', function () {
console.log(this);
return this;
});
String.method('alert', function () {
window.alert(this);
return this;
});
var str = 'abc';
str.trim().writeln().alert();
函数节流的设计思想就是让某些代码能够再间断情况下重复执行。实现的方法就是使用定时器对函数进行节流。
函数节流是降低函数被调用的频率,主要针对 DOM 事件暴露的问题,提出的一种解决的方案。
例如,使用 resize 、 mousemove 、 mouseover 、 mouseout 、 keydown 、 keyup 等事件,都会频繁的触发事件,如果这些事件的处理函数包含大量的耗时操作,如 Ajax 请求、数据库查询、 DOM 遍历等,如此高频耗时的操作会让浏览器崩溃。
而已使用计时器避除。
oTrigger.onmouseover = function (e) {
oContainer.autoTimeoutId && clearInterval(oContainer.autoTimeoutId);
e = e || window.event;
var target = e.target || e.srcElement;
if (/li$/i.test(target.nodeName)) {
oContainer.autoTimeoutId = setTimeout(function () {
addTweenForContainer(oContainer, oTrigger, target);
}, 300);
}
};
// 函数节流封装代码,参数 method 表示要执行的函数, delay 表示要延迟时间,单位毫秒
function throttle(method, delay) {
var timer = null;
// 定时器句柄
return function () {
// 返回节流函数
var context = this,
args = arguments;
// 上下文环境和参数对象
clearTimeout(timer); // 先清理未执行的函数
timer = setTimeout(function () {
// 重新定义定时器,记录新的定时器句柄
method.apply(context, args); // 执行预设的函数
}, delay);
};
}
分支函数主要解决问题:浏览器之间兼容的重复判断。解决浏览器之间兼容性的一般方法是使用 if 语句进行检测能力检测,根据浏览器不同的实现来实现功能上的兼容,这样做的问题是:每执行一次代码,可能都需要进行一次代码检测,这是没必要的。
var XHR = function () {
var standard = {
createXHR: function () {
return new ActionXObject('Msxm12.XMLHTTP');
},
};
var oldActionXObject = {
createXHR: function () {
return new ActionXObject('Microsoft.XMLHTTP');
},
};
if (standard.createXHR()) {
return standard;
} else {
try {
newActionXObject.createXHR();
return newActionXObject;
} catch (o) {
oldActionXObject.createXHR();
return oldActionXObject;
}
}
};
从代码可以看出,分支的设计原理:先声明几个不同名称的对象,但是这些对象都声明一个名称相同的方法(很关键)。针对这些来自于不同的对象,但是拥有相同的方法,根据不同的浏览器设计各自的目的,接着开始一次浏览器检测,并且由经过浏览器检测的结果来决定返回哪一个对象,这样无论返回哪一个对象,最后名称相同的方法都作为对外一致的接口。
这种方法再 JavaScript 运行期间进行动态监测,将检测结果返回赋值给其它对象,并且提供相同的接口,这样储存的对象就可以使用相同的接口了。其实,惰性载入函数跟分支在原理上是非常接近的,只是在代码实现方法有差异而已。
惰性载入函数主要解决的也是兼容性问题。
var addEvent = function (el, type, handle) {
addEvent = el.addEventListener
? function (el, type, handle) {
el.addEventListener(type, handle, false);
}
: function (el, type, handle) {
el.attachEvent('On' + type, handle);
};
addEvent(el, type, handle);
};
从代码来看,惰性载入函数也是在函数内部改变自身的一种方式,这样在重复执行的时候就不用再进行兼容性方面的检测。
惰性载入表示函数执行的分支进会发生一次,即第一次调用时。在第一次调用过程中,该函数会被覆盖给另一个适合执行的函数,这样任何对原函数的调用都可以不再经过执行的分支了。其优点是:
由于浏览器的行为差异,多数 JavaScript 代码包含了大量的 if 语句,将执行引导到正确的代码中。
function createXHR() {
if (typeof XMLHttpRequest != 'undefined') {
return new XMLHttpRequest();
} else if (typeof ActiveXObject != 'undefined') {
if (typeof arguments.callee.ActiveXString != 'string') {
versions = ['MSXML2.XMLHttp', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp.6.0'];
for (var i = 0, len = versions.length; i < len; i++) {
try {
var xhr = new ActiveXObject(versions[i]);
arguments.callee.ActiveXString = versions[i];
return xhr;
} catch (ex) {
// 跳过
}
}
}
return new ActiveXObject(arguments.callee.ActiveXString);
} else {
throw new Error('No XHR object available.');
}
}
第一次调用 createXHR() 函数时都要经过浏览器所支持的能力检测,这意味着每次调用都会调用 createXHR() 的时都会经过相同的检测,重复的检测及没必要了。减少 if 的使用使其不必每一次都运行,代码就会运行的快一些。解决方案就是惰性载入的技巧。
function createXHR() {
if (typeof XMLHttpRequest != 'undefined') {
createXHR = function () {
return new XMLHttpRequest();
};
} else if (typeof ActiveXObject != 'undefined') {
createXHR = function () {
if (typeof arguments.callee.ActiveXString != 'string') {
versions = [
'MSXML2.XMLHttp',
'MSXML2.XMLHttp.3.0',
'MSXML2.XMLHttp.6.0',
];
for (var i = 0, len = versions.length; i < len; i++) {
try {
var xhr = new ActiveXObject(versions[i]);
arguments.callee.ActiveXString = versions[i];
return xhr;
} catch (ex) {
// 跳过
}
}
}
return new ActiveXObject(arguments.callee.ActiveXString);
};
} else {
throw new Error('No XHR object available.');
}
return createXHR();
}
if 的每一个分支都会为 createXHR 变量赋值,有效覆盖了原函数。最后一步便调用了新赋值的函数。下次调用 createXHR() 函数时就会直接调用被分配的函数,这样就不会再次执行 if 语句了。
在 JavaScript 中,使用函数式编程风格,应该对表达式有深刻的理解,并能够主动使用表达式的连续运算来组织代码。
函数作为运算符参加运算,具有非惰性求值特性。非惰性求值值行为会行为自然会对整个程序产生一定的负面影响。
var a = 2;
function f(x) {
return x;
}
alert(f(a, (a = a * a)));
alert(f(a));
两次调用同意函数并传递同一变量,说返回值不相同。在第一次调用函数时,向函数传递两个参数,第二个参数是表达式,该表达式对变量 a 进行重新计算和赋值。也就是说,当调用函数时,第二个参数虽然没用,但是也被计算了。这就是 JavaScript 的非惰性求值特性。就是不管表达式是否被利用,只要在执行代码中,都会被计算。
如果在第一个函数参数中添加了几个表达式,虽然不会对函数的运算结果产生影响,但是由于表达式的执行,就会对整个程序产生负面影响。
在惰性求值语言中,如果参数不会调用,那么无论参数直接量,还是某个表达式,都不会占用系统资源。但是,由于 JavaScript 的非惰性求值,问题就变得特殊了。
function f(){}; f(function(){while(true)}())
在上面的示例中,虽然函数 f 没有参数,但是在调用时会执行传递给它的参数表达式,该表达式是一个死循环的数值,将导致系统崩溃。
惰性韩式模式是一种将对函数或请求的处理延迟到真正需要结果进行的通用概念。很多应用程序都采用了这种概念,从惰性编程的思想来看,可以帮助消除代码不必要的计算。
分时函数和节流函数的设计思路相同,但应用的场景略不相同。当批量操作影响页面性能时,如一次向页面添加大量的 DOM 节点,显然给浏览器的渲染带来了影响。
var n = 0;
var timeChunk = function (ary, fn, count) {
var t;
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); // 分批执行的时间间隔,也可以用参数的形式传入
};
};
偏函数是柯里化运算的一种特定场景。它是把一个函数的某些参数先固化,也就是说设置为默认值,返回一个新的函数,在新的函数继续接受剩余的参数,这样的调用这个新的函数会更简单。
function wrap(tag) {
var stag = '<' + tag + '>';
var etag = '</' + tag.replace(/s.\*/, '') + ' >';
return function (x) {
return stag + x + etag;
};
}
var b = wrap('b');
console.log(b('粗体字'));
var i = wrap('i');
console.log(i('斜体字'));
JavaScript 具有动态类型语言的部分特点,如用户不用关心一个对象是否拥有某个方法,一个对象也不必只是用自己的方法,使用 call 和 apply 可以动态调用,可以使用别人的方法,这样该方法中的 this 就不再局限与原对象,而是被泛化,从而得到更广泛的使用。
泛型函数( uncurry )的设计目的,将泛化 this 的过程提取出来,将 fn.call 或 fn.apply 抽象成通用的函数。
实现。
function.prototype.uncurry = function(){
var self = this;
return function() {return Function.prototype.apply.apply(self,arguments);}
};
函数可以利用对象去记住先前的操作,从而避免了无畏的运算。这种优化称之为记忆。 JavaScript 的对象和数组要实现这种优化是很方便的。
下面是一个常规函数。
var fibonacci = function (n) {
return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
};
for (var i = o; i <= 10; i++) {
console.log('' + i + ': ' + fibonacci(n));
}
该段代码中 fibonacci 函数被调用 435 次,其中循环调用11 次,它自身调用 442 次,去计算可能已经计算过的结果。如果该值具备记忆功能,就可以显著减少运算次数。
var fibonacci = (function () {
var memo = [0, 1];
var fib = function (n) {
var result = memo[n];
if (typeof result !== 'number') {
result = fib(n - 1) + fib(n - 2);
memo[n] = result;
}
return result;
};
return fib;
})();
for (var i = 0; i <= 10; i++) {
console.log('' + i + ': ' + fibonacci(i));
}
同样的计算结果,但是下面这个只被调用了 29 次,其中循环调用 11次,它自身调用 了 18 次,去取得之前的结果。
当然,我们抽象化之后,以构造带记忆功能的函数。 memoizer 函数将取得一个初始的 memo 数组和 fundamental 函数。 memoizer 函数返回一个管理 memo 储存在需要时调用 fundamental 函数。 memoizer 函数传递这个 shell 函数和该函数的参数给 fundamental 函数。
var memoizer = function (mome, formula) {
var recur = function (n) {
if (typeof result !== 'number') {
result = formula(recur, n);
memo[n] = result;
}
return result;
};
return recur;
};
这样就可以使用 memoizer 来定义 fundamental 函数,提供初始的 memo 数组和 fundamental 函数。
var fibonacci = memoizer([0, 1], function (recur, n) {
return;
recur(n - 1) + recur(n - 2);
});
这种设计能产生其它函数的函数,可以极大减少不必要的工作。例如,产生一个可记忆的阶乘函数,只需提供一个基础的阶乘公司即可。
var factorial = memoizer([1, 1], function (recur, n) {
return;
n * recur(n - 1);
});