JavaScript 对象 | 自在学
对象
对象是JavaScript语言的核心特性之一,也是理解这门语言的关键所在。在JavaScript的世界里,几乎所有的数据类型都可以看作是对象,或者至少具有对象的某些特征。理解对象不仅有助于我们更好地组织代码,还能让我们充分利用JavaScript的动态特性来构建灵活而强大的程序。
与许多传统的面向对象编程语言不同,JavaScript采用了基于原型的对象系统。这意味着对象可以直接从其他对象继承属性和方法,而不需要通过类的中介。这种设计使得JavaScript具有极大的灵活性,同时也要求我们以不同的思维方式来理解和使用对象。
对象本质上是属性的集合,每个属性都有一个名称和对应的值。属性的值可以是基本数据类型,也可以是函数(我们称之为方法)或者其他对象。通过这种简单而强大的结构,我们可以表示现实世界中复杂的实体和概念。
对象的创建
JavaScript为我们提供了多种创建对象的方式,每种方式都有其特定的使用场景和优势。掌握这些不同的创建方法,能够让我们在面对不同需求时选择最合适的解决方案。
对象字面量
对象字面量是创建对象最直接、最常用的方法。所谓“字面量”,就是直接在代码中写出对象的内容。我们通过一对花括号 {} 来包裹一组由属性名和属性值组成的键值对,每个属性之间用逗号分隔。例如:
// 创建一个空对象
let emptyObject = {};
// 创建一个包含基本信息的学生对象
let student = {
name: "王小华" ,
age: 20 ,
major: "计算机科学" ,
isActive: true
};
// 创建一个包含嵌套对象的课程信息
let course = {
title: "JavaScript编程" ,
instructor: {
name: "李老师" ,
department: "计算机学院" ,
email: "li.teacher@university.edu"
},
schedule: {
days: [ "周一" , "周三" , "周五" ],
time: "上午10:00-11:30" ,
classroom: "A201"
},
credits: 3
};
在使用对象字面量时,属性名可以是标识符、字符串字面量,甚至可以使用保留字。当属性名包含空格、连字符或者是保留字时,必须使用引号将其包围。
let productInfo = {
"product-name" : "智能手机" ,
"price" : 2999 ,
"in-stock" : true ,
"for" : "个人使用" , // for是保留字,需要引号
manufacturer: "科技公司"
};
构造函数创建
使用 new 关键字配合构造函数是 JavaScript 中创建对象的另一种重要方式。构造函数本质上是一个普通的函数,但按照约定,构造函数名称通常以大写字母开头。当我们使用 new 关键字调用构造函数时,会自动执行以下几个步骤:
创建一个全新的空对象;
将新对象的原型(__proto__)指向构造函数的 prototype 属性;
构造函数内部的 this 指向这个新对象;
执行构造函数中的代码,为新对象添加属性和方法;
如果构造函数没有显式返回对象,则默认返回新创建的对象。
JavaScript 内置了许多常用的构造函数,例如 Date、Array、RegExp 等,我们可以直接使用它们来创建相应类型的对象。同时,我们也可以根据实际需求自定义构造函数,用于批量创建具有相同结构和行为的对象。例如,可以自定义一个 Book 构造函数来创建图书对象,每个对象都包含书名、作者、页数等属性,并可以拥有自己的方法。
通过构造函数创建对象的方式,能够实现对象的“模板化”,便于管理和扩展,尤其适合需要创建多个类似对象的场景。
// 使用内置构造函数
let currentDate = new Date ();
let numbers = new Array ( 1 , 2 , 3 , 4 , 5 );
let userPattern = new RegExp ( " \\ w+@ \\ w+ \\ . \\ w+" );
// 自定义构造函数
function Book
Object.create()方法
Object.create() 方法是一种用于创建新对象的高级方式。与使用对象字面量或构造函数不同,Object.create() 允许我们直接指定新对象的原型(即 proto 指向的对象),从而实现对继承关系的精确控制。
例如,我们可以将某个现有对象作为新对象的原型,这样新对象就可以继承原型对象的属性和方法。
此外,Object.create() 还可以接受第二个参数,用于定义新对象自身的属性描述符(如 writable、enumerable、configurable 等),
这为对象属性的控制提供了更大的灵活性。因此,当我们需要创建具有特定原型链结构或需要细致控制属性特性的对象时,Object.create() 是非常有用的工具。
// 创建一个基础的动物对象作为原型
let animalPrototype = {
species: "未知" ,
makeSound : function () {
console. log ( "动物发出声音" );
},
introduce : function () {
console. log ( `我是一只${ this . species }` );
}
};
// 使用Object.create()创建具体的动物对象
let dog = Object.
原型链与继承
JavaScript的原型链是理解对象继承机制的核心概念。每个JavaScript对象都有一个内部链接指向另一个对象,这个被指向的对象就是原型。
当我们试图访问对象的某个属性时,如果对象本身没有这个属性,JavaScript引擎就会沿着原型链向上查找,直到找到该属性或者到达原型链的顶端。
这种继承机制与传统的类继承有本质的不同。在基于类的继承中,对象是类的实例,继承关系在对象创建时就已经确定。而在JavaScript的原型继承中,对象可以动态地从其他对象继承属性,这种关系可以在运行时改变。
// 创建一个基础的交通工具对象
let vehicle = {
type: "交通工具" ,
speed: 0 ,
start : function () {
console. log ( `${ this . type }开始运行` );
this .speed = 10 ;
},
stop : function () {
console. log ( `${ this .
原型链的查找过程是自动进行的,但了解这个过程有助于我们更好地理解JavaScript的工作机制。当我们访问对象的属性时,JavaScript引擎会按照以下顺序进行查找:
首先检查对象本身是否具有该属性。如果有,直接返回该属性的值。如果没有,则查找对象的原型对象。如果原型对象中有该属性,返回原型对象中该属性的值。如果原型对象中也没有,则继续查找原型对象的原型,如此递归,直到找到该属性或者到达原型链的顶端(通常是Object.prototype)。
// 演示原型链查找过程
let grandparent = {
familyName: "张" ,
tradition: "尊老爱幼"
};
let parent = Object. create (grandparent);
parent.profession = "教师" ;
parent.values = "诚实守信" ;
let child = Object. create (parent);
child.name = "张小明" ;
child.age = 10
原型链光速电梯
属性的操作
对象的属性操作是JavaScript编程中最基础也是最重要的技能之一。
常见的属性操作包括:获取属性值、设置属性值、修改已有属性、删除属性、检查属性是否存在等。我们可以通过点(.)或方括号([])两种语法来访问和操作属性。
点语法适用于属性名为合法标识符的情况,方括号语法则可以用变量或包含特殊字符的属性名。
此外,属性的存在性检查可以使用in运算符或hasOwnProperty方法。掌握这些操作方法,有助于我们灵活地处理对象数据结构,编写出健壮的JavaScript代码。
属性的读取和设置
JavaScript提供了两种主要的属性访问语法:点表示法和方括号表示法。点表示法语法简洁,适合属性名是有效标识符的情况。方括号表示法更加灵活,可以使用变量作为属性名,也可以处理包含特殊字符的属性名。
let employee = {
firstName: "李" ,
lastName: "明" ,
"work-location" : "北京" ,
age: 28 ,
department: "技术部"
};
// 使用点表示法读取属性
console. log (employee.firstName); // "李"
console. log (employee.age); // 28
// 使用方括号表示法读取属性
console. log (employee[ "lastName" ]); // "明"
在实际开发中,我们经常会遇到多层嵌套的对象结构。例如,一个用户信息对象可能包含个人信息、联系方式、偏好设置等子对象。当我们需要访问这些嵌套属性时,如果中间某一级属性不存在,
直接访问会导致运行时错误(如TypeError: Cannot read property 'xxx' of undefined)。
因此,安全地访问嵌套对象的属性非常重要。常见的做法是逐层判断每一级属性是否存在,只有在所有中间属性都存在的情况下,才去访问最深层的属性值。
这样可以有效避免因属性不存在而导致的程序崩溃。此外,现代JavaScript还提供了可选链(Optional Chaining)操作符(?.),可以更简洁地实现安全访问嵌套属性。
let userProfile = {
personal: {
name: "王小红" ,
contact: {
email: "wang.xiaohong@email.com" ,
phone: "13900139000"
}
},
preferences: {
theme: "dark" ,
language: "zh-CN"
}
};
// 安全的属性访问
let email = userProfile.personal &&
userProfile.personal.contact &&
属性的检测
在JavaScript编程中,判断一个对象是否拥有某个属性是非常常见的需求。常用的属性检测方法主要有以下几种:
in 操作符
用于检查属性名是否存在于对象中(包括对象自身的属性和原型链上的继承属性)。
例如:"name" in obj。如果属性存在(无论是自有还是继承),结果为 true。
hasOwnProperty() 方法
只判断对象自身(自有)的属性,不会检查原型链上的属性。
例如:obj.hasOwnProperty("name")。只有当 name 是对象自身的属性时才返回 true。
属性值与 undefined 比较
通过判断 obj.prop !== undefined 来检测属性是否存在,但这种方式无法区分“属性不存在”和“属性存在但值为 undefined”的情况。
typeof 检查属性类型
可以结合 typeof 判断属性类型,例如:typeof obj.prop === "string"。这种方式适合需要同时判断属性存在且类型正确的场景。
每种方法适用的场景不同。例如,如果需要判断属性是否被对象自身定义,推荐使用 hasOwnProperty();如果只关心属性是否可访问(包括继承),可以使用 in 操作符;如果需要判断属性类型,则可以结合 typeof 使用。实际开发中应根据具体需求选择合适的属性检测方式。
let library = {
name: "市立图书馆" ,
books: 50000 ,
isOpen: true ,
address: undefined
};
// 使用in操作符检查属性(包括继承的属性)
console. log ( "name" in library); // true
console. log ( "books" in library); // true
console. log ( "toString" in library); // true(继承自Object.prototype)
属性的删除
在JavaScript中,delete 操作符用于从对象上删除指定的属性。它的基本语法是 delete 对象.属性 或 delete 对象["属性名"]。需要注意以下几点:
delete 只能删除对象自身(自有)的属性,不能删除对象原型链上继承来的属性。例如,如果一个属性是从 Object.prototype 继承而来,使用 delete 并不会影响它。
如果要删除的属性不存在,delete 操作不会报错,而是返回 true。
某些属性(如使用 var 声明的全局变量,或通过 Object.defineProperty 设置了 configurable: false 的属性)是不可删除的。尝试删除这些属性时,delete 会返回 false,在严格模式下甚至会抛出错误。
delete 只能删除对象的属性,不能删除变量(如局部变量、函数参数等)。
删除数组的元素时,delete 只会将该元素置为 undefined,不会改变数组的长度。如果想真正移除数组元素,建议使用 Array.prototype.splice 方法。
示例:
let restaurant = {
name: "美味餐厅" ,
cuisine: "中式" ,
rating: 4.5 ,
"special-offer" : "周末八折" ,
location: "市中心"
};
// 删除属性
console. log ( "删除前:" , restaurant);
delete restaurant.rating;
delete restaurant[ "special-offer" ];
console. log ( "删除后:" , restaurant);
属性的枚举
属性枚举是遍历对象所有属性的过程,这在数据处理、对象分析和动态操作中非常重要。JavaScript提供了多种枚举属性的方法,每种方法都有其特定的行为和适用场景。
for...in循环是最传统的属性枚举方法,它会遍历对象的所有可枚举属性,包括继承的属性。这种方法简单直接,但在某些情况下可能会枚举到我们不希望处理的继承属性。
let smartphone = {
brand: "华为" ,
model: "P50" ,
price: 4999 ,
storage: "256GB" ,
color: "曜金黑"
};
// 基本的for...in枚举
console. log ( "手机规格:" );
for ( let property in smartphone) {
console. log ( `${ property }: ${ smartphone [
ES5引入了Object.keys()、Object.values()和Object.entries()等方法,为属性枚举提供了更现代、灵活且安全的方式。
Object.keys(obj) 返回一个包含对象自身所有可枚举属性名 的数组(不包括继承属性和不可枚举属性)。
Object.values(obj) 返回一个包含对象自身所有可枚举属性值 的数组。
Object.entries(obj) 返回一个包含对象自身所有可枚举属性的 [key, value] 对 的数组。
这些方法只会处理对象本身(即自有)的可枚举属性,不会遍历原型链上的继承属性 ,因此可以避免for...in循环中可能出现的继承属性干扰。
此外,这些方法返回的结果都是数组,非常适合与数组的高阶方法(如forEach、map、filter等)结合使用,便于数据处理和转换。
let studentRecord = {
studentId: "2023001" ,
name: "张三" ,
major: "计算机科学" ,
year: "大三" ,
gpa: 3.8 ,
credits: 90
};
// 使用Object.keys()获取属性名数组
let propertyNames = Object. keys (studentRecord);
console. log ( "属性名:" , propertyNames);
// 使用Object.values()获取属性值数组
let
在实际开发中,有些对象的属性可能被设置为不可枚举(enumerable: false),这意味着它们不会出现在 for...in 循环或 Object.keys() 的结果中。但有时我们需要获取对象的所有自有属性(包括那些不可枚举的),比如在调试、对象深拷贝或做底层分析时。此时可以使用 Object.getOwnPropertyNames() 方法。该方法会返回一个包含所有自有属性名称(无论是否可枚举)的数组,这样我们就能全面地了解对象的结构和内容。例如,如果一个对象有通过 Object.defineProperty 添加的不可枚举属性,Object.getOwnPropertyNames() 也能将其列出,而 Object.keys() 则不会。
// 创建一个带有不可枚举属性的对象
let secureConfig = {};
Object. defineProperty (secureConfig, 'apiKey' , {
value: 'secret-api-key-12345' ,
enumerable: false ,
writable: false
});
Object. defineProperty (secureConfig, 'version' , {
value: '1.0.0' ,
enumerable: true ,
writable: false
});
secureConfig.environment =
访问器属性
访问器属性(也称为getter和setter属性)是JavaScript对象系统中的一个强大特性。与普通的数据属性不同,访问器属性不直接存储值,而
是通过函数来定义属性的读取和设置行为。这种机制使我们能够在属性访问时执行自定义逻辑,实现数据验证、计算属性、属性监听等高级功能。
当我们读取访问器属性时,JavaScript会调用getter函数,该函数的返回值就是属性的值。
当我们设置访问器属性时,JavaScript会调用setter函数,并将赋值的内容作为参数传递给setter函数。这种设计让我们能够在保持简洁语法的同时,实现复杂的属性操作逻辑。
// 创建一个温度转换对象
let temperature = {
_celsius: 0 , // 内部存储摄氏度值
// getter:获取摄氏度
get celsius () {
return this ._celsius;
},
// setter:设置摄氏度
set celsius ( value ) {
if ( typeof value !== 'number' ) {
throw new Error ( '温度值必须是数字'
访问器属性(getter 和 setter)在实现数据封装和数据验证时非常有用。通过定义 getter 和 setter,我们可以让对象的属性像普通属性一样被访问和赋值,
但实际上在读取或设置属性值时,可以在背后执行额外的逻辑。例如,我们可以在 setter 中对传入的数据类型进行检查和验证,防止无效的数据被赋值;
也可以在 getter 中动态计算属性值或对外隐藏内部实现细节。这样既保证了接口的简洁易用,又增强了数据的安全性和灵活性。
// 创建一个银行账户对象
let bankAccount = {
_balance: 0 ,
_accountNumber: '' ,
_transactionHistory: [],
get balance () {
return this ._balance;
},
set balance ( amount ) {
throw new Error ( '不能直接设置余额,请使用存款或取款方法' );
},
get accountNumber
除了在对象字面量中直接定义 getter 和 setter 外,我们还可以通过 Object.defineProperty() 方法为已有对象动态添加访问器属性(getter/setter)。
这种方式非常适合在对象创建后,根据实际需求灵活地添加或修改属性的行为。例如,可以为对象增加只读属性、计算属性,或者在属性读取和设置时执行特定逻辑,从而实现更精细的属性控制和数据保护。
// 创建一个商品库存管理对象
let inventory = {
items: new Map (),
addItem ( id , name , quantity , price ) {
this .items. set (id, { name, quantity, price });
},
removeItem ( id ) {
this .items. delete (id);
}
};
// 为库存对象添加计算属性
属性特性与对象特性
在JavaScript中,每个属性都有一组特性(attributes)来控制其行为,每个对象也有一些特性来控制其整体行为。
理解这些特性对于编写高质量的JavaScript代码至关重要,特别是在需要精确控制对象行为或者创建库和框架时。
属性特性
每个对象属性都有一组“属性特性”(Property Attributes),它们决定了该属性的行为。对于数据属性 (即直接存储值的属性),有四个主要特性:
value(值) :属性的实际值。
writable(可写) :布尔值,表示属性的值是否可以被修改。
enumerable(可枚举) :布尔值,表示属性是否可以被枚举(如在for...in循环或Object.keys()中出现)。
configurable(可配置) :布尔值,表示属性的特性是否可以被更改,以及属性是否可以被删除。
对于访问器属性 (即通过getter/setter函数访问的属性),则有以下四个特性:
get :一个函数,在读取属性时被调用,返回属性值。
set :一个函数,在写入属性时被调用,用于设置属性值。
enumerable(可枚举) :同上,表示属性是否可被枚举。
configurable(可配置) :同上,表示属性的特性是否可被更改,属性是否可被删除。
这些特性共同决定了属性的可读写性、可见性以及能否被重新定义或删除。通过Object.defineProperty等方法可以精确地控制这些特性,从而实现更灵活和安全的对象设计。
let product = {};
// 定义一个普通的数据属性
Object. defineProperty (product, 'name' , {
value: '智能手表' ,
writable: true , // 可以修改值
enumerable: true , // 可以被枚举
configurable: true // 可以被重新配置
});
// 定义一个只读属性
Object. defineProperty (product, 'id' , {
value: 'WATCH001' ,
writable: false ,
我们可以使用 Object.getOwnPropertyDescriptor() 方法来详细查看某个属性的特性(如 value、writable、enumerable、configurable、get、set 等)。例如:
// 创建一个配置对象系统
function createConfig ( initialConfig ) {
let config = {};
// 定义多个配置属性
Object. defineProperties (config, {
// 应用名称(只读)
appName: {
value: initialConfig.appName || 'MyApp' ,
writable: false ,
enumerable: true ,
configurable: false
},
// 版本号(只读)
version: {
value: initialConfig.version
对象特性
除了属性特性外,对象本身也有三个重要特性:prototype(原型)、class(类)和extensible(可扩展性)。这些特性控制着对象的继承行为、类型标识和是否可以添加新属性。
// 演示对象的可扩展性控制
let userAccount = {
username: 'john_doe' ,
email: 'john@example.com' ,
level: 'basic'
};
// 检查对象是否可扩展
console. log ( '对象可扩展:' , Object. isExtensible (userAccount));
// 添加新属性
userAccount.lastLogin = new Date ();
console. log ( '添加属性后:' , userAccount);
// 使对象不可扩展
Object. preventExtensions
在实际开发中,这些特性控制功能在创建不可变数据结构、实现配置系统和确保数据安全方面非常有用:
// 创建一个不可变的配置系统
function createImmutableConfig ( config ) {
let immutableConfig = {};
// 深度冻结函数
function deepFreeze ( obj ) {
// 获取所有属性名
Object. getOwnPropertyNames (obj). forEach ( function ( prop ) {
let value = obj[prop];
// 如果属性值是对象且不为null,递归冻结
if (value && typeof
对象序列化
对象序列化是将JavaScript对象转换为字符串形式的过程,以便于存储、传输或持久化。反序列化则是将字符串重新转换为JavaScript对象的过程。
JSON(JavaScript Object Notation)是现代Web开发中最常用的序列化格式,JavaScript内置了JSON.stringify()和JSON.parse()方法来处理对象的序列化和反序列化。
JSON序列化虽然简单易用,但也有一些限制。它只能处理基本数据类型、对象和数组,不能处理函数、undefined、Symbol、Date对象会被转换为字符串,正则表达式会变成空对象。理解这些限制有助于我们更好地使用JSON序列化。
// 基本的JSON序列化示例
let studentData = {
id: 'STU001' ,
name: '李小明' ,
age: 20 ,
courses: [ '数学' , '物理' , '化学' ],
grades: {
math: 95 ,
physics: 87 ,
chemistry: 92
},
isActive: true ,
enrollmentDate: new Date (
为了解决JSON序列化的限制,我们可以使用自定义的序列化函数,或者利用JSON.stringify()和JSON.parse()的第二个参数来控制序列化过程:
// 自定义序列化函数
function customStringify ( obj ) {
return JSON . stringify (obj, function ( key , value ) {
// 处理函数
if ( typeof value === 'function' ) {
return value. toString ();
}
// 处理Date对象
if (value instanceof Date ) {
return
练习
现在让我们通过一些练习题来巩固本章学到的知识。每道题都涵盖了JavaScript对象的核心概念,请先尝试独立完成,然后再查看答案。
A. 对象字面量
B. 构造函数
C. Object.create()
D. 以上都可以
A. 点表示法
B. 方括号表示法
C. 两者都可以
D. 两者都不可以
A. 自有属性
B. 继承属性
C. 两者都包括
D. 都不包括
A. getter
B. setter
C. value
D. writable
A. Object.preventExtensions()
B. Object.seal()
C. Object.freeze()
D. 以上都可以
完成以下对象创建代码,添加getter和setter:
let person = {
name: "张三" ,
age: 25 ,
// 创建一个getter获取全名
get _______ () {
return this .firstName + this .lastName;
},
// 创建一个setter设置年龄
set _______ ( value ) {
if (value > 0 ) this ._age = value;
}
};
let person = {
firstName: "张" ,
lastName: "三" ,
_age: 25 , // 使用下划线前缀表示私有属性
// getter获取全名
get fullName () {
return this .firstName + this .lastName;
},
// setter设置年龄
set age ( value ) {
if (value > 0 ) {
填写对象属性操作代码:
let obj = { a: 1 , b: 2 };
// 检查属性是否存在
if (_______ in obj) {
console. log ( "属性a存在" );
}
// 删除属性
_______ obj.b;
// 枚举所有属性
for ( let key _______ obj) {
console. log (key);
}
let obj = { a: 1 , b: 2 };
// 检查属性是否存在
if ( "a" in obj) {
console. log ( "属性a存在" );
}
// 删除属性
delete obj.b;
// 枚举所有属性
for ( let key in obj) {
console. log (key); // 输出:a
}
in运算符用于检查对象是否有某个属性
delete运算符用于删除对象的属性
完成原型继承代码:
let vehicle = {
start : function () {
console. log ( "启动交通工具" );
}
};
// 创建继承自vehicle的car对象
let car = Object. _______ (_______);
car.type = "汽车" ;
car. honk = function () {
console. log ( "嘀嘀!" );
};
let vehicle = {
start : function () {
console. log ( "启动交通工具" );
}
};
// 创建继承自vehicle的car对象
let car = Object. create (vehicle);
car.type = "汽车" ;
car. honk = function () {
console. log ( "嘀嘀!" );
};
// 测试继承
填写属性描述符:
let obj = {};
Object. defineProperty (obj, "readOnly" , {
value: "不可修改" ,
_______: false , // 不可写
_______: true , // 可枚举
_______: false // 不可配置
});
let obj = {};
Object. defineProperty (obj, "readOnly" , {
value: "不可修改" ,
writable: false , // 不可写
enumerable: true , // 可枚举
configurable: false // 不可配置
});
console. log (obj.readOnly); // "不可修改"
obj.readOnly = "尝试修改" ; // 无效,因为writable为false
console. log (obj.readOnly); // 仍然是"不可修改"
编写一个学生管理系统,要求创建Student构造函数,包含姓名、年龄、专业属性,添加计算GPA的getter方法,添加课程管理方法:
<! DOCTYPE html >
< html lang = "zh-CN" >
< head >
< meta charset = "UTF-8" >
< title >学生管理系统</ title >
</ head >
< body >
< script >
// Student构造函数
function Student ( name , age
(
title
,
author
,
pages
) {
this .title = title;
this .author = author;
this .pages = pages;
this .isRead = false ;
this . markAsRead = function () {
this .isRead = true ;
console. log ( `《${ this . title }》已标记为已读` );
};
}
// 使用自定义构造函数创建对象
let book1 = new Book ( "JavaScript高级程序设计" , "Nicholas C. Zakas" , 800 );
let book2 = new Book ( "你不知道的JavaScript" , "Kyle Simpson" , 400 );
book1. markAsRead ();
create
(animalPrototype);
dog.species = "狗" ;
dog.breed = "拉布拉多" ;
dog. makeSound = function () {
console. log ( "汪汪汪!" );
};
let cat = Object. create (animalPrototype);
cat.species = "猫" ;
cat.color = "橘色" ;
cat. makeSound = function () {
console. log ( "喵喵喵!" );
};
// 创建一个没有原型的对象
let pureObject = Object. create ( null );
pureObject.data = "这个对象没有继承任何属性" ;
type
}停止运行`
);
this .speed = 0 ;
}
};
// 创建汽车对象,继承自交通工具
let car = Object. create (vehicle);
car.type = "汽车" ;
car.wheels = 4 ;
car. honk = function () {
console. log ( "嘀嘀!" );
};
// 创建自行车对象,也继承自交通工具
let bicycle = Object. create (vehicle);
bicycle.type = "自行车" ;
bicycle.wheels = 2 ;
bicycle. ring = function () {
console. log ( "叮铃铃!" );
};
// 测试继承
car. start (); // 汽车开始运行(继承的方法)
car. honk (); // 嘀嘀!(自有方法)
bicycle. start (); // 自行车开始运行(继承的方法)
bicycle. ring (); // 叮铃铃!(自有方法)
;
// 属性查找演示
console. log (child.name); // "张小明" - 自有属性
console. log (child.profession); // "教师" - 从父级继承
console. log (child.familyName); // "张" - 从祖父级继承
console. log (child.tradition); // "尊老爱幼" - 从祖父级继承
// 当我们修改属性时,只会影响当前对象
child.tradition = "创新进取" ;
console. log (child.tradition); // "创新进取" - 新值
console. log (parent.tradition); // "尊老爱幼" - 原始值不变
console. log (grandparent.tradition); // "尊老爱幼" - 原始值不变
console.
log
(employee[
"work-location"
]);
// "北京"
// 使用变量作为属性名
let propertyName = "department" ;
console. log (employee[propertyName]); // "技术部"
// 设置属性值
employee.salary = 8000 ; // 使用点表示法设置新属性
employee[ "start-date" ] = "2023-01-15" ; // 使用方括号表示法设置新属性
employee.age = 29 ; // 修改现有属性
// 动态属性操作
let fields = [ "email" , "phone" , "address" ];
let values = [ "li.ming@company.com" , "13800138000" , "北京市海淀区" ];
for ( let i = 0 ; i < fields. length ; i ++ ) {
employee[fields[i]] = values[i];
}
console. log (employee);
userProfile.personal.contact.email;
// 或者使用条件检查
if (userProfile.personal && userProfile.personal.contact) {
console. log ( "用户邮箱:" , userProfile.personal.contact.email);
} else {
console. log ( "邮箱信息不可用" );
}
// 现代JavaScript中可以使用可选链操作符(如果环境支持)
// let email = userProfile.personal?.contact?.email;
console. log ( "nonexistent" in library); // false
// 使用hasOwnProperty()检查自有属性
console. log (library. hasOwnProperty ( "name" )); // true
console. log (library. hasOwnProperty ( "books" )); // true
console. log (library. hasOwnProperty ( "toString" )); // false(继承的属性)
// 通过比较undefined来检查属性
console. log (library.name !== undefined ); // true
console. log (library.address !== undefined ); // false(属性存在但值为undefined)
console. log (library.nonexistent !== undefined ); // false(属性不存在)
// 使用typeof检查属性类型
if ( typeof library.books === "number" ) {
console. log ( `图书馆有${ library . books }本书` );
}
// 实际应用示例:配置对象的属性检查
function initializeSettings ( config ) {
let defaultSettings = {
theme: "light" ,
fontSize: 14 ,
autoSave: true ,
language: "zh-CN"
};
let settings = {};
// 合并配置,优先使用用户提供的配置
for ( let key in defaultSettings) {
if (config && config. hasOwnProperty (key)) {
settings[key] = config[key];
} else {
settings[key] = defaultSettings[key];
}
}
return settings;
}
let userConfig = { theme: "dark" , fontSize: 16 };
let finalSettings = initializeSettings (userConfig);
console. log (finalSettings);
// 检查删除结果
console. log ( "rating" in restaurant); // false
console. log ( "special-offer" in restaurant); // false
// 删除不存在的属性不会报错
delete restaurant.nonexistent; // 不报错,返回true
// 变量声明的属性通常不能删除
var globalVar = "不能删除" ;
delete globalVar; // 在非严格模式下返回false,严格模式下会报错
// 实际应用:清理用户数据
function sanitizeUserData ( userData ) {
let cleanData = Object. assign ({}, userData);
// 删除敏感信息
delete cleanData.password;
delete cleanData.creditCard;
delete cleanData.socialSecurityNumber;
// 删除空值属性
for ( let key in cleanData) {
if (cleanData[key] === null || cleanData[key] === undefined || cleanData[key] === "" ) {
delete cleanData[key];
}
}
return cleanData;
}
let rawUserData = {
username: "user123" ,
email: "user@example.com" ,
password: "secret123" ,
age: 25 ,
creditCard: "1234-5678-9012-3456" ,
phone: "" ,
address: null
};
let cleanUserData = sanitizeUserData (rawUserData);
console. log (cleanUserData);
property
]
}`
);
}
// 更实际的应用:对象数据的格式化输出
function displayProductInfo ( product ) {
let info = [];
for ( let key in product) {
if (product. hasOwnProperty (key)) {
let displayName = key. replace ( / ( [A-Z] ) / g , ' $1' ). toLowerCase ();
displayName = displayName. charAt ( 0 ). toUpperCase () + displayName. slice ( 1 );
info. push ( `${ displayName }: ${ product [ key ] }` );
}
}
return info. join ( ' \n ' );
}
let laptop = {
brand: "联想" ,
screenSize: "14英寸" ,
processor: "Intel i7" ,
ramSize: "16GB" ,
storageType: "SSD"
};
console. log ( displayProductInfo (laptop));
propertyValues
=
Object.
values
(studentRecord);
console. log ( "属性值:" , propertyValues);
// 使用Object.entries()获取[key, value]对数组
let propertyEntries = Object. entries (studentRecord);
console. log ( "属性条目:" , propertyEntries);
// 实际应用:数据统计分析
function analyzeStudentData ( students ) {
let analysis = {
totalStudents: students. length ,
averageGpa: 0 ,
majorDistribution: {},
yearDistribution: {}
};
let totalGpa = 0 ;
students. forEach ( student => {
// 计算平均GPA
if ( typeof student.gpa === 'number' ) {
totalGpa += student.gpa;
}
// 统计专业分布
if (student.major) {
analysis.majorDistribution[student.major] =
(analysis.majorDistribution[student.major] || 0 ) + 1 ;
}
// 统计年级分布
if (student.year) {
analysis.yearDistribution[student.year] =
(analysis.yearDistribution[student.year] || 0 ) + 1 ;
}
});
analysis.averageGpa = totalGpa / students. length ;
return analysis;
}
let studentList = [
{ name: "李四" , major: "计算机科学" , year: "大二" , gpa: 3.6 },
{ name: "王五" , major: "数学" , year: "大三" , gpa: 3.9 },
{ name: "赵六" , major: "计算机科学" , year: "大一" , gpa: 3.4 },
{ name: "钱七" , major: "物理" , year: "大二" , gpa: 3.7 }
];
console. log ( analyzeStudentData (studentList));
'production'
;
// 比较不同枚举方法的结果
console. log ( "Object.keys():" , Object. keys (secureConfig));
console. log ( "Object.getOwnPropertyNames():" , Object. getOwnPropertyNames (secureConfig));
// 实用工具:深度对象复制函数
function deepClone ( obj , visited = new WeakMap ()) {
// 处理基本类型和null
if (obj === null || typeof obj !== 'object' ) {
return obj;
}
// 处理循环引用
if (visited. has (obj)) {
return visited. get (obj);
}
// 处理数组
if (Array. isArray (obj)) {
let arrCopy = [];
visited. set (obj, arrCopy);
obj. forEach (( item , index ) => {
arrCopy[index] = deepClone (item, visited);
});
return arrCopy;
}
// 处理对象
let objCopy = {};
visited. set (obj, objCopy);
// 复制所有自有属性(包括不可枚举的)
Object. getOwnPropertyNames (obj). forEach ( key => {
objCopy[key] = deepClone (obj[key], visited);
});
return objCopy;
}
let originalData = {
name: "测试数据" ,
nested: {
value: 42 ,
array: [ 1 , 2 , { inner: "深层数据" }]
}
};
let clonedData = deepClone (originalData);
clonedData.nested.value = 100 ;
console. log ( "原始数据:" , originalData.nested.value); // 42
console. log ( "克隆数据:" , clonedData.nested.value); // 100
);
}
this ._celsius = value;
},
// getter:获取华氏度(计算属性)
get fahrenheit () {
return this ._celsius * 9 / 5 + 32 ;
},
// setter:通过华氏度设置温度
set fahrenheit ( value ) {
if ( typeof value !== 'number' ) {
throw new Error ( '温度值必须是数字' );
}
this ._celsius = (value - 32 ) * 5 / 9 ;
},
// getter:获取开尔文温度
get kelvin () {
return this ._celsius + 273.15 ;
},
// setter:通过开尔文温度设置
set kelvin ( value ) {
if ( typeof value !== 'number' ) {
throw new Error ( '温度值必须是数字' );
}
this ._celsius = value - 273.15 ;
}
};
// 使用访问器属性
temperature.celsius = 25 ;
console. log ( `摄氏度: ${ temperature . celsius }°C` );
console. log ( `华氏度: ${ temperature . fahrenheit }°F` );
console. log ( `开尔文: ${ temperature . kelvin }K` );
// 通过不同单位设置温度
temperature.fahrenheit = 86 ;
console. log ( `摄氏度: ${ temperature . celsius }°C` );
() {
return this ._accountNumber;
},
set accountNumber ( number ) {
if ( ! / ^ \d {10,12}$ / . test (number)) {
throw new Error ( '账号必须是10-12位数字' );
}
this ._accountNumber = number;
},
get transactionHistory () {
// 返回交易历史的副本,防止外部修改
return [ ... this ._transactionHistory];
},
// 存款方法
deposit ( amount ) {
if (amount <= 0 ) {
throw new Error ( '存款金额必须大于0' );
}
this ._balance += amount;
this ._transactionHistory. push ({
type: '存款' ,
amount: amount,
date: new Date (),
balance: this ._balance
});
},
// 取款方法
withdraw ( amount ) {
if (amount <= 0 ) {
throw new Error ( '取款金额必须大于0' );
}
if (amount > this ._balance) {
throw new Error ( '余额不足' );
}
this ._balance -= amount;
this ._transactionHistory. push ({
type: '取款' ,
amount: amount,
date: new Date (),
balance: this ._balance
});
}
};
// 使用银行账户对象
bankAccount.accountNumber = '1234567890' ;
bankAccount. deposit ( 1000 );
bankAccount. withdraw ( 300 );
console. log ( `账户余额: ${ bankAccount . balance }` );
console. log ( '交易历史:' , bankAccount.transactionHistory);
Object. defineProperty (inventory, 'totalValue' , {
get : function () {
let total = 0 ;
for ( let item of this .items. values ()) {
total += item.quantity * item.price;
}
return total;
},
enumerable: true
});
Object. defineProperty (inventory, 'itemCount' , {
get : function () {
return this .items.size;
},
enumerable: true
});
Object. defineProperty (inventory, 'totalQuantity' , {
get : function () {
let total = 0 ;
for ( let item of this .items. values ()) {
total += item.quantity;
}
return total;
},
enumerable: true
});
// 添加库存商品
inventory. addItem ( '001' , '笔记本电脑' , 50 , 5999 );
inventory. addItem ( '002' , '无线鼠标' , 200 , 99 );
inventory. addItem ( '003' , '机械键盘' , 80 , 299 );
console. log ( `商品种类: ${ inventory . itemCount }` );
console. log ( `总库存量: ${ inventory . totalQuantity }` );
console. log ( `库存总价值: ¥${ inventory . totalValue }` );
// 不可修改
enumerable: true ,
configurable: true
});
// 定义一个不可枚举的属性
Object. defineProperty (product, 'internalCode' , {
value: 'INTERNAL_WATCH_001' ,
writable: true ,
enumerable: false , // 不会在for...in中出现
configurable: true
});
// 定义一个访问器属性
Object. defineProperty (product, 'displayName' , {
get : function () {
return `${ this . brand || '未知品牌'} ${ this . name }` ;
},
set : function ( value ) {
// 解析显示名称
let parts = value. split ( ' ' );
if (parts. length >= 2 ) {
this .brand = parts[ 0 ];
this .name = parts. slice ( 1 ). join ( ' ' );
}
},
enumerable: true ,
configurable: true
});
// 测试属性特性
product.brand = 'Apple' ;
console. log (product.displayName); // "Apple 智能手表"
product.name = '智能手环' ; // 成功修改
console. log (product.name);
try {
product.id = 'WATCH002' ; // 尝试修改只读属性
} catch (error) {
console. log ( '无法修改只读属性' );
}
// 枚举测试
console. log ( '可枚举的属性:' );
for ( let key in product) {
console. log (key, product[key]);
}
console. log ( '所有自有属性:' , Object. getOwnPropertyNames (product));
||
'1.0.0'
,
writable: false ,
enumerable: true ,
configurable: false
},
// 调试模式(可读写)
debug: {
value: initialConfig.debug || false ,
writable: true ,
enumerable: true ,
configurable: true
},
// API端点(通过getter/setter验证)
apiEndpoint: {
get : function () {
return this ._apiEndpoint || 'https://api.example.com' ;
},
set : function ( value ) {
if ( typeof value === 'string' && value. startsWith ( 'https://' )) {
this ._apiEndpoint = value;
} else {
throw new Error ( 'API端点必须是有效的HTTPS URL' );
}
},
enumerable: true ,
configurable: true
},
// 内部设置(不可枚举)
_internalSettings: {
value: {},
writable: true ,
enumerable: false ,
configurable: false
}
});
return config;
}
let appConfig = createConfig ({
appName: '任务管理器' ,
version: '2.1.0' ,
debug: true
});
// 查看属性描述符
console. log ( 'appName描述符:' , Object. getOwnPropertyDescriptor (appConfig, 'appName' ));
console. log ( 'apiEndpoint描述符:' , Object. getOwnPropertyDescriptor (appConfig, 'apiEndpoint' ));
// 测试配置
appConfig.debug = false ;
appConfig.apiEndpoint = 'https://api.myapp.com/v2' ;
console. log ( '配置对象:' , appConfig);
(userAccount);
console. log ( '设置不可扩展后:' , Object. isExtensible (userAccount));
// 尝试添加新属性(在严格模式下会报错,非严格模式下静默失败)
userAccount.newProperty = 'test' ;
console. log ( '尝试添加新属性后:' , userAccount);
// 但仍然可以修改现有属性
userAccount.level = 'premium' ;
console. log ( '修改现有属性:' , userAccount.level);
// 对象密封:不可扩展 + 所有属性不可配置
let sealedObject = {
name: '密封对象' ,
value: 100
};
Object. seal (sealedObject);
console. log ( '对象已密封:' , Object. isSealed (sealedObject));
// 可以修改现有属性的值
sealedObject.value = 200 ;
console. log ( '修改密封对象的值:' , sealedObject.value);
// 但不能删除属性或修改属性特性
delete sealedObject.name; // 无效
console. log ( '尝试删除属性后:' , sealedObject);
// 对象冻结:密封 + 所有属性只读
let frozenObject = {
constant: '不变的值' ,
number: 42
};
Object. freeze (frozenObject);
console. log ( '对象已冻结:' , Object. isFrozen (frozenObject));
// 任何修改都无效
frozenObject.constant = '尝试修改' ;
frozenObject.number = 100 ;
console. log ( '尝试修改冻结对象后:' , frozenObject);
value
===
'object'
) {
deepFreeze (value);
}
});
return Object. freeze (obj);
}
// 复制配置到新对象
Object. keys (config). forEach ( key => {
if (config[key] && typeof config[key] === 'object' ) {
immutableConfig[key] = JSON . parse ( JSON . stringify (config[key]));
} else {
immutableConfig[key] = config[key];
}
});
// 添加一些有用的方法(这些方法本身也会被冻结)
immutableConfig. get = function ( path ) {
return path. split ( '.' ). reduce (( obj , key ) => obj && obj[key], this );
};
immutableConfig. has = function ( path ) {
return this . get (path) !== undefined ;
};
immutableConfig. toJSON = function () {
let result = {};
Object. keys ( this ). forEach ( key => {
if ( typeof this [key] !== 'function' ) {
result[key] = this [key];
}
});
return result;
};
return deepFreeze (immutableConfig);
}
// 使用不可变配置
let config = createImmutableConfig ({
database: {
host: 'localhost' ,
port: 5432 ,
name: 'myapp'
},
cache: {
ttl: 3600 ,
maxSize: 100
},
features: {
enableLogging: true ,
enableCache: false
}
});
console. log ( '数据库主机:' , config. get ( 'database.host' ));
console. log ( '缓存启用:' , config. has ( 'features.enableCache' ));
// 以下操作都无效
config.database.host = 'remote.server.com' ;
config.newFeature = true ;
delete config.cache;
console. log ( '配置对象保持不变:' , config. toJSON ());
'2023-09-01'
)
};
// 序列化对象
let jsonString = JSON . stringify (studentData);
console. log ( '序列化结果:' , jsonString);
// 反序列化对象
let parsedData = JSON . parse (jsonString);
console. log ( '反序列化结果:' , parsedData);
// 注意:Date对象被转换为字符串
console. log ( '原始日期类型:' , typeof studentData.enrollmentDate);
console. log ( '解析后日期类型:' , typeof parsedData.enrollmentDate);
// 处理JSON序列化的限制
let complexObject = {
name: '复杂对象' ,
number: 42 ,
text: '文本' ,
date: new Date (),
func : function () { return 'function' ; },
undef: undefined ,
nullValue: null ,
regex: / pattern / g ,
symbol: Symbol ( 'test' )
};
console. log ( '原始对象:' , complexObject);
console. log ( '序列化后:' , JSON . stringify (complexObject));
console. log ( '反序列化后:' , JSON . parse ( JSON . stringify (complexObject)));
{
__type: 'Date' ,
__value: value. toISOString ()
};
}
// 处理正则表达式
if (value instanceof RegExp ) {
return {
__type: 'RegExp' ,
__value: value. toString ()
};
}
// 处理undefined
if (value === undefined ) {
return {
__type: 'undefined'
};
}
return value;
});
}
// 自定义反序列化函数
function customParse ( jsonString ) {
return JSON . parse (jsonString, function ( key , value ) {
// 恢复特殊类型
if (value && typeof value === 'object' && value.__type) {
switch (value.__type) {
case 'Date' :
return new Date (value.__value);
case 'RegExp' :
let match = value.__value. match ( / \/ ( . *? ) \/ ( [gimuy] * ) $ / );
return new RegExp (match[ 1 ], match[ 2 ]);
case 'undefined' :
return undefined ;
}
}
return value;
});
}
// 测试自定义序列化
let testObject = {
name: '测试对象' ,
date: new Date (),
pattern: / test / gi ,
value: undefined ,
nested: {
number: 123 ,
date: new Date ( '2023-01-01' )
}
};
let customSerialized = customStringify (testObject);
console. log ( '自定义序列化:' , customSerialized);
let customDeserialized = customParse (customSerialized);
console. log ( '自定义反序列化:' , customDeserialized);
console. log ( '日期对象恢复:' , customDeserialized.date instanceof Date );
console. log ( '正则对象恢复:' , customDeserialized.pattern instanceof RegExp );
this ._age = value;
}
},
// getter获取年龄
get age () {
return this ._age;
}
};
console. log (person.fullName); // "张三"
person.age = 30 ;
console. log (person.age); // 30
person.age = - 5 ; // 无效,年龄不会改变
console. log (person.age); // 30
getter和setter是访问器属性,允许在读取或设置属性时执行自定义逻辑。getter使用get关键字定义,setter使用set关键字定义。在这个例子中,setter会验证年龄是否为正数。
for...in循环用于遍历对象的所有可枚举属性car. start (); // "启动交通工具"(继承自vehicle)
car. honk (); // "嘀嘀!"(car自己的方法)
Object.create()方法创建一个新对象,使用第一个参数作为新对象的原型。这样car对象就继承了vehicle的所有属性和方法。
// 可以枚举
for ( let key in obj) {
console. log (key); // "readOnly"
}
// 不能删除,因为configurable为false
delete obj.readOnly; // 无效
writable:控制属性值是否可写
enumerable:控制属性是否可枚举
configurable:控制属性是否可配置(删除、修改描述符等)
,
major
) {
this .name = name;
this .age = age;
this .major = major;
this .courses = []; // 课程数组
this .scores = {}; // 成绩对象
}
// 添加课程
Student . prototype . addCourse = function ( courseName , score ) {
this .courses. push (courseName);
this .scores[courseName] = score;
};
// 删除课程
Student . prototype . removeCourse = function ( courseName ) {
let index = this .courses. indexOf (courseName);
if (index > - 1 ) {
this .courses. splice (index, 1 );
delete this .scores[courseName];
}
};
// 计算GPA的getter
Object. defineProperty ( Student . prototype , "gpa" , {
get : function () {
if ( this .courses. length === 0 ) {
return 0 ;
}
let total = 0 ;
for ( let course of this .courses) {
total += this .scores[course];
}
return (total / this .courses. length ). toFixed ( 2 );
},
enumerable: true ,
configurable: true
});
// 序列化
Student . prototype . toJSON = function () {
return {
name: this .name,
age: this .age,
major: this .major,
courses: this .courses,
scores: this .scores
};
};
// 使用示例
let student = new Student ( "张三" , 20 , "计算机科学" );
student. addCourse ( "数学" , 90 );
student. addCourse ( "英语" , 85 );
student. addCourse ( "编程" , 95 );
console. log ( "学生:" + student.name);
console. log ( "GPA:" + student.gpa);
// 序列化
let json = JSON . stringify (student);
console. log ( "序列化结果:" + json);
// 反序列化
let data = JSON . parse (json);
let student2 = new Student (data.name, data.age, data.major);
for ( let course of data.courses) {
student2. addCourse (course, data.scores[course]);
}
console. log ( "反序列化后的GPA:" + student2.gpa);
</ script >
</ body >
</ html >
使用构造函数创建对象
使用原型添加方法
使用getter计算属性
实现序列化和反序列化
对象的方法和属性管理