1. 不要使用undefined判断Map中是否存在某个键
js中map取值时如果取不到或者本身的值为undefined,都会返回undefined,所以通过map.get(key) === undefined
的方式判断是否存在某个键容易埋下隐患。正确的方式是使用map.has(key)
。
const map = new Map();
map.get("a") === undefined; // true
map.has("a"); // false
同理,判断对象中是否存在某个键时不应该使用obj[key] === undefined
,应该使用obj.hasOwnProperty(key)
。
const obj = {};
obj["a"] === undefined; // true
obj.hasOwnProperty("a"); // false
2. 尽量不要使用==
和!=
==
和!=
会进行类型转换,容易导致一些奇怪的问题,比如:
[] == ![]; // true
const a = {};
const b = {};
a == !b; // false
// 空数组会被转换成原始值(空字符串),![]为false,"" == false -> "" == 0 -> 0 == 0
// 但是空对象不会被转换成空字符串,所以a == !b为false
所以平常尽量使用===
和!==
,建议只有在null
和undefined
的判断时使用==
和!=
。
3. 不要使用for...in
遍历数组
for...in
会遍历对象的所有可枚举属性,包括原型链上的属性,所以不要使用for...in
遍历数组,而是使用for...of
或者forEach
。
Object.defineProperty(Array.prototype, "foo", {
value: "bar",
enumerable: true, // 设置为了可枚举
});
const arr = [1, 2, 3];
for (const i in arr) {
console.log(i); // 0, 1, 2, "foo"
console.log(arr[i]); // 1, 2, 3, "bar"
}
for (const ele of arr) {
console.log(ele); // 1, 2, 3
}
// Array的原型链上有一个foo属性,所以使用for...in遍历数组时会遍历到foo属性
4. 使用undefined
有风险
undefined
不是一个保留字,在局部作用域下可以被赋值,比如:
function foo() {
const undefined = 1;
console.log(undefined); // 1
}
foo();
所以在局部作用域下使用undefined
有风险,可以使用void 0
代替undefined
。
function foo() {
const undefined = 1;
console.log(void 0); // undefined
}
foo();
5. 尽量不要使用var
var
有很多问题,比如:
- 可以重复声明
var a = 1; if (true) { var a = 2; } console.log(a); // 2
- 会变量提升
console.log(a); // undefined var a = 1;
- 作用域问题
for (var i = 0; i < 3; i++) { setTimeout(() => { console.log(i); // 3, 3, 3 }, 100); }
- 全局变量自动添加到window上,污染全局
var a = 1; console.log(window.a); // 1
所以尽量使用let
和const
。
6. 不要使用parseInt()
将number类型的浮点数转换成整数
parseInt()
的参数为字符串,如果传入了其他类型,会被先转换成字符串,当一个浮点数的位数比较多时,转换成字符串是会变成科学计数法,比如:
0.0000001.toString(); // "1e-7"
parseInt(0.0000001); // 1
0.0000000000000005.toString(); // "5e-16"
parseInt(0.0000000000000005); // 5
正确的方式是使用Math.floor()
、Math.round()
或者Math.ceil()
。
Math.floor(0.0000001); // 0
Math.floor(0.0000000000000005); // 0
7. 注意第三方大数库的数据类型转换
某些第三方大数库可能会提供大数转换成number类型的方法,但是如果数字过大,转换成number类型时会丢失精度,比如:
// 随便用一个库举例子
const a = new BigNumber("9007199254740993");
a.toNumber(); // 9007199254740992
由于9007199254740993
超过了Number.MAX_SAFE_INTEGER
(9007199254740992
),所以转换成number类型时会丢失精度,所以如果想将这个数显示出来,应该使用toString()
。(这个问题我已经在工作中遇到过了)
8. 分清楚||
和??
||
和??
都可以用来做空值合并,但是有一些区别。||
实际意思是“或”,当其左边的值为false
时,会返回右边的值,所以0
、""
、NaN
、null
、undefined
都会被当成false
;??
的意思是“空值合并”,只有当其左边的值为null
或者undefined
时,才会返回右边的值。
const a = 0;
const b = a || 1;
console.log(b); // 1
const c = 0;
const d = c ?? 1;
console.log(d); // 0
const e = null;
const f = e || 1;
console.log(f); // 1
9. ??
和==
(===
)的优先级问题
??
的优先级比==
(===
)底,所以a ?? b == c
等价于a ?? (b == c)
,而不是(a ?? b) == c
。如果要将??
用在相等判断中,大概率需要加括号,比如a == (b ?? c)
。
const a = {c: 0};
console.log((a.b ?? 1) === 1); // true
console.log(a.c ?? 1 === 1); // 0
// 由于??的优先级比===底,所以先计算1 === 1,结果为true
// 然后由于a.c存在且为0,所以1 === 1的值被忽略,最终结果为0
我平时开发经常使用a?.b ?? c
处理空值,经常直接和===
连用,有时候会忘记加括号,导致出现一些奇怪的问题,所以这个问题需要注意一下。
10. ts中枚举类型的遍历问题
ts中枚举类型的值可以是字符串,也可以是数字,但是如果枚举类型的值是数字,那么枚举类型的值会在编译时被添加到枚举类型的属性上(如果值为字符串则没有这个问题),比如:
enum Foo {
a = 1,
b = 2,
}
console.log(Foo); // {"1": "a", "2": "b", a: 1, b: 2}
所以如果要遍历枚举类型,应该做一些额外的判断,比如:
enum Foo {
a = 1,
b = 2,
}
for (const key in Foo) {
if (typeof Foo[key] === "number") {
console.log(key); // a, b
}
}
for (const key in Foo) {
if (isNaN(Number(key))) {
console.log(key); // a, b
}
}
网友评论