首先看下类型之间相互转换结果
ES6 前,JavaScript 共有六种数据类型:Undefined、Null、Boolean、Number、String、Object。 先说下他们之间的类型转换
1. 其他转布尔值
我们使用 Boolean 函数将类型转换成布尔类型,在 JavaScript 中,只有 6 种值可以被转换成 false,其他都会被转换成 true。
- 注意空字符串
console.log(Boolean()) // false
console.log(Boolean(false)) // false
console.log(Boolean(undefined)) // false
console.log(Boolean(null)) // false
console.log(Boolean(+0)) // false
console.log(Boolean(-0)) // false
console.log(Boolean(NaN)) // false
console.log(Boolean("")) // false
console.log(Boolean(" ")) // true
2. 原始值转数字
我们可以使用 Number 函数将类型转换成数字类型,如果参数无法被转换为数字,则返回 NaN。 如果 Number 函数不传参数,返回 +0,如果有参数,调用 ToNumber(value)。
注意这个 ToNumber 表示的是一个底层规范实现上的方法,并没有直接暴露出来。 而 ToNumber 则直接给了一个对应的结果表 Undefined NaN Null +0 Boolean 如果参数是 true,返回 1。参数为 false,返回 +0 Number 返回与之相等的值 String 这段比较复杂,看例子
console.log(Number()) // +0
console.log(Number(undefined)) // NaN
console.log(Number(null)) // +0
console.log(Number(false)) // +0
console.log(Number(true)) // 1
console.log(Number("123")) // 123
console.log(Number("-123")) // -123
console.log(Number("1.2")) // 1.2
console.log(Number("000123")) // 123
console.log(Number("-000123")) // -123
console.log(Number("0x11")) // 17
console.log(Number("")) // 0
console.log(Number(" ")) // 0
console.log(Number("123 123")) // NaN
console.log(Number("foo")) // NaN
console.log(Number("100a")) // NaN
如果通过 Number 转换函数传入一个字符串,它会试图将其转换成一个整数或浮点数,而且会忽略所有前导的 0,如果有一个字符不是数字,结果都会返回 NaN,鉴于这种严格的判断,我们一般还会使用更加灵活的 parseInt 和 parseFloat 进行转换。
(了解一下) parseInt 只解析整数,parseFloat 则可以解析整数和浮点数,如果字符串前缀是 "0x" 或者"0X",parseInt 将其解释为十六进制数,parseInt 和 parseFloat 都会跳过任意数量的前导空格,尽可能解析更多数值字符,并忽略后面的内容。如果第一个非空格字符是非法的数字直接量,将最终返回 NaN:
console.log(parseInt("3 abc")) // 3
console.log(parseFloat("3.14 abc")) // 3
console.log(parseInt("-12.34")) // -12
console.log(parseInt("0xFF")) // 255
console.log(parseFloat(".1")) // 0.1
console.log(parseInt("0.1")) // 0
3. 原始值转字符串
String 函数将类型转换成字符串类型 String 函数不传参数,返回空字符串,如果有参数,调用 ToString(value),而 ToString 也给了一个对应的结果表 ToString 和上一节的 ToNumber 都是底层规范实现的方法,并没有直接暴露出来。
Undefined "undefined" Null "null" Boolean 如果参数是 true,返回 "true"。参数为 false,返回 "false" String 返回与之相等的值 Number 又是比较复杂,可以看例子
console.log(String()) // 空字符串
console.log(String(undefined)) // undefined
console.log(String(null)) // null
console.log(String(false)) // false
console.log(String(true)) // true
console.log(String(0)) // 0
console.log(String(-0)) // 0
console.log(String(NaN)) // NaN
console.log(String(Infinity)) // Infinity
console.log(String(-Infinity)) // -Infinity
console.log(String(1)) // 1
4. 原始值转对象
原始值通过调用 String()、Number() 或者 Boolean() 构造函数,转换为它们各自的包装对象。 null 和 undefined 属于例外,当将它们用在期望是一个对象的地方都会造成一个类型错误 (TypeError) 异常,而不会执行正常的转换。
var a = 1;
console.log(typeof a); // number
var b = new Number(a);
console.log(typeof b); // object
5. 对象转字符串和数字
对象转布尔值这里就再说,所有对象(包括数组和函数)都转换为 true。对于包装对象也是这样 console.log(Boolean(new Boolean(false))) // true
对象转字符串
对象到字符串和对象到数字的转换都是通过调用待转换对象的一个方法来完成的。而 JavaScript 对象有两个不同的方法来执行转换,一个是 toString,一个是 valueOf。注意这个跟上面所说的 ToString 和 ToNumber 是不同的,这两个方法是真实暴露出来的方法。
所有的对象除了 null 和 undefined 之外的任何值都具有 toString 方法,通常情况下,它和使用 String 方法返回的结果一致。toString 方法的作用在于返回一个反映这个对象的字符串,然而这才是情况复杂的开始。
Object.prototype.toString 方法会根据这个对象的[[class]]内部属性,返回由 "[object " 和 class 和 "]" 三个部分组成的字符串。举个例子:
Object.prototype.toString.call({a: 1}) // "[object Object]"
({a: 1}).toString() // "[object Object]"
({a: 1}).toString === Object.prototype.toString // true
我们可以看出当调用对象的 toString 方法时,其实调用的是 Object.prototype 上的 toString 方法
-
⚠️注意当然这里 同String方法是可以重写的
-
对象根据不同类型有自己的各自的特点,定义了更多版本的 toString 方法
- 数组的 toString 方法将每个数组元素转换成一个字符串,并在元素之间添加逗号后合并成结果字符串。
- 函数的 toString 方法返回源代码字符串。
- 日期的 toString 方法返回一个可读的日期和时间字符串。
- RegExp 的 toString 方法返回一个表示正则表达式直接量的字符串。
console.log(({}).toString()) // [object Object]
console.log([].toString()) // ""
console.log([0].toString()) // 0
console.log([1, 2, 3].toString()) // 1,2,3
console.log((function(){var a = 1;}).toString()) // function (){var a = 1;}
console.log((/\d+/g).toString()) // /\d+/g
console.log((new Date(2010, 0, 1)).toString()) // Fri Jan 01 2010 00:00:00 GMT+0800 (CST)
而另一个转换对象的函数是 valueOf,表示对象的原始值。默认的 valueOf 方法返回这个对象本身,数组、函数、正则简单的继承了这个默认方法,也会返回对象本身。日期是一个例外,它会返回它的一个内容表示: 1970 年 1 月 1 日以来的毫秒数。
var date = new Date(2017, 4, 21);
console.log(date.valueOf()) // 1495296000000
对象转数字
了解了 toString 方法和 valueOf 方法,我们分析下从对象到字符串是如何转换的。其实就是 ToString 方法的对应表,只是这次我们加上 Object 的转换规则: Object 1. primValue = ToPrimitive(input, String) 2. 返回ToString(primValue).
所谓的 ToPrimitive 方法,其实就是输入一个值,然后返回一个一定是基本类型的值。 我们总结一下,当我们用 String 方法转化一个值的时候,如果是基本类型,就参照 “原始值转字符” 这一节的对应表,如果不是基本类型,我们会将调用一个 ToPrimitive 方法,将其转为基本类型,然后再参照“原始值转字符” 这一节的对应表进行转换。
其实,从对象到数字的转换也是一样: Object 1. primValue = ToPrimitive(input, Number) 2. 返回ToNumber(primValue)。
ToPrimitive
那接下来就要看看 ToPrimitive 了,在了解了 toString 和 valueOf 方法后,这个也很简单。 让我们看规范 9.1,函数语法表示如下: ToPrimitive(input[, PreferredType]) 复制代码第一个参数是 input,表示要处理的输入值。 第二个参数是 PreferredType,非必填,表示希望转换成的类型,有两个值可以选,Number 或者 String。 当不传入 PreferredType 时,如果 input 是日期类型,相当于传入 String,否则,都相当于传入 Number。 如果传入的 input 是 Undefined、Null、Boolean、Number、String 类型,直接返回该值。
- 如果是 ToPrimitive(obj, String),处理步骤如下:
如果 obj为 基本类型,直接返回 否则,调用 toString 方法,如果返回一个原始值,则 JavaScript 将其返回。 否则,调用 valueOf 方法,如果返回一个原始值,则 JavaScript 将其返回。 否则,JavaScript 抛出一个类型错误异常。
- 如果是 ToPrimitive(obj, Number),处理步骤如下:
如果 obj 为 基本类型,直接返回 否则,调用 valueOf 方法,如果返回一个原始值,则 JavaScript 将其返回。 否则,调用 toString 方法,如果返回一个原始值,则 JavaScript 将其返回。 否则,JavaScript 抛出一个类型错误异常。
7. 对象转字符串和数字的总结
所以总结下
对象转字符串可以概括为:
如果对象具有 toString 方法,则调用这个方法。如果他返回一个原始值,JavaScript 将这个值转换为字符串,并返回这个字符串结果。 如果对象没有 toString 方法,或者这个方法并不返回一个原始值,那么 JavaScript 会调用 valueOf 方法。如果存在这个方法,则 JavaScript 调用它。如果返回值是原始值,JavaScript 将这个值转换为字符串,并返回这个字符串的结果。 否则,JavaScript 无法从 toString 或者 valueOf 获得一个原始值,这时它将抛出一个类型错误异常。
对象转数字
对象转数字的过程中,JavaScript 做了同样的事情,只是它会首先尝试 valueOf 方法 如果对象具有 valueOf 方法,且返回一个原始值,则 JavaScript 将这个原始值转换为数字并返回这个数字 否则,如果对象具有 toString 方法,且返回一个原始值,则 JavaScript 将其转换并返回。 否则,JavaScript 抛出一个类型错误异常。
例子
console.log(Number({})) // NaN
console.log(Number({a : 1})) // NaN
console.log(Number([])) // 0
console.log(Number([0])) // 0
console.log(Number([1, 2, 3])) // NaN
console.log(Number(function(){var a = 1;})) // NaN
console.log(Number(/\d+/g)) // NaN
console.log(Number(new Date(2010, 0, 1))) // 1262275200000
console.log(Number(new Error('a'))) // NaN
当我们 Number([]) 的时候,先调用 [] 的 valueOf 方法,此时返回 [],因为返回了一个对象而不是原始值,所以又调用了 toString 方法,此时返回一个空字符串,接下来调用 ToNumber 这个规范上的方法,参照对应表,转换为 0, 所以最后的结果为 0。
而当我们 Number([1, 2, 3]) 的时候,先调用 [1, 2, 3] 的 valueOf 方法,此时返回 [1, 2, 3],再调用 toString 方法,此时返回 1,2,3,接下来调用 ToNumber,参照对应表,因为无法转换为数字,所以最后的结果为 NaN。
JSON.stringify
JSON.stringify() 方法可以将一个 JavaScript 值转换为一个 JSON 字符串,实现上也是调用了 toString 方法,也算是一种类型转换的方法。下面讲一讲JSON.stringify 的注意要点:
1. 处理基本类型时,与使用toString基本相同,结果都是字符串,除了 undefined
console.log(JSON.stringify(null)) // null
console.log(JSON.stringify(undefined)) // undefined,注意这个undefined不是字符串的undefined
console.log(JSON.stringify(true)) // true
console.log(JSON.stringify(42)) // 42
console.log(JSON.stringify("42")) // "42"
2. 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
JSON.stringify([new Number(1), new String("false"), new Boolean(false)]); // "[1,"false",false]"
3. undefined、任意的函数以及 symbol 值
undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。
JSON.stringify({x: undefined, y: Object, z: Symbol("")});
// "{}"
JSON.stringify([undefined, Object, Symbol("")]);
// "[null,null,null]"
4. JSON.stringify 有第二个参数 replacer
JSON.stringify 有第二个参数 replacer,它可以是数组或者函数,用来指定对象序列化过程中哪些属性应该被处理,哪些应该被排除。
function replacer(key, value) {
if (typeof value === "string") {
return undefined;
}
return value;
}
var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};
var jsonString = JSON.stringify(foo, replacer);
console.log(jsonString)
// {"week":45,"month":7}
// 另一个例子
var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};
console.log(JSON.stringify(foo, ['week', 'month']));
// {"week":45,"month":7}
5. 序列化的对象拥有 toJSON 方法
如果一个被序列化的对象拥有 toJSON 方法,那么该 toJSON 方法就会覆盖该对象默认的序列化行为:不是那个对象被序列化,而是调用 toJSON 方法后的返回值会被序列化,例如:
var obj = {
foo: 'foo',
toJSON: function () {
return 'bar';
}
};
JSON.stringify(obj); // '"bar"'
JSON.stringify({x: obj}); // '{"x":"bar"}'
隐式类型转换
1. 隐式类型转换的条件
隐式类型转换主要发生在+、-、*、/以及==、>、<这些运算符之间 当然本节也会讲解下 == 和 === 的区别
一元操作符 +
当 + 运算符作为一元操作符的时候,根据ES规范会调用 ToNumber 处理该值,相当于 Number('')
那么下边这些结果呢
console.log(+[]); // 0
console.log(+['1']); // 1
console.log(+['1', '2', '3']); // toString 为 1,2,3 ToNumber NaN
console.log(+{}); // NaN
既然是调用 ToNumber 方法,根据前边章节中的内容,当输入的值是对象的时候,先调用 ToPrimitive(input, Number) 方法,执行的步骤是:
如果 obj 为基本类型,直接返回 否则,调用 valueOf 方法,如果返回一个原始值,则 JavaScript 将其返回。 否则,调用 toString 方法,如果返回一个原始值,则JavaScript 将其返回。 否则,JavaScript 抛出一个类型错误异常。
以 +[] 为例,[] 调用 valueOf 方法,返回一个空数组,因为不是原始值,调用 toString 方法,返回 ""。 得到返回值后,然后再调用 ToNumber 方法,"" 对应的返回值是 0,所以最终返回 0。
二元操作符
基本上就是+-*/
先说 - * /
1 * '23' // 23
1 * false // 0
1 / 'aa' // NaN
console.log(1 / {}) // NaN
console.log({} / 1) // NaN
console.log([] / 1) // 0
console.log(1 / [])// Infinity
console.log('2' / 2) // 1
console.log(2 / '0.2') // 10
console.log(2 / '2n') // NaN
console.log( 1/ null) // Infinity
console.log( 1/ false) // Infinity
console.log( 1/ true) // 1
再说 +
- 先说结论 当计算 value1 + value2时:
lprim = ToPrimitive(value1) rprim = ToPrimitive(value2) 如果 lprim 是字符串或者 rprim 是字符串,那么返回 ToString(lprim) 和 ToString(rprim)的拼接结果 返回 ToNumber(lprim) 和 ToNumber(rprim)的运算结果
console.log(1 + true); // 2
console.log({} + {}); // "[object Object][object Object]"
console.log(new Date(2017, 04, 21) + 1) // "Sun May 21 2017 00:00:00 GMT+0800 (CST)1"
需要注意点
以上的运算都是在 console.log 中进行,如果你直接在 Chrome 或者 Firebug 开发工具中的命令行直接输入,你也许会惊讶的看到一些结果的不同,比如: {} + [] 的结果是 0 {} + [] 的结果是 "[object Object]" 呐,这怎么变成了 0 了? 但是加上括号 ({}+[])
其实,在不加括号的时候,{} 被当成了一个独立的空代码块,所以 {} + [] 变成了 +[],结果就变成了 0 同样的问题还出现在 {} + {} 上,而且火狐和谷歌的结果还不一样:
> {} + {}
// 火狐: NaN
// 谷歌: "[object Object][object Object]"
"==" 双等号
当执行x == y 时: 先来结论
-
如果x与y是同一类型:
-
x是Undefined,返回true
-
x是Null,返回true
-
x是数字: x是NaN,返回false y是NaN,返回false x与y相等,返回true x是+0,y是-0,返回true x是-0,y是+0,返回true 返回false
-
x是字符串,完全相等返回true,否则返回false
-
x是布尔值,x和y都是true或者false,返回true,否则返回false
-
x和y指向同一个对象,返回true,否则返回false
-
-
x是null并且y是undefined,返回true
-
x是undefined并且y是null,返回true
-
x是数字,y是字符串,判断x == ToNumber(y)
-
x是字符串,y是数字,判断ToNumber(x) == y
-
x是布尔值,判断ToNumber(x) == y
-
y是布尔值,判断x ==ToNumber(y)
-
x不是字符串或者数字,y是对象,判断x == ToPrimitive(y)
-
x是对象,y不是字符串或者数字,判断ToPrimitive(x) == y
-
返回false
console.log([] == ![])
// 首先会执行 ![] 操作,转换成 false,相当于 [] == false 相当于 [] == 0 相当于 '' == 0 相当于 0 == 0,结果返回 true
console.log(false == undefined)
// false == undefined 相当于 0 == undefined 不符合上面的情形,执行最后一步 返回 false
console.log(false == [])
// false == [] 相当于 0 == [] 相当于 0 == '' 相当于 0 == 0,结果返回 true
再来些例子
// 都是true
console.log(false == "0")
console.log(false == 0)
console.log(false == "")
console.log("" == 0)
console.log("" == [])
console.log([] == 0)
console.log("" == [null])
console.log(0 == "\n")
console.log([] == 0)
对于<和>比较符
如果两边都是字符串,则比较字母表顺序:
'ca' < 'bd' // false
'a' < 'b' // true
其他情况下,转换为数字再比较:
'12' < 13 // true
false > -1 // true
以上说的是基本类型的隐式转换,而对象会被ToPrimitive转换为基本类型再进行转换:
var a = {}
a > 2 // false
a.valueOf() // {}, 上面提到过,ToPrimitive默认type为number,所以先valueOf,结果还是个对象,下一步
a.toString() // "[object Object]",现在是一个字符串了
Number(a.toString()) // NaN,根据上面 < 和 > 操作符的规则,要转换成数字
NaN > 2 //false,得出比较结果
Object.is() 与比较操作符 “===”、“==” 的区别?
使用双等号(==)进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较。 使用三等号(===)进行相等判断时,如果两边的类型不一致时,不会做强制类型准换,直接返回 false。 使用 Object.is 来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN 是相等的。
- 在这里可以考虑下 Object.is 自己实现
|| 和 && 操作符的返回值?
|| 和 && 首先会对第一个操作数执行条件判断,如果其不是布尔值就先强制转换为布尔类型,然后再执行条件判断。
对于 || 来说,如果条件判断结果为 true 就返回第一个操作数的值,如果为 false 就返回第二个操作数的值。 && 则相反,如果条件判断结果为 true 就返回第二个操作数的值,如果为 false 就返回第一个操作数的值。
|| 和 && 返回它们其中一个操作数的值,而非条件判断的结果
isNaN 和 Number.isNaN 函数的区别?
函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。 函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。
JavaScript 中的包装类型
在 JavaScript 中,基本类型是没有属性和方法的,但是为了便于操作基本类型的值,在调用基本类型的属性或方法时 JavaScript 会在后台隐式地将基本类型的值转换为对象,如
const a = "abc";
a.length; // 3
a.toUpperCase(); // "ABC"
在访问'abc'.length时,JavaScript 将'abc'在后台转换成String('abc'),然后再访问其length属性。 JavaScript也可以使用Object函数显式地将基本类型转换为包装类型:
var a = 'abc'
Object(a) // String {"abc"}
也可以使用valueOf方法将包装类型倒转成基本类型:
var a = 'abc'
var b = Object(a)
var c = b.valueOf() // 'abc'
例子
var a = new Boolean( false );
if (!a) {
console.log( "Oops" ); // never runs
}
// 答案是什么都不会打印,因为虽然包裹的基本类型是false,但是false被包裹成包装类型后就成了对象,所以其非值为false,所以循环体中的内容不会运行。
其他的记录关于 new Object 的参数
let a = new Object(1)
console.log(a)
<!-- Number {1} -->
let b = new Object([1])
console.log(b)
<!-- [1] -->
new Object()将会根据参数value的数据类型,返回对应类型的对象:
如果value为基本数据类型String、Number、Boolean,则返回对应类型的对象。
如果value本身为对象,则返回其本身。
如果省略了value参数,或value为null、undefined,则返回自身无任何属性的Object对象,即返回一个空对象。