JavaScript 设计模式与开发实践读书笔记 http://www.open-open.com/lib/view/open1469154727495.html

JavaScript 设计模式与开发实践读书笔记

最近利用碎片时间在 Kindle 上面阅读《JavaScript 设计模式与开发实践读书》这本书,刚开始阅读前两章内容,和大家分享下我觉得可以在项目中用的上的一些笔记。

我的 github 项目会不定时更新,有需要的同学可以移步到我的 github 中去查看源码: https://github.com/lichenbuliren/design-mode-notes

1、currying 函数柯里化

currying 又称 部分求值 。一个 currying 的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,将刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性的用于求值。

假设我们需要编写一个计算每个月开销的函数,在每天结束之前,我们要记录每天花掉了多少钱。

通用 currying 函数:

var currying = function(fn) {
  var args = [];

  return function() {
    if (arguments.length === 0) {
      return fn.apply(this, args);
    } else {
      [].push.apply(args, arguments);
      // 返回函数本身,这里指向 return 后面的匿名函数!
      return arguments.callee;
    }
  }
};

var cost = (function() {
  // 闭包存储最后的值
  var money = 0;

  return function() {
    for (var i = 0, len = arguments.length; i < len; i++) {
      money += arguments[i];
    }

    return money;
  }
})();

// 转化成 currying 函数
// 这个时候,闭包内部的 fn 指向真正的求值函数
// 也就是 cost 自运行的时候返回的匿名函数
var cost = currying(cost);

cost(200);
cost(300);
cost(500);

// 求值输出
console.log(cost());

2、uncurrying 函数

Function.prototype.uncurrying = function() {
  // 此时 selft 是后面例子中的 Array.prototype.push;
  var self = this;

  return function() {
    // arguments: { ‘0‘: { ‘0‘: 1, length: 1 }, ‘1‘: 2 }
    var obj = Array.prototype.shift.call(arguments);
    return self.apply(obj, arguments);
  }
};

// 另外一种实现方式
Function.prototype.uncurrying = function() {
  var self = this;

  return function() {
    return Function.prototype.call.apply(self, arguments);
  }
};

var push = Array.prototype.push.uncurrying();

var obj = {
  "length": 1,
  "0": 1
};

push(obj, 2);
console.log(obj);

3、函数节流

JavaScript 中的函数大多数情况下都是由用户主动调用触发的,除非是函数本身的实现不合理,否则我们一般不会遇到跟性能相关的问题。但是在一些少数情况下,函数的触发不是有由用户直接控制的。在这些场景下,函数有可能被非常频繁的调用,而造成大的性能问题。

函数被频繁调用的场景:

  • window.onresize 事件
  • mousemove 事件
  • 上传进度

函数节流原理

上面三个提到的场景,可以发现它们面临的共同问题是函数被触发的频率太高。

比如我们在 window.onresize 事件中要打印当前浏览器窗口大小,在我们拖拽改变窗口大小的时候,控制台1秒钟进行了 10 次。而我们实际上只需要 2 次或者 3 次。这就需要我们按时间段来忽略掉一些事件请求,比如确保在 500ms 内打印一次。很显然,我们可以借助 setTimeout 来完成。

函数节流实现

/**
 * 函数节流实现
 * @param  {Function} fn       需要节流执行的函数
 * @param  {[type]}   interval 事件执行间隔时间,单位 ms
 * @return {[type]}            [description]
 */
var throttle = function(fn, interval) {
  var _self = fn,
      timer,
      firstTime = true;

  console.log(_self);

  return function() {
    var args = arguments,
        _me = this;  // 这里代表当前的匿名函数

    console.log(_me);

    if (firstTime) {
      _self.apply(_me, args);
      return firstTime = false;
    }

    if (timer) {
      return false;
    }

    timer = setTimeout(function() {
      clearTimeout(timer);
      timer = null;
      _self.apply(_me, args);
    }, interval || 500);
  };
};

window.onresize = throttle(function() {
  console.log(‘test‘);
}, 500);

4、分时函数

我们经常会遇到这么一种情况,某些函数确实是用户主动调用的,但是因为一些客观原因,这些函数会严重地影响页面性能。

一个例子就是创建 WebQQ 的 QQ 好友列表。列表中通常会有成百上千个好友,如果一个好友用一个节点来表示,当我们在页面中渲染这个列表的时候,可能要一次性往页面中创建成百上千个节点。

在短时间内往页面中大量添加 DOM 节点显然也会让浏览器吃不消,我们看到的结果往往就是浏览器的卡顿甚至假死。所以我们需要一个分时函数来解决这个问题

/**
 * 分时函数例子
 * 以创建 WebQQ 列表为例
 * @param  {[type]}   data     函数执行需要用到的数据
 * @param  {Function} fn       真正需要分时执行的函数
 * @param  {[type]}   count    每次创建一批节点的数量
 * @param  {[type]}   interval 函数执行间隔
 * @return {[type]}            [description]
 */
var timeChunk = function(data, fn, count, interval) {
  var t;

  var len = data.length;

  var start = function() {
    for (var i = 0; i < Math.min(count || 1, data.length); i++) {
      var obj = data.shift();
      fn(obj);
    }
  }

  return function() {
    t = setInterval(function() {
      if (data.length === 0) {
        return clearInterval(t);
      }

      start();
    }, interval);
  }
}

5、惰性加载函数

以创建事件绑定函数为例:

在进入第一个条件分支之后,在函数内部重写这个函数,重写之后,就是我们所需要的函数,在下一次进入的时候,就不再需要判断了。

