资讯专栏INFORMATION COLUMN

透彻研究Javascript类型转换

dailybird / 2932人阅读

摘要:注释空数组空对象转换为布尔型也是坑。系统会在自动类型转换的时候调用他们,所以我们通常不需要手动调用他们。严格相等不存在类型转换,对于类型不同的两个值直接返回。

Javascript 中有5种基本类型(不包括 symbol),以及对象类型,他们在不同的运算中会被系统转化为不同是类型,当然我们也可以手动转化其类型。

Javascript 类型转换中的坑极多,就连 Douglas Crockford 在 《Javascript: The Good Parts》一书中也极力 "吐槽" 。下面我们来自习研究一下这个部分,希望不要把自己绕晕。

typeof 运算

在解释各个类型之前,我们需要理解 typeof 运算。该运算得到对象的类型:

</>复制代码

  1. typeof 2; //number
  2. typeof "abc"; //string
  3. typeof true; //boolean
  4. typeof undefined; //undefined
  5. typeof new Date(); //object
  6. typeof null; //object
  7. typeof NaN; //number, NaN 即 Not a Number,表示一个非法值。
  8. typeof [1,2,3]: //object
  9. typeof /^d*$/; //object
  10. function fn(){} //定义一个函数
  11. typeof fn; //function

通过上面例子我们可以很明显的看到,除了基本类型以外的类型,都是对象,但是有例外:null 的 typeof 值是 "object" 【坑1】, 函数的 typeof 值是 "function" ! (函数对象的构造函数是 Function,也就继承了 Function 的原型)【坑2】

</>复制代码

  1. 注:JS 中 typeof 根据变量存储单元特性判断变量类型,其中当变量的前三位都是 0, 这个变量就是对象类型。但不幸的是,null 的所以位都是 0,结果就被识别成对象了。(摘自《你不知道的 JS 上卷》)

而且我们不难发现,NaN 的类型也是 "number",这个地方也是矛盾十足【坑3】

注意:本文的测试在现在最新浏览器上进行,老版本浏览器可能有所不同。比如Safari 3.X中typeof /^d*$/;"function"【坑4:兼容性复杂】。

不是所有对象都是返回 "object",而且还有 null 捣乱,那我们如何判断一个值的类型呢?这个问题超过了本篇文章的知识范围,但我会实现一个 typeof 函数,可以更好的取代这个 typeof 运算符。为了不让读者和下文内容混了,我把它放在了文章末尾。

强制类型转换(手动类型转换)

对于基本类型而言,数值、布尔和字符串具有其对应的对象类型,其构造函数在没有new关键字调用的时候是类型转换函数,使用方法如下:

</>复制代码

  1. var num = Number("43"); //43
  2. typeof num; //"number"
  3. var str = String(num); //"43"
  4. var flag = Boolean(num); //true

具体的转换规律参看下表:

原始类型 目标类型(string) 目标类型(number) 目标类型(boolean) 目标类型(object)
undefined "undefined" NaN false throw TypeError
null "null" 0 false throw TypeError
true "true" 1 - new Boolean(true)
false "false" 0 - new Boolean(false)
"" - 0 false new String("")
"1.2" - 1.2 true new String("1.2")
"1.2a" - NaN true new String("1.2a")
"a" - NaN true new String("a")
0 "0" - false new Number(0)
-0 "0" - false new Number(-0)
NaN "NaN" - false new Number(NaN)
Infinity "Infinity" - true new Number(Infinity)
-Infinity "-Infinity" - true new Number(-Infinity)
1 "1" - true new Number(1)
{} toPrimitive toPrimitive true -
[] "" 0 true -
[9] "9" 9 true -
["a", "b"] "a,b" NaN true -
function 函数源代码 NaN true -

注释1: 对于 toPrimitive 会在下文详细解释。

注释2:只有空字符串("")、nullundefined+0-0NaN 转为布尔型是 false,其他的都是 true

注释3:空数组、空对象转换为布尔型也是 true【坑5】。

注释4:nullundefined 转换为数字是表现不一,分别为NaN0。【坑6】

有个东西需要多带带说明:
字符串转换为数字,除了 Number() 还有 parseInt()parseFloat() 函数。他们是有区别的:

parseInt() 将输入值转化为整数;parseFloat() 如果输入的是小数字符串(或具有可转换小数的字符串)转换为小数,如果输入是个整数字符串依然返回整数【坑7】:

</>复制代码

  1. console.log(parseFloat(" 6.2 ")); //6.2
  2. console.log(parseFloat("10")); //10

parseFloat() 可以转换以“点 + 数字”可是开头的字符,其默认整数部分为0parseInt()不行,会返回NaN

</>复制代码

  1. console.log(parseInt(".21")); //NaN
  2. console.log(parseFloat(".21")); //0.21
  3. console.log(parseFloat(".0d")); //0

parse***() 函数可以转换以数字开头(或开头有正负号)的所有字符串,遇到无法转换的字母或符号停止转换,返回已转换的部分。对于不能转换的字符串返回NaN

</>复制代码

  1. console.log(parseInt("10.3")); //10
  2. console.log(parseFloat(".d1")); //NaN
  3. console.log(parseFloat("10.11.33")); //10.11
  4. console.log(parseFloat("4.3years")); //4.3
  5. console.log(parseFloat("He40.3")); //NaN

parseInt()在没有第二个参数时默认以十进制转换数值,有第二个参数时,以第二个参数为基数转换数值,如果基数有误返回NaN

</>复制代码

  1. console.log(parseInt("13")); //13
  2. console.log(parseInt("11",2)); //3
  3. console.log(parseInt("17",8)); //15
  4. console.log(parseInt("1f",16)); //31

Number() 参数不支持参数中有不符合数字规范的任何符号,不满足此要求返回NaN, 对于满足此要求的参数,返回十进制数值(整数或浮点数)

</>复制代码

  1. console.log(Number("19")); //19
  2. console.log(Number("1.2f")); //NaN
  3. console.log(Number("-10.3")); //-10.3
  4. console.log(Number("10.3.3")); //NaN

parseInt()Number() 也支持 "0x" 或 "0X" 引导的十六进制,但不支持 "0" 引导的八进制【坑8】:

</>复制代码

  1. console.log(parseInt("010")); //10
  2. console.log(parseInt("0x20")); //32
  3. console.log(parseInt("-0x20")); //-32
  4. console.log(Number("010")); //10
  5. console.log(Number("0x20")); //32

但是 Number 不支持负的十六进制【坑9】:

</>复制代码

  1. console.log(Number("-0x20")); //NaN

parseInt()Number() 都会忽略字符串首尾的空格,但parseInt() 不会忽略格式化字符,而Number() 会将格式化字符与空格一起忽略【坑10】

