JavaScript鏈式結構序列化詳解

FletcherPer 7年前發布 | 6K 次閱讀 JavaScript開發 JavaScript

一、概述

在JavaScript中,鏈式模式代碼,太多太多,如下:

if_else:

if(...){ 
    //TODO 
}else if(...){ 
    //TODO 
}else{ 
    //TODO 
} 

switch:

switch(name){ 
    case ...:{ 
        //TODO 
        break; 
    } 
    case ...:{ 
        //TODO 
        break; 
    } 
    default:{ 
        //TODO     
    } 
} 

疑問:諸如上述這些鏈式代碼,倘若,我們想將其扁平化鏈式處理呢?如下:

//fn1,f2,f3為處理函數 
_if(fn1)._elseIf(fn2)._else(fn3); 

下面我們就來一起嘗試實現下唄。

二、鏈式代碼扁平化

假如,現在我們有如下鏈式代碼:

if(name === 'Monkey'){ 
    console.log('yes, I am Monkey'); 
}else if(name === 'Dorie'){ 
    console.log('yes, I am Dorie'); 
}else{ 
    console.log('sorry, over for ending!'); 
} 

好了,現在我們一步一步將其”扁平化”。

其實看看上面的代碼,不難發現,if…else這種格式,其實就是數據結構中的單鏈表,那么,初步利用JavaScript實現單鏈表,如下:

var thens = []; 
thens.resolve = function(name){ 
    for(var i = 0, len = this.length; i < len;i++){ 
        if(this[i](name) !== 'next'){ 
            break; 
        } 
    } 
} 
thens.push(f1, f2, f3); 

其中f1,f2,f3為判斷函數,并且我們假設,如果諸如f1、f2、f3返回’next’時,就繼續往下查找,否則,停止往下查找。如下:

function f1(name){ 
    if(name === 'Monkey'){ 
        console.log('yes, I am Monkey'); 
    }else{ 
        return 'next'; 
    } 
} 
function f2(name){ 
    if(name === 'Dorie'){ 
        console.log('yes, I am Dorie'); 
    }else{ 
        return 'next'; 
    } 
} 
function f3(){ 
    console.log('sorry, over for ending!'); 
} 

好了,這就是鏈表的模式。

但是,我們的最終目的是想實現如下這樣的呢?

//fn1,f2,f3為處理函數 
_if(fn1)._elseIf(fn2)._else(fn3); 

你可能會說,將上述代碼改成如下這樣,不就好了嗎?!!

thens.push(f1).push(f2).push(f3).resolve(); 

But,JavaScript的push方法返回的是數組的新長度,而不是數組對象哦。

So,那我們只能新寫一個add方法,效果和push一樣,但是返回數組對象。如下:

thens.add = function(f){ 
    if(typeof f === 'function'){ 
        this.push(f); 
        return this;         
    }         
} 

測試代碼如下:

var thens = []; 
thens.add = function(f){ 
    if(typeof f === 'function'){ 
        this.push(f); 
        return this;         
    }         
} 
thens.resolve = function(name){ 
    for(var i = 0, len = this.length; i < len;i++){ 
        if(this[i](name) !== 'next'){ 
            break; 
        } 
    }     
} 
thens.add(f1).add(f2).add(f3).resolve(); 

但是,這樣有個缺點,我們是將add、resolve方法綁定在全局變量thens中的,總不能每次創建一個數組時,都復制粘貼一遍方法吧,所以重構代碼如下:

function Slink(){ 
    this.thens = []; 
    this.thens.add = function(f){ 
        if(typeof f === 'function'){ 
            this.push(f); 
            return this;         
        }         
    } 
    this.thens.resolve = function(name){ 
        for(var i = 0, len = this.length; i < len;i++){ 
            if(this[i](name) !== 'next'){ 
                break; 
            } 
        }     
    } 
} 

顯然,add,resolve這種公共方法,在每次實例化時,都創建一遍是不科學的,so,利用prototype在原有的基礎上繼續變形,如下:

function Slink(){ 
    this.thens = []; 
} 
Slink.prototype = { 
    add: function(f){ 
            if(typeof f === 'function'){ 
                this.thens.push(f); 
                return this;         
            }         
    }, 
    resolve: function(name){ 
            for(var i = 0, len = this.thens.length; i < len; i++){ 
                if(this.thens[i](name) !== 'next'){ 
                    break; 
                } 
            }     
    } 
} 

測試代碼如下:

var thens = new Slink(); 
thens.add(f1).add(f2).add(f3); 
thens.resolve(); 

不錯,但是這樣,我們每次都得手動new一個Slink,有點麻煩,所以,我們將new Slink這個過程,封裝到函數中,如同jQuery一樣,如下:

function $go(f){ 
    return new Slink(f); 
} 
function Slink(f){ 
    this.thens = []; 
    this.thens.push(f); 
} 
Slink.prototype = { 
    add: function(f){ 
            if(typeof f === 'function'){ 
                this.thens.push(f); 
                return this;         
            }         
    }, 
    resolve: function(name){ 
            for(var i = 0, len = this.thens.length; i < len; i++){ 
                if(this.thens[i](name) !== 'next'){ 
                    break; 
                } 
            }     
    } 
} 

