JS的鏈?zhǔn)骄幊?/h1>

前言

鏈?zhǔn)骄幊虒嶋H是將多個方法(函數(shù))通過某種方式鏈接在一起姆打,使多個邏輯塊能按流程逐步執(zhí)行(或跳過執(zhí)行),從而實現(xiàn)解耦,在js上最典型的鏈?zhǔn)酱a:

/* 鏈?zhǔn)?*/
console.log(
    [1,2,3,4]
        .concat(5)
        .filter((item)=>(item<3))
        .concat(6)
        .join("")
);  // 輸出 126

/* 非鏈?zhǔn)?*/
const arr = [1,2,3,4];
const arr1 = arr.concat(5);
const arr2 = arr1.filter((item)=>(item<3));
const arr3 = arr2.concat(6);
console.log(arr3.join(""));

實現(xiàn)鏈?zhǔn)椒磻?yīng)的本質(zhì)為:每次該對象(Object-A)調(diào)用其方法(Method-1)時,返回值仍為本對象(Object-A),從而后面使用鏈?zhǔn)降姆绞皆僬{(diào)用另外一個方法(Method-2)時霞篡,得到的this仍為原對象(Object-A)世蔗,然后返回值同樣(Object-A),從而仍可通過鏈?zhǔn)降姆绞皆僬{(diào)用該對象上的別的方法(Method-3)寇损,以此類推凸郑。

在js上常見的鏈?zhǔn)骄幊逃幸韵聨追N具體應(yīng)用:

  • 對象方法return this的鏈?zhǔn)讲僮?/li>
  • Promise
  • 責(zé)任鏈(Chain of responsibility)

它們有不同的目標(biāo)與思路,下面就逐一介紹~

一矛市、對象方法

對同一個對象不斷執(zhí)行相同或不同的方法

jQuery不用多說了吧芙沥,jQuery里面有很多的方法的使用方式就是此類形式,如:

$("#myDiv")
    .css('color','red')
    .html('<p>123</>')
    .appendTo('<div>dddd</div>')

我們來寫一個對象里掛載多個方法:

var myObj = {
    name: '',
    setName: function(newName) {
        this.name = newName;
        // 要實現(xiàn)在調(diào)用setName方法后仍能鏈?zhǔn)秸{(diào)用myObj的其他方法就必須返回this浊吏,即返回myObj
        return this;
    },
    addStr: function(str) {
        this.name += str;
        return this;
    },
    consoleName: function() {
        console.log(this.name);
        return this;
    }
};

myObj
    .setName('帥哥')
    .addStr('就是我')
    .consoleName();     // 輸出'帥哥就是我'

上面的三個方法的返回值都為this而昨,所以每次調(diào)用之后返回值均為myObj,下面我們驗證下:

var obj1 = myObj.setName('帥哥');
var obj2 = obj1.addStr('就是我');
var obj3 = obj2.consoleName();
console.log(obj1 === myObj);    // true
console.log(obj2 === myObj);    // true
console.log(obj3 === myObj);    // true

既然obj1找田、obj2歌憨、obj3、myObj都是同一個墩衙,那我們不就可以合并代碼了嘛务嫡,不需要每次都多聲明一個變量:

/* 第一次簡化 */
myObj.setName('帥哥');
myObj.addStr('就是我');
myObj.consoleName();

/* 第二次簡化 */
myObj.setName('帥哥').addStr('就是我').consoleName();

二、Promise

將多個異步邏輯塊解耦漆改,并使其能按序執(zhí)行心铃,若其中一個出現(xiàn)錯誤則退出鏈?zhǔn)剑苯舆M(jìn)入catch

Promise為ES6新特性挫剑,用于避免寫出沖擊波式代碼(callback hell)去扣,那么就會有人問,什么是沖擊波代碼了樊破,給你們瞧一瞧:

getData(x => {
    getMoreData(x, y => {
        getPerson(person => {
            getPlanet(person, (planet) => {
                getGalaxy(planet, (galaxy) => {
                    getLoca(planet, (galaxy) => {
                        console.log(galaxy);
                    });
                });
            });
        });
    });
});

如果你想的話愉棱,還可以弄得更大更長~:smirk::smirk:

下面先來個最簡單的Promise使用:

var myPromise = new Promise(function(resolve, reject) {
    // 一秒鐘后執(zhí)行resolve方法
    window.setTimeout(resolve, 1000);
});
myPromise.then(function() {
    // 一秒鐘之后將會進(jìn)入此callback
    console.log('!');
});

可以看到構(gòu)造Promise對象需要傳入一個Function,該Function接受兩個參數(shù)哲戚,分別是resolvereject奔滑,前者作為成功回調(diào),后者作為失敗回調(diào)顺少。