</>复制代码

  1. Number(" 34
  2. "); //34
  3. Number("
  4. 34 "); //34
  5. Number(" 3
  6. 4 "); //NaN, 不和开头结尾的空格一起的格式化字符不会被忽略
  7. parseInt("
  8. 34 "); //NaN

他们对空字符串的处理也不一样【坑11】

</>复制代码

  1. Number(" "); //0, 空格被忽略了,所以 " " 等价于 ""
  2. parseInt(" "); //NaN, 空格被忽略了,所以 " " 等价于 ""

进制转换不局限在十六进制,js 会利用 0=9 和 A-Z 进行最高36进制的数制转换:

</>复制代码

  1. parseInt("f*ck"); // -> NaN
  2. parseInt("f*ck", 16); // -> 15
  3. parseInt(null, 24) // -> 23
  4. parseInt("Infinity", 10) // -> NaN
  5. // ...
  6. parseInt("Infinity", 18) // -> NaN...
  7. parseInt("Infinity", 19) // -> 18
  8. // ...
  9. parseInt("Infinity", 23) // -> 18...
  10. parseInt("Infinity", 24) // -> 151176378
  11. // ...
  12. parseInt("Infinity", 29) // -> 385849803
  13. parseInt("Infinity", 30) // -> 13693557269
  14. // ...
  15. parseInt("Infinity", 35) // -> 1201203301724
  16. parseInt("Infinity", 36) // -> 1461559270678...
  17. parseInt("Infinity", 37) // -> NaN

对于 Number() 而言,不传值和传入 undefiend 是不一样的【坑12】:

</>复制代码

  1. Number() // -> 0
  2. Number(undefined) // -> NaN

Number() 接受数值作为参数,此时它既能识别负的十六进制,也能识别0开头的八进制,返回值永远是十进制值

</>复制代码

  1. Number(3); //3
  2. Number(3.15); //3.15
  3. Number(023); //19
  4. Number(0x12); //18
  5. Number(-0x12); //-18
利用自动类型转换简单的实现手动类型转换

这个部分利用一些简单运算会自己调用相关函数,实现转换可以简化代码。需要说明的是:_Douglas Crockford_ 在 《Javascript: The Good Parts》书中推荐使用这个方法转换类型,而不是手写函数调用,因为以下方法执行效率更高。

</>复制代码

  1. // 任意值 => 字符串
  2. var str = "" + 2; //"2"
  3. // 任意值 => 数字
  4. var num = +"2"; //2
  5. // 任意值 => 布尔
  6. var bool = !!2; //true
  7. // 数值取整数
  8. var integer = ~~3.1415926; //3,这个不涉及类型转换
  9. // 数值取小数
  10. var decimals = 3.1415926 % 1; //0.14159260000000007,这个不涉及类型转换
对象类型和基本类型的关系

刚才我们解释了基本变量的类型转换,但没有举例一个基本变量和对象之间的转换关系。在研究其关系之前,我们需要知道 new 关键字可以生成一个对象,new 后面的函数成为构造函数。

</>复制代码

  1. var str = new String(32); //String{...}
  2. var num = new Number("22"); //Number{...}
  3. var flag = new Boolean("hello"); //Boolean{...}
  4. // 这里的参数也是会发生对应类型转换的,但得到的是对象
  5. typeof str; //object
  6. typeof num; //object
  7. typeof flag; //object

js中每一个对象,都是继承自 Object 原型的(除非你手动实现一个不继承自 Object.prototype 的对象),这里我们暂不讨论原型。对于 String(), Number() 和 Boolean() 得到的对象都具有一个名为`[[PrimitiveValue]]
`的属性,改属性是对象对应的原始值,即基本类型变量。

默认地,每个对象都有一个toString()方法和一个valueOf()方法,当需要获取对象原始值([[PrimitiveValue]])时候,调用valueOf()方法,需要获取字符串时调用toString()方法。系统会在自动类型转换的时候调用他们,所以我们通常不需要手动调用他们。

隐式类型转换不仅仅使用 toString()valueOf(),比如基本类型转换为对象依然是使用 new 关键字;而基本类型直接互相转换使用其类型对应函数,比如字符串转换为数字,使用 Number()

隐式类型转换(自动类型转换)

由于 js 是个弱类型语言,所以不是所有运算都要求类型一致,Js 为了一些运算可以执行,使用了隐式类型转换。也就是说,在一些计算中,系统会悄悄的完成类型转换,比如以下情况:

</>复制代码

  1. (3.1415926).toFixed(2); //3.14, 由于数字是基本类型不具备方法,所以自动将其转换为对象类型
  2. 3 + "23"; //"323" 数值和字符串类型不同,运算时将3转换为字符串
  3. 5 == "5"; //比较双方类型不同,发生类型转换。
  4. "a" < "b"; //这个更不一样,因为字符串比较实际上是比较其 ASCII 码的大小
数值加法和字符串连接

为什么 3 + "23"; 不把字符串转成数字呢?只能说这是规定!!也可能是考虑到了字符串不一定都能转成数字,而数字一定可以转成字符串吧。其实广义来讲,只要不是两个数字相加,都会吧不是字符串的那一个(或2个)转换为字符串然后连接,所以这个部分比较简单,我们只看2个有特点的例子就好:

</>复制代码

  1. console.log({o:1} + "88"); //[object Object]88
  2. console.log([5,9] + "88"); //5,988
  3. console.log(function(e){return;} + "88"); //function(e){return;}88

默认的对象转换为字符串使用了 toString 方法(实际上没这么简单,详细见下文),而 toString 对于一般对象而言得到 [object 构造函数名称] 这样的一个字符串。而数组和函数重写了对象的 toString 方法,所以数组得到用逗号链接的元素序列字符串;函数得到其源代码字符串。
不过要注意到,除了加号(+),其他符号都是默认转换为数值型:

</>复制代码

  1. "3" - 1 // -> 2
  2. "3" == 3 //转换后比较 3 == 3,而不是 "3" == "3"

但是,不巧的是这里又有例外了:就是 null 和 undefined!!

null 和 undefined

这里面首先需要解释的一个坑就是 null 和 undefined 相关的比较问题:
1、 null/undefined 和字符串相加是转换为字符串"null"/"undefined",和数字相加是,null 转化为0,而 undefined 转换为 NaN(NaN 和任何数值相加得到的都是 NaN)【坑13】

</>复制代码

  1. console.log(null + 20); //20
  2. console.log(undefined + 20); //NaN
  3. console.log(null + "20"); //null20
  4. console.log(undefined + "20"); //undefined20

2、 null 和 undefined 除了和自己以及彼此以外和谁都不相等,比如下面这个例子,虽然 null 和 undefined 类型转换都是 false,但它们谁都不等于 false【坑14】

</>复制代码

  1. console.log(false == undefined); // false
  2. console.log(false == null); // false
  3. console.log(true == undefined); // false
  4. console.log(true == null); // false
  5. console.log(null == undefined); // true

虽然它们彼此是相等的,但不严格相等

</>复制代码

  1. console.log(null === undefined); // false

那么我们就有必要区分一下相等和严格相等。简单来说:

相等:对于类型不同的两个值而言,通过类型转换可以相等的依然返回 true。

严格相等:不存在类型转换,对于类型不同的两个值直接返回 false。

这样的解释,简单但不明了,因为你会遇到下面这个坑【坑15】:

</>复制代码

  1. if("0") {
  2. console.log("yes");
  3. }

由于之前我们总结过,只有空字符串("")、nullundefined0NaN 的布尔型是 false,其他的都是 true,所以上述代码是可以输出 ‘yes’ 的。但是我们执行以下代码:

</>复制代码

  1. console.log(false == "0"); // true
  2. console.log(true == "0"); // false

到这里一脸懵逼!这简直不能更坑!没办法,想搞明白这个事还得去看规范(7.2.13-7.2.14):

关于 ==!=

</>复制代码

  1. The comparison x == y, where x and y are values, produces true or __false__. Such a comparison is performed as follows:

  2. If Type(x) is the same as Type(y), then

  3. Return the result of performing Strict Equality Comparison x === y.

  4. If x is null and y is __undefined__, return __true__.

  5. If x is undefined and y is __null__, return __true__.

  6. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).

  7. If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.

  8. If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.

  9. If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).

  10. If Type(x) is either String, Number, or Symbol and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).

  11. If Type(x) is Object and Type(y) is either String, Number, or Symbol, return the result of the comparison ToPrimitive(x) == y.

  12. Return __false__.

翻译如下:

</>复制代码

  1. 比较表达式 x == y (x 和 y 为值) 返回 true 或 __false__,执行过程如下:

  2. 如果 Type(x) 和 Type(y) 相同,则

  3. 返回 x === y 的结果;

  4. 如果 x 是 null 并且 y 是 __undefined__,返回 __true__;

  5. 如果 x 是 undefined 并且 y 是 __null__,返回 __true__;

  6. 如果 Type(x) 是数值并且 Type(y) 是字符串,返回 x == ToNumber(y) 的结果;

  7. 如果 Type(x) 是字符串并且 Type(y) 是数值,返回 ToNumber(x) == y 的结果;

  8. 如果 Type(x) 是布尔型,返回 ToNumber(x) == y 的结果;

  9. 如果 Type(y) 是布尔型,返回 x == ToNumber(y) 的结果;

  10. 如果 Type(x) 是字符串、数值或 Symbol 并且 Type(y) 是对象, 返回 x == ToPrimitive(y) 的结果;

  11. 如果 Type(x) 是对象并且 Type(y) 是字符串、数值或 Symbol , 返回 ToPrimitive(x) == y 的结果;

  12. 返回 __false__;

关于规范中的 ToPrimitive() 用来将对象转换为 原始值字符串 ,在规范7.1.1节中也有解释,简单来说:

ToPrimitive() 默认将类型转为原始值,但是对象可以通过@@toPrimitive 方法重新定义其行为。规范中只有 Date 对象和 Symbol 重新定义了该行为,Date 和 Symbol 的 ToPrimitive() 默认得到 String 类型;

其次,ToPrimitive() 是依赖对象的 toString()valueOf() 方法的。对象转换基本类型时,先调用 valueOf(),如果 valueOf() 返回的不是基本类型,才调用 toString()

如果toString()valueOf()都不是函数或是返回对象的函数,则抛出 TypeError 异常。

详见规范第7.1.1 节 OrdinaryToPrimitive

关于 ===!==

</>复制代码

  1. The comparison x === y, where x and y are values, produces true or __false__. Such a comparison is performed as follows:

  2. If Type(x) is different from Type(y), return __false__.

  3. If Type(x) is Number, then

  4. If x is __NaN__, return __false__.

  5. If y is __NaN__, return __false__.

  6. If x is the same Number value as y, return __true__.

  7. If x is +0 and y is __-0__, return __true__.

  8. If x is -0 and y is __+0__, return __true__.

  9. Return __false__.

  10. Return SameValueNonNumber(x, y).

  11. </>复制代码

    1. NOTE: This algorithm differs from the SameValue Algorithm in its treatment of signed zeroes and NaNs.

翻译如下:

</>复制代码

  1. 比较表达式 x === y (x 和 y 为值) 返回 true 或 __false__,执行过程如下:

  2. 如果 Type(x) 和 Type(y) 不同, 返回 __false__;

  3. 如果 Type(x) 是数值, 则

  4. 如果 x 是 __NaN__, 返回 __false__;

  5. 如果 y 是 __NaN__, 返回 __false__;

  6. 如果 x 和 y 值相等, 返回 __true__;

  7. 如果 x 是 +0 并且 y 是 __-0__, 返回 __true__;

  8. 如果 x 是 -0 并且 y 是 __+0__, 返回 __true__;

  9. 返回 __false__;

  10. 返回 SameValueNonNumber(x, y);

  11. </>复制代码

    1. 注意: SameValue 算法在对待 0NaN 存在差别

感觉上面这个注意又是个坑呀,博主赶紧去继续查手册,发现这个函数的操作方法:

</>复制代码

  1. The internal comparison abstract operation SameValueNonNumber(x, y), where neither x nor y are Number values, produces true or __false__. Such a comparison is performed as follows:

  2. Assert: Type(x) is not Number.

  3. Assert: Type(x) is the same as Type(y).

  4. If Type(x) is Undefined, return __true__.

  5. If Type(x) is Null, return __true__.

  6. If Type(x) is String, then

  7. If x and y are exactly the same sequence of code units (same length and same code units at corresponding indices), return __true__; otherwise, return __false__.

  8. If Type(x) is Boolean, then

  9. If x and y are both true or both __false__, return __true__; otherwise, return __false__.

  10. If Type(x) is Symbol, then

  11. If x and y are both the same Symbol value, return __true__; otherwise, return __false__.

  12. If x and y are the same Object value, return __true__. Otherwise, return __false__.

翻译如下:

</>复制代码

  1. 内部的抽象比较操作 SameValueNonNumber(x, y) (x 和 y 为值) 返回 true 或 __false__,执行过程如下::

  2. 断言: Type(x) 不是数值;(译注: 不符合直接抛出异常)

  3. 断言: Type(x) 和 Type(y) 类型一样;(译注: 不符合直接抛出异常)

  4. 如果 Type(x) 是 undefined,返回 __true__;

  5. 如果 Type(x) 是 null,返回 __true__;

  6. 如果 Type(x) 是字符串, 则

  7. 如果 x 和 y 是严格相同的字符序列 (相同长度并且对应下标的字符编码一致),返回 __true__; 否则,返回 __false__;

  8. 如果 Type(x) 是布尔型, 则

  9. 如果 x 和 y 都是 true 或者都是 __false__,返回 __true__; 否则,返回 __false__;

  10. 如果 Type(x) 是 symbol, 则

  11. 如果 x 和 y 是同一个 Symbol,返回 __true__; 否则,返回 __false__;

  12. 如果 x 和 y 是同一个对象,返回 __true__; 否则,返回 __false__;

一下翻译了这么多,至少不会感到晕了。js 就是这样比较两个值的,读完这些内容,是不是理解什么:

</>复制代码

  1. 只要 ===__true__== 一定为__true__;
    只要 !=__false__!== 一定为__false__

比如下面再看一些奇怪的东西:

数组、对象比较

</>复制代码

  1. var a = [1];
  2. var b = [2];
  3. var c = a;
  4. console.log(a == b); //false, 因为不是同一个对象
  5. console.log(a == c); //true, 因为是同一个对象
  6. // 所以
  7. console.log([] == []); //false
  8. console.log({} == {}); //false

比如这样的代码:

</>复制代码

  1. !![] // -> true, 和 ==, ===, !=, !== 无关的类型转换不会调用内置的 toPrimitive, 这里调用 Boolean([]) 得到 true
  2. [] == true // -> false, 这个通过转换得到的是 0 == 1, 返回 false

以下两个同理:

</>复制代码

  1. !!null // -> false
  2. null == false // -> false

关于 toString() 和 valueOf()

</>复制代码

  1. "J" + { toString: function() { return "S"; } }; // "JS"
  2. 2 * { valueOf: function() { return 3; } }; // 6

上面这个例子不深究的话,看上去似乎若合符节,一个转为字符串,调用了 toString,第二个转换为数字,调用了 valueOf。实际上并不是这么简单【坑16】:
根据之前那个表格,这里使用 toPrimitive 而再看 toPrimitive 的定义,除了 Date 和 Symbol 类型转化为字符串,其余的对象都默认转化为数字,所以这里都是先调用 valueOf ,而对象的 valueOf 默认返回对象本身(this),这个不符合规范,因为规范要求不能返回对象,所以第一个表达式继续调用toString 得到了 "S",而第二个 valueOf 直接返回 3,没有调用 toString。 为了说明这个逻辑,我们再看一个例子,这次我做过多解释了:

</>复制代码

  1. var oriObj = {}
  2. var myObj = {
  3. toString: function() {
  4. return "myObj";
  5. },
  6. valueOf: function() {
  7. return 17;
  8. }
  9. };
  10. "object: " + myObj; // "object: 17"

+0 和 -0 是一致的

</>复制代码

  1. console.log(+0 === -0); //true
  2. console.log(+0 == -0); //true

</>复制代码

  1. 补充

  2. 即便如此,我们也可以用如下方法区别 +0 和 -0

  3. </>复制代码

    1. function isNegativeZero(num) {
    2. return num === 0 && (1 / num < 0);
    3. }

NaN 是唯一一个不等于自己的值【坑17】

</>复制代码

  1. var x = NaN;
  2. console.log(x == x); //false

这里有一个容易记混的地方

对于 + 运算,字符串和数字相加是将数字转换为字符串;而 == 运算中是将字符串转换为数字【坑18】

</>复制代码

  1. // 结合之前的【坑10】,就得到这么一让人想骂娘的结果
  2. console.log("
  3. " == 0); //true
toLocaleString 和 toString

toLocaleString 和 toString 方法同时存在,它定义了个性化的字符串转换功能,对于对象而言 toLocaleString 和 toString 是一样的。不过Array, Number, Date 和TypedArray(ES6中的类型,这里不讨论)都重写了 toLocaleString。比如说数值类型:

</>复制代码

  1. console.log((1234).toLocaleString()); //1,234
  2. console.log((1234567).toLocaleString("zh-Hans-CN-u-nu-hanidec", {useGrouping: false})); //一二三四五六七
  3. console.log((1234567).toLocaleString("zh-Hans-CN-u-nu-hanidec", {useGrouping: true})); //一,二三四,五六七

日期类型:
得到一些地域性的时间表示

</>复制代码

  1. var date = new Date();
  2. console.log(date.toString()); //Tue Apr 15 2014 11:50:51 GMT+0800 (中国标准时间)
  3. console.log(date.toLocaleString()); //2014-4-15 11:50:51
  4. console.log(date.toLocaleDateString()); //2014-4-15
  5. console.log(date.toLocaleTimeString()); //上午11:50:51

数组类型的 toLocaleString 就是将数组中的数值类型和日期类型分别按 toLocaleString 转换为字符串,再形成整体字符串。

关于 toLocaleString 的定义官方也是故意没给出具体的实现细节【坑19】,这一点完全不能理解,所以这个方法用的场合也比较有限,这里不再赘述了。

Infinity

关于 Infinity 的数学运算也比较简单,如果学过数学中的极限的话很好理解,对于不定式运算(0 / 0, ∞ / ∞, ∞ - ∞),返回 NaN:

</>复制代码

  1. console.log(Infinity + Infinity); //Infinity
  2. console.log(Infinity - Infinity); //NaN
  3. console.log(Infinity * Infinity); //Infinity
  4. console.log(Infinity / Infinity); //NaN
  5. console.log(0 / 0); //NaN
javascript精度

javascript的小数精度范围是$-1.79e308至1.79e308$,同时可以认为大数在-9e15~9e15之间的计算可以认为是没有误差的,即 MIN_SAFE_INTEGERMAX_SAFE_INTEGER。我们可以用Number.MAX_VALUENumber.MIN_VALUE获得js中可表示的最大数和最小数。

</>复制代码

  1. console.log(Number.MIN_VALUE); //5e-324
  2. console.log(Number.MAX_VALUE); //1.7976931348623157e+308
  3. console.log(Number.MAX_SAFE_INTEGER); //9007199254740991
  4. console.log(Number.MIN_SAFE_INTEGER); //-9007199254740991

对于计算值超过该范围的数会被转换为 Infinity 或 0,而且这个转换不属于类型转换,而是编程语言处理了内存溢出后的结果:

</>复制代码

  1. console.log(2e200 * 73.987e150); //Infinity
  2. console.log(-1e309); //-Infinity
  3. console.log(4.18e-1000); //0

而且数值会在浮点计数和科学技术法间自动转换,自动转换临界是1e-6

</>复制代码

  1. console.log(0.000006); //0.000006
  2. console.log(0.0000006); //6e-7

但在精度范围边界,总会有一些问题【坑20】,姑且认为这也是个坑吧,不过这样的问题在其他编程语言中也普遍存在

</>复制代码

  1. console.log(1e200 + 1 === 1e200); //true
  2. console.log(0.1 + 0.2); //0.30000000000000004
  3. console.log(0.3 === 0.1 + 0.2); //false

在比如下面这个

</>复制代码

  1. 999999999999999 // -> 999999999999999
  2. 9999999999999999 // -> 10000000000000000
  3. 10000000000000000 // -> 10000000000000000
  4. 10000000000000000 + 1 // -> 10000000000000000
  5. 10000000000000000 + 1.1 // -> 10000000000000002
[] 和 {}

有了上面的基础,这个最坑的部分来了

</>复制代码

  1. console.log(+{}); //NaN
  2. console.log(+[]); //0

以上这两个属于转换为数值,所以其值会调用 valueOf()(返回了对象),而后调用 toString(),前者得到 [object Object],后者得到 "", 再调用
Number() 得到结果,前者为 NaN,后者为 0

理解了上面这个下面这个就不难了,都是转换到字符串以后进行字符串链接

</>复制代码

  1. console.log({} + []); //[object Object]
  2. console.log({} + {}); //[object Object][object Object]
  3. console.log([] + []); //""
  4. console.log([] + {}); //[object Object]

但如果像下面这样使用呢,我们如何理解?

</>复制代码

  1. console.log({}[]); //[]
  2. console.log([]{}); //"SyntaxError"(语法错误)

首先我们需要明白这2个表达式是从左到右执行的。这个地方我们可以很简单的证明第一个表达式中的{},不是对象:

</>复制代码

  1. var obj = {};
  2. console.log(obj[]); //SyntaxError: Unexpected token ]

所以这里他是个表示代码段的括号(注意块级作用域是 ES6 提出了,在 ES5 中 {} 仅仅表示一个代码段,如 if(exp){...} 中的 {}) ,这里这个代码段里面什么也没有,执行完以后这个 {} 就没了,剩下一个数组 []。第二个表达式 []{} 从左到右先遇到一个数组,数组后面定义代码段或者对象都是不符合语法的。

我们再看几个赋值相关的,这里又是一个坑,居然 js 敢不限制赋值表达式的左值是标识符或 Symbol【坑21】:

</>复制代码

  1. var [] = 1; //"TypeError"(类型错误)
  2. var [] = "1" ; //(正常执行,由于字符串对象本身就是类数组对象)
  3. var [] = {}; //"TypeError"(类型错误)
  4. var {} = [] ; //(正常执行,仅仅是指针指向从对象改变到了数组)

以上的2个错误,都是 “TypeError: undefined is not a function”,很明显,由于表达式不规范导致被js误认为是一个函数,从而报错。

如果你理解了这些,不妨研究一下下面两个表达式的值吧:

</>复制代码

  1. (![]+[])[+[]]+(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]] //"fail"
  2. (!(~+[])+{})[--[~+""][+[]]*[~+[]] + ~~!+[]]+({}+[])[[~!+[]]*~+[]] //"sb"

当然还有更奇怪的,原因还是在于数组对象重写了对象的 toString 方法【坑】:

</>复制代码

  1. [] == ![] //true
  2. {} == !{} //false

下面这个输入,博主一直很疑惑。把2行代码分别输入到 chrome 控制台,得到对应结果。按规范的逻辑应该输出[object Object],但这个是为什么呢?

</>复制代码

  1. console.log({} + []); //[object Object]
  2. {}+[]; //0

原因是第二行中的{}被当做了快作用域,而不是一个对象。

数组中的 null 和 undefined

数组中的 null 和 undefined 会在转换为字符串时被看做空,也就是可以直接忽略。

</>复制代码

  1. "" == [null]; //true
  2. "1,,3" == [1,undefined,3] //true
大于号和小于号

大于和小于运算的两边都会被转化为数字,但字符串会安其 ASCII 码或 UNICODE 码把每个字符一次比较,得到 Boolean 值。比如:

</>复制代码

  1. "abc" > "abd"; //false
  2. "aBc" > "abc"; //false
  3. "093" < "15"; //true

但这里有一个奇怪的例子:

</>复制代码

  1. var a = {pro: 29};
  2. var b = {pro: 43};
  3. a < b; //false
  4. a == b; //false
  5. a > b; //false
  6. a <= b; //true
  7. a >= b; //true

对于大于(等于)和小于(等于)号,两个对象 a 和 b 都被转换成了字符串 "[object Object]",所以他们应该是相等的,所以 a < ba > b 都是 false,而 a <= ba > = b 都是 true。但是 a == b 为 false。有了上面的知识,就很好理解这个问题,a, b都是对象,所以不发生类型转换,而两个对象引用不同,结果为 false。

ES6 中的类型转换和坑

ES6 中同样带入了许多坑,当然这些坑不一定都是类型转换导致的。

label 和 块作用域

比如下面这段代码,看似像定义对象属性,但实际上是个块级作用域,foo: 是一个的标签,用来给 break 指定跳转的地方。

</>复制代码

  1. foo: {
  2. console.log("first"); //first
  3. break foo;
  4. console.log("second"); //不输出
  5. }

再看下面这个:

由于前面的 a-g 都是标签,而后面的逗号表达式会返回最后一个表达式的值

</>复制代码

  1. a: b: c: d: e: f: g: 1, 2, 3, 4, 5; // -> 5
解构赋值

比如这样定义变量,并且结构赋值

</>复制代码

  1. let x, { x: y = 1 } = { x }; //由于 x 是 undefined 所以 y 取了默认值 1
  2. console.log(y); //1
模板字符串和对象中的类型转换

对象在类似 EL 表达式中会被自动转换为字符串, 而对象的键值也会被默认转换为字符串(除了 Symbol 类型)

</>复制代码

  1. `${{Object}}` //"[object Object]"
  2. { [{}]: {} } // -> { "[object Object]": {} }
展开运算符

由于字符串具有 iterator 就被展开了:

</>复制代码

  1. [...[..."..."]].length //3 实际上得到的是[".", ".", "."]
try catch 语句

这个不算是 es6 的问题,不过我们也看一看:

try 中的 return 和 throw 会在有 finally 语句是中的 return 或 throw 覆盖(这里的确是覆盖,而不是前一个 return 未执行,详细可以参看规范第13.15.8节。

</>复制代码

  1. (() => {
  2. var i = 0;
  3. try {
  4. return ++i;
  5. } finally {
  6. return ++i;
  7. }
  8. })(); // 2

可见上面两个 return 都执行了,但后一个把前一个覆盖了。如果你认为第一个 return 没执行,而是执行了自加,那你一定忘了程序执行的最小单元是语句,而这里的 ++i 并不是一个完整的语句。

class 类

</>复制代码

  1. //这个代码是不会报错的,系统会直接将 "class" 字符串作为对象的属性名
  2. const foo = {
  3. class: function() {}
  4. };
  5. var obj = new class {
  6. class() {}
  7. };
  8. console.log(obj); //{}, 和 var obj = new class{} 一样
Symbol

这个类型转换为字符串必须是显示的,隐式转换会出错

</>复制代码

  1. var s = Symbol("aabb");
  2. String(s); //"Symbol(aabb)"
  3. s + ""; //TypeError: Cannot convert a Symbol value to a string
另一个更好的 typeOf 函数

</>复制代码

  1. function typeOf(val){
  2. return Object.prototype.toString.call(val).slice(8, -1); //同样可以很好的处理 nullundefined
  3. }

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/97607.html

相关文章

  • JavaScript数据类型转换

    摘要:本文主要介绍数据类型强制转换和自动转换,自动转换是基于强制转换之上。强制转换主要指使用和三个函数,手动将各种类型的值,分布转换成数字字符串或者布尔值。 前言 JavaScript是一门动态语言,所谓的动态语言可以暂时理解为在语言中的一切内容都是不确定的。比如一个变量,这一时刻是个整型,下一时刻可能会变成字符串了。虽然变量的数据类型是不确定的,但是各种运算符对数据类型是有要求的。如果运算...

    blastz 评论0 收藏0
  • JavaScript数据类型转换

    摘要:本文主要介绍数据类型强制转换和自动转换,自动转换是基于强制转换之上。强制转换主要指使用和三个函数,手动将各种类型的值,分布转换成数字字符串或者布尔值。 前言 JavaScript是一门动态语言,所谓的动态语言可以暂时理解为在语言中的一切内容都是不确定的。比如一个变量,这一时刻是个整型,下一时刻可能会变成字符串了。虽然变量的数据类型是不确定的,但是各种运算符对数据类型是有要求的。如果运算...

    chaos_G 评论0 收藏0
  • JavaScript数据类型转换

    摘要:本文主要介绍数据类型强制转换和自动转换,自动转换是基于强制转换之上。强制转换主要指使用和三个函数,手动将各种类型的值,分布转换成数字字符串或者布尔值。 前言 JavaScript是一门动态语言,所谓的动态语言可以暂时理解为在语言中的一切内容都是不确定的。比如一个变量,这一时刻是个整型,下一时刻可能会变成字符串了。虽然变量的数据类型是不确定的,但是各种运算符对数据类型是有要求的。如果运算...

    Julylovin 评论0 收藏0
  • 【Vue原理】Vue源码阅读总结大会 - 序

    摘要:扎实基础幸好自己之前花了大力气去给自己打基础,让自己现在的基础还算不错。 写文章不容易,点个赞呗兄弟专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于 Vue版本 【2.5.17】 如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧 【Vue原理】Vue源码阅读总结大会 - 序 阅读源码是需...

    Edison 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<