搜索
首页web前端js教程javascript提高前端代码强大的一些方法,很好!

javascript栏目介绍提高前端代码强大的一些方法。

javascript提高前端代码强大的一些方法,很好!

免费推荐: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就可以进行测试了

在此之前,我们可以封装一个安全获取对象属性的方法

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 + &#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()复制代码

同步代码

对于同步代码,可以使用通过责任链模式封装错误,即当前函数如果可以处理错误,则在catch中进行处理:如果不能处理对应错误,则重新将catch抛到上一层

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

异步代码

由于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来万。

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

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

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

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

以上是javascript提高前端代码强大的一些方法,很好!的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:juejin。如有侵权,请联系admin@php.cn删除
es6数组怎么去掉重复并且重新排序es6数组怎么去掉重复并且重新排序May 05, 2022 pm 07:08 PM

去掉重复并排序的方法:1、使用“Array.from(new Set(arr))”或者“[…new Set(arr)]”语句,去掉数组中的重复元素,返回去重后的新数组;2、利用sort()对去重数组进行排序,语法“去重数组.sort()”。

JavaScript的Symbol类型、隐藏属性及全局注册表详解JavaScript的Symbol类型、隐藏属性及全局注册表详解Jun 02, 2022 am 11:50 AM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于Symbol类型、隐藏属性及全局注册表的相关问题,包括了Symbol类型的描述、Symbol不会隐式转字符串等问题,下面一起来看一下,希望对大家有帮助。

原来利用纯CSS也能实现文字轮播与图片轮播!原来利用纯CSS也能实现文字轮播与图片轮播!Jun 10, 2022 pm 01:00 PM

怎么制作文字轮播与图片轮播?大家第一想到的是不是利用js,其实利用纯CSS也能实现文字轮播与图片轮播,下面来看看实现方法,希望对大家有所帮助!

JavaScript对象的构造函数和new操作符(实例详解)JavaScript对象的构造函数和new操作符(实例详解)May 10, 2022 pm 06:16 PM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于对象的构造函数和new操作符,构造函数是所有对象的成员方法中,最早被调用的那个,下面一起来看一下吧,希望对大家有帮助。

JavaScript面向对象详细解析之属性描述符JavaScript面向对象详细解析之属性描述符May 27, 2022 pm 05:29 PM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于面向对象的相关问题,包括了属性描述符、数据描述符、存取描述符等等内容,下面一起来看一下,希望对大家有帮助。

javascript怎么移除元素点击事件javascript怎么移除元素点击事件Apr 11, 2022 pm 04:51 PM

方法:1、利用“点击元素对象.unbind("click");”方法,该方法可以移除被选元素的事件处理程序;2、利用“点击元素对象.off("click");”方法,该方法可以移除通过on()方法添加的事件处理程序。

整理总结JavaScript常见的BOM操作整理总结JavaScript常见的BOM操作Jun 01, 2022 am 11:43 AM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于BOM操作的相关问题,包括了window对象的常见事件、JavaScript执行机制等等相关内容,下面一起来看一下,希望对大家有帮助。

foreach是es6里的吗foreach是es6里的吗May 05, 2022 pm 05:59 PM

foreach不是es6的方法。foreach是es3中一个遍历数组的方法,可以调用数组的每个元素,并将元素传给回调函数进行处理,语法“array.forEach(function(当前元素,索引,数组){...})”;该方法不处理空数组。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。