Jest项目实战(1):JavaScript 库设计原则及最佳实践

JavaScript库设计

在开始项目实战之前,我们先来介绍一下在设计开源库的时候有哪些原则以及最佳实践。

函数的设计

函数包含三要素:

  • 函数名
  • 参数
  • 返回值

函数名

函数通常表示做一件事情,因此函数名一般为一个动词或者表示动作的短语,我们希望通过函数名就能够传达这个函数是做什么的。哪怕整个函数单词用得多一些,整个函数名长一些也无所谓,只要能够传达当前函数的作用。

例如 react 源码中的一些函数:workLoopConcurrent、checkScheduledUpdateOrContext、bailoutOnAlreadyFinishedWork

另外就是函数名要力求准确,这里的准确还包含单词的拼写不要出错,因为作为一个开源库,代码一旦开源出去,就很难收回了,换句话说,一旦有这种拼写的错误,那么大概率后面就会一直延用这种错误的拼写。例如 HTTP 协议里面就有一个拼写的错误,请求头里面有一个 referer 字段实际上是错误的,正确的拼写为 referrer

函数参数

理论上来讲,关于函数参数的设计,函数参数的个数应该是越少越好,这样的话能够降低使用者的心智负担。如果你所设计的函数参数过多,那么也从侧面说明了函数的设计是有问题的,你在一个函数里面融入了过多的功能,因此你应该考虑对你当前的这个函数重新进行设计。

一般来讲,在设计函数的参数的时候,不要超过 3 个,如果可以的话,最好是 2 个参数,如果是 2 个参数,一般来讲,第一个参数代表必传参数,第二个一般代表可选参数。

例如:

getParams('?a=1&b=2', 'a'); // 输出1// 我们先不管函数具体的实现,下面有两种参数设计
getParams(url, key, sep='&', eq='=')
getParams(url, key, opt = { sep: "&", eq: "=" })

在上面的示例中,第二种函数参数的设计要明显优于第一种,通过对象化的思路,减少后参数的数量,降低了用户的心智负担,其实还有一个好处,就是采用对象化的设计,未来在进行扩展的时候也更加容易一些。

返回值

如果你的返回值是针对函数传入的参数进行的查询或者某种操作,那么返回值的类型尽量和参数的类型保持一致,这样做是比较符合直觉的。

另外就是如果没有设置返回值,那么默认返回值为 undefined。

举个例子:

