JavaScript 数据类型,类型检测以及类型转换详解

153 阅读7分钟

1. 数据类型

截止自前为止,js中共有七大内置数据类型:

  • string(字符串)
  • number(数字)
  • boolean(布尔值)
  • null(空值)
  • undefined(未定义,与null区别在于它并未赋值)
  • object(对象)
  • symbol(符号,ES6新增)

undefined类型只有一个值underined,未赋值的变量值默认都是undefined boolean类型只有两种值,falso和true

object又称为“复杂类型",其他类型统称为“基本类型

1.1 值和引用

string, number等基本类型的变量值总是通过值复制(深拷贝)的方式来赋值/传递:

 let a = 2:
 let b = a;
 b++;
 a; //2
 b; //3

object类型(包括数组)和函数则是通过引用复制(浅拷贝),的方式来赋值/传递:

 let a = [1,2,3]:
 let b = a;
 b.push(4):
 a: //[1,2,3,4]
 b; //[1,2,3,4]

引用指向的是值本身而并非变量,一个引用无法更改另一个引用的指向,

 let a = [1,2,3]:
 let b = a
//尝试通过重新赋值b来更改a的引用指向 
b=[3,4,5]:
a; //更改失败,依然是[1,2,3] 
b: //[3,4,5]

1.2 封装对象

基本类型(atring,number等)的值本身不具有属性和方法(如Iength和toString()),通过对应的构造活数(如new String(),new Number())进行了对象封装处理才使其拥有了属性和方法。

有意思的是,我们不需要通过手动进行对象封装而只需要字面量直接赋值(如let a ="字符串") 就可访问其对应的属性和方法。这是因为在访问属性和万法时,JS引擎进行了对象封装的自动处理,访问完华后又从封装对象还原成基本类型

2、类型检测

2.1 typeof

 typeof "字符串"//string"
 
 typeof 12 //number
 
 typeof 12/"a" //"number" 
 
 typeof false //"boolean" 
 
 typeof null //"objecet"
 
 typeof undefined //"undefined" 

 typeof (name:"scw") //"object"

 typeof [1,2,3] //object 

 typeof new Date() //object
 
 typeof new string("字符半") //object 
 
 typeof Symbol("name") //"symbol"
 
 typeof function(){}  //"function"

注意点:

  • 对于数学运算异常返回的NaN,对其执行eypeor依然返回"number"
  • null是基本类型中的唯一一个"假值”,typeof对它的返回值为object"
  • 对函数执行typeor其返回值为"function",function是object的一个子类型

缺点: 无法进一步检测具体的object类型(比如数组)

2.2 instanceof

针对引用赋值方式的数据类型,用typeof检测统一都会返回"object’(除了函数)。可通过instanceof来获取具体的object举型

instanceof运算符用于检测指定构造函数的prototype属性所指向的对象是否出现在某个实例对象的原型链上

//非基本类型
([1,2]) instanceof Object; //true
([1,2]) instanceof Array: //true
( {name:"scw"}) instanceof Object; //true
(funccion (){}) instanceof Object; //true
(function (){}) instanceof Function; //true

//基本类型的字面量不可用于检测,结果统一为ralse
(1234) instanceof Object: //false
(1234) instanceof Number: //false
"字符串" instanceof Object: //false
"字符串" instanceof String: //false

//基本类型的封装对象
 new Number (1234) instanceof Object: //true
 new Number (1234) instanceof Number; //true
 new string("字符丰") instanceof Object: //true 
 new String("字符串") instanceof Strng: //true
 

 //自定义构造函数例子
 function Person(){
 }

 const p1 = new ferson()
 p1 instanceof Person //true

缺点:

  • 通过字面量形式创建的基本类型值(如const a = "string")不可用于instanceof检测,其本身并不是对象类型的实例。可通过手动封装对象(如const a = new String("string"))来解决这一问题
  • 无法检测symbol,null和undefined,因为这些类型的值没有与之对应的构造函数

