灏天阁

高阶函数

· Yin灏

回调函数

下面根据日期对对象进行排序

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 具有动态类型语言的部分特点,如果用户不用关心一个对象是否拥有某个方法,一个对象也不限于只能使用自己的方法 - 使用 callapply 动态调用,可以使用其他对象的方法。这样该方法中的 this 就不再局限于原对象,而是被泛化。

泛型函数的设计目的:将泛化 this 的过程提取出来,将 fn.callfn.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

- Book Lists -