天天都在写的JS判断语句,你真的了解吗?

在真实的世界里,人与人老是有那么一点不信任,“争是非,辨明理”是常事。在编程开发领域中,一样须要对变量的存在、类型、真伪进行校验,几乎天天都是在和if === typeof 打交道。可是你真的弄懂了这些判断语句吗?编程

1、真假判断

“真亦假时假亦真,无为有处有还无。”——《红楼梦》后端

if( x )

在if语句中,会触发Boolean上下文,JavaScript会将值强制类型转换为布尔值,再执行判断。 当 x is Trusy时,if语句命中。markdown

在JavaScript中,存在七种假值(又称虚值),分别为undefined null "" false 0 0n NaN。其中0nESFuture中新增的一种假值。函数

undefined是最多见的假值,当判断在一个对象中是否存在某个字段时,使用的就是undefined空值判断:工具

let people = {name}
if(people.age){  // 等价于 people.age === void 0
		// ...
}
复制代码

建议在代码中使用void 0来代替undefined,缘由是undefined不是JS的保留字,并且void 0undefined字符少。 事实上,很多JavaScript压缩工具在压缩过程当中,将undefined使用void 0代替。oop

2、相等判断

雄兔脚扑朔,雌兔眼迷离。双兔傍地走,安能辨我是雄雌。——《木兰诗》spa

在JavaScript中,有两个相等运算符来判断两个操做数是否相等,一个是==相等运算符,另外一个是===全等运算符。它们最大区别在于对类型的宽容度。prototype

x === y

===全等运算符对左右两边的孩子(操做数)是严厉的,就像一个严厉的父亲。 ===全等运算符首先会检查两边的操做数类型是否一致,而后再检查其值。具体流程以下:code

  1. 两边类型不一样,返回false;orm

  2. 类型相同,比较其值:

    a. 双方都是number类型:

    有一方是`NaN`,返回 false;
     一方是+0,一方是-0,返回 true;
     双方值相同,返回 true;
     其余,返回 false;
    复制代码

    b. 双方都是string类型:

    对双方挨个比较字符,若字符顺序数量相同,返回 true;
     其余,返回 false;
    复制代码

    c. 双方均是boolean类型:

    值相同,返回 true;
     其余,返回 false;
    复制代码

    d. 双方均是object类型:

    若引用地址相同,返回 true;
     其余,返回 false;
    复制代码

x == y

==相等运算符对左右两边的孩子(操做数)是宽容的,就像一个慈祥的母亲。 当对两个操做数进行比较时,JavaScript会先对其中一个操做数隐式类型转换,那么当两个操做数进行比较时,都作了些什么?咱们须要从ECMA标准中寻找答案。

基本流程以下:

  1. 若是type(x) === type(y) , 此处比较过程与===相同;
  2. 若是x is nully is undefined,则返回true;反之亦然;
  3. 若是x is stringy is number,则执行ToNumber(x) == y;反之亦然;
  4. 若是x is boolean,则执行ToNumber(x) == y;反之亦然;
  5. 若是x is object,则执行ToPrimitive(x) == y;反之亦然;
  6. 都不是以上状况,返回false;

简要概述:类型相同,拼家世拼内涵;类型不一样,先平等后比较;nullundefined是一家,number一家独大,其余都要向我靠;object你别骄傲,降成平民再比较。

在上面过程当中咱们看到,若是两个操做数的类型相同,则比较过程与===是一致的。若是类型不一样,那么就须要隐式类型转换为类型相同的状况后再比较。

上面提到了ToNumberToPrimitive的转换过程就是隐式的类型转换。那么它们究竟作了些什么呢?

ToNumber(x):