/**
 * 事件绑定
 * @param {[type]} el      [description]
 * @param {[type]} type    [description]
 * @param {[type]} handler [description]
 */
var addEvent = function(el, type, handler) {
  if (window.addEventListener) {
    addEvent = function(el, type, handler) {
      el.addEventListener(type, handler, false);
    }
  } else if (window.attachEvent) {
    addEvent = function(el, type, handler) {
      el.attachEvent(‘on‘ + type, handler);
    }
  }

  addEvent(el, type, handler);
}

Q&A

暂时这么多,以后会不定期更新一些关于我读这本书的笔记内容!

时间: 2024-10-27 12:50:12

JavaScript 设计模式与开发实践读书笔记 http://www.open-open.com/lib/view/open1469154727495.html的相关文章

JavaScript设计模式与开发实践——读书笔记1.高阶函数(下)

上部分主要介绍高阶函数的常见形式,本部分将着重介绍高阶函数的高级应用. 1.currying currying指的是函数柯里化,又称部分求值.一个currying的函数会先接受一些参数,但不立即求值,而是继续返回给另一个函数,通过闭包存储起来.等到函数被真正需求要求值的时候,将之前传入的参数统一起来求值.例如,我们要计算一个月的开销,我们并不需要计算每天具体花了多少,而是需要计算月底总共花掉多少,也就是说,实际上我们只需要在月底计算一次.所以每个月的前29天,我们都只需要保存好当天的开销,到30

JavaScript设计模式与开发实践---读书笔记(1)

前言 设计模式的定义是:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案. 从某些角度来看,设计模式确实有可能带来代码量的增加,或许会把系统的逻辑搞的更复杂.但软件开发的成本并非全部在开发阶段,设计模式的作用是让人们写出可复用和可维护性高的程序. 所有设计模式的实现都遵循一条原则,即“找出程序中变化的地方,并将变化封装起来”. 不变和稳定的部分是非常容易复用的. 分辨模式的关键是意图而不是结构 模式只有放在具体的环境下才有意义,辨别模式的关键是这个模式出现的场景,以及为我们解决的问题.

JavaScript设计模式与开发实践---读书笔记(7) 迭代器模式

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示. JavaScript中的Array.prototype.foreach. 1.JQuery中的迭代器 $.each函数 2.自己实现一个each函数 var each = function(ary,callback){ for(var i=0,l=ary.length;i<l;i++){ callback.call(ary[i],i,ary[i]);//把下标和元素当作参数传给callback函数 }

JavaScript设计模式与开发实践-读书笔记(3)闭包和高阶函数

闭包(closure) 闭包的形成与变量的作用域以及变量的生存周期密切相关. 变量的作用域,就是指变量的有效范围. 全局变量和局部变量. 在JavaScript中,函数可以用来创造函数作用域. 变量的生存周期,全局变量的生命周期是永久的,除非我们主动销毁这个全局变量. 对于在函数体内用var关键字声明的局部变量来说,当退出函数时,这些局部变量即失去了它们的价值,它们都会随着函数调用的结束而被销毁. 利用闭包我们可以完成许多奇妙的工作. 闭包的作用: 1.封转变量 闭包可以帮助我们把一些不需要暴露

Javascript设计模式与开发实践读书笔记(1-3章)

第一章 面向对象的Javascript 1.1 多态在面向对象设计中的应用   多态最根本好处在于,你不必询问对象“你是什么类型”而后根据得到的答案调用对象的某个行为--你只管调用行为就好,剩下的一切多态会搞定 换句话说就是:多态的最根本作用就是把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句 例子:假设有一个地图应用,每个地图API提供商都提供了show方法,负责在页面上显示地图,首先我们用一些分支条件语句来实现一个调用方法renderMap 此时一旦需要增加搜搜地图的应用,

JavaScript设计模式与开发实践---读书笔记(5) 策略模式

策略模式的定义是:定义一系列的算法,把它们一个个封转起来,并且使它们可以相互替换. JavaScript版本的策略模式: 奖金系统: var strategies = { "S": function(salary){ return salary*4; }, "A": function(salary){ return salary*3; }, "B": function(salary){ return salary*2; } }; var calc

JavaScript设计模式与开发实践---读书笔记(8) 发布-订阅模式

发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知. 发布-订阅模式可以广泛应用于异步编程中,这是一种替代传递回调函数的方案. 可以取代对象之间硬编码的通知机制,一个对象不用再显式地调用另外一个对象的某个接口. 自定义事件 首先要指定好谁充当发布者: 然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者: 最后发布消息时,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数. 另外,我们还可以往回调函数里填入

JavaScript设计模式与开发实践---读书笔记(9) 命令模式

命令模式的用途: 命令模式是最简单和优雅的模式之一,命令模式中的命令(command)指的是一个执行某些特定事情的指令. 命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么.此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系. 命令模式的例子-菜单程序: <!DOCTYPE html> <html lang="en"> <head> <met

JavaScript设计模式与开发实践---读书笔记(10) 组合模式

组合模式就是用小的子对象来构建更大的对象,而这些小的子对象也许是由更小的"孙对象"构成的. 组合模式将对象组合成树形结构,以表示"部分-整体"的层次结构. 抽象类在组合模式中的作用: 组合模式最大的优点在于可以一致地对待组合对象和基本对象.这种透明性带来的便利,在静态类型语言中体现的尤为明显. JavaScript中实现组合模式的难点在于要保证组合对象和叶对象拥有同样的方法,这通常需要用鸭子类型的思想对它们进行接口检查. 透明性带来的安全问题: 组合模式的例子-扫描