1、js数据类型
最新的ECMAScript 标准定义了8 种数据类型。
7种基本类型(也就是原始值):
Undefined、Null、Boolean、Number、String、Symbol(es6)、BigInt(es10)(这里不讨论 Symbol、BigInt 两种类型。)
1种对象类型:
Object
2、类型转换
数据类型间的转换可分为:
- 原始值间的转换:转换为Boolean、Number、String
- 原始值转换为对象:转换为Object
- 对象转换为原始值:有两种转换方式
这里着重解释下对象转换为原始值的转换规则。
2.1 对象转换为原始值
Js引擎内部的抽象操作ToPrimitive(转换为原始值)的方法大体如下:
/**
* @obj 需要转换的对象
* @type 期望转换为的原始数据类型,可选
*/
ToPrimitive(obj,type)
type 表示期望对象转换为的原始数据类型,分为 Number 和 String。
若type被标记为String(对象到字符串的转换):
- 若对象有 toString() ,则调用,若返回结果为原始值,则进一步转换为字符串(若本身不是字符串)并返回;
- 若对象没有 toString() 或返回的不是一个原始值,那么调用 valueOf() ,若结果为原始值,后续同上;
- 都无法获得原始值,那么抛出TypeError 异常;
若type被标记为Number(对象到数字的转换):
- 若对象有 valueOf() ,则调用,若返回结果为原始值,则进一步转换为数字(看情况转换,非必须)并返回;
[123] == "123" // true [123] == 123 //true
- 否则,若对象具有 toString() ,则调用,若返回结果为原始值,后续同上;
- 都无法获得原始值,那么抛出TypeError 异常;
type 是个可选参数,不传入时,默认按照下面规则自动设置:
- 若对象为 Date 类型,则 type为 String;
- 否则 type 为 Number;
这里,Date 类型的转换和上文讲述的转换不完全一致。它调用 valueOf() 或 toString() 返回的原始值将被直接使用,而不会被强制转换为数字或字符串。
2.1.1 valueOf() 和 toString() 的返回值解析
控制台输出 Object.prototype,就可以看到 valueOf() 和 toString() 。而所有对象继承自Object,因此所有对象都继承了这两个方法。
对 Js 常见的内置对象:基本包装类型(Number、String、Boolean)、单体内置类型(global、Math)、Date、Array、Function、RegExp,分别使用 toString() 和 valueOf() 得到的返回结果,做进一步分析。
valueOf():
- 基本包装类型直接返回原始值
- Date 类型返回毫秒数
- 其他都返回对象本身:由于大多对象是复合值,无法真正表示为一个原始值,因此返回对象本身。
// 1. 基本包装类型直接返回原始值
var num = new Number('123');
num.valueOf(); // 123
var str = new String('123abc');
str.valueOf(); // '123abc'
var bool = new Boolean('abc');
bool.valueOf(); // true
// 2. Date 类型返回一个内部表示:1970年1月1日以来的毫秒数
var date = new Date();
date.valueOf(); // 1608784980823
// 3. 返回对象本身
var obj = new Object({});
obj.valueOf() === obj; // true
var arr = new Array();
arr.valueOf() === arr; // true
var reg = new RegExp(/a/);
reg.valueOf() == reg; // true
var func = function() {};
func.valueOf() == func; // true
// 单体内置类型
global.valueOf() == global; // true
Math.valueOf() == Math; // true
toString()
作用是返回一个反应该对象的字符串。
- 基本包装类型直接返回原始值
- 默认的 toString() 并不会返回看起来有直观意义的值,例如 [object Object]
- 但很多类都有实现各自版本的 toString(),例如日期、数组、正则表达式、函数。
Number.prototype.hasOwnProperty('toString'); // true
String.prototype.hasOwnProperty('toString'); // true
Boolean.prototype.hasOwnProperty('toString'); // true
Date.prototype.hasOwnProperty('toString'); // true 返回可读的日期和时间字符串
Array.prototype.hasOwnProperty('toString'); // true 将每个元素转换为字符串
RegExp.prototype.hasOwnProperty('toString'); // true 返回表示正则表达式的字符串
Function.prototype.hasOwnProperty('toString'); // true 返回这个函数定义的 Javascript 源代码字符串
// 1. 基本包装类型返回原始值
var num = new Number('123abc');
num.toString(); // 'NaN'
var str = new String('123abc');
str.toString(); // '123abc'
var bool = new Boolean('abc');
bool.toString(); // 'true'
// 2. 默认的 toString()
var obj = new Object({});
obj.toString(); // "[object Object]"
global.toString() // "[object Window]"
Math.toString(); // "[object Math]"
// 3. 类自己定义的 toString()
// Date类型转换为可读的日期和时间字符串
var date = new Date();
date.toString(); // "Wed Oct 11 2017 08:00:00 GMT+0800 (中国标准时间)"
// 将每个元素转换为字符串
var arr = new Array(1,2);
arr.toString(); // '1,2'
// 返回表示正则表达式的字符串
var reg = new RegExp(/a/);
reg.toString(); // "/a/"
// 返回这个函数定义的 Javascript 源代码字符串
var func1 = function () {}
func1.toString(); // "function () {}"
function func2() {}
func2.toString(); // "function func2() {}"
对于日期类型,转换为日期的字符串形式比毫秒数来得有意义,因此 type 默认为 String。
2.2 类型转换表分析和归纳
JavaScript类型转换表,参自《JavaScript权威指南》
这里的对象类型(Object)转换为原始值的结果,按默认方式显示(即 Date 标记为 String,其他对象类型标记为 Number)
- 转换为字符串:
-
Undefined、Null 没有 toString() 会报错,可用 String() 打印;
-
Boolean、Number 直接打印 toString() 后的结果;
a. Number:不过那些极小和极大的数字会使用指数形式 -
Symbol 类型的值直接转换,但是只允许显式强制类型转换,使用隐式强制类型转换会产生错误
-
对象类型:除非自行定义 toString() 方法,否则会调用默认的 toString()
a. 数组:[ ] => “”,[9] => “9”,[“a”, “b”, “c”] => “a,b,c”;补充: [undefined] == '' // true 自红宝书:如果数组中的某一项的值是 null 或者 undefined,那么该值在 join()、toLocaleString()、toString()和 valueOf()方法返回的结果中以空字符串表示。
b. 函数:返回这个函数定义的 Javascript 源代码字符串
c. 正则表达式:返回正则表达式的字符串
d. 日期:返回可读的日期和时间字符串
e. 基本包装类型:同其原始值返回的结果
f. 单体内置类型:global => “[object Window]”,Math => “[object Math]”
-
- 转换为数值:
- Undefined => NaN,null => 0;
- Boolean:true => 1,false => 0;
- String:(同调用Number())"" => 0,‘1.2’ => 1.2,‘one’ => NaN;
- Symbol 类型的值不能转换为数字,会报错。
- 对象类型:
a. 数组:[ ] => “” => 0,[9] => “9” => 9,[“a”, “b”, “c”] => “a,b,c” => NaN;
b. 除了数组的前两种情况,其他都为NaN
- 转换为布尔值:
- Undefined、Null、""、正负0、NaN,这五种转换为布尔值时,都会变成 false;
- 其他,包括对象类型都为 true;
- 原始值转换为对象:
- Undefined、Null 转换为对象时报错,而使用 Object() 在这种情况下,不会报错,只是返回一个空对象;
- 其他的原始值通过调用String()、Number() 或 Boolean() 构造函数,转换为基本包装类型;
3、显式转换
人为的转换,使得代码看起来更清晰易读。
显式转换,最简单的方式是使用函数:
- 转换为字符串:toString() 或 String()
- 转换为数值:Number()、parseInt()、parseFloat()
- 转换为布尔值:Boolean()
- 转换为对象:Object()
来测试一下吧
String(false) // "false" 布尔值转换为字符串
Number("123") // 123 字符串转换为数值
Boolean([]) // true 对象类型转换为布尔值
Object(123) // new Number(123) 原始值通过Number()构造函数转换为基本包装类型
4、隐式转换
当运算符在运算时,两边数据不统一,编译器会自动将两边数据进行数据类型转换成统一的再计算。
常见的隐式转换:
- 逻辑语句的类型转换:当使用if、while、for 时,隐式转换为布尔值;
- 逻辑表达式:
- ! 逻辑非,隐式转换为布尔值,并取反,!!两次逻辑非,隐式转换为布尔值;
- || 和 && 会将非布尔值操作数,隐式转换为布尔值,再判断;
- 算术表达式
- 递增递减++ --,一元加减 +a、- a(结果的符号取反),二元 -,乘法,除法,求模,隐式转换为 Number;
- +运算符:
- 不同类型间的转换规则
- 如果其中一个操作符是对象,则进行对象到原始值的转换(这里是通过 toString 方法获取);
- 进行了对象到原始值的转换后,如果其中一个操作数是字符串,则另一个操作数也会转换成字符串,再进行连接;
- 否则两个操作数都转换为数字(或者NaN),然后进行加法操作。
- 具体使用时,需要考虑加法的结合性对运算顺序的影响
1 + 2 + "hh" // "3hh" 1 + (2 + "hh") // "12hh"
- 不同类型间的转换规则
- == 运算符:
- 注意:尽管 if 语句会隐式转换为布尔值,但 == 运算符并不会自动得将操作数转换为布尔值。
- 不同类型间的转换规则
-
null == undefined 为true,和其他的比较都为false;
-
如果一个值是数字,另一个是字符串,先将字符串转换为数字,再进行比较;
a. NaN 与所有值都不相等,包括它自己,可使用 isNaN() 来判断一个值是否是数字;console.log(NaN=="dsdd"); // false 字符串"dsdd"转为数字为NaN,但是NaN != NaN
b. 数字间比较,以0开头是8进制;
console.log(012==10); // true console.log(099==99); // true 这种情况是因为八进制中不可能出现9,所以看成一个十进制
-
如果一个值是布尔值,转换为数字再比较;
true == '2' // false, 先把 true 变成 1, '2' 变成 2
-
如果一个值是对象,另一个是数字或字符串或 symbol,将对象转换为原始值再比较(调用 valueOf 方法);
a. 注意,同类型比较时,对象间除非指针一样,否则它就是两个不同的对象; -
其他不同类型比较均不相等;
来三个例子分析下:
// + 运算符
1 + {} = ? // "1[object Object]"
对象(到原始值的转换)转换为字符串后进行连接
分析:
1. {} => "[object Object]" 规则1:如果其中一个操作符是对象,转换为原始值,此时为 1 + "[object Object]"
2. 1 => "1" 规则2:转换后,若其中一个操作数是字符串,另外一个也转换为字符串,再进行连接,此时为 "1[object Object]"
// == 运算符
"1" == true // true
分析:
1. true => 1 规则3:如果一个值是布尔值,转换为数字再比较。 此时转换为 "1" == 1
2. "1" => 1 规则2:如果一个值是数字,另一个值是字符串,则字符串转换数字再比较,此时 1 == 1
// * 运算符
["123"] * {} // NaN
分析:
1. 乘*:两边的操作符会隐式转换为 Number
2. ["123"] 执行对象到数字的转换,type 为 Number
2.1 ["123"] 先执行 valueOf() 返回对象本身
2.2 再通过 toString() 得到原始值(字符串) "123",进一步转换为数字 123
3. {} 执行对象到数字的转换,type 为 Number
3.1 同理得到原始值(字符串) "[object Object]",进一步转换为数字 NaN
4. 结果 123 * NaN = NaN
在代码中,常用到的隐式转换:
x + "" // 等价于 String(x)
+x // 等价于 Number(x),也可以写成 x - 0
!!x // 等价于Boolean(x)
!x // 转换为布尔值,并取反