动态类型
JavaScript 是一种弱类型或者说动态语言。这意味着你不用提前声明变量的类型,在程序运行过程中,类型会被自动确定。这也意味着你可以使用同一个变量保存不同类型的数据:
let a = 18; // a 现在是一个数字
a = "js"; // a 现在是一个字符串
a = true; // a 现在是一个布尔值
数据类型
JavaScript 语言中类型集合由 原始值 和 对象 组成。
目前 JavaScript 中的原始值即基本数据类型包括:
Boolean(布尔类型),Number(数字类型),String(字符串类型),Undefined,Null,BigInt,Symbol(符号类型)。其中BigInt和Symbol是 ES6 新增的数据类型。
JavaScript 中的对象即引用类型包括
Object类型,Function类型,Array类型,Date类型,RegExp类型和基本包装类型。
本质区别
基本数据类型和引用类型在内存中的存储方式不同。
- 基本数据类型是直接存储在栈中的简单数据段,占据空间小,属于被频繁使用的数据。
- 引用类型是存储在堆内存中,占据空间大。引用类型在栈中存储了指针,该指针指向堆中该实体的起始地址,当解释器寻找引用值时,会检索其在栈中的地址,取得地址后从堆中获得实体。
原始值
除对象类型(object)以外的其它任何类型定义的不可变的值(值本身无法被改变)。
布尔类型
在计算机科学中,布尔值 是一种取值仅能为 真 或 假 的数据类型,它赋予了编程语言在逻辑上表达 真 或 假 的能力。
数字类型
目前 ECMAScript 标准定义了两种数值类型:Number(数字类型)和 BigInt(见下方)。
数字类型是一种基于 IEEE 754 标准的双精度 64 位二进制格式的值(从
-(2^53-1)到(2^53-1)之间的数字)。除了能够表示浮点数外,还有三个带符号的值:+Infinity、-Infinity和NaN(非数值,Not a Number)。
全局属性 Infinity 是一个数值,表示无穷大。
字符串类型
JavaScript 的字符串类型用于表示文本数据。它是一组 16 位的无符号整数值的“元素”。在字符串中的每个元素占据了字符串的位置。第一个元素的索引为
0,下一个是索引1,依此类推。字符串的长度是它的元素的数量。
Undefined类型
undefined是全局对象的一个属性。也就是说,它是全局作用域的一个变量。undefined的最初值就是原始数据类型undefined。
一个没有被赋值的变量的类型是
undefined。如果方法或者是语句中操作的变量没有被赋值,则会返回undefined。
Null类型
null是一个字面量,不像 undefined,它不是全局对象的一个属性。null是表示缺少的标识,指示变量未指向任何对象。把null作为尚未创建的对象,也许更好理解。在 API 中,null常在返回类型应是一个对象,但没有关联的值的地方使用。
BigInt类型
BigInt是一种内置对象,特点是数据涵盖的范围大,能够解决超出普通数据类型范围报错的问题。它提供了一种方法来表示大于2^53-1的整数。这原本是 Javascript 中可以用Number表示的最大数字。BigInt可以表示任意大的整数。
使用
BigInt,应用程序不再需要变通方法或库来安全地表示Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER之外的整数。现在可以在标准JS中执行对大整数的算术运算,而不会有精度损失的风险。
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991
使用方法:
- 整数末尾直接 +n
- 调用
BigInt()
let a = 29292929291188181818n;
let b = BigInt(121212321312342323);
typeof a; // bigint
typeof b; // bigint
Symbol类型
这种数据类型的特点是没有重复的数据,可以作为 object 的 key。
let sym1 = Symbol('zzz');
let sym2 = Symbol('zzz');
console.log(sym1 == sym2); // false
数据的创建方法
Symbol(),因为它的构造函数不够完整,所以不能使用new Symbol()创建数据。由于Symbol()创建数据具有唯一性,所以Symbol() !== Symbol(), 同时使用Symbol数据作为key不能使用 for 循环获取到这个key,需要使用Object.getOwnPropertySymbols(obj)获得这个对象中key类型。
let key = Symbol('key')
let obj = { [key]: 'symbol'}
let keyArray = Object.getOwnPropertySymbols(obj) // 返回一个数组[Symbol('key')]
obj[keyArray[0]] // 'symbol'
对象
在计算机科学中, 对象(object)是指内存中的可以被标识符引用的一块区域。
Object类型
常见的创建 Object 实例的方式有两种:
一种方式是使用对象字面量表示法。
// 注意使用逗号分隔不同属性
var person = {
name: '小明',
age: 20
};
另一种是 new Object:
var person = new Object();
person.name = "小明";
person.age = 20;
Function类型
每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此 函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。函数通常是使用函数声明语法定义的,如下面的例子所示:
function sum(num1, num2) {
return num1 + num2;
}
下面使用函数表达式定义函数:
var sum = function(num1, num2) {
return num1 + num2;
}
还有一种用 Function 构造函数定义函数的方法,Function 构造函数可以接收任意数量的参数,但最后一个参数始终都被看成是函数体,而前面的参数则枚举出了新函数的参数。例如:
var sum = new Function("num1", "num2", "return num1 + num2");
sum(1, 2); // 3
函数内部属性
在函数内部,有两个特殊的对象:arguments 和 this。
其中
arguments是一个类数组对象,包含着传入函数中的所有参数。arguments的主要用途是保存函数参数,但这个对象还有一个名叫callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数。
另一个特殊对象是
this,其行为与 Java 和 C# 中的 this 大致类似。换句话说,this引用的是函数据以执行的环境对象——或者也可以说是this值(当在网页的全局作用域中调用函数时,this 对象引用的就是 window )。
Array类型
ECMAScript 数组的每一项可以保存任何类型的数据。例如,可以用数组的第一个位置来保存字符串,用第二位置来保存数值。而且,ECMAScript 数组的大小是可以动态调整的,可以随着数据的添加自动增长以容纳新增数据。
创建数组的基本方式有两种。第一种是使用数组字面量表示法:
var colors = ["red", "blue", "green"]; // 创建一个包含 3 个字符串的数组
var names = []; // 创建一个空数组
第二种是使用 Array 构造函数:
var arr = new Array();
Date类型
要创建一个日期对象,使用 new 操作符和 Date 构造函数即可,如下所示:
var now = new Date();
在调用 Date 构造函数而不传递参数的情况下,新创建的对象自动获得当前日期和时间。若根据特定的日期和时间创建日期对象,必须传入表示该日期的毫秒数(即从 UTC 时间 1970 年 1 月 1 日午夜起至该日期止经过的毫秒数)。
为了简化这一计算过程,ECMAScript 提供了两个方法: Date.parse() 和 Date.UTC()。
Date.parse()方法接收一个表示日期的字符串参数,然后尝试根据这个字符串返回相应日期的毫秒数。若该字符串不能表示日期,那么它会返回NaN。实际上,如果直接将表示日期的字符串传递给 Date 构造函数,也会在后台调用Date.parse()。
Date.UTC()方法同样也返回表示日期的毫秒数。参数分别是年份、基于 0 的月份(一月是 0,二月是 1,以此类推)、月中的哪一天(1 到 31)、小时数(0 到 23)、分钟、秒以及毫秒数。
RegExp类型
ECMAScript 通过 RegExp 类型来支持正则表达式。RegExp 对象用于将文本与一个模式匹配。
有两种方法可以创建一个 RegExp 对象,一种是字面量:
// var regex = /pattern/modifiers
var regex = /^[0-9]+$/g;
另一种是构造函数 new RegExp():
// var regex = new RegExp("pattern", "modifiers")
var regex = new RegExp("^[0-9]+$", "g");
关于正则表达式的详细介绍请参考:JS正则表达式完整教程(略长)
基本包装类型
var str = 'hello'; // string类型
str.charAt(0); // h
上面的 str 是一个基本数据类型,为什么它却能调用 charAt() 方法呢?
是因为在执行第二行代码时,后台会自动进行下面的步骤:
- 自动创建 String 类型的一个实例(和基本类型的值不同,这个实例就是一个基本包装类型的对象)
- 调用实例(对象)上指定的方法
- 销毁这个实例
包装对象,就是当基本数据类型以对象的方式去使用时,JavaScript 会转换成对应的包装类型,相当于
new一个对象,内容和基本类型的内容一样,然后当操作完成再去访问的时候,这个临时对象会被销毁。
number、string、boolean 都有对应的包装类型。
因为有了基本包装类型,所以 JavaScript 中的基本类型值可以被当作对象来访问。
检测数据类型
检测数据类型的方法:
typeof
// 基本数据类型
console.log(typeof "123"); // string
console.log(typeof 1); // number
console.log(typeof true); // boolean
console.log(typeof undefined); // undefined
console.log(typeof null); // object
console.log(typeof Symbol('sym')); // symbol
console.log(typeof 9n); // bigint
// 引用类型
console.log(typeof []); // object
console.log(typeof function(){}); // function
console.log(typeof {}); // object
为什么 typeof null 结果为 object ?
这个 bug 是第一版 Javascript 留下来的。在这个版本,数值是以 32 字节存储的,由标志位(1~3个字节)和数值组成。标志位存储的是低位的数据。
在 JavaScript 中二进制前三位都为 0 的话会被判断为
object类型, null 的二进制表示是全 0,自然前三位也是 0,所以执行 typeof 时会返回object。详情请参考: The history of “typeof null”。
instanceof
instanceof 可以用于引用类型的检测,但对于基本数据类型是不生效的。
另外,不能用于检测null和undefined,会报错。
// 基本数据类型
console.log('1' instanceof String); // false
console.log(1 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log(undefined instanceof undefined); // TypeError
console.log(null instanceof null); // TypeError
console.log(typeof Symbol('sym') instanceof Symbol); // false
console.log(typeof 9n instanceof BigInt); // false
// 引用类型
console.log([] instanceof Array); // true
console.log(function (){} instanceof Function); // true
console.log({} instanceof Object); // true
Object.prototype.toString.call
// 基本数据类型
console.log(Object.prototype.toString.call(1)); // [object Number]
console.log(Object.prototype.toString.call('Hello')); // [object String]
console.log(Object.prototype.toString.call(true)); // [object Boolean]
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call(1n)); // [object BigInt]
console.log(Object.prototype.toString.call(Symbol('sym'))); // [object Symbol]
// 引用类型
console.log(Object.prototype.toString.call(console.log)); // [object Function]
console.log(Object.prototype.toString.call(new Date)); // [object Date]
console.log(Object.prototype.toString.call([1, 2, 3])); // [object Array]
console.log(Object.prototype.toString.call(new Object)); // [object Object]
在任何值上调用 Object 原生的 toString() 方法,都会返回一个 [object NativeConstructorName] 格式的字符串。每个类在内部都有一个 [[Class]] 属性,这个属性中就指定了上述字符串中的构造函数名。但是它不能检测非原生构造函数的构造函数名。
constructor
// 基本数据类型
console.log((1).constructor === Number); // true
console.log('hello'.constructor === String); // true
console.log(true.constructor === Boolean); // true
console.log((1n).constructor === BigInt); // true
console.log(Symbol('sym').constructor === Symbol); // true
// 引用类型
console.log(console.log.constructor === Function); // true
console.log((new Date).constructor === Date); // true
console.log((new Object).constructor === Object); // true
console.log([1, 2, 3].constructor === Array); // true
注意:constructor 不能判断 undefined 和 null。
相关参考: