Appearance
对象
对象是 JavaScript 中最核心的数据类型之一,用于存储键值对集合。本章将详细介绍对象的创建、属性操作、this 关键字以及面向对象编程。
创建对象
对象字面量
javascript
// 使用对象字面量创建对象(最常用)
let person = {
name: '张三',
age: 25,
city: '北京',
sayHello: function() {
console.log('你好,我是' + this.name);
}
};
// 访问属性
console.log(person.name); // 张三
console.log(person['age']); // 25
// 调用方法
person.sayHello(); // 你好,我是张三
// ES6 增强的对象字面量
let name = '李四';
let age = 30;
// 属性简写
let user = {
name, // 等同于 name: name
age, // 等同于 age: age
// 方法简写
greet() {
console.log(`你好,我是${this.name}`);
},
// 计算属性名
['full' + 'Name']: '李四三'
};
console.log(user.name); // 李四
console.log(user.fullName); // 李四三
user.greet(); // 你好,我是李四new Object()
javascript
// 使用 new Object() 创建对象
let person = new Object();
person.name = '张三';
person.age = 25;
person.greet = function() {
console.log('你好');
};
console.log(person.name); // 张三
// 等价于
let person2 = {
name: '张三',
age: 25,
greet: function() {
console.log('你好');
}
};构造函数
javascript
// 使用构造函数创建对象
function Person(name, age, city) {
// this 指向新创建的对象
this.name = name;
this.age = age;
this.city = city;
this.sayHello = function() {
console.log('你好,我是' + this.name);
};
}
// 使用 new 关键字创建实例
let person1 = new Person('张三', 25, '北京');
let person2 = new Person('李四', 30, '上海');
console.log(person1.name); // 张三
console.log(person2.city); // 上海
person1.sayHello(); // 你好,我是张三
// 构造函数的执行过程:
// 1. 创建一个空对象
// 2. 将 this 指向这个空对象
// 3. 执行函数体内的代码
// 4. 返回 this(新对象)
// 检查是否是某个构造函数的实例
console.log(person1 instanceof Person); // true
console.log(person1 instanceof Object); // true
// 构造函数的 prototype 属性
Person.prototype.nationality = '中国';
console.log(person1.nationality); // 中国
console.log(person2.nationality); // 中国Object.create()
javascript
// Object.create() 创建对象,指定原型
let personProto = {
greet: function() {
console.log('你好,我是' + this.name);
},
getInfo: function() {
return `${this.name}, ${this.age}岁`;
}
};
// 创建以 personProto 为原型的对象
let person1 = Object.create(personProto);
person1.name = '张三';
person1.age = 25;
person1.greet(); // 你好,我是张三
console.log(person1.getInfo()); // 张三, 25岁
// 使用属性描述符
let person2 = Object.create(personProto, {
name: {
value: '李四',
writable: true,
enumerable: true,
configurable: true
},
age: {
value: 30,
writable: true,
enumerable: true,
configurable: true
}
});
// 创建没有原型的对象
let obj = Object.create(null);
console.log(obj.toString); // undefined(没有继承任何属性)类(ES6)
javascript
// ES6 类语法
class Person {
// 构造函数
constructor(name, age) {
this.name = name;
this.age = age;
}
// 实例方法
greet() {
console.log(`你好,我是${this.name}`);
}
getInfo() {
return `${this.name}, ${this.age}岁`;
}
// 静态方法
static create(name, age) {
return new Person(name, age);
}
// getter
get info() {
return `${this.name} (${this.age}岁)`;
}
// setter
set info(value) {
const [name, age] = value.split(',');
this.name = name;
this.age = parseInt(age);
}
}
let person = new Person('张三', 25);
person.greet(); // 你好,我是张三
console.log(person.info); // 张三 (25岁)
person.info = '李四,30';
console.log(person.name); // 李四
console.log(person.age); // 30
// 静态方法调用
let person2 = Person.create('王五', 35);
console.log(person2.name); // 王五
// 继承
class Student extends Person {
constructor(name, age, grade) {
super(name, age); // 调用父类构造函数
this.grade = grade;
}
// 重写方法
getInfo() {
return `${super.getInfo()}, ${this.grade}年级`;
}
// 新方法
study() {
console.log(`${this.name}正在学习`);
}
}
let student = new Student('小明', 12, 6);
student.greet(); // 你好,我是小明
student.study(); // 小明正在学习
console.log(student.getInfo()); // 小明, 12岁, 6年级属性操作
访问属性
javascript
let person = {
name: '张三',
age: 25,
'full-name': '张三三'
};
// 点表示法
console.log(person.name); // 张三
console.log(person.age); // 25
// 方括号表示法
console.log(person['name']); // 张三
console.log(person['full-name']); // 张三三(特殊属性名必须用方括号)
// 动态属性名
let key = 'name';
console.log(person[key]); // 张三
// 访问不存在的属性返回 undefined
console.log(person.city); // undefined
// 嵌套对象
let user = {
name: '张三',
address: {
city: '北京',
district: '朝阳'
}
};
console.log(user.address.city); // 北京
console.log(user['address']['city']); // 北京
// 可选链(ES2020)
console.log(user.contact?.phone); // undefined(不会报错)
console.log(user.address?.street?.name); // undefined添加和修改属性
javascript
let person = {
name: '张三'
};
// 添加属性
person.age = 25;
person['city'] = '北京';
console.log(person); // { name: '张三', age: 25, city: '北京' }
// 修改属性
person.name = '李四';
person['age'] = 30;
console.log(person.name); // 李四
console.log(person.age); // 30
// 添加方法
person.greet = function() {
console.log('你好');
};
person.greet(); // 你好删除属性
javascript
let person = {
name: '张三',
age: 25,
city: '北京'
};
// 删除属性
delete person.city;
console.log(person); // { name: '张三', age: 25 }
// 删除不存在的属性返回 true
console.log(delete person.country); // true
// 删除成功返回 true
console.log(delete person.age); // true
console.log(person); // { name: '张三' }
// 无法删除 var 声明的全局变量
var globalVar = '全局';
console.log(delete globalVar); // false检查属性
javascript
let person = {
name: '张三',
age: 25
};
// in 运算符:检查属性是否存在(包括继承的)
console.log('name' in person); // true
console.log('city' in person); // false
console.log('toString' in person); // true(继承自 Object)
// hasOwnProperty():检查自身属性(不包括继承的)
console.log(person.hasOwnProperty('name')); // true
console.log(person.hasOwnProperty('toString')); // false
// propertyIsEnumerable():检查是否可枚举
console.log(person.propertyIsEnumerable('name')); // true
// 使用 undefined 检查(不推荐)
console.log(person.name !== undefined); // true
console.log(person.city !== undefined); // false
// 问题:如果属性值为 undefined,会误判
// Object.hasOwn() - ES2022(推荐)
console.log(Object.hasOwn(person, 'name')); // true
console.log(Object.hasOwn(person, 'city')); // false遍历属性
javascript
let person = {
name: '张三',
age: 25,
city: '北京'
};
// for...in 遍历可枚举属性(包括继承的)
for (let key in person) {
console.log(key, person[key]);
}
// name 张三
// age 25
// city 北京
// 只遍历自身属性
for (let key in person) {
if (person.hasOwnProperty(key)) {
console.log(key, person[key]);
}
}
// Object.keys():获取所有可枚举自身属性的键名
let keys = Object.keys(person);
console.log(keys); // ['name', 'age', 'city']
// Object.values():获取所有可枚举自身属性的值
let values = Object.values(person);
console.log(values); // ['张三', 25, '北京']
// Object.entries():获取所有可枚举自身属性的键值对
let entries = Object.entries(person);
console.log(entries); // [['name', '张三'], ['age', 25], ['city', '北京']]
// 遍历 entries
for (let [key, value] of Object.entries(person)) {
console.log(`${key}: ${value}`);
}
// Object.getOwnPropertyNames():获取所有自身属性名(包括不可枚举)
let allKeys = Object.getOwnPropertyNames(person);
console.log(allKeys); // ['name', 'age', 'city']
// Reflect.ownKeys():获取所有自身属性(包括 Symbol)
let allProps = Reflect.ownKeys(person);
console.log(allProps);属性描述符
javascript
// 属性描述符:定义属性的特性
let person = {};
// Object.defineProperty() 定义属性
Object.defineProperty(person, 'name', {
value: '张三', // 属性值
writable: true, // 是否可写
enumerable: true, // 是否可枚举
configurable: true // 是否可配置(可删除、可修改描述符)
});
console.log(person.name); // 张三
// 不可写属性
Object.defineProperty(person, 'id', {
value: 1001,
writable: false, // 不可修改
enumerable: true,
configurable: false // 不可删除、不可重新配置
});
person.id = 1002; // 严格模式下报错
console.log(person.id); // 1001(未改变)
// delete person.id; // 删除失败
// 不可枚举属性
Object.defineProperty(person, 'secret', {
value: '密码',
enumerable: false
});
console.log(person.secret); // 密码
console.log(Object.keys(person)); // ['name', 'id'](不包含 secret)
// 获取属性描述符
let descriptor = Object.getOwnPropertyDescriptor(person, 'name');
console.log(descriptor);
// { value: '张三', writable: true, enumerable: true, configurable: true }
// 定义多个属性
Object.defineProperties(person, {
age: {
value: 25,
writable: true,
enumerable: true
},
city: {
value: '北京',
writable: true,
enumerable: true
}
});
console.log(person.age); // 25
console.log(person.city); // 北京Getter 和 Setter
javascript
// getter 和 setter
let person = {
firstName: '张',
lastName: '三',
// getter:读取属性时调用
get fullName() {
return this.firstName + this.lastName;
},
// setter:设置属性时调用
set fullName(value) {
this.firstName = value[0];
this.lastName = value.slice(1);
}
};
console.log(person.fullName); // 张三
person.fullName = '李四四';
console.log(person.firstName); // 李
console.log(person.lastName); // 四四
// 使用 defineProperty 定义 getter/setter
let user = {
_age: 0 // 私有属性约定
};
Object.defineProperty(user, 'age', {
get() {
console.log('读取 age');
return this._age;
},
set(value) {
console.log('设置 age');
if (value < 0) {
console.log('年龄不能为负数');
return;
}
this._age = value;
},
enumerable: true,
configurable: true
});
user.age = 25; // 设置 age
console.log(user.age); // 读取 age,25
user.age = -5; // 设置 age,年龄不能为负数this 关键字
this 的指向
javascript
// this 的值取决于函数的调用方式
// 1. 全局上下文中的 this
console.log(this); // window(浏览器中)
console.log(this === window); // true
// 2. 普通函数中的 this
function showThis() {
console.log(this);
}
showThis(); // window(非严格模式)
// 严格模式下是 undefined
// 3. 对象方法中的 this
let person = {
name: '张三',
greet: function() {
console.log('你好,我是' + this.name);
console.log(this === person); // true
}
};
person.greet(); // 你好,我是张三
// 4. 构造函数中的 this
function Person(name) {
this.name = name; // this 指向新创建的对象
}
let p = new Person('张三');
console.log(p.name); // 张三
// 5. 事件处理函数中的 this
// document.getElementById('btn').addEventListener('click', function() {
// console.log(this); // 触发事件的元素
// });
// 6. 箭头函数中的 this
let obj = {
name: '张三',
greet: function() {
// 箭头函数没有自己的 this,继承外层作用域的 this
let arrowGreet = () => {
console.log('你好,我是' + this.name);
};
arrowGreet(); // 你好,我是张三
}
};
obj.greet();改变 this 指向
javascript
let person1 = {
name: '张三',
greet: function(greeting, punctuation) {
console.log(greeting + ', 我是' + this.name + punctuation);
}
};
let person2 = {
name: '李四'
};
// call():立即调用函数,this 指向第一个参数
person1.greet.call(person2, '你好', '!'); // 你好, 我是李四!
// apply():与 call 类似,但参数是数组
person1.greet.apply(person2, ['你好', '!']); // 你好, 我是李四!
// bind():返回一个新函数,this 被永久绑定
let greetPerson2 = person1.greet.bind(person2);
greetPerson2('你好', '!'); // 你好, 我是李四!
// bind 可以预设参数
let greetP2WithHi = person1.greet.bind(person2, '嗨');
greetP2WithHi('~'); // 嗨, 我是李四~
// call、apply、bind 的区别
let obj = { name: '对象' };
function func(a, b, c) {
console.log(this.name, a, b, c);
}
func.call(obj, 1, 2, 3); // 对象 1 2 3
func.apply(obj, [1, 2, 3]); // 对象 1 2 3
func.bind(obj, 1, 2, 3)(); // 对象 1 2 3
// 实际应用:借用方法
let arrayLike = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
// 借用数组的 join 方法
let result = Array.prototype.join.call(arrayLike, '-');
console.log(result); // 'a-b-c'
// 借用数组的 slice 方法转换为数组
let arr = Array.prototype.slice.call(arrayLike);
console.log(arr); // ['a', 'b', 'c']原型和继承
原型链
javascript
// 每个对象都有一个原型对象
// 对象从原型继承属性和方法
let person = {
name: '张三'
};
// __proto__ 指向原型对象
console.log(person.__proto__); // Object.prototype
console.log(person.__proto__ === Object.prototype); // true
// 原型链
console.log(person.__proto__.__proto__); // null
// 原型对象也有原型,形成原型链
// 查找属性时沿着原型链向上查找
function Person(name) {
this.name = name;
}
// 在原型上添加方法
Person.prototype.greet = function() {
console.log('你好,我是' + this.name);
};
let person1 = new Person('张三');
let person2 = new Person('李四');
person1.greet(); // 你好,我是张三
person2.greet(); // 你好,我是李四
// 所有实例共享原型上的方法
console.log(person1.greet === person2.greet); // true
// 原型链
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
// 获取原型
console.log(Object.getPrototypeOf(person1) === Person.prototype); // true
// 设置原型
let newObj = {};
Object.setPrototypeOf(newObj, person1);
console.log(newObj.name); // 张三原型继承
javascript
// 原型继承方式 1:原型链继承
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(this.name + '在吃东西');
};
function Dog(name, breed) {
Animal.call(this, name); // 借用构造函数
this.breed = breed;
}
// 设置原型链
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修复 constructor
Dog.prototype.bark = function() {
console.log(this.name + '在叫');
};
let dog = new Dog('小黄', '金毛');
dog.eat(); // 小黄在吃东西
dog.bark(); // 小黄在叫
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
// 原型继承方式 2:组合继承
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 第二次调用 Parent
this.age = age;
}
Child.prototype = new Parent(); // 第一次调用 Parent
Child.prototype.constructor = Child;
// 原型继承方式 3:寄生组合继承(最优)
function inherit(Child, Parent) {
let prototype = Object.create(Parent.prototype);
prototype.constructor = Child;
Child.prototype = prototype;
}
function Parent2(name) {
this.name = name;
}
Parent2.prototype.sayName = function() {
console.log(this.name);
};
function Child2(name, age) {
Parent2.call(this, name);
this.age = age;
}
inherit(Child2, Parent2);对象方法
Object 静态方法
javascript
// Object.assign():合并对象
let target = { a: 1 };
let source = { b: 2, c: 3 };
let result = Object.assign(target, source);
console.log(result); // { a: 1, b: 2, c: 3 }
console.log(target); // { a: 1, b: 2, c: 3 }(target 被修改)
// 浅拷贝
let obj1 = { a: { b: 1 } };
let obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
console.log(obj2.a.b); // 2(受影响)
// 深拷贝
let deepCopy = JSON.parse(JSON.stringify(obj1));
// Object.freeze():冻结对象(不可修改、添加、删除)
let frozen = Object.freeze({ name: '张三' });
// frozen.name = '李四'; // 无效
// frozen.age = 25; // 无效
// delete frozen.name; // 无效
console.log(Object.isFrozen(frozen)); // true
// Object.seal():密封对象(不可添加、删除,但可修改)
let sealed = Object.seal({ name: '张三' });
sealed.name = '李四'; // 可以修改
// sealed.age = 25; // 无效
// delete sealed.name; // 无效
console.log(Object.isSealed(sealed)); // true
// Object.preventExtensions():禁止扩展(不可添加新属性)
let noExtend = Object.preventExtensions({ name: '张三' });
noExtend.name = '李四'; // 可以修改
delete noExtend.name; // 可以删除
// noExtend.age = 25; // 无效
console.log(Object.isExtensible(noExtend)); // false
// Object.fromEntries():键值对数组转对象 - ES2019
let entries = [['name', '张三'], ['age', 25]];
let obj = Object.fromEntries(entries);
console.log(obj); // { name: '张三', age: 25 }
// Map 转对象
let map = new Map([['a', 1], ['b', 2]]);
let objFromMap = Object.fromEntries(map);
console.log(objFromMap); // { a: 1, b: 2 }
// Object.is():比较两个值是否相等
console.log(Object.is(1, 1)); // true
console.log(Object.is(NaN, NaN)); // true(与 === 不同)
console.log(Object.is(-0, 0)); // false(与 === 不同)
console.log(Object.is({}, {})); // falseObject 实例方法
javascript
let obj = {
name: '张三',
age: 25
};
// hasOwnProperty():检查自身属性
console.log(obj.hasOwnProperty('name')); // true
console.log(obj.hasOwnProperty('toString')); // false
// isPrototypeOf():检查是否是另一个对象的原型
let parent = {};
let child = Object.create(parent);
console.log(parent.isPrototypeOf(child)); // true
// propertyIsEnumerable():检查属性是否可枚举
console.log(obj.propertyIsEnumerable('name')); // true
// toString():返回对象的字符串表示
console.log(obj.toString()); // [object Object]
// valueOf():返回对象的原始值
console.log(obj.valueOf()); // { name: '张三', age: 25 }
// toLocaleString():返回本地化字符串
let date = new Date();
console.log(date.toLocaleString());对象拷贝
浅拷贝
javascript
// 浅拷贝:只拷贝第一层
// 方法 1:Object.assign()
let obj1 = { a: 1, b: { c: 2 } };
let copy1 = Object.assign({}, obj1);
// 方法 2:展开运算符
let copy2 = { ...obj1 };
// 方法 3:for...in
let copy3 = {};
for (let key in obj1) {
if (obj1.hasOwnProperty(key)) {
copy3[key] = obj1[key];
}
}
// 浅拷贝的问题:嵌套对象是引用
obj1.b.c = 100;
console.log(copy1.b.c); // 100(受影响)深拷贝
javascript
// 深拷贝:递归拷贝所有层级
// 方法 1:JSON(简单但有局限)
let obj = { a: 1, b: { c: 2 } };
let deepCopy1 = JSON.parse(JSON.stringify(obj));
// 局限性:
// - 无法处理函数、undefined、Symbol
// - 无法处理循环引用
// - Date 变成字符串
// - RegExp 变成空对象
// 方法 2:递归实现
function deepClone(obj, hash = new WeakMap()) {
// 基本类型直接返回
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理循环引用
if (hash.has(obj)) {
return hash.get(obj);
}
// 处理特殊对象
if (obj instanceof Date) {
return new Date(obj);
}
if (obj instanceof RegExp) {
return new RegExp(obj);
}
if (obj instanceof Map) {
let copy = new Map();
hash.set(obj, copy);
obj.forEach((value, key) => {
copy.set(deepClone(key, hash), deepClone(value, hash));
});
return copy;
}
if (obj instanceof Set) {
let copy = new Set();
hash.set(obj, copy);
obj.forEach(value => {
copy.add(deepClone(value, hash));
});
return copy;
}
// 处理数组和对象
let copy = Array.isArray(obj) ? [] : {};
hash.set(obj, copy);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepClone(obj[key], hash);
}
}
// 处理 Symbol 属性
let symbolKeys = Object.getOwnPropertySymbols(obj);
for (let key of symbolKeys) {
copy[key] = deepClone(obj[key], hash);
}
return copy;
}
// 测试
let original = {
a: 1,
b: { c: 2 },
arr: [1, 2, 3],
date: new Date(),
regex: /test/g,
func: function() { return 'hello'; },
[Symbol('key')]: 'symbol value'
};
original.self = original; // 循环引用
let cloned = deepClone(original);
console.log(cloned);
console.log(cloned !== original); // true
console.log(cloned.b !== original.b); // true
console.log(cloned.self === cloned); // true(循环引用正确处理)
// 方法 3:structuredClone()(浏览器 API)
// let cloned2 = structuredClone(original);
// 支持循环引用,但不支持函数小结
本章学习了 JavaScript 对象的核心知识:
- 创建对象:对象字面量、构造函数、Object.create()、类
- 属性操作:访问、添加、修改、删除、检查、遍历
- 属性描述符:value、writable、enumerable、configurable
- Getter/Setter:自定义属性读写行为
- this 关键字:this 的指向和改变方法
- 原型和继承:原型链、原型继承方式
- 对象方法:静态方法、实例方法
- 对象拷贝:浅拷贝、深拷贝
下一章我们将学习 DOM操作,了解如何操作网页元素。
