《Javascript设计模式与开发实践》--读书笔记

第 2 章 this call apply

bind()方法创建一个新的函数,在 bind()被调用时,这个新函数的 this 被 bind 的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。
bind()方法底层实现

Function.prototype.bind =
  Function.prototype.bind ||
  function () {
    var self = this;
    var rest1 = Array.prototype.slice.call(arguments);
    var context = rest1.shift(); // 获取第一个参数,this的指向
    return function () {
      var rest2 = Array.prototype.slice.call(arguments); // 获取其余参数
      return self.apply(context, rest1.concat(rest2)); // 将预设参数和其余参数一起传参
    };
  };
var food = {
  name: "汉堡",
  price: "5块钱",
  getPrice: function (place, name) {
    console.log(place + this.price + name);
  },
};
var getPrice1 = food.getPrice.bind({ name: "鸡腿", price: "7块钱" }, "肯打鸡 ");
getPrice1("jesse");

bind()的另一个最简单的用法是使一个函数拥有预设的初始参数。只要将这些参数(如果有的话)作为 bind()的参数写在 this 后面。当绑定函数被调用时,这些参数会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们后面

第 3 章 闭包和高阶函数

高阶函数的应用

函数柯里化

var curring = function(fn){
    var args = [];
    return  function(){
        if(arguments.length === 0){
            return fn.apply(this,args);
        }else{
            [].push.apply(args,arguments);
            return arguments.callee;
        }
    }
}
var cost = (function(){
    var money = 0;
    return function(){
        for(var i = 0;,l = arguments.length;i < l;i++){
            money += aruments[i];
        }
        return money;
    }
})()
var cost = curring(cost);
cost(100);
console.log(cost());

uncurring()

Function.prototype.uncurring = fucntion(){
    var self = this;
    return function(){
        var obj = Array.prototype.shift.call(arguments);
        return self.apply(obj,arguments);
    }
}
var push = Array.prototype.push.uncurring();
(function(){
    push(arguments,4);
    console.log(arguments); //[1,2,3,4]
})(1,2,3);

函数节流,用于解决函数频繁被调用而造成的性能问题

var throttle = function (fn, interval) {
  var _self = fn,
    timer,
    firstTime = true;
  return function () {
    var args = arguments,
      _me = this;
    if (firstTime) {
      _self.apply(_me, args);
      return (firstTime = false);
    }
    if (timer) {
      //500毫秒之内再次触发的缩放事件不处理
      return false;
    }
    timer = setTimeout(function () {
      clearTimeout(timer);
      timer = null;
      _self.apply(_me, args);
    }, interval || 500);
  };
};
window.onresize = throttle(function () {
  console.log(1);
}, 500);

惰性加载函数

var addEvent = function (elem, type, handler) {
  if (window.addEventListener) {
    addEvent = function (elem, type, handler) {
      //重写函数,避免频繁调用嗅探函数
      elem.addEventListener(type, handler, false);
    };
  } else if (window.attachEvent) {
    addEvent = function (elem, type, handler) {
      elem.attachEvent("on" + type, handler);
    };
  }
  addEvent(elem, type, handler);
};

var div = document.getElementById("div1");
addEvent(div, "click", function () {
  alert(1);
});
addEvent(div, "click", function () {
  alert(2);
});

第 4 章 单例模式

单例模式的核心是确保只有一个实例,并提供全局访问。
该模式可用于定义单一弹窗

var CreateDiv = function (html) {
  this.html = html;
  this.init();
};
CreateDiv.prototype.init = function () {
  var div = document.createElement("div");
  div.innerHTML = this.html;
  document.body.appendChild(div);
};
//将创建对象和保证单一对象分开
var ProxySingletonCreateDiv = (function () {
  var instance;
  return function (html) {
    if (!instance) {
      instance = new CreateDiv(html);
    }
    return instance;
  };
})();

var a = new ProxySingletonCreateDiv("sven1");
var b = new ProxySingletonCreateDiv("sven2");
alert(a === b);

