数据类型概述
数据类型概述
JavaScript有七种基本数据类型:undefined、null、布尔值、数字、字符串、符号和大整数,以及一种复杂数据类型:对象。本文将概述这些数据类型的特点和用法。
JavaScript的数据类型系统
JavaScript是一种动态类型语言,这意味着变量的数据类型可以随时改变,不需要事先声明。JavaScript的数据类型可以分为两大类:
- 原始类型(基本数据类型):直接代表最底层的数据
- 引用类型(复杂数据类型):由多个值构成的对象
原始数据类型
1. Undefined类型
undefined
表示变量已声明但尚未赋值,或访问对象不存在的属性时返回的值。
let undefinedVar;
console.log(undefinedVar); // 输出: undefined
const obj = {};
console.log(obj.nonExistentProperty); // 输出: undefined
需要注意的是,undefined
是一个特殊的值,而不仅仅是一个关键字:
console.log(typeof undefined); // 输出: "undefined"
console.log(undefined === undefined); // 输出: true
2. Null类型
null
表示空值或不存在的对象引用,通常用于显式表示"没有值"。
let emptyValue = null;
console.log(emptyValue); // 输出: null
// null与undefined的区别
console.log(null === undefined); // 输出: false
console.log(null == undefined); // 输出: true(宽松相等)
null
的类型判断有一个历史遗留问题:
console.log(typeof null); // 输出: "object"(这是JavaScript的一个历史bug)
3. Boolean类型
Boolean
类型只有两个值:true
和false
,用于表示逻辑真和假。
let isActive = true;
let isLoggedIn = false;
// 条件语句中的布尔值
if (isActive) {
console.log("用户处于活动状态");
}
其他类型的值可以通过条件判断或Boolean()
函数转换为布尔值:
// 转换为false的值(假值)
console.log(Boolean(0)); // false
console.log(Boolean("")); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN)); // false
// 转换为true的值(真值)
console.log(Boolean(1)); // true
console.log(Boolean("hello")); // true
console.log(Boolean([])); // true(空数组)
console.log(Boolean({})); // true(空对象)
4. Number类型
Number
类型表示整数和浮点数,JavaScript使用IEEE 754标准的64位双精度浮点数表示所有数值。
// 整数
let integer = 42;
// 浮点数
let float = 3.14159;
// 科学计数法
let scientific = 5e3; // 5000
let smallScientific = 5e-3; // 0.005
// 特殊数值
let infinityValue = Infinity; // 无穷大
let negativeInfinity = -Infinity; // 负无穷大
let notANumber = NaN; // 非数值(Not a Number)
Number
类型有一些特殊值和限制:
// 最大和最小安全整数
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991 (2^53 - 1)
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991 -(2^53 - 1)
// 浮点数精度问题
console.log(0.1 + 0.2); // 输出: 0.30000000000000004(不精确)
console.log(0.1 + 0.2 === 0.3); // 输出: false
// 判断NaN
console.log(NaN === NaN); // 输出: false(NaN不等于任何值,包括自身)
console.log(isNaN(NaN)); // 输出: true
console.log(Number.isNaN(NaN)); // 输出: true(更严格的检查)
5. String类型
String
类型表示文本数据,JavaScript字符串是不可变的Unicode字符序列。
// 字符串字面量
let singleQuotes = '单引号字符串';
let doubleQuotes = "双引号字符串";
let backticks = `反引号字符串(模板字符串)`;
// 字符串长度
console.log(singleQuotes.length); // 输出字符串的字符数
// 字符串拼接
let firstName = "张";
let lastName = "三";
let fullName = firstName + lastName; // "张三"
// 模板字符串(ES6)
let greeting = `你好,${fullName}!`; // "你好,张三!"
let multiLine = `这是第一行
这是第二行`; // 支持多行字符串
字符串有丰富的方法:
let text = "JavaScript编程";
// 常用字符串方法
console.log(text.charAt(0)); // "J"(获取指定位置的字符)
console.log(text.indexOf("Script")); // 4(查找子字符串位置)
console.log(text.substring(4, 10)); // "Script"(提取子字符串)
console.log(text.replace("JavaScript", "JS")); // "JS编程"(替换)
console.log(text.toUpperCase()); // "JAVASCRIPT编程"(转大写)
console.log(" trim me ".trim()); // "trim me"(去除首尾空格)
console.log(text.split("")); // ["J","a","v","a","S","c","r","i","p","t","编","程"](分割)
6. Symbol类型
Symbol
是ES6引入的新原始类型,表示唯一的标识符,主要用于创建对象的唯一属性键。
// 创建Symbol
const sym1 = Symbol();
const sym2 = Symbol("描述");
const sym3 = Symbol("描述"); // 即使描述相同,sym2和sym3也是不同的Symbol
// Symbol的唯一性
console.log(sym2 === sym3); // false
// 作为对象属性
const obj = {
[sym1]: "Symbol属性值",
regularKey: "常规属性值"
};
console.log(obj[sym1]); // "Symbol属性值"
console.log(Object.keys(obj)); // ["regularKey"](Symbol属性不会出现在常规枚举中)
全局Symbol注册表:
// 使用Symbol.for创建共享的Symbol
const globalSym1 = Symbol.for("globalSymbol");
const globalSym2 = Symbol.for("globalSymbol");
console.log(globalSym1 === globalSym2); // true(相同的键返回相同的Symbol)
console.log(Symbol.keyFor(globalSym1)); // "globalSymbol"(获取全局Symbol的键)
7. BigInt类型
BigInt
是ES2020引入的新原始类型,用于表示任意精度的整数,解决了Number
类型无法精确表示大整数的问题。
// 创建BigInt
const bigInt1 = 9007199254740991n; // 使用n后缀
const bigInt2 = BigInt("9007199254740991"); // 使用BigInt()函数
// BigInt运算
console.log(bigInt1 + 1n); // 9007199254740992n
console.log(bigInt1 * 2n); // 18014398509481982n
// BigInt与Number不能混合运算
// console.log(bigInt1 + 1); // TypeError: Cannot mix BigInt and other types
// 比较
console.log(1n === 1); // false(不同类型)
console.log(1n == 1); // true(宽松相等)
引用数据类型
Object类型
Object
是JavaScript中最复杂的数据类型,几乎所有JavaScript中的复杂数据结构都是对象。
// 对象字面量
const person = {
name: "李四",
age: 30,
isEmployed: true,
greet: function() {
return `你好,我是${this.name}`;
}
};
// 访问对象属性
console.log(person.name); // "李四"(点符号)
console.log(person["age"]); // 30(方括号符号)
// 修改属性
person.age = 31;
// 添加新属性
person.location = "北京";
// 删除属性
delete person.isEmployed;
常见的对象类型
JavaScript有多种内置对象类型,它们都基于基本的Object
类型:
- 数组(Array):有序集合
// 数组字面量
const fruits = ["苹果", "香蕉", "橙子"];
// 访问元素
console.log(fruits[0]); // "苹果"
// 数组长度
console.log(fruits.length); // 3
// 常用数组方法
fruits.push("葡萄"); // 添加到末尾
fruits.pop(); // 移除末尾元素
fruits.unshift("草莓"); // 添加到开头
fruits.shift(); // 移除开头元素
console.log(fruits.includes("香蕉")); // true(检查是否包含)
console.log(fruits.join(", ")); // "苹果, 香蕉, 橙子"(连接成字符串)
// 数组迭代
fruits.forEach(fruit => console.log(fruit));
const upperFruits = fruits.map(fruit => fruit.toUpperCase());
const longFruits = fruits.filter(fruit => fruit.length > 2);
- 函数(Function):可调用的对象
// 函数声明
function add(a, b) {
return a + b;
}
// 函数表达式
const multiply = function(a, b) {
return a * b;
};
// 箭头函数(ES6)
const divide = (a, b) => a / b;
// 函数是一等公民,可以作为值传递
function calculate(operation, a, b) {
return operation(a, b);
}
console.log(calculate(add, 5, 3)); // 8
- 日期(Date):表示日期和时间
// 创建日期对象
const now = new Date();
const specificDate = new Date("2023-01-01");
// 获取日期组件
console.log(now.getFullYear()); // 当前年份
console.log(now.getMonth()); // 当前月份(0-11)
console.log(now.getDate()); // 当前日(1-31)
console.log(now.getDay()); // 当前星期(0-6,0表示星期日)
// 格式化日期
console.log(now.toLocaleDateString()); // 本地日期格式
console.log(now.toLocaleTimeString()); // 本地时间格式
- 正则表达式(RegExp):用于模式匹配
// 创建正则表达式
const pattern1 = /\d+/g; // 字面量语法
const pattern2 = new RegExp("\\d+", "g"); // 构造函数语法
// 使用正则表达式
const text = "我有2个苹果和3个橙子";
console.log(pattern1.test(text)); // true(检查是否匹配)
console.log(text.match(pattern1)); // ["2", "3"](查找所有匹配)
console.log(text.replace(pattern1, "几")); // "我有几个苹果和几个橙子"(替换)
- Map和Set(ES6):新的集合类型
// Map:键值对集合,任何值都可以作为键
const userMap = new Map();
userMap.set("id001", { name: "王五", age: 25 });
userMap.set("id002", { name: "赵六", age: 30 });
console.log(userMap.get("id001")); // { name: "王五", age: 25 }
console.log(userMap.has("id003")); // false
console.log(userMap.size); // 2
// Set:唯一值的集合
const uniqueNumbers = new Set([1, 2, 3, 3, 4, 4, 5]);
console.log(uniqueNumbers); // Set(5) {1, 2, 3, 4, 5}(重复值被自动去除)
uniqueNumbers.add(6);
console.log(uniqueNumbers.has(3)); // true
console.log(uniqueNumbers.size); // 6
类型检测
JavaScript提供了多种检测数据类型的方法:
typeof运算符
typeof
运算符返回一个表示操作数类型的字符串:
console.log(typeof 42); // "number"
console.log(typeof "hello"); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof Symbol()); // "symbol"
console.log(typeof 42n); // "bigint"
console.log(typeof {}); // "object"
console.log(typeof []); // "object"(注意:数组也是对象)
console.log(typeof null); // "object"(这是一个历史遗留bug)
console.log(typeof function(){}); // "function"
instanceof运算符
instanceof
运算符检查对象是否是特定构造函数的实例:
const arr = [1, 2, 3];
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true(数组也是对象)
const date = new Date();
console.log(date instanceof Date); // true
console.log(date instanceof Object); // true(日期对象也是对象)
// 原始类型不是任何构造函数的实例
console.log(42 instanceof Number); // false
console.log("hello" instanceof String); // false
Object.prototype.toString方法
这是一种更可靠的类型检测方法,可以区分所有内置类型:
const toString = Object.prototype.toString;
console.log(toString.call(42)); // "[object Number]"
console.log(toString.call("hello")); // "[object String]"
console.log(toString.call(true)); // "[object Boolean]"
console.log(toString.call(undefined)); // "[object Undefined]"
console.log(toString.call(null)); // "[object Null]"
console.log(toString.call({})); // "[object Object]"
console.log(toString.call([])); // "[object Array]"
console.log(toString.call(function(){})); // "[object Function]"
console.log(toString.call(new Date())); // "[object Date]"
console.log(toString.call(/\d+/)); // "[object RegExp]"
console.log(toString.call(Symbol())); // "[object Symbol]"
console.log(toString.call(42n)); // "[object BigInt]"
Array.isArray方法
专门用于检测数组:
console.log(Array.isArray([1, 2, 3])); // true
console.log(Array.isArray({})); // false
类型转换
JavaScript中的类型转换可以是显式的(手动转换)或隐式的(自动转换)。
显式类型转换
使用内置函数进行显式类型转换:
// 转换为数字
console.log(Number("42")); // 42
console.log(Number("3.14")); // 3.14
console.log(Number("非数字")); // NaN
console.log(Number(true)); // 1
console.log(Number(false)); // 0
// 转换为字符串
console.log(String(42)); // "42"
console.log(String(true)); // "true"
console.log(String(null)); // "null"
console.log(String(undefined)); // "undefined"
console.log(String([1, 2, 3])); // "1,2,3"
console.log(String({name: "张三"})); // "[object Object]"
// 转换为布尔值
console.log(Boolean(42)); // true
console.log(Boolean(0)); // false
console.log(Boolean("hello")); // true
console.log(Boolean("")); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
隐式类型转换
在特定上下文中,JavaScript会自动进行类型转换:
// 字符串拼接
console.log("3" + 4); // "34"(数字被转换为字符串)
// 数学运算(除了+)
console.log("3" - 1); // 2(字符串被转换为数字)
console.log("3" * "2"); // 6(两个字符串都被转换为数字)
// 比较运算
console.log("3" > 2); // true(字符串被转换为数字)
console.log(0 == false); // true(布尔值被转换为数字)
console.log("" == 0); // true(空字符串被转换为0)
// 逻辑运算
console.log(!0); // true(0被转换为布尔值false,然后取反)
console.log(!!42); // true(42被转换为布尔值true,然后两次取反回到true)
严格相等(=)与宽松相等()
JavaScript提供两种相等比较操作符:
// 严格相等(===)不进行类型转换
console.log(3 === 3); // true
console.log("3" === 3); // false(类型不同)
console.log(null === undefined); // false(类型不同)
// 宽松相等(==)会进行类型转换
console.log("3" == 3); // true(字符串"3"被转换为数字3)
console.log(null == undefined); // true(特殊规则)
console.log(0 == false); // true(false被转换为0)
原始类型与包装对象
JavaScript为原始类型提供了对应的包装对象,允许我们像对象一样使用原始值:
// 原始字符串与String对象
const primitiveString = "hello";
const objectString = new String("hello");
console.log(typeof primitiveString); // "string"
console.log(typeof objectString); // "object"
// 原始数字与Number对象
const primitiveNumber = 42;
const objectNumber = new Number(42);
// 原始布尔值与Boolean对象
const primitiveBoolean = true;
const objectBoolean = new Boolean(true);
当我们在原始值上调用方法时,JavaScript会临时创建一个包装对象,调用方法后立即销毁:
const name = "张三";
console.log(name.toUpperCase()); // "张三"(临时创建String对象,调用方法,然后销毁)
// 等同于:
const temp = new String(name);
const result = temp.toUpperCase();
temp = null; // 销毁临时对象
不可变性与可变性
原始类型是不可变的,而对象类型是可变的:
// 原始类型不可变
let str = "hello";
str.toUpperCase(); // 不会改变原始字符串
console.log(str); // 仍然是"hello"
str = str.toUpperCase(); // 需要重新赋值才能改变变量的值
console.log(str); // 现在是"HELLO"
// 对象类型可变
const arr = [1, 2, 3];
arr.push(4); // 直接修改数组
console.log(arr); // [1, 2, 3, 4]
const obj = { name: "李四" };
obj.name = "王五"; // 直接修改对象属性
console.log(obj); // { name: "王五" }
值传递与引用传递
理解JavaScript中的值传递和引用传递对于正确处理数据至关重要:
// 原始类型是值传递
let a = 5;
let b = a; // b得到a的值的副本
a = 10;
console.log(b); // 仍然是5,不受a的变化影响
// 对象类型是引用传递
let obj1 = { value: 10 };
let obj2 = obj1; // obj2得到对obj1的引用
obj1.value = 20;
console.log(obj2.value); // 20,因为obj1和obj2引用同一个对象
// 函数参数
function changeValues(num, obj) {
num = 100; // 不会影响外部变量
obj.value = 100; // 会影响外部对象
}
let number = 50;
let object = { value: 50 };
changeValues(number, object);
console.log(number); // 仍然是50
console.log(object.value); // 变成了100
总结
JavaScript的类型系统虽然简单,但有许多细微之处需要理解:
- JavaScript有7种原始类型(undefined、null、Boolean、Number、String、Symbol、BigInt)和1种复杂类型(Object)
- 原始类型是不可变的,按值传递;对象类型是可变的,按引用传递
- JavaScript是动态类型语言,变量可以随时改变类型
- 类型转换可以是显式的(手动)或隐式的(自动)
- 使用严格相等(===)可以避免隐式类型转换带来的意外结果
理解JavaScript的数据类型及其行为是掌握这门语言的基础,也是避免常见错误的关键。在后续章节中,我们将深入探讨每种数据类型的特性和高级用法。
练习
- 尝试不同类型之间的转换,观察结果并解释原因
- 编写一个函数,能够准确判断任何JavaScript值的具体类型
- 探索浮点数精度问题,并尝试实现一个可靠的浮点数比较函数