官术网_书友最值得收藏!

5.1 Lodash.js是什么

本節先分析為什么需要使用Lodash.js,接著再通過實例來對比使用Lodash.js和原生JavaScript的區別。

5.1.1 概述

正如Lodash.js官方主頁上所寫的,它是一個具有一致性、模塊化且高性能的JavaScript實用工具庫。一致性,是指無論是在瀏覽器環境下,還是在服務端的Node.js環境下,開發者都可以通過統一的API來使用它。模塊化,是指你可以僅在程序中引用Lodash.js提供的工具函數的子集,而不需要在所有場景中加載整個庫。開發中,大多數計算邏輯背后的算法都不是唯一的,不同的實現方式之間自然也就有了性能的比較,開發者當然都希望自己能使用性能比較好的方法,但并不是每個人都有能力判定哪個最優,Lodash.js在內部幫助大家完成了這件事情。

說到實用性,Lodash.js所做的工作實際上很抽象,就是將重復率非常高的方法、算法和處理邏輯等提取出來形成一個方法集合,將原本暴露在業務邏輯代碼中的循環語句和條件判斷語句隱藏起來,而對外提供一致且便于記憶的API,這樣不僅可以使業務邏輯代碼變得更加精簡且易讀,也降低了多人合作開發時因個人能力差異造成的混亂。當你在檢視或維護代碼時,不再需要猜測別人代碼中那一層層的for循環和if判斷的真正意圖。

在真實的開發合作中,其他人不了解你所認為的“理所應當”的事情是一種非常普遍的現象,因為與你合作的人甚至可能都不是前端工程師。對于for(var...)循環、for(let...)循環、for...of...、forEach和map這些看起來非常相似的方法,一些非前端開發者很有可能分不清它們之間的區別,以及它們對數據源的影響。如果所有人統一使用“_.each()”或“_.map()”進行遍歷,則不僅能降低JavaScript的編寫難度,而且數據的變化相對來說也更容易追蹤。當然,在ES6+版本的規范早已普及的今天,許多同名的方法都已經標準化,原生的JavaScript語法配合Babel插件使用可以讓代碼變得更加簡潔和優雅,但在一些去重、分組、遞歸遍歷等更為復雜的場景中,使用Lodash.js往往能使語義變得更清晰。

5.1.2 代碼的較量

我們先通過一個示例直觀地感受一下不同的編程方式是如何處理同一個業務邏輯需求的,以便于更深入地了解Lodash.js帶來的便利。

假設后端在響應中返回了一個包含了多個對象的數組類型的數據,它的結構如下,dinner屬性中記錄了每個人最近3天每餐所吃的食物,記錄中的每一項既可能是字符串,也可能是字符串組成的數組(代表所吃的食物不止一種):

