Javascript设计模式

基础概念——《JavaScript设计模式与开发实践》

  • 1.静态类型 类型固定
  • 2.动态类型 无需进行类型监测,变量被赋予某个值就可以具有某个类型
  • 3.鸭子类型 只关注他的行为。是面向接口编程的。不用借助类型的帮助。
  • 4.多态 同一个操作作用于不同的对象上,可以产生不同的解释和不同的执行结果
      - 把做什么和谁去做分离开来,消除类型直接的耦合性
      - 将过程化的条件分支语句转化为对象的多态性,从而消除分支语句
    
  • 5.封装——封装数据,封装实现,封装类型,封装变化——实现私有化privete和公共化public
  • 6.原型编程——面向对象

原型模式

  • 不关心对象的具体类型,找到一个对象,通过克隆来创建一个一模一样的对象——创建一个对象
  • clone方法——es5提供了Object。create方法,用来克隆对象
  • 基于原型链的委托机制

原型编程规范规则:

  • 1.所有数据都是对象
    • 除了undefined之外,一切都是对象,都存在包装类,对象都源于Object.prototype对象
  • 2.要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它
    • 通过var obj = new Object或var obj2 ={} 引擎会从Object.prototype上克隆一个对象出来
  • 3.对象记住它的原型
    • js对象具有proto的隐藏属性,默认指向构造器的原型对象
  • 4.如果对象无法相应某个请求,会把请求委托给自己的原型
    • 原型链查找

this与call/apply

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

this指向

  • 1.作为对象的方法调用,this指向该对象
  • 2.作为普通的函数调用,this总是指向全局对象, 在浏览器中全局对象为window,在nodejs中全局对象为global,严格模式下为undefined
  • 3.构造器调用,this通常指向返回的对象
  • 4.Function.prototype.call或Function.prototype.apply调用动态的绑定this到传入的第一个参数

call和apply的用途

  • 1.改变this的指向
  • 2.Function.prototype.bind =function(){}
    Function.prototype.bind =function(context,argument){
      var seelf = this;
      return function(){
          return self.apply(context,arguments)
      }
    }
    
  • 3.借用其他对象的方法:借用构造函数、对类数组甚至对象使用数组方法(对象本身可以存取属性,length属性可读写)

设计模式 Design pattern

  • 1.定义:是一套被反复使用,思想成熟,经过分类和无数实战设计经验总结的。
  • 2.目的:让系统代码可重用,可扩展,可解耦,易于理解且可靠
  • 3.最早被整理在Erich Gamma/Richard Hlem/Ralph Johnson/Jhon Vlissides(绰号四人帮:the Gang of Four简称Gof)

背景

JavaScript 特别灵活;随着Node.js以及html5和web2.0的兴起,JavaScript变得越来越受重视

设计原则

  • 1.【开闭原则】对扩展开放,对修改关闭
  • 2.【里氏转化原则】子类继承父类,单独调用完全可以运行
  • 3.【依赖倒转原则】引用一个对象,如果这个对象有底层类型,直接引用底层
  • 4.【接口隔离原则】每一个接口是一个角色,每个接口做一件事
  • 5.【合成/聚合复用原则】新的对象应使用一些已有的对象,使之成为新对象的一部分。
  • 6.【迪米特原则】一个对象应对其他对象有尽可能少的了解

单例模式 【用在系统间各种模式的通信协调上】

  • 1.定义:保证一个类只有一个实例
  • 2.实现的方法:先判断实例存在与否,如果存在就直接返回,如果不存在就创建了再返回。
  • 3.实现方法确保一个类只有一个实例对象
  • 4.单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象

单例模式的作用

  • 1.模块间的通信
  • 2.系统中某个类的对象只能存在一个
  • 3.保护自己的属性和方法

PS:注意

  • 1.注意this的使用——谁调用我,我就指向谁
  • 2.闭包容易造成内存泄漏,不需要的赶快干掉
  • 注意new的成本。(继承)new是有成本的
var a= "";
var a=new String();//这个是有成本的

解释

  • 1.单例模式只允许单次实例化——但同一个对象可以有许多实例
  • 2.单例模式阻止了客户端创建多个实例,在第一个对象被创建之后,返回自己的实例
  • 3.传统实现:保证一个类只有一个实例——先判断实例存在与否,如果存在就直接返回,如果不存在就创建了再返回
  • 4.单例作为一个命名空间,提供一个唯一的访问点来访问该对象