ES6 写法

let instance;
class CreateDiv {
  constructor(html) {
    if (instance) {
      return instance;
    }
    this.html = html;
    this.init();
    return (instance = this);
  }

  init() {
    var div = document.createElement("div");
    div.innerHTML = this.html;
    document.body.appendChild(div);
  }
}
var a = new CreateDiv("sven1");
var b = new CreateDiv("sven2");
console.log(a === b); // true

第 5 章 策略模式

策略模式指的是定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换
该模式常用于表单校验

var strategies = {
  S: function (salary) {
    return salary * 4;
  },
  A: function (salary) {
    return salary * 3;
  },
  B: function (salary) {
    return salary * 2;
  },
};
var calculateBonus = function (level, salary) {
  return strategies[level](salary);
};

console.log(calculateBonus("S", 20000)); // 输出:80000
console.log(calculateBonus("A", 10000)); // 输出:30000

5.6.2 用策略模式重构表单校验

/***********************策略对象**************************/
var strategies = {
  isNonEmpty: function (value, errorMsg) {
    if (value === "") {
      return errorMsg;
    }
  },
  minLength: function (value, length, errorMsg) {
    if (value.length < length) {
      return errorMsg;
    }
  },
  isMobile: function (value, errorMsg) {
    if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
      return errorMsg;
    }
  },
};
/***********************Validator 类**************************/
var Validator = function () {
  this.cache = [];
};
Validator.prototype.add = function (dom, rules) {
  var self = this;
  for (var i = 0, rule; (rule = rules[i++]); ) {
    (function (rule) {
      var strategyAry = rule.strategy.split(":");
      var errorMsg = rule.errorMsg;
      self.cache.push(function () {
        var strategy = strategyAry.shift();
        strategyAry.unshift(dom.value);
        strategyAry.push(errorMsg);
        return strategies[strategy].apply(dom, strategyAry);
      });
    })(rule);
  }
};
Validator.prototype.start = function () {
  for (var i = 0, validatorFunc; (validatorFunc = this.cache[i++]); ) {
    var errorMsg = validatorFunc();
    if (errorMsg) {
      return errorMsg;
    }
  }
};
/***********************客户调用代码**************************/
var registerForm = document.getElementById("registerForm");
var validataFunc = function () {
  var validator = new Validator();
  validator.add(registerForm.userName, [
    {
      strategy: "isNonEmpty",
      errorMsg: "用户名不能为空",
    },
    {
      strategy: "minLength:6",
      errorMsg: "用户名长度不能小于10 位",
    },
  ]);
  validator.add(registerForm.password, [
    {
      strategy: "minLength:6",
      errorMsg: "密码长度不能小于6 位",
    },
  ]);
  var errorMsg = validator.start();
  return errorMsg;
};
registerForm.onsubmit = function () {
  var errorMsg = validataFunc();
  if (errorMsg) {
    alert(errorMsg);
    return false;
  }
};

第 6 章 代理模式

虚拟代理在惰性加载中的应用

var miniConsole = (function(){
    var cache = [];
    var handler = function( ev ){
        if ( ev.keyCode === 113 ){
            var script = document.createElement( 'script' );
            script.onload = function(){
                for ( var i = 0, fn; fn = cache[ i++ ]; ){
                    fn();
                }
            };
            script.src = 'miniConsole.js';
            document.getElementsByTagName( 'head' )[0].appendChild( script );
            document.body.removeEventListener( 'keydown', handler );// 只加载一次miniConsole.js
        }
    };
    document.body.addEventListener( 'keydown', handler, false );
    return {
        log: function(){
            var args = arguments;
            cache.push( function(){
                return miniConsole.log.apply( miniConsole, args );
            });
        }
    }
})();

