JavaScript最佳实践
干净代码(clean code)
includes优化多条件判断
replace/match/exec使用分组
replace传递回调函数以获得更细粒度的操作。
使用箭头函数
使用扩展运算符
使用结构赋值
使用函数默认值
使用模板字符串
使用promise和async function处理异步
控制反转
可读性
JSdoc
Eslint
Typescript
稳定性
为展开运算符使用默认值
使用可选链
Reduce始终提供初始值
如果我们要合并数组内的元素,例如这样的结构:
很容易想到这样的代码
但是这隐藏着一个巨大的坑,因为当数组仅有一个元素(无论位置如何)并且没有提供初始值 initialValue,或者有提供 initialValue但是数组为空,那么此唯一值将被返回且 callbackfn
不会被执行。
因此,当上面的例子中arr
如果只有一个元素时,那么只能得到那个元素本身,而无法得到我们想要的数组。
此外,当数组为空时调用reduce
方法,会触发报错。
要避免出现这两个问题,就需要传递一个初始值,可以使用这个方法来解决上个例子中的问题。
提供初始值后,无论是原数组是一个空数组,抑或是数组中只有一个元素,都能得到一个数组结构的返回值,且不会触发异常(原数组是空数组时)。
因此使用reduce时通常传递初始值更安全。
Sort排序传递回调
在JavaScript中,Array.prototype.sort()
方法用于对数组元素进行排序,默认行为(即不传递回调函数时)是将元素转换成字符串再进行比较。这可能会出现以下问题:
排序结果不符合预期:由于默认的排序算法只是将元素转换成字符串后进行比较,并不考虑数字大小或其他特殊情况,因此可能会导致排序结果不符合预期。
排序稳定性不确定:在某些JavaScript引擎中,使用默认排序算法在排序相等元素时可能会导致排序稳定性不确定,即在排序前和排序后两个相等的元素的位置关系可能会发生改变。自 ES10(EcmaScript 2019)起,规范 要求
Array.prototype.sort
为稳定排序,但是ES10(EcmaScript 2019)以前没有要求稳定性。
例如:
如果指明了 compareFn
,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:
如果
compareFn(a, b)
大于 0,b 会被排列到 a 之前。如果
compareFn(a, b)
小于 0,那么 a 会被排列到 b 之前;如果
compareFn(a, b)
等于 0,a 和 b 的相对位置不变。备注:ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);compareFn(a, b)
必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。
如果要比较数字而非字符串,则可以传递这样的回调函数来实现排序:
因此,如果要比较数字大小或其他特殊情况,则建议传入回调函数来确保排序结果的正确性和稳定性。如果必须使用默认排序算法,请确保测试充分,并在使用时注意兼容性问题。
Array.prototype.join遇到只有一个元素的数组时
当数组只有一个元素时,Array.prototype.join
方法不会对元素做处理,而是直接返回这个元素,这意味着如果想对数组进行某些处理,那么一定要记得考虑只有一个元素的情况。
例如你想实现为数组里每个元素都加上一个标记和换行符的效果时,你可能会这么写:
但是当数值只有一个元素时,就不会做任何处理:
有些时候join函数不对空数组进行处理可能是更合理的,但是有时候我们却不想这样,哪怕只有一个元素,我们也希望能够处理一遍。
解决的方法很简单,可以判断一下数组的长度,然后分别处理不同的情况。但是有一个更便捷的方法:先利用map
先处理一遍,然后再利用join
进行拼接。这样无论数组的长度如何,都可以正常地进行处理。
避免使用arguments
arguments
是类数组,拥有length
属性且以属性索引以0开始,类数组不拥有数组的任何方法,例如forEach
、map
等。
另一个问题是arguments在箭头函数中不存在,在箭头函数中访问arguments
会返回undefined
。
使用剩余参数替代,或者扩展运算符/Array.from将其转换成真正的数组是更好的选择,除非是需要在不支持ES6的浏览器上运行。如果需要统一在函数和箭头函数中都能访问到所有参数,那应该使用剩余参数。
禁用arguments.callee
在严格模式 (en-US)下,第 5 版 ECMAScript (ES5) 禁止使用
arguments.callee()
。当一个函数必须调用自身的时候,避免使用arguments.callee()
,通过要么
给函数表达式一个名字,要么使用一个函数声明。
arguments.callee
引用当前执行函数本身,当这个函数是一个匿名函数时就很有用了,可以直接通过arguments.callee
执行自身,以实现递归调用。
这是一个非常糟糕的方案,其缺点在于不能实现尾调用、内联,并且每次递归调用会获取到一个不同的 this
值。
而解决的方式也很简单,为使用函数名称进行调用即可。
避免使用arguments.callee
的优势在于:
该函数可以像代码内部的任何其他函数一样被调用
它不会在外部作用域中创建一个变量 (除了 IE 8 及以下)
它具有比访问 arguments 对象更好的性能
为for…in添加hasOwnProperty
判断构造函数是否被new调用
JavaScript中的构造函数(或者说类)也可以作为普通函数被调用,虽然可以通过函数首字母是否大写的形式来判断是否是构造函数,但是最好的方法还是应该在代码中硬性规定一定要被new调用,以此避免可能的错误。
检查是否被new调用可以使用new.target
。ES6
为 new
命令引入了一个 new.target
属性,该属性一般用在构造函数之中,返回 new
命令作用于的那个构造函数。如果构造函数不是通过 new
命令或 Reflect.construct()
调用的,new.target
会返回 undefined
。
每次手动写判断方法太过麻烦,ES6的Class是构造函数的语法糖,Class规定必须要被new调用,否则将会报错。
因此可以使用Class语法替代原本的构造函数写法,如果要使用构造函数,那么最好加一层判断,避免被直接调用。
冻结对象
使用Symbol
Symbol最常用于避免对象冲突,另外一点就是常见的遍历方法不会遍历到symbol类型。
属性描述符
Array.prototype.fill不要传递引用值为参数
使用Array.prototype.fill时不要直接将一个引用值作为参数传入,比较常见的例子是快速创建一个嵌套数组:
let result = new Array(10).fill([])
这将导致result中所有的元素都指向同一个数组,当你改变result
中某个元素时,除非破坏这种引用关系,否则其他的元素都会一起改变。
另一种想法是通过map
来遍历依次赋值:
但是map这种ES6遍历方法会跳过数组中所有空的(undefined)元素,因此无法达到我们想要的效果。
有两种解决办法:
第一种先通过fil填充,再调用map依次赋值,第二种方法是通过Array.from来创建。
Match和MatchAll使用捕获组
String.prototype.match在开启全局匹配(g)和不开启全局匹配时,返回的结果是不一样的,不开启全局匹配时,返回一个类数组结构,包含input
、groups
等字段,而在开启全局匹配时,返回的却是一个包含所有匹配结果的数组结构,此时不包含input
、groups
等字段。
在需要使用捕获组时,建议在不需要开启全局匹配时使用match,在需要开启全局匹配时使用matchAll。
需要注意的是,matchAll返回的是一个迭代器,并且matchAll必须开启全局匹配模式(g),否则会报错。
技巧
获取随机数
判断类型
最后更新于