再起航,我的學習筆記之JavaScript設計模式27(鏈模式)

分類:IT技術 時間:2017-10-02

鏈模式

概念介紹

鏈模式(Operatc of Responsibility):
通過在對象方法中將當前對象返回,實現對同一個對象多個方法的鏈式調用。從而簡化對該對象的多個方法的多次調用時,對該對象的多次引用。

原型式繼承

鏈模式顧名思義就是像鏈子一樣一個接一個的,我們可以通過點語法在一個方法的後面接著調用另一個方法,那麽這種模式是怎麽做到的呢?一般來說鏈模式是基於原型繼承的,並且在每一個原型方法的實現上都返回當前對象this,使當前對象一直處於原型鏈作用域的頂端。這樣我們就可以實現鏈式調用
我們創建一個對象A,並且給A的原型對象上添加一個length屬性,和size方法,如果我們要調用的話就要通過new來創建新對象訪問

var A=function(){}
A.prototypee={
    length:2,
    size:function(){
        return this.length;
    }
}
var a=new A();
console.log(a.size());

我們看到這樣調用我們可以正常訪問,但是下面兩種方式去訪問程序就會報錯

console.log(A.size())

因為size綁定在A的原型上沒有綁定在其自身上。

console.log(A().size())

因為A函數對象執行的結果沒有返回值所以找不到size方法
上述兩種方式都是因為size方法綁定在A類的原型上導致的。那麽我們如果能讓其訪問呢?
我們可以借助另一個對象來實現。

var A=function(){
    return B;
}
var B=A.prototypee={
    length:2,
    size:function(){
        return this.length;
    }
}

我們再來看看

console.log(A().size());

獲取元素

我們看到現在這個A().size()的形態是不是就有鏈式結構的雛形了,如果這個A()能獲取對象該多好,那我們接著往下面拓展功能

var A=function(){
    return A.fn;
}

A.fn=A.prototypee={}

var A=function(selector){
    return A.fn.init(selector);
}


A.fn=A.prototype={
    init:function(selector){
        return document.getElementById(selector)
    },
    length:2,
    size:function(){
        return this.length;
    }
}


console.log(A('demo'));

現在我們已經能獲取到元素了,但是如果想一級一級的去調用,我們還要讓A對象返回的結果能擁有A.fn中的方法,這個時候我們就可以通過返回this來達到我們的目的

var A=function(selector){
    return A.fn.init(selector);
}
A.fn=A.prototype={
    init:function(selector){
        //作為當前對象的屬性值保存
        this[0]=document.getElementById(selector);
        //校正length屬性
        this.length=1;
        //返回當前對象
        return this;
    },
    length:2,
    size:function(){
        return this.length;
    }
}

我們來測試一下

var demo=A('demo');
console.log(demo);
console.log(A('demo').size());

如果想把結果像數組那樣訪問,我們可以將他們的屬性值順序地設置為數字索引。為了更像數組我們還校正了它的length屬性

但我們這樣做有個弊端,就是後面獲取的對象會把我們前面的對象覆蓋掉

var test=A('container');
console.log(demo);

出現這種情況的原因是因為每次在A的構造函數中返回的A.fn.init(selector)對象都指向同一個對象造成的,我們直接使用new來創建即可

var A=function(selector){
    return new A.fn.init(selector);
}
console.log(A('demo'))
console.log(A('container'))
console.log(A('demo').size())

我們發現雖然我們解決了元素覆蓋的問題,但是我們那種鏈式的寫法好像也失效了,為什麽會出現這種情況呢?
這是因為A.fn.init(selector)與new A.fn.init(selector)的實現的差別造成的,牽著返回的this指向當前的對象,而後者用new對對象內的屬性進行了復制,所以this指向的就不當前對象了,我們來測試說明一下。

init:function(selector){
    this[0]=document.getElementById(selector);
    this.length=1;
    console.log(this===A.fn,this===A.prototypee,this);
    return this;
}

A.fn.init(selector)返回結果如下

new A.fn.init(selector)返回結果如下

那麽我們如何去解決這個問題呢,其實只用將構造函數的原型指向一個已存在的對象即可
A.fn.init.prototype=A.fn;

我們再試試

var A=function(selector){
    return new A.fn.init(selector);
}
A.fn=A.prototype={
    init:function(selector){
    this[0]=document.getElementById(selector);
    this.length=1;
    return this;
    },
    length:2,
    size:function(){
        return this.length;
    }
}
A.fn.init.prototype=A.fn;

console.log(A('demo').size())

好了現在我們可以接著像之前那樣調用了

方法拓展

那麽現在我們也能獲取到對象了也能調用方法了,那我們怎麽通過點語法鏈式使用呢,我們又要如何添加呢?

