欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JS中的几种设计模式举例说明

程序员文章站 2022-10-11 07:58:21
什么是设计模式? 设计模式是解决实际问题的一种固定思路,这些思路通常是从实际经验中总结而来。大部分问题的解决之道是有相同模式的,这些模式总结出来就是我们通常所讲的设计模式。 工厂模式 js中创建对象...

什么是设计模式?

设计模式是解决实际问题的一种固定思路,这些思路通常是从实际经验中总结而来。大部分问题的解决之道是有相同模式的,这些模式总结出来就是我们通常所讲的设计模式。

工厂模式

js中创建对象的一种模式,可以理解为批量生产具有类似属性和行为的对象。

// again factory pattern
function factory(name){
    var o = new object();
    o.name = name;
    o.type = 'factory';
    o.can = function(){
        console.log('my name is ' + this.name);
    }
}
var o1 = factory('o1');
var o2 = factory('o2');

o1.can(); // my name is o1
o2.can(); // my name is o2

工厂模式具有问题就是不知道对象是那个工厂函数创建的。

看一下下面的代码,复杂的工厂模式

function factory(){
    this.name = 'factory';
    this.can = function(){
        console.log('i can produce the product');
    }
}
factory.prototype.produce = function(){
    // 下面实现子类中重复性的业务
    console.log('i start work at 8 clock');
    // throw new error('fn: produce has to be define by sub');
}

function extend(sub, sup){
    sub.prototype = new sup();
    sub.prototype.constructor = sub;
    sub.prototype.sup = sup.prototype;
}

// another extend func (just inherit the sup's prototype)
function extend2(sub, sup){
    var f = function(){};
    f.prototype = sup.prototype;

    sub.prototype = new f(); //  为什么要借助一个中间函数f
    sub.prototype.constructor = sub;
    sub.sup = sup.prototype;
}

function carfactory(){
    this.type = 'car';
    factory.call(this);
}
carfactory.prototype.produce=function(){
    phonefactory.sup.produce.call(this);
    console.log('i produce the car');
}


function phonefactory(){
    this.type = 'phone';
    factory.call(this);
}

extend2(phonefactory, factory);
phonefactory.prototype.produce=function(){
    phonefactory.sup.produce.call(this);
    console.log('i produce the phone');
}
var phone = new phonefactory();
var sub = new carfactory();

单体模式

单体模式的特点使实例只会被实例化一次。虽然,对象字面量也可以看做是实现单体模式的一种方式,但它不能实例化。

function singlepattern(name){
    if(singlepattern.instance){
        return singlepattern.instance;
    }
    this.name = name;
    this.say = function(){
        console.log(this.name);
    }
    singlepattern.instance = this;
}

var a = new singlepattern('aaaaa');
var b = new singlepattern('bbbbb');

a.say(); // aaaaa
b.say(); // aaaaa
a === b; // true  因为单体模式下,只能被实例化一次,当第二次new的时候,返回的是第一次生成的实例

???那么问题来了,页面弹窗是否可以考虑使用单体模式?

单体模式的高级写法:

var getsingleinstance = function(fn){
    var o = null;

    return function(){
        return o || (o = fn.apply(this, arguments));
    }
}

// 创建一个弹窗实例的方法
function createdlg(html){
    console.log(arguments);
    var p = document.createelement('p');
    p.innerhtml = html;
    p.style.display = 'none';
    return p;
}
// 创建iframe实例的方法
function createiframe(html){    
    var iframe = document.createelement('iframe');
    iframe.innerhtml = html;
    iframe.style.display = 'none';
    return iframe;
}

var createsingedlg = getsingleinstance(createdlg);
var createsingeiframe = getsingleinstance(createiframe);

var dlg1 = createsingedlg( 'dlg1');
var dlg2 = createsingedlg( 'dlg2');
var iframe1 = createsingeiframe('iframe1');
var iframe2 = createsingeiframe('iframe2');

console.log(dlg1.innerhtml); // dlg1
console.log(dlg2.innerhtml); // dlg1
console.log(iframe1.innerhtml); // iframe1
console.log(iframe2.innerhtml); // iframe1

代码示例中最终输出结果证明了只有一个实例被生成

模块模式

还没弄懂这种模式的使用场景,留个坑先

代理模式

保护机制: 过滤掉一些不合理的行为;下面这个例子中,from想送礼物给to, 但是to对于价格低于200的礼物不会接受,这时候它就找了个proxy,帮它甄别这些礼物。

var to = function(){
    this.received = function(gift){ console.log("i have received the " + gift); }
}

var from = function(price, name){
    this.gift={
        price: price,
        name: name
    }
    this.sendgift = function(proxy){
        console.log('i send a gift, and its price is:' + this.gift.price);
        proxy.sendgift(this.gift);
    }
}

var proxy=function(){
    this.sendgift = function(gift){

        if(gift.price < 200){
            console.log('the gift\'s price is too low~~');
        } else {
            new to().received(gift.name);
        }
    }
}

var proxy = new proxy();

new from(100, 'book').sendgift(proxy);
new from(300, 'car').sendgift(proxy);

单一原则:对一个类而言,只做一件事;应用实例(图片预加载)
ps: 面向对象设计的原则之一:单一原则

var loadingimg = 'https://loading.io/assets/img/ajax.gif';
var targetimg = 'https://bpic.588ku.com/original_origin_min_pic/18/05/31/f7c539d4b0e349d0869838920f9fc05c.jpg';
// 传统方式的加载
var loadimage=function(src){
    var imgnode = document.createelement('img');
    imgnode.src = loadingimg;
    document.body.appendchild(imgnode);
    var img = new image();
    img.onload=function(){
        imgnode.src = src;
    }
    img.src = src;
}
loadimage(targetimg);

// 使用代理模式
var loadimg = (function(){
    var imgnode = document.createelement('img');
    document.body.appendchild(imgnode);
    return function(src){
        imgnode.src = src;
    }
})();
var proxy = (function(){
    var img = new image();
    img.onload=function(){
       loadimg(this.src);
    }
    return function(src){
        loadimg(loadingimg);
        img.src = src;
    }
})();

proxy(targetimg);

缓存代理:对一些开销大且可能重复性的任务进行缓存处理。
下面实现有个求和函数,如果所有的参数都相同,则直接从缓存中取上一次的计算结果。

function sum(arr){
    var sum = 0;
    sum = arr.reduce(function(pre, cur){
        return pre + cur;
    });
    return sum;
}

var proxy = function(arr){
    if(!proxy.cache){
        proxy.cache = {};
    }
    arr.sort();
    if(proxy.cache[''+arr.join('')] !== undefined){
        console.log('the result is from cache');
        return proxy.cache[arr.join('')]
    } else {
        proxy.cache[arr.join('')] = sum(arr);
        return proxy.cache[arr.join('')]
    }
}

虚拟代理:把一些开销很大 的操作延迟到真正需要的时候再进行;
文件同步的例子,比如客户端c要从服务端s下载文件。在客户端,每个文件都有个checkbox。
勾选checkbox,则下载文件;不勾选,则不需要下载服务端的文件

var download = function(file){ console.log('download file: ' + file);}

var proxy = function(){
    var that = this;
    if(proxy.o){
        return proxy.o;
    }
    this.files = [];
    setinterval(function(){
        that.files.foreach(function(v){
            download(v);
            that.files.remove(v);
        })
    }, 5000);
    proxy.o = this;
}

var p = new proxy();
var ul = document.createelement('ul');
document.body.appendchild(ul);
for(let i = 0; i < 20; i++){
    let li = document.createelement('li');
    let file = (+new date())+(math.random(0,1)* 10000).tofixed(0);
    li.innerhtml = '' + file;
    li.onclick=function(){
        var input = li.firstelementchild;
        if(input.checked){
            p.files.add(input.value);
        } else {
            p.files.remove(input.value);
        }
    }
    ul.appendchild(li);
}

array.prototype.add=function(item){
    if(this.indexof(item) == -1){
        this.push(item);
    } 
}
array.prototype.remove=function(item){
    let i = this.indexof(item);
    this.splice(i, 1);
}

观察者模式

下面是个开胃菜,一个很简单的例子

// 创建对象
var o = { };

function observer(oldval, newval) {
    // 其他处理逻辑...
    console.info('name属性的值从 '+ ( oldval || '' ) +' 改变为 ' + newval);
}

object.defineproperty(o, 'name', {
    configurable: true,
    enumerable: true,
    get: function(){
        return this.val;
    },
    set: function(n){
        observer(this.val, n);
        this.val = n;
    }
});

