Maison  >  Article  >  interface Web  >  Certaines façons d’améliorer la puissance du code front-end à l’aide de JavaScript sont excellentes !

Certaines façons d’améliorer la puissance du code front-end à l’aide de JavaScript sont excellentes !

coldplay.xixi
coldplay.xixiavant
2020-11-19 17:24:482784parcourir

La colonne

javascript présente quelques méthodes pour améliorer la puissance du code front-end.

Certaines façons d’améliorer la puissance du code front-end à l’aide de JavaScript sont excellentes !

Recommandations gratuites : JavaScript(Vidéo)

Au cours de mon expérience de développement passée, j'ai traité divers bugs étranges et réalisé que la robustesse du code (robustesse) est un indicateur important pour améliorer l'efficacité du travail et la qualité de vie. Cet article résume principalement quelques réflexions sur l'amélioration de la robustesse du code.

J'ai déjà compilé des articles sur la robustesse du code

  • Écrire sérieusement des tests unitaires JavaScript
  • Comment se connecter au code

Cet article continuera à explorer d'autres méthodes pour aider à améliorer la robustesse du code JavaScript en plus des tests unitaires et de la journalisation.

Accédez aux objets de manière plus sûre

Ne faites pas confiance aux données de l'interface

Ne faites pas confiance aux paramètres transmis par le front-end et ne faites pas confiance aux données renvoyées par l'arrière-plan

Par exemple, une certaine api/xxx/list interface, selon la convention du document

{
    code: 0,
    msg: "",
    data: [     // ... 具体数据
    ],
};复制代码

le code frontal peut être écrit comme

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

car nous supposons que les données renvoyées par l'arrière-plan sont un tableau, nous utilisons donc data.forEach directement si certaines exceptions sont manquées lors du débogage conjoint

  • il est prévu que les données renvoient un [] tableau vide lorsqu'il y en a. il n'y a pas de données, mais l'implémentation en arrière-plan ne renvoie pas le champ data
  • Suite à la mise à jour de l'interface, les données sont passées d'un tableau à un dictionnaire et la synchronisation avec le front-end n'a pas été opportune

À ces moments-là, une erreur sera signalée lors de l'utilisation de data.forEach,

Uncaught TypeError : data.forEach n'est pas une fonction

Donc dans ces endroits où l'interface backend renvoie directement les valeurs, il est préférable d'ajouter une détection de type

Array.isArray(data) && data.forEach(()=>{})复制代码

De même, le backend traite le front-end Lors de la demande de paramètres, une détection de type pertinente doit également être effectuée.

Opérateur de fusion de valeurs nulles

En raison de la nature dynamique de JavaScript, lorsque nous interrogeons un certain attribut d'un objet tel que x.y.z, il est préférable de vérifier si x et y existe

let z = x && x.y && x.y.z复制代码

Il semble souvent très difficile d'écrire comme ça. Accéder en toute sécurité aux propriétés des objets dans Dart est beaucoup plus simple

var z = a?.y?.z;复制代码

a proposé un brouillon de opérateur de fusion de valeurs nulles dans ES2020, y compris les opérateurs et ?? peuvent obtenir le même accès sécurisé aux propriétés des objets que Dart. Actuellement, vous pouvez le tester en ouvrant la dernière version de Chrome?.

Avant cela, nous pouvons encapsuler une méthode pour obtenir en toute sécurité les propriétés des objets

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);复制代码

Le devant la fin est inévitable Lorsqu'il s'agit de divers navigateurs et appareils, un problème très important est la

compatibilité, surtout maintenant que nous sommes habitués à utiliser les fonctionnalités d'ES2015 pour développer du code, peut aider à le résoudre La plupart de nos questions. polyfill

N'oubliez pas la gestion des exceptions

Référence :

    Gestion des erreurs JS MDN
  • Solution unifiée de gestion des exceptions JS pour la création d'une interface utilisateur, cette série d'articles est écrit Très bien
