Javascript中你不了解的对象

41 阅读3分钟

Javascript中你不了解的对象

个人主页:康师傅前端面馆


在编程中,对象是一个基础且常用的概念,很多人初学时会认为对象不过是属性和值的简单集合,十分容易掌握。然而,随着开发的深入,我们会发现对象隐藏着许多容易被忽视的细节和陷阱。从引用类型的赋值问题,到 this 指向的困惑,再到遍历对象时的各种意外情况,这些常见错误时刻提醒着我们:对象远没有想象中那么简单。本文将全面剖析对象的各个方面,帮助大家深入理解并正确使用对象。

什么是对象?

对象是属性的集合,每个属性都有一个键(key)和值(value)。键通常是字符串或 Symbol,值可以是任何数据类型,包括其他对象或函数。

const person = {
  name: "张三",
  age: 30,
  greet: function() {
    return "Hello, " + this.name;
  }
};

创建对象的方式

1. 对象字面量(Object Literal)

最简单直观的方式:

const obj = {
  property1: value1,
  property2: value2,
  method: function() {
    // 方法体
  }
};

2. 使用 new Object()

const obj = new Object();
obj.property1 = value1;
obj.property2 = value2;

3. 构造函数模式

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.greet = function() {
    return "Hello, " + this.name;
  };
}

const person1 = new Person("李四", 25);

4. Object.create()

const proto = {
  greet: function() {
    return "Hello, " + this.name;
  }
};

const obj = Object.create(proto);
obj.name = "王五";

属性操作

访问属性

const obj = { name: "张三", age: 30 };

// 点表示法
console.log(obj.name); // "张三"

// 括号表示法
console.log(obj["age"]); // 30

// 动态属性访问
const prop = "name";
console.log(obj[prop]); // "张三"

添加/修改属性

const obj = {};

// 添加属性
obj.name = "张三";
obj["age"] = 30;

// 修改属性
obj.name = "李四";

// 使用 Object.defineProperty
Object.defineProperty(obj, 'gender', {
  value: 'male',
  writable: true,
  enumerable: true,
  configurable: true
});

删除属性

const obj = { name: "张三", age: 30 };

delete obj.age; // 返回 true
console.log(obj); // { name: "张三" }

对象方法

内置静态方法

Object.keys()

返回对象自身可枚举属性的数组:

const obj = { a: 1, b: 2, c: 3 };
console.log(Object.keys(obj)); // ["a", "b", "c"]
Object.values()

返回对象自身可枚举属性值的数组:

const obj = { a: 1, b: 2, c: 3 };
console.log(Object.values(obj)); // [1, 2, 3]
Object.entries()

返回对象自身可枚举属性的键值对数组:

const obj = { a: 1, b: 2, c: 3 };
console.log(Object.entries(obj)); // [["a", 1], ["b", 2], ["c", 3]]
Object.assign()

复制对象属性:

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);
console.log(returnedTarget); // { a: 1, b: 4, c: 5 }

原型与继承

原型链

每个对象都有一个内部属性 [[Prototype]],指向其原型对象:

function Animal(name) {
  this.name = name;
}

Animal.prototype.speak = function() {
  console.log(`${this.name} makes a noise.`);
};

