数据类型
前言
最新的 ECMAScript 标准定义了 8 种数据类型:
- 7 种基本数据类型:
- Undefined
- Null
- Boolean
- Number
- BigInt(ECMAScript 2020)
- String
- Symbol(ECMAScript 2015)
- 1 种复杂数据类型(又称引用数据类型):
- Object
基本数据类型保存在栈内存,引用类型保存在堆内存中。根本原因在于保存在栈内存的必须是大小固定的数据,引用类型的大小不固定,只能保存在堆内存中,但是可以把它的地址写在栈内存中以供我们访问。
如果是基本数据类型,则按值访问,操作的就是变量保存的值;如果是引用类型的值,我们只是通过保存在变量中的引用类型的地址来操作实际对象。
使用 typeof 操作符判断数据类型
typeof
用于检测给定变量的数据类型,对一个值使用 typeof
操作符会返回一个表示操作数的类型的字符串。但 typeof
的运算结果,与运行时类型的规定有很多不一致的地方。
我们可以看下表来对照一下。
示例表达式 | typeof 结果 | 运行时类型行为 |
---|---|---|
void(0) | undefined | Undefined |
null | object | Null |
true | boolean | Boolean |
7 | number | Number |
9007199254740992n | bigint | BigInt |
“飞翔得牛二” | string | String |
Symbol(“牛二”) | symbol | Symbol |
(function(){}) | function | Function object |
{} | object | Any other object |
在表格中,多数项是对应的,但是请注意 object —— Null
和 function —— Object
是特例,我们理解类型的时候需要特别注意这个区别。
此外,由于 typeof
是一个操作符而不是函数,后面可加括号也可省略。
8 种数据类型详细介绍
Undefined 类型
Undefined 类型只有一个值,即特殊的 undefined
。
Undefined值会被赋值给所有未初始化变量,在查看不存在的对象属性时也返回这个值。在布尔上下文中,未定义值被认为是假值。
注意: undefined 被认为是真正的基本数据类型。 除非显式转换,否则与在逻辑上下文中评估为false的其他类型相比,未定义的值可能会出现不可预料的行为。
var test; // 变量被声明但未定义,其值被赋为undefined值
var testObj = {};
console.log(test); // test的值存在,显示为undefined
console.log(testObj.myProp); // testObj存在,但属性不存在,显示为undefined
console.log(undefined == null); // 未强制类型检查,显示为true
console.log(undefined === null); // 强制类型检查,显示为false
注意:未定义没有内置的语言文字。因此(x === undefined)
并不是检查变量是否未定义的万无一失的方法,因为在ECMAScript 5之前的版本,如果写代码var undefined = "I'm defined now";
是合法的。更稳健的比较方法是(typeof x === 'undefined')
。(—— 出自维基百科)
下属函数不会如期望那样工作:
function isUndefined(x) { var u; return x === u; } // 如这个...
function isUndefined(x) { return x === void 0; } // ... 或第二个
function isUndefined(x) { return (typeof x) === "undefined"; } // ... 或第三个
如果my_var是未知标识符,调用isUndefined(my_var)
会抛出ReferenceError,但typeof my_var === 'undefined'
不会抛出异常。
对未初始化和未声明的变量执行 typeof
操作符都会返回 undefined
值。
显示地初始化变量是明智的选择,这样当 typeof
操作符返回 "undefined"
值时,我们就知道被检测地变量还没有被声明,而不是尚未初始化。(—— 出自红宝书)
Null 类型
Null 类型也只有一个值,即特殊的 null
。
从逻辑角度来看,null
值表示一个空对象指针,所以使用 typeof
操作符检测 null
值时会返回 "object"
。
如果定义的变量准备在将来用于保存对象,那么最好将该变量初始化为 null
而不是其他值。这样一来,只要直接检查相应的变量是否等于 null
值就可以知道它是否已经保存了一个对象的引用。(—— 出自红宝书)
实际上,undefined
值是派生自 null
值的,因此 null == undefined
会返回 true
,但 null === undefined
则返回 false
了。
Boolean 类型
Boolean 类型只有两个字面值:true
和 false
。
Number 类型
Number 类型使用 IEEE 754 格式来表示浮点双精度数。不能精确表示一些实数或分数。如:
console.log(0.2 + 0.1 === 0.3); // false
console.log(0.94 - 0.01); // 0.9299999999999999
1)浮点数值的整数化
因为保存浮点数值需要得内存空间是保存整数值的两倍,所以凡是可以「整数化」的浮点数都会被转换为整数值,例如:1.
和 1.0
都会被解析为 1
。
对于那些极大或极小的数值,可以用 e 表示法(即科学计数法)表示的浮点数值表示。(用 e 表示法表示的数值等于 e 前面的数值乘以 10 的指数次幂)
2)数值范围限制
JavaScript 能够表示的最小数值为 Number.MIN_VALUE
,在大多数浏览器中这个值是 5e-324
;
JavaScript 能够表示的最大数值为 Number.MAX_VALUE
,在大多数浏览器中这个值是 1.7976931348623157e+308
。
超出范围的正数会被转换成 Infinity
(正无穷),超出范围的负数会被转换成 -Infinity
(负无穷)。
可以使用 isFinite()
函数判断括号里的参数是否位于最小与最大数值之间。
3)特殊的 NaN
NaN
,即非数值(Not a Number)是一个特殊的数值。它有两个特点:一是任何涉及 NaN
的操作都会返回 NaN
,二是 NaN
与任何值都不相等,包括 NaN
本身。
Infinity 和 NaN 是数字:
typeof Infinity; // returns "number"
typeof NaN; // returns "number"
NaN 不等于其自身:
const nan = NaN;
console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
console.log(NaN !== NaN); // true
console.log(nan !== nan); // true
// 可以通过 `isNaN()` 函数来确认括号里的参数是否「不是数值」
console.log(isNaN("converted to NaN")); // true
console.log(isNaN(NaN)); // true
console.log(Number.isNaN("not converted")); // false
console.log(Number.isNaN(NaN)); // true
可以通过 isNaN()
函数来确认括号里的参数是否「不是数值」,需要注意的是,isNaN()
在接收到一个参数后,会尝试将这个值转换为数值,某些不是数值的值会直接转换为数值,例如字符串 "10"
或 Boolean
值。
4)数值转换函数
有 3 个函数可以把非数值转换为数值:Number()
、parseInt()
和 parseFloat()
。
由于 Number()
函数在转换字符串时比较复杂而且不够合理,因此更常用过的是另外两个函数。(—— 出自红宝书)
parseInt()
在转换时可以拥有第二个参数:转换时使用的基数(即多少进制),建议无论在什么情况下都明确指定基数。(—— 出自红宝书)
parseFloat()
只解析十进制值,因此它没有用第二个参数指定基数的用法。另外如果字符串没有小数点,或者小数点后都是零,parseFloat()
会返回整数。
转换规则:这 3 个函数都会忽略字符串前面的空格,直至找到第一个非空格字符。如果第一个字符不是数字字符或负号,就会返回 NaN
,直到解析完所有后续字符或者遇到了一个非数字字符。
区别是 parseInt()
转换过程中,小数点不是有效的数字字符;而 parseFloat()
转换过程中,第一个小数点是有效的,后面的小数点是无效的,从第二个小数点开始的后面所有字符会被忽略。
BigInt 类型(ECMAScript 2020)
BigInt 类型是在 ECMAScript 2020(ES11)引入的新特性。
JavaScript 中能够精确表达的最大数字是 2⁵³ - 1
,即 Number.MAX_SAFE_INTEGER
,如果超过了这个范围,运算结果就不再准确了。
const max = Number.MAX_SAFE_INTEGER;
console.log(max); // 9007199254740991
console.log(max + 1); // 9007199254740992
console.log(max + 2); // 9007199254740992
console.log(max + 3); // 9007199254740994
console.log(Math.pow(2, 53) === Math.pow(2, 53) + 1); // true
而新的 BigInt 数据类型可以解决这个问题,它能够创建更大的数字。
通过在数字末尾加上字母 n
,就可以将它转换成 BigInt。但要注意,我们无法将标准数字与 BigInt 数字混合在一起计算,否则将抛出 TypeError。
const bigNum = 100000000000000000000000000000n;
console.log(bigNum + 1n); // 200000000000000000000000000000n
console.log(bigNum + 1); // TypeError: Cannot mix BigInt and other types, use explicit conversions
String 类型
JavaScript中的字符串是一个字符序列。在JavaScript中,可以通过将一系列字符放在双引号(") 或单引号(')之间直接创建字符串(作为字面量)。此种字符串必须写在单行上,但可包含转义的换行符(如\n). JavaScript标准允许反引号字符(`,即重音符或反撇号)引用多行文字字符串以及模板文字,这允许在字符串内插入类型强制计算的表达式,但这仅在2016年开始的某些浏览器上支持:Firefox和Chrome,但Internet Explorer 11不支持。
var greeting = "Hello, World!";
var anotherGreeting = 'Greetings, people of Earth.';
可以使用charAt
方法(由String.prototype
提供)访问字符串中的单个字符。这是访问字符串中的单个字符时的首选方式,因为它也适用于非现代浏览器:
var h = greeting.charAt(0);
在现代浏览器中,可以通过与数组相同的表示法访问字符串中的单个字符:
var h = greeting[0];
但是,JavaScript字符串是不可变的:
greeting[0] = "H"; // Fails.
如果字符串具有相同的内容,则将相等运算符 (“==”) 应用于两个字符串将返回 true,这意味着:具有相同的长度并包含相同的字符序列(大小写对字母很重要)。因此:
var x = "World";
var compare1 = ("Hello, " +x == "Hello, World"); // true.
var compare2 = ("Hello, " +x == "hello, World"); // false. 第二个字符串 h 大小写不一致
除非转义,否则不能嵌套相同类型的引号:
var x = '"Hello, World!" he said.'; // Just fine.
var x = ""Hello, World!" he said."; // Not good.
var x = "\"Hello, World!\" he said."; // Works by escaping " with \"
String构造函数创建一个字符串对象(一个包装字符串的对象):
var greeting = new String("Hello, World!");
这些对象有一个valueOf
方法,返回包装在其中的原始字符串:
var s = new String("Hello !");
typeof s; // Is 'object'.
typeof s.valueOf(); // Is 'string'.
两个String对象之间的相等与字符串原语不同:
var s1 = new String("Hello !");
var s2 = new String("Hello !");
s1 == s2; // Is false, because they are two distinct objects.
s1.valueOf() == s2.valueOf(); // Is true.
数值转换字符串
要把一个值转换为一个字符串有两种方式:
第一种,几乎每个值都有的 toString()
方法(除了 null
和 undefined
)。其中数值型字符串在调用该方法时,可以传递一个参数——输出数值的基数(默认是十进制)。
第二种,String()
函数,它在转换过程中,如果值有 toString()
方法,则调用该方法(没有参数);如果值是 null
,则返回 "null"
;如果值是 undefined
,则返回 "undefined"
。
Symbol 类型(ECMAScript 2015)
Symbol 类型是在 ECMAScript 2015(ES6)引入的新特性。
ES5 的对象属性名都是字符串,这容易造成属性名的冲突。因此 ES6 引入了一种新的基本数据类型 Symbol,表示独一无二的值。
例如:
var x = Symbol(1);
var y = Symbol(1);
console.log(x === y); // => false
var symbolObject = {};
var normalObject = {};
// since x and y are unique,
// they can be used as unique keys in an object
symbolObject[x] = 1;
symbolObject[y] = 2;
console.log(symbolObject[x]); // => 1
console.log(symbolObject[y]); // => 2
// as compared to normal numeric keys
normalObject[1] = 1;
normalObject[1] = 2; // overrides the value of 1
console.log(normalObject[1]); // => 2
// changing the value of x does not change the key stored in the object
x = Symbol(3);
console.log(symbolObject[x]); // => undefined
// changing x back just creates another unique Symbol
x = Symbol(1);
console.log(symbolObject[x]); // => undefined
关于 Symbol 的知识点可以参考阮一峰老师编写的《ES6标准入门(第3版)》中 Symbol 章节。
Object 类型
JavaScript 中的对象是一组数据和功能的集合。对象可以通过执行 new
操作符后跟要创建的对象类型的名称来创建。
简单说,对象就是一组“键值对”(key-value)的集合,是一种无序的复合数据集合。
判断数据类型
JavaScript 中判断数据类型主要有下列几种方式:
typeof
typeof
只能区分基本类型:undefined、object、boolean、number、bigint,string,symbol,function,object,对于 null、array、object 来说,使用 typeof 都会统一返回 object 字符串。
typeof {} // "object"
typeof [] // "object"
typeof null // "object"
Object.prototype.toString.call()
Object.prototype.toString.call()
能用于判断原生引用类型数据,返回一个形如 "[object XXX]"
的字符串。
判断基本类型:
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call('abc'); // "[object String]"
Object.prototype.toString.call(123); // "[object Number]"
Object.prototype.toString.call(true); // "[object Boolean]"
判断原生引用类型:
// 函数类型
function fn(){
console.log('test');
}
Object.prototype.toString.call(fn); // "[object Function]"
// 日期类型
var date = new Date();
Object.prototype.toString.call(date); // "[object Date]"
// 数组类型
var arr = [1,2,3];
Object.prototype.toString.call(arr); // "[object Array]"
// 正则表达式
var reg = /[hbc]at/gi;
Object.prototype.toString.call(reg); // "[object RegExp]"
但是无法判断自定义类型:
function Person(name, age) {
this.name = name;
this.age = age;
}
var person = new Person("Rose", 18);
Object.prototype.toString.call(arr); // "[object Object]"
很明显这种方法不能准确判断 person
是 Person
类的实例。
instanceof
instanceof
运算符用于测试构造函数的 prototype
属性是否出现在对象的原型链中的任何位置,
可以用来判断某个构造函数的 prototype
属性是否存在另外一个要检测对象的原型链上,即判断一个对象是否是某个构造函数或其子构造函数的实例。
它的用法类似于 object instanceof class
注意左侧必须是对象(object),如果不是,直接返回 false。
function Person(name, age) {
this.name = name;
this.age = age;
}
var person = new Person("Rose", 18);
console.log(person instanceof Person); // true
数据类型转换
参考 JavaScript 类型转换。
参考资料
- 《JavaScript高级程序设计(第3版)》
- 《ES6标准入门(第3版)》
- MDN:JavaScript 数据类型和数据结构
- 维基百科-javaScript基本数据类型
(完)