測試代碼如下:

$go(f1).add(f2).add(f3).resolve(); 

好了,大功告成,接下來就是語法糖滴問題咯,整理代碼如下:

function _if(f){ 
    return new Slink(f); 
} 
function Slink(f){ 
    this.thens = []; 
    this.thens.push(f); 
} 
Slink.prototype = { 
    _elseIf: function(f){ 
            if(typeof f === 'function'){ 
                this.thens.push(f); 
                return this;         
            }         
    }, 
    _else: function(f){ 
            return this._elseIf(f); 
    }, 
    resolve: function(name){ 
            for(var i = 0, len = this.thens.length; i < len; i++){ 
                if(this.thens[i](name) !== 'next'){ 
                    break; 
                } 
            } 
            return this;             
    } 
} 

測試代碼如下:

_if(f1)._elseIf(f2)._else(f3).resolve(); 

當然,除開利用數組這種方式,還可以利用 閉包 ,實現鏈式扁平化效果,如下:

var func = Function.prototype; 
func._else = func._elseIf = function(fn){ 
    var _this = this; 
    return function(){ 
        var res = _this.apply(this,arguments); 
        if(res==="next"){  //值為Boolean 
            return fn.apply(this,arguments); 
        } 
        return res; 
    } 
} 

測試代碼如下:

function f1(name){ 
    if(name === 'Monkey'){ 
        console.log('yes, I am Monkey'); 
    }else{ 
        return 'next'; 
    } 
} 
function f2(name){ 
    if(name === 'Dorie'){ 
        console.log('yes, I am Dorie'); 
    }else{ 
        return 'next'; 
    } 
} 
function f3(){ 
    console.log('sorry, over for ending!'); 
} 
f1._elseIf(f2)._else(f3)('Dorie'); 

三、異步代碼鏈式扁平化

在上面我們討論的都是同步過程,倘若,鏈式調用函數中有異步情況呢?

什么意思?如下:

function f1(name){ 
    setTimeout(function(){ 
        if(name === 'Monkey'){ 
            console.log('yes, I am Monkey'); 
        }else{ 
            return 'next'; 
        } 
    }, 2000); 
} 
function f2(name){ 
    if(name === 'Dorie'){ 
        console.log('yes, I am Dorie'); 
    }else{ 
        return 'next'; 
    } 
} 
function f3(){ 
    console.log('sorry, over for ending!'); 
} 

我們將f1利用setTimeout變成了異步,按照上述代碼的邏輯,應該是等f1完全執行完畢(包括setTimeout執行)后,判斷是否執行f2,但真的如此嗎?

測試代碼如下:

_if(f1)._elseIf(f2)._else(f3).resolve(); 

執行代碼的結果就是,什么也不輸出。

Why?

因為JavaScript是單線程嘛。

那該怎么解決呢?

由于有異步代碼,且必須在異步代碼后處理后續的鏈,那么我們就等待異步代碼執行完畢后,才執行后續的鏈嘛,如下:

function f1(name){ 
    setTimeout(function(){ 
        if(name === 'Monkey'){ 
            console.log('yes, I am Monkey'); 
        }else{ 
            //處理后續鏈 
            this.resolve(name, 1);//1代表下一個需處理函數在數組中的位置 
        } 
    }.bind(this), 2000); 
} 

好了,由于在函數中,我們使用了this,其代表Slink對象,且改變了resolve方法,固,需細微調整Slink構造函數和原型鏈,如下:

function Slink(f){ 
    this.thens = []; 
    this.thens.push(f.bind(this)); 
} 
Slink.prototype = { 
    _elseIf: function(f){ 
            if(typeof f === 'function'){ 
                this.thens.push(f.bind(this)); 
                return this;        
            }        
    }, 
    _else: function(f){ 
            return this._elseIf(f.bind(this)); 
    }, 
    resolve: function(name, flag){ 
            for(var i = flag, len = this.thens.length; i < len; i++){ 
                if(this.thens[i](name) !== 'next'){ 
                    break; 
                } 
            } 
            return this;            
    } 
} 

測試代碼如下:

function f1(name){ 
    setTimeout(function(){ 
        if(name === 'Monkey'){ 
            console.log('yes, I am Monkey'); 
        }else{ 
            //處理后續鏈 
            this.resolve(name, 1);//1代表下一個需處理函數在數組中的位置 
        } 
    }.bind(this), 2000); 
} 
function f2(name){ 
    if(name === 'Dorie'){ 
        console.log('yes, I am Dorie'); 
    }else{ 
        return 'next'; 
    } 
} 
function f3(){ 
    console.log('sorry, over for ending!'); 
} 
_if(f1)._elseIf(f2)._else(f3).resolve('',0); 

哈哈,如果你了解Promise,是不是感覺這么相似呢。

是的,宗旨都一樣,達到異步代碼扁平化目的,不過這里的代碼比Promise要簡約得多啦。

 

 

來自:http://developer.51cto.com/art/201612/525374.htm

 

 本文由用戶 FletcherPer 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!