//對象拓展
A.extend=A.fn.extend=function(){
    //拓展對象從第二個參數算起
    var i=1,
    //獲取參數長度
    len=arguments.length,
    //第一個參數為源對象
    target=arguments[0],
    //拓展對象中屬性
    j;
    //如果只穿一個參數
    if(i==len){
        //源對象為當前對象
        target=this;
        //i從0計數
        i--;
    }
    //遍歷參數中拓展對象
    for(;i<len;i++){
        //遍歷拓展對象中的屬性
        for(j in arguments[i]){
        //拓展源對象
        target[j]=arguments[i][j];
        }
    }
    //返回源對象
    return target;
}

我們來調用試試

var demo=A.extend({first:1},{second:2},{third:3});
console.log(demo);
A.extend(A.fn,{version:'1.0'});
console.log(A('demo').version);
A.fn.extend({getVersion:function(){return this.version}})
console.log(A('demo').getVersion());
A.extend(A,{names:'李四'});
console.log(A.names);

實現鏈式調用

A.extend({
    //分割帶“-”樣式,變為駝峰式寫法
    camelCase:function(str){
        return str.replace(/\-(\w)/g,function(all,letter){
            return letter.toUpperCase();
        });
    }
});

A.fn.extend({
    //設置css樣式
    css:function(){
        var arg=arguments,
        len=arg.length;
        if(this.length<1){
            return this;
        }
        //只有一個參數時
        if(len===1){
            //如果為字符串則為獲取一個元素CSS樣式
            if(typeof arg[0]==='string'){
                //IE
                if (this[0].currentStyle) {
                    return this[0].currentStyle[name];
                } else{
                    return getComputedStyle(this[0],false)[name];
                }
            //為對象時則設置多個樣式
            }else if(typeof arg[0]==='object'){
                //遍歷每個樣式
                for (var i in arg[0]) {
                    for(var j=this.length-1;j>=0;j--){
                        //分割-為駝峰式寫法
                        this[j].style[A.camelCase(i)]=arg[0][i];
                    }
                }
            }
            //兩個參數則設置一個樣式
        }else if(len===2){
            for (var j=this.length-1;j>=0;j--) {
                this[j].style[A.camelCase(arg[0])]=arg[1];
            }
        }
        return this ;
    }
})

A.fn.extend({
    //設置屬性
    attr:function(){
        var arg=arguments,
        len=arg.length;
        if(this.length<1){
            return this;
        }
        //如果一個參數
        if (len===1) {
            //為字符串獲取第一個元素屬性
            if(typeof arg[0]==='string'){
                return this[0].getAttribute(arg[0]);
            }
            //為對象設置每個元素的多個屬性
            else if(typeof arg[0]==='object'){
                //遍歷屬性
            for (var i in arg[0]) {
                for (var j=this.length-1;j>=0;j--) {
                    this[j].setAttribute(i,arg[0][i]);
                }
            }
        }
            //兩個參數則設置每個元素單個屬性
        }else if(len===2){
    
                for (var j=this.length-1;j>=0;j--) {
                    this[j].setAttribute(arg[0],arg[i]);
                }
            
        }
        return this;
    }
})

A.fn.extend({
    //獲取或設置元素的內容
    html:function(){
        var arg=arguments,
        len=arg.length;
        //如果沒參數則取第一個元素的內容
        if (len===0) {
            return this[0]&&this[0].innerHTML;
        }else{
            //一個參數則設置每個元素的內容
            for (var i=this.length-1;i>=0;i--) {
                this[i].innerHTML=arg[0];
            }
        }
        return this;
    }
})
A.fn.extend({
    //添加時間
    on:(function(){
        //如果支持DOM2級事件
        if(document.addEventListener){
            return function(type,fn){
                var i=this.length-1;
                for(;i>=0;i--){
                    this[i].addEventListener(type,fn,false);
                }
                return this;
            }
            //IE瀏覽器DOM2級事件
        }else if(document.attachEvent){
            return function(type,fn){
                var i=this.length-1;
                for (;i>=0;i--) {
                    this[i].addEvent('on'+type,fn);
                }
                return this;
            }
            //不支持DOM2級瀏覽器添加事件
        }else{
            return function(type,fn){
                var i=this.length-1;
                for(;i>=0;i--){
                    this[i]['on'+type]=fn;
                }
                return this;
            }
        }
    })()
})

我們來看一下效果

A('demo').css({
    height:'30px',
    borer:'1px solid #000',
    'background-color':'red'
})
.attr('class','demo')
.html('添加文字')
.on('click',function(){
    console.log('觸發點擊事件');
});

總結

JavaScript中的鏈模式的核心思想就是通過在對象中的每個方法調用執行完畢後返回當前對象this來實現,由於鏈模式使得代碼緊湊簡潔而高效,目前的主流代碼庫都已該模式作為一種風格,比如我們最熟悉的jQuery。

也謝謝大家看到這裏:)如果你覺得我的分享還可以請點擊推薦,分享給你的朋友讓我們一起進步~

好了以上就是本次分享的全部內容,本次示例參考自javascript設計模式一書,讓我們一點點積累一點點成長,希望對大家有所幫助。

歡迎轉載,轉載請註明作者,原文出處。


Tags: 對象 原型 我們 方法 調用 size

文章來源:


ads
ads

相關文章
ads

相關文章

ad