• 作者:老汪软件技巧
  • 发表时间:2024-12-20 00:08
  • 浏览量:

前言

在写了上篇文章:JS类型转换:那些你不知道的隐性陷阱和规则 - 掘金 我兴致满满的去刷了类型转换的题目,却发现自己在面对+"1" 、+[]、[]+{}这类题目时还是很懵逼

12ce73e1bff4df683aafe00bbe99928.jpg

于是我便花了一上午时间阅读了一些大佬的文章终于对JS类型转换机制有了更清晰的认识,接着便写了这篇更深入的JS类型转换机制的文章,并加入toString、valueOf、toPrimitive的详细解析。

Primitive => Object

原始值通过调用 String()、Number() 或者 Boolean() 构造函数,转换为它们各自的包装对象。

话不多说,先上代码:

var a = 1.234;
console.log(typeof a);
var b = new Number(a);
console.log(typeof b);
console.log(b.toFixed(1))
console.log(a.toFixed(1))

运行一下

number
object
1.2
1.2

可以看到,代码中我们使用Number构造函数创建了一个新的Number对象b,并将变量a的值作为参数传递给构造函数。所以当我们调用Number对象b的toFixed方法,并传入参数1(该方法用于将数字格式化为指定小数位数的字符串)时,输出了1.2。可是为什么a.toFixed(1)也能成功打印1.2,a难道不是一个原始的数字类型吗???

这就涉及到了我们今天分享的第一个知识点——包装类

Object => Primitive1.对象转布尔值

对象转布尔值只需记住一句话:所有的对象(包括数组和函数)都转换为true

代码示例

console.log(Boolean(new Boolean(false)))//true
console.log(Boolean({}))//true
console.log(Boolean([1,2,3]))//true

2. 对象转字符串和数字(难点)ToPrimitive

Js引擎内部的抽象操作ToPrimitive(转换为原始值)的方法大体如下:

/**
* @obj 需要转换的对象
* @type 期望转换为的原始数据类型,可选
*/
ToPrimitive(obj,type)

当type为Number时,处理步骤如下:

若对象有 valueOf() ,则调用,若返回结果为原始值,则进一步转换为数字(看情况转换,非必须)并返回;

否则,若对象具有toString(),则调用,若返回结果为原始值,后续同上;

都无法获得原始值,那么抛出TypeError 异常;

代码示例

let objectWihoutPrimitiveValueOf = {
    valueOf: function() {
        console.log('Calling valueOf')
        return this;
    },
    toString: function() {
        console.log('Calling toString')
        return '789';
    }
}
console.log(Number(objectWihoutPrimitiveValueOf)) 
let problemObj = {
    valueOf: function() {
        console.log('Calling valueOf')
        return this;
    },
    toString: function() {
        console.log('Calling toString')
        return this;
    }
}
try{
console.log(Number(problemObj))
}catch(e){
    console.log('error')
}

输出结果

Calling valueOf
Calling toString
789
Calling valueOf
Calling toString
error

可以看到:

当type为String时,处理步骤如下:

若对象有 toString() ,则调用,若返回结果为原始值,则进一步转换为字符串(若本身不是字符串)并返回;

若对象没有 toString() 或返回的不是一个原始值,那么调用 valueOf() ,若结果为原始值,后续同上;

都无法获得原始值,那么抛出TypeError 异常;

代码示例

let objectWithStringValue = {
    toString: function() {
        return 'hello';
    },
    valueOf: function() {
        return 1;
    }
}
console.log(String(objectWithStringValue))
let objectWithValueOf = {
    toString: function() {
        console.log('Calling toString')
        return this;
    },
    valueOf: function() {
        return 2;
    }
}
console.log(String(objectWithValueOf))

输出结果

hello
toString
2

分析一下:

type 是个可选参数,不传入时,默认按照下面规则自动设置

若对象为 Date 类型,则 type为 String;否则 type 为 Number;toString()和valueOf()

image.png

_前端转换json格式的方法_前端转化什么意思

可以看到,当我们在控制台输入Object.prototype时就可以看到 valueOf() 和 toString() 。而所有对象继承自Object,因此所有对象都继承了这两个方法。

Object.prototype.valueOf():对象的valueOf旨在返回对象的原始值,会在需要将对象转换成原始值的地方自动执行。机制如下:

   
 // 1. 基本包装类型直接返回原始值
var num = new Number('123');
console.log(num.valueOf()); // 123
var str = new String('123abc');
console.log(str.valueOf()); // '123abc'
var bool = new Boolean('abc');
console.log(bool.valueOf()); // true
// 2. Date 类型返回一个内部表示:1970年1月1日以来的毫秒数
var date = new Date(2024,12,19);
console.log(date.valueOf())//1737216000000
//3.返回对象本身
var arr = [1,2,3]
console.log(arr.valueOf());//[ 1, 2, 3 ]

Object.prototype.toString():toString()方法会返回表示该对象的字符串,会在对象预期要被转换成字符串的地方自动执行。机制如下:

   
 // 1. 基本包装类型返回原始值