var printer = (function(){  
    var printerInstance;
    function create(){    //私有方法
        function print(){}
        function turnOn(){}
        return {
            print:print,
            turnOn:turnOn
        };
    }

    return {
        getInstance:function(){   //公开方法
            if(!printerInstance){
                printerInstance = create();
            }
            return printerInstance;
        }
    };
    function Singleton () {
    if(!printerInstance) {
      printerInstance = intialize();
    }
  }; 
})()


//生成打印机的实例
var officePrinter = printer.getInstance();

目标

//测试
var uni = new Universe()
var uni2 = new Universe()
uni=== uni2   //结果为true

单例分类

惰性单例——在需要的时候才创建对象实例

  • 通用的惰性单例
var getSingle = function(fn){
    var result;
    return function(){
        return result || (result = fn.apply(this.arguments));
    }
}

实现方法:

  • 1.使用全局变量来存储实例this
    • 1.1全局变量会污染命名空间
    • 1.2避免全局空间污染:
      • 1.使用命名空间 :减少全局变量的数量——使用对象字面量的方式
      • 2.使用闭包封装私有变量:把变量封装在闭包的内部,只留一些接口跟外界通信
  • 2.在构造函数的静态属性中缓存该实例this.

优点:简洁;类似Universe.instance的属性用来缓存该属性

缺点:instance属性是公开可访问的属性

function Universe(){
//判断是否已经存在Universe.instance 
    if(typeof Universe.instance ==="object"){
        return Universe.instance;
    }
    this.start_time = 0;
    this.bang ="Big"

    //缓存
    Universe.instance =this;    //Universe.instance是公开属性,存在被外界修改的可能
    //隐式返回
    return this    
}
  • 3.将该实例this封装到闭包

优点:【保证实例的私有性,并保证该实例不会被构造函数之外的代码替代】

缺点:带来了额外的闭包开销

//实现1——————————私有成员模式实现————————重写构造函数————————闭包
function Universe(){
    //缓存实例
    var instance = this;
    //正常添加属性
    this.start_time = 0;
    this.bang ="Big"

    //重写构造函数
    Universe = function(){
        return instance;
    }
}

//缺点:需要重写构造函数,且测试如下
Universe.prototype.nothing = true; //向原型添加属性
var uni = new Universe();

Universe.prototype.everything =true; //在创建初始化对象之后,再次向该原型添加属性
var uni2 = new Universe();

//uni.nothing 与 unin2.nothing 都存在为true
//uni.everything 与uni2.everything 都不存在 为undefined

uni.constructor.name //结果为Universe

uni.constructor === Universe//结果为false   uni,constructor指向原始的构造函数
//实现2 将构造函数的指针在函数内部进行处理
function Universe(){
    //缓存实例
    var instance;
    //重写构造函数
    Universe = function(){
        return instance;
    }

    Universe.prototype = this; //保留原型属性
    instance = new Universe(); //构造实例
    //重置构造函数指针
    instance.constructor =Universe;

    //加功能
    instance.start_time = 0;
    instance.bang = "Big"

    return instance;
}
//如上测试均成功
//uni.nothing && uni.everything && uni2.nothing && uni2.everything //结果为true
//将构造函数和实例包装在即时函数中
var Universe;
(function(){
    var instance;
    Universe = function Universe(){
        if(instance){ return instance; }
        instance = this;
        this.start_time =0;
        this.bang = "Big"
    }
})()
  • 5.使用对象字面量
    • 1.可以包含大量的属性和方法
    • 2.扩展加上私有成员和方法,使用闭包封装这些变量和函数声明,只暴露public成员和方法
    • 3.在使用的时候才初始化,另外构造一个函数来初始化这些代码
    • 4.最佳实践代码
      //只是单例
      var mySingleton = {
      property1:"",
      property2:"",
      method:function(){
      }
      }
      