下面展示如何使用Promise來封裝異步請求的發(fā)送與處理

/* 封裝異步請求 */
function getUserInfo(userId){
    return new Promise(function(resolve,reject){
        if(!userId){
            reject('userId不能為空');
            return;
        }
        // 異步請求
        ajax({
            url:'./getUserInfo',
            method:'GET',
            params:{userId},
            success:function(res){
                resolve(res);
            },
            error:function(){
                reject('請求錯誤');
            }
        })
    });
}

/* 調(diào)用 */
getUserInfo()
    .then(function(data){
        console.log(data)
    })
    .catch(function(msg){
        console.log(msg)
    });
// 最后輸出 'userId不能為空'

那么來修改剛開始的沖擊波代碼:

function getUserInfo(obj){
    return new Promise(function(resolve,reject){
        if(!obj.id){
            reject('對象id不能為空');
            return;
        }
        // 使用定時器來模擬異步請求(或其他異步操作)
        window.setTimeout(
            ()=>resolve(obj),
            3000
        );
    })
}
function getUserLocal(obj){
    return new Promise(function(resolve,reject){
        if(!obj.lastIP){
            reject('對象的IP不能為空');
            return;
        }
        window.setTimeout(resolve,2000);
    })
}
getBaseInfo({id:null,lastIP:123})
    .then(function(obj){
        return getUserDetail(obj);
    })
    .catch(function(errMsg){
        //在最后加catch的話档押,如果then中某處出現(xiàn)了錯誤,這不再繼續(xù)執(zhí)行下面的語句祈纯,直接執(zhí)行catch,并且將錯誤信息傳給catch
        console.log(errMsg);
    })
// 最后會輸出(console) '對象id不能為空'

Promise中的catch會捕捉當(dāng)前鏈?zhǔn)街械淖罱K的錯誤(the eventual error)

三叼耙、責(zé)任鏈(Chain of responsibility)

劃分多個任務(wù)(責(zé)任)塊腕窥,按序執(zhí)行,每個任務(wù)塊都有權(quán)決定是否繼續(xù)交給下一個任務(wù)塊

簡單的來講筛婉,就像是面試一樣:

  • 人事篩選簡歷簇爆,如果簡歷信息各項符合就交給技術(shù)負(fù)責(zé)人癞松,否則就沒有然后了
  • 技術(shù)負(fù)責(zé)人面試,如果技術(shù)過關(guān)了交給主管
  • 主管面試入蛆,如果各方面都合適了交給老板
  • 老板....以此類推

其中這一個個的就是任務(wù)塊(handler)

下面來個栗子:

// 任務(wù)塊:篩選性別
const genderHandler = function(next, data) {
    if(data.gender === 'male') {
        console.log('我們不要男的');
        return;
    }
    next(data);
};
// 任務(wù)塊:篩選年齡
const ageHandler = function(next, data) {
    if(data.age > 30) {
        console.log('年齡太大了');
        return;
    }
    next(data);
};
// 任務(wù)塊:最終處理函數(shù)
const finalSuccHandler = function(next, data) {
    console.log('emmmm...不錯不錯');
};


import Chain from './chain.js';
// 使用Chain來構(gòu)建鏈?zhǔn)较烊兀愃朴凇敖⑸a(chǎn)線”
const peopleChain = new Chain()
    .setNextHandler(genderHandler)
    .setNextHandler(ageHandler)
    .setNextHandler(finalSuccHandler);


/* 往責(zé)任鏈上載入不同的信息 */
peopleChain.start({
    gender: 'male',
    age: 21
});     // 輸出 '我們不要男的'

peopleChain.start({
    gender: 'female',
    age: 48
});     // 輸出 '年齡太大了'

peopleChain.start({
    gender: 'female',
    age: 18
});     // 輸出 'emmmm...不錯不錯'

構(gòu)造簡單的Chain類,用以構(gòu)建鏈?zhǔn)剑?/p>

// chain.js
class Chain {
    handlers = [];      // 處理函數(shù)集合哨毁,用于存儲當(dāng)前鏈?zhǔn)缴纤械膄unc
    cache = [];         // 緩存枫甲,用于存儲當(dāng)前鏈?zhǔn)缴线€未觸發(fā)的func

    /* 設(shè)置下一個 handler */
    setNextHandler(fn) {
        if (typeof fn !== "function") {
            throw new Error("[chain] successor must be a function.");
        }
        this.handlers.push(fn);
        return this;
    }

