JS基本数据类型
- undefined
- null
- number
- string
- symbol(ES6 2015)
- boolean
- bigInt(ES10 2019)
1.JS分为基本数据类型与引用类型,基本数据类型中的string,number,boolean在创建的时候,会自动形成一个包装类型,此包装类型与引用类型的主要区别就是生存周期,时候用new操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中,而自动创建的基本包装类型的对象,则只存在于一行代码的执行瞬间,然后会被立即销毁,这意味着不能给包装类型添加属性和方法
可以将包装类型的创建看做以下过程
- 创建String类型的一个实例
- 在实例上调用指定的方法
- 销毁实例
当然可以显式的调用Boolean、Number、String来创建基本包装类型的对象,不过请不要这么做,因为这样的做法很容易让人无法区分是“基本类型”还是“引用类型”
const a = new Boolean(false);
const b = false;
console.log(typeof a); // object
console.log(typeof b); // boolean
console.log(a instanceof Boolean); // true
console.log(b instanceof Boolean); // false
const c = new Object(false);
console.log(typeof c); // object
console.log(c instanceof Object); // true
console.log(c instanceof Boolean); // true
通过new Object创建的对象,根据传入的类型,如果是number,boolean,string,则会返回包装类型,所以instanceof通过原型链,既能找到Object构造函数,又能找到Boolean构造函数
将Number、String、Boolean作为构造函数调用,类型就已经是object了,通过原型链判断,可以判断构造函数为Boolean
如果不将Number、String、Boolean作为构造函数调用,也就是不加new,则就是作为普通函数调用,会把传入的参数转为number,string,boolean类型(就是基本数据类型,不是包装类型)
类型转换:ToPrimitive 算法
在做条件判断时,除了null,undefined,flase,NaN,'',0,-0,其他所有值都转为true
JavaScript 对象转换到基本类型值时,会使用 ToPrimitive 算法,这是一个内部算法,是编程语言在内部执行时遵循的一套规则。
ToPrimitive算法执行时,会传入一个hint,表示这是什么类型的转换运算(也有叫做期望值),根据这个hint参数,会决定如何执行ToPrimitive算法逻辑
hint参数取值只能是以下3个之一:
- string
- number
- default
当对象发生转换成基本类型时,对按照以下逻辑调用对象上的方法
- 如果存在
[Symbol.toPrimitive],则优先调用 - 如果1不存在,且
hint是string,则依次调用toString()和valueOf()(如果toString()返回了基本类型,则不会继续调用) - 如果1不存在,且
hint是number或者default,则依次调用valueOf()和toString()(如果valueOf()返回了基本类型,则不会继续调用)
详细过程:
ToPrimitive(input [, PreferredType]) 该抽象操作接受一个参数input和一个可选的参数PreferredType。该抽象操作的目的是把参数input转化为非对象数据类型,也就是原始数据类型。如果input可以同时转化为多个原始数据,那么会优先参考PreferredType的值。
如果input的类型是对象,则执行以下步骤:
- 如果没有传入PreferredType参数,则hint等于"default"
- 如果PreferredType是string,则hint为string,number同理
- 判断是否存在
[Symbol.toPrimitive]方法如果存在,则调用 (1)执行[Symbol.toPrimitive]方法(2)如果结果是原始数据类型,则返回(3)抛出类型错误的异常 - 如果hint是"default",则让hint等于"number"
- 返回OrdinaryToPrimitive(object,hint)操作结果
OrdinaryToPrimitive就是一个判断转换顺序的逻辑
- 如果hint是"string",则执行顺序为toString=>valueOf
- 如果hint是"number",则执行顺序为valueOf=>toString
- 如果无法返回基本数据类型,则抛出错误
并不要求[Symbol.toPrimitive]和toString()/valueOf()返回hint期望的值,但是
[Symbol.toPrimitive]和toString方法返回的值必须是基本类型- valueOf可以返回基本类型和非基本类型
Symbol.toPrimitive 是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。
默认情况下,普通对象具有toString和valueOf方法
toString方法返回一个字符串"[object Object]"valueOf返回对象自身
// 一个没有提供 Symbol.toPrimitive属性的对象,参与运算时的输出结果
const obj1 = {};
console.log(+obj1); // NaN
console.log(`${obj1}`); // "[object Object]"
console.log(obj1 + ""); // "[object Object]"
// 接下面声明一个对象,手动赋予了Symbol.toPrimitive属性,再来查看输出结果
const 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"
Symbol和Date已经默认定义了ToPrimitive方法。 Date默认定义的方法是Date.prototype[Symbol.toPrimitive]、 Symbol默认定义的方法是Symbol.prototype[Symbol.toPrimitive]
Date 对象的
@@toPrimitive方法可以返回一个原始值,或是 string,或是number。 如果 hint 是 "string" 或 "default",@@toPrimitive将会调用 toString。如果 toString 属性不存在,则调用 valueOf。如果 valueOf 也不存在,则抛出一个TypeError。 如果 hint 是 "number",@@toPrimitive会首先尝试 valueOf,若失败再尝试 toString。 当期望一个原始值却收到一个对象时,JavaScript 可以自动的调用@@toPrimitive方法来将一个对象转化成原始值,所以你很少会需要自己调用这个方法。
从标准看ToPrimitive 算法
从加法逻辑开始,可以看到在加法逻辑中使用了ToPrimitive方法(第5、6步)

