高阶函数
回调函数
下面根据日期对对象进行排序
var a = {
id: 1,
date: new Date(2019, 3, 12)
},
b = {
id: 2,
date: new Date(2019, 1, 14)
},
c = {
id: 3,
date: new Date(2019, 2, 26)
};
var arr = [a, b, c];
arr.sort(function(x, y) {
return x.date - y.date;
})
console.log(arr);
for(var i=0; i<arr.length; i++) {
console.log(arr[i].id + " " + arr[i].date.toLocaleString());
}
map
表达式将 func
函数作用于 array
的每一个元素,并返回一个新的 array
。
function map(array, func) {
var res = [];
for(var i in array) {
res.push(func(array[i]))
}
return res;
}
console.log(map([1, 3, 5, 7, 8], function(n) {
return n*n;
})); // [1, 9, 25, 49, 64]
console.log(map(['one', 'two', 'three', 'four'], function(item) {
return item[0].toUpperCase() + item.slice(1).toLowerCase();
})); // ["One", "Two", "Three", "Four"]
单例模式
单例模式是保证一个类型只有一个实例。实现方法:先判断实例是否存在,如果存在则直接返回,否则就创建实例再返回。
单例模式可以作为一个命名空间,提供一个唯一的访问点来访问该对象。
- 单例模式封装代码
var getSingle = function(fn) {
var ret;
return function() {
return ret || (ret = fn.apply(this, arguments));
}
}
在脚本中定义 XMLHttpRequest
对象,由于一个页面可能需要多次创建异步请求对象,使用单例模式封装之后,就不用重复创建实例请求。
var getSingle = function(fn) {
var ret;
return function() {
// this = window
return ret || (ret = fn.apply(this, arguments));
}
}
function XHR() {
return new XMLHttpRequest();
}
var xhr = getSingle(XHR);
var a = xhr();
var b = xhr();
console.log(a === b); // ture 确保一个类型只有一个实例
可以限定函数仅能调用一次,避免重复调用;这在事件处理函数中非常有用。
function getSingle(fn) { // 封装单例模式
var ret;
return function() {
return ret || (ret = fn.apply(this, arguments));
}
}
var f = function() {
console.log(this.nodeName); // 事件处理函数
}
document.getElementsByTagName('button')[0].onclick = getSingle(f);
实现 AOP
AOP
面向切面编程,就是把一些与业务逻辑无关的功能抽离出来,如 日志统计-安全控制-异常处理等。然后通过 “动态织入” 的方式掺入业务逻辑模块中。
下面实现 AOP
一般是把一个函数 “动态织入” 到另一个函数中。
Function.prototype.before = function(beforefn) {
var _self = this; // 保存原函数的引用
return function() { // 返回包含了原函数和新函数的“代理”函数
beforefn.apply(this, arguments); // 执行新函数,修正 this
return _self.apply(this, arguments); // 执行原函数
}
}
Function.prototype.after = function(afterfn) {
var _self = this;
return function() {
var ret = _self.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
函数节流
函数节流就是降低函数被调用的频率。
- 使用定时器进行函数节流
// 函数节流封装代码,参数 method 表示要执行的函数,delay 表示要延迟的时间,单位是毫秒
function throttle(method, delay) {
var timer = null;
return function() { // arguments 参数
var context = this, args = arguments;
clearTimeout(timer);
timer = setTimeout(function() {
method.apply(context, args);
}, delay);
}
}
应用代码
<input id="search" type="text" name="search" />
function queryData(text) {
console.log('搜索:' + text);
}
var input = document.getElementById('search');
input.addEventListener("keyup", function(event) {
queryData(this.value);
})
var n = 0;
function f() {
console.log("响应次数:" + ++n);
}
window.onresize = f;
通过观察发现,在拖动改变窗口的一瞬间,resize
事件响应了几十次,keyup
事件也会立即响应很多次;改善如下:
function throttle(method, delay) {
var timer = null;
return function() { // arguments 参数
var context = this, args = arguments;
clearTimeout(timer);
timer = setTimeout(function() {
method.apply(context, args);
}, delay);
}
}
function queryData(text) {
console.log('搜索:' + text);
}
var input = document.getElementById('search');
input.addEventListener("keyup", function(event) {
throttle(queryData, 500)(this.value);
})
var n = 0;
function f() {
console.log("响应次数:" + ++n);
}
window.onresize = throttle(f, 500);
分时函数
当批量操作影响到页面性能时,如果一次性往页面添加大量 DOM 节点,显然会给浏览器的渲染带来影响,极端下回出现卡顿;可以将批量操作分批处理。
// 设计思路:把 1 秒创建 1000 个节点,改为每隔 200ms 创建 100 个节点等
/*
* [ary] 表示批量操作是需要用到的数据
* [fn] 表示封装了批量操作的逻辑函数
* count 表示分批操作的数量
**/
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);
}
}
var arr = [];
for(var i = 1; i <= 10000; i++) {
var span = document.createElement("span");
span.style.padding = "6px 12px";
span.innerHTML = i;
arr.push(span);
}
var fn = function(obj) {
document.body.appendChild(obj);
}
timeChunk(arr, fn, 100)();
惰性载入函数
惰性载入就是当第1次根据条件执行函数后,第 2 次调用函数时,就不再检测条件,直接执行函数。
var addEvent = function (element, type, handle) {
if(element.addEventListener) {
element.addEventListener(type, handle, false);
} else {
element.attachEvent("on" + type, handle);
}
}
addEvent(document, "mousemove", function() {
console.log("鼠标移动");
})
addEvent(window, "resize", function() {
console.log("改变窗口大小");
})
每次调用 addEvent
都会做一次条件判断的检测。
var addEvent = function (element, type, handle) {
addEvent = element.addEventListener ? function(element, type, handle) {
element.addEventListener(type, handle, false);
} : function(element, type, handle) {
element.attachEvent("on" + type, handle);
}
// 在第一次执行 addEvent 函数时,修改了 addEvent 函数之后,必须执行一次
// 执行了才能赋值
addEvent(element, type, handle);
}
分支函数
分支函数与惰性载入函数都是解决条件检测问题。
var XHR = function () {
var standard = {
createXHR: function() {
return new XMLHttpRequest();
}
}
var newActionXObject = {
createXHR: function() {
return new newActionXObject("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;
}
}
}
var xhr = XHR.createXHR();
/*
在代码初始化之后,XHR 被初始化为一个对象,拥有 createXHR() 方法,该方法的实现已经在初始化阶段根据当前浏览器选择了合适的方法,当调用的时候,就不再去检查浏览器的兼容性问题。
*/
偏函数
偏函数是函数柯里化的运算的一种特定应用场景,简单描述,就是把一个函数的某些参数先固化,也就是设置默认值,返回一个新函数,在新函数中继续接收剩余参数,这样调用这个新函数会更简单。
var isType = function(type) {
return function(obj) {
return Object.prototype.toString.call(obj) == '[object ' + type +']';
}
}
// 根据偏函数获取不同类型检测函数
var isString = isType("String"); // 专一功能检测函数,检测字符串
var isFunction = isType("Function"); // 专一功能检测函数,检测函数
console.log(isString("12")); // true
console.log(isFunction(function(){})); // true
console.log(isFunction({})); // false
下面示例设计一个 wrap()
偏函数,该函数的主要功能是产生一个 HTML
包裹函数。
function wrap(tag) {
var stag = '<' + tag + '>';
var etag = '</' + tag.replace(/s.*/, '') + '>';
return function(x) {
return stag + x + etag;
}
}
var b = wrap('b');
document.write(b('粗体字'));
var i = wrap('i');
document.write(i('斜体字'));
var u = wrap('u');
document.write(u('下划线字'));
泛型函数
JS
具有动态类型语言的部分特点,如果用户不用关心一个对象是否拥有某个方法,一个对象也不限于只能使用自己的方法 - 使用 call
或 apply
动态调用,可以使用其他对象的方法。这样该方法中的 this
就不再局限于原对象,而是被泛化。
泛型函数的设计目的:将泛化 this 的过程提取出来,将 fn.call
或 fn.apply
抽象成通用函数。
实现代码
Function.prototype.uncurry = function() {
var self = this;
return function() {
return Function.prototype.apply.apply(self, arguments);
}
}
// 泛化 Array.prototype.push
var push = Array.prototype.push.uncurry();
var obj = {};
push(obj, [3, 4, 5]);
console.log(obj); // {0: 3, 1: 4, 2: 5, length: 3}
for (var i in obj) {
console.log(i); // 0 1 2 length
}
类型检测
本例通过 JS
高阶函数特性来重新设计 typeOf()
函数,并提供单项类型判断函数。
function typeOf(obj) {
var str = Object.prototype.toString.call(obj);
return str.match(/\[object (.*?)\]/)[1].toLowerCase();
}
['Null', 'Undefined', 'Object', 'Array', 'String', 'Number', 'Boolean', 'Function', 'RegExp'].forEach(function(t) {
typeOf['is' + t] = function(o) {
return typeOf(o) === t.toLowerCase();
}
})
console.log(typeOf({})); // object
console.log(typeOf([])); // array
console.log(typeOf(0)); // number
console.log(typeOf(null)); // null
console.log(typeOf(undefined)); // undefined
//console.log(typeOf(//)); // Regex
console.log(typeOf(new Date())); // 'date'
// 类型判断
console.log(typeOf.isObject({})); // true
console.log(typeOf.isNumber(NaN)); // true
console.log(typeOf.isRegExp(true)); // false