中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

javascript提高前端代碼強大的案例分析

發布時間:2020-11-20 10:02:30 來源:億速云 閱讀:265 作者:小新 欄目:web開發

這篇文章給大家分享的是有關javascript提高前端代碼強大的案例分析的內容。小編覺得挺實用的,因此分享給大家做個參考。一起跟隨小編過來看看吧。

在過去的開發經歷中處理了各種奇葩BUG,認識到代碼健壯性(魯棒性)是提高工作效率、生活質量的一個重要指標,本文主要整理了提高代碼健壯性的一些思考。

之前整理過關于代碼健壯性相關的文章

  • 正兒八經地寫JavaScript之單元測試
  • 如何在代碼中打日志

本文將繼續探究除了單元測試、打日志之外其余一些幫助提高JavaScript代碼健壯性的方法。

更安全地訪問對象

不要相信接口數據

不要相信前端傳的參數,也不要信任后臺返回的數據

比如某個api/xxx/list的接口,按照文檔的約定

{
    code: 0,
    msg: "",
    data: [     // ... 具體數據
    ],
};復制代碼

前端代碼可能就會寫成

const {code, msg, data} = await fetchList()
data.forEach(()=>{})復制代碼

因為我們假設了后臺返回的data是一個數組,所以直接使用了data.forEach,如果在聯調的時候遺漏了一些異常情況

  • 預期在沒有數據時data會返回[]空數組,但后臺的實現卻是不返回data字段
  • 后續接口更新,data從數組變成了一個字典,跟前端同步不及時

這些時候,使用data.forEach時就會報錯,

Uncaught TypeError: data.forEach is not a function

所以在這些直接使用后臺接口返回值的地方,最好添加類型檢測

Array.isArray(data) && data.forEach(()=>{})復制代碼

同理,后臺在處理前端請求參數時,也應當進行相關的類型檢測。

空值合并運算符

由于JavaScript動態特性,我們在查詢對象某個屬性時如x.y.z,最好檢測一下xy是否存在

let z = x && x.y && x.y.z復制代碼

經常這么寫就顯得十分麻煩,dart中安全訪問對象屬性就簡單得多

var z = a?.y?.z;復制代碼

在ES2020中提出了空值合并運算符的草案,包括???.運算符,可以實現與dart相同的安全訪問對象屬性的功能。目前打開最新版Chrome就可以進行測試了

javascript提高前端代碼強大的案例分析

在此之前,我們可以封裝一個安全獲取對象屬性的方法

function getObjectValueByKeyStr(obj, key, defaultVal = undefined) {    if (!key) return defaultVal;    let namespace = key.toString().split(".");    let value,
        i = 0,
        len = namespace.length;    for (; i < len; i++) {
        value = obj[namespace[i]];        if (value === undefined || value === null) return defaultVal;
        obj = value;
    }    return value;
}var x = { y: { z: 100,},};var val = getObjectValueByKeyStr(x, "y.z");// var val = getObjectValueByKeyStr(x, "zz");console.log(val);復制代碼

前端不可避免地要跟各種各種瀏覽器、各種設備打交道,一個非常重要的問題就是兼容性,尤其是目前我們已經習慣了使用ES2015的特性來開發代碼,polyfill可以幫助解決我們大部分問題。

記得異常處理

參考:

  • JS錯誤處理 MDN
  • js構建ui的統一異常處理方案,這個系列的文章寫得非常好

異常處理是代碼健壯性的首要保障,關于異常處理有兩個方面

  • 合適的錯誤處理可以提要用戶體驗,在代碼出錯時優雅地提示用戶
  • 將錯誤處理進行封裝,可以減少開發量,將錯誤處理與代碼解耦

錯誤對象

可以通過throw語句拋出一個自定義錯誤對象

// Create an object type UserExceptionfunction UserException (message){  // 包含message和name兩個屬性
  this.message=message;  this.name="UserException";
}// 覆蓋默認[object Object]的toStringUserException.prototype.toString = function (){  return this.name + ': "' + this.message + '"';
}// 拋出自定義錯誤function f(){    try {        throw new UserException("Value too high");
    }catch(e){        if(e instanceof UserException){            console.log('catch UserException')            console.log(e)
        }else{            console.log('unknown error')            throw e
        }
    }finally{        // 可以做一些退出操作,如關閉文件、關閉loading等狀態重置
        console.log('done')        return 1000 // 如果finally中return了值,那么會覆蓋前面try或catch中的返回值或異常
    }
}
f()復制代碼

同步代碼

對于同步代碼,可以使用通過責任鏈模式封裝錯誤,即當前函數如果可以處理錯誤,則在catch中進行處理:如果不能處理對應錯誤,則重新將catch拋到上一層