1. x is undefined, return NaN;
2. x is null, return 0;
3. x is number, return x;
4. x is boolean, return x === true ? 1 : 0;
5. x is string, return [ToNumber Applied to the String Type](http://www.ecma-international.org/ecma-262/5.1/#sec-9.3.1);
6. x is object, return ToNumber(ToPrimitive(x))
复制代码

ToPrimitive(x):

1. x is object, return x.valueOf() or x.toString();
2. x is non-object, return x;
复制代码

在ECMA标准中,ToPrimitive(x)能够指定第二个参数PreferredType = "number" | "string",区别在于当x is object时,先调用valueOf()方法仍是先调用toString()方法,默认"number"。 至此,咱们大体知道了隐式类型转换都作了哪些“幕后工做”了。

通过以上分析,我得出的结论是:能不用==就不要用它,隐式类型转换规则较多,容易产生意想不到的结果。

固然,==也不是一无可取的,那么什么时候用==呢? 当接口返回的值多是数字,多是字符串,而你又不能肯定时,好比obj.chance == 1; 然而,我仍然鼓励你积极和你的后端小伙伴沟通一下,明确下发字段的类型比较稳妥呢!

Object.is(x, y)

在ES6,新增了一种判断两个操做数是否相等的方法,也是最为严格的判等方式——Object.is()。使用它,能够确保两个操做必定是相等的,容不得一点沙子。

Object.is()===全等运算符的区别在于对待NaN+0-0的断定有所不一样:

// NaN
NaN === NaN  // false
Object.is(NaN, NaN)  // true

// +0 -0
+0 === -0  // true
Object.is(+0, -0)  // false
复制代码

3、类型判断

我是谁?我从哪里来?我要到哪里去?——《人生三问》

对变量类型的判断是代码健壮的基础,只有正确判断变量的类型,才能够安心调用变量上部署的方法,如string.charAt() array.map()等。那么有哪些方法能够判断类型呢?

typeof x

typeof是JavaScript内置的一个用于判断类型的一元操做符,它返回一个表示类型的字符串。下表总结了typeof可能的返回值:

typeof x Result
Undefined "undefined"
Boolean "boolean"
Number "number"
String "string"
Symbol "symbol"
BigInt "bigint"
Function "function"
Null "object"
其余 "object"

从上表能够看出,typeof判断基本类型时很是合适,能够正确返回咱们指望的类型字符串。而操做数是对象或者null时,则统一返回"object"字符串,则须要其余的方式来进一步判断类型。

所以,能够得出一个结论:若是你明确变量是基础类型时,请使用typeof操做符来判断类型。其余类型typeof则有些力不从心了。

({}).toString.call(x)

当须要判断更多类型的时候,toString老大哥就勇敢站出来了,须要注意的是toStringObject原型链上的方法。其实,每一个内置对象都有一个toString方法,不一样对象的toString的行为都是不同,但它们都是继承自Object{}.toString.call(x)返回一个字符串[object Type],其中Type可能取值为 Number String Boolean Function Undefined Null Object Symbol Date Math RegExp ...

ECMA标准这样描述:

  1. x is undefind, return [object Undefined];
  2. x is null,return [object Null];
  3. let O = toObject(x);
  4. return "[object " + classOf(O) + "]"

({}).toString.call(x)能够很方便的判断JS的内置对象类型,它进一步细化了object分支里的大部分类型,适用于须要更多类型判断场景,如下是type工具函数的示例:

function type(obj) {
    let toString = Object.prototype.toString,
        typeReg = /\[object\s([A-Z][a-z]*)\]/,
        matchArr = toString.call(obj).match(typeReg)

    return matchArr ? matchArr[1].toLowerCase() : ''
}
复制代码

对象的toString 方法可使用Symbol.toStringTag 这个特殊的对象属性进行自定义输出(详细可参考ES6——Symbol)。举例说明:

let user = {
    [Symbol.toStringTag]: 'User'
}
console.log(({}).toString.call(user)  // [object User]
复制代码

如此你能够为你本身的对象或类定义个性化的类型字符串。宿主环境的大部分环境相关对象用的就是这个原理:

console.log(({}).toString.call(window))  // [object Window]
复制代码

x instanceof XXX

typeof + toString的强强联合,已经包揽了80%的类型判断场景,但仍有20%的自定义对象类型场景无能为力,例如你有一个自定义类Person:

class Person {}
let person = new Person()
typeof person // "object"
{}.toString.call(person) // "[object Object]"
复制代码

此时,你须要instanceof运算符来检测构造函数的 prototype 属性是否出如今 person 实例对象的原型链上。 有点绕口,至于何为原型链,就不在这里展开了。 x instanceof X,字面意思 x 是否由 X 实例化,返回 true/false。

person instanceof Person // true
// or
Object.getPrototypeOf(person) === Person.prototype
复制代码

仍是instanceof比较直观,😃

4、显式类型转换

隐式类型转换是一把双刃剑。它帮开发者自动转换类型,省去麻烦;有时候又出其不意,莫名其妙。显式类型转换架起基础类型防线。

+x or Number(x)

+一元正号运算符,计算操做数的数值,若是这个操做数不是数值,则尝试将其转换为数值。根据标准描述,+x其实就是执行的toNumber(x)操做:

+x Result
Boolean 0 / 1
Number 值自己
String 转换为数值
Null +0
Undefined NaN
Symbol throw a TypeError
Object ToPrimitive(x),再重复上面步骤
通常是执行valueOf 或 toString方法

同理,Number(x)执行的过程也是toNumber(x)的过程。

''+x or String(x)

'' + x中的加号不一样于 +x中的加号,此处的加号的做用是字符串的拼接。它会显示的将不是字符串类型的操做数转换为字符串后拼接,即toString操做。 从toString(x)小结中,咱们知道,在对象上运行toString方法时,会在原型链上查找到最近的toString方法运行。通常而言,继承自Object的其余对象都会实现本身的toString方法。

"" + 34  // 等价于 "" + (34).toString()
"" + true  //等价于 "" + (true).toString()
"" + {}  //等价于 "" + ({}).toString()
复制代码

直接调用String构造函数来显示转换类型,和""+x基本一致。但ES6中新增的基本类型Symbol则不适用直接调用""+x方式来转换为字符串,会异常报错。

// Error
"" + Symbol('JS')  // Uncaught TypeError: Cannot convert a Symbol value to a string at <anonymous>

String(Symbol('JS'))
// or
(Symbol('JS')).toString()
复制代码

看起来,String(x)要比''+x靠谱的多。

!!x

双重非!!运算符显式将任意值强制转换为布尔值。 还记得刚才提到的JS中的七个假值吗?即,x是七个假值时,!!x必定返回的 false,其余状况都返回的 ture。

总结

  1. JS中存在七个假值(Falsy),undefined null "" false 0 0n NaN
  2. ===全等运算符比==相等运算符更加严格,且更符合相等性判断预期;
  3. typeoftoString的类型判断方法能够覆盖80%的类型判断场景,够用;
  4. 显示类型转换能够减小隐式类型转换的不肯定性;

ECMA参考标准

相关文章
相关标签/搜索