昨天我们介绍了 undefined
、null
、NaN
,也带到了如何将这些特别的值判断出来。
今天我们要来看更多,更多这些会让人感到困惑的判断式,以及它们在实战中误用会发生什麽惨况。
本篇文章会参考 JavaScript-Equality-Table,这是个视觉化又好懂的整理图表,非常推荐在感到困惑的时候来看看。
主要有两个主题:
这个会是我们通篇讨论的基础,一律使用严格相等( === )来判断。
一般相等的转型满复杂的,而且转型的规则不是那麽直觉,所以这边不特别讲,可以参考网站中的这张表格,一目了然:
按照上图,我们可能会有这种不太直觉的东西:
if (1 == "1") console.log(' 1 == "1" ');
if (true == "true") console.log(' true == "true" ');
看起来,第一个判断式如果成立,那第二个应该也要成立吧?都只是数值的外面框上一个双引号变成字串呀?
但结果只有第一个判断式成立,而第二个却不是。
执行结果
1 == "1"
诸如此类的神奇的转型规则还有很多,可以自行参照上图,但正是因为转型规则太复杂,要使用还得不断思考转型後是否如预期,背一大堆转型的例外。
因此我们才会说一律使用严格相等,果断放弃转型,确保等号两边一定是「基於型别相等的比较」,比较合乎逻辑。
Javascript 真的是有很多奇特的点,虽然严格相等已经比一般相等容易理解了,但还是有原则与例外,先来看网站上的图表:
是不是比一般相等乾净多了?
基本上严格相等就是要符合这两点,才会成立:
需特别注意这边所谓的「同值」,会根据数值本身是 primitive 或 non-primitive 而又有不同,所以应该这样写:
所以才会看到上图中,绿色的格子并没有填满 []
、{}
、[0]
这种 non-primitive 数值,代表以下都是不成立的:
console.log([] === []); // false
console.log({} === {}); // false
console.log([0] === [0]); // false
基本上每次产生一个 object,记忆体位址就是新的一个,除非把 object 放到变数存起来,否则 non-primitive 的比较通常都不会成立:
const person = { name: 'Joey' };
console.log(person === person); // true
console.log(person === { name: 'Joey' }); // false
经过拷贝後呢?因为拷贝其实是把原本 object 内的 key/value 复制到一个新的 object,所以仍然会有一个全新的记忆体位址产生:
const person = { name: 'Joey' };
const copiedPerson = { ...person };
console.log(person === copiedPerson); // false
就是 NaN
,请参考昨天的文章:
console.log(NaN === NaN); // false
判断是否相等时,除了 NaN
要用 isNaN()
来判断,其他全都用严格相等,并且特别注意 non-primitive 的比较。
在搞定了一般相等与严格相等之後,我们接着来看另一种常见的写法,就是更懒一点,连等号都不写了,直接把变数或数值塞在判断式里面:
if (300) conosle.log('会执行');
if ('Hello') conosle.log('会执行');
if ([1,2,3,4]) conosle.log('会执行');
这种判断式里面也会转型,不过比较单纯一点,单纯就是帮你转成 true 或 false。
会被转成 true 的数值,就叫做 truthy value,反之叫做 falsy value。
false
, 0
, -0
, 0n
, ""
, null
, undefined
, NaN
可以注意到,有些我们感觉应该要是空值的东西,其实并不是 falsy:
if ([]) conosle.log('会执行'); // 会执行
if ({}) conosle.log('会执行'); // 会执行
if ("0") conosle.log('会执行'); // 会执行
基於这个原因,如果直接把变数放进 if 判断式转型,有时候会出意外:
const arr = [
{ name: 'Jack', score: 70 },
{ name: 'Allen', score: 65 },
{ name: 'Alice', score: 60 },
{ name: 'Susan', score: 90 }
];
const failedArr = arr.filter(item => item.score < 60);
if (failedArr) {
const displayFailedArr = failedArr.map(item => item.name).join('、');
console.log(`低於 60 分名单:${displayFailedArr}`);
}
执行结果
低於 60 分名单:
上面的范例,没有人低於 60 分,所以其实第 7 行的 failedArr
是一个空阵列 []
。
而第 9 行的 if (failedArr)
就会变成 if ([])
,其实没有打算执行 if 里面的程序,但因为 []
是 truthy,所以还是会成立,最後印出来的名单就好像缺了什麽东西一样。
如果要判断阵列内是否有值,可以使用 Array.isArray(arr)
加上 arr.length > 0
两个判断:
const arr = [
{ name: 'Jack', score: 70 },
{ name: 'Allen', score: 65 },
{ name: 'Alice', score: 60 },
{ name: 'Susan', score: 90 }
];
const failedArr = arr.filter(item => item.score < 60);
if (Array.isArray(failedArr) && failedArr.length > 0) {
const displayFailedArr = failedArr.map(item => item.name).join('、');
console.log(`低於 60 分名单:${displayFailedArr}`);
}
执行结果
如果嫌 if 判断式内太冗长,可以考虑使用 lodash/isEmpty
,或者自行把判断式写成共用函式,这样就不会到处都要写这麽长:
const isArrayEmpty = (inputArr) => Array.isArray(inputArr) && inputArr.length === 0;
// ... 这边都跟上个范例一样
if (!isArrayEmpty(failedArr)) {
// ... 这边都跟上个范例一样
}
const arr = [
{ name: 'Jack', score: 70 },
{ name: 'Allen' },
{ name: 'Alice', score: 60 },
{ name: 'Susan', score: 0 }
];
arr.forEach(item => {
if (item.score) {
console.log(`${item.name}:${item.score} 分`);
}
});
执行结果
Jack:70 分
Alice:60 分
上面的范例 arr
有一些缺陷,有一些人是没有 score
的,面对这种通常会用到 filter
先筛选一遍,把有问题的资料筛掉,或者直接跑 forEach
用 if 来判断。
所以我们试着把 score
放进 if 判断式,期待能够把「有 score
的人都印出来」。
但在众多 number
里面,唯独 0 是 falsy (先不讨论 NaN
),所以如果直接把一个 number
放进 if 判断式,遇到 0 的 case 就会发生意外了(如上例 Susan
没有被印出来)。
因此,如果是要判断「是不是数字」,可以用 Number.isInteger()
判断整数,或 Number.isFinite()
判断有限数:
const arr = [
{ name: 'Jack', score: 70 },
{ name: 'Allen' },
{ name: 'Alice', score: 60 },
{ name: 'Susan', score: 0 }
];
arr.forEach(item => {
if (Number.isFinite(item.score)) {
console.log(`${item.name}:${item.score} 分`);
}
});
执行结果
Jack:70 分
Alice:60 分
Susan:0 分
同理,如果嫌 if 判断式内太冗长,也可以拉出去当共用函式:
const isNormalNumber = (inputNum) => Number.isFinite(inputNum);
// ... 这边都跟上个范例一样
arr.forEach(item => {
if (isNormalNumber(item.score)) {
// ... 这边都跟上个范例一样
}
});
接着回到我们昨天丢下的问题,有些人会想用 「!
」 这个运算子,来一次排除掉这三种空值的概念:
const a = undefined;
const b = null;
const c = NaN;
if (!a && !b && !c) {
console.log('用一个惊叹号似乎就可以判断了?');
}
如果看完前面的介绍,你应该就会发现,用这种方式除了会把 undefined
、null
、NaN
这三个滤掉,其实也会把所有 falsy 的数值都滤掉,包括了 false
、0
、""
。
而且如果是想要滤掉 []
、{}
这种,其实是 truthy 的值,反而会达不到效果。
关於这点我其实也找不到一个统一的解,毕竟真的是要看 if 到底要筛选出什麽样的资料,有时候 null
要可以过,有时候 []
不可以过。
因此我想,认真学会每个类别的判断方式,并且在适当的时机拿出来用,是比较有效的方式,自己也才真的知道在做什麽。
typeof
Number.isFinite()
Number.isNaN()
Array.isArray()
这个章节写完之後,才发觉 Javascript 身为一个弱型别语言,在类别转型方面真的是充满惊恐,对於新手来说或许是友善的,但对於半生不熟的老菜鸟们,想要深入去理解背後的判断原理,真的是需要下一番苦功。
转生之後
没了面孔与相貌
只剩灵魂的连结
JavaScript-Equality-Table
Equality_comparisons_and_sameness
<<: D20/ 怎麽在 compose 与 non-compoe 间传资料 - Compose Side-Effect part 2
当你开始想要为你的产品或是服务广告,注册完 Google Ads 後,需要填入你的网站资讯,让我们逐...
制作出Candy Crush的游戏开发商King在今年3月时宣布推出拥有25年丰富历史、陪伴全球许多...
昨天刚打完疫苗,没想到晚上所有症状通通都来了,整个人烂得跟什麽一样,有够惨 今天就把简单的部分贴一贴...
看完这篇文章你会得到的成果图 此篇文章的范例程序码 github https://github.co...
要注意什麽,才能更顺利地提供可执行的设计稿 !? 一、参考热门的设计框架、典范大厂的 Web 框架或...