今天看啥  ›  专栏  ›  Yzz

一言难尽的 JS 强制转换

Yzz  · 掘金  ·  · 2019-07-29 17:59
阅读 17

一言难尽的 JS 强制转换

题目来源:javascript-puzzlers.herokuapp.com/。

先试试下面六道题目

let a = [0];
if ([0]) {
  console.log(a == true);
} else {
  console.log("wut");
}
//log false
复制代码
'5' + 3; // log "53"
'5' - 3; // log "3"
复制代码
let a = [1,2];
let b = [3,4];

a + b; // "1,23,4"
复制代码
2 == [[[2]]]

// log true
复制代码
[] == []
// log false

[] == ![]
// log true
复制代码
let a = [1, 2, 3];
let b = [1, 2, 3];
let c = [1, 2, 4];

a == b;
a === b;
a > c;
a < c;

// log false, false, false, true
复制代码

细心的你一定看出来了,上述五道题目都与 JS 的类型相关,再细一点的话,应该是与类型转换相关。

引用 You Don't Know JS 的结论,我们可以将类型转换分为:隐式转换(Implicit coercion)、显式转换(Explicit coercion)。

显式转换

我们主动的将一些变量按照我们的期望进行转变。那么有几种方法呢?

ToNumer

参考 www.ecma-international.org/ecma-262/5.…

其他类型转换为 number 类型的几种方法

输入类型结果
UndefinedNaN
Null+0
Boolean如果参数是 true,结果为 1。如果参数是 false,此结果为 +0。
Number结果等于输入的参数(不转换)。
Stringstring ---> number
Object object ---> number
  • string ---> number

    这个过程类似于判断字符串是否为一个字符串数值常量,例如

    let a = '1'
    let b = 'hello world'
    
    console.log(Number(a)) // 1
    console.log(Number(b)) // NaN
    console.log(+a) // 1
    console.log(+b) // NaN
    复制代码

    其中,在开源的 JS 社区中一般将 + 视为一个 明确的 强制转换形式。

    同时,这个转换过程中也包含着一些细节,类似于 Number("") // 0 等,具体可见 www.ecma-international.org/ecma-262/5.…

Number(a) 可以的话,那么 new Number(a) 可以么?可见 juejin.im/post/5d37ad…

  • object ---> number

    这个过程可以分为两部,object --> ToPrimitive(object) --> number

    ToPrimitive 运算符把其值参数转换为非对象类型,也就是调用其内部的 [[DefaultValue]] 方法。

    [[DefaultValue]] (hint)hint期望类型(很关键):

    • 如果 hintnumber:先 Object.prototype.valueOf ,如果结果为原始值,则返回,否则调用 Object.prototype.toString
    • 如果 hintstring:先 Object.prototype.toString ,如果结果为原始值,则返回,否则调用 Object.prototype.valueOf
    var a = {
        valueOf: function() { return 42; },
        toString: function() { return 4; }
    };
    
    console.log(Number(a)) // 42
    console.log(String(a)) // 4
    复制代码

ToString

www.ecma-international.org/ecma-262/5.…

输入类型结果
Undefined"undefined"
Null"null"
Boolean如果参数是 true,那么结果为 "true"。
如果参数是 false,那么结果为 "false"。
String结果等于输入的参数(不转换)。
Numbernumber--->string
Object见上文。
  • number ---> string

    string ---> number 类似,大体上是将字符串数值常量转为字符串,其中有些科学计数法的转化细节

    String(1e-1) // "0.1"
    复制代码

    还有一些 NaN 的细节,见参考。

ToBoolean

www.ecma-international.org/ecma-262/5.…

相对于前两个,ToBoolean 的规则较为明确

输入类型结果
Undefinedfalse
Nullfalse
Boolean结果等于输入的参数(不转换)。
Number如果参数是 +0, -0, 或 NaN,结果为 false ;否则结果为 true。
String如果参数参数是空字符串(其长度为零),结果为 false,否则结果为 true。
Objecttrue

除了几个特殊的 undefinednull0""NaN,其他的一律转为 true。主要的转换方法,Boolean 以及 !!

let a = "0";
let b = [];
let c = {};

let d = "";
let e = 0;
let f = null;
let g;

Boolean( a ); // true
!!a; 		  // true
Boolean( b ); // true
!!a;		  // true
Boolean( c ); // true
!!c;          // true

Boolean( d ); // false
!!d;		  // false
Boolean( e ); // false
!!e;          // false
Boolean( f ); // false
!!f;          // false
Boolean( g ); // false
!!g;          // false
复制代码

