JavaScript 数据类型
简介
JavaScript 中的数据类型分为两大类:原始类型(Primitive) 和 引用类型(Reference)。
原始类型(值不可变,按值传递):
- string 字符串
- number 数字
- boolean 布尔值
- undefined 未定义
- null 空值
- symbol Symbol(ES6)
- bigint BigInt(ES2020)
引用类型(值可变,按引用传递):
- object 对象(包括数组、函数、日期等)
原始数据类型
string(字符串)
表示文本数据,详见下文重点介绍。
number(数字)
表示整数和浮点数,所有数字都是 Number 类型。
let age = 25;
let price = 19.99;
let hex = 0xFF; // 255
let exp = 1e3; // 1000
// 特殊值
console.log(Infinity); // 无穷大
console.log(-Infinity); // 负无穷大
console.log(NaN); // 非数字(Not a Number)
console.log(NaN === NaN); // false(NaN 不等于任何值)
boolean(布尔值)
只有两个值:true 和 false。
let isActive = true;
let isDeleted = false;
undefined
变量已声明但未赋值时的值为 undefined。
let x;
console.log(x); // undefined
function foo() {} // 没有 return 的函数默认返回 undefined
console.log(foo()); // undefined
null
表示"空值"或"不存在的对象"。
let user = null; // 明确表示没有值
// typeof 的怪异行为
console.log(typeof null); // "object"(这是历史遗留 bug)
console.log(null === undefined); // false
console.log(null == undefined); // true(== 下相等)
symbol(Symbol)
ES6 引入,表示唯一的标识符,常用于对象属性的键。
const id = Symbol('id');
const id2 = Symbol('id');
console.log(id === id2); // false(每个 Symbol 都是唯一的)
bigint(BigInt)
ES2020 引入,表示任意精度的整数,用于超出 Number 范围的大整数。
const big = 12345678901234567890n; // 末尾的 n 表示 BigInt
const alsoBig = BigInt(9007199254740991);
console.log(typeof big); // "bigint"
字符串(String)—— 重点
定义方式
// 单引号或双引号(推荐保持一致)
let name1 = 'Alice';
let name2 = "Bob";
// 模板字符串(反引号,ES6)—— 支持多行和插值
let name = 'Alice';
let age = 25;
let greeting = `Hello, my name is ${name} and I'm ${age} years old.`;
console.log(greeting); // Hello, my name is Alice and I'm 25 years old.
// 多行字符串
let multiLine = `第一行
第二行
第三行`;
console.log(multiLine);
字符串不可变性
字符串一旦创建就不能被修改,所有"修改"操作都返回新字符串。
let str = 'hello';
str[0] = 'H'; // 不会报错,但无效
console.log(str); // "hello"(原字符串未改变)
str = 'Hello'; // 这是重新赋值,不是修改原字符串
console.log(str); // "Hello"
常用属性
let str = 'Hello World';
console.log(str.length); // 11(字符串长度)
console.log(str[0]); // "H"(按索引访问)
console.log(str[str.length - 1]); // "d"(最后一个字符)
常用方法
大小写转换:
console.log('Hello'.toUpperCase()); // "HELLO"
console.log('Hello'.toLowerCase()); // "hello"
查找与判断:
let str = 'Hello World';
console.log(str.indexOf('World')); // 6(首次出现位置,找不到返回 -1)
console.log(str.includes('Hello')); // true(是否包含)
console.log(str.startsWith('Hello')); // true(是否以...开头)
console.log(str.endsWith('World')); // true(是否以...结尾)
提取子串:
let str = 'Hello World';
console.log(str.slice(0, 5)); // "Hello"(从索引 0 到 4)
console.log(str.slice(6)); // "World"(从索引 6 到末尾)
console.log(str.slice(-5)); // "World"(负数表示从末尾往前数)
console.log(str.substring(0, 5)); // "Hello"(类似 slice,但不支持负数)
替换:
let str = 'Hello World';
console.log(str.replace('World', 'JavaScript')); // "Hello JavaScript"
console.log(str.replace(/l/g, 'L')); // "HeLLo WorLd"(正则全局替换)
分割与合并:
// split:字符串 → 数组
let str = 'apple,banana,orange';
let arr = str.split(',');
console.log(arr); // ["apple", "banana", "orange"]
// 数组 → 字符串(见数组部分)
去除空白:
console.log(' hello '.trim()); // "hello"(去除两端空白)
console.log(' hello '.trimStart()); // "hello "(去除开头空白)
console.log(' hello '.trimEnd()); // " hello"(去除结尾空白)
其他常用方法:
console.log('hello'.repeat(3)); // "hellohellohello"
console.log('hello'.charAt(0)); // "h"(指定位置的字符)
console.log('hello'.concat(' world')); // "hello world"(拼接)
数组(Array)—— 重点
定义方式
// 字面量(推荐)
let fruits = ['apple', 'banana', 'orange'];
// 构造函数
let numbers = new Array(1, 2, 3);
let empty = new Array(3); // 创建一个长度为 3 的空数组
基本操作
let arr = ['a', 'b', 'c'];
// 访问和修改
console.log(arr[0]); // "a"
arr[1] = 'B'; // 修改元素
console.log(arr); // ["a", "B", "c"]
// 长度
console.log(arr.length); // 3
arr.length = 2; // 可以修改 length 来截断数组
console.log(arr); // ["a", "B"]
增删元素
会改变原数组的方法:
let arr = ['a', 'b'];
// 末尾添加/删除
arr.push('c'); // 末尾添加,返回新长度
console.log(arr); // ["a", "b", "c"]
let last = arr.pop(); // 末尾删除,返回被删除的元素
console.log(last); // "c"
console.log(arr); // ["a", "b"]
// 开头添加/删除
arr.unshift('0'); // 开头添加,返回新长度
console.log(arr); // ["0", "a", "b"]
let first = arr.shift(); // 开头删除,返回被删除的元素
console.log(first); // "0"
console.log(arr); // ["a", "b"]
// splice:全能增删改
let nums = [1, 2, 3, 4, 5];
nums.splice(1, 2); // 从索引 1 开始,删除 2 个元素
console.log(nums); // [1, 4, 5]
nums = [1, 2, 3, 4, 5];
nums.splice(2, 0, 'a', 'b'); // 从索引 2 开始,删除 0 个,插入 'a','b'
console.log(nums); // [1, 2, "a", "b", 3, 4, 5]
nums = [1, 2, 3, 4, 5];
nums.splice(1, 2, 'x'); // 从索引 1 开始,删除 2 个,替换成 'x'
console.log(nums); // [1, "x", 3, 4, 5]
查找元素
let arr = [10, 20, 30, 20];
console.log(arr.indexOf(20)); // 1(首次出现索引,找不到返回 -1)
console.log(arr.lastIndexOf(20)); // 3(最后一次出现索引)
console.log(arr.includes(30)); // true(是否包含)
console.log(arr.find(x => x > 15)); // 20(返回第一个满足条件的元素)
console.log(arr.findIndex(x => x > 15)); // 1(返回第一个满足条件的索引)
遍历与转换
不会改变原数组的方法:
let arr = [1, 2, 3, 4, 5];
// forEach:遍历(无返回值)
arr.forEach((item, index) => {
console.log(index, item);
});
// map:映射(返回新数组)
let doubled = arr.map(x => x * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(arr); // [1, 2, 3, 4, 5](原数组不变)
// filter:过滤
let evens = arr.filter(x => x % 2 === 0);
console.log(evens); // [2, 4]
// reduce:归约(强大!)
let sum = arr.reduce((acc, cur) => acc + cur, 0);
console.log(sum); // 15
// 找出最大值
let max = arr.reduce((a, b) => a > b ? a : b);
console.log(max); // 5
// some:是否存在满足条件的元素
console.log(arr.some(x => x > 3)); // true
// every:是否所有元素都满足条件
console.log(arr.every(x => x > 0)); // true
console.log(arr.every(x => x > 3)); // false
其他常用方法
let a = [1, 2];
let b = [3, 4];
// concat:合并数组(不改变原数组)
console.log(a.concat(b)); // [1, 2, 3, 4]
console.log(a); // [1, 2](不变)
// slice:提取子数组(不改变原数组)
let arr = [1, 2, 3, 4, 5];
console.log(arr.slice(1, 3)); // [2, 3](从索引1到2)
console.log(arr.slice(-2)); // [4, 5](最后两个)
// join:数组 → 字符串
console.log(['a', 'b', 'c'].join(',')); // "a,b,c"
console.log(['a', 'b', 'c'].join('')); // "abc"
// sort:排序(会改变原数组!)
let nums = [3, 1, 4, 1, 5];
nums.sort((a, b) => a - b); // 升序
console.log(nums); // [1, 1, 3, 4, 5]
// reverse:反转(会改变原数组!)
nums.reverse();
console.log(nums); // [5, 4, 3, 1, 1]
解构赋值(ES6)
let arr = ['Alice', 'Bob', 'Charlie'];
// 基本解构
let [first, second] = arr;
console.log(first); // "Alice"
console.log(second); // "Bob"
// 跳过元素
let [a, , c] = arr;
console.log(c); // "Charlie"
// 默认值
let [x, y, z, w = 'default'] = arr;
console.log(w); // "default"
// 剩余运算符
let [head, ...tail] = arr;
console.log(head); // "Alice"
console.log(tail); // ["Bob", "Charlie"]
对象(Object)—— 重点
定义方式
// 字面量(推荐)
let person = {
name: 'Alice',
age: 25,
isStudent: true
};
// 构造函数
let person2 = new Object();
person2.name = 'Bob';
person2.age = 30;
属性的访问与修改
let person = {
name: 'Alice',
age: 25,
'favorite-color': 'blue' // 属性名包含特殊字符时,必须用字符串
};
// 点符号(属性名必须是有效的标识符)
console.log(person.name); // "Alice"
person.age = 26; // 修改属性
person.city = 'Beijing'; // 添加新属性
// 方括号(属性名可以是变量或特殊字符)
console.log(person['name']); // "Alice"
console.log(person['favorite-color']); // "blue"
let key = 'age';
console.log(person[key]); // 26(使用变量访问)
// 删除属性
delete person.city;
console.log(person.city); // undefined
对象方法
对象可以包含函数作为方法。
let person = {
name: 'Alice',
age: 25,
greet: function() {
console.log('Hello, I am ' + this.name);
},
// ES6 简写
sayAge() {
console.log('I am ' + this.age + ' years old.');
}
};
person.greet(); // Hello, I am Alice
person.sayAge(); // I am 25 years old.
对象简写(ES6)
let name = 'Alice';
let age = 25;
// 属性名和变量名相同时可以简写
let person = { name, age };
// 方法也可以简写
let obj = {
hello() {
console.log('Hello!');
}
};
解构赋值(ES6)
let person = { name: 'Alice', age: 25, city: 'Beijing' };
// 基本解构
let { name, age } = person;
console.log(name); // "Alice"
console.log(age); // 25
// 重命名
let { name: fullName } = person;
console.log(fullName); // "Alice"
// 默认值
let { name, country = 'China' } = person;
console.log(country); // "China"
// 嵌套解构
let user = {
name: 'Bob',
address: {
city: 'Shanghai',
zip: '200000'
}
};
let { address: { city } } = user;
console.log(city); // "Shanghai"
展开运算符(ES6)
let person = { name: 'Alice', age: 25 };
// 复制对象(浅拷贝)
let copy = { ...person };
console.log(copy); // { name: 'Alice', age: 25 }
// 合并对象
let info = { ...person, city: 'Beijing', age: 26 };
console.log(info); // { name: 'Alice', age: 26, city: 'Beijing' }
常用静态方法
let obj = { a: 1, b: 2, c: 3 };
// 获取所有键
console.log(Object.keys(obj)); // ["a", "b", "c"]
// 获取所有值
console.log(Object.values(obj)); // [1, 2, 3]
// 获取所有键值对
console.log(Object.entries(obj)); // [["a", 1], ["b", 2], ["c", 3]]
// 遍历对象
Object.entries(obj).forEach(([key, value]) => {
console.log(key, value);
});
// 判断是否有某个属性
console.log('a' in obj); // true
console.log(obj.hasOwnProperty('a')); // true
类型检测
// typeof:检测原始类型(有坑!)
console.log(typeof 'hello'); // "string"
console.log(typeof 123); // "number"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof Symbol()); // "symbol"
console.log(typeof 123n); // "bigint"
console.log(typeof null); // "object"(历史 bug!)
console.log(typeof []); // "object"(数组也是对象)
console.log(typeof {}); // "object"
console.log(typeof function() {}); // "function"
// 正确检测数组
console.log(Array.isArray([])); // true
console.log(Array.isArray({})); // false
// instanceof:检测对象是否是指定构造函数的实例
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
// Object.prototype.toString.call():最准确的类型检测
console.log(Object.prototype.toString.call('')); // "[object String]"
console.log(Object.prototype.toString.call([])); // "[object Array]"
console.log(Object.prototype.toString.call(null)); // "[object Null]"
console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]"
原始类型 vs 引用类型
值传递 vs 引用传递
// 原始类型:按值传递(复制值)
let a = 10;
let b = a; // b 得到 a 的一个副本
b = 20;
console.log(a); // 10(a 不受影响)
console.log(b); // 20
// 引用类型:按引用传递(复制引用)
let arr1 = [1, 2, 3];
let arr2 = arr1; // arr2 和 arr1 指向同一个数组
arr2.push(4);
console.log(arr1); // [1, 2, 3, 4](arr1 也被影响了!)
console.log(arr2); // [1, 2, 3, 4]
// 对象同理
let obj1 = { name: 'Alice' };
let obj2 = obj1;
obj2.name = 'Bob';
console.log(obj1.name); // "Bob"(obj1 也被影响了!)
如何正确复制引用类型
// 数组:使用 slice() 或展开运算符(浅拷贝)
let arr1 = [1, 2, 3];
let arr2 = [...arr1]; // 或 arr1.slice()
arr2.push(4);
console.log(arr1); // [1, 2, 3](不受影响)
console.log(arr2); // [1, 2, 3, 4]
// 对象:使用展开运算符或 Object.assign()(浅拷贝)
let obj1 = { name: 'Alice', age: 25 };
let obj2 = { ...obj1 }; // 或 Object.assign({}, obj1)
obj2.name = 'Bob';
console.log(obj1.name); // "Alice"(不受影响)
注意:以上方法都是浅拷贝,如果对象或数组嵌套了其他引用类型,嵌套部分仍然是引用传递。
综合示例
// 学生信息处理
let students = [
{ name: 'Alice', scores: [85, 90, 78] },
{ name: 'Bob', scores: [75, 88, 92] },
{ name: 'Charlie', scores: [95, 87, 91] }
];
// 计算每个学生的平均分
let result = students.map(student => {
let avg = student.scores.reduce((sum, s) => sum + s, 0) / student.scores.length;
return {
name: student.name,
average: Math.round(avg * 100) / 100
};
});
console.log(result);
// [
// { name: 'Alice', average: 84.33 },
// { name: 'Bob', average: 85 },
// { name: 'Charlie', average: 91 }
// ]
// 找出平均分最高的学生
let topStudent = result.reduce((top, cur) => cur.average > top.average ? cur : top);
console.log(`Top student: ${topStudent.name} (${topStudent.average})`);
// Top student: Charlie (91)