function Dog(name) {
  Animal.call(this, name);
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.speak = function() {
  console.log(`${this.name} barks.`);
};

const dog = new Dog('Rex');
dog.speak(); // "Rex barks."

ES6 类语法

class Animal {
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Dog extends Animal {
  speak() {
    console.log(`${this.name} barks.`);
  }
}

const dog = new Dog('Rex');
dog.speak(); // "Rex barks."

高级特性

Getter 和 Setter

const obj = {
  firstName: 'John',
  lastName: 'Doe',
  
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  },
  
  set fullName(value) {
    const parts = value.split(' ');
    this.firstName = parts[0];
    this.lastName = parts[1];
  }
};

console.log(obj.fullName); // "John Doe"
obj.fullName = "Jane Smith";
console.log(obj.firstName); // "Jane"

属性描述符

const obj = {};

Object.defineProperty(obj, 'prop', {
  value: 42,
  writable: false, // 不可写
  enumerable: false, // 不可枚举
  configurable: false // 不可配置
});

console.log(obj.prop); // 42
obj.prop = 77; // 无效
console.log(obj.prop); // 仍是 42

计算属性名

const propertyName = 'dynamicProperty';
const obj = {
  [propertyName]: 'computed value',
  [`method_${propertyName}`]() {
    return 'computed method';
  }
};

console.log(obj.dynamicProperty); // "computed value"

解构赋值

const person = { 
  name: "张三", 
  age: 30, 
  city: "北京" 
};

// 基本解构
const { name, age } = person;
console.log(name, age); // "张三" 30

// 重命名
const { name: fullName, age: years } = person;
console.log(fullName, years); // "张三" 30

// 默认值
const { name, country = "中国" } = person;
console.log(country); // "中国"

常见误区

1. 引用类型陷阱

// 错误示例
const obj1 = { a: 1 };
const obj2 = obj1;
obj2.a = 2;
console.log(obj1.a); // 2 (不是 1!)

// 正确做法
const obj1 = { a: 1 };
const obj2 = Object.assign({}, obj1); // 或 {...obj1}
obj2.a = 2;
console.log(obj1.a); // 1

2. this 指向问题

const obj = {
  name: "张三",
  greet: function() {
    console.log(`Hello, ${this.name}`);
  }
};

const greetFunc = obj.greet;
greetFunc(); // "Hello, undefined"

// 解决方案
const boundGreet = obj.greet.bind(obj);
boundGreet(); // "Hello, 张三"

3. for...in 遍历陷阱

const obj = { a: 1, b: 2 };
Object.prototype.c = 3; // 扩展原型

// for...in 会遍历原型链上的可枚举属性
for (let key in obj) {
  console.log(key); // 输出 a, b, c
}

// 正确做法:检查自有属性
for (let key in obj) {
  if (obj.hasOwnProperty(key)) {
    console.log(key); // 只输出 a, b
  }
}

// 更好的做法:使用 Object.keys
Object.keys(obj).forEach(key => {
  console.log(key); // 只输出 a, b
});

4. 浅拷贝 vs 深拷贝

// 浅拷贝问题
const obj1 = { 
  a: 1, 
  nested: { b: 2 } 
};

const obj2 = { ...obj1 }; // 浅拷贝
obj2.nested.b = 3;
console.log(obj1.nested.b); // 3 (被意外修改了!)

// 深拷贝解决方案
const deepClone = obj => JSON.parse(JSON.stringify(obj));
const obj3 = deepClone(obj1);
obj3.nested.b = 4;
console.log(obj1.nested.b); // 3 (正常)

最佳实践

1. 使用严格相等比较

// 推荐
if (obj.property !== undefined) { /* ... */ }

// 不推荐
if (obj.property != undefined) { /* ... */ }

2. 使用 hasOwnProperty 检查属性

// 安全地检查属性是否存在
if (obj.hasOwnProperty('property')) {
  // 处理属性
}

3. 合理使用冻结对象

const CONSTANTS = Object.freeze({
  API_URL: 'https://api.example.com',
  MAX_RETRY: 3
});

// CONSTANTS.API_URL = 'new url'; // 严格模式下会抛出错误

4. 使用现代语法简化代码

// 使用简写属性
const name = "张三";
const age = 30;
const person = { name, age }; // 等同于 { name: name, age: age }

// 使用简写方法
const obj = {
  // 简写
  method() {
    // ...
  },
  
  // 而不是
  method2: function() {
    // ...
  }
};

5. 合理组织对象结构

// 清晰的对象结构
const user = {
  profile: {
    name: "张三",
    email: "zhangsan@example.com"
  },
  preferences: {
    theme: "dark",
    language: "zh-CN"
  },
  actions: {
    login: () => {},
    logout: () => {}
  }
};