Home > Web Front-end > JS Tutorial > Some ways to improve the power of front-end code using JavaScript are great!

Some ways to improve the power of front-end code using JavaScript are great!

coldplay.xixi
Release: 2020-11-19 17:24:48
forward
2822 people have browsed it

The

javascript column introduces some methods to improve the power of front-end code.

Some ways to improve the power of front-end code using JavaScript are great!

Free recommendation:JavaScript(Video)

In the past development experience, I have dealt with various weird bugs and realized that code robustness (robustness) is an important indicator for improving work efficiency and quality of life. This article mainly summarizes some thoughts on improving code robustness.

I have compiled articles related to code robustness before

  • Write JavaScript unit tests seriously
  • How to log in the code

This article will continue to explore some other methods to help improve the robustness of JavaScript code in addition to unit testing and logging.

Access objects more safely

Don’t trust the interface data

Don’t trust the parameters passed by the front end, and don’t trust the data returned by the background

For example, a certain The interface of api/xxx/list, according to the convention of the document

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

, the front-end code may be written as

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

because we assume that the data returned by the background is an array. So data.forEach is used directly. If some exceptions are missed during joint debugging,

  • is expected that data will return [] when there is no data. Empty array, but the background implementation does not return the data field
  • Subsequent interface updates, data changed from an array to a dictionary, and the synchronization with the front end was not timely

At these times, an error will be reported when using data.forEach,

Uncaught TypeError: data.forEach is not a function

So in Where the return value of the backend interface is directly used, it is best to add type detection

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

Similarly, the backend should also perform relevant type detection when processing front-end request parameters.

Null value merging operator

Due to the dynamic nature of JavaScript, when we query a certain attribute of an object such as x.y.z, it is best to detect x and yDoes it exist?

let z = x && x.y && x.y.z复制代码
Copy after login

It is very troublesome to write like this often. It is much simpler to safely access object properties in dart

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

In ES2020, null The draft value merging operator , including the ?? and ?. operators, can achieve the same safe access to object properties as Dart. Currently, you can test it by opening the latest version of Chrome

Before this, we can encapsulate a method to safely obtain object properties

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

The front end is inevitable When dealing with various browsers and devices, a very important issue is compatibility, especially now that we are accustomed to using the features of ES2015 to develop code, polyfillcan help solve most of our problems.

Remember exception handling

Reference:

  • JS error handling MDN
  • JS builds a unified exception handling solution for UI, this series of articles is written Very good

Exception handling is the primary guarantee of code robustness. There are two aspects to exception handling

  • Appropriate error handling can improve the user experience. When the code goes wrong Prompt the user gracefully when
  • Encapsulate error handling, which can reduce the amount of development and decouple error handling from the code

The error object

can be passed through the throw statement Throw a custom error object

// 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()复制代码
Copy after login

Synchronous code

For synchronous code, you can use to encapsulate errors through the chain of responsibility pattern , that is, if the current function can handle the error, then Process in catch: If the corresponding error cannot be handled, throw the catch to the previous layer again

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;)
    }
}复制代码
Copy after login

Asynchronous code

Since catch cannot obtain the exception thrown in the asynchronous code, in order to achieve Chain of responsibility, the exception handling needs to be passed to the asynchronous task through the callback function

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

Exception handling of Prmise

Promise only contains three states: pending, rejected and fulfilled

let promise2 = promise1.then(onFulfilled, onRejected)复制代码
Copy after login

The following are several rules for promise to throw 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
    })
}复制代码
Copy after login

Therefore, we can onRejected Handle the error of the current promise. If it cannot, throw it to the next promise

async

async/awaitEssentially it is a promise syntactic sugar, so you can also use promise.catchSimilar capture mechanism

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);
    }
}复制代码
Copy after login

More stable third-party modules

When implementing some relatively small functions, such as For date formatting, etc., we may not be used to finding a mature library from npm, but instead write a function package ourselves. Due to insufficient development time or test cases, bugs may easily occur when encountering some unconsidered boundary conditions.

这也是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
  }
}复制代码
Copy after login

上线后发现某些小米平板用户的逻辑判断出现异常,调日志看见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复制代码
Copy after login

即使把MI PAD添加到正则判断中临时修复一下,万一后面又出现其他设备的特殊UA呢?所以,凭借自己经验写的很难把所有问题都考虑到,后面替换成mobile-detect这个库。

使用模块的缺点在于

  • 可能会增加文件依赖体积,增加打包时间等,这个问题可以通过打包配置解决,将不会经常变更的第三方模块打包到vendor文件中配置缓存
  • 在某些项目可能会由于安全考虑需要减少第三方模块的使用,或者要求先进行源码code review

当然在进行模块选择的时候也要进行各种考虑,包括稳定性、旧版本兼容、未解决issue等问题。当选择了一个比较好的工具模块之后,我们就可以将更多的精力放在业务逻辑中。

本地配置文件

在开发环境下,我们可能需要一些本地的开关配置文件,这些配置只在本地开发时存在,不进入代码库,也不会跟其他同事的配置起冲突。

我推崇将mock模板托管到git仓库中,这样可以方便其他同事开发和调试接口,带来的一个问题时本地可能需要一个引入mock文件的开关

下面是一个常见的做法:新建一个本地的配置文件config.local.js,然后导出相关配置信息

// config.local.jsmodule.exports = {  needMock: true}复制代码
Copy after login

记得在.gitignore中忽略该文件

config.local.js复制代码
Copy after login

然后通过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}')
}复制代码
Copy after login

最后在整个应用的入口文件判断开发环境并引入

if (process.env.NODE_ENV === 'development') {  require('../mock/entry')
}复制代码
Copy after login

通过这种方式,就可以在本地开发时愉快地进行各种配置,而不必担心忘记在提交代码前注释对应的配置修改~

Code Review

参考:

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

Code Review应该是是上线前一个必经的步骤,我认为CR主要的作用有

  • 能够确认需求理解是否出现偏差,避免扯皮

  • 优化代码质量,包括冗余代码、变量命名和过分封装等,起码除了写代码的人之外还得保证审核的人能看懂相关逻辑

对于一个需要长期维护迭代的项目而言,每一次commit和merge都是至关重要的,因此在合并代码之前,最好从头检查一遍改动的代码。即使是在比较小的团队或者找不到审核人员,也要把合并认真对待。

小结

本文主要整理了提高JavaScript代码健壮性的一些方法,主要整理了

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

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

The above is the detailed content of Some ways to improve the power of front-end code using JavaScript are great!. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:juejin.im
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template