2.3 Object.prototype.toString.call(推荐)


 Object.prototype.toString.call ("string") //"[object String]"
 Object.prototype.toString.call (new String("string") // "[object String]"
 Object.prototype.toString.call (()=>{}) //"[object Function]"
 Object.prototype.toString.call ([1,2,3]) //"[object Array]"
 Object.prototype.toString.call ({a:1}) //"[object Object]"
 Object.prototype.toString.call (123) //"[object Number]"
 Object.prototype.toString.call (true) //"[object Boolean]"
 Object.prototype.toString.call (null) //"[object Null]"
 Object.prototype.toString.call (undefined) //"[object Undefined]"

可以发现,Object.prototype.toString.call非常适合检测具体的数据类型,也没有像instanceof那样的关于基本类型检测无效的问题。那么这个方法到底是怎么来的呢

所有对象(包括基本类型)的toString()方法原本就是从Object.prototype继承而来的,但是Number,String, Function,Array等这些构造函数各自的原型在继承时改写了该方法,如下示例:

({name: 123}) .toString() //"[object Object]"
"字符串".toString()//"字符串"
(123).toString() //123
[1,2,3] .toString() // ‘1,2,3’
(()=>()).toString()//"()=>{}"

 String.tostring //function String() { [native code ]}
 Number.tostring //function Number() { [native code ]}
 Array.tostring //function Array() { [native code ]}

而Object.prototype.toString()是可以对this对象返回对应的具体数据类型的,直接调用Object.prototype.toString()时它内部的this指向的是Object.prototype,所以永远返回的是"[object Object]"。我们通过call修改this指向为我们需要检测类型的值即可

验证:

"字符串".toString()//字符串
delete String.procotype.toString
"字符串".toString()// "[object String]"

删除构造函数String原型上改写的toString方法后,就会沿若原型链调用Object.prototype.toString

2.4 constructor

每个对象实例默认都可以访问到一个constructor属性,这个属性值“似乎"指向创建这个对象的构造函数:

"string".constructor === String //true
({}) .constructor === Object //true
[1,2,3,4] .constructor === Array //true
(()=>{}) .constructor === Function //true

严格意义上讲,constructor并不是表示“对象实例由:..构造(相关原型细节知识自行查询)


 function Person(){
 }

 const p1 = new ferson()
// 修改构造函数的原型对象
 person.prototype = new Array()
 //新建一个实例
 conat p2 = new Person()
 //contructor指向改变了
 p2.constructor === Person //false
 p2.constructor === Array //true,

缺点:

  • 可靠性低,construccor指向可被随意修改
  • 无法检测symbol,null和undefined,因为没有与之对应的构造函数

2.5 其他检测

  • Number.isInteger() ES6新增,判断一个值是否为整数,只有number类型的整数才会返回true

  • Number. isNan ()
    E56新增,判斯一个值是否为NaN,只有值为NaN才会返回true(代替window.isNan)

  • Number.isFinate() ES6新增,判断一个数值是否为有限的,只有值为数字才会返回true(代替window.isFinate)

  • Array.isArray() 判断一个值是否为数组

3.类型转换

3.1 显式转换

显式转换通常只发生在主动调用存在类型转换的API方法中,如Number() Number.parseInt(),String(),JSON.parse()等

3.2 隐式转换

(1) ==

== 允许在相等比较时可以转换类型,见下面4.值比较

(2)数学符号运算

对于变量之间做+运算,它的隐式转换规则与+前后变量的数据类型有关,规则比较复杂,详细规则可以自行查询。

不过日常业务中我们用的最多的也就是字符串与数字或字符串之间的+运算,对于字符串与字符串则是拼接操作,对于字符串与数字,则是将数字默认进行 toString()操作,然后再进行字符串拼接。

对于其他数据类型的+运算,除非你很了解对应的转换规则,否则请不要轻易运算,或者选择在+运算前做显式转换

下面是一些例子:

"a"+5: //"a5
([])+5; //S
(1,2,3])+5: 1/1,2,35*
({})+"a"://"[object Object]a"
 true+"a": //truea
1+true://2
//特殊例子
{}+[]: //0
[]+{}://"[object Object]"

对于变量之间微-,*以及/运算,那么对于非number类型的变量会预先进行对应类型的Number()转换,然后和其他number类型变量进行数字运算

"a" / 5  //NaN
([]) / 5 //0
([1,2,3]) / 5  //NaN
true / "5"  //0.2
"5" / ([]) //Infinity
({}) / (()=>{})  //NaN

(3)API隐式转换

一些JS API在操作时也会发生不易发现的隐式转换,如window.isNan和window.isFinates在操作前会将目标值转换成number类型

4,值比较

4.1 == 和 ===

== 允许在相等比较时转换类型,而 === 不允许

我们最常用的一比较场景应该就是字符串和数字的比较。这里在比较时会把字符串转为数字

关于==比较在隐式转换时的一些细节过程这里不再具体介绍,针对不同类型的值的比较,其转换规则之复杂有时近人很难懂,存在于些出人意科的特殊和极端情况(总之就是坑)。

=== 比较时只有两种特殊情况需要注意:

(1) NaN !== NaN (2) +0 === -0

从这个角度讲,我个人建议还是尽量使用===, 因为它可靠的让人放心。

4.2 Object.is(a,b)

E56新增,它与===基本一致,区别在于:

(1)两个NaN比较返回true

(2)+0和-0比较返回false

一般情况下,用===效率更高,Object.is只适用于特殊值的比较