简介
编程语言会有不同的数据类型,这是因为数据是对不同场景的实体的量化抽象,不同实体之间会有区别,另外,不同类型的数据的应用场景和操作也是不一样的。
例如提示的话术就应该用字符串类型,可以拼接你好,我是${name}
。而例如游戏得分应该用数值类型,可以进行加减等运算。
数据类型
JavaScript类型介绍
数据类型分类
ECMSScript有5种简单数据类型(也称为基本数据类型):Undefined、Null、Boolean、Number、String。还有一种复杂的数据类型——Object。
—— 《JavaScript高级程序设计》
根据《JavaScript高级程序设计》中说明,JavaScript有6种数据类型,Undefined、Null、Boolean、Number、String、Object。但实际上typeof null
的值是"object"
,另外typeof function() {}
的值是"function"
。因此我们认为null并不是一个独立的类型,null是object类型是一个值,而function也是一个独立的类型。
__js数据类型有6种:
- number
- string
- boolean
- object
- function
- undefined
undefined类型的值只有一个,就是undefined。
数值类型有两个特殊的值,NaN(not a number)和Infinity(无穷大)
object类型又可以分为
- plain object // 普通对象
- Date // 日期
- Array // 数组
- RegExp // 正则
其中number、string、boolean、undefined是值类型,function和object是引用类型。
值类型和引用类型
值类型和引用类型的区别是,值类型赋的变量直接存储数据,引用类型的变量存储数据的引用。
let a = 1;
let b = a;
b = 2;
console.log(a, b); // 1, 2
let c = {attr: 'yes'};
let d = c;
d.attr = 'no';
console.log(c.attr, d.attr); // no no
function test(arg) {
arg = 2;
}
let a = 1;
// 相当于将a的值赋给test中的参数变量,参数改变并不会影响到a
test(a);
console.log(a); // 1
function update(arg) {
arg.attr = 2;
}
let b = {attr: 1};
// 将b的引用赋给update的参数变量,参数变量改变引用指向的数据,也会影响到b
update(b);
console.log(b.attr); // 2
包装类型
基础类型的数据在使用时候,js引擎会先将之包装为对象,语句执行完对象被销毁。这个过程也被称为“装箱拆箱”。例如
const arr = '1,2,3'.split(',');
字符串先包装为String对象,然后对象执行相应方法,语句执行完后,包装对象就被销毁。
再如(1).toString()将返回数据类型的包装对象转换成的字符串。
注意:1.toString()会将”.”解析为小数点,因此会报语法错误
包装类型机制扩展了基本数据类型的能力,方便了日常开发。
因为基础类型也有包装类型转为对象,因此除了Symbol都有构造函数。
"1".constructor // 返回函数 String() { [native code] }
(1).constructor // 返回函数 Number() { [native code] }
false.constructor // 返回函数 Boolean() { [native code] }
[1,2,3].constructor // 返回函数 Array() { [native code] }
{}.constructor // 返回函数 Object() { [native code] }
new Date().constructor // 返回函数 Date() { [native code] }
function () {}.constructor // 返回函数 Function(){ [native code] }
null和undefined的区别
本身都表示“没有”,但null表示引用类型的对象为空,undefined则表示变量未定义。
在相等判断时候,null和undefined是相等的。
但null和undefined在很多方面有区别。
含义不同
null表示对象空指针,undefined表示变量未定义。
类型不同
typeof null // 'object'
typeof undefined // 'undefined'
Number(null) // 0
Number(undefined) // NaN
应用场景不同
null
作为对象原型链的终点。
undefined
定义了变量,没有初始化,默认是undefined。
函数不return,或者return后面没有值,则函数默认返回undefined。
函数参数如果不传,默认是undefined。
类型判断
判断类型的方法
typeof
typeof用来查看字面量或者变量的数据类型
typeof 1 // 'number'
typeof '1' // 'string'
typeof false // 'boolean'
typeof {} // 'object'
typeof [] // 'object'
typeof new Date() // 'object'
typeof (() => {}) // 'function'
typeof undefined // 'undefined'
typeof Symbol(1) // 'symbol'
由结果可知typeof可以测试出number、string、boolean、Symbol、undefined及function,而对于null及数组、对象,typeof均检测出为object,不能进一步判断它们的类型。
instanceof
instanceof可以判断一个对象的构造函数是否等于给定的值
({}) instanceof Object // true
[] instanceof Array // true
new Date() instanceof Date // true
/123/g instanceof RegExp // true
instanceof方法一般用于判断自定义构造函数实例。
function Person() {}
const p = new Person();
p instanceof Person // true
p instanceof Object // true
The instanceof** operator** tests to see if the prototype property of a constructor appears anywhere in the prototype chain of an object. The return value is a boolean value. —— MDN
instanceof原理是判断构造函数的原型prototype属性是否出现在对象的原型链上。
需要注意的是,这里提到instanceof是判断对象的构造函数,不适用与非对象类型的变量,看MDN的例子:
let literalString = 'This is a literal string';
let stringObject = new String('String created with constructor');
literalString instanceof String; // false, string literal is not a String
stringObject instanceof String; // true
constructor
console.log(false.constructor === Boolean);// true
console.log((1).constructor === Number);// true
console.log(''.constructor === String);// true
console.log([].constructor === Array);// true
console.log(({}).constructor === Object);// true
console.log((function test() {}).constructor === Function);// true
console.log(Symbol('1').constructor === Symbol);// true
注意:undefined和null没有contructor属性
这里可以看到虽然数字1的构造函数是Number,但1是对象字面量,不是通过new创建的,因此使用instanceof判断为false。
Object.prototype.toString
Object是js中所有其他数据类型的父类。意思是所有的数据类型都继承了Object。但是无论是string还是array都是会重写这个tostring方法的。所以'1'.toString()
和Object.prototype.toString.call('1')
的结果不同。
Object.prototype.toString.call可以用来区分数组、null等引用类型。
function Test(){};
const t = new Test();
Object.prototype.toString.call(1); '[object Number]'
Object.prototype.toString.call(NaN); '[object Number]'
Object.prototype.toString.call('1'); '[object String]'
Object.prototype.toString.call(true); '[object Boolean]'
Object.prototype.toString.call(undefined); '[object Undefined]'
Object.prototype.toString.call(null); '[object Null]'
Object.prototype.toString.call(Symbol());'[object Symbol]'
Object.prototype.toString.call(Test); '[object Function]'
Object.prototype.toString.call([1,2,3]); '[object Array]'
Object.prototype.toString.call({});'[object Object]'
Object.prototype.toString.call(t);'[object Object]'
注意自定义对象的判断只能得到”[object Object]”的结果。
常见变量的类型判断
判断一个变量是否是对象
Object.prototype.toString.call(obj) ==='[object Object]'
判断JavaScript对象是否为空对象
// 方法1 注意该方法性能较差
function isEmptyObject(obj) {
return JSON.stringify(obj) === '{}';
}
// 方法2 因为for in只能枚举对象自身的属性,不能枚举原型属性,因此可以用来判断空对象
function isEmptyObject(obj) {
for (var key in obj) {
return false;
}
return true;
}
// 方法3 Object.keys也是只能获取自身属性,不能获取原型属性
function isEmptyObject(obj) {
return Object.keys(obj).length === 0;
}
如何判断一个对象是否数组
// ES6中增加的数组方法
Array.isArray()
// 使用constructor判断
function isArray(arr) {
return arr.constructor.toString().indexOf("Array") > -1;
}
function isArray(arr) {
return arr.constructor === Array;
}
// 用instanceof判断
function isArray(arr) {
return arr instanceof Array;
}
判断NaN
isNaN()用来判断一个变量是否为NaN
isNaN(NaN); // true
或者利用NaN和自己不相等的特性
typeof num === 'number' && num !== num
类型转换
为什么要做类型转换
js是弱类型的语言,声明变量时候未指定变量类型,因此在很多场景下需要做类型转换。
js和其他端交互(如服务端、native、DOM(如input的value是字符串,需要转换为数字))时候,其他端对数据类型可能有要求
不同类型数据之间可能要进行运算
某些场景支持的数据类型固定(如if的condition需要是bool),这时候需要进行类型转换。
类型转换分为显式转换(包装类型函数、parseInt )和自动转换(隐式转换)。
转换为字符串类型
使用toString方法或者String()效果相同
(1).toSting(); // '1'
String(1); // '1'
null和undefined没有toString方法,其他的类型都有。null 和undefined转换字符串可以用String(null)或者’’ + null
转换为数值类型
parseInt和parseFloat对字符串解析会将字符串前面符合数字规则的部分解析成数字,如果开头就不是数字则返回NaN
parseInt(123); // 123
parseInt('123'); // 123
parseInt('123a'); // 123
parseInt('a123'); // NaN
parseInt('123.123'); // 123
parseFloat('123.123'); // 123.123
解析数组,解析第一个元素。其他情况都返回NaN。
parseInt([]); // NaN
parseInt([1]); // 1
parseInt([1, 2]); // 1
parseInt(['1']); // 1
parseInt(['a']); // NaN
类型转换规则
常见变量转换表
原始值 | 转为数字 | 转为字符串 | 转为布尔 |
---|---|---|---|
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 | “-Infinity” | true |
“” | 0 | “” | false |
“20” | 20 | “20” | true |
“a” | NaN | “a” | true |
[] | 0 | “” | true |
[10,20] | NaN | “10,20” | true |
function(){} | NaN | “function(){}” | true |
{ } | NaN | “[object Object]” | true |
null | 0 | “null” | false |
undefined | NaN | “undefined” | false |
对象转原始类型
- 如果有Symbol.toPrimitive()方法,优先调用再返回
- 调用valueOf(),如果转换为原始类型,则返回
- 调用toString(),如果转换为原始类型,则返回
- 没有Symbol.toPrimitive/valueOf/toString的情况
- 转布尔值
- null转为false
- 非null转为true
- 转数字NaN
- 转字符串’[object Object]’
- 转布尔值
// Symbol.toPrimitive
var obj = {
[Symbol.toPrimitive] () {
return 3;
},
valueOf () {
return 2;
},
toString () {
return 1;
}
}
console.log(obj) // 3
// valueOf
var obj = {
valueOf () {
return 2;
},
toString () {
return 1;
}
}
console.log(obj) // 2
// toString
var obj = {
toString () {
return 1;
}
}
console.log(obj) // 1
// 默认
var obj = {
}
console.log(!!{}); // true
console.log(obj + '') // "[object Object]"
console.log(+obj); // NaN
数组转为原始类型
- 转成bool永远是true
- 转成字符串,用逗号将各个元素连接起来
- 转成数值,先转成字符串,再将字符串转成数值类型
另一个例子:
如何让if (a == 1 && a == 2)返回true
var a = {
value: 0,
valueOf: function() {
this.value++;
return this.value;
}
};
console.log(a == 1 && a == 2);//true
隐式转换
在一些场景中,不同类型的变量会放在一起处理,这时候js引擎会做隐式转换转,转换为相同的类型后再处理。还有些情况下对变量的类型有要求,而变量如果不符合要求就会进行隐式转换(如if语句要求是bool值,如果是非bool值,会先转换为bool再处理)。
隐式转换场景
- 算术运算
- 单目运算符+
- if条件表达式转换为布尔
- !运算符转为布尔
- ==比较
- 比较运算符>、<、≥、≤
转换规则
- 双目+号
- 如果两个操作数都是数值,执行常规的加法计算
- 如果两个操作数是数值或者布尔,则都转为数值进行计算
- 如果有至少一个操作数是字符串,则都转成两个字符串拼接
- 如果有一个操作数是对象,则将这个对象调用toString()转为字符串进行计算
- -*/号都转换成数字。注意NaN和任何变量运算结果为NaN
- if转换为bool。
- !转换为bool。
- 单目+会转为数字,转换失败时候会转为NaN。
- ===如果类型不同返回false,如果类型相同则比较值是否相同,注意引用类型对象只和自身相等。
- == 转换规则
- 如果是类型相同,直接进行===比较
- 如果类型不同,要进行转换再比较
- 如果有一个操作数是布尔,则在比较前先将其转为数值(true -> 1; false -> 0)
- 数值和字符串比较,比较前先将字符串转为数字
- 如果一个是对象,另一个不是,则先调用对象的valueOf方法,用得到的基本类型值按照前面的规则比较。
- null和undefined相等
- 比较之前,null和undefined不转换为其他任何值(所以null只与自己和undefined==,而且null只和自己===;undefined也是一样)
- 如果有一个是NaN,则相等返回false,不等返回false
- 如果两个都是对象,则如果它们是同一个对象返回true,否则返回false
- 比较运算符> < >= <=转换规则
- 如果两个操作数都是数值,直接比较
- 如果只有一个是数值,将另一个操作数转为数值,然后比较
- 如果两个操作数都是字符串,则按字典比较
- 如果有一个是bool,则将这个转为数值再比较
- 如果有一个操作数是对象,则调用它的valueOf()获取原始值,若没有valueOf()方法,则调用toString()方法得到字符串,然后按照之前的规则进行比较。
练习题
1 + '1' // '11'
1 + + '1' // 2
1 + 1 + '1' // '21'
1 + '1' + 1 // '111'
1 + 'a' // '1a'
1 + +'a' // NaN
1 - '1' // 0
!![] // true
!!'' // false
!!{} // true
if ([]) {console.log('bingo')} // 'bingo'
if ('') {console.log('bingo')} //
if ('0') {console.log('bingo')} // 'bingo'
if ('{}') {console.log('bingo')} // 'bingo'
NaN == '' // false
NaN == 0 // false
NaN == 'NaN' // false
1 == '1' // true
0 == '0' // true
0 == '' // true
true == '1' // true
true == 'true' // false
true == '0' // false
[] == true // false
[] == ![] // true (首先这个表达式等同于[] == false,然后布尔转为数字:[] == 0,然后对象要转为字符串再比较,即:'' == 0,这样是一个字符串和一个数值比较,要先将字符串转为数字,即:0 == 0)
[] == '0' // false
[] == '' // true
({}) == '[object Object]' // true
({}) == 0 // false
({}) == NaN // false
true > '0' // true
'1.2.3' < '1.2.4' // true
[2] > 1 // true
[2, 1] > 1 // false
__
__