miniConsole.log( 11 ); // 开始打印log
// miniConsole.js 代码
miniConsole = {
    log: function(){
    // 真正代码略
    console.log( Array.prototype.join.call( arguments ) );
}

缓存代理

var mult = function(){
    console.log( '开始计算乘积' );
    var a = 1;
    for ( var i = 0, l = arguments.length; i < l; i++ ){
        a = a * arguments[i];
    }
    return a;
};
mult( 2, 3 ); // 输出:6
mult( 2, 3, 4 ); // 输出:24
//现在加入缓存代理函数:
var proxyMult = (function(){
    var cache = {};
    return function(){
        var args = Array.prototype.join.call( arguments, ',' );
        if ( args in cache ){
            return cache[ args ];
        }
        return cache[ args ] = mult.apply( this, arguments );
    }
})();

proxyMult( 1, 2, 3, 4 ); // 输出:24
proxyMult( 1, 2, 3, 4 ); // 输出:24
我们在常常在项目中遇到分页的需求,同一页的数据理论上只需要去后台拉取一次,这些已经拉取到的数据在某个地方被缓存之后,下次再请求同一页的时候,便可以直接使用之前的数据。

第 7 章 迭代器模式

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各种元素,而又不需要暴露该对象的内部表示

var getActiveUploadObj = function () {
  //...
};
var getFlashUploadObj = function () {
  //...
};
var getFormUploadObj = function () {
  //...
};
var iteratorUploadObj = function () {
  for (var i = 0, fn; (fn = arguments[i++]); ) {
    var uploadObj = fn();
    if (uploadObj !== false) {
      return uploadObj;
    }
  }
};
var uploadObj = iteratorUploadObj(
  getActiveUploadObj,
  getFlashUploadObj,
  getFormUploadObj
);

第 8 章 发布订阅模式

发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状 态发生改变时,所有依赖于它的对象都将得到通知。在 JavaScript 开发中,我们一般用事件模型 来替代传统的发布—订阅模式。

var Event = (function () {
  var clientList = {},
    listen,
    trigger,
    remove;
  listen = function (key, fn) {
    if (!clientList[key]) {
      clientList[key] = [];
    }
    clientList[key].push(fn);
  };
  trigger = function () {
    var key = Array.prototype.shift.call(arguments),
      fns = clientList[key];
    if (!fns || fns.length === 0) {
      return false;
    }
    for (var i = 0, fn; (fn = fns[i++]); ) {
      fn.apply(this, arguments);
    }
  };
  remove = function (key, fn) {
    var fns = clientList[key];
    if (!fns) {
      return false;
    }
    if (!fn) {
      fns && (fns.length = 0);
    } else {
      for (var l = fns.length - 1; l >= 0; l--) {
        var _fn = fns[l];
        if (_fn === fn) {
          fns.splice(l, 1);
        }
      }
    }
  };
  return {
    listen: listen,
    trigger: trigger,
    remove: remove,
  };
})();

Event.listen("squareMeter88", function (price) {
  // 小红订阅消息
  console.log("价格= " + price); // 输出:'价格=2000000'
});

Event.trigger("squareMeter88", 2000000); // 售楼处发布消息

第 9 章 命令模式

在面向对象设计中,命令模式的接收者被当成 command 对象的属性保存起来,同时约定执行命令的操作调用 command.execute 方法。在使用闭包的命令模式实现中,接收者被封闭在闭包产生的环境中,执行命令的操作可以更加简单,仅仅执行回调函数即可。无论接收者被保存为对象的属性,还是被封闭在闭包产生的环境中,在将来执行命令的时候,接收者都能被顺利访问。用闭包实现的命令模式如下代码所示:

var RefreshMenuBarCommand = function (receiver) {
  return {
    execute: function () {
      receiver.refresh();
    },
  };
};
var setCommand = function (button, command) {
  button.onclick = function () {
    command.execute();
  };
};
var refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar);
setCommand(button1, refreshMenuBarCommand);

第 10 章 组合模式

10.7 组合模式的例子——扫描文件夹

