JavaScript之对象操作详解
JavaScript中对象是数据和功能的集合,几乎所有值都可以视为对象(除原始类型外),掌握对象的创建、属性操作、遍历及继承等核心技能,是深入理解JavaScript的关键。
一、对象的创建方式
JavaScript提供了多种创建对象的方式,各有适用场景,选择合适的方式能让代码更高效。
1.1 对象字面量(最常用)
对象字面量是创建对象最简洁的方式,用{}
包裹键值对,键名可省略引号(符合标识符规则时)。
// 基础语法
const user = {
name: "张三",
age: 25,
isStudent: false,
// 方法简写(ES6+)
greet() {
console.log(`你好,我是${this.name}`);
},
// 嵌套对象
address: {
city: "北京",
district: "海淀区"
}
};
// 调用属性和方法
console.log(user.name); // "张三"
user.greet(); // "你好,我是张三"
console.log(user.address.city); // "北京"
特性:语法简洁,适合创建单个对象,无需定义构造函数,是日常开发的首选。
1.2 构造函数与new
关键字
构造函数用于创建多个结构相同的对象,通过this
绑定属性,new
关键字实例化对象。
// 定义构造函数(首字母大写,约定俗成)
function Person(name, age) {
// 实例属性
this.name = name;
this.age = age;
// 实例方法(每次创建实例都会重复定义,不推荐)
this.greet = function() {
console.log(`我是${this.name}`);
};
}
// 原型方法(所有实例共享,推荐)
Person.prototype.sayAge = function() {
console.log(`我${this.age}岁`);
};
// 实例化对象
const person1 = new Person("李四", 30);
const person2 = new Person("王五", 28);
console.log(person1.name); // "李四"
person1.greet(); // "我是李四"
person2.sayAge(); // "我28岁"
特性:适合创建多个同类型对象,通过原型(prototype
)共享方法可节省内存;需注意忘记写new
时this
会指向全局对象(严格模式下报错)。
1.3 Object.create()
方法
Object.create()
通过指定原型对象创建新对象,是实现继承的灵活方式。
// 原型对象
const animal = {
type: "动物",
eat() {
console.log("进食中");
}
};
// 以animal为原型创建新对象
const cat = Object.create(animal);
cat.name = "咪咪";
cat.meow = function() {
console.log("喵喵叫");
};
console.log(cat.name); // "咪咪"(自身属性)
console.log(cat.type); // "动物"(继承自原型)
cat.eat(); // "进食中"(继承的方法)
特性:直接指定原型,适合实现复杂继承关系;创建的对象默认无自身属性,需手动添加。
1.4 其他方式(ES6+)
ES6新增了class
语法糖(本质还是构造函数)和对象扩展运算符,简化对象创建:
// 1. class语法(ES6+)
class Car {
constructor(brand) {
this.brand = brand;
}
drive() {
console.log(`${this.brand}在行驶`);
}
}
const bmw = new Car("宝马");
bmw.drive(); // "宝马在行驶"
// 2. 对象扩展运算符(复制对象)
const obj1 = { a: 1 };
const obj2 = { ...obj1, b: 2 }; // 复制obj1并添加新属性
console.log(obj2); // { a: 1, b: 2 }
二、对象属性的操作方法
对象的核心是属性(包括数据属性和方法),掌握属性的增删改查是对象操作的基础。
2.1 访问与修改属性
属性访问有两种方式:点语法(.
)和方括号语法([]
),后者支持动态属性名。
const user = { name: "赵六", age: 22 };
// 1. 访问属性
console.log(user.name); // "赵六"(点语法,属性名固定)
console.log(user["age"]); // 22(方括号语法,属性名可动态)
// 2. 修改属性
user.age = 23;
user["name"] = "赵六六";
console.log(user); // { name: "赵六六", age: 23 }
// 3. 动态属性名(ES6+)
const propKey = "gender";
user[propKey] = "男"; // 等价于user.gender = "男"
console.log(user); // { name: "赵六六", age: 23, gender: "男" }
注意:方括号中需用字符串或变量(变量值为字符串);属性名若为数字会自动转为字符串(如obj[1]
等价于obj["1"]
)。
2.2 添加与删除属性
对象属性可动态添加,通过delete
关键字删除。
const book = { title: "JavaScript入门" };
// 添加属性
book.author = "张三";
book["price"] = 59;
console.log(book); // { title: "JavaScript入门", author: "张三", price: 59 }
// 删除属性
delete book.price;
console.log(book.price); // undefined(属性已删除)
console.log("price" in book); // false(检查属性是否存在)
特性:delete
仅删除对象自身属性,无法删除继承的属性;删除成功返回true
(即使属性不存在),但不能删除变量或函数。
2.3 检查属性是否存在
判断属性是否存在需区分“自身属性”和“继承属性”,常用方法如下:
const obj = { a: 1 };
// 继承自Object.prototype的属性
console.log(obj.toString); // 存在(继承)
// 1. in运算符:检查自身+继承属性
console.log("a" in obj); // true(自身属性)
console.log("toString" in obj); // true(继承属性)
// 2. hasOwnProperty():仅检查自身属性
console.log(obj.hasOwnProperty("a")); // true
console.log(obj.hasOwnProperty("toString")); // false(继承属性)
// 3. 检查属性值是否为undefined(不可靠,因属性可能存在但值为undefined)
obj.b = undefined;
console.log(obj.b === undefined); // true(但属性b存在)
console.log("b" in obj); // true(正确判断)
最佳实践:用hasOwnProperty()
检查自身属性,in
运算符检查是否可访问(包括继承)。
三、对象的遍历方法
遍历对象的属性是常见需求,不同方法适用于不同场景,需注意遍历顺序和属性类型。
3.1 for...in
循环
for...in
遍历对象的可枚举属性(包括继承的),适合简单遍历。
const car = { brand: "奔驰", price: 300000 };
// 添加不可枚举属性
Object.defineProperty(car, "color", {
value: "黑色",
enumerable: false // 不可枚举
});
// 遍历可枚举属性(自身+继承的可枚举属性)
for (const key in car) {
// 过滤继承属性
if (car.hasOwnProperty(key)) {
console.log(`${key}: ${car[key]}`);
}
}
// 输出:
// brand: 奔驰
// price: 300000
注意:
- 会遍历继承的可枚举属性(需用
hasOwnProperty()
过滤); - 遍历顺序:数字属性按升序,字符串属性按插入顺序,符号属性不遍历。
3.2 Object.keys()
与Object.values()
Object.keys()
返回对象自身可枚举属性的键名数组,Object.values()
返回值数组。
const user = { name: "孙七", age: 26, gender: "男" };
// 获取键名数组
const keys = Object.keys(user);
console.log(keys); // ["name", "age", "gender"]
// 获取值数组
const values = Object.values(user);
console.log(values); // ["孙七", 26, "男"]
// 遍历键值对
keys.forEach(key => {
console.log(`${key}: ${user[key]}`);
});
特性:仅包含自身可枚举属性,不包括继承和不可枚举属性;返回数组的顺序与for...in
一致。
3.3 Object.entries()
与Object.fromEntries()
Object.entries()
返回键值对数组,Object.fromEntries()
则将键值对数组转为对象(互为逆操作)。
const obj = { a: 1, b: 2 };
// 转为键值对数组
const entries = Object.entries(obj);
console.log(entries); // [["a", 1], ["b", 2]]
// 遍历键值对
entries.forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});
// 键值对数组转回对象
const newObj = Object.fromEntries(entries);
console.log(newObj); // { a: 1, b: 2 }
应用场景:结合数组方法处理对象,例如过滤属性:
// 过滤值大于1的属性
const filteredEntries = entries.filter(([key, value]) => value > 1);
const filteredObj = Object.fromEntries(filteredEntries); // { b: 2 }
3.4 其他方法(获取所有属性)
Object.getOwnPropertyNames(obj)
:返回自身所有属性(包括不可枚举,不包括符号属性);Object.getOwnPropertySymbols(obj)
:返回自身所有符号属性;Reflect.ownKeys(obj)
:返回自身所有属性(包括不可枚举和符号属性),最全面。
const sym = Symbol("id");
const obj = { a: 1 };
obj[sym] = 100;
Object.defineProperty(obj, "b", { value: 2, enumerable: false });
console.log(Object.getOwnPropertyNames(obj)); // ["a", "b"](不含符号属性)
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(id)]
console.log(Reflect.ownKeys(obj)); // ["a", "b", Symbol(id)](所有自身属性)
四、对象的高级操作
除基础操作外,对象的复制、合并、属性描述符等高级功能在复杂场景中非常实用。
4.1 对象的复制(浅拷贝与深拷贝)
对象是引用类型,直接赋值会导致共享内存,需通过方法实现拷贝。
4.1.1 浅拷贝(仅复制顶层属性)
const obj = { a: 1, b: { c: 2 } };
// 1. 对象扩展运算符
const copy1 = { ...obj };
// 2. Object.assign()
const copy2 = Object.assign({}, obj);
// 浅拷贝的问题:嵌套对象仍共享引用
copy1.b.c = 3;
console.log(obj.b.c); // 3(原对象被修改)
4.1.2 深拷贝(复制所有层级)
const obj = { a: 1, b: { c: 2 }, d: [3, 4] };
// 1. JSON方法(简单场景,有局限性:不支持函数、符号、循环引用等)
const deepCopy1 = JSON.parse(JSON.stringify(obj));
// 2. 递归实现深拷贝(完整方案)
function deepClone(target) {
if (typeof target !== "object" || target === null) {
return target; // 非对象直接返回
}
let clone = Array.isArray(target) ? [] : {};
for (const key in target) {
if (target.hasOwnProperty(key)) {
clone[key] = deepClone(target[key]); // 递归拷贝
}
}
return clone;
}
const deepCopy2 = deepClone(obj);
deepCopy2.b.c = 3;
console.log(obj.b.c); // 2(原对象不受影响)
4.2 对象的合并
合并多个对象为一个,相同属性后面的会覆盖前面的。
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const obj3 = { d: 5 };
// 1. Object.assign()
const merged1 = Object.assign({}, obj1, obj2, obj3);
console.log(merged1); // { a: 1, b: 3, c: 4, d: 5 }
// 2. 对象扩展运算符
const merged2 = { ...obj1, ...obj2, ...obj3 };
console.log(merged2); // 同上
特性:均为浅合并,嵌套对象仍共享引用;适合合并配置对象等场景。
4.3 属性描述符(Object.defineProperty()
)
通过属性描述符可精确控制属性的行为(是否可修改、枚举等)。
const obj = {};
// 定义属性描述符
Object.defineProperty(obj, "name", {
value: "属性值", // 属性值
writable: false, // 是否可修改(默认false)
enumerable: true, // 是否可枚举(默认false)
configurable: false // 是否可删除或修改描述符(默认false)
});
obj.name = "新值"; // 无效(writable为false)
delete obj.name; // 无效(configurable为false)
console.log(Object.keys(obj)); // ["name"](enumerable为true)
应用场景:定义常量属性、实现数据劫持(如Vue响应式原理)等。
五、常见问题与避坑指南
5.1 引用类型的赋值陷阱
const obj1 = { a: 1 };
const obj2 = obj1; // 引用赋值,共享内存
obj2.a = 2;
console.log(obj1.a); // 2(原对象被修改)
解决方案:用浅拷贝或深拷贝创建新对象,避免直接引用赋值。
5.2 for...in
遍历的继承属性问题
const obj = { a: 1 };
// 给原型添加属性(会被for...in遍历到)
Object.prototype.b = 2;
for (const key in obj) {
console.log(key); // "a", "b"(不希望遍历继承的b)
}
解决方案:用hasOwnProperty()
过滤继承属性:
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key); // "a"(仅自身属性)
}
}
5.3 符号属性的遍历问题
符号(Symbol
)作为属性名时,for...in
、Object.keys()
等方法无法遍历:
const sym = Symbol("id");
const obj = { a: 1, [sym]: 2 };
console.log(Object.keys(obj)); // ["a"](不含符号属性)
for (const key in obj) {
console.log(key); // "a"(不含符号属性)
}
解决方案:用Object.getOwnPropertySymbols()
获取符号属性:
const symbols = Object.getOwnPropertySymbols(obj);
console.log(symbols); // [Symbol(id)]
console.log(obj[symbols[0]]); // 2
总结:对象操作的核心原则
- 选择合适的创建方式:简单对象用字面量,多实例对象用
class
或构造函数,复杂继承用Object.create()
;- 区分自身与继承属性:操作属性时注意
hasOwnProperty()
的使用,避免误操作继承属性;- 遍历方法的选择:
Object.keys()
/values()
适合简单遍历,Reflect.ownKeys()
适合获取所有属性;- 深浅拷贝的区别:浅拷贝适合扁平对象,嵌套对象需用深拷贝避免引用问题。
若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