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)共享方法可节省内存;需注意忘记写newthis会指向全局对象(严格模式下报错)。

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...inObject.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

总结:对象操作的核心原则

  1. 选择合适的创建方式:简单对象用字面量,多实例对象用class或构造函数,复杂继承用Object.create()
  2. 区分自身与继承属性:操作属性时注意hasOwnProperty()的使用,避免误操作继承属性;
  3. 遍历方法的选择Object.keys()/values()适合简单遍历,Reflect.ownKeys()适合获取所有属性;
  4. 深浅拷贝的区别:浅拷贝适合扁平对象,嵌套对象需用深拷贝避免引用问题。

若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值