[JS]【二】[2]-- 基本数据转换

256 阅读11分钟

javascript是弱类型语言

JavaScript 是弱类型语言,弱类型语言的数据类型可以被忽略的语言 声明变量的时候并没有预先确定的类型,变量的类型就是其值的类型,也就是说跟随值的变化类型也在变化;所以要精通JS的各类型的转换(☝->强类型语言强制数据类型定义的语言;)

类型转换

类型转换可以分为两种:🌛隐式类型转换和☀️显式类型转换(强制类型)

显式类型强制转换是指当开发人员通过编写适当的代码用于在类型之间进行转换,比如:Number(value)

隐式类型转换是指在对不同类型的值使用运算符时,值可以在类型之间自动的转换,比如 1 == null

(==)两边是隐式转换

严格相等运算符(===)不会触发类型隐式转换,所以它可以用来比较值和类型是否都相等。

常见的隐式转换场景

  • if判断中 -----Boolean类型

  • 比较操作符 "=="

    【 == 比较过程】:

    1. 如果两个值类型相同,再进行三个等号(===)的比较;

    2. 如果两个值类型不同,也有可能相等,需根据以下规则进行类型转换在比较:

      • 如果两个值都是null,或是undefined,那么相等;如果一个是null,一个是undefined,那么相等;

      • 如果一个是字符串,一个是数值,把字符串转换成数值之后再进行比较;

        1 == “1// true
        1 === “1// false
        
      • 如果一个是数值,有一个是布尔值,则在比较之前布尔值先将其转换为数值——false转换为0,而true转换为1;==1和true使用比较结果为true,0和false使用比较结果也为true==,其它都为false;可以理解记忆为使用==比较整数时将1等同true,将0等同false。

        1 == true //true
        1.0000 == true //true
        "1" == true //true
        -1 == true //false -1==true相当于-1==1
        0 == false // true
        -0 == false  // true
        
      • 如果两个值都对象,要看引用地址是否一致 ,引用同一个对象或是函数,那么相等,否则不相等 引用类型 == 值类型 // 对象转化成原始类型的值,再进行比较

        比较规则:

        1. 数组与数值进行比较,会先转成数值,再进行比较;

        2. 与字符串进行比较,会先转成字符串,再进行比较;

        3. 与布尔值进行比较,两个运算子都会先转成数值,然后再进行比较。

          故如果是进行同类型值比较进行逻辑条件是否成立的判断,直接使用===(三等号)比较效率高!不需要进行类型转换。

          [] == []  // false 2个地址
          {} == {}  // false
          [] == {}  // false
          [] != []  // true != 是非等于
          [] == ![] // !的优先级要大于==的,所以先运算右边,![]=>结果为false,那么[]==![] -->[]==false --> 0==0 -->//true
          

    【 === 比较过程】:

    1. 如果类型不同,就一定不相等;

    2. 如果两个都是数值,并且是同一个值,那么相等;如果其中至少一个是NaN,那么不相等。(判断一个值是否是NaN,只能使用isNaN( ) 来判断)

    3. 如果两个都是字符串,每个位置的字符都一样,那么相等,否则不等;

  • 加号"+" 与 减号 "-"

    var add = 1 + 2 + '3'
    console.log(add);  //'33'
    var minus = 3 - true
    console.log(minus); //2
    

    +用作加号操作符,有些时候+作为字符串连接符true被转换为Number类型,值为1,相减后得到2。

  • 关系运算符比较时

     3 > 4  // false
    "2" > 10  // false
    "2" > "10"  // true
    

    如果比较运算符两边都是数字类型,则直接比较大小。如果是非数值进行比较时,则会将其转换为数字然后在比较,如果符号两侧的值都是字符串时,不会将其转换为数字进行比较,而是分别比较字符串中字符的Unicode编码。

  • .点号操作符

    var a = 2;
    console.log(a.toString()); // '2';
    
    var b = 'zhang';
    console.log(b.valueOf()); //'zhang';
    

    在对数字,字符串进行点操作调用方法时,默认将数字,字符串转成对象。

