一、JS 中的两类数据类型
JS 中只有两类数据类型:基本数据类型 和 Object 类型。核心区别只有一点:基本数据类型是值传递的,而 Object 类型是引用传递。
基本类型只有 boolean、number(含 NaN)、string、null、undefined。
其他的,如我们常用的 Array、Date、Object 等都是 Object 类型。
二、原始类型的自动装箱
原始类型中的 boolean、number、string 三类,JS 标准库提供了包装对象:
boolean ---- Boolean
number ----- Number
string ----- String
在需要的时候,原始类型会自动转换成对应的包装对象,这个过程就叫自动装箱。
我们用一段很常用的代码来举个例子:
var str = 'string';
console.log(str.length);
// 6
对于原始类型来说,自然没有 length 这样的属性,但是我们就是能够拿到 length 属性的值。这里实际上就是一个自动装箱的过程。
自动装箱过程并不会影响原来的变量,我们来看扩展一下上面的代码:
var str = 'string';
console.log(str.length);
// 6
var copy_str = str;
copy_str = 'new_string';
console.log(str, copy_str);
// 'string' 'new_string'
通过这段代码我们看到变量 str 依旧是一个原始类型( 因为很明显看到这里是一个值传递 )。说明自动装箱过程并不影响变量本身,变量本身不发生任何变化。
三、通过包装对象实现强制类型转换
上面的三个包装对象可以充当构造函数,直接 new 一个包装对象来使用,来实现强制类型转换。来看下面的一个小例子:
var StrObject = new String("str");
var str = 'str';
console.log(typeof StrObject, typeof str, StrObject == str, StrObject === str);
// 'object' 'string' true false
四、类型转换中,避不开的两个函数: valueOf() 和 toString()
valueOf() -- 返回这个对象逻辑上对应的原始类型的值。如 String 包装对象的 valueOf 返回对应字符串。
toString() -- 返回这个对象的字符串表示。即用一个字符串描述这个对象的内容。
这两个方法都是定义在 Object.prototype 上的方法,所有的 Object 类型都会继承到这两个方法。(实际上 boolean number string 通过自动装箱过程也可以得到这两个方法)
/**
* valueOf 实例
*/
var array = [1];
console.log(array.valueOf());
// [1] -- 返回对象本身(对象类型)
var boolObj = new Boolean(true);
console.log(boolObj.valueOf());
// true -- 返回对应的 bool 值(原始类型)
var date = new Date('8/10/2018');
console.log(date.valueOf());
// 1533830400000 -- 返回从 UTC 1970 年 1 月 1 日午夜开始计算,到所封装的日期所经过的毫秒数(原始类型)
var func = function() { return true; }
console.log(func.valueOf());
// ƒ () { return true; } -- 返回函数本身(对象类型)
var numObj = new Number(10);
console.log(numObj.valueOf());
// 10 -- 返回对应的数值(可能是 NaN)(原始类型)
var obj = { key: 'key' };
console.log(obj.valueOf());
// {key: "key"} -- 对象本身(对象类型)
var str = new String('abc');
console.log(str.valueOf());
// abc -- 返回 字符串值(原始类型)
/**
* toString() 总是返回一个原始 string 类的值
*/
五、JS 内部用于实现类型转换的 4 个函数
这四个函数是用的 JS 引擎内部的,所以不用关注这些函数本身,只需要关注结果就可以了。
/**
* ToPrimitive(input[, PreferredType])
* 将 input 转化为原始类型的值。 PreferredType 的值只能是 Number 或 String。
*
* 如果 PreferredType 的值是 Number, 则按如下规则执行:
* 1、如果 input 是原始类型,则直接返回 input.
* 2、调用 input.valueOf(),如果结果是原始类型,则返回该结果
* 3、调用 input.toString(),如果结果是原始类型,则返回该结果
* 4、抛出 TypeError 异常
*
* 如果 PreferredType 的值是 String, 则按如下规则执行:
* (其实和上面的区别是交换了 2/3 步的顺序)
* 1、如果 input 是原始类型,则直接返回 input.
* 2、调用 input.toString(),如果结果是原始类型,则返回该结果
* 3、调用 input.valueOf(),如果结果是原始类型,则返回该结果
* 4、抛出 TypeError 异常
*
* 如果 PreferredType 的值未传入
* 如果 input 是 Date 类型,则视为把 PreferredType 的值视为 String 操作
* 否则 PreferredType 视为 Number 操作。
*/
/**
* ToBoolean(argument)
* 实际上 if 后面表达式的值就是会按照这个函数操作。具体规则如下:
* (Argument Type) (Result)
* Undefined false
* Null false
* Boolean 参数本身对应的 bool 值
* Number 仅当 argument 为 +0, -0, NaN 是返回 false, 其余返回 true
* String 仅当 argument 为空字符串(长度为0)是,返回 false, 其余返回 true
* Symbol true
* Object true
*/
/**
* ToNumber(argument)
* ToNumber的转化并不总是成功,有时会转化成NaN,有时则直接抛出异常
* 规则如下:
* (Argument Type) (Result)
* Undefined NaN
* Null +0
* Boolean true -- 1; false -- +0
* Number 对应的 number 原始类型
* String 将字符串中的内容转化为数字(比如"23"->23),如果转化失败则返回NaN(比如"23a"->NaN)
* Symbol 抛出 TypeError 异常
* Object 先primValue = ToPrimitive(argument, Number),再对primValue 使用 ToNumber(primValue)
*/
/**
* ToString(argument)
*
* 规则如下:
* (Argument Type) (Result)
* Undefined "undefined"
* Null "null"
* Boolean true -- "true"; false -- "false"
* Number 用字符串显示数字
* String 直接返回对应 string 原始类型
* Symbol 抛出 TypeError 异常
* Object 先primValue = ToPrimitive(argument, Number),再对primValue 使用 ToString(primValue)
*/
六、隐式类型转换
什么是隐式类型转换?
当 JS 期望得到某种类型的值,而实际上这里的值是其他类型的,就会发生隐式类型转换。隐式类型转换涉及到上面说的 4 种方法:
ToBoolean, ToPrimitive, ToNumber, ToString
这里举几个隐式类型转换的例子:
1、期望得到 Number 的相关操作
isNaN
递增递减操作符 ( 前置/后置 ++ -- ) 和 一元正负符号操作符 ( 前置/后置 +=, =+ )
计算符,如 *(乘号) /(除号) ( + 号比较特殊,会根据前后的类型判断 string 还是 number。优先 string)
2、期望得到 Boolean 的相关操作
if 后面的表达式
七、显式类型转换
通过包装对象(Boolean、Number、String)强制返回一个期望类型的值。