1. 基本类型转换
由于Javascript中存在七种基本类型:number
, string
, boolean
, object
, null
, undefined
, symbol
,在使用过程中会有意识和无意识的在 运行时 存在相互转换。
1.1 toString
1.1.1 较大数和较小数
当数字处于较大或较小时,由于Javascript会将数字转换为科学记数法方式记录,这个时候如果对其进行转换为字符串,那么显示结果是科学记数法的字符串
console.log(String(0.0000000001)); // '1e-10'
console.log(String(10000000000000000000000)); // '1e+22'
1.1.2 对象的toString
一般对象的toString
方法,将会显示该对象的[[class]]
Object.prototype.toString.call({}); // '[object Object]'
Object.prototype.toString.call(function(){}) // '[object Function]'
1.1.3 数组的toString
数组由于自身重写过toString
方法,数组的toString
方法会返回逗号连接的字符串
Array.prototype.toString.call(['1', 'a', {'a': 1}]); // '1,a,[object Object]'
1.1.4 JSON.stringify()
JSON.stringify()
方法用于将数据转换为JSON
格式的字符串,但是对于undefined
, function
, symbol
类型的数据,在转换时会选择抛弃,从而返回undefined
JSON.stringify(undefined); // undefined
JSON.stringify(function(){}); // undefined
JSON.stringify(Symbol('test')); // undefined
转换对象在数组中时,这些值会被赋值为null
JSON.stringify([undefined, 1, function(){}]); // [null, 1, null]
如果转换对象出现循环引用,那么在转换的时候会抛出异常。
为了使得我们所有的对象在转换成JSON
字符串的时候可以正常转换,我们可以定义toJSON
方法,该方法在JSON.stringify
调用前会进行调用,对数据进行处理
var a = {a: 1, b: 2};
a.toJSON = function() {
return {a: 1}
}
JSON.stringify(a); // "{"a": "1"}"
JSON.stringify
一共可以接受三个参数,除了第一个参数为转换对象以外,第二个参数可以接收了一个数组或者方法对转换对象属性进行过滤,第三个参数则可以将JSON
字符串进行格式化
var a = {a: 1, b: 2};
// 第二个参数使用数组进行过滤
JSON.stringify(a, ['a']); // "{"a": "1"}"
// 第二个参数使用function,自身对象会先调用方法一次后,每个属性调用一次,返回undefined时则过滤掉该属性
JSON.stringify(a, (k, v)=>{
if (k!=='a') {
return v;
}
}); // "{"b": 2}"
// 第三个参数进行格式化,接收数字时每个层级添加相应数目空格
JSON.stringify({a:1, b:2, c: {a: 1}}, null, 2);
// 第三个参数如果为字符串,则每个层级添加该字符串,能添加的字符串最大长度为10
JSON.stringify({a:1, b:2, c: {a: 1}}, null, '--');
1.2 toNumber
1.2.1 基本类型转Number
基本类型转为number
,按照以下规则转换:
// null , false , '' 转换为0
console.log(Number(null)); // 0
console.log(Number(false)); // 0
console.log(Number('')); // 0
// true转换为1
console.log(Number(true)); // 1
// undefined转换为NaN
console.log(Number(undefined)); // NaN
// 字符串如果是数字格式则会进行转换
console.log(Number('123')); // 123
1.2.2 对象转Number
遵循toPrimitive
的规则,如果对象存在valueOf
方法,则调用该方法,如果调用后返回的结果为基本类型,则按照 1.2.1 基本类型转Number 规则进行转换;如果返回不为基本类型或者不存在valueOf
方法,则调用toString
方法后,按照 1.2.1 基本类型转换Number 规则进行处理
var a = [1];
// 转为Number,首先使用a.valueOf()方法,返回[1]
// 继续调用toString方法,返回'1'
// 再将字符串转为数字1
console.log(Number(a)); // 1
// 如果重写valueOf方法,则会返回超乎想象的结果
a.valueOf = () => {return 2};
console.log(Number(a)); // 2
1.3 toBoolean
boolean
类型转换过程只会进行真假值的检查,其中假值包括: false
, ''
, null
, undefined
, NaN
, +0
, -0
,假值将会转换为false
,假值以外的其他值均为真值,转换为true
console.log(Boolean(false)); // false
console.log(Boolean('')); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN)); // false
console.log(Boolean(+0)); // false
console.log(Boolean(-0)); // false
1.4 特殊
对于一个特殊的对象Object.create(null)
,由于原型链的继承关系,该对象不继承Object
,所以不存在valueOf
和toString
方法,那么在进行转换的时候将抛出异常
var a = Object.create(null);
console.log(Number(a)); // Uncaught TypeError: Cannot convert object to primitive value
2. 类型转换
类型转换可以分为显示类型转换和隐式类型转换,但显示和隐式只是相对的,在我们知道会进行转换的时候他就是隐式,当我们知道会进行转换以后其实就变为了显式转换了。
2.1 显示类型转换
2.1.1 String()和Number()转换
按照 1. 基本类型转换 的toString和toNumber的规则进行转换
2.1.2 一元运算符转换
使用一元运算符(+
, -
)会将数据转换为number
类型,相当于Number(data)
console.log(+[]); // 0 ,使用toNumber的规则,相当于Number([])
2.1.3 位反运算符转换
使用位反运算符(~
)会将数据转换为number
类型,相当于-(Number(data) + 1)
console.log(~[]); // -1
利用位反运算符,可以一些特定方法返回值进行处理后,简化判断
// 例如indexOf匹配失败会返回-1,就可以利用位反运算简化判断条件
if('abc'.indexOf('a') > -1) {}
// 相当于
if(~'abc'.indexOf('a')){}
2.1.4 字符串解析
使用parseInt
,parseFloat
可以将字符串(非字符串会优先转换为字符串)解析为number
类型,不同于Number()
的强制类型转换,解析过程会进行到不能解析为止,而Number()
转换在判断不能转换到时候直接返回NaN
console.log(parseInt('12ab')); // 12
console.log(Number('12ab')); // NaN
在ES5以后,parseInt
默认使用了10进制作为基数进行转换,可以通过第二个参数指定基数,此时对字符串解析过程会按照指定进制进行
console.log(parseInt('1a', 19)); // 29 因为按照19进制1和a都是有效的字符,返回结果为29
2.1.5 !转换
使用'!',可以转换为boolean
类型,转换相当于Boolean(data)
console.log(!0); // true
console.log(!!0); //false
2.1.6 Symbol()对象转换
Symbol
对象不能通过隐式转换进行,如果要进行转换必须使用构造方法来显示转换(似乎只能转为字符串)。
var a = Symbol('1');
console.log(a + 1); Uncaught TypeError: Cannot convert a Symbol value to a number
console.log(String(a) + 1); // 'Symbol(1)1'
2.2 隐式类型转换
2.2.1 算数运算转换
在算数运算的过程中会进行数据转换:
对于+
运算来说,如果两个操作数中存在字符串,则会尝试进行字符串连接操作,如果操作数为对象,则会使用toPrimitive
(使用valueOf
方法和toString
方法)转为基本类型后,再进行判断。如果不存在字符串,则全部转为数字进行加法操作。
console.log(1 + ['1']); // 11 , 先将对象['1']根据toPrimitive规则转为'1',操作变为 1 + '1' , 进行字符串拼接
对于-
运算来说,直接将所有操作数进行toNumber
操作后进行计算
console.log(1 - ['1']); // 0
2.2.2 逻辑语句中的类型转换
作为逻辑语句中的判断条件,将转换为boolean
值进行处理
if(1) { console.log(1)}; // 由于Boolean(1)是true,所以会打印1
if(0) { console.log(1)}; // 由于Boolean(0)是false,所以不会打印1
2.2.3 ||和&&
||
和&&
的操作,返回结果并不是boolean
值,而是根据短路规则,判断操作数的Boolean()
结果,返回两个操作数的其中之一,其中||
在true
时进行短路返回,&&
在false
时进行短路返回
var a = 1 && 0;
console.log(a); // 0 , 由于第一个操作数Boolean(1)是true,所以返回第二个操作数
a = 0 && 1;
console.log(a); // 0 , 由于第一个操作数Boolean(0)是false,所以返回第一个操作数
a = 1 || 0;
console.log(a); // 1, 由于第一个操作数Boolean(1)是true,所以返回第一个操作数
a = 0 || 1;
console.log(a); // 1, 由于第一个操作数Boolean(0)是false,所以返回第二个操作数
3. 数据比较
3.1 宽松等于
使用==
进行比较的称做宽松等于,区别于===
的严格等于,在于进行比较的时候,等式两边的数值在类型不相等的时候会尝试进行强制转换,而严格等于不会进行类型的转换。
宽松等于的转化规则如下:
- 如果两个比较数类型一致且为基本类型,则直接进行比较,数字按照大小,字符串按照字母表顺序比较。
- 如果两个比较数类型不一致且为基本类型,则转换为
number
进行比较。 - 如果两个比较数据均为对象类型,则比较对象的引用是否相等
- 如果两个数据中一个为对象,则根据
toPrimitive
规则,将其转换为基本类型后,进行比较。
console.log('a' == 'a'); // true , 类型相同且为基本类型,则直接比较
console.log('1' == 1); // true , 类型不同但均为基本类型,将'1'转为number进行比较
console.log({} == {}); // false , 两个比较数均为对象,判断引用是否相等
console.log('a' == ['a']); // true,一个比较数为对象,则将['a']转换为'a'后进行比较
对于特殊的undefined
和null
console.log(undefined == null); // true
3.2 抽象比较
抽象比较也就是非等于比较,比较规则和宽松等于规则相同
console.log('a' < 'b'); // true, 类型相同,直接进行比较,字符串按照字母表顺序
console.log('0' < 1); // true, 类型不同但均未基本类型,将'0'转为number后进行比较
console.log('a' < ['b']); // true, 一个比较数为对象,则将['b']转换为'b'后进行比较
4. 参考
《你不知道的Javascript(中卷)》