1、JS类型转换共有三种情况:

  • 转换为布尔值
  • 转换为数字
  • 转换为字符串

2、类型转换表格:

原始值 转换目标 结果
number 布尔值 除了0、-0、NaN都为true
string 布尔值

除了空字符串都为true

undefined、null 布尔值 false
引用类型 布尔值 true
number 字符串 5 -----> '5'
Boolean 字符串 true -----> 'true'
数组 字符串 [1, 2] ----> '1, 2'
对象 字符串 {id: 1} ----> '[object Object]'
string 数字 '1' ----> 1 ; 'a' ----> NaN
数组 数字 空数组为0,存在一个元素且为数字转数字,其他情况NaN
null 数字 0
除了数组的引用类型 数字 NaN
Symbol 数字 抛错
  • 转Boolean:在条件判断时,除undefinednullfalse''NaN0-0其他都转为true,包括所有对象。

3、类型转换

  • valueOf():如果转换为基础类型,就返回这个对象逻辑上对应的原始类型的值。如String包装对象的valueOf(),应该返回这个对象所包装的字符串;
  • x.toString()返回这个对象的字符串表示。用一个字符串来描述这个对象的内容

  基本类型的valueOf()会返回自身的原始类型,而Array、Function、Object都返回自身,Date返回时间戳,Error和Math没有valueOf()方法

// object
var obj = {a: 1};
obj.valueOf();    // {a: 1}
obj.toString();    // "[object Object]"

// array
var arr = [1, 2];
arr.valueOf();    // [1, 2]
arr.toString();    // "1, 2"

// function
var fun = function() {};
fun.valueOf();    // ƒ () {}
fun.toString();    // "function() {}"

// date
var date = new Date();
date.valueOf();    // 1572592931967
date.toString();    // "Fri Nov 01 2019 15:22:01 GMT+0800 (中国标准时间)"

// string
var str = 'abc';
str.valueOf();    // "abc"
str.toString();    // "abc"

// number
var num = 222;
num.valueOf();    // 222
num.toString();    // "222"

// boolean
var bool = true;
bool.valueOf();    // true
bool.toString();    // "true"

// null & undefined  无法调用,报错

// regExp
var reg = /cat/g;
reg.valueOf();    // /cat/g
reg.toString();    // "/cat/g"

// window
var win = window;
win.valueOf();    // window
win.toString();    // "[object Window]"

// error
var err = new Error('abc');
err.valueOf();    // Error: abc
    at <anonymous>:1:11
err.toString();    // "Error: abc"

4、检测类型的四个内部方法:

  • ToPrimitive( input [, PreferredType ] )
  • ToBoolean( argument )
  • ToString( argument )
  • ToNumber( argument )

  这4个方法是ECMAScript定义的4个抽象的操作,它们在JS内部使用,进行类型转换。JS的使用者不能直接调用这些函数,但是了解这些函数有利于我们了解JS类型转换的原理。

  (1)ToPrimitive( input [, PreferredType ] )

    将input转换成一个原始类型的值。PreferredType参数要么不传入,要么是Number或String。如果PreferredType参数是Number,ToPrimitive这样执行:

  1. 如果input本身就是原始类型,直接返回input。
  2. 调用input.valueOf(),如果结果是原始类型,则返回这个结果。
  3. 调用input.toString(),如果结果是原始类型,则返回这个结果。
  4. 抛出TypeError异常。

    以下是PreferredType不为Number时的执行顺序:

    • 如果PreferredType参数是String,则交换上面这个过程的第2步和第3步的顺序,其他执行过程相同。
    • 如果PreferredType参数没有传入
      • 如果input是内置的Date类型,PreferredType视为String
      • 否则PreferredType视为Number  -  先调用ValueOf再调用toString
Date 转换的时候 PreferredType 视为String
    所有先调用toString。

console.log(111 + new Date());
// "111Thu Nov 07 2019 20:29:33 GMT+0800 (中国标准时间)"

  (2)ToBoolean:

Argument Type Result
Undefined Return false
Null Return false
Boolean Return argument
Number 仅当argument为+0,-0,NaN时,return false;否则一律return true
String 仅当argument为空字符串(长度为0)时,return false;否则一律return true
Symbol Return true
Object Return true

  这些规定都来自ECMA的标准,在条件判断时,除了undefined、null、false、NaN、''、+0、-0,其他所有值都转为true,包括所有对象。

  (3)ToNumber:

Argument Type Result
Undefined Return NaN
Null Return +0
Boolean 如果argument为true,return 1,为false则return +0
Number 直接return argument
String 将字符串的内容转化为数字(比如"23" -> 23),如果转化失败则return NaN(比如"23a" -> NaN)
Symbol 抛出TypeError异常
Object primValue = ToPrimitive(argument, Number),再对primValue使用ToNumber(primValue)

  由此可见ToNumber的转化并不总是成功,有时会转化成NaN,有时则直接抛出异常。

所以Number([1, 2]) -> 先由ToPrimitive(argument, Number)转换[1, 2] 