La gestion des exceptions est la principale garantie de la robustesse du code. La gestion des exceptions comporte deux aspects

    Une gestion appropriée des erreurs peut améliorer l'expérience utilisateur lorsque le code. ne va pas Inviter l'utilisateur avec élégance lorsque
  • Encapsuler la gestion des erreurs, ce qui peut réduire la quantité de développement et dissocier la gestion des erreurs du code
L'objet d'erreur

peut être transmis via l'instruction throw Lancer un objet d'erreur personnalisé

// Create an object type UserExceptionfunction UserException (message){  // 包含message和name两个属性
  this.message=message;  this.name="UserException";
}// 覆盖默认[object Object]的toStringUserException.prototype.toString = function (){  return this.name + &#39;: "&#39; + this.message + &#39;"&#39;;
}// 抛出自定义错误function f(){    try {        throw new UserException("Value too high");
    }catch(e){        if(e instanceof UserException){            console.log(&#39;catch UserException&#39;)            console.log(e)
        }else{            console.log(&#39;unknown error&#39;)            throw e
        }
    }finally{        // 可以做一些退出操作,如关闭文件、关闭loading等状态重置
        console.log(&#39;done&#39;)        return 1000 // 如果finally中return了值,那么会覆盖前面try或catch中的返回值或异常
    }
}
f()复制代码

Code synchrone

Pour le code synchrone, vous pouvez utiliser

pour encapsuler les erreurs via le modèle de chaîne de responsabilité , qui c'est-à-dire que si la fonction actuelle peut gérer l'erreur, alors traiter dans catch : si l'erreur correspondante ne peut pas être gérée, lancez à nouveau le catch vers la couche précédente

function a(){    throw &#39;error b&#39;}// 当b能够处理异常时,则不再向上抛出function b(){    try{
        a()
    }catch(e){        if(e === &#39;error b&#39;){            console.log(&#39;由b处理&#39;)
        }else {            throw e
        }
    }
}function main(){    try {
        b()
    }catch(e){        console.log(&#39;顶层catch&#39;)
    }
}复制代码

Code asynchrone

Puisque catch ne peut pas obtenir l'exception levée dans le code asynchrone, afin d'atteindre la chaîne de responsabilité, la gestion des exceptions doit être transmise à la tâche asynchrone via la fonction de rappel

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);复制代码
Gestion des exceptions de Prmise

Promesse uniquement contient trois états :

, pending et rejectedfulfilled

let promise2 = promise1.then(onFulfilled, onRejected)复制代码
Voici plusieurs règles pour que la promesse lève des exceptions

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
    })
}复制代码
Par conséquent, nous pouvons gérer l'erreur de la promesse actuelle dans

. Sinon, jetez-le au suivant. Un onRejectedpromise

async

est essentiellement du sucre syntaxique pour la promesse, donc un async/await mécanisme de capture similaire. peut également être utilisé 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);
    }
}复制代码
pour un premier module tiers plus stable

Lors de la mise en œuvre de certaines fonctions relativement petites, telles que le formatage de la date, etc., nous ne sommes peut-être pas habitués à trouver un mature bibliothèque de npm, mais écrivez nous-mêmes un package de fonctions En raison du temps de développement ou des tests. S'il n'y a pas suffisamment de cas d'utilisation, des bogues peuvent facilement survenir lorsque vous rencontrez des conditions limites non prises en compte.

这也是npm上往往会出现一些很小的模块,比如这个判断是否为奇数的包:isOdd,周下载量居然是60来万。

使用一些比较成熟的库,一个很重要原因是,这些库往往经过了大量的测试用例和社区的考验,肯定比我们顺手些的工具代码更安全。

一个亲身经历的例子是:根据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代码健壮性的一些方法,主要整理了

  • 安全地访问对象属性,避免数据异常导致代码报错
  • 捕获异常,通过责任链的方式进行异常处理或上报
  • 使用更稳定更安全的第三方模块,
  • 认真对待每一次合并,上线前先检查代码

此外,还需要要养成良好的编程习惯,尽可能考虑各种边界情况。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer