JavaScript 作为一种弱类型的语言,不用像 C 语言那样要定义好数据类型,因为允许变量类型的 隐式类型转换 和允许 强制类型转换。我们在定义一个变量的时候,只需一个 var
、let
、const
搞定,不用担心数据的类型。
从 ECMAScript Standard 中了解 Number、String、Boolean、Array 和 Object 之间的相互转换会更加直观。
此处所说的 ToString 并非对象的
toString()
方法,而是指其他类型的值转换为字符串类型的操作。
下面列出常见转换为 String 类型的规则:
null
:转为 "null"
undefined
:转为 "undefined"
true
转为 "true"
false
转为 "false"
10
转为 "10"
1e21
转为 "1e+21"
,
连接,相当于调用数组 Array.prototype.join()
方法''
null
和 undefined
会被当作 空字符串 处理Object.prototype.toString()
,返回 [object Object]
String(null);// "null"String(undefined);// 'undefined'String(true);// 'true'String(10);// '10'String(1e21);// '1e+21'String([1, 2, 3]);// '1,2,3'String([]);// ''String([null]);// ''String([1, undefined, 3]);// '1,,3'String({});// '[object Objecr]'
null
: 转为 0
undefined
:转为 NaN
0
NaN
true
将被转为 1
false
将被转为 0
Number(null);// 0Number(undefined);// NaNNumber('10');// 10Number('10a');// NaNNumber('');// 0Number(true);// 1Number(false);// 0Number([]);// 0Number(['1']);// 1Number({});// NaN
JavaScript 中假值只有 false
、null
、undefined
、""
、0
和 NaN
,其他值转为 Boolean 类型都为 true
。
Boolean(null);// falseBoolean(undefined);// falseBoolean('');// flaseBoolean(NaN);// flaseBoolean(0);// flaseBoolean([]);// trueBoolean({});// trueBoolean(Infinity);// true
ToPrimitive 方法用于将引用类型转换为原始数据类型的操作
🔬 值为引用数据类型时,会调用 JavaScript 内置的 @@ToPrimitive(hint)
方法来指定其目标类型。
valueOf()
方法,若返回值为原始数据类型,则结束 @@ToPrimitive
操作,如果返回的不是原始数据类型,则继续调用对象的 toString()
方法,若返回值为原始数据类型,则结束 @@ToPrimitive
操作,如果返回的还是引用数据类型,则抛出异常。toString()
方法,再调用 valueOf()
方法。[1, 2] =='1,2'[(1, 2)] // true.valueOf() // "[1,2]"[(1, 2)].toString(); // "1,2"const a = {};a == '[object Object]'; // truea.valueOf().toString(); // "[object Object]"
对于不同类型的引用数据类型,ToPrimitive 的规则有所不同,比如 Date 对象会先调用
toString()
方法,具体可以参考 ECMAScript6 规范中对 ToPrimitive 的定义解释以 JavaScript 实现 ToPrimitive
值得一提的是对于 数值类型 的 valueOf()
函数的调用结果仍为数组,因此数组类型的隐式类型转换结果是字符串。
而在 ES6 中引入 Symbol 类型之后,JavaScript 会优先调用对象的 [Symbol.toPrimitive]
方法来将该对象转化为原始类型,那么方法的调用顺序就变为了:
obj[Symbol.toPrimitive](preferredType)
方法存在时,优先调用该方法preferredType
参数为 String 类型,则依次尝试 obj.toString()
与 obj.valueOf()
preferredType
参数为 Number 类型或者默认值,则依次尝试 obj.valueOf()
与 obj.toString()
通过手动进行类型转换,JavaScript 提供了以下转型函数:
Number(mix)
parseInt(string, radix)
parseFloat(string)
toString(radix)
String(mix)
Boolean(mix)
在 JavaScript 中,当运算符在运算时,如果 两边数据不统一,CPU 就无法运算,这时我们编译器会自动将运算符两边的数据做一个数据类型转换,转成相同的数据类型再计算。
这种无需开发者手动转换,而由 编译器自动转换 的方式就称为 隐式类型转换。
JavaScript 的数据类型隐式转换主要分为三种情况:
值在 逻辑判断 和 逻辑运算 时会隐式转换为 Boolean 类型。
Boolean 类型转换规则表:
数据值 | 转换后的值 |
---|---|
数字 0 | false |
NaN | false |
空字符串 "" | false |
null | false |
undefined | false |
非 !0 数字 | true |
非空字符串 !"" | true |
非 !null 对象类型 | true |
⚠️ 注意事项:使用 new
运算符创建的对象隐式转换为 Boolean 类型的值都是 true
。
连续两个非操作可以将一个数强制转换为 Boolean 类型。
!!undefined;// false!!null;// false!!1;// true!!'';// false!!'Hello';// true!!{};// true!![];// true!!function () {};// true
很多内置函数期望传入的参数的数据类型是固定的,如 alert(value)
,它期望传入的 value
为 String 类型,但是如果我们传入的是 Number 类型或者 Object 类型等非 String 类型的数据的时候,就会发生数据类型的隐式转换。这就是环境运行环境对数据类型转换的影响。
类似的方法还有:
alert()
parseInt()
当加号运算符作为一元运算符运算值时,它会将该值转换为 Number 类型。
' ' +// 0'0' +// 0'10' +// 10'String' +// NaNtrue +// 1false +// 0undefined +// 0null +// 0[] +// 0![] +// 0[1] +// 1[1, 2] +// NaN[[1]] +// 1[[1, 2]] +// NaN{} +// NaNfunction () {};// NaN+'' +// 0
当加号运算符作为二元运算符操作值时,它会根据两边值类型进行数据类型隐式转换。
首先,当引用对象类型的值进行二元加号运算符运算时,会涉及到转换为原始数据类型的问题。事实上,当一个对象执行例如加法操作的时候,如果它是原始类型,那么就不需要转换。否则,将遵循以下规则:
valueOf()
方法,如果有返回的是基础类型,停止下面的过程;否则继续toString()
方法,如果有返回的是基础类型,停止下面的过程;否则继续如果运算符两边均为原始数据类型时,则按照以下规则解释:
String()
方法转成字符串然后拼接Number()
方法隐式转换为 Number 类型(如果无法成功转换成数字,则变为 NaN
,再往下操作),然后进行加法算术运算值转换为 Number 类型和 String 类型都会遵循一个原则:如果该值为原始数据类型,则直接转换为 String 类型或 Number 类型。如果该值为引用数据类型,那么先通过固定的方法将复杂值转换为原始数据类型,再转为 String 类型或 Number 类型。ToPrimitive
⚠️ 注意事项:当 {} + 任何值
时, 前一个 {}
都会被 JavaScript 解释成空块并忽略他。
"1" + 1 // "11""1" + "1" // "11""1" + true // "1true""1" + NaN // "NaN""1" + [] // "1""1" + {} // "1[object Object]""1" + function(){} // "1function(){}""1" + new Boolean() // "1false"1 + NaN // NaN1 + "true" // "1true"1 + true // 21 + undefined // NaN1 + null // 11 + [] // "1"1 + [1, 2] // "11,2"1 + {} // "1[object Object]"1 + function(){} // "1function(){}"1 + Number() // 11 + String() // "1"[] + [] // ""{} + {} // "[object Object][object Object]"{} + [] // 0{a: 0} + 1 // 1[] + {} // "[object Object]"[] + !{} // "false"![] + [] // "false"'' + {} // "[object Object]"{} + '' // 0[]["map"] + [] // "function map(){ [native code] }"[]["a"] + [] // "undefined"[][[]] + [] // "undefined"+!![] + [] // 1+!![] // 11-{} // NaN1-[] // 1true - 1 // 0{} - 1 // -1[] !== [] // true[]['push'](1) // 1(![]+[])[+[]] // "f"(![]+[])[+!![]] // "a"
相等运算符 ==
会对操作值进行隐式转换后进行比较
Number()
函数将字符串转换为数值valueOf()
方法,得到的结果按照前面的规则进行比较null
与undefined
是相等的NaN
,则返回 false
'1' == true; // true'1' == 1; // true'1' == {}; // false'1' == []; // falseundefined == undefined; // trueundefined == null; // truenul == null; // true
关系运算符:会把其他数据类型转换成 Number 之后再比较关系(除了 Date 类型对象)
valueOf()
方法(如果对象没有 valueOf()
方法则调用 toString()
方法),得到的结果按照前面的规则执行比较📍 NaN
是非常特殊的值,它不和任何类型的值相等,包括它自己,同时它与任何类型的值比较大小时都返回 false
。
5 > 10;// false'2' > 10;// false'2' > '10';// true'abc' > 'b';// false'abc' > 'aad';// true
JavaScript 原始类型转换表
原始值 | 转换为数字类型 | 转换为字符串类型 | 转换为布尔类型 |
---|---|---|---|
false | 0 | "false" | false |
true | 1 | "true" | true |
0 | 0 | "0" | false |
1 | 1 | "1" | true |
"0" | 0 | "0" | true |
"000" | 0 | "000" | true |
"1" | 1 | "1" | true |
NaN | NaN | "NaN" | false |
Infinity | Infinity | "Infinity" | true |
-Infinity | -Infinity | "-Inifinity" | true |
"" | 0 | "" | false |
" " | 0 | " " | true |
"20" | 20 | "20" | true |
"Hello" | NaN | "Hello" | true |
[] | 0 | "" | true |
[20] | 20 | "20" | true |
[10, 20] | NaN | "10,20" | true |
["Hello"] | NaN | "Hello" | true |
["Hello", "World"] | NaN | "Hello,World" | true |
function(){} | NaN | "function(){}" | true |
{} | NaN | "[object Object]" | true |
null | 0 | "null" | false |
undefined | NaN | "undefined" | false |
(a == 1) && (a == 2) && (a == 3)
能不能为true
?
事实上是可以的,就是因为在 ==
比较的情况下,会进行隐式类型转换。如果参数不是 Date 对象实例,就会进行类型转换,先 valueOf()
再 toString()
。所以,我们只要改变原生的 valueOf()
或者 toString()
方法就可以达到效果:
const a = {num: 0,valueOf: function () {return (this.num += 1);},};const eq = a == 1 && a == 2 && a == 3;console.log(eq);// true// 或者改写他的 toString 方法const num = 0;Function.prototype.toString = function () {return ++num;};function a() {}// 还可以改写 ES6 的 Symbol 类型的 toPrimitive 的方法const a = {[Symbol.toPrimitive]: (function (i) {return function () {return ++i;};})(0),};
每一次进行等号的比较,就会调用一次 valueOf()
方法,自增 1,所以能成立。 另外,减法也是同理:
const a = {num: 4,valueOf: function () {return (this.num -= 1);},};const res = a == 3 && a == 2 && a == 1;console.log(res);
参考文章: