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 div = document.createElement('div');
    div.innerHTML = html;
    div.style.display = 'none';
    return div;
}
// 创建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 = '<input type="checkbox" value="' + file + '">' + 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的典型应用场景。

猜你喜欢

转载自blog.csdn.net/u013137242/article/details/80671756
今日推荐