function a(){    throw 'error b'}// 當b能夠處理異常時,則不再向上拋出function b(){    try{
        a()
    }catch(e){        if(e === 'error b'){            console.log('由b處理')
        }else {            throw e
        }
    }
}function main(){    try {
        b()
    }catch(e){        console.log('頂層catch')
    }
}復制代碼

異步代碼

由于catch無法獲取異步代碼中拋出的異常,為了實現責任鏈,需要把異常處理通過回調函數的方式傳遞給異步任務

function a(errorHandler) {    let error = new Error("error a");    if (errorHandler) {
        errorHandler(error);
    } else {        throw error;
    }
}function b(errorHandler) {    let handler = e => {        if (e === "error b") {            console.log("由b處理");
        } else {
            errorHandler(e);
        }
    };    setTimeout(() => {
        a(handler);
    });
}let globalHandler = e => {    console.log(e);
};
b(globalHandler);復制代碼

Prmise的異常處理

Promise只包含三種狀態:pendingrejectedfulfilled

let promise2 = promise1.then(onFulfilled, onRejected)復制代碼

下面是promise拋出異常的幾條規則

function case1(){    // 如果promise1是rejected態的,但是onRejected返回了一個值(包括undifined),那么promise2還是fulfilled態的,這個過程相當于catch到異常,并將它處理掉,所以不需要向上拋出。
    var p1 = new Promise((resolve, reject)=>{        throw 'p1 error'
    })

    p1.then((res)=>{        return 1
    }, (e)=>{        console.log(e)        return 2
    }).then((a)=>{        // 如果注冊了onReject,則不會影響后面Promise執行
        console.log(a) // 收到的是2
    })
}function case2(){    //  在promise1的onRejected中處理了p1的異常,但是又拋出了一個新異常,,那么promise2的onRejected會拋出這個異常
    var p1 = new Promise((resolve, reject)=>{        throw 'p1 error'
    })
    p1.then((res)=>{        return 1
    }, (e)=>{        console.log(e)        throw 'error in p1 onReject'
    }).then((a)=>{}, (e)=>{        // 如果p1的 onReject 拋出了異常
        console.log(e)
    })
}function case3(){    // 如果promise1是rejected態的,并且沒有定義onRejected,則promise2也會是rejected態的。
    var p1 = new Promise((resolve, reject)=>{        throw 'p1 error'
    })

    p1.then((res)=>{        return 1
    }).then((a)=>{        console.log('not run:', a)
    }, (e)=>{        // 如果p1的 onReject 拋出了異常
        console.log('handle p2:', e)
    })
}function case4(){    // // 如果promise1是fulfilled態但是onFulfilled和onRejected出現了異常,promise2也會是rejected態的,并且會獲得promise1的被拒絕原因或異常。
    var p1 = new Promise((resolve, reject)=>{
        resolve(1)
    })
    p1.then((res)=>{        console.log(res)        throw 'p1 onFull error'
    }).then(()=>{}, (e)=>{        console.log('handle p2:', e)        return 123
    })
}復制代碼

因此,我們可以在onRejected中處理當前promise的錯誤,如果不能,,就把他拋給下一個promise

async

async/await本質上是promise的語法糖,因此也可以使用promise.catch類似的捕獲機制

function sleep(cb, cb2 =()=>{},ms = 100) {
    cb2()    return new Promise((resolve, reject) => {        setTimeout(() => {            try {
                cb();
                resolve();
            }catch(e){
                reject(e)
            }
        }, ms);
    });
}// 通過promise.catch來捕獲async function case1() {    await sleep(() => {        throw "sleep reject error";
    }).catch(e => {        console.log(e);
    });
}// 通過try...catch捕獲async function case2() {    try {        await sleep(() => {            throw "sleep reject error";
        })
    } catch (e) {        console.log("catch:", e);
    }
}// 如果是未被reject拋出的錯誤,則無法被捕獲async function case3() {    try {        await sleep(()=>{}, () => {            // 拋出一個未被promise reject的錯誤
            throw 'no reject error'
        }).catch((e)=>{            console.log('cannot catch:', e)
        })
    } catch (e) {        console.log("catch:", e);
    }
}復制代碼

更穩定的第三方模塊

在實現一些比較小功能的時候,比如日期格式化等,我們可能并不習慣從npm找一個成熟的庫,而是自己順手寫一個功能包,由于開發時間或者測試用例不足,當遇見一些未考慮的邊界條件,就容易出現BUG。

這也是npm上往往會出現一些很小的模塊,比如這個判斷是否為奇數的包:isOdd,周下載量居然是60來萬。

javascript提高前端代碼強大的案例分析

使用一些比較成熟的庫,一個很重要原因是,這些庫往往經過了大量的測試用例和社區的考驗,肯定比我們順手些的工具代碼更安全。