var Folder = function (name) {
  this.name = name;
  this.files = [];
};
Folder.prototype.add = function (file) {
  this.files.push(file);
};
Folder.prototype.scan = function () {
  console.log("开始扫描文件夹: " + this.name);
  for (var i = 0, file, files = this.files; (file = files[i++]); ) {
    file.scan();
  }
};
/******************************* File ******************************/
var File = function (name) {
  this.name = name;
};
File.prototype.add = function () {
  throw new Error("文件下面不能再添加文件");
};
File.prototype.scan = function () {
  console.log("开始扫描文件: " + this.name);
};

var folder = new Folder("学习资料");
var folder1 = new Folder("JavaScript");
var folder2 = new Folder("jQuery");
var file1 = new File("JavaScript 设计模式与开发实践");
var file2 = new File("精通jQuery");
var file3 = new File("重构与模式");
folder1.add(file1);
folder2.add(file2);
folder.add(folder1);
folder.add(folder2);
folder.add(file3);

var folder3 = new Folder("Nodejs");
var file4 = new File("深入浅出Node.js");
folder3.add(file4);
var file5 = new File("JavaScript 语言精髓与编程实践");

folder.add(folder3);
folder.add(file5);

folder.scan();

10.8 一些值得注意的地方

  1. 组合模式不是父子关系 组合对象把请求委托给它所包含的所有叶对象,它们能够合作的关键是拥有相同的接口。
  2. 对叶对象操作的一致性
  3. 双向映射关系

第 11 章 模板方法模式

var Beverage = function () {};
Beverage.prototype.boilWater = function () {
  console.log("把水煮沸");
};
Beverage.prototype.brew = function () {
  throw new Error("子类必须重写brew 方法");
};
Beverage.prototype.pourInCup = function () {
  throw new Error("子类必须重写pourInCup 方法");
};
Beverage.prototype.addCondiments = function () {
  throw new Error("子类必须重写addCondiments 方法");
};
Beverage.prototype.customerWantsCondiments = function () {
  return true; // 默认需要调料
};
Beverage.prototype.init = function () {
  //封装了子类的算法框架
  this.boilWater();
  this.brew();
  this.pourInCup();
  if (this.customerWantsCondiments()) {
    // 如果挂钩返回true,则需要调料
    this.addCondiments();
  }
};
var CoffeeWithHook = function () {};
CoffeeWithHook.prototype = new Beverage();
CoffeeWithHook.prototype.brew = function () {
  console.log("用沸水冲泡咖啡");
};
CoffeeWithHook.prototype.pourInCup = function () {
  console.log("把咖啡倒进杯子");
};
CoffeeWithHook.prototype.addCondiments = function () {
  console.log("加糖和牛奶");
};
CoffeeWithHook.prototype.customerWantsCondiments = function () {
  return window.confirm("请问需要调料吗?");
};
var coffeeWithHook = new CoffeeWithHook();
coffeeWithHook.init();

模板方法模式是基于继承的一种设计模式,父类封装了子类的算法框架和方法的执行顺序,
子类继承父类之后,父类通知子类执行这些方法

第 12 章 享元模式

var Upload = function (uploadType) {
  this.uploadType = uploadType;
};

Upload.prototype.delFile = function (id) {
  uploadManager.setExternalState(id, this); // (1)
  if (this.fileSize < 3000) {
    return this.dom.parentNode.removeChild(this.dom);
  }

  if (window.confirm("确定要删除该文件吗? " + this.fileName)) {
    return this.dom.parentNode.removeChild(this.dom);
  }
};

var UploadFactory = (function () {
  var createdFlyWeightObjs = {};
  return {
    create: function (uploadType) {
      if (createdFlyWeightObjs[uploadType]) {
        return createdFlyWeightObjs[uploadType];
      }
      return (createdFlyWeightObjs[uploadType] = new Upload(uploadType));
    },
  };
})();