//单例+公私分明
var mySingleton = function () {
    /* 这里声明私有变量和方法 */
    var privateVariable = 'something private';
    function showPrivate() {
        console.log(privateVariable);
    }
    /* 公有变量和方法(可以访问私有变量和方法) */
    return {
        publicMethod: function () {
            showPrivate();
        },
        publicVar: 'the public can see this!'
    };
};
var single = mySingleton();
single.publicMethod();  // 输出 'something private'
console.log(single.publicVar); // 输出 'the public can see this!'
//单例+ 公私分明+按需初始化
var Singleton = (function(){
    var instantiated;
    function init(){
        //定义单例代码
        return{
          publicMethod:function(){
          console.log('hello world');
          },
          publicProperty:'test'
        };
    }
    return {
        getInstance:function(){
            if(!instantiated){
                instantiated = init();
            }
            return instantiated;
        }
    };
})();
Singleton.getInstance().publicMethod();
//最佳实践的代码
var SingletonTester = (function(){
    //参数:传递给单例一个参数集合
    function Singleton(args){
         //设置args变量为接收的参数或者为空(如果没有提供的话)
        var args = args || {};
        //设置name参数
        this.name = 'SingletonTester';
        //设置pointX的值
        this.pointX = args.pointX || 6; //从接收的参数里获取,或者设置为默认值
        //设置pointY的值
        this.pointY = args.pointY || 10;
    }
    //实例容器
    var instance;
    var _static = {
        name: 'SingletonTester',
        //获取实例的方法
        //返回Singleton的实例
        getInstance: function (args) {
            if (instance === undefined) {
                instance = new Singleton(args);
            }
            return instance;
        }
    };
    return _static;
})();
var singletonTest = SingletonTester.getInstance({ pointX: 5 });
console.log(singletonTest.pointX); // 输出 5
  • 6.使用代理实现单例模式
var CreateDiv = function(html){
    this.html=html
    this.init()
}
CreateDiv.prototype.init = function(){
    var div =document.createElement('div');
    div.innerHTML = this.htmll
    document.body,appendChild(div);
} 

var ProxySingleCreateDiv = (function(){  //代理
    var instance;
    return function(){
        if(!instance){
            instance = new CreateDiv(html);
        }
        return instance;        
    }
})()

var a =new ProxySingleCreateDiv('seven1');
var b = new ProxySingleCreateDiv('sevent2');
alert(a==b)

观察者模式

  • 1.又称为发布订阅模式,定义了一种一对多的关系。让多个观察者同时监听某一个主题对象,对象的状态发生变化时就会通知所有的观察者对象,使他们能自动更新自己。
  • 2.模式的好处:
    • 支持简单的广播通信,自动通知所有已经订阅过的对象
    • 页面载入后目标对象很容易与观察者存在一种动态关联,增加了灵活性
    • 目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用
  • 3.被观察者可以为观察者设计订阅、退订及发布到观察者的功能。

Javascript对观察者模式的实现是通过回调来实现的。

  • 定义一个pubsub对象,内部3个方法:订阅/退订/发布
var pubsub = {};
(function (q) {
    var topics = {}, // 回调函数存放的数组
        subUid = -1;
    // 发布方法
    q.publish = function (topic, args) {   //args='hello world!' topics['example1']存在
        if (!topics[topic]) {
            return false;
        }
        setTimeout(function () {
            var subscribers = topics[topic],     //subscribe = topics['example1']
                len = subscribers ? subscribers.length : 0; //len=2
            while (len--) {
                subscribers[len].func(topic, args);      //subscribers[2]
            }
        }, 0);
        return true;
    };
    //订阅方法
    q.subscribe = function (topic, func) {    //topic = 'example1'; func=function(topics,data){ console.log(topics+':'+data);}
        if (!topics[topic]) {             //topics['example1'] =[]
            topics[topic] = [];
        }
        var token = (++subUid).toString();   //subUid=0  token="0"
        topics[topic].push({      // topics['example1'] = {token:token,func:function(topics,data){ console.log(topics+':'+data);}
            token: token,
            func: func
        });
        return token;      // return "0"
    };
    //退订方法
    q.unsubscribe = function (token) {
        for (var m in topics) {
            if (topics[m]) {
                for (var i = 0, j = topics[m].length; i < j; i++) {
                    if (topics[m][i].token === token) {
                        topics[m].splice(i, 1);
                        return token;
                    }
                }
            }
        }
        return false;
    };
} (pubsub));

//使用方法:来,订阅一个
pubsub.subscribe('example1', function (topics, data) {
    console.log(topics + ": " + data);
});     //return "0"
//发布通知
pubsub.publish('example1', 'hello world!');
pubsub.publish('example1', ['test', 'a', 'b', 'c']);
pubsub.publish('example1', [{ 'color': 'blue' }, { 'text': 'hello'}]);

