上篇刚刚介绍完Javascript数据类型,这篇就写写数据类型检测跟数据类型转换。
在Javascript中多数都是使用typeof来判断数据类型,typeof是否能正确判断数据类型?
1.typeof判断数据类型
let a = "a"
console.log(typeof a) // string
a = 1
console.log(typeof a) // number
a = false
console.log(typeof a) // boolean
a = Symbol()
console.log(typeof a) // symbol
a = undefined
console.log(typeof a) // undefined
a = null
console.log(typeof a) // object
a = []
console.log(typeof a) // object
a = {}
console.log(typeof a) // object
a = function () {}
// a = console.log
console.log(typeof a) // function
null不是对象,答案在Javascript数据类型文章中有讲解
基础数据类型除了null,都显示正确的类型
但是引用数据类型,除了函数之外,都会显示object
因此用typeof判断就不太合适,采用instanceof会更好,instanceof的原理是基于原型链的查询,只要处于原型链中,判断永远为true
2.instanceof判断数据类型
let o = {}
console.log(o instanceof Object) // true
console.log(o instanceof Array) // false
let a = []
console.log(a instanceof Array) // true
console.log(a instanceof Object) // 这是因为 Array 是 object 的子类 所以也是符合true
console.log(a instanceof String) // false
let b = function () {}
console.log(b instanceof Function) // true
console.log(b instanceof Object) // 这是因为 Function 是 object 的子类 所以也是符合true
console.log(b instanceof Array) // false
如果想让instanceof判断基础类型咋弄?
Stmbol.hasInstance 用于判断某对象是否为某构造器的实例。因此你可以用它自定义instanceof行为!
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
let a = []
let aa = {}
console.log(a instanceof MyArray) // true
console.log(aa instanceof MyArray) // false
class MyNumber {
static [Symbol.hasInstance](instance) {
return typeof instance === 'number';
}
}
let b = 123
let bb = 'b'
console.log(b instanceof MyNumber) // true
console.log(bb instanceof MyNumber) // false
能不能手动实现一下instanceof的功能?
核心思路:原型链的向上查找
function myInstanceof(left, right) {
// 基本数据类型直接返回false
if(typeof left !== 'object' || left === null) return false
// getProtypeOf是Object对象自带的一个方法,能够拿到参数的原型对象
let proto = Object.getPrototypeOf(left)
while(true) {
// 查找到尽头,还没找到
if(proto == null) return false
// 找到相同的原型对象
if(proto == right.prototype) return true
proto = Object.getPrototypeof(proto)
}
}
console.log(myInstanceof("111", String)) // false
console.log(myInstanceof(new String("111"), String)) // true
3.==、===、Object.is()讲解
Object.is在===严格相等的基础上修复了一些特殊情况下的失误,具体来说就是+0和-0,NaN和NaN。
function is(x, y) {
if (x === y) {
//运行到1/x === 1/y的时候x和y都为0,但是1/+0 = +Infinity, 1/-0 = -Infinity, 是不一样的
return x !== 0 || y !== 0 || 1 / x === 1 / y;
} else {
//NaN===NaN是false,这是不对的,我们在这里做一个拦截,x !== x,那么一定是 NaN, y 同理
//两个都是NaN的时候返回true
return x !== x && y !== y;
}
}
宽松相等(loose equals)== 和严格相等(strict equals)=== 都用来判断两个值是否 "相等",但是它们之间有一个很重要的区别,特别是在判断条件上。
常见的误区是"==检查值是否相等,===检查值和类型是否相等"。听起来蛮有道理,然而还不够准确。
正确的解释是"==允许在相等比较中进行强类型转换,而===不允许"。
这时候要先讲一些类型转换!
强制类型转换和类型转换
类型转换(type casting)这是显式的操作,强制类型转换(coercion)这是隐式操作。
类型转换发生在静态类型语言的编译阶段,强制类型转换发生在动态类型语言运行时。
然而javascript中通常将它们都称为强制类型转换,建议用"隐式强制类型转换(implicit coercion)"和"显式强制类型转换(explicit coercion)"来区分。
let a = 42
let b = a + "" // 隐式强制类型转换
let c = String(a) // 显式强制类型转换
抽象值操作
字符串、数字和布尔值之间类型转换的基本规则。ES5规范第9节中定义了一些"抽象操作"(即"仅供内部使用的操作")和转换规则。如:ToString、ToNumber、ToBoolean、ToPrimitive
ToString
它主要负责处理非字符串到字符串的强制类型转换。
基本类型的字符串化规则为:
- null 转换为 "null"
- undefined 转换为 "undefined "
- true 转换为 "true"
- 数字的字符串化遵循通用规则,对于极小和极大的数使用指数形式
- 普通对象,除非自定义,否则toString()(object.protoptye.toString())返回内部属性[[ class ]]]的值,如:"[object object]"
极小和极大的数使用指数形式:
let a1 = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 + ""
console.log(a1, typeof a1) // 1.07e+21 string
let aa2 = 0.000000000000000000000000000000007 + ""
console.log(aa2, typeof aa2) // 7e-33 string
普通对象:
let a = {} + ""
console.log(a) // [object Object]
let b = { // 自定义toString
toString: () => {
return "111111"
}
} + ""
console.log(b) // 111111
JSON 字符串化
JSON应该是我们在 JS 中最常用的序列化和反序列化工具之一了。JSON.stringify()在将对象序列化为字符串的时候也用到了 ToString。但是需要注意,JSON 字符串化并非严格意义上的强制类型转换。
对于大多数基本类型值来说,JSON 字符串化的结果和toString()是差不多的,只不过序列化的结果总是字符串
JSON.stringify(1) // "1"
JSON.stringify('1') // ""1"" (带有双引号的字符串)
JSON.stringify(null) // "null"
JSON.stringify(true) // "true"
有些值JSON是无法处理的,例如:undefined、function、symbol和包含循环引用(对象之间相互引用)的对象。我们把这些它们称作不安全的JSON值。
相对
所有安全的JSON值都是可以用JSON.stringif()字符串化。
JSON.stringify()在对象中遇到undefined、function、symbol的时候会自动忽略他们,在数组中这回返回 null(为的是保证数据的下标不变),在遇到循环引用的对象时会报错。例如
JSON.stringify(undefined) // undefined
JSON.stringify(function () {}) // undefined
JSON.stringfiy([1, undefined, function () {}, 4]) // "[1,null,null,4]"
JSON.stringify({ a: 1, b: function () {}, c: undefined }) // "{"a":1}"
JSON.stringify({
toString: function () {
return '1'
},
}) // "{}"
let a = {}
let b = { a: a }
a.b = b
JSON.stringify(a) // Uncaught TypeError: Converting circular structure to JSON
如果对象中定义了toJSON()方法,JSON字符串化的时候会首先调用该方法,然后用它的返回值来进行序列化。如果你对某些非法 JSON 值定义了ToJSON()方法,并返回一个安全的 JSON 值,那么这个值就能被字符串化了。注意,对象是不自带toJSON()方法的,需要你主动定义它。如:
let a = {}
let b = { a: a }
a.b = b
b.toJSON = function () {
return {}
}
JSON.stringify(a) // "{"b":{}}"
let foo = function () {}
foo.toJSON = function () {
return 123
}
JSON.stringify(foo) // "123"
let bar = {
a: undefined,
toJSON: function () {
return { a: null }
},
}
JSON.stringify(bar) // "{"a":null}"
toJSON()返回的并不一定是JSON字符串化后的值(这样其实会对字符串再做一次字符串化),而应当是一个适当的值,可以是任何类型,然后再由JSON.stringify()对其字符串化。
总结:
JSON.stringify()并不是强制类型转换
1.字符串、数字、布尔值、null的JSON.stringify()规则与ToString基本相同。
2.如果传递给JSON.stringify()的对象中定义了toJSON()方法,那么该方法会在字符串化前调用,以便将对象转换为安全的JSON值。
ToNumber
它主要负责处理非数字到数字的强制类型转换。
将基本数据类型转换为数字的规则为:
- true 转换为 1
- false 转换为 0
- undefined 转换为 NaN
- null 转换了 0
- 对字符串的转换遵循通用规则,处理失败时返回NaN,对以 0 开头的十六进制数按十进制处理而非十六进制
- 对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其转换成数字
对象转换成基本类型值的规则为:
symbolToPrimitive抽象操作会检查该值是否有valueOf()方法
如果有并且返回基本类型值,就使用该值进行强制类型转换。
如果没有则使用toString()方法,判断toString()有没有返回值 有的话就进行强制类型转换。
如果valueOf()和toString()都不返回基本类型值,则会产生 TypeError 错误。
例子:
let a = {
valueOf: function () {
return '1'
},
}
let b = {
toString: function () {
return '2'
},
}
Number(a) // 1
Number(b) // 2
let c = [1, 2, 3]
Number(c) // NaN
c.valueOf = function () {
return 1
}
Number(c) // 1
let d = []
Number(d) // 0
d.toString = function () {
return 1
}
Number(d) // 1
let e = [1, 2, 3]
e.toString = function () {
return this.join('') // 123
}
Number(e) //123
ToBoolean
Javascript中有两个关键词true和false,分别代表布尔值类型中的真、假。我们常误认为数值1和0分别等同于true和false。在有些语言中可能是这样,都是在Javascript中布尔值和数字是不一样的。虽然我们可以将1强制类型转换为true,将0转换为false,但是它们并不是一回事。
Javascript中的值可以分为以下两类:
1.可以被强制类型转换为false的值
2.可以被强制类型转换为true的值
以下这些是假值(false):
- undefined
- null
- false
- +0、-0和NaN
- ""
除了假值表以外的所有值都可以理解为真值(true),一般来说,所有的对象都是真值,但是有一些特殊情况,我们可以把他们叫做假值对象。
例子:
var a = new Boolean(false)
var b = new Number(0)
var c = new String('')
var d = new Boolean(0)
var foo = Boolean(a && b && c && d)
foo // true
foo为true,说明a、b、c、d都为true。这些封装了假值的对象并非假值。那它究竟是什么?
虽然Javascript代码中出现了假值对象,但它实际上并不属于javascript语言的范畴。
它们是来自浏览器在某些特定条件下创造的对象,例如 document.all:
document.all // 返回一个 HTMLAllCollection 对象
!!document.all // false
这是一个类数组对象,由 DOM 提供(而非 JavaScript 引擎)提供给 JavaScript 程序使用。它以前曾是一个真正意义上的对象,布尔强制类型转换的结果为 true,但现在它是一个假值对象——并且这已经是一个被废止的方法了。
由于很多 JavaScript 程序依赖document.all来判断是否是旧版浏览器,因此一直没有把它去掉。
再说一说几个真值的情况——虽然刚才说过了假值表以外的都是真值,但是你还可能会遇到一些比较刁钻的状况:
let a = Boolean('false')
console.log(a) // true
let b = Boolean('0')
console.log(b) // true
let c = Boolean('""')
console.log(c) // true
对于字符串来说,除了""以外都是真值。同时,对于[]、{}、function() {}这一类的对象都是真值。
ToPrimitive
它主要负责将值转换为原始值(基本数据类型)的强制类型转换,注意原始值不包括symbol