前情知识点**

  1. 先调用对象的 Symbol.toPrimitive 这个方法,如果不存在这个方法
  2. 再调用对象的 valueOf 获取原始值,如果获取的值不是原始值
  3. 再调用对象的 toString 把其变为字符串
  4. 最后再把字符串基于Boolean/Number方法转换为结果

Symbol.toPrimitive

**Symbol.toPrimitive** 是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,作用是将一个对象转换为对应的原始值。

该函数被调用时,会被传递一个字符串参数 hint ,表示要转换到的原始值的预期类型。 hint 参数的取值是 "number""string""default" 中的任意一个。

// 一个没有提供 Symbol.toPrimitive 属性的对象,参与运算时的输出结果
var obj1 = {};
console.log(+obj1);     // NaN
console.log(`${obj1}`); // "[object Object]"
console.log(obj1 + ""); // "[object Object]"

// 接下面声明一个对象,手动赋予了 Symbol.toPrimitive 属性,再来查看输出结果
var obj2 = {
  [Symbol.toPrimitive](hint) {
    if (hint == "number") {
      return 10;
    }
    if (hint == "string") {
      return "hello";
    }
    return true;
  }
};
console.log(+obj2);     // 10      -- hint 参数值是 "number"
console.log(`${obj2}`); // "hello" -- hint 参数值是 "string"
console.log(obj2 + ""); // "true"  -- hint 参数值是 "default"

valueOf 和 toString

  • toString:返回一个表示该对象的字符串;
  • valueOf:返回当前对象的原始值。

区别和共同点

  • 共同点:在输出对象时会自动调用。
  • 不同点:默认返回值不同,且存在优先级关系

二者并存的情况下,在数值运算中,优先调用了valueOf字符串运算中,优先调用了toString


  • toString()valueOf()都是对象的方法
  • toString()返回的是字符串,而valueOf()返回的是原对象
  • undefinednull没有toString()valueOf()方法
  • 包装对象的valueOf()方法返回该包装对象对应的原始值
  • 使用toString()方法可以区分内置函数和自定义函数

什么时候会自动调用呢

使用操作符的时候,如果其中一边为对象,则会先调用toSting方法,也就是隐式转换,然后再进行操作。

let c = [1, 2, 3]
let d = {a:2}
console.log(2 + 1)  // 3
console.log('s')    // 's'
console.log('s'+2)  // 's2'
console.log(c < 2)  // false  
/*[1,2,3].toString()
(3) [1, 2, 3]
Number("1,2,3")
NaN
NaN<2
false
*/
console.log(c + c)  
/* "1,2,31,2,3" (两次 => 'Array') 
[1,2,3]+[2,3,4] "1,2,32,3,4"
[1,2,3].toString()
"1,2,3"
[2,3,4].toString()
"2,3,4"
"1,2,3"+"2,3,4"
"1,2,32,3,4"
*/

