数据类型
JavaScript是一种弱类型语言,变量的类型可以在运行时动态改变。JavaScript的数据类型分为两大类:基本数据类型和引用数据类型。本文将详细介绍JavaScript的数据类型及其特性。
数据类型分类
基本数据类型
基本数据类型(Primitive Types)是直接存储在栈中的数据,它们的值是不可变的。JavaScript有7种基本数据类型:
- String:字符串类型
- Number:数字类型
- Boolean:布尔类型
- Null:空值类型
- Undefined:未定义类型
- Symbol:符号类型(ES6+)
- BigInt:大整数类型(ES10+)
引用数据类型
引用数据类型(Reference Types)是存储在堆中的对象,栈中存储的是指向堆中对象的引用地址。JavaScript的引用数据类型包括:
- Object:对象类型
- Array:数组类型
- Function:函数类型
- Date:日期类型
- RegExp:正则表达式类型
- Map:映射类型(ES6+)
- Set:集合类型(ES6+)
- WeakMap:弱映射类型(ES6+)
- WeakSet:弱集合类型(ES6+)
基本数据类型
1. String
字符串是由零个或多个字符组成的序列,用单引号(')、双引号(")或反引号(`)包裹。
使用方式:
// 单引号
const str1 = 'Hello';
// 双引号
const str2 = "World";
// 反引号(模板字符串)
const str3 = `Hello ${str2}`; // Hello World特点:
- 字符串是不可变的,一旦创建就不能修改。
- 可以使用字符串的方法,但这些方法会返回新的字符串,不会修改原字符串。
- 模板字符串(Template Literals)使用反引号包裹,可以嵌入表达式。
2. Number
数字类型包括整数和浮点数,JavaScript使用IEEE 754标准表示数字。
使用方式:
// 整数
const num1 = 123;
// 浮点数
const num2 = 123.456;
// 科学记数法
const num3 = 1.23e2; // 123
// 八进制(ES6+)
const num4 = 0o123; // 83
// 十六进制
const num5 = 0x123; // 291
// 二进制(ES6+)
const num6 = 0b1010; // 10特殊值:
- Infinity:正无穷大
- -Infinity:负无穷大
- NaN:非数字(Not a Number)
const positiveInfinity = Infinity;
const negativeInfinity = -Infinity;
const notANumber = NaN;
console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity
console.log('abc' - 1); // NaN特点:
- JavaScript的数字精度有限,对于非常大或非常小的数字可能会有精度问题。
NaN不等于任何值,包括它自己。
3. Boolean
布尔类型只有两个值:true和false。
使用方式:
const isTrue = true;
const isFalse = false;转换规则: 在条件判断中,以下值会被转换为false(falsy值):
false0-00n(BigInt零)''(空字符串)nullundefinedNaN
其他值都会被转换为true(truthy值)。
4. Null
null表示一个空值,它是一个特殊的对象类型值。
使用方式:
const emptyValue = null;特点:
null表示一个有意的空值。typeof null返回'object',这是JavaScript的一个历史遗留问题。
5. Undefined
undefined表示一个未定义的值,当变量声明但未赋值时,其值为undefined。
使用方式:
let unassignedVariable;
console.log(unassignedVariable); // undefined
function foo() {}
console.log(foo()); // undefined特点:
undefined表示一个变量未被初始化。typeof undefined返回'undefined'。
6. Symbol
Symbol是ES6引入的一种新的基本数据类型,用于创建唯一的标识符。
使用方式:
// 创建Symbol
const sym1 = Symbol();
const sym2 = Symbol('description');
// Symbol是唯一的
const sym3 = Symbol('key');
const sym4 = Symbol('key');
console.log(sym3 === sym4); // false
// 用作对象属性
const obj = {
[sym1]: 'value1',
[sym2]: 'value2'
};特点:
- 每个Symbol值都是唯一的,即使它们的描述相同。
- Symbol值可以用作对象的属性名,这样可以创建私有属性。
- Symbol值不会出现在
for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()返回。
7. BigInt
BigInt是ES10引入的一种新的基本数据类型,用于表示任意精度的整数。
使用方式:
// 创建BigInt
const bigInt1 = 123n;
const bigInt2 = BigInt(123);
// 大整数
const bigInt3 = 9007199254740991n; // 2^53 - 1
const bigInt4 = 9007199254740992n; // 超出Number范围
// BigInt运算
const sum = bigInt1 + bigInt2; // 246n
const product = bigInt1 * bigInt2; // 15129n特点:
- BigInt可以表示任意精度的整数,不受Number类型的范围限制。
- BigInt使用
n后缀或BigInt()构造函数创建。 - BigInt不能与Number类型直接运算,需要转换类型。
引用数据类型
1. Object
对象是JavaScript中最基本的引用数据类型,它是键值对的集合。
使用方式:
// 对象字面量
const obj1 = {
name: 'John',
age: 30,
sayHello: function() {
console.log(`Hello, ${this.name}!`);
}
};
// 构造函数
const obj2 = new Object();
obj2.name = 'Jane';
obj2.age = 25;
// Object.create
const obj3 = Object.create(obj1);
console.log(obj3.name); // John(继承自obj1)特点:
- 对象是可变的,可以添加、修改和删除属性。
- 对象是通过引用传递的,不是通过值传递的。
- 对象可以继承其他对象的属性和方法。
2. Array
数组是一种特殊的对象,用于存储有序的元素集合。
使用方式:
// 数组字面量
const arr1 = [1, 2, 3, 4, 5];
// 构造函数
const arr2 = new Array(5); // 创建长度为5的空数组
const arr3 = new Array(1, 2, 3); // [1, 2, 3]
// 数组方法
arr1.push(6); // 添加元素
arr1.pop(); // 移除最后一个元素
arr1.map(item => item * 2); // 映射数组特点:
- 数组的长度是可变的,可以动态添加和删除元素。
- 数组的索引从0开始。
- 数组继承自Object,具有对象的所有特性。
- 数组有许多内置方法,如
push、pop、shift、unshift、map、filter、reduce等。
3. Function
函数是一种特殊的对象,用于封装可重用的代码块。
使用方式:
// 函数声明
function add(a, b) {
return a + b;
}
// 函数表达式
const subtract = function(a, b) {
return a - b;
};
// 箭头函数(ES6+)
const multiply = (a, b) => a * b;
// 构造函数
const divide = new Function('a', 'b', 'return a / b');特点:
- 函数是一等公民,可以作为参数传递,可以作为返回值,可以存储在变量中。
- 函数有自己的作用域。
- 函数可以访问外部作用域的变量(闭包)。
- 箭头函数没有自己的
this、arguments、new.target。
4. Date
日期对象用于处理日期和时间。
使用方式:
// 当前日期和时间
const now = new Date();
// 指定日期和时间
const specificDate = new Date('2023-12-25');
const specificDateTime = new Date(2023, 11, 25, 10, 30, 0);
// 日期方法
console.log(now.getFullYear()); // 年份
console.log(now.getMonth()); // 月份(0-11)
console.log(now.getDate()); // 日期(1-31)
console.log(now.getHours()); // 小时(0-23)
console.log(now.getMinutes()); // 分钟(0-59)
console.log(now.getSeconds()); // 秒(0-59)特点:
- Date对象基于时间戳,即自1970年1月1日00:00:00 UTC以来的毫秒数。
- Date对象的月份从0开始(0表示1月,11表示12月)。
- Date对象有许多方法用于获取和设置日期和时间的各个部分。
5. RegExp
正则表达式对象用于匹配字符串中的模式。
使用方式:
// 正则表达式字面量
const regex1 = /pattern/flags;
const regex2 = /\d+/g; // 匹配所有数字
// 构造函数
const regex3 = new RegExp('pattern', 'flags');
const regex4 = new RegExp('\\d+', 'g'); // 匹配所有数字
// 正则表达式方法
const str = 'Hello 123 World 456';
console.log(str.match(regex2)); // ['123', '456']
console.log(regex2.test(str)); // true标志:
g:全局匹配i:忽略大小写m:多行匹配s:允许.匹配换行符u:使用Unicode模式y:粘性匹配
特点:
- 正则表达式用于字符串的搜索、替换和验证。
- 正则表达式有自己的语法规则。
- 正则表达式对象有许多方法,如
test、exec等。
6. Map
Map是ES6引入的一种新的数据结构,用于存储键值对,其中键可以是任意类型。
使用方式:
// 创建Map
const map = new Map();
// 添加元素
map.set('name', 'John');
map.set(1, 'one');
map.set(true, 'boolean');
// 获取元素
console.log(map.get('name')); // John
// 检查元素是否存在
console.log(map.has('name')); // true
// 删除元素
map.delete('name');
// 清空Map
map.clear();
// 遍历Map
map.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
for (const [key, value] of map) {
console.log(`${key}: ${value}`);
}特点:
- Map的键可以是任意类型,包括对象、数组、函数等。
- Map的键是唯一的。
- Map会记住键的插入顺序。
- Map的大小可以通过
size属性获取。
7. Set
Set是ES6引入的一种新的数据结构,用于存储唯一的值。
使用方式:
// 创建Set
const set = new Set();
// 添加元素
set.add(1);
set.add('hello');
set.add(true);
set.add(1); // 重复元素,不会被添加
// 检查元素是否存在
console.log(set.has(1)); // true
// 删除元素
set.delete(1);
// 清空Set
set.clear();
// 遍历Set
set.forEach(value => {
console.log(value);
});
for (const value of set) {
console.log(value);
}特点:
- Set中的值是唯一的,不会有重复值。
- Set会记住值的插入顺序。
- Set的大小可以通过
size属性获取。
8. WeakMap
WeakMap是ES6引入的一种新的数据结构,用于存储键值对,其中键只能是对象,且键是弱引用的。
使用方式:
// 创建WeakMap
const weakMap = new WeakMap();
// 添加元素
const obj = {};
weakMap.set(obj, 'value');
// 获取元素
console.log(weakMap.get(obj)); // value
// 检查元素是否存在
console.log(weakMap.has(obj)); // true
// 删除元素
weakMap.delete(obj);特点:
- WeakMap的键只能是对象。
- WeakMap的键是弱引用的,即如果没有其他引用指向这个对象,那么这个对象会被垃圾回收。
- WeakMap没有
size属性,也没有clear()方法。 - WeakMap不能被遍历。
9. WeakSet
WeakSet是ES6引入的一种新的数据结构,用于存储对象,其中对象是弱引用的。
使用方式:
// 创建WeakSet
const weakSet = new WeakSet();
// 添加元素
const obj1 = {};
const obj2 = {};
weakSet.add(obj1);
weakSet.add(obj2);
// 检查元素是否存在
console.log(weakSet.has(obj1)); // true
// 删除元素
weakSet.delete(obj1);特点:
- WeakSet只能存储对象。
- WeakSet中的对象是弱引用的,即如果没有其他引用指向这个对象,那么这个对象会被垃圾回收。
- WeakSet没有
size属性,也没有clear()方法。 - WeakSet不能被遍历。
数据类型检测
1. typeof 操作符
typeof操作符用于检测变量的类型,返回一个字符串。
使用方式:
console.log(typeof 'hello'); // string
console.log(typeof 123); // number
console.log(typeof true); // boolean
console.log(typeof null); // object(历史遗留问题)
console.log(typeof undefined); // undefined
console.log(typeof Symbol()); // symbol
console.log(typeof 123n); // bigint
console.log(typeof {}); // object
console.log(typeof []); // object
console.log(typeof function() {}); // function
console.log(typeof new Date()); // object
console.log(typeof new RegExp()); // object特点:
typeof对于基本数据类型(除了null)的检测是准确的。typeof null返回'object',这是JavaScript的一个历史遗留问题。typeof对于引用数据类型(除了function)的检测都返回'object',无法区分具体的对象类型。
2. instanceof 操作符
instanceof操作符用于检测对象是否是某个构造函数的实例,返回一个布尔值。
使用方式:
console.log([] instanceof Array); // true
console.log([] instanceof Object); // true
console.log({} instanceof Object); // true
console.log(function() {} instanceof Function); // true
console.log(new Date() instanceof Date); // true
console.log(new RegExp() instanceof RegExp); // true特点:
instanceof用于检测引用数据类型的具体类型。instanceof基于原型链进行检测,因此一个对象可能是多个构造函数的实例。instanceof不能用于检测基本数据类型。
3. Object.prototype.toString.call()
Object.prototype.toString.call()方法用于检测任意值的类型,返回一个格式为'[object Type]'的字符串。
使用方式:
console.log(Object.prototype.toString.call('hello')); // [object String]
console.log(Object.prototype.toString.call(123)); // [object Number]
console.log(Object.prototype.toString.call(true)); // [object Boolean]
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
console.log(Object.prototype.toString.call(Symbol())); // [object Symbol]
console.log(Object.prototype.toString.call(123n)); // [object BigInt]
console.log(Object.prototype.toString.call({})); // [object Object]
console.log(Object.prototype.toString.call([])); // [object Array]
console.log(Object.prototype.toString.call(function() {})); // [object Function]
console.log(Object.prototype.toString.call(new Date())); // [object Date]
console.log(Object.prototype.toString.call(new RegExp())); // [object RegExp]
console.log(Object.prototype.toString.call(new Map())); // [object Map]
console.log(Object.prototype.toString.call(new Set())); // [object Set]特点:
Object.prototype.toString.call()是最准确的类型检测方法,它可以检测所有数据类型,包括null和undefined。- 它返回的字符串格式为
'[object Type]',其中Type是数据类型的名称。
4. Array.isArray()
Array.isArray()方法用于检测值是否是数组,返回一个布尔值。
使用方式:
console.log(Array.isArray([])); // true
console.log(Array.isArray({})); // false
console.log(Array.isArray('hello')); // false特点:
Array.isArray()是检测数组的专门方法,比instanceof更准确,因为它可以跨iframe检测。
5. 其他检测方法
Number.isNaN():检测值是否是NaNNumber.isFinite():检测值是否是有限的数字Number.isInteger():检测值是否是整数isNaN():检测值是否是NaN(会进行类型转换)isFinite():检测值是否是有限的数字(会进行类型转换)
数据类型转换
1. 显式类型转换
1.1 转换为字符串
String()函数toString()方法
console.log(String(123)); // "123"
console.log(String(true)); // "true"
console.log(String(null)); // "null"
console.log(String(undefined)); // "undefined"
console.log((123).toString()); // "123"
console.log(true.toString()); // "true"
// null和undefined没有toString()方法1.2 转换为数字
Number()函数parseInt()函数parseFloat()函数+一元运算符
console.log(Number('123')); // 123
console.log(Number('123.456')); // 123.456
console.log(Number('')); // 0
console.log(Number('true')); // 1
console.log(Number('false')); // 0
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN
console.log(parseInt('123')); // 123
console.log(parseInt('123.456')); // 123
console.log(parseInt('123abc')); // 123
console.log(parseInt('abc123')); // NaN
console.log(parseFloat('123')); // 123
console.log(parseFloat('123.456')); // 123.456
console.log(parseFloat('123.456abc')); // 123.456
console.log(+'123'); // 123
console.log(+'123.456'); // 123.4561.3 转换为布尔值
Boolean()函数!!双重否定运算符
console.log(Boolean('hello')); // true
console.log(Boolean('')); // false
console.log(Boolean(123)); // true
console.log(Boolean(0)); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN)); // false
console.log(!!'hello'); // true
console.log(!!''); // false
console.log(!!123); // true
console.log(!!0); // false2. 隐式类型转换
JavaScript在某些操作中会自动进行类型转换,这称为隐式类型转换。
2.1 字符串拼接
当使用+运算符连接字符串和其他类型的值时,其他类型的值会被转换为字符串。
console.log('hello' + 123); // "hello123"
console.log('hello' + true); // "hellotrue"
console.log('hello' + null); // "hellonull"
console.log('hello' + undefined); // "helloundefined"2.2 算术运算
当使用算术运算符(除了+)操作非数字类型的值时,非数字类型的值会被转换为数字。
console.log(123 - '45'); // 78
console.log(123 * '45'); // 5535
console.log(123 / '45'); // 2.7333333333333334
console.log(123 % '45'); // 33
console.log('123' - 0); // 123
console.log(true - 0); // 1
console.log(false - 0); // 0
console.log(null - 0); // 0
console.log(undefined - 0); // NaN2.3 比较运算
当使用比较运算符比较不同类型的值时,会进行隐式类型转换。
console.log(123 == '123'); // true(会进行类型转换)
console.log(123 === '123'); // false(不会进行类型转换)
console.log(0 == false); // true
console.log(0 === false); // false
console.log(null == undefined); // true
console.log(null === undefined); // false2.4 逻辑运算
当使用逻辑运算符时,会进行隐式类型转换为布尔值。
console.log(!0); // true
console.log(!''); // true
console.log(!null); // true
console.log(!undefined); // true
console.log(!NaN); // true
console.log(!123); // false
console.log(!'hello'); // false
console.log(0 || 'default'); // "default"
console.log('hello' || 'default'); // "hello"
console.log(0 && 'value'); // 0
console.log('hello' && 'value'); // "value"面试常见问题
1. JavaScript的数据类型有哪些?
JavaScript的数据类型分为两大类:
- 基本数据类型:String、Number、Boolean、Null、Undefined、Symbol(ES6+)、BigInt(ES10+)
- 引用数据类型:Object、Array、Function、Date、RegExp、Map(ES6+)、Set(ES6+)、WeakMap(ES6+)、WeakSet(ES6+)
2. 基本数据类型和引用数据类型的区别是什么?
- 存储位置:基本数据类型存储在栈中,引用数据类型存储在堆中,栈中存储的是指向堆中对象的引用地址。
- 复制方式:基本数据类型是按值复制的,引用数据类型是按引用复制的。
- 可变性:基本数据类型是不可变的,引用数据类型是可变的。
- 比较方式:基本数据类型比较的是值,引用数据类型比较的是引用地址。
3. typeof null为什么返回'object'?
这是JavaScript的一个历史遗留问题。在JavaScript的早期实现中,值是通过32位的字来表示的,其中最低的3位用于表示类型标签。对于对象,类型标签是000。而null被表示为全0,因此它的类型标签也是000,被错误地识别为对象。
4. 如何准确检测一个值的类型?
使用Object.prototype.toString.call()方法,它可以检测任意值的类型,返回一个格式为'[object Type]'的字符串。
5. == 和 === 的区别是什么?
==:宽松相等运算符,会进行隐式类型转换,然后比较值。===:严格相等运算符,不会进行隐式类型转换,直接比较值和类型。
6. 什么是falsy值?
在条件判断中,以下值会被转换为false:
false0-00n(BigInt零)''(空字符串)nullundefinedNaN
7. 如何将字符串转换为数字?
Number()函数:转换整个字符串为数字。parseInt()函数:解析字符串的整数部分。parseFloat()函数:解析字符串的浮点数部分。+一元运算符:将字符串转换为数字。
8. 如何将数字转换为字符串?
String()函数:将数字转换为字符串。toString()方法:将数字转换为字符串,可以指定进制。'' + 数字:使用字符串拼接进行隐式类型转换。
9. Symbol的特点是什么?
- 每个Symbol值都是唯一的,即使它们的描述相同。
- Symbol值可以用作对象的属性名,这样可以创建私有属性。
- Symbol值不会出现在
for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()返回。
10. BigInt的特点是什么?
- BigInt可以表示任意精度的整数,不受Number类型的范围限制。
- BigInt使用
n后缀或BigInt()构造函数创建。 - BigInt不能与Number类型直接运算,需要转换类型。
11. Map和Object的区别是什么?
- 键的类型:Map的键可以是任意类型,Object的键只能是字符串或Symbol。
- 键的顺序:Map会记住键的插入顺序,Object在ES6之前不保证键的顺序。
- 大小:Map的大小可以通过
size属性获取,Object的大小需要手动计算。 - 遍历:Map可以通过
forEach、for...of等方法遍历,Object需要通过for...in、Object.keys()等方法遍历。 - 性能:对于频繁添加和删除键值对的场景,Map的性能更好。
12. Set和Array的区别是什么?
- 唯一性:Set中的值是唯一的,Array中的值可以重复。
- 顺序:Set会记住值的插入顺序,Array也会记住值的插入顺序。
- 大小:Set的大小可以通过
size属性获取,Array的大小可以通过length属性获取。 - 方法:Set有自己的方法,如
add、delete、has等,Array有自己的方法,如push、pop、shift等。 - 遍历:Set可以通过
forEach、for...of等方法遍历,Array也可以通过这些方法遍历。
总结
JavaScript的数据类型是JavaScript的基础,理解数据类型的特性和转换规则对于编写高质量的JavaScript代码非常重要。
- 基本数据类型包括String、Number、Boolean、Null、Undefined、Symbol和BigInt,它们存储在栈中,是不可变的。
- 引用数据类型包括Object、Array、Function、Date、RegExp、Map、Set、WeakMap和WeakSet,它们存储在堆中,是可变的。
- 数据类型检测方法包括typeof、instanceof、Object.prototype.toString.call()、Array.isArray()等。
- 数据类型转换包括显式类型转换和隐式类型转换,了解这些转换规则可以避免很多常见的错误。
通过掌握JavaScript的数据类型,你可以更好地理解JavaScript的行为,编写更加健壮和高效的代码。