Appearance
JavaScript 对象与原型
JavaScript是一种基于原型的面向对象语言,对象是其核心概念之一。理解对象和原型机制对于掌握JavaScript至关重要。
对象创建
JavaScript提供了多种创建对象的方式:
对象字面量
最常用的创建对象方式,简洁明了。
javascript
const person = {
name: 'John',
age: 30,
greet() {
return `Hello, I'm ${this.name}`;
},
// ES6简写方法
introduce: function() {
return `Hi, I'm ${this.name} and I'm ${this.age} years old.`;
}
};
构造函数
使用函数作为模板创建对象。
javascript
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function() {
return `Hello, I'm ${this.name}`;
};
}
const john = new Person('John', 30);
Object构造函数
javascript
const person = new Object();
person.name = 'John';
person.age = 30;
person.greet = function() {
return `Hello, I'm ${this.name}`;
};
Object.create()
直接指定原型创建对象。
javascript
const personPrototype = {
greet() {
return `Hello, I'm ${this.name}`;
},
introduce() {
return `Hi, I'm ${this.name} and I'm ${this.age} years old.`;
}
};
const john = Object.create(personPrototype);
john.name = 'John';
john.age = 30;
// 或者指定属性描述符
const jane = Object.create(personPrototype, {
name: { value: 'Jane', writable: true, enumerable: true },
age: { value: 25, writable: true, enumerable: true }
});
ES6 类(语法糖)
ES6引入的类语法,本质上仍是基于原型的。
javascript
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, I'm ${this.name}`;
}
static species() {
return 'Homo sapiens';
}
}
const john = new Person('John', 30);
原型链
原型链是JavaScript实现继承的核心机制。
原型概念
- 每个JavaScript对象都有一个指向其原型对象的内部链接
- 原型对象也有自己的原型,直到到达Object.prototype
- 查找属性时,JavaScript会沿着原型链向上查找
javascript
function Animal(name) {
this.name = name;
}
// 在原型上添加方法
Animal.prototype.speak = function() {
console.log(`${this.name} makes a noise.`);
};
const dog = new Animal('Rex');
dog.speak(); // 'Rex makes a noise.'
// 原型链关系
console.log(dog.__proto__ === Animal.prototype); // true
console.log(Animal.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
原型链继承
构造函数继承
javascript
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a noise.`);
};
function Dog(name, breed) {
Animal.call(this, name); // 调用父构造函数
this.breed = breed;
}
// 设置原型链
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
// 添加Dog特有的方法
Dog.prototype.bark = function() {
console.log(`${this.name} barks.`);
};
const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.speak(); // 'Buddy makes a noise.' (继承自Animal)
myDog.bark(); // 'Buddy barks.' (Dog自己的方法)
// 检查实例关系
console.log(myDog instanceof Dog); // true
console.log(myDog instanceof Animal); // true
ES6 类继承
javascript
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
bark() {
console.log(`${this.name} barks.`);
}
// 重写父类方法
speak() {
super.speak(); // 调用父类方法
this.bark();
}
}
const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.speak(); // 'Buddy makes a noise.' \n 'Buddy barks.'
对象属性操作
属性访问
javascript
const obj = { name: 'John', age: 30 };
// 点表示法
console.log(obj.name); // 'John'
// 括号表示法(可计算属性名)
console.log(obj['name']); // 'John'
const propName = 'age';
console.log(obj[propName]); // 30
属性描述符
每个属性都有描述符,定义其特性:
javascript
const obj = {};
Object.defineProperty(obj, 'name', {
value: 'John',
writable: true, // 是否可修改
enumerable: true, // 是否可枚举(for...in循环)
configurable: true // 是否可配置(删除、修改描述符)
});
// 获取属性描述符
const descriptor = Object.getOwnPropertyDescriptor(obj, 'name');
console.log(descriptor);
属性类型
数据属性
包含一个值的属性,有四个特性:
- [[Value]]:属性的值
- [[Writable]]:是否可修改
- [[Enumerable]]:是否可枚举
- [[Configurable]]:是否可配置
访问器属性
不包含值,而是包含getter和setter函数:
javascript
const person = {
firstName: 'John',
lastName: 'Doe',
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(name) {
const parts = name.split(' ');
this.firstName = parts[0];
this.lastName = parts[1];
}
};
console.log(person.fullName); // 'John Doe'
person.fullName = 'Jane Smith';
console.log(person.firstName); // 'Jane'
console.log(person.lastName); // 'Smith'
对象方法
属性检测
javascript
const obj = { name: 'John', age: 30 };
// 检测自有属性
console.log(obj.hasOwnProperty('name')); // true
// 检测属性是否存在(包括原型链)
console.log('name' in obj); // true
console.log('toString' in obj); // true (继承自Object.prototype)
// 检测是否为自有属性(而非继承属性)
console.log(obj.propertyIsEnumerable('name')); // true
对象遍历
javascript
const obj = { a: 1, b: 2, c: 3 };
// for...in 循环(遍历所有可枚举属性,包括继承的)
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key, obj[key]);
}
}
// Object.keys() - 获取所有自有可枚举属性名
console.log(Object.keys(obj)); // ['a', 'b', 'c']
// Object.values() - 获取所有自有可枚举属性值
console.log(Object.values(obj)); // [1, 2, 3]
// Object.entries() - 获取所有自有可枚举属性的键值对数组
console.log(Object.entries(obj)); // [['a', 1], ['b', 2], ['c', 3]]
// Object.getOwnPropertyNames() - 获取所有自有属性名(包括不可枚举的)
console.log(Object.getOwnPropertyNames(obj)); // ['a', 'b', 'c']
对象复制
javascript
const original = { a: 1, b: { c: 2 } };
// 浅拷贝
const shallow1 = Object.assign({}, original);
const shallow2 = { ...original }; // ES6扩展运算符
// 深拷贝(简单实现)
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof Array) return obj.map(item => deepClone(item));
if (typeof obj === 'object') {
const cloned = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
}
// 使用JSON方法进行深拷贝(有局限性)
const deep = JSON.parse(JSON.stringify(original));
// 注意:此方法不能处理函数、undefined、Symbol、循环引用等
解构赋值
ES6引入的解构赋值语法,可以从数组或对象中提取值:
对象解构
javascript
const person = { name: 'John', age: 30, city: 'New York' };
// 基本解构
const { name, age } = person;
console.log(name, age); // 'John', 30
// 重命名
const { name: personName, age: personAge } = person;
console.log(personName, personAge); // 'John', 30
// 默认值
const { name, age, country = 'USA' } = person;
console.log(country); // 'USA'
// 嵌套解构
const user = {
name: 'John',
address: {
city: 'New York',
zip: '10001'
}
};
const { address: { city, zip } } = user;
console.log(city, zip); // 'New York', '10001'
// 函数参数解构
function displayUser({ name, age, city = 'Unknown' }) {
console.log(`Name: ${name}, Age: ${age}, City: ${city}`);
}
displayUser(person); // 'Name: John, Age: 30, City: New York'
数组解构
javascript
const numbers = [1, 2, 3, 4, 5];
// 基本解构
const [first, second, third] = numbers;
console.log(first, second, third); // 1, 2, 3
// 跳过元素
const [a, , c] = numbers; // 跳过第二个元素
console.log(a, c); // 1, 3
// 剩余元素
const [x, y, ...rest] = numbers;
console.log(x, y, rest); // 1, 2, [3, 4, 5]
// 默认值
const [p, q, r = 10] = [1, 2];
console.log(r); // 10
对象增强特性(ES6+)
计算属性名
javascript
const nameKey = 'name';
const ageKey = 'age';
const obj = {
[nameKey]: 'John',
[ageKey]: 30,
[nameKey + 'Full']: 'John Doe' // 'nameFull': 'John Doe'
};
方法简写
javascript
const obj = {
// 简写方法
greet() {
return `Hello!`;
},
// 与getter/setter结合
get currentYear() {
return new Date().getFullYear();
},
set currentYear(year) {
console.log(`Setting year to ${year}`);
}
};
Object.assign() 和扩展运算符
javascript
// Object.assign() - 合并对象
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const merged = Object.assign({}, obj1, obj2);
// 扩展运算符 - 更简洁的合并方式
const spreadMerged = { ...obj1, ...obj2 };
特殊对象类型
Map
键值对集合,键可以是任意类型:
javascript
const map = new Map();
map.set('name', 'John');
map.set(1, 'number one');
map.set({}, 'empty object');
console.log(map.get('name')); // 'John'
console.log(map.size); // 3
Set
唯一值的集合:
javascript
const set = new Set([1, 2, 3, 3, 2, 1]); // [1, 2, 3]
set.add(4);
console.log(set.has(4)); // true
console.log(set.size); // 4
WeakMap 和 WeakSet
弱引用集合,用于防止内存泄漏:
javascript
const weakMap = new WeakMap();
const objKey = {};
weakMap.set(objKey, 'value');
// 当objKey不再被其他地方引用时,它可以从内存中被清理
const weakSet = new WeakSet();
const obj = {};
weakSet.add(obj);