[{
    id:0,
    name:'Tony',
    dinner:['apple',['peach','blueberry'],//....]
},{
    //....
},//....]

現在為了分析目標群體的飲食結構,我們需要把在dinner屬性中出現過的所有食物都記錄下來,并按照字母順序對其排序,相同的食物只需要出現一次即可。下面就來看看不同的開發者可能會如何實現這樣的功能。

1. 初級開發者的代碼

function getAllFood(data){
    let resultMap = {};
    //遍歷每一條記錄的dinner字段,然后使用對象實現去重功能
    for(let i = 0; i < data.length; i++){
        for(let j = 0; j <data[i].dinner.length; j++){
            let foods = data[i].dinner[j];
            if(typeof foods === 'string' && !resultMap[foods]){
                //處理字符串類型的情況
                resultMap[foods] = 1;
            }else if (isArray(foods)){
                //處理數組類型的情況
                foods.forEach(item=>{
                   if(!resultMap[item]){
                       resultMap[item] = 1;
                    }
                });
            }
        }
    }
    //從對象中生成并返回最終結果
    return Object.keys(resultMap).sort((a,b)=>a.localeCompare(b));
}
//判斷傳入的數據是不是一個數組
function isArray(data){
    return Object.prototype.toString.call(data).slice(8,-1) === 'Array'
}

上面的代碼大約有30行,盡管實現了示例中所要求的功能,但暴露了太多實現細節。開發者將無關緊要的代碼細節都平鋪在函數中了,其他人可能需要完整地閱讀整段代碼才能搞清楚開發者的意圖。同時,開發者并沒有對可能出現的狀況進行足夠的預判,上面統計部分的代碼最多只能實現二維嵌套數組的解析,盡管它可以滿足當前的需求,但是很難應對未來可能出現的變化,換句話說就是程序的健壯性不足。另一方面,getAllFood方法承擔了過多實現業務邏輯的責任,違背了“單一職責”的模塊化設計原則,以至于很難直接被復用,后續再出現類似的需求時,幾乎只能選擇復制粘貼,然后再進行逐句檢查和修改,如果團隊在構建流水線上加入了重復代碼檢查等環節,那么這樣的代碼恐怕無法通過檢查。

2. 中級開發者的代碼

let originData = require('./data.js');
function getAllFood(data) {
    return sortAndUnique(flatmap(data.map(item=>item.dinner),[]));
}
//排序去重
function sortAndUnique(arr){
    let resultMap = {};
    arr.forEach(i=>resultMap[i]=1);
    return Object.keys(resultMap).sort((a, b) => a.localeCompare(b));
}
//數組扁平化
function flatmap(arr, result){
    if(isArray(arr)){
        arr.map(item=>{
            flatmap(item,result);
        });
    }else{
        result.push(arr);
    }
    return result;
}
//判斷傳入的數據是不是一個數組
function isArray(data) {
    return Object.prototype.toString.call(data).slice(8, -1) === 'Array';
}

中級開發者的代碼大約也有30行,但代碼質量明顯比前面的要好。這份代碼遵循了“單一職責”的開發原則,將數組的類型判斷、數組的扁平化、數組的排序和去重等可重用的方法從業務邏輯中剝離出來,形成了獨立的方法。同時,主業務邏輯本身只用了一行代碼,而且通過函數名就可以非常直觀地了解其功能。提取出來的功能方法,其實現邏輯不止一種,僅數組去重就能夠寫出5種以上的方式,即使開發者在此使用的算法效率不高,后續的優化工作也可以直接針對這個方法進行,而不需要去重寫業務邏輯代碼。這樣的代碼就已經非常好了。

3. 高級開發者的代碼

//高級開發者的實現
let originData = require('./data.js');
const _ = require('lodash');
function getAllFood(){
    return _.chain(originData)
            .map('dinner')
            .flattenDeep()
            .sortBy()
            .sortedUniq()
            .value();
}
console.log(getAllFood(originData));

當使用Lodash.js封裝好的工具來實現同樣的邏輯時,代碼總共不超過10行,主業務邏輯部分只有一個鏈式調用。在高級開發者實現的代碼中,首先是調用“_.chain”方法聲明一段鏈式調用邏輯,然后通過map方法將原數組中的指定字段提取出來(本質上是一種數組映射),通過flattenDeep方法將數組展平為一維數組,通過sortBy方法對集合進行排序,通過sortedUniq方法對一個已經排好序的集合實現去重功能(已排序序列的去重操作可以通過對相鄰項進行比較來完成,相較于針對亂序集合去重的算法,排序去重的效率更高),Lodash.js中鏈式調用的語法并不是立即執行的,它需要調用value方法來啟動計算。與中級開發者編寫的代碼相比,使用Lodash.js后,開發者不需要再重復實現常用的工具方法,而且可以使用鏈式調用的方式將方法連接在一起,提高了代碼的可讀性。當大家都使用類似的API來組織代碼時,代碼的規范性和可讀性自然就提高了。

主站蜘蛛池模板: 六枝特区| 岚皋县| 南投县| 当涂县| 永兴县| 时尚| 屯留县| 通州市| 大田县| 承德县| 普陀区| 仙桃市| 三门峡市| 思茅市| 尉犁县| 包头市| 敦煌市| 宁蒗| 华亭县| 延川县| 象州县| 怀化市| 兴山县| 临沂市| 松桃| 桐梓县| 霍山县| 壶关县| 开江县| 铅山县| 蓬安县| 广宗县| 周至县| 保康县| 彰化市| 全南县| 宁夏| 舞阳县| 高雄市| 武强县| 保靖县|