利用原型的特性实现

function Observer(){
    this.fns=[];
}
Obeserver.prototype = {
    subscribe :function(fn){
        this.fns.push(fn);
    },
    unsubscribe:function(fn){
      this.fns = this.fns.filter(   //将不fn的元素取出来,再赋给this.fn,用来得到新的订阅单
          function(el){
              if(el !== fn){
                  return el;
              }
          }
      )
    },
    update:function(arg, thisObj){
        var scope=thisObj || window;
        this.fns.forEach(   //执行每一个观察者都执行的函数
            function(el){
                el.call(scope,arg);   //call的两个参数,以为this指向,二为传入的参数
                //让每个观察者都执行   ???如何理解
            }
        );
    }
};

//测试

var o=new Observer;
var f1 = function(data){
  console.log('函数1'+data);
};
var f2 = function(data){
    console.log('函数2'+data);
}
o.subscribe(f1);
o.subscribe(f2);
o.update('更新');
o.unsubscribe(f1);
o.update('更新测试');

定义一个通用函数,将该函数的功能用到需要观察者功能的对象上

/** delete obj.name 对象属性删除
 * delete obj;变量删除
 * delete 删除不了原型链中的变量
* */
//通用代码
var observer ={
    //订阅
    addSubscriber:function(callback){
        this.subscribers[this.subscribers.length] =callback;
    },
    //退订
    removeSubscriber:function(callback){
        for(var i=0;i<this.subscribers.length;i++){
            if(this.subscribers[i] === callback){
                delete(this.subscribers[i]);   //删除
            }
        }
    },
    //发布
    publish:function(what){
        for(var i=0;i<this.subscribers.length;i++){
            if(typeof  this.subscribers[i]==='function'){
                this.subscribers[i](what);
            }
        }
    },

    //将对象o具有观察者功能
    make:function(o){
        for(var i in this){
            o[i] =this[i];
            o.subscribers = [];
        }

    }
}

//订阅对象
var blogger = {
  recommend:function(id){
      var msg='杜杜推荐了帖子'+id;
      this.publish(msg);
  }
};

var user ={
    vote:function(id){
        var msg="有人投票了帖子Id为"+id;
        this.publish(msg);
    }
};
observer.make(blogger);
observer.make(user);

//订阅不同的回调函数,以便可以注册到不同的观察者对象里,也可以同时注册到多个观察者对里
var tom={
    read:function(what){
        console.log('Tom看到了如下信息'+what);
    }
}
var mm = {
    show:function(what){
        console.log('mm看到了如下消息'+what);
    }
};
//订阅
blogger.addSubscriber(tom.read); //将tom.read添加到blogger.subscribers数组里面
blogger.addSubscriber(mm.show);
blogger.recommend(123);//调用发布
//退订
blogger.removeSubscriber(mm.show);
blogger.recommend(456);
//另外一个对象的订阅
user.addSubscriber(mm.show);
user.vote(789); //调用发布

jQuery版的观察者 jQuery 1.7 版新增的on/off

(function($){
    var o =$({});
    $.subscribe = function(){
        o.on.apply(o,arguments);  //o.on是绑定函数 function(){}  apply是将this指向了o,然后传参数进去
    };
    $.unsubscribe=function(){
        o.off.apply(o,arguments);  //移除一个事件处理函数
    };
    $.publish = function(){
        o.trigger.apply(o,arguments);  //根据绑定到匹配元素的给定的事件类型执行所有的处理程序和行为
        //trigger 第一个参数是event事件??
    }
})(jQuery);

//回调函数
function handle(e,a,b,c){
    //e是事件对象,不需要关注
}

//订阅
$.subscribe("/some/topic",handle);

//发布
$.publish("/some/topic",["a","b","c"]);//输出abc
$.unsubscribe("/some/topic",handle);

//订阅
$.subscribe("some",function(e,a,b,c){
    console.log(a+b+c);
});

$.publish("/some/topic",["a","b","c"]);

//退订  
$.unsubscribe("/some/topic");
JSRUN前端笔记, 是针对前端工程师开放的一个笔记分享平台,是前端工程师记录重点、分享经验的一个笔记本。JSRUN前端采用的 MarkDown 语法 (极客专用语法), 这里属于IT工程师。