var uploadManager = (function () {
  var uploadDatabase = {};
  return {
    add: function (id, uploadType, fileName, fileSize) {
      var flyWeightObj = UploadFactory.create(uploadType);
      var dom = document.createElement("div");
      dom.innerHTML =
        "<span>文件名称:" +
        fileName +
        ", 文件大小: " +
        fileSize +
        "</span>" +
        '<button class="delFile">删除</button>';
      dom.querySelector(".delFile").onclick = function () {
        flyWeightObj.delFile(id);
      };

      document.body.appendChild(dom);
      uploadDatabase[id] = {
        fileName: fileName,
        fileSize: fileSize,
        dom: dom,
      };
      return flyWeightObj;
    },
    setExternalState: function (id, flyWeightObj) {
      var uploadData = uploadDatabase[id];
      for (var i in uploadData) {
        flyWeightObj[i] = uploadData[i];
      }
    },
  };
})();

var id = 0;
window.startUpload = function (uploadType, files) {
  for (var i = 0, file; (file = files[i++]); ) {
    var uploadObj = uploadManager.add(
      ++id,
      uploadType,
      file.fileName,
      file.fileSize
    );
  }
};

startUpload("plugin", [
  {
    fileName: "1.txt",
    fileSize: 1000,
  },
  {
    fileName: "2.html",
    fileSize: 3000,
  },
  {
    fileName: "3.txt",
    fileSize: 5000,
  },
]);
startUpload("flash", [
  {
    fileName: "4.txt",
    fileSize: 1000,
  },
  {
    fileName: "5.html",
    fileSize: 3000,
  },
  {
    fileName: "6.txt",

    fileSize: 5000,
  },
]);

享元模式带来的好处很大程度上取决于如何使用以及何时使用,一般来说,以下情况发生时 便可以使用享元模式。

  1. 一个程序中使用了大量的相似对象。
  2. 由于使用了大量对象,造成很大的内存开销。
  3. 对象的大多数状态都可以变为外部状态。
  4. 剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象。

第 13 章 职责链模式

职责链模式的最大优点就是解耦了请求发送者和 N 个接收者之间的复杂关 系,由于不知道链中的哪个节点可以处理你发出的请求,所以你只需把请求传递给第一个节点即 可

var order500 = function( orderType, pay, stock ){
    if ( orderType === 1 && pay === true ){
        console.log( '500 元定金预购,得到100 优惠券' );
    }else{
        return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
    }
};

var order200 = function( orderType, pay, stock ){
    if ( orderType === 2 && pay === true ){
        console.log( '200 元定金预购,得到50 优惠券' );
    }else{
        return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
    }
};

var orderNormal = function( orderType, pay, stock ){
    if ( stock > 0 ){
        console.log( '普通购买,无优惠券' );
    }else{
        console.log( '手机库存不足' );
    }
};

Chain.prototype.setNextSuccessor 指定在链中的下一个节点
Chain.prototype.passRequest 传递请求给某个节点
var Chain = function( fn ){
    this.fn = fn;
    this.successor = null;
};

Chain.prototype.setNextSuccessor = function( successor ){
    return this.successor = successor;
};

Chain.prototype.passRequest = function(){

    var ret = this.fn.apply( this, arguments );
    if ( ret === 'nextSuccessor' ){
        return this.successor && this.successor.passRequest.apply( this.successor, arguments );
    }
    return ret;
};

var chainOrder500 = new Chain( order500 );
var chainOrder200 = new Chain( order200 );
var chainOrderNormal = new Chain( orderNormal );

chainOrder500.setNextSuccessor( chainOrder200 );
chainOrder200.setNextSuccessor( chainOrderNormal );
chainOrder500.passRequest( 1, true, 500 ); // 输出:500 元定金预购,得到100 优惠券
chainOrder500.passRequest( 2, true, 500 ); // 输出:200 元定金预购,得到50 优惠券
chainOrder500.passRequest( 3, true, 500 ); // 输出:普通购买,无优惠券
chainOrder500.passRequest( 1, false, 0 ); // 输出:手机库存不足

Function.prototype.after = function( fn ){
    var self = this;
    return function(){
        var ret = self.apply( this, arguments );
        if ( ret === 'nextSuccessor' ){
            return fn.apply( this, arguments );
        }
        return ret;
    }
};

