一、对象的基本定义
JavaScript对象是一种复合数据类型,用于存储键值对(属性和方法)的集合。在JavaScript中,对象是引用类型的数据结构,几乎所有JavaScript值(除了原始类型)都是对象。
根据JavaScript语言规范,对象是:无序的属性集合,每个属性可以包含原始值、对象或函数。
对象的核心特性
键值对集合:对象由零个或多个键值对组成。
动态性:可以随时添加、修改或删除属性。
引用类型:对象赋值时传递的是引用而非副本。
原型继承:对象可以从其他对象继承属性和方法。
属性的多样性:属性可以是数据属性或访问器属性
对象的内部表示
在JavaScript引擎中,对象通常以哈希表或类似结构实现,包含:
属性名(字符串或Symbol)。
属性值的引用。
指向原型对象的内部链接
[[Prototype]])。
二、对象的创建方式
对象字面量语法(最常用)
const person = {
name: "雷龙",
age: 28,
greet() { // ES6方法简写
return `你好,我是${this.name}`;
}
};工厂函数模式
function createPerson(name, age) {
// 创建并返回一个新对象
const person = {};
person.name = name;
person.age = age;
person.greet = function() {
return `你好,我是${this.name}`;
};
return person; // 显式返回对象
}
const person1 = createPerson("追风", 28);
const person2 = createPerson("破军", 30);工厂函数方式特征
函数名通常为动词形式(createXXX)。
在函数内部创建一个新对象。
为对象添加属性和方法。
显式返回创建的对象。
创建的对象无法识别其类型(person1 instanceof createPerson 为 false)。
构造函数方式
// Object构造函数
const person = new Object();
person.name = "追风";
person.age = 25;
person.greet = function() {
return `你好,我是${this.name}`;
};// 自定义构造函数
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function() {
return `你好,我是${this.name}`;
};
}
const person1 = new Person("追风", 28);
const person2 = new Person("破军", 30);构造函数方式特征
使用函数定义对象类型(通常首字母大写)。
通过 new 关键字调用函数。
在函数内部使用 this 关键字指向新创建的对象。
不需要显示返回对象。
创建的对象是该构造函数的实例(person1 instanceof Person 为 true)。
原型模式
function Person() {}
// 方法定义在原型上,所有实例共享
Person.prototype.name = "默认名";
Person.prototype.greet = function() {
return `你好,我是${this.name}`;
};
const person1 = new Person();
person1.name = "烈阳";组合模式(构造函数+原型)
function Person(name, age) {
// 实例属性
this.name = name;
this.age = age;
this.friends = ["黄九"];
}
// 共享方法
Person.prototype.greet = function() {
return `你好,我是${this.name}`;
};ES6类语法
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `你好,我是${this.name}`;
}
static createAdult(name) {
return new Person(name, 18);
}
}Object.create()方法
// 创建以指定对象为原型的新对象
const personProto = {
greet() {
return `你好,我是${this.name}`;
}
};
const person = Object.create(personProto, {
name: { value: "陆通", writable: true }
});三、原型与继承机制
原型链(Prototype Chain)
每个对象都有一个内部的
[[Prototype]]属性,指向它的原型对象。当访问对象的属性时,如果对象本身没有该属性,会沿着原型链向上查找。
原型链的顶端是
Object.prototype,其[[Prototype]]为null
原型继承的实现方式
// 原型继承示例
function SuperType() {
this.superValue = true;
}
SuperType.prototype.getSuperValue = function() {
return this.superValue;
};
function SubType() {
this.subValue = false;
}
// 关键:将子类型的原型设为父类型的实例
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.subValue;
};
const instance = new SubType();
console.log(instance.getSuperValue()); // trueES6的类继承
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name}发出叫声`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 必须先调用super()
this.breed = breed;
}
speak() {
console.log(`${this.name}汪汪叫`); // 重写父类方法
}
}
const shiba = new Dog('小柴', '柴犬');
shiba.speak(); // 小柴汪汪叫四、对象的属性类型
1. 数据属性的四个特性
value:属性的值(赋值的值)。
writable:是否可修改(默认值:true)。
enumerable:是否可枚举(如在for...in循环中)(默认值:true,即可枚举)。
configurable:是否可配置(如删除属性,默认值:true,即可配置)。
// 创建一个空对象
const user = {};
// 添加一个具有自定义特性的属性
Object.defineProperty(user, 'username', {
value: 'admin',
writable: true,
enumerable: true,
configurable: true
});
console.log(user.username); // 'admin'value特性示例
const obj = {};
// 设置value
Object.defineProperty(obj, 'test', {
value: 'initial value'
});
console.log(obj.test); // 'initial value'
// 尝试修改value(如果writable为true)
Object.defineProperty(obj, 'test', {
value: 'new value'
});
console.log(obj.test); // 'new value'writable特性示例
const product = {};
// 创建不可写的属性
Object.defineProperty(product, 'price', {
value: 100,
writable: false // 不可修改
});
console.log(product.price); // 100
// 尝试修改(在严格模式下会抛出错误,非严格模式下静默失败)
try {
product.price = 150;
console.log(product.price); // 仍然是100,因为writable为false
} catch (e) {
console.log('错误:', e.message);
}enumerable特性示例
const car = {};
// 添加可枚举属性
Object.defineProperty(car, 'brand', {
value: 'Toyota',
enumerable: true
});
// 添加不可枚举属性
Object.defineProperty(car, 'engine', {
value: 'V6',
enumerable: false
});
// 直接访问都可以
console.log(car.brand); // 'Toyota'
console.log(car.engine); // 'V6'
// for...in循环只显示可枚举属性
console.log('\nfor...in循环结果:');
for (const prop in car) {
console.log(prop + ': ' + car[prop]);
// 只输出: brand: Toyota
}
// Object.keys()也只返回可枚举属性
console.log('\nObject.keys()结果:');
console.log(Object.keys(car)); // ['brand']
// Object.getOwnPropertyNames()返回所有自有属性,无论是否可枚举
console.log('\nObject.getOwnPropertyNames()结果:');
console.log(Object.getOwnPropertyNames(car)); // ['brand', 'engine']configurable特性示例
const settings = {};
// 创建可配置的属性
Object.defineProperty(settings, 'theme', {
value: 'dark',
configurable: true
});
// 创建不可配置的属性
Object.defineProperty(settings, 'language', {
value: 'zh-CN',
configurable: false
});
// 尝试删除可配置属性
console.log(settings.theme); // 'dark'
delete settings.theme;
console.log(settings.theme); // undefined(已删除)
// 尝试删除不可配置属性
console.log(settings.language); // 'zh-CN'
try {
delete settings.language;
console.log(settings.language); // 仍然是'zh-CN',删除失败
} catch (e) {
console.log('错误:', e.message);
}
// 尝试修改不可配置属性的特性
console.log('尝试修改language的writable特性:');
try {
Object.defineProperty(settings, 'language', {
writable: true
});
console.log('成功修改');
} catch (e) {
console.log('错误:', e.message); // 在严格模式下会抛出错误
}实际应用示例
下面是一个综合示例,展示如何使用这些特性创建一个简单的只读常量对象:
// 创建一个包含常量的对象
const CONFIG = {};
// 定义不可写、不可配置的常量属性
Object.defineProperty(CONFIG, 'API_URL', {
value: 'https://api.example.com',
writable: false,
configurable: false,
enumerable: true
});
Object.defineProperty(CONFIG, 'TIMEOUT', {
value: 5000,
writable: false,
configurable: false,
enumerable: true
});
// 或者使用Object.defineProperties一次定义多个属性
Object.defineProperties(CONFIG, {
DEBUG: {
value: true,
writable: false,
configurable: false,
enumerable: true
},
VERSION: {
value: '1.0.0',
writable: false,
configurable: false,
enumerable: true
}
});
// 尝试修改会失败
CONFIG.API_URL = 'https://new-api.example.com';
console.log(CONFIG.API_URL); // 仍然是 'https://api.example.com'
// 枚举所有配置
console.log('\n配置项:');
for (const key in CONFIG) {
console.log(key + ': ' + CONFIG[key]);
}数据属性之间的关系
当 configurable 为 false 时,有一些限制:
不能删除该属性。
不能将 configurable 从 false 改为 true。
不能修改 enumerable 特性。
可以修改 writable 从 true 改为 false(但不能反向)。
可以修改 value 特性的值(如果 writable 为 true)。
const limitedObj = {};
// 先创建一个可配置的属性
Object.defineProperty(limitedObj, 'test', {
value: 1,
writable: true,
configurable: true
});
// 然后将其设为不可配置
Object.defineProperty(limitedObj, 'test', {
configurable: false
});
// 现在可以将writable改为false
Object.defineProperty(limitedObj, 'test', {
writable: false
});
// 但不能再改回true
console.log('尝试将writable从false改回true:');
try {
Object.defineProperty(limitedObj, 'test', {
writable: true
});
} catch (e) {
console.log('错误:', e.message);
}通过这些示例,我们可以看到JavaScript对象的属性特性如何控制属性的行为,以及如何使用这些特性来实现更精细的对象属性管理。
2. 访问器属性的四个特性
get:获取属性值的函数。
set:设置属性值的函数。
enumerable:是否可枚举。
configurable:是否可配置。
基本示例
// 示例1: 使用Object.defineProperty()定义访问器属性
const person = {
_name: "七杀" // 下划线通常表示内部属性
};
// 定义访问器属性name
Object.defineProperty(person, "name", {
get() {
console.log("获取name属性");
return this._name;
},
set(newValue) {
console.log("设置name属性为:", newValue);
this._name = newValue;
},
enumerable: true,
configurable: true
});
// 使用访问器属性
console.log(person.name); // 输出: 获取name属性 七杀
person.name = "柔柔"; // 输出: 设置name属性为: 柔柔
console.log(person.name); // 输出: 获取name属性 柔柔在对象字面量中定义访问器属性
// 示例2: 在对象字面量中使用get/set语法糖
const person2 = {
_name: "黄仙儿",
_age: 25,
// 定义getter和setter
get name() {
return this._name;
},
set name(value) {
this._name = value;
},
// 使用访问器属性进行数据验证或计算
get age() {
return this._age;
},
set age(value) {
if (value >= 0 && value <= 150) {
this._age = value;
} else {
console.log("年龄必须在0-150之间");
}
}
};
console.log(person2.name); // 黄仙儿
person2.age = 160; // 年龄必须在0-150之间
console.log(person2.age); // 25 (未被修改)访问器属性通过 Object.defineProperty() 或 Object.defineProperties() 方法定义,也可以在对象字面量中使用get/set语法糖定义。
// 属性批量定义
const person = {};
Object.defineProperties(person, {
firstName: {
value: "建",
writable: true
},
lastName: {
value: "木",
writable: true
},
fullName: {
get() {
return `${this.firstName}${this.lastName}`;
}
}
});使用Object.getOwnPropertyDescriptor()方法查看属性的特性:
// 查看访问器属性的特性
const descriptor = Object.getOwnPropertyDescriptor(person, "name");
console.log(descriptor);
/* 输出:
{
get: [Function: get],
set: [Function: set],
enumerable: true,
configurable: true
}
*/
// 注意:访问器属性没有value和writable特性
console.log("value特性:", descriptor.value); // undefined
console.log("writable特性:", descriptor.writable); // undefined3. 注意事项
访问器属性和数据属性的区别:
数据属性有:value、writable、enumerable、configurable。
访问器属性有:get、set、enumerable、configurable。
两者不能同时拥有(会抛出错误)。
严格模式下的行为:
在严格模式下,尝试写入一个只有getter没有setter的属性会抛出错误。
尝试读取一个只有setter没有getter的属性会返回undefined(非严格模式)。
继承:访问器属性可以被继承,子类可以覆盖父类的getter/setter。
五、对象的高级特性
对象的可扩展性
Object.preventExtensions():阻止添加新属性。
Object.seal():阻止添加/删除属性(将所有属性的configurable设为false)。
Object.freeze():阻止添加/删除/修改属性(冻结对象)。
Object.isExtensible(), Object.isSealed(), Object.isFrozen():检查对象状态。
Object.preventExtensions():阻止添加新属性
// 创建一个普通对象
let obj1 = { name: 'John', age: 30 };
// 检查对象是否可扩展
console.log(Object.isExtensible(obj1)); // true
// 阻止对象扩展
Object.preventExtensions(obj1);
// 尝试添加新属性(在严格模式下会抛出错误)
try {
obj1.city = 'New York';
console.log(obj1.city); // undefined(非严格模式下静默失败)
} catch (e) {
console.log(e.message); // 在严格模式下会显示错误信息
}
// 验证对象是否仍然可扩展
console.log(Object.isExtensible(obj1)); // false
// 仍然可以修改和删除现有属性
obj1.name = 'Alice';
console.log(obj1.name); // 'Alice'
delete obj1.age;
console.log(obj1.age); // undefinedObject.seal():阻止添加/删除属性(将所有属性的configurable设为false)
// 创建一个普通对象
let obj2 = { name: 'John', age: 30 };
// 检查对象是否被密封
console.log(Object.isSealed(obj2)); // false
// 密封对象
Object.seal(obj2);
// 尝试添加新属性(在严格模式下会抛出错误)
try {
obj2.city = 'New York';
console.log(obj2.city); // undefined(非严格模式下静默失败)
} catch (e) {
console.log(e.message); // 在严格模式下会显示错误信息
}
// 尝试删除现有属性(在严格模式下会抛出错误)
try {
delete obj2.age;
console.log(obj2.age); // 30(非严格模式下静默失败,属性仍然存在)
} catch (e) {
console.log(e.message); // 在严格模式下会显示错误信息
}
// 验证对象是否被密封
console.log(Object.isSealed(obj2)); // true
// 仍然可以修改现有属性的值
obj2.name = 'Bob';
console.log(obj2.name); // 'Bob'
// 尝试修改属性的描述符(会失败,因为configurable现在为false)
try {
Object.defineProperty(obj2, 'name', { writable: false });
console.log('Success'); // 在非严格模式下不会执行到这里
} catch (e) {
console.log(e.message); // 在严格模式下会显示错误信息
}Object.freeze():阻止添加/删除/修改属性(冻结对象)
// 创建一个普通对象
let obj3 = { name: 'John', age: 30 };
// 检查对象是否被冻结
console.log(Object.isFrozen(obj3)); // false
// 冻结对象
Object.freeze(obj3);
// 尝试添加新属性(在严格模式下会抛出错误)
try {
obj3.city = 'New York';
console.log(obj3.city); // undefined(非严格模式下静默失败)
} catch (e) {
console.log(e.message); // 在严格模式下会显示错误信息
}
// 尝试删除现有属性(在严格模式下会抛出错误)
try {
delete obj3.age;
console.log(obj3.age); // 30(非严格模式下静默失败,属性仍然存在)
} catch (e) {
console.log(e.message); // 在严格模式下会显示错误信息
}
// 尝试修改现有属性的值(在严格模式下会抛出错误)
try {
obj3.name = 'Charlie';
console.log(obj3.name); // 'John'(非严格模式下静默失败,值未改变)
} catch (e) {
console.log(e.message); // 在严格模式下会显示错误信息
}
// 验证对象是否被冻结
console.log(Object.isFrozen(obj3)); // true
// 尝试修改属性的描述符(会失败)
try {
Object.defineProperty(obj3, 'name', { writable: true });
console.log('Success'); // 在非严格模式下不会执行到这里
} catch (e) {
console.log(e.message); // 在严格模式下会显示错误信息
}Object.isExtensible(), Object.isSealed(), Object.isFrozen():检查对象状态
// 创建普通对象
let normalObj = { prop: 'value' };
console.log('普通对象:');
console.log('可扩展:', Object.isExtensible(normalObj)); // true
console.log('已密封:', Object.isSealed(normalObj)); // false
console.log('已冻结:', Object.isFrozen(normalObj)); // false
// 创建不可扩展对象
let preventExtObj = { prop: 'value' };
Object.preventExtensions(preventExtObj);
console.log('\n不可扩展对象:');
console.log('可扩展:', Object.isExtensible(preventExtObj)); // false
console.log('已密封:', Object.isSealed(preventExtObj)); // false
console.log('已冻结:', Object.isFrozen(preventExtObj)); // false
// 创建密封对象
let sealedObj = { prop: 'value' };
Object.seal(sealedObj);
console.log('\n密封对象:');
console.log('可扩展:', Object.isExtensible(sealedObj)); // false
console.log('已密封:', Object.isSealed(sealedObj)); // true
console.log('已冻结:', Object.isFrozen(sealedObj)); // false
// 创建冻结对象
let frozenObj = { prop: 'value' };
Object.freeze(frozenObj);
console.log('\n冻结对象:');
console.log('可扩展:', Object.isExtensible(frozenObj)); // false
console.log('已密封:', Object.isSealed(frozenObj)); // true
console.log('已冻结:', Object.isFrozen(frozenObj)); // true注意事项
严格模式 vs 非严格模式:
在非严格模式下,违反这些限制的操作会静默失败。
在严格模式下(使用 "use strict";),违反限制会抛出 TypeError 异常。
深度vs浅度操作:
这些方法只对对象本身进行操作,不影响嵌套对象。
要完全冻结嵌套对象,需要递归应用这些方法。
状态关系:
冻结对象一定是密封的,密封对象一定是不可扩展的。
因此 Object.isFrozen(obj) 为 true 时,Object.isSealed(obj) 和 Object.isExtensible(obj) 分别为 true 和 false。
性能优化:
浏览器可能会对密封或冻结的对象进行性能优化,因为它们的结构不会改变。
对象的合并与复制
// 浅拷贝
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = Object.assign({}, obj1);
// 或使用展开运算符
const obj3 = { ...obj1 };// 深拷贝(简单实现)
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj.getTime());
if (obj instanceof Array) return obj.map(item => deepClone(item));
const clonedObj = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = deepClone(obj[key]);
}
}
return clonedObj;
}const person = { name: "小脚姑娘", age: 20, city: "北京" };
// 遍历键
for (const key in person) {
if (person.hasOwnProperty(key)) {
console.log(key);
}
}
// 获取所有键
const keys = Object.keys(person);
// 获取所有值
const values = Object.values(person);
// 获取所有键值对
const entries = Object.entries(person);
// 使用for...of遍历
for (const [key, value] of Object.entries(person)) {
console.log(`${key}: ${value}`);
}六、对象的上下文与this
this的绑定规则
默认绑定:独立函数调用时,this指向全局对象。
隐式绑定:作为对象方法调用时,this指向该对象。
显式绑定:使用call()/apply()/bind()时,this指向指定对象。
构造函数绑定:使用new时,this指向新创建的对象。
const person = {
name: "水月",
greet() {
console.log(`你好,我是${this.name}`);
},
delayedGreet() {
// 这里this指向person
setTimeout(function() {
// 这里this指向全局对象(非严格模式)
console.log(`你好,我是${this.name}`);
}, 1000);
},
delayedGreetArrow() {
// 箭头函数继承外部作用域的this
setTimeout(() => {
console.log(`你好,我是${this.name}`); // this指向person
}, 1000);
}
};
person.greet(); // 你好,我是水月
person.delayedGreet(); // 你好,我是
person.delayedGreetArrow(); // 你好,我是水月七、JavaScript对象的特殊性质
一切皆对象?
JavaScript中,除了原始类型(string、number、boolean、null、undefined、symbol、bigint),其他值都是对象。原始类型有对应的包装对象。
内置对象与宿主对象
内置对象:Object、Array、Function、Date、RegExp等。
宿主对象:由执行环境提供,如浏览器中的 Window、Document 等。
对象的相等性比较
// 引用比较
const obj1 = { a: 1 };
const obj2 = { a: 1 };
const obj3 = obj1;
console.log(obj1 == obj2); // false
console.log(obj1 === obj2); // false
console.log(obj1 === obj3); // true
// 深度比较需要手动实现或使用库小知识
BigInt 是在 ECMAScript 2020(ES11)中正式引入的原始数据类型,用于表示任意精度的整数,可以表示超出 Number 类型范围的整数。
BigInt 的特点:
使用后缀 n 来表示,例如 123n。
可以表示任意大的整数,不受 Number 类型 2^53 - 1 限制。
不能与 Number 类型直接进行混合运算。
typeof 操作符对 BigInt 返回 'bigint'。
示例:
const bigNum = 123456789012345678901234567890n;
console.log(typeof bigNum); // 输出: "bigint"
// 超出 Number 安全整数范围的计算
const maxSafeInt = Number.MAX_SAFE_INTEGER; // 9007199254740991
console.log(maxSafeInt + 1 === maxSafeInt + 2); // 输出: true (因为溢出了)
const bigIntValue = BigInt(maxSafeInt);
console.log(bigIntValue + 1n === bigIntValue + 2n); // 输出: false (BigInt 正确处理)八、性能优化考虑
对象属性访问优化
避免频繁修改对象结构。
使用对象字面量一次性创建对象。
对于频繁访问的属性,考虑使用局部变量缓存。
对象内存管理
避免不必要的闭包引用导致的内存泄漏。
不再使用的大型对象设置为null。
注意循环引用问题。
总结
JavaScript对象是语言的核心构建块,其灵活性和强大功能支持了复杂的应用开发。通过深入理解对象的内部结构、原型继承机制、属性特性和各种高级特性,我们可以更高效地设计和实现JavaScript应用,避免常见陷阱,编写出更加健壮和可维护的代码。