-> valueOf返回本身,再调用toString返回'1, 2' -> 再ToNumber(primValue)转换为NaN

  (4)ToString:

Argument Type Result
Undefined Return 'undefined'
Null Return 'null'
Boolean 如果argument为true,return 'true',为false则return 'false'
Number 用字符串来表示这个数字
String 直接return argument
Symbol 抛出TypeError异常
Object primValue = ToPrimitive(argument, hint String),再对primValue使用ToString(primValue)

5、隐式类型转换(自动类型转换):

(1)当JS期望得到某种类型的值,而实际的值是其他类型,就会发生隐式类型转换。系统会自动调用内部的ToBoolean( argument )、ToNumber( argument )、ToString( argument )转换方法。

  eg1:

if(!undefined
    && !null
    && !0
    && !NaN
    && !''
) {
    console.log('true');
}
    // true

    eg1:因为在if的括号中,js期望得到boolean的值,所以对括号中每一个值都是用Toolean( argument ),将它们转换成boolean。

  eg2:

3 * { valueOf: function() { return 5 } };    // 15

     eg2:在乘号的两段,js期望得到Number类型的值,所以对右边的对象使用ToNumber( argument ),得到结果5,再与乘号左边的3相乘。

  eg3:

3 * { valueOf: function() { return {} }, toString: function() { return {} } }
// Uncaught TypeError: Cannot convert object to primitive value

    eg3:调用ToNumber( argument )的过程中,调用了ToPrimitive(input, Number),因为在toPrimitive中valueOf和toString都没有返回原始类型,所以抛出异常。

(2)加法:

  符号“+”既可以表示“算数加法”,也可以表示“字符串拼接”。因此:只要‘+’两端的任意一个操作数是字符串,就表示拼接。否则表示算数加法

12 + 3     // 15
12 + '3'    // '123'

  计算过程:

  1. 令lval = 符号左边的值,rval = 符号右边的值;
  2. 令lprim = ToPrimitive(lval), rprim = ToPrimitive(rval);
  3. 如果lprim和rprim中有任意一个为String类型,将ToString(lprim)和ToString(rprim)的结果做字符串拼接;

    否则,将ToNumber(lprim)和ToNumber(rprim)的结果做加法。

[] + []    // ''
// 提示:ToPrimitive([])返回空字符串

[] + {}    // "[object Object]"
// 提示:ToPrimitive({})返回"[object Object]"

{} +[]    // 0
// 结果不符合我们的预期:"[object Object]"
// 提示:在Chrome中,符号左边的{}被解释成了一个语句块,而不是一个对象
// 注意在别的执行引擎上可能会将{}解释成对象
// 这一行等价于 '+ []'
// '+ anuValue' <==>Number(anyValue)

({}) + []    // "[object Object]"
// 加上括号以后,{}被解释成了一个对象

  另外对于加法还需要注意 'a' + + 'b'

'a' + + 'b'    // "aNaN"
// 先执行+'b',等于NaN,所以结果为"aNaN"
// +'1' 类似的形式快速获取Number类型,相当于调用ToNumber方法

  (3)其他运算符:其他运算符中,只要其中一方是数字,那么另一方就会被转为数字

// 减法运算
4 - '';    // 4
4 - '3';    // 1
4 - 'b';    // NaN
4 - true;    // 3
4 - [];    // 4
4 - [1, 2];    //NaN
4 - {};    //NaN
4 - {id: 1};    // NaN

// 乘法运算
4 * '';    // 0
4 * '3';    // 12
4 * 'b';    // NaN
4 * true;    // 4
4 * [];    // 0
4 * [1, 2];    //NaN
4 * {};    //NaN
4 * {id: 1};    // NaN

// 除法运算
4 / '';    // Infinity
4 / '3';    // 1.333333333
4 / 'b';    // NaN
4 / true;    // 4
4 / [];    // Infinity
4 / [1, 2];    //NaN
4 / {};    //NaN
4 / {id: 1};    // NaN

 TEST:

5 * '5';    // 25
Null / 5;    // 0
5 * [1, 2];    // NaN
[] + [ ];    // ''
5 >= NaN;    // false
5 * 'a';    // NaN
5 / NaN;    // NaN
[] + {};    // "[object Object]"
0 / 0;    // Infinity
5 * [];    // 0
5 < [1, 2];    // false
5 / 'a';    // NaN
5 >= null;    // true
5 / [1];    //5
'a' + + 'b';    // "aNaN"
5 / undefined;    // NaN
5 * false;    // 0
5 + [1, 2, 3];    // "51, 2, 3"
5 >= true;    // true
{} + [];    // 0
5 / [];    // Infinity
5 * true;    // 5
({}) + [];    // "[object Object]"
5 * null;    // 0
5 >= 'true';    // false
5 > [];    // true
123 + {toString: function() { return 'def'; }}    // "123def"
5 / null;    // Infinity
"16" > "5";    // false
0 > [1];    // false
123 + {toString: function() { return 'def'; },
    valueOf: function() { return 3; }}    // 126

  (4)比较运算符:

   1.如果是对象,就通过 ToPrimitive 转换对象;

   2.如果是字符串,就通过 unicode 字符索引来比较