var order = order500yuan.after( order200yuan ).after( orderNormal );
order( 1, true, 500 ); // 输出:500 元定金预购,得到100 优惠券
order( 2, true, 500 ); // 输出:200 元定金预购,得到50 优惠券
order( 1, false, 500 ); // 输出:普通购买,无优惠券

第 14 章 中介者模式

中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。

var goods = {
  // 手机库存
  "red|32G": 3,
  "red|16G": 0,
  "blue|32G": 1,
  "blue|16G": 6,
};
var colorSelect = document.getElementById("colorSelect"),
  memorySelect = document.getElementById("memorySelect"),
  numberInput = document.getElementById("numberInput"),
  colorInfo = document.getElementById("colorInfo"),
  memoryInfo = document.getElementById("memoryInfo"),
  numberInfo = document.getElementById("numberInfo"),
  nextBtn = document.getElementById("nextBtn");
var mediator = (function () {
  return {
    changed: function (obj) {
      var color = colorSelect.value, // 颜色
        memory = memorySelect.value, // 内存
        number = numberInput.value, // 数量
        stock = goods[color + "|" + memory]; // 颜色和内存对应的手机库存数量
      if (obj === colorSelect) {
        // 如果改变的是选择颜色下拉框
        colorInfo.innerHTML = color;
      } else if (obj === memorySelect) {
        memoryInfo.innerHTML = memory;
      } else if (obj === numberInput) {
        numberInfo.innerHTML = number;
      }
      if (!color) {
        nextBtn.disabled = true;
        nextBtn.innerHTML = "请选择手机颜色";
        return;
      }
      if (!memory) {
        nextBtn.disabled = true;
        nextBtn.innerHTML = "请选择内存大小";
        return;
      }
      if (((number - 0) | 0) !== number - 0) {
        // 输入购买数量是否为正整数
        nextBtn.disabled = true;
        nextBtn.innerHTML = "请输入正确的购买数量";
        return;
      }
      nextBtn.disabled = false;
      nextBtn.innerHTML = "放入购物车";
    },
  };
})();
// 事件函数:
colorSelect.onchange = function () {
  mediator.changed(this);
};
memorySelect.onchange = function () {
  mediator.changed(this);
};
numberInput.oninput = function () {
  mediator.changed(this);
};

第 15 章 装饰者模式

因为装饰者对象和它所装饰的对象拥有一致的接口,所以它们对使用该对象的客户来说是透 明的,被装饰的对象也并不需要了解它曾经被装饰过,这种透明性使得我们可以递归地嵌套任意 多个装饰者对象

var plane = {
  fire: function () {
    console.log("发射普通子弹");
  },
};
var missileDecorator = function () {
  console.log("发射导弹");
};
var atomDecorator = function () {
  console.log("发射原子弹");
};
var fire1 = plane.fire;
plane.fire = function () {
  fire1();
  missileDecorator();
};
var fire2 = plane.fire;
plane.fire = function () {
  fire2();
  atomDecorator();
};
plane.fire();
// 分别输出: 发射普通子弹、发射导弹、发射原子弹

用 AOP 装饰函数

Function.prototype.before = function (beforefn) {
  var __self = this; // 保存原函数的引用
  return function () {
    // 返回包含了原函数和新函数的"代理"函数
    beforefn.apply(this, arguments); // 执行新函数,且保证 this 不被劫持,新函数接受的参数 // 也会被原封不动地传入原函数,新函数在原函数之前执行
    return __self.apply(this, arguments); // 执行原函数并返回原函数的执行结果, 2 // 并且保证 this 不被劫持
  };
};
Function.prototype.after = function (afterfn) {
  var __self = this;
  return function () {
    var ret = __self.apply(this, arguments);
    afterfn.apply(this, arguments);
    return ret;
  };
};
document.getElementById = document.getElementById.before(function () {
  alert(1);
});
var button = document.getElementById("button");

不喜欢这种污染原型的方式,那么我们可以做一些变通