    next() {
        if (this.cache && this.cache.length > 0) {
            let ware = this.cache.shift();    // 釋放隊頭 handler
            ware.call(
                this,
                this.next.bind(this),       // 遞歸
                arguments && arguments[0]
            );
        }
    }

    /* 開始觸發(fā)鏈?zhǔn)?*/
    start() {

        // 將 [this.handlers] 復(fù)制一份,賦給 [this.cache]
        this.cache = this.handlers.map(function(fn) {
            return fn;
        });

        // 主動觸發(fā)第一個 handler
        this.next(arguments[0]);
    }
}
export default Chain;

在vue扼褪、react想幻、小程序等框架中使用的話,鏈?zhǔn)絻?nèi)部可能需要使用到上下文(this)话浇,需要看下面的栗子:

// chain.js
class Chain {
    handlers = [];      // 處理函數(shù)集合脏毯,用于存儲當(dāng)前鏈?zhǔn)缴纤械膄unc
    cache = [];         // 緩存,用于存儲當(dāng)前鏈?zhǔn)缴线€未觸發(fā)的func
    context = null;     // 上下文幔崖,用于存儲外部this

    /* 設(shè)置下一個 handler */
    setNextHandler(fn) {
        if (typeof fn !== "function") {
            throw new Error("[chain] successor must be a function.");
        }
        this.handlers.push(fn);
        return this;
    }

    next() {
        if (this.cache && this.cache.length > 0) {
            let ware = this.cache.shift();    // 釋放隊頭 handler
            ware.call(
                this,
                this.context,
                this.next.bind(this),       // 遞歸
                arguments && arguments[0]
            );
        }
    }

    /* 開始觸發(fā)鏈?zhǔn)?*/
    start() {

        // start 方法接受 [context] 及其他參數(shù)
        const { context, ...rest } = arguments[0];

        // 將 [this.handlers] 復(fù)制一份食店,賦給 [this.cache]
        this.cache = this.handlers.map(function(fn) {
            return fn;
        });

        // 暫存上下文
        this.context = context;

        // 主動觸發(fā)第一個 handler
        this.next(rest);

    }
}

export default Chain;
    // 任務(wù)塊:篩選性別
    const genderHandler = function(context, next, data) {
        if(data.gender === 'male') {
            context.showTips('我們不要男的');
            return;
        }
        next(data);
    };
    // 任務(wù)塊:篩選年齡
    const ageHandler = function(context, next, data) {
        if(data.age > 30) {
            context.showTips('年齡太大了');
            return;
        }
        next(data);
    };

    // 使用Chain來構(gòu)建鏈?zhǔn)?    const peopleChain = new Chain()
        .setNextHandler(genderHandler)
        .setNextHandler(ageHandler);

    // 這里使用objA來作為上下文,如:在vue中的話context參數(shù)傳該組件的vm即可
    const objA = {
        showTips: function(str) {
            window.alert(str);
        }
    };

    peopleChain.start({
        context: objA,
        gender: 'male',
        age: 21
    });

    peopleChain.start({
        context: objA,
        gender: 'female',
        age: 48
    });
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者

  • 序言:七十年代末赏寇,一起剝皮案震驚了整個濱河市吉嫩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蹋订,老刑警劉巖率挣,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異露戒,居然都是意外死亡椒功,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門智什,熙熙樓的掌柜王于貴愁眉苦臉地迎上來动漾,“玉大人,你說我怎么就攤上這事荠锭『得校” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵证九,是天一觀的道長删豺。 經(jīng)常有香客問我,道長愧怜,這世上最難降的妖魔是什么呀页? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮拥坛,結(jié)果婚禮上蓬蝶,老公的妹妹穿的比我還像新娘尘分。我一直安慰自己,他們只是感情好丸氛,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布培愁。 她就那樣靜靜地躺著,像睡著了一般缓窜。 火紅的嫁衣襯著肌膚如雪定续。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天雹洗,我揣著相機與錄音香罐,去河邊找鬼。 笑死时肿,一個胖子當(dāng)著我的面吹牛庇茫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播螃成,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼旦签,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了寸宏?” 一聲冷哼從身側(cè)響起宁炫,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎氮凝,沒想到半個月后羔巢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡罩阵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年竿秆,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稿壁。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡幽钢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出傅是,到底是詐尸還是另有隱情匪燕,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布喧笔,位于F島的核電站帽驯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏书闸。R本人自食惡果不足惜尼变,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望梗劫。 院中可真熱鬧享甸,春花似錦、人聲如沸梳侨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽走哺。三九已至蚯嫌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丙躏,已是汗流浹背择示。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留晒旅,地道東北人栅盲。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像废恋,于是被迫代替她去往敵國和親谈秫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容