让前端飞

【JS/TS】一些容易导致错误的用法(一)

2024-01-14  本文已影响0人  来一斤BUG

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

所以平常尽量使用===!==,建议只有在nullundefined的判断时使用==!=

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有很多问题,比如:

  1. 可以重复声明
    var a = 1;
    if (true) {
        var a = 2;
    }
    console.log(a); // 2
    
  2. 会变量提升
    console.log(a); // undefined
    var a = 1;
    
  3. 作用域问题
    for (var i = 0; i < 3; i++) {
        setTimeout(() => {
            console.log(i); // 3, 3, 3
        }, 100);
    }
    
  4. 全局变量自动添加到window上,污染全局
    var a = 1;
    console.log(window.a); // 1
    

所以尽量使用letconst

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""NaNnullundefined都会被当成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
    }
}
上一篇 下一篇

猜你喜欢

热点阅读