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

前言

鏈?zhǔn)骄幊虒?shí)際是將多個(gè)方法(函數(shù))通過某種方式鏈接在一起,使多個(gè)邏輯塊能按流程逐步執(zhí)行(或跳過執(zhí)行),從而實(shí)現(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(""));

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

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

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

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

一、對(duì)象方法

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

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

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

我們來寫一個(gè)對(duì)象里掛載多個(gè)方法:

var myObj = {
    name: '',
    setName: function(newName) {
        this.name = newName;
        // 要實(shí)現(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();     // 輸出'帥哥就是我'

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

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都是同一個(gè),那我們不就可以合并代碼了嘛,不需要每次都多聲明一個(gè)變量:

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

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

二、Promise

將多個(gè)異步邏輯塊解耦,并使其能按序執(zhí)行,若其中一個(gè)出現(xiàn)錯(cuò)誤則退出鏈?zhǔn)?,直接進(jìn)入catch

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

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

如果你想的話,還可以弄得更大更長(zhǎng)~:smirk::smirk:

下面先來個(gè)最簡(jiǎn)單的Promise使用:

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

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

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

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

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

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

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

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

簡(jiǎn)單的來講,就像是面試一樣:

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

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

下面來個(gè)栗子:

// 任務(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...不錯(cuò)不錯(cuò)');
};


import Chain from './chain.js';
// 使用Chain來構(gòu)建鏈?zhǔn)?,類似于“建立生產(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...不錯(cuò)不錯(cuò)'

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

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

    /* 設(shè)置下一個(gè) 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();    // 釋放隊(duì)頭 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;
        });

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

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

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

    /* 設(shè)置下一個(gè) 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();    // 釋放隊(duì)頭 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;

        // 主動(dòng)觸發(fā)第一個(gè) 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)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容