一個親身經歷的例子是:根據UA判斷用戶當前訪問設備,正常思路是通過正則進行匹配,當時為了省事就自己寫了一個

export function getOSType() {  const ua = navigator.userAgent  const isWindowsPhone = /(?:Windows Phone)/.test(ua)  const isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone  const isAndroid = /(?:Android)/.test(ua)  // 判斷是否是平板
  const isTablet =    /(?:iPad|PlayBook)/.test(ua) ||
    (isAndroid && !/(?:Mobile)/.test(ua)) ||
    (/(?:Firefox)/.test(ua) && /(?:Tablet)/.test(ua))  // 是否是iphone
  const isIPhone = /(?:iPhone)/.test(ua) && !isTablet  // 是否是pc
  const isPc = !isIPhone && !isAndroid && !isSymbian && !isTablet  return {
    isIPhone,
    isAndroid,
    isSymbian,
    isTablet,
    isPc
  }
}復制代碼

上線后發現某些小米平板用戶的邏輯判斷出現異常,調日志看見UA為

"Mozilla/5.0 (Linux; U; Android 8.1.0; zh-CN; MI PAD 4 Build/OPM1.171019.019) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 Quark/3.8.5.129 Mobile Safari/537.36復制代碼

即使把MI PAD添加到正則判斷中臨時修復一下,萬一后面又出現其他設備的特殊UA呢?所以,憑借自己經驗寫的很難把所有問題都考慮到,后面替換成mobile-detect這個庫。

使用模塊的缺點在于

  • 可能會增加文件依賴體積,增加打包時間等,這個問題可以通過打包配置解決,將不會經常變更的第三方模塊打包到vendor文件中配置緩存
  • 在某些項目可能會由于安全考慮需要減少第三方模塊的使用,或者要求先進行源碼code review

當然在進行模塊選擇的時候也要進行各種考慮,包括穩定性、舊版本兼容、未解決issue等問題。當選擇了一個比較好的工具模塊之后,我們就可以將更多的精力放在業務邏輯中。

本地配置文件

在開發環境下,我們可能需要一些本地的開關配置文件,這些配置只在本地開發時存在,不進入代碼庫,也不會跟其他同事的配置起沖突。

我推崇將mock模板托管到git倉庫中,這樣可以方便其他同事開發和調試接口,帶來的一個問題時本地可能需要一個引入mock文件的開關

下面是一個常見的做法:新建一個本地的配置文件config.local.js,然后導出相關配置信息

// config.local.jsmodule.exports = {  needMock: true}復制代碼

記得在.gitignore中忽略該文件

config.local.js復制代碼

然后通過try...catch...加載該模塊,由于文件未進入代碼庫,在其他地方拉代碼更新時會進入catch流程,本地開發則進入正常模塊引入流程

// mock/entry.jstry {  const { needMock } = require('./config.local')  if (needMock) {    require('./index') // 對應的mock入口
    console.log('====start mock api===')
  }
} catch (e) {  console.log('未引入mock,如需要,請創建/mock/config.local并導出 {needMock: true}')
}復制代碼

最后在整個應用的入口文件判斷開發環境并引入

if (process.env.NODE_ENV === 'development') {  require('../mock/entry')
}復制代碼

通過這種方式,就可以在本地開發時愉快地進行各種配置,而不必擔心忘記在提交代碼前注釋對應的配置修改~

Code Review

參考:

  • Code Review 是苦澀但有意思的修行

Code Review應該是是上線前一個必經的步驟,我認為CR主要的作用有

  • 能夠確認需求理解是否出現偏差,避免扯皮

  • 優化代碼質量,包括冗余代碼、變量命名和過分封裝等,起碼除了寫代碼的人之外還得保證審核的人能看懂相關邏輯

對于一個需要長期維護迭代的項目而言,每一次commit和merge都是至關重要的,因此在合并代碼之前,最好從頭檢查一遍改動的代碼。即使是在比較小的團隊或者找不到審核人員,也要把合并認真對待。

感謝各位的閱讀!關于javascript提高前端代碼強大的案例分析就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

乌拉特后旗| 唐山市| 崇明县| 华阴市| 九江市| 新龙县| 郯城县| 高碑店市| 东光县| 霍山县| 绥阳县| 蕉岭县| 迁西县| 滕州市| 雅安市| 陆丰市| 溧阳市| 扬中市| 徐水县| 长寿区| 东兰县| 罗甸县| 习水县| 上林县| 石屏县| 成都市| 涞水县| 枣庄市| 印江| 道真| 福鼎市| 湘西| 汉源县| 鞍山市| 西乡县| 剑阁县| 任丘市| 襄樊市| 陇西县| 邹城市| 青铜峡市|