隐式转换(一般伴随着表达式、操作符

当我们在使用条件表达式或者一些运算、相等操作符时,如果不注意,就会发生隐式的类型转换。

判断语句

主要有一下五种情况。

  1. 在一个if (..)语句中的条件表达式;
  2. 在一个for ( .. ; .. ; .. ) 中间的条件表达式;
  3. while (..)do..while(..) 循环中的条件表达式;
  4. ? :三元表达式中的条件表达式(第一个子句);
  5. ||(“逻辑或”)和&&(“逻辑与”)操作符左手边的操作数。

在日常的开发中,我们通常不会将判断语语句中的 condition 的结果强制转换为 boolean,例如

let a = 42
let b = {}

if (a) {
	console.log( "yep" ); // yep
}

while (b) {
	console.log( "forever running" );
}
复制代码

这时我们已经默认使用隐式转换来简化代码。而在没有默认参数的 ES5,我们也会使用,来为函数添加默认参数

let a;
let b = 42;

a || b;		// 42
复制代码

或者利用 && 来简化 if 语句

let a = 42;
let b = "abc";

a && b;		// "abc"
复制代码

所以在第一道题目中

let a = [0];
if ([0]) {
  console.log(a == true);
}
// [0] 会转化为 true
复制代码

+- 运算符

www.ecma-international.org/ecma-262/5.…

根据ES5语言规范,如果两个操作数之一已经是一个 string,或者下列步骤产生一个 string 表达形式,+ 将会进行连接。

所以题目中

'5' + 3; // log "53"
复制代码

但,如果当 + 的两个操作数之一收到一个object(包括array)时,它首先在这个值上调用 ToPrimitive,例如

'5' + [1,2,3] // "51,2,3"
复制代码

会将 [1,2,3] 转为了 string 调用 toString,至于原因见上文。

所以第三题目中,ab 分别被转化为 "1,2" 以及 "3,4"

let a = [1,2];
let b = [3,4];

a + b; // "1,23,4"
复制代码

而一元 - 运算符将其操作数转换为 number 类型,所以

'5' - 3; // log 2
复制代码

== 以及 === 相等操作符

www.ecma-international.org/ecma-262/5.…

最为常考,开发用的少,面试用的多的 == 来了,这里引用一句You Don't Know JS 中的一句话

"== allows coercion in the equality comparison and === disallows coercion."

===== 的本质区别在与 coercion 也就是说本质在于是否发生了强制类型转换。那么 == 是如何转化类型的呢?

x == y 为例(篇幅所限省略,x、y 类型相同的规则,见参考地址)

  • xnullyundefined, 返回 true;
  • xundefinedynull,返回 true;
  • 若 Type(x) 为 number 且 Type(y) 为 string,返回 x == ToNumber(y) 的结果;
  • Type(x) 为 string 且 Type(y) 为 number,返回 ToNumber(x) == y的结果;
  • Type(x) 为 boolean,返回 ToNumber(x) == y 的结果;
  • Type(y) 为 boolean,返回 x == ToNumber(y) 的结果;
  • Type(x) 为 stringnumber,且 Type(y)object,返回 x == ToPrimitive(y) 的结果;
  • Type(x) 为 object 且 Type(y) 为 stringnumber, 返回比较ToPrimitive(x) == y 的结果。

看似很多,其实本质上满足两条:

  • 如果 x,y 的类型为非引用类型,将其转化为 number 进行对比;
  • 如果 x,y 的类型为引用类,将调用 ToPrimitive 转化为基础值进行对比。
2 == '2' // true
1 == true // true
0 == false // true
复制代码

而题目中,[[[2]]] 需要获取 ToPrimitive 的结果

2 == [[[2]]]
// [[[2]]] 调用 valueOf() 得到 [Array(1)] 非基础类型
// [[[2]]] 调用 toString() 得到 2
// log true
复制代码
[] == []
// [] 与 [] 类型都为 object,所以比对是否引用同一个对象
// false

[] == ![]
// ![] 强制转化为 boolean,false,
// ![] 转化为 boolean,所以 [] 需要利用 ToPrimitive 得到 ""
// 原式变为 "" == false,false 转化为 0,而 "" 也变为 0
// log true
复制代码

同样还有 ><

www.ecma-international.org/ecma-262/5.…

let a = [1, 2, 3];
let b = [1, 2, 3];
let c = [1, 2, 4];

a == b; // 非同一引用
a === b; // 非同一引用
a > c; // a 转化为 "1,2,3",c 转化为 "1,2,4"
a < c;

// log false, false, false, true
复制代码



原文地址:访问原文地址
快照地址: 访问文章快照