var num = new Number('123abc');
console.log(num.toString()); // NaN
var str = new String('123abc');
console.log(str.toString()); // '123abc'
var bool = new Boolean('abc');
console.log(bool.toString()); // true
  
 // 2. 默认的 toString()
 console.log(({a: 1}).toString())//[object Object]
console.log([1,2].toString())//1,2
console.log((function(){var a = 1;}).toString())//function(){var a = 1;}  
 // 3. 类自己定义的 toString()
  
 // Date类型转换为可读的日期和时间字符
 console.log(String(new Date(2024,12,18)))//Sat Jan 18 2025 00:00:00 GMT+0800 (中国标准时间)
 
 console.log(JSON.stringify({a: 1}))//{"a":1}

值得一提的是,在默认的 toString()中:

另外:

因此,会输出"{"a":1}"。一元操作符 +

作为一元运算符时,+用于将操作数转换为数字类型。即ToNumber()方法,如果操作数已经是数字类型,则不会发生任何变化。如果操作数是字符串类型,则会尝试将其转换为数字。如果操作数是对象,则会先调用对象的valueOf()方法,如果该方法返回的不是原始值,则会继续调用toString()方法,然后将结果转换为数字。如果无法转换为数字,则结果为NaN。

在铺垫了这么多知识后,我们把罪魁祸首的题目搬出来拷打一遍

console.log(+"1")
console.log(+[])
console.log(+['1,2,3'])
console.log(+['1'])
console.log(+{})

已知我们需调用ToNumber()处理,且当输入的值为对象时,先调用ToPrimitive(obj,Number),再对返回值调用Number()方法

我以+{}为例,{}先调用valueOf方法,返回其对象本身,因为不是原始值,,所以再调用toString方法,返回[object Object],再对其使用Number方法,打印结果为NaN。

剩下的题目以此类推,得出结果:

1
0
NaN
1
NaN

怎么样,你做对了吗?

二元运算符 +

+符号既可以用作一元运算符,也可以用作二元运算符。当它作为二元运算符时,它执行加法操作。执行步骤如下:

当计算 value1 + value2时:

lprim = ToPrimitive(value1)

rprim = ToPrimitive(value2)

如果返回值中有一方为字符串,那么返回 ToString(lprim) 和 ToString(rprim)的拼接结果(即+作字符串连接)

否则,返回 ToNumber(lprim) + ToNumber(rprim)的运算结果

以一些题目作为例子

console.log(1+'1')//11
console.log(null + 1)//1
console.log([]+{})//[object Object]
console.log({}+{})//[object Object][object Object]

分析:

1 + '1'

因为1与'1'都是基本类型,所以直接返回其本身,即lprim=1 , rprim='1' 因为rprim是字符串,所以执行ToString(1) + '1',得到'11'

null + 1

同上,null 与 1 都是基本类型,所以直接返回其本身,即lprim=null,rprim=1 又因为lprim与rprim都不是字符串,所以返回 ToNumber(null) + ToNumber(1)的运算结果,得到1

[] + {}

1.- lprim = ToPrimitive([]),因为两个对象中间有个运算符+,相当于ToPrimitive([], Number)先调用valueOf方法,返回对象本身,因为不是原始值,调用toString方法,返回空字符串""

rprim = ToPrimitive({}),同上,返回[object Object]

lprim和rprim都是字符串,执行拼接操作,""+[object Object],得到[object Object]

{}+{}

lprim=rprim = ToPrimitive({}),返回了两个[object Object],都是字符串,即进行[object Object]+[object Object],得到[object Object][object Object]

关于==规范

"=="用于比较两个值是否相等,当要比较的两个值类型不一样的时候,就会发生类型的转换。

使用==进行比较时的具体步骤如下:

image.png

image.png

给出一段代码做一些例子:

console.log([]==![])//true
console.log(42 == ['42'])//true 
console.log(true == '2')//false
console.log(1 == '2')//false
console.log(null == undefined)//true

分析

1.[]==![]

首先会执行![]操作,转换成 false,由规范中第7条相当于[] == false,且ToNumber(false)=0,即[] == 0,由规范中第9条=>比较ToPrimitive([])==0,相当于'' == 0由规范中第4条=>ToNumber('')==0相当于0 == 0,结果返回true

2.42 == ['42']看规范第8条:

如果Type(x) 时String或Number ,而Type(y) 时 Object,返回比较结果x==ToPrimitive(y)

以这个例子为例,会使用 ToPrimitive 处理 ['42'],调用valueOf,返回对象本身,再调用 toString,返回 '42',所以

42 == ['42'] 相当于 42 == '42' 相当于42 == 42,结果为 true。

3.true == '2'

由规范中第6、7条可知,当一方出现布尔值的时候,就会对这一方的值进行ToNumber处理,也就是说true会被转化成

true == '2'就相当于1 == '2'就相当于1 == 2,结果自然是false。

4.1 == '2'

做到这是不是知道一眼false了,相当于1==ToNumber('2'),即1==2,false

5.null == undefined

看规范第2、3条,当一方为未定义,另一方为null时,返回true

其他情况就不一一列举了,规范中条例的很清楚

如果有错误或不足,欢迎纠正或补充。希望这篇文章能给大家带来帮助。


上一条查看详情 +程序是怎么执行的(二):内存的作用
下一条 查看详情 +没有了