ToPrimitive(obj,preferredType)
JS引擎内部转换为原始值ToPrimitive(obj,preferredType)函数接受两个参数,第一个obj为被转换的对象,第二个
preferredType为希望转换成的类型(默认为空,接受的值为Number或String)
在执行ToPrimitive(obj,preferredType)时如果第二个参数为空并且obj为Date的事例时,此时preferredType会
被设置为String,其他情况下preferredType都会被设置为Number如果preferredType为Number,ToPrimitive执
行过程如
下:
1. 如果obj为原始值,直接返回;
2. 否则调用 obj.valueOf(),如果执行结果是原始值,返回之;
3. 否则调用 obj.toString(),如果执行结果是原始值,返回之;
4. 否则抛异常。
如果preferredType为String,将上面的第2步和第3步调换,即:
1. 如果obj为原始值,直接返回;
2. 否则调用 obj.toString(),如果执行结果是原始值,返回之;
3. 否则调用 obj.valueOf(),如果执行结果是原始值,返回之;
4. 否则抛异常。
首先我们要明白obj.valueOf()和obj.toString() 还有原始值分别是什么意思,这是弄懂上面描述的前提之一:
toString用来返回对象的字符串表示
let obj = {};
console.log(obj.toString());//[object Object]
let arr2 = [];
console.log(arr2.toString());//""空字符串
let date = new Date();
console.log(date.toString());//Sun Feb 28 2016 13:40:36 GMT+0800 (中国标准时间)
valueOf方法返回对象的原始值(基本数据类型),可能是字符串、数字、布尔值等
let obj = {
name: "obj"
};
console.log(obj.valueOf());//Object {name: "obj"}
let arr1 = [1];
console.log(arr1.valueOf());//[1]
let date = new Date();
console.log(date.valueOf());//1456638436303
如代码所示,三个不同的对象实例调用valueOf返回不同的数据
原始值(基本数据类型):null、undefined、string、boolean、number五种
弄清楚这些以后,举个简单的例子:
let a={};
ToPrimitive(a)
分析:a是对象类型但不是Date实例对象,所以preferredType默认是Number,先调用a.valueOf()不是原始值,继续来调
用a.toString()得到string字符串,此时为原始值,返回之.所以最后ToPrimitive(a)得到就是"[object Object]"
如果觉得描述还不好明白,一大堆描述晦涩又难懂,我们用代码说话:
const toPrimitive = (obj, preferredType='Number') => {
let Utils = {
typeOf: function(obj) {
return Object.prototype.toString.call(obj).slice(8, -1);
},
isPrimitive: function(obj) {
let types = ['Null', 'String', 'Boolean', 'Undefined', 'Number'];
return types.indexOf(this.typeOf(obj)) !== -1;
}
};
if (Utils.isPrimitive(obj)) {
return obj;
}
preferredType = (preferredType === 'String' || Utils.typeOf(obj) === 'Date') ?
'String' : 'Number';
if (preferredType === 'Number') {
if (Utils.isPrimitive(obj.valueOf())) {
return obj.valueOf()
};
if (Utils.isPrimitive(obj.toString())) {
return obj.toString()
};
} else {
if (Utils.isPrimitive(obj.toString())) {
return obj.toString()
};
if (Utils.isPrimitive(obj.valueOf())) {
return obj.valueOf()
};
}
}
var a={};
ToPrimitive(a);//"[object Object]",与上面文字分析的一致
还有更细节的操作,我就不讲了,如果感兴趣的话可以看看《你不知道的 JavaScript 中卷》第一部分第四章,好了,我们回到原题讲解
这时候就会有人说那是不是==比===慢呀?
实际上虽然强制类型转行确实要花多点时间,但仅仅是微秒级(百万分之一秒)的差距而已。
1.字符串和数字之间的相等比较
let a = 42
let b = "42"
console.log(a == b) // true
console.log(a === b) // false
因为 a === b 没有强制类型转换,所以为false,42与"42"不相等。
而 a == b 是宽松相等,即如果两个值得类型不同,则对其中之一或者两者都进行强制类型转换。
具体怎么转换?是 a 从42转换为 字符串的 "42",还是b从"42"转成数字42?
ES5规范定义 11.9.3.4-5这样定义:
1.如果Type(x)是数字,Type(y)是字符串,则返回 x == ToNumber(y) 的结果。
2.如果Type(x)是字符串,Type(y)是数字,则返回ToNumber(x) == y的结果。
根据规范,b的值"42" 应该被强制类型转换为数字以便进行相等比较,所以例子中两个值相等,均为42。
2.其他类型和布尔类型之间的相等比较
let a = "42"
let b = true
console.log(a == b) // false
console.log(a === b) // false
ES5规范定义 11.9.3.6-7这样定义:
1.如果Type(x)是布尔类型,Type(y)是其他类型,则返回 ToNumber(x) == y的结果。
2.如果Type(y)是布尔类型,Type(y)是其他类型,则返回 x == ToNumber(y)的结果。
所以图上例子,Type(b)是布尔类型会将true转换成为1,变成 "42" == 1,然后两者数据类型不一致,所以"42"根据规则11.9.3.4-5(也就是字符串与数字比较规则)进行强制类型转换,所以"42"会变成42,最后变成 42 == 1,结果为false
3.null和undefined之间的相等比较
简单讲解一下 undefined和undeclared
undefined:是在作用域中声明了但是还没有赋值的变量
undeclared:是没有在作用域声明过的变量 如果直接写会报错如: x ReferenceError: b is not define
let test
console.log(test) // undefined
console.log(test2) // test2 is not defined
回到原题
let a = null
let b;
console.log(b) // undefined
console.log(a == b) // true
console.log(a == null) // true
console.log(b == null) // true
console.log(a == false) // false
console.log(b == false) // false
console.log(a == "") // false
console.log(b == "") // false
console.log(a == 0) // false
console.log(b == 0) // false
ES5规范定义 11.9.3.2-3这样定义:
1.如果Type(x)是null,Type(y)是undefined,则返回true。
2.如果Type(x)是undefined,Type(y)是null,则返回 true。
4.对象和非对象之间的相等比较
对象包括:对象、函数、数组
非对象包括:字符串、数字、布尔值(布尔值会先被强制类型转换为数字)
ES5规范定义 11.9.3.8-9这样定义:
1.如果Type(x)是字符串或数字,Type(y)是对象,则返回 x == ToPrimitive(y) 的结果。
2.如果Type(x)是对象,Type(y)是字符串或数字,则返回 ToPrimitive(x) == y 的结果。
let a = 42
let b = [42]
console.log(a == b) // true
[42]会先调用ToPrimitive抽象操作,返回"42",变成 "42" == 42 然后又变成 42 == 42,所以返回true
let a ="abc"
let c = Object(a)
console.log(c) // [String: 'abc']
console.log(c == a) // true
console.log(c === a) // false
c == a 返回true,因为c是通过ToPrimitive进行强制类型转换,并返回标量基本类型值"abc","abc" == "abc"所以返回true。
但是有些值不这样,因为==算法优先级规则。如:
let a = null
let b = Object(a)
console.log(a == b) // false
let c = undefined
var d = Object(c)
console.log(c == d) // false
let e = NaN
let f = Object(e)
console.log(e == f) // false
因为没有对应的封装对象,所以null和undefined不能够封装(boxed),Object(null)和Object()均返回一个常规对象,
NaN能够封装为数字对象,但是拆封之后NaN == NaN 返回false,因为NaN 不等于NaN。
NaN理解为"无效数值"、"失败数值"、"坏数值",因为NaN是一个特殊值,所以它和自身是不相等。
如何判断NaN相等?
let a = NaN
console.log(isNaN(a)) // true
就讲解到这里。
参考:
https://github.com/jawil/blog/issues/5
http://47.98.159.95/my_blog/js-base/002.html
《你不知道的javascript 中卷》
本文深入探讨了JavaScript中的数据类型检测方法,包括typeof、instanceof的使用及其局限性,以及如何自定义instanceof行为。同时,详细解析了==、===、Object.is()的差异和类型转换规则。

被折叠的 条评论
为什么被折叠?



