简介
编程语言会有不同的数据类型,这是因为数据是对不同场景的实体的量化抽象,不同实体之间会有区别,另外,不同类型的数据的应用场景和操作也是不一样的。
例如提示的话术就应该用字符串类型,可以拼接你好,我是${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, 2let 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中的参数变量,参数改变并不会影响到atest(a);console.log(a); // 1function update(arg) {arg.attr = 2;}let b = {attr: 1};// 将b的引用赋给update的参数变量,参数变量改变引用指向的数据,也会影响到bupdate(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) // 0Number(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 // truenew Date() instanceof Date // true/123/g instanceof RegExp // true
instanceof方法一般用于判断自定义构造函数实例。
function Person() {}const p = new Person();p instanceof Person // truep 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 StringstringObject instanceof String; // true
constructor
console.log(false.constructor === Boolean);// trueconsole.log((1).constructor === Number);// trueconsole.log(''.constructor === String);// trueconsole.log([].constructor === Array);// trueconsole.log(({}).constructor === Object);// trueconsole.log((function test() {}).constructor === Function);// trueconsole.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); // 123parseInt('123'); // 123parseInt('123a'); // 123parseInt('a123'); // NaNparseInt('123.123'); // 123parseFloat('123.123'); // 123.123
解析数组,解析第一个元素。其他情况都返回NaN。
parseInt([]); // NaNparseInt([1]); // 1parseInt([1, 2]); // 1parseInt(['1']); // 1parseInt(['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.toPrimitivevar obj = {[Symbol.toPrimitive] () {return 3;},valueOf () {return 2;},toString () {return 1;}}console.log(obj) // 3// valueOfvar obj = {valueOf () {return 2;},toString () {return 1;}}console.log(obj) // 2// toStringvar obj = {toString () {return 1;}}console.log(obj) // 1// 默认var obj = {}console.log(!!{}); // trueconsole.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' // 21 + 1 + '1' // '21'1 + '1' + 1 // '111'1 + 'a' // '1a'1 + +'a' // NaN1 - '1' // 0!![] // true!!'' // false!!{} // trueif ([]) {console.log('bingo')} // 'bingo'if ('') {console.log('bingo')} //if ('0') {console.log('bingo')} // 'bingo'if ('{}') {console.log('bingo')} // 'bingo'NaN == '' // falseNaN == 0 // falseNaN == 'NaN' // false1 == '1' // true0 == '0' // true0 == '' // truetrue == '1' // truetrue == 'true' // falsetrue == '0' // false[] == true // false[] == ![] // true (首先这个表达式等同于[] == false,然后布尔转为数字:[] == 0,然后对象要转为字符串再比较,即:'' == 0,这样是一个字符串和一个数值比较,要先将字符串转为数字,即:0 == 0)[] == '0' // false[] == '' // true({}) == '[object Object]' // true({}) == 0 // false({}) == NaN // falsetrue > '0' // true'1.2.3' < '1.2.4' // true[2] > 1 // true[2, 1] > 1 // false
__
__