console.log(d > d)  // false        (两次 => 'Object')
/*
d.toString();
"[object Object]"
Number("[object Object]")
NaN
NaN>NaN
false

三种类型转换

  • 转化为 Number 类型:Number() / parseFloat() / parseInt()
  • 转化为 String 类型:String() / toString()
  • 转化为 Boolean 类型: Boolean()

其他类型转为字符串类型

null:转为"null"

undefined:转为"undefined"

Boolean:true转为"true"false转为"false"

Number:11转为"11",科学计数法11e20转为"1.1e+21"

数组:空数组[]转为空字符串"",如果数组中的元素有null或者undefined,同样当做空字符串处理,[1,2,3,4]转为"1,2,3,4",相当于调用数组的join方法,将各元素用逗号","拼接起来。

函数:function a(){}转为字符串是"function a(){}"

一般对象:相当于调用对象的toString()方法,返回的是"[object,object]"

    String(null)  // "null"
    String(undefined) // "undefined"
    String(true)  // "true"
    String(false)  // "false"
    String(11)  // "11"
    String(11e20)  // "1.1e+21"
    String([])  // ""
    String([1,null,2,undefined,3])  // 1,,2,,3
    String(function a(){})  // "function a(){}"
    String({})  // "[object,object]"
    String({name:'zhang'})  // "[object,object]"

其他类型转为Boolean类型

只有nullundefined0falseNaN空字符串这6种情况转为布尔值结果为false,其余全部为true,例子如下

    Boolean(null)  // false
    Boolean(undefined)  // false
    Boolean(0)  // false
    Boolean(false)  // false
    Boolean("false")  // true
    Boolean(NaN)  // false
    Boolean("")  // false
    Boolean([])  // true
    Boolean({})  // true

其他类型转为Number类型 ----> Number类型篇

null:转为 0

undefined:转为NaN

Boolean:true转为1false转为0

字符串:如果是纯数字的字符串,则转为对应的数字,如11转为"11""1.1e+21"转为1.1e+21空字符串转为0,其余情况则为NaN

数组:数组首先会被转换成原始类型,即primitive value,得到原始类型后再根据上面的转换规则转换。

对象:和数组一样

Number(null)  // 0
Number(undefined)  //NaN
Number(true)  //1
Number(false)  //0
Number("11")  //11
Number(" ")  //0
Number("1.1e+21") //1.1e+21
Number("abc")  //NaN
Number([])   // 0
Number([0])  // 0
Number([1,2,3])  //NaN
Number(["abc"])  //NaN
Number({})  // NaN

对象转为其他类型(原始类型)

当对象转为其他原始类型时,会先调用对象的valueOf()方法,如果valueOf()方法返回的是原始类型,则直接返回这个原始类型

如果valueOf()方法返回的是不是原始类型或者valueOf()方法不存在,则继续调用对象的toString()方法,如果toString()方法返回的是原始类型,则直接返回这个原始类型,如果不是原始类型,则直接报错抛出异常。

注意:对于不同类型的对象来说,转为原始类型的规则有所不同,比如Date对象会先调用toString

     var a = {
            valueOf() {
                return 0
            }
        }
        var b = {
            toString() {
                return 2
            }
        }
        var c = {
            valueOf() {
                return 3
            },
            toString() {
                return 4
            }
        }
        var d = {
            valueOf() {
                return 5
            },
            toString() {
                return {}
            }
        }
        var e = {
            valueOf() {
                return {}
            },
            toString() {
                return 6
            }
        }
        console.log(Number(a)); //0
        console.log(Number(b)); //2
        console.log(Number(c)); //3
        console.log(Number(d)); //5
        console.log(Number(e)); //6
        console.log(String(a)); //[object Object]
        console.log(String(b)); //2
        console.log(String(c)); //4
        console.log(String(d)); //5
        console.log(String(e)); //6
        console.log(Boolean(a)); //true 这里虽然返回结果是0 但传入的Boolean(val)val是对象{}所以是true
        console.log(Boolean(b)); //true
        console.log(Boolean(c)); //true
        console.log(Boolean(d)); //true
        console.log(Boolean(e)); //true

面试题

true + false
12 / "6"
"number" + 15 + 3
15 + 3 + "number"
[1] > null
"foo" + + "bar"
"true" == true
false == "false"
null == ""
!!"false" == !!"true"
["x"] == "x"
[] + null + 1
0 || "0" && {}  
[1,2,3] == [1,2,3]
{} + [] + {} + [1]
! + [] + [] + ![]
new Date(0) - 0
new Date(0) + 0

答案解析

接下来我们按照之前的转换逻辑来解释一下每一道题,看一下是否和你的答案一样。

true + false  // 1

'+' 运算符会触发 number 类型转换对于 true 和 false

12 / '6'  // 2

算数运算符会把字符串 ‘6’ 转为 number 类型

"number" + 15 + 3  // "number153"

'+' 运算符按从左到右的顺序的执行,所以优先执行 “number” + 15, 把 15 转为 string 类型,得到 “number15” 然后同理执行 “number15” + 3

15 + 3 + "number"  // "18number"

15 + 3 先执行,运算符两边都是 number 类型 ,不用转换,然后执行 18 + “number” 最终得到 “18number”

[1] > null  // true

==> '1' > 0
==> 1 > 0
==> true
复制代码

比较运算符 > 执行 number 类型隐式转换。

"foo" + + "bar"  // "fooNaN"

==> "foo" + (+"bar")
==> "foo" + NaN
==> "fooNaN"
复制代码

一元 + 运算符比二元 + 运算符具有更高的优先级。所以 + bar表达式先求值。一元加号执行字符串“bar” 的 number 类型转换。因为字符串不代表一个有效的数字,所以结果是NaN。在第二步中,计算表达式'foo' + NaN。

'true' == true // false

==> NaN == 1
==> false

'false' == false // false

==> NaN == 0
==> false
复制代码

== 运算符执行 number 类型转换,'true' 转换为 NaN, boolean 类型 true 转换为 1

null == ''  // false
复制代码

null 不等于任何值除了 null 和 undefined

!!"false" == !!"true"  // true

==> true == true
==> true
复制代码

!! 运算符将字符串 'true' 和 'false' 转为 boolean 类型 true, 因为不是空字符串,然后两边都是 boolean 型不在执行隐式转换操作。

['x'] == 'x'  // true
复制代码

== 运算符对数组类型执行 number 转换,先调用对象的 valueOf() 方法,结果是数组本身,不是原始类型值,所以执行对象的 toString() 方法,得到字符串 'x'

[] + null + 1  // 'null1'

==> '' + null + 1
==> 'null' + 1
==> 'null1'
复制代码

'+' 运算符执行 number 类型转换,先调用对象的 valueOf() 方法,结果是数组本身,不是原始类型值,所以执行对象的 toString() 方法,得到字符串 '', 接下来执行表达式 '' + null + 1。

0 || "0" && {}  // {}

==> (0 || '0') && {}
==> (false || true) && {}
==> true && {}
==> {}
复制代码

逻辑运算符 || 和 && 将值转为 boolean 型,但是会返回原始值(不是 boolean)。

[1,2,3] == [1,2,3]  // false
复制代码

当运算符两边类型相同时,不会执行类型转换,两个数组的内存地址不一样,所以返回 false

{} + [] + {} + [1]  // '0[object Object]1'

==> +[] + {} + [1]
==> 0 + {} + [1]
==> 0 + '[object Object]' + '1'
==> '0[object Object]1'
复制代码

所有的操作数都不是原始类型,所以会按照从左到右的顺序执行 number 类型的隐式转换, object 和 array 类型的 valueOf() 方法返回它们本身,所以直接忽略,执行 toString() 方法。 这里的技巧是,第一个 {} 不被视为 object,而是块声明语句,因此它被忽略。计算从 +[] 表达式开始,该表达式通过toString()方法转换为空字符串,然后转换为0。

! + [] + [] + ![] // 'truefalse'

==> !(+[]) + [] + (![])
==> !0 + [] + false
==> true + [] + false
==> true + '' + false
==> 'truefalse'
复制代码

一元运算符优先执行,+[] 转为 number 类型 0,![] 转为 boolean 型 false。

new Date(0) - 0  // 0

==> 0 - 0
==> 0
复制代码

'-' 运算符执行 number 类型隐式转换对于 Date 型的值,Date.valueOf() 返回到毫秒的时间戳。

new Date(0) + 0

==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)' + 0
==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)0'

'+' 运算符触发默认转换,因此使用 toString() 方法,而不是 valueOf()。