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: () => {}
}
};