《Javascript 设计模式与开发实践》学习笔记

  • 原型继承的未来
  • 第 2 章:this、call 和 apply
  • 第 3 章:闭包和高阶函数
  • 第 4 章:单例模式
  • 第 5 章:策略模式
  • 第 6 章:代理模式
  • 第 7 章:迭代器模式
  • 第 8 章:发布订阅模式
  • 第 9 章:命令模式
  • 第 10 章:组合模式
  • 第 11 章:模板方法模式
  • 第 12 章:享元模式(flyweight)
  • 第 13 章:职责链模式
  • 第 14 章:中介者模式
  • 第 15 章:装饰者模式
  • 第 16 章:状态模式
  • 第 17 章:适配器模式
  • 第 18 章:单一职责原则
  • 第 19 章:最少知识原则
  • 第 19 章:开放-封闭原则
  • 第 21 章:接口和面向接口编程
  • 第 22 章:代码重构
  • 第 1 章:面向对象的 Javascript

    静态类型

    优点:

    缺点:

    动态类型

    优点:

    缺点:

    鸭子类型

    多态

    封装

    javascript 中的原型继承

    js原型继承

    原型编程的基本原则:

    所有的数据都是对象:

    js 引入了两套类型机制:基本类型和对象类型。基本类型包括:undefined、number、boolean、string、function、object

    要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它:

    function Person() {
      this.name = name;
    };
    
    Person.prototype.getName = function() {
      return this.name;
    };
    
    var objectFactory = function() {
    
      // 从 Object.prototype 上克隆一个空的对象
      var obj = new Object(),
    
      // 取得外部传入的构造器,此例是 Person
      Constructor = [].shift.call(arguments); 
      obj._proto_ = Constructor.prototype; // 指向正确的原型
    }
    
    var a = objectFactory(Person, 'seven');
    
    console.log(a.name); // 输出  sven
    console.log(a.getName()); // 输出:sven
    console.log(Object.getPrototypeOf(a) === Person.prototype); // 输出: true
    
    

    对象会记住它的原型:

    如果对象无法响应某个请求,它会把这个请求委托给它自己的原型:

    原型继承的未来

    第 2 章:this、call 和 apply

    this

    js 的 this 总指向一个对象,具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境

    this 的指向:

    this 的指向可以分为 4 种情况:

    作为对象的方法调用:

    this 指向该对象

    作为普通函数调用:

    function func(){
    	"use strict"; // 启用严格模式
    	alert(this); // 输出: undefined
    }
    

    构造器调用:

    丢失的 this:

    以下代码会抛出异常,因为 getId 引用 document.getElementById ,调用 getId,此时就成了普通函数调用,函数内部的 this 指向了 widnow, 而不是原来的 document

        <div id="div1">我是一个 div</div>
        <script type="text/javascript">
        var getId = document.getElementById;
        getId('div1');
        </script>
    

    call 和 apply

    call 和 apply 的区别:

    # 使用 call  apply 如果第一个参数为 null, 函数内的 this 会指向默认的宿主对象浏览器你是 window
    
    var func = function(a, b, c){
    	alert( this ==== window); // 输出 true,如果启用严格模式,this 会是 null
    }
    
    func.apply(null, [1,2,3]);
    

    call 和 apply 的用途:

        Function.prototype.bind = function(context) {
            var self = this; // 保存原函数
            return function() { // 返回一个新的函数
                return self.apply(context, arguments); // 执行新的函数的时候,会把之前传入的 context 当做新函数体内的 this
            }
        }
    
        var obj = {
            name: 'seven'
        };
    
        var func = function() {
            console.log(this.name); // 输出 seven
        }.bind(obj);
    
        func();
    

    第 3 章:闭包和高阶函数

    js 设计之初参考了 LISP 的两大方言之一的 Scheme,引入了 Lambda 表达式、闭包、高阶函数等特性

    闭包

    变量的作用域:

    变量的生存周期:

    闭包的例子:

        var nodes = document.getElementsByTagName('div');
        var len = nodes.length;
        for (var i = 0; i < len; i++) {
            (function(i) {
                nodes[i].onclick = function() {
                    alert(i);
                }
            })(i);
        }
    

    闭包的更多作用:

      // var report = function(src) {
      //     var img = new Image();
      //     img.src = src;
      // }
    
      // img 变量用闭包封闭起来,可以解决请求丢失的问题:
        var report = (function() {
            var imgs = [];
            return function(src) {
                var img = new Image();
                imgs.push(img);
                img.src = src;
            }
        })();
    

    闭包和面向对象设计:

    闭包相关代码:

    var extend = function() {
        var value = 0;
        return {
          call: function() {
            value++;
            console.log(value);
          }
        }
      };
    
      var extend = extend();
      extend.call(); // 输出 1
      extend.call(); // 输出 1
      extend.call(); // 输出 1
    

    面向对象的写法:

    var extend = {
            value: 0,
            call: function() {
                this.value++;
                console.log(this.value);
            }
        }
    
        // var extend = extend();
        extend.call(); // 输出 1
        extend.call(); // 输出 1
        extend.call(); // 输出 1
    

    原型继承写法:

        var Extend = function() {
            this.value = 0;
        }
    
        Extend.prototype.call = function() {
            this.value++;
            console.log(this.value);
        }
    
        var extend = new Extend();
        extend.call(); // 输出 1
        extend.call(); // 输出 2
        extend.call(); // 输出 3
    

    闭包和内存管理:

    高阶函数

    高阶函数至少满足以下条件:

    单例模式:

     var getSingle = function(fn) {
            var ret;
            return function() {
                return ret || (ret = fn.apply(this, arguments));
            }
        }
    
        var getScript = getSingle(function(){
            return document.createElement('script');
        })
    
        var script1 = getScript();
        var script2 = getScript();
    
        console.log(script1 === script2);
    

    高阶函数实现 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); // 执行新函数,修正 this
                return ret;
            }
        }
    
        var func = function() {
            console.log(2);
        }
        func = func.before(function() {
            console.log(1)
        }).after(function() {
            console.log(3);
        });
    
        func();
    

    柯里化(currying):*

    
    var currying = function(fn){
        var args = [];
    
        return function(){
            if(arguments.length === 0){
                return fn.apply(this, args);
            }else{
                [].push.apply(args, arguments);
                return arguments.callee;
            }
        };
    
    };
    

    uncurrying:

    // uncurrying 实现
    Function.prototype.uncurrying = function() {
        var self = this;
        return function() {
            var obj = Array.prototype.shift.call(arguments);
            return self.apply(obj, arguments);
        };
    };
    

    函数节流:

    函数被频繁调用的场景:

    函数节流的代码实现:

    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){ // 如果定时器还在,说明前一次延迟执行还没有完成
                return false;
            }
    
            timer = setTimeout(function(){ // 延迟一段时间执行
                clearTimeout(timer);
                timer = null;
                _self.apply(__me, args);
            }, interval || 500);
        };
    };
    
    window.onresize = throttle(function(){
        console.log(1);
    }, 500);
    

    函数节流的原理:

    将即将被执行的函数用 setTimeout 延迟一段时间执行。如果延迟执行还没有完成,则忽略接下来的调用

    分时函数:

    通过定时调用 setInterval,可以将大量数据的处理分批进行

    var timeChunk = function(ary, fn, count) {
        var obj, t;
        
        var start = function() {
    
            //  Math.min(int a, int b)方法用法实例教程 - 此方法返回a和b中的较小的, 避免 count 大于 ary.length 的 bug
            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); // 分批执行的时间间隔,也可以用参数的方式
        }
    }
    

    惰性加载函数:

    addEvent 函数内有分支判断,第一次进入条件分支后,函数内部会重写这个函数

        // 方案 3,惰性载入函数方案
    
        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('hello');
        });
    
        addEvent(div, 'click', function(){
            alert('world');
        });
    
        addEvent(div, 'click', function(){
            alert('andrew');
        });
    

    第 4 章:单例模式

    单例模式定义: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。

    代理实现单例模式:

    <script type="text/javascript">
    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); // true
    </script>
    

    javascript 中的的单例模式:

    降低全局变量命名污染的方式:

    1. 使用命名空间:

    //  对象字面量命名空间
    var namespace1 = {
        a: function() {
            alert(1);
        },
        b: function() {
            alert(2);
        }
    };
    
    // 动态创建命名空间
    var Myapp = {};
    
    Myapp.namespace = function(name) {
        var parts = name.split('.');
        var current = Myapp;
        for (var i in parts) {
            if (!current[parts[i]]) {
                current[parts[i]] = {};
            }
            current = current[parts[i]];
        }
    };
    
    Myapp.namespace('event');
    Myapp.namespace('dom.style');
    
    console.dir(Myapp);
    
    // 上述代码等价于:
    var Myapp = {
        event: {},
        dom: {
            style: {}
        }
    }
    
    1. 使用闭包封装私有变量
    // 使用闭包封装私有变量
    var user = (function() {
        var _name = 'seven',
            _age = 29;
    
        return {
            getUserInfo: function() {
                return _name + '-' + _age;
            }
        }
    })();
    

    惰性单例:

    通用惰性单例示例代码:

    <button id="loginbtn">打开百度</button>
    <script type="text/javascript">
    var getSingle = function(fn) {
        var result;
        return function() {
            return result || (result = fn.apply(this, arguments));
        };
    };
    
    
    var createSingleIframe = getSingle(function(){
        var iframe = document.createElement('iframe');
        document.body.appendChild(iframe);
        return iframe;
    });
    
    document.getElementById('loginbtn').onclick = function() {
        var loginLayer = createSingleIframe();
        loginLayer.src = "http://baidu.com";
    }
    </script>
    

    第 5 章:策略模式

    策略模式的优缺点:

    优点:

    缺点:

    第 6 章:代理模式

    虚拟代理实现图片预加载:

        <script type="text/javascript">
        var myImage = (function() {
            var imgNode = document.createElement('img');
            document.body.appendChild(imgNode);
            return function(src) {
                imgNode.src = src;
            };
        })();
    
        // 没有改变或增加 myImage 的接口
        // 通过代理给系统增加了新的行为,符合开放封闭原则
        var proxyImage = (function() {
            var img = new Image;
            img.onload = function() {
                var src = this.src;
                setTimeout(function() {
                    myImage(src);
                }, 1000);
            }
    
            return function(src) {
                myImage("/images/loading.gif");
                img.src = src;
            };
    
        })();
    
        // proxyImage('http://ww1.sinaimg.cn/large/aeb19806gw1ermlpunhdej20rs15owon.jpg')
        proxyImage("http://bbsimg0.dahe.cn/Day_141111/1038_14484479_acc705d86c0bc0a.jpg");
    
        </script>
    

    第 7 章:迭代器模式

    内部迭代器:

    var each = function(ary, callback){
      for( var i = 0, l = ary.length; i < l; i++){
        callback.call(ary[i], i, ary[i]); // 把下标和元素当作参数传给 callback 参数
      }
    };
    

    外部迭代器:

    var Iterator = function(obj) {
        var current = 0;
    
        var next = function() {
            current += 1;
        };
    
        var isDone = function() {
            return current >= obj.length;
        };
    
        var getCurrItem = function() {
            return obj[current];
        };
    
        return {
            next: next,
            isDone: isDone,
            getCurrItem: getCurrItem
        }
    };
    

    第 8 章:发布订阅模式

    自定义事件:

    发布订阅模式的通用实现:

    var event = {
        clientList: [],
        listen: function(key, fn) {
            if (!this.clientList[key]) { // 如果还没有订阅过此类消息,给该类消息创建一个缓存列表
                this.clientList[key] = [];
            }
            this.clientList[key].push(fn); // 订阅的消息添加进消息缓存列表
        },
        trigger: function() {
            var key = Array.prototype.shift.call(arguments), // 取出消息类型
                fns = this.clientList[key]; //取出该类消息对应的回调函数集合
    
            if (!fns || fns.length === 0) { // 如果没有订阅该消息,则返回
                return false;
            }
    
            for (var i = 0, fn; fn = fns[i++];) {
                fn.apply(this, arguments); // arguments 是发布消息时带上的参数
            }
        }
    };
    
    // 给对象动态安装发布-订阅功能
    var installEvent = function(obj) {
        for (var i in event) {
            obj[i] = event[i];
        }
    };
    

    第 9 章:命令模式

    js 命令模式示例:

    var button1 = document.getElementById('button1');
    
    var setCommand = function(button, command) {
        button.onclick = function(){
            command.execute();
        }
    };
    
    var MenuBar = {
        refresh: function() {
            console.log('刷新菜单目录');
        }
    };
    
    var SubMenu = {
        add: function() {
            console.log('增加子菜单');
        },
        del: function() {
            console.log('删除子菜单');
        }
    };
    
    var RefreshMenuBarCommand = function(receiver) {
        return {
            execute: function() {
                receiver.refresh();
            }
        };
    };
    
    var refreshMenuBarCommand  = RefreshMenuBarCommand(MenuBar);
    setCommand(button1, refreshMenuBarCommand);
    

    第 10 章:组合模式

    组合模式示例代码:

    var closeDoorCommand = {
        execute: function() {
            console.log('关门');
        },
        add: funciton() {
            throw new Error('叶对象不能添加子节点');
        }
    };
    
    var openPcCommand = {
        execute: function() {
            console.log('开电脑');
        },
        add: funciton() {
            throw new Error('叶对象不能添加子节点');
        }
    };
    
    var openQQCommand = {
        execute: function() {
            console.log('登录 QQ');
        },
        add: funciton() {
            throw new Error('叶对象不能添加子节点');
        }
    };
    
    var MacroCommand = function() {
        return {
            commandsList: [],
            add: function(command) {
                this.commandsList.push(command)
            },
            execute: function() {
                for (var i = 0, command; command = this.commandsList[i++];) {
                    command.execute();
                }
            }
        }
    };
    
    var macroCommand = MacroCommand();
    macroCommand.add(closeDoorCommand);
    macroCommand.add(openPcCommand);
    macroCommand.add(openQQCommand);
    
    macroCommand.execute();
    

    第 11 章:模板方法模式

    抽象类:

    模板方法示例代码:

    var Beverage = function() {};
    
    Beverage.prototype.boilWater = function() {
        console.log('把水煮沸');
    };
    
    Beverage.prototype.brew = function() {}; // 空方法,应该由子类重写
    Beverage.prototype.pourInCup = function() {}; // 空方法,应该由子类重写
    Beverage.prototype.addCondiments = function() {}; // 空方法,应该由子类重写
    
    Beverage.prototype.customerWantsCondiments = function() {
        return true; // 默认需要调料
    };
    
    Beverage.prototype.init = function() {
        this.boilWater();
        this.brew();
        this.pourInCup();
        if (this.customerWantsCondiments()) {
            this.addCondiments();
        }
    };
    
    var Coffee = function() {};
    
    Coffee.prototype = new Beverage();
    
    Coffee.prototype.brew = function() {
        console.log('用沸水冲泡咖啡');
    };
    
    Coffee.prototype.pourInCup = function() {
        console.log('把咖啡倒进杯子');
    };
    
    Coffee.prototype.addCondiments = function() {
        console.log('加糖和牛奶');
    };
    
    Coffee.prototype.customerWantsCondiments = function() {
        return window.confirm('请问需要调料吗?');
    };
    
    
    var coffee = new Coffee();
    coffee.init();
    

    好莱坞原则:

    非继承代码示例:

    var Beverage = function(params) {
    
        var boilWater = function() {
            console.log('把水煮沸');
        };
    
        var brew = params.brew || function() {
            throw new Error('必须传递 brew 方法');
        };
    
        var pourInCup = params.pourInCup || function() {
            throw new Error('必须传递 pourInCup 方法');
        };
    
        var addCondiments = params.addCondiments || function() {
            throw new Error('必须传递 addCondiments 方法');
        };
    
        var customerWantsCondiments = params.customerWantsCondiments || function() {
            return true;
        };
    
        var F = function() {};
    
        F.prototype.init = function() {
            boilWater();
            brew();
            pourInCup();
            if (customerWantsCondiments()) {
                addCondiments();
            }
        };
    
        return F;
    };
    
    var Tea = Beverage({
        brew: function() {
            console.log('用沸水浸泡茶叶');
        },
        pourInCup: function() {
            console.log('把茶倒进杯子');
        },
        addCondiments: function() {
            console.log('加柠檬');
        },
        customerWantsCondiments: function(){
            return window.confirm('请问需要加调料吗?')
        }
    });
    
    var tea = new Tea();
    tea.init();
    

    第 12 章:享元模式(flyweight)

    享元模式代码示例:

    var Model = function(sex, underwear) {
        this.sex = sex;
        this.underwear = underwear;
    }
    
    Model.prototype.takePhoto = function() {
        console.log('sex=' + this.sex + ' underwear=' + this.underwear);
    };
    
    var maleModel = new Model('male'),
        femaleModel = new Model('female');
    
    for (var i = 1; i <= 50; i++) {
        maleModel.underwear = 'underwear' + i;
        maleModel.takePhoto();
    };
    
    for (var i = 1; i <= 50; i++) {
        femaleModel.underwear = 'underwear' + i;
        femaleModel.takePhoto();
    };
    

    内部状态与外部状态:

    对象池代码示例:

    var objectPoolFactory = function(createObjFn) {
        var objectPool = [];
    
        return {
            create: function() {
                var obj;
                // var obj = objectPool.length === 0 ?
                // createObjFn.apply(this, arguments) : objectPool.shift();
                if (objectPool.length === 0) {
                    obj = createObjFn.apply(this, arguments);
                } else {
                    obj = objectPool.shift();
                }
    
                return obj;
            },
            recover: function(obj) {
                objectPool.push(obj);
                console.log(objectPool.length);
            }
        }
    };
    
    var iframeFactory = objectPoolFactory(function() {
        var iframe = document.createElement('iframe');
        document.body.appendChild(iframe);
    
        iframe.onload = function() {
            iframe.onload = null; // 防止 iframe 重复加载的 bug
            iframeFactory.recover(iframe); // iframe 加载完成之后回收节点
        }
        return iframe;
    });
    
    var iframe1 = iframeFactory.create();
    iframe1.src = 'http://baidu.com';
    
    var iframe2 = iframeFactory.create();
    iframe2.src = 'http://qq.com';
    
    setTimeout(function() {
        var iframe3 = iframeFactory.create();
        iframe3.src = 'http://163.com';
    }, 3000);
    

    第 13 章:职责链模式

    职责链代码示例:

    
    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('手机库存不足');
        }
    };
    
    
    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); // 输出:手机库存不足
    

    职责链中的优缺点:

    AOP 实现职责链:

    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;
        }
    }; 
    

    第 14 章:中介者模式

    中介者模式

    现实的中介者:

    小结:

    第 15 章:装饰者模式

    js 版本的装饰者示例:

    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();
    

    第 16 章:状态模式

    有限状态机

    有限状态机的实践:

    第 17 章:适配器模式

    第 18 章:单一职责原则

    第 19 章:最少知识原则

    第 19 章:开放-封闭原则

    第 21 章:接口和面向接口编程

    第 22 章:代码重构