o.name = 1;
o.name = 2;

发布订阅模式

这种模式跟观察者模式很类似,他们都实现了不同间的解耦。

array.prototype.add=function(item){
    if(this.indexof(item) == -1){
        this.push(item);
    } 
}
array.prototype.remove=function(item){
    let i = this.indexof(item);
    this.splice(i, 1);
}

function com(name){
    this.name = name;
}
com.prototype = (function () {
            var events = {};
            return {
                on: function (e, fn) {
                    events[e] = events[e] || [];
                    events[e].add(fn);
                },

                off: function (e, fn) {
                    events[e] = events[e] || [];
                    if (fn) {
                        events[e].remove(fn);
                    } else {
                        events[e] = null;
                    }
                },

                fire:function(e, data){
                    if(events[e]){
                        events[e].foreach(function(fn){
                            fn(data)
                        });
                    }
                }
            }
        })();

com.prototype.constructor = com;

var a = new com('a');
a.on('e.a', function(d){console.log(d)});

var b = new com('b');
b.fire('e.a', 'b')

// 多写了一个深度复制的方法
object.defineproperty(object.prototype, 'extend', {
    confiugrable: true,
    enumerable: false,
    writable: false,
    value: function(){
        var source = null;

        while(source  = array.prototype.shift.call(arguments)){
            for(key in source){
                if(typeof source[key] === 'object'){
                    this[key] = this[key] || array.isarray(source[key])  [] : {} ;
                    this[key].extend(source[key]);
                } else {
                    this[key] = source[key];
                }
            }
        }
    }
});

迭代器模式

var iterator = function() {
    this.index = 0;
    this.items = [1,2,,3,4,5];
}

iterator.prototype={
    next: function(){
        if(this.index >= this.items.length){
            this.index = this.index - this.items.length;
        }
        return this.items[this.index++];
    },
    hasnext: function(){
        return this.index < this.items.length;
    },

    rewind: function(){
        this.index = 0;
    },

    current: function(){
        return this.items[this.index]
    }
}
iterator.prototype.constructor = iterator;

var a = new iterator();
var b = new iterator();

console.log(a.next());
console.log(a.next());
console.log(b.next());

利用迭代器模式,重写了array.prototype.reduce方法

(function(){
    function array1(a){
        var values = new array();
        values.push.apply(values, arguments);
        values.next = 1;
        values.cur = 0;
        return values;
}

array.prototype.reduce1=function(fn){ 
    if(this.length==0){
        return null;
    }
    if(this.length == 1){
        return fn(this[0], '', 0, this);
    }
    if(this.length == 2){
        return fn(this[0], this[1], 1, this);
    }
    var that = this, pre = that[0];
    while(this[this.next]){
        pre = fn(pre, that[that.next], that.next, that);
        this.cur++;
        this.next++;
    }
    return pre;
}

var a = new array1(1,2,3,4);
var r = a.reduce1(function(pre, cur, index, arr){
    return pre * cur;
});

console.log(r);

})()

装饰者模式

在不改变原有对象基础上,丰富原对象的功能。

function a(){
    this.name = 'a';
}
a.prototype.say=function(){console.log(this.name)}

var say1 = a.prototype.say;

a.prototype.say=function(){
    say1.call(this);
    console.log("i am new say fn ");
}
var a = new a();
a.say();

装饰者模式的一个应用场景有哪些?
面向切面 aspect oriented programming(aop)

下面的例子中一个前置装饰,一个后置装饰。

function.prototype.before=function(beforefn){
    var that = this;
    return function(){
        beforefn.apply(this, arguments);
        return that.apply(this, arguments);
    }
}
function.prototype.after=function(afterfn){
    var that = this;
    return function(){
        var result = that.apply(this, arguments);
        afterfn.apply(this, arguments);
        return result;
    }
}
function foobar(x, y){
    console.log(x, y)
}

function foo(x, y){
    console.log(x/10, y/10);
}

function bar(x, y){
    console.log(x*10, y*10);
}

foobar = foobar.before(foo).after(bar);

foobar(2,3);
// 0.2 0.3
// 2 3
// 20 30

aop的应用场景相对而言,还是比较广泛的。日志、安全控制/访问控制、性能监测以及缓存等,这些都是aop的典型应用场景。