var before = function (fn, beforefn) {
  return function () {
    beforefn.apply(this, arguments);
    return fn.apply(this, arguments);
  };
};
var a = before(
  function () {
    alert(3);
  },
  function () {
    alert(2);
  }
);
a = before(a, function () {
  alert(1);
});
a();

第 16 章 状态模式

状态模式的关键是把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部

javascript 版的状态机

var Light = function () {
  this.currState = FSM.off; // 设置当前状态
  this.button = null;
};

Light.prototype.init = function () {
  var button = document.createElement("button"),
    self = this;
  button.innerHTML = "已关灯";
  this.button = document.body.appendChild(button);
  this.button.onclick = function () {
    self.currState.buttonWasPressed.call(self); // 把请求委托给FSM 状态机
  };
};
var FSM = {
  off: {
    buttonWasPressed: function () {
      console.log("关灯");
      this.button.innerHTML = "下一次按我是开灯";
      this.currState = FSM.on;
    },
  },
  on: {
    buttonWasPressed: function () {
      console.log("开灯");
      this.button.innerHTML = "下一次按我是关灯";
      this.currState = FSM.off;
    },
  },
};
var light = new Light();
light.init();

第 17 章 适配器模式

适配器模式的作用是解决两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体可以一起工作。

var guangdongCity = {
  shenzhen: 11,
  guangzhou: 12,
  zhuhai: 13,
};
var getGuangdongCity = function () {
  var guangdongCity = [
    {
      name: "shenzhen",
      id: 11,
    },
    {
      name: "guangzhou",
      id: 12,
    },
  ];
  return guangdongCity;
};
var render = function (fn) {
  console.log("开始渲染广东省地图");
  document.write(JSON.stringify(fn()));
};
var addressAdapter = function (oldAddressfn) {
  var address = {},
    oldAddress = oldAddressfn();
  for (var i = 0, c; (c = oldAddress[i++]); ) {
    address[c.name] = c.id;
  }
  return function () {
    return address;
  };
};
render(addressAdapter(getGuangdongCity));
  1. 适配器模式主要用来解决两个已有接口之间不匹配的问题,它不考虑这些接口是怎样实现的,也不考虑它们将来可能会如何演化。适配器模式不需要改变已有的接口,就能够使它们协同作用。
  2. 装饰者模式和代理模式也不会改变原有对象的接口,但装饰者模式的作用是为了给对象增加功能。装饰者模式常常形成一条长的装饰链,而适配器模式通常只包装一次。代理模式是为了控制对对象的访问,通常也只包装一次。
  3. 外观模式的作用倒是和适配器比较相似,有人把外观模式看成一组对象的适配器,但外观模式最显著的特点是定义了一个新的接口。

第 18 章 单一职责原则

SRP 原则体现为:一个对象(方法)只做一件事情

第 19 章 最少知识原则

最少知识原则要求我们在设计程序时,应当尽量减少对象之间的交互。如果两个对象之间不必彼此直接通信,那么这两个对象就不要发生直接的相互联系。常见的做法是引入一个第三者对象,来承担这些对象之间的通信作用。如果一些对象需要向另一些对象发起请求,可以通过第三 者对象来转发这些请求。

第 20 章 开放封闭原则

当需要改变一个程序的功能或者给这个程序增加新功能的时候,可以使用增加代码的方式,但是不允许改动程序的源代码
通过封装变化的方式,可以把系统中稳定不变的部分和容易变化的部分隔离开来。在系统的 演变过程中,我们只需要替换那些容易变化的部分

第 22 章 代码重构

  1. 提炼函数
  2. 合并重复的条件判断
  3. 把条件分支语句提炼成函数
  4. 合理使用循环
  5. 提前让函数退出代替嵌套条件分支
  6. 传递对象参数代替过长的参数列表
  7. 尽量减少参数数量
  8. 少用三目运算符
  9. 合理使用链式调用
  10. 分解大型类
  11. 用 return 退出多重循环