function getParams(url){if(url){// xxx}
}
// 这里有个默认的返回值为 undefined

假设用户想要在获取参数后将其转为十六进制:

getParams(url).toString(16);

但是上面的函数设计就会存在隐患,因此更好的方法就是像上面所说,保持返回值的类型一致。

function getParams(url){if(url){// xxx}return "";
}

提升健壮性

我们的开源库会被很多人使用,并且环境是未知的,即便我们在文档中规定了必须要传递什么类型的参数,但是使用者也有可能违反约定,甚至还有一些情况,数据来源于服务器、数据来源于各种逻辑计算之后的结果,传入到了我们的函数,所以这个时候我们就需要对我们的参数进行一个防御。

function trimStart(str){return str.replace(/^\s+/, '');
}trimStart(111);

在上面的代码中,如果意外传入了非字符串类型的参数,那么就会出现异常,这个时候我们就可以采取一些防御性的措施:

function trimStart(str){return String(str).replace(/^\s+/, '');
}trimStart(111);

在进行参数防御的时候,参数分为两种,一种是必传参数,另外一种是可选参数,针对不同的参数类型,有如下的校验和转换规则:

  • 如果参数是要传递给系统函数,则可以把校验这一步下沉给系统函数来处理
  • 对于 object、array、function 类型的参数,要做强制校验,如果校验失败,对于必传参数来讲就执行异常流程,对于可选参数来讲就设置默认值
  • 对于 number、string、boolean 类型的参数,要做自动转换
    • 数字使用 Number 函数进行转换
    • 整数使用 Math.round 函数进行转换
    • 字符串使用 String 函数进行转换
    • 布尔值使用 !! 进行转换
  • 对于 number 类型参数,如果转换出来是 NaN,那么对于必传参数来讲执行异步流程,对于可选参数来讲就设置默认值
  • 对于复合类型的内部数据,也要执行上述流程

异常捕获

在设计开源库的时候,异步捕获也是一个非常重要的步骤,也就是说,如果代码内部发生了错误,应该怎么办?

JSON.parse 方法可以将字符串转为 JS 对象,但是传入这个方法的字符串如果不符合 JSON 的语法, 那么最终转换的时候就会报错。那么如果你设计的函数内部用到了这个方法,那么就会有出错的可能性,因此我们需要考虑到这种情况

function safeParse(str, backupData){try {return JSON.parse(str);} catch (e) {return backupData;}
}

安全防护

在设计开源库的时候,由于我们所设计的函数在使用的时候环境未知,所以你不能够想当然的认为会发生什么,正确思路是需要防患于未然,各种意外的情况都需要考虑到,对你所设计的函数做一些安全上面的防护措施。

最小功能设计

开源库应该对外提供最小的功能,尽可能隐藏内部的实现细节,不相关的功能不要向外暴露,一旦某个接口决定向外暴露,那么这个接口就是对外的一种承诺,承诺暴露出来的接口会一直维护,并且永久向下兼容。

这里我们来看一个例子:

例如我这里有一个叫做 guid 的函数,该函数每调用一次,就会生成一个唯一的 ID,内部依赖一个计数器来实现

export let count = 1;
export function guid() {return count++;
}

在上面的例子中,count 就没有暴露的必要,它应该是属于模块内部的数据。

再例如:

class Guid {count = 1;guid() {return this.count++;}
}const g = new Guid();
g.count = 'xxx'; // 直接修改了内部 count,导致代码报错

这里应该考虑将这个属性设置为私有属性。

关于类的私有属性,社区方面一直在进行探索,之前的方案是通过添加一个下划线前缀来表示这是一个私有属性,但是这种方式归根结底只是一种约定而已

class Guid {_count = 1;
}

目前更好的做法,是将私有属性放置到 constructor 里面:

class Guid {constructor(){let count = 1;this.guid = () => {return count++;}}
}

现在已经有了更好的做法,ES2022 里面正式提供了私有属性的标志符,通过一个 # 号表示私有属性。

class Guid {#count = 1;constructor(){this.guid = () => {return this.#count++;}}
}

最小参数设计

参数的个数不要太多,尽量保证在 3 个以内,参数的类型尽可能是简单类型,如果是复杂(引用)类型,尽量不要修改传入的参数,而是在传入的参数的基础上,复制一份,再进行操作。

举个例子,fill 函数可以实现用指定的值来填充数组:

function fill(arr, value){for(let i = 0; i < arr.length; i++){arr[i] = value;}return arr;
}

上面的这种设计,虽然也能够达到目的,但是对原来传入的数组进行了填充修改,这可能不是使用者所期望的。

更理想的方式,对传入的参数进行一个复制,例如:

function fill(arr, value){const newArr = clone(arr); // 假设这里的 clone 是一个深度克隆方法for(let i = 0; i < newArr.length; i++){newArr[i] = value;}return newArr;
}

对象的冻结

暴露出去的接口可能会被使用者有意或者无意的进行修改,导致开源库在开发阶段都是运行良好的,但是在某些情况下就出错了。

import $ from 'jquery';
$.version = undefined; // 外部代码修改 $ 对象的属性
$.version.split('.'); // 正常代码因为上面的那一行代码导致报错

这种时候,我们就可以对对象进行一个冻结,常见的冻结方法:

方法修改原型指向添加属性修改属性配置删除属性修改属性
Object.preventExtensions
Object.seal
Object.freeze

因此我们这边就可以采用这些方法来对对象进行一个冻结:

import $ from 'jquery';
Object.freeze($);
$.version = undefined;

在上面的代码中,我们针对 $ 进行了冻结,冻结的对象属性是无法修改的。如果尝试修改,那么在严格模式下会报错,在非严格模式下会静默失败。

避免原型入侵

JavaScript 是基于原型来实现的面向对象,因此我们在设计方法的时候要避免在标准库的对象原型上面(Array.prototype、Object.prototype…)添加方法,因为一旦你这么做,就会影响所有的对象。

Object.prototype.tree = function(){console.log(Object.keys(this));
}const obj = {a : 1,b : 2
}obj.tree(); // ['a', 'b']

一旦你这么做,就会给所有的对象都增加一个可枚举的方法,通过 for in 进行遍历的时候,就会多这么一个方法出来。

另外还会带来一个问题,可能会存在冲突的问题。不同的库可能会扩展同名的方法,一旦冲突的方法实现不一致,此时就会导致必然会有一个库的代码会失效。

这样的做法实际上也被称之为猴子补丁(monkey-patching),在社区里面已经达成了一个共识“不要耍流氓,不是你的对象你不要动手动脚的”。

更好的方式就是常见一个新的构造函数去继承你想要的修改的构造函数,例如:

class myNum extends Number {constructor(...args) {super(...args);}isEven() {return this % 2 === 0}
}
const i = new myNum(42);
// 无论是新类添加的方法还是 Number 类里面的方法都可以使用
console.log(i.isEven()); // true
console.log(i.toFixed(2)); // 42.00

以前前端在原型入侵上面是有先例的(bad case),前端库 Mootools 和 prototype.js 这两个库都对标准库对象的原型进行了扩展。这两个库当时都给数组扩展了一个名为 flatten 的方法(拍平多维数组),但是这两个库在方法的实现上面是不一致的,这就带来了冲突,冲突的结果就是如果你同时引入这两个库,就必然有一个库的代码会失效。

而且这种做法还影响到了 ES 规范,我们知道 ES6 目前提供了一个叫做 Array.prototype.flat 的方法(数组拍平),关于这个方法当时 ECMA 委员会实际上是想要叫做 flatten,但是由于和 Mootools 和 prototype.js 这两库的 flatten 方法重名了,又由于这两个库的使用者众多,因此最终被迫改了名字叫做 flat。

总结

本小节我们主要介绍了在设计 JavaScript 库时的一些注意点,我们从下面的 5 个点进行了介绍:

  • 函数的设计
    • 函数主要就是需要注意函数三要素:函数名、函数参数、返回值
    • 函数名要力求准确无误,最好能够通过函数名就知道该函数的作用
    • 参数的个数尽量控制在 1-3 个以内,如果是复杂类型的参数,最好不要修改原来的参数
    • 返回值尽量保持和传入的参数相同的类型
  • 提高健壮性
    • 需要对参数做一些防御性的措施,特别是在类型上面的判断
  • 异常捕获
    • 需要考虑到出错时的备选方案,特别是封装的函数中调用了其他函数时,尤其需要考虑出现异常的情况下应该怎么做
  • 安全防护
    • 不需要向外暴露的功能,就不要向外暴露
    • 一旦向外暴露了,就是对外的一种承诺,暴露的接口一般都是会持续维护并且永久向下兼容的
    • 目前 ES2022 开始已经正式支持私有属性了
  • 避免原型入侵
    • 避免“猴子补丁”的做法,“别耍流氓,不是你的对象别动手动脚”

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/59523.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HTML 基础标签——分组标签 <div>、<span> 和基础语义容器

文章目录 1. `<div>` 标签特点用途示例2. `<span>` 标签特点用途示例3. `<fieldset>` 标签特点用途示例4. `<section>` 标签特点用途示例5. `<article>` 标签特点用途示例总结HTML中的分组(容器)标签用于结构化内容,将页面元素组织成逻辑区域…

hpp文件的使用

提示&#xff1a;文章 文章目录 前言一、背景二、过程2.1 编写代码2.3 问题探究 总结 前言 前期疑问&#xff1a; 本文目标&#xff1a; 一、背景 最近突然看到hpp文件&#xff0c;查了百度也没有hpp文件怎么写的说明&#xff0c;自己就试着写了下&#xff0c;写成功了。 而…

NPU 可不可以代替 GPU

结论 先说结论&#xff0c;GPU分为可以做图形处理的传统意义上的真GPU&#xff0c;做HPC计算的GPGPU和做AI加速计算的GPGPU&#xff0c;所以下面分别说&#xff1a; 对于做图形处理的GPU&#xff0c;这个就和NPU 一样&#xff0c;属于DSA&#xff0c;没有替代性。当然&#xf…

java: 题目:银行账户管理系统

题目&#xff1a;银行账户管理系统 设计一个简单的银行账户管理系统。要求实现以下功能&#xff1a; 1. 创建一个银行账户 BankAccount 类&#xff0c;该类具有以下属性&#xff1a;accountNumber&#xff08;账户号码&#xff0c;类型为 String&#xff09; balance&#xff…

2024年无线领夹麦克风十大品牌推荐,衣领麦克风哪个品牌好

声音&#xff0c;是沟通的桥梁&#xff0c;是信息的载体。在信息爆炸的时代&#xff0c;如何让自己的声音脱颖而出&#xff0c;成为了每个人都需要思考的问题。无线领夹麦克风&#xff0c;以其小巧便携、无线传输的特点&#xff0c;成为了众多声音爱好者的首选。市场上无线领夹…

【C++的vector、list、stack、queue用法简单介绍】

【知识预告】 vector的介绍及使用list的介绍及使用list与vector的对比stack的介绍和使用queue的介绍和使用priority_queue的介绍和使用 1 vector的介绍及使用 1.1 vector的介绍 vector是表示可变大小数组的序列容器和数组类似&#xff0c;vector也采用连续存储空间来存储元…

杨传辉:云+AI 时代的一体化数据库|OceanBase发布会实录

在 2024 OceanBase 年度发布会 上&#xff0c; OceanBase CTO 杨传辉进行了主题为《云和 AI 时代的一体化数据库战略思考》的演讲&#xff0c;本文为演讲实录&#xff0c;欢迎阅读。 视频观看可点击&#xff1a;https://www.oceanbase.com/video/9001825 各位 OceanBase 的客…

[大模型]视频生成-Sora简析

参考资料&#xff1a; Sora技术报告https://openai.com/index/video-generation-models-as-world-simulators/4分钟详细揭密&#xff01;Sora视频生成模型原理https://www.bilibili.com/video/BV1AW421K7Ut 一、概述 相较于Gen-2、Stable Diffusion、Pika等生成模型的前辈&am…

【docker入门】docker的安装

目录 Centos 7 添加docker 官方仓库到yum源 将 Docker 的官方镜像源替换为国内可以的 Docker 镜像源 安装docker 配置docker加速源 Ubuntu 创建 gpg key 目录 下载 gpg key 添加国内可用镜像源到 系统的 APT 仓库中 安装docker 配置加速源 Centos 7 添加docker 官方仓…

2024年【汽车修理工(高级)】考试总结及汽车修理工(高级)试题及解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 汽车修理工&#xff08;高级&#xff09;考试总结是安全生产模拟考试一点通总题库中生成的一套汽车修理工&#xff08;高级&#xff09;试题及解析&#xff0c;安全生产模拟考试一点通上汽车修理工&#xff08;高级&a…

vscode的一些使用心得

问题1&#xff1a;/home目录空间有限 连接wsl或者remote的时候&#xff0c;会在另一端下载一个.vscode-server&#xff0c;vscode的插件都会安装进去&#xff0c;导致空间增加很多&#xff0c;可以选择更换这个文件的位置 参考&#xff1a;https://blog.csdn.net/weixin_4389…

Qt(openCV的应用)

1. OpenCV简介 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉库&#xff0c;它提供了丰富的图像处理和计算机视觉功能。该库由英特尔公司发起&#xff0c;并在 BSD 许可证下发布&#xff0c;因此它是免费的&#xff0c;且开放源代…

【大语言模型】ACL2024论文-06 探索思维链COT在多模态隐喻检测中的应用

【大语言模型】ACL2024论文-06 探索思维链COT在多模态隐喻检测中的应用 目录 文章目录 【大语言模型】ACL2024论文-06 探索思维链COT在多模态隐喻检测中的应用目录摘要研究背景问题与挑战如何解决创新点算法模型1. 知识总结模块&#xff08;Knowledge Summarization Module&…

Kubernetes中的statefulset控制器

华子目录 statefulset控制器功能StatefulSet的组成部分 问题复现示例statefulset示例总结 statefulset控制器 功能 Statefulset是为了解决有状态服务的问题设计的StatefulSet将应用状态抽象成了两种情况拓扑状态&#xff1a;应用实例必须按照某种顺序启动。新创建的Pod必须和…

【Android】时区规则库tzdata更新

1 背景&#xff1a; 最近我遇到墨西哥城时区&#xff0c;会出现夏令时&#xff0c;而墨西哥城在2022年底都已经取消夏令时了。 看起来是要更新RK3588上的时区库&#xff0c;我的还是2021a&#xff0c;而现在都已经2024年了 这样能看版本号&#xff1a; cat /system/usr/sha…

【论文速看】DL最新进展20241106-图像分类、图像分割、时间序列预测

目录 【图像分类】【图像分割】【时间序列预测】 【图像分类】 [2024 解耦数据增强] Decoupled Data Augmentation for Improving Image Classification 机构&#xff1a;腾讯优图 论文链接&#xff1a;https://arxiv.org/pdf/2411.02592v1 代码链接&#xff1a;无 最近在图…

[MRCTF2020]PYWebsite1

如果输入的密钥是对的那么我们就直接跳转到flag.php页面 那么我们直接访问&#x1f60e;&#xff0c;他不带我们去我们自己去. 那就用XFF呗. 知识点&#xff1a; 定义&#xff1a;X-Forwarded-For是一个HTTP请求头字段&#xff0c;用于识别通过HTTP代理或负载均衡方式连接到W…

穿越文化与时空的回响——从廖问洁《红豆诗词选》看当代人文情怀

穿越文化与时空的回响 ——从廖问洁《红豆诗词选》看当代人文情怀 在快节奏的现代生活中&#xff0c;我们时常感到身心的疲惫&#xff0c;渴望找到一种能够洗涤内心的方式。而廖问洁的《红豆诗词选》就如同一股清泉&#xff0c;为我们带来了心灵的洗礼和慰藉。 这位来自94年的…

【sqlmap使用手册-持续更新中】

SQLMap 简介 SQLMap 是一个开源的渗透测试工具&#xff0c;用于自动化检测和利用 SQL 注入漏洞。它支持多种数据库&#xff0c;包括 MySQL、PostgreSQL、Oracle、SQL Server 等。 可以通过以下命令安装sqlmap git clone https://github.com/sqlmapproject/sqlmap.git最常用的…

吉利极氪汽车嵌入式面试题及参考答案

inline 的作用 inline 是 C++ 中的一个关键字。它主要用于函数,目的是建议编译器将函数体插入到调用该函数的地方,而不是像普通函数调用那样进行跳转。 从性能角度来看,当一个函数被标记为 inline 后,在编译阶段,编译器可能会将函数的代码直接复制到调用它的位置。这样做…