ToPrimitive方法逻辑原解释,可以看到ToPrimitive算法,会将所有基本类型数据都直接返回,只有对象会进行[[DefaultValue]]逻辑处理

[[DefaultValue]](hint)逻辑原解释

再看减法操作,可以看到减法就有一个ToNumber的处理(5、6步)

再看ToNumber,可以发现ToNumber的逻辑是先进行ToPrimitive操作,再返回了基本数据类型后,再进行一次ToNumber操作,返回number类型的最终结果

可以简单理解一下这个逻辑
const abc = {}; // 定义一个对象
abc - 1; // 执行逻辑减法:最后值为NaN,
abc + 1; // 执行逻辑加法:最后值为"[object Object]1"
减法分析步骤
- 对abc进行ToNumber操作
- 判断abc是对象,对abc进行ToPrimitive逻辑处理
[[DefaultValue]](Number)对abc进行处理,判断abc是否有Symbol.toPrimitive对象,如果有就返回,如果没有,优先调用valueOf()- abc.valueOf() 的值为{},继续进行toString()处理
- abc.toString() 的值为"[object Object]",返回
- 对上一步返回的值再进行
ToNumber操作,即等同于Number("[object Object]"),得到值NaN - 最后执行减法,NaN - 1,值为 NaN
加法分析步骤
- 对abc进行ToPrimitive操作
- 判断abc是对象,对abc进行
[[DefaultValue]](default)处理,判断abc是否有Symbol.toPrimitive对象,如果有就返回,如果没有,优先调用valueOf() - abc.valueOf() 的值为{},继续进行toString()处理
- abc.valueOf() 的值为{},继续进行toString()处理
- abc.toString() 的值为"[object Object]",返回
- 逻辑操作已经变成了"[object Object]" + 1,根据加法规则第7条,任意一方是string,则对另一方进行
ToString操作,类似String(),所以这里会对1进行String(1)处理,变成"1"; - 最后执行字符串追加,结果为"[object Object]1"
关键点:
- 可以看到ToPrimitive会对object使用
[[DefaultValue]](hint)的转换逻辑,[[DefaultValue]](hint)的处理逻辑其实就是上面OrdinaryToPrimitive方法的内容,简单说来就是hint为string,则toString()优先调用,如果hint是number,则valueOf()优先调用,任意一个调用返回了基本数据类型,就返回,如果都没有基本数据类型返回,则抛出错误 - 注意到加法操作很特殊,特别是第七条和第八条,核心解释就是加法操作有任意一方直接就是string或者通过ToPrimitive转换后是string,就会优先执行字符串的追加,否则才会进行ToNumber转换,进行数学加法处理,这也是为什么是以下结果的原因
const abc = {};
const abc2 = NaN;
const abc3 = null;
abc + 1; // "[object Object]1"
abc2 + 1; // NaN,因为abc2判断是number,在ToPrimitive的时候,只有object会进行进一步处理,其他的值都会直接返回,返回后,进行ToNumber处理,参考ToNumber的处理表,number会直接返回值,所以这里是 NaN + 1 = NaN
abc3 + 1 // 1 ,这里也是同样,abc2判断是null,在ToPrimitive的时候,返回了null,判断null的类型不是string,就会进行ToNumber处理,参考ToNumber的处理表,ToNumber会对null处理成+0,所以这里的结果就是 +0 + 1 = 1
- 除了ToNumber还有ToString、ToBoolean等,这些都是指代js的转换逻辑,每一个都有一张转换表,对应什么类型做什么样的转换,比如ToNumber遇到number类型就直接返回,遇到对象就就行ToPrimitive逻辑处理,取得了基本数据类型后,再进行一次ToNumber处理,体现到方法就是Number()、Boolean()、String()
其他计算操作符的逻辑
一元操作符
- ++UnaryExpression 【ToNumber】
- --UnaryExpression 【ToNumber】
- +UnaryExpression 【ToNumber】
- -UnaryExpression 【ToNumber】
- ~UnaryExpression 【ToInt32】
- !UnaryExpression 【ToBoolean】
二元操作符
- MultiplicativeExpression * UnaryExpression
- MultiplicativeExpression / UnaryExpression
- MultiplicativeExpression % UnaryExpression
- / % - 操作,全部都是【ToNumber】处理
唯独只有 + 操作,会先进行ToPrimitive处理,再依据左右两边的值进行字符串拼接或者ToNumber
比较操作符
比较操作符也是ToPrimitive逻辑处理,但是指定了hint为number,这里要注意Date,Date的默认ToPrimitive的hint是string,在作为比较操作的时候,Date会优先进行hint为number的转换

等比较操作符
等比较操作符,重点只关注==,都知道==会做隐式转换,===则不会,所以要关注的就是==的转换规则

再贴上中文版的

这里就可以解释经典的[] == ![] 返回true的疑惑了
- 等式右边![] 返回为 false,等式变为[] == false
- false通过ToNumber转换,等式变为 [] == 0
- 等式左边[]通过ToPrimitive处理,等式变为 "" == 0
- 对等式左边进行ToNumber处理,等式变为 0 == 0
- 返回true
相关参考:ToPrimitive解释、博客1、博客2、 博客3