let a = {
    valueOf() {
        return 0;
    },
    toString() {
        return '-1';
    }
}

a > -1;    // true

   3. == 双等:

    1)x、y都为 Null 或 undefined,return true;一方为 null 或 undefined、NaN,return false;

    2)如果x和y为String、Number或Boolean并且类型不一致,都转为Number再进行比较;

    3)如果存在Object,都转为原始值,再比较

 TEST:

"0" == null;    // false
"0" == undefined;    // false
"0" == false;    // true
"0" == NaN;    // false
"0" == 0;    // true
"0" == '';    // false

false == null;    // false
false  == undefined;    // false
false  == NaN;    // false
false  == 0;    // true
false  == '';    // true
false  == [];    // true
false  == {};    // false

"" == null;    // false
"" == undefined;    // false
"" == NaN;    // false
"" == 0;    // true
"" == '';    // true
"" == [];    // true
"" == {};    // false

0 == null;    // false
0 == undefined;    // false
0 == NaN;    // false
0 == [];    // true
0 == {};    // false

[] == ![];    // true

6、显式类型转换(强制类型转换):

  (1) 显式调用Boolean(value)、Number(value)、String(value)完成的类型转换,叫显式类型转换

  (2)new Boolean(value)、new Number(value)、new String(value)传入各自对应的原始类型的值,可以实现“装箱” -- 将原始类型封装成一个对象。其实这三个函数不仅仅可以当作构造函数,可以直接当作普通函数使用,将任何类型的参数转换成原始类型的值:

Boolean('sdjdh');    //    true
Number('123');    // 123
String({ a: 24 });    //"[object Object]"

    其实这三个函数用于类型转换的时候,调用的就是JS内部的 ToBoolean( argument )、ToNumber( argument )、ToString( argument ) 方法!

  Number()比parseInt parseFloat严格。parseInt parseFloat会只截取数字转换。

  (3) 这里解释一下 String({ a: 24 });   //  "[object Object]" 的过程:

    · 执行String({a: 24}) 

      · 执行js内部函数ToString({a: 24})

        · 执行 primValue = ToPrimitive({a: 24}, hint String)

      · {a: 24}不是原始类型,进入下一步

        · 在ToPrimitive内调用({a: 24}).toString(),返回原始值"[object Object]",因此直接返回这个字符串,ToPrimitive后面的步骤不用继续进行了

     · primValue被复制为ToPrimitive的返回值"[object Object]"

        · 执行JS内部函数ToString("[object Object]"),返回"[object Object]"

        · 返回"[object Object]"

      · 返回"[object Object]"

    · 返回 "[object Object]"

  Tips: 为了防止出现意料之外的结果,最好在不确定的地方使用显示类型转换

let obj2 = {
    valueOf() {
        return 2
    },
    toString() {    
        return []
    }
}

String(obj2);    // '2'
2 + obj2;    // 4

  可以重写 Symbol.toPrimitive,该方法在转原始类型时调用优先级最高

let a = {
    valueOf() {
        return 0
    },
    toString() {
        return '1';
    },
    [Symbol.toPrimitive]() {
        return 2;
    }
}

1 + a;    // 3

a = {
    valueOf() {
        return 3
    },    
    toString() {
        return '1';
    }
}

1 + a;    // 4

   (4)parseInt:只会转换字符串。如果传入的不是字符串,会先转换成字符串在进行parse。

let obj = {
    valueOf: function() {
        return '2px'
    },
    toString: function() {
        return []
    }
}

parseInt(obj);    // 2      --->如果不是字符串会先根据ToString转换成字符串,再去转换

parseInt(1/0, 19);    // 18    --->1/0转换成Infinity。而有效数字范围是0-9 a-i  所以第一位为I代表18.n超出19的范围了,所以只返回18

parseInt(0.0000008);    // 8    ---> 8e+7 6个0转换成字符串会转换成指数

parseInt(0.000008);    // 0    ---> 0.000008

parseInt(false, 16);    // 250    ---> 'false'=>'fa'在16进制下有效

parseInt(function() {...}, 16);    // 15   ---> 'f'在16进制下有效

    如果不填写第二个参数,则会根据传入的值来判断是什么进制,填写了代表转换成多少进制。

parseInt('0x10');    // 16
parseInt('103', 2);    // 2

  (5)~运算符表示-(x + 1):

~2;    // -3
~-1;    // 0

    根据这个特性,可以对-1进行特殊判断。比如indexof时,if(~a.indexof('bbb'))表示如果是-1就会返回false。

let a = 'aa';
if(~a.indexOf('bbb')) { console.log(11); } else {console.log(1222);}        // 1222

  (6)| 运算符:

3.2 | 0;    // 3
-6.7 | 0;    // -6
posted on 2019-10-25 17:42  minoz  阅读(380)  评论(0编辑  收藏  举报