如果我要用一种清晰简洁的方式解释什么是面向对象编程(OOP),那大概就是这样的:面向对象编程(OOP)是一种编程范式,它关注的是创建可重用的对象来模拟现实世界中的实体或概念。在 OOP 中,主要构建块是 对象——这些自我包含的单元封装了数据(称为属性或特性)以及作用于这些数据的函数(称为方法)。
面向对象编程与过程化编程有何不同?
与 OOP 不同,过程化编程侧重于编写独立处理数据的函数,遵循一步一步的方法。在过程化编程中,函数与其使用的数据是分离的,而 OOP 则围绕着既包含数据又包含方法的对象展开。这种在过程化编程中的分离可能导致函数依赖外部数据,而 OOP 的封装则通过将它们组合在一起提供了更好的数据保护。随着程序的增长,维护过程化程序可能变得更为困难,而 OOP 的结构通过在对象内部隔离变化简化了大型程序的管理。
OOP 的核心概念
以下是 OOP 在 JavaScript 中的一些核心概念及其简要说明:
- 类:创建对象的蓝图,定义了对象的属性和方法。
- 对象:类的实例,代表现实世界的实体,包含数据(属性)和行为(方法)。
- 继承:一个机制,允许一个类从另一个类继承属性和方法,促进代码复用。
- 多态:不同对象能够以不同的方式响应相同的方法调用的能力,通常通过方法覆盖实现。
- 封装:将数据和方法捆绑在一个对象内,限制对对象某些组件的直接访问。
- 抽象:隐藏复杂细节的过程,只展示必要的特征,简化对象的交互。
这样解释保持了简洁性,同时传达了关键思想。本文中将逐一讨论每个概念。
JavaScript 中的对象与原型
在 JavaScript 中,对象是用来表示现实世界实体或抽象的核心数据结构之一。每个对象都是属性和方法的集合。JavaScript 使用了一种 基于原型的继承模型,意味着对象可以从其他对象继承属性和方法,而不是像基于类的语言那样从类继承。
JavaScript 中的对象
JavaScript 中的对象就是一个简单的键值对集合,其中键是 属性(数据)和 方法(函数)。
下面是如何创建一个简单对象的例子:
const car = {
brand: 'Toyota',
model: 'Corolla',
start: function() {
console.log(`${
this.brand} ${
this.model} is starting...`);
}
};
car.start(); // 输出: Toyota Corolla is starting...
JavaScript 中的原型
原型是对象之间相互继承特性的底层机制。每个对象都有一个原型,形成了一个原型链。当一个对象被创建时,它会从其原型继承属性和方法,这条链会一直延续到原型为 null 的原型为止。
例子:
const myObject = {
city: "New York",
greet() {
console.log(`Hello from ${
this.city}`);
},
};
Object.getPrototypeOf(myObject); // Object { city: "New York", greet: [Function] }
原型链
const myObject = {
city: "LA",
greet() {
console.log(`Greetings from ${
this.city}`);
},
};
myObject.greet(); // Greetings from LA
这是一个具有 city
属性和 greet()
方法的对象。如果你在控制台中输入对象的名字后面加一个点号,比如 myObject.
,控制台会弹出该对象所有可用属性的列表。你会看到 city
和 greet
。
由于原型本身也是一个对象,它也会有自己的原型。这样就形成了一个原型链。当你试图访问一个对象的属性时:如果在对象本身找不到该属性,就会在原型中搜索该属性。如果仍然找不到,那么就会搜索原型的原型,依此类推,直到找到属性或者到达链的末端,在这种情况下会返回 undefined
。
所以当我们调用 myObject.toString()
时,浏览器:
- 会在
myObject
中查找toString
- 如果在那里找不到,就会在其原型对象中查找
toString
- 在那里找到了,然后调用它。
现在,我们如何找出某个对象的原型呢?我们可以使用 Object.getPrototypeOf()
函数。
Object.getPrototypeOf(myObject)
如果你在一个对象中定义了一个属性,而该对象的原型中也有一个同名的属性会发生什么?
当你向一个对象添加一个与其原型中已有的属性同名的新属性时,新属性会 遮蔽 或 替换 原型中的那个属性,但这仅限于那个特定的对象。对象中的属性将优先于原型中的属性被使用,JavaScript 将不再沿原型链向上查找。
使用 Object.create 设置原型
Object.create()
是 JavaScript 中的一个强大方法,它允许你创建一个新的对象并指定其原型。它直接将新创建的对象链接到另一个对象,使新对象可以通过 原型继承 访问原型对象的属性和方法。
它是如何工作的:
当你使用 Object.create()
时,你创建了一个新的对象,并将其原型设置为一个现有的对象。这意味着新对象可以通过原型继承访问原型对象的属性和方法。
例子:
const animal = {
makeSound: function() {
console.log(this.sound);
}
};
const dog = Object.create(animal); // dog 继承自 animal
dog.sound = 'Bark';
dog.makeSound(); // 输出: Bark
向原型添加方法的例子:
const car = {
start: function() {
console.log(`${
this.model} is starting...`);
}
};
const myCar = Object.create(car);
myCar.model = 'Tesla Model S';
myCar.start(); // 输出: Tesla Model S is starting...
构造函数
构造函数 是一种特殊的函数,用于在 JavaScript 中创建和初始化对象。在 ES6 引入类之前,构造函数是创建具有相同结构和行为的多个对象的主要方式。
构造函数有助于以一种可重复且共享的方式来设置对象的属性和方法。它们的工作原理如下:
- 定义对象的初始属性。
- 使用
new
关键字调用构造函数以创建新的对象实例。
如何定义构造函数
构造函数只是一个普通的函数,但按照惯例,它的名称首字母大写,以此区别于普通函数。
function Person(name, age) {
// 为正在创建的对象分配属性
this.name = name;
this.age = age;
// 你也可以在这里定义方法
this.greet = function() {
console.log(`你好,我的名字是 ${
this.name}`);
};
}
需要注意的一点是,你 不能 使用箭头函数来定义构造函数。因为箭头函数没有自己的 this
上下文或 prototype
,所以不适合用作构造函数。
如何使用构造函数
要使用构造函数创建一个对象,你需要使用 new
关键字:
const person1 = new Person('Alice', 25);
const person2 = new Person('Bob', 30);
person1.greet(); // 输出: 你好,我的名字是 Alice
person2