原型链继承
首先来看下原型和原型链的区别 原型(Prototype)
原型是JavaScript中实现对象继承的核心概念之一。每个JavaScript对象(除了null
)都与一个原型对象相关联,这个原型对象通过内部属性[[Prototype]]
(在旧版JavaScript中可通过__proto__
属性访问)与对象建立联系。原型对象本身也是普通的JavaScript对象,它可以有自己的属性和方法。
当试图访问一个对象的属性或方法时,JavaScript引擎首先在该对象自身的属性中查找。如果在当前对象上找不到该属性或方法,引擎会沿着[[Prototype]]
链向上查找,直到找到该属性或方法,或者查找到null
(即到达原型链的顶端)。这种基于原型的属性和方法查找机制就是所谓的"原型链"。
原型链(Prototype Chain)
原型链是通过对象的[[Prototype]]
属性形成的对象间的连接链条。当一个对象的属性或方法在自身定义中未找到时,JavaScript引擎会自动到其原型对象中去查找。如果原型对象还有自己的原型(即[[Prototype]]
),那么查找过程会进一步延续到原型的原型,直到找到所需的属性或方法,或者查找到原型链的尽头(null
)。
原型与原型链的区别
原型和原型链虽然密切相关,但它们描述的是对象继承机制的不同方面:
-
原型:特指一个对象直接关联的那个原型对象,它是对象继承属性和方法的直接来源。原型本身是一个普通的JavaScript对象,可以定义属性、方法,也可以有自己的原型(即形成原型链)。
-
原型链 :是由一个对象与其原型、原型的原型、直至
null
形成的链式结构。原型链是实现对象属性和方法继承的查找路径,它体现了对象间继承关系的层级结构。
简单来说,原型 是个体对象,是原型链中的一个节点;原型链 则是由这些原型节点通过[[Prototype]]
链接起来的整体结构。理解原型和原型链的区别有助于更好地掌握JavaScript的面向对象编程和继承机制。
上图来自[JS]深入理解原型和原型链以及区别(包含面试题详解) - 掘金 (juejin.cn)
javascript
// 1. 定义父类(基类):首先创建一个基础构造函数,如Person,并在其原型(Person.prototype)上定义共享的方法和属性。
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHello = function () {
console.log(`Hello, my name is ${this.name}, and I'm ${this.age} years old.`)
}
// 2. 创建子类:定义一个新的构造函数,如Student,并使其原型对象指向父类的一个实例。这样,子类实例就可以通过原型链访问到父类的属性和方法。
function Student(name, age, major) {
Person.call(name, age)
this.major = major
}
Student.prototype = Object.create(Person.prototype)
// 修复子类的constructor属性:由于我们直接将子类的原型设置为父类实例,子类的constructor属性不再指向Student,而是指向Person。为了修复这个问题,通常会手动设置回来:
Student.prototype.constructor = Student
// 添加子类特有的方法:在子类的原型上添加专属于子类的方法,这些方法不会影响到父类或其他继承自同一父类的子类。
Student.prototype.sayMajor = function () {
console.log(`My major is ${this.major}.`)
}
// 使用示例
const student = new Student("Alice", 20, "Computer Science");
student.sayHello(); // 输出:Hello, my name is Alice, and I'm 20 years old. student.sayMajor(); // 输出:My major is Computer Science.
Class继承
在JavaScript中,从ES6(ECMAScript 6)开始引入了基于类(Class)的面向对象编程模型,使得创建和继承类变得更加直观和简洁。以下是如何使用class
关键字实现类继承的示例:
javascript
// 定义父类(基类)
class ParentClass {
constructor(param1, param2) {
this.property1 = param1
this.property2 = param2
}
method1() {
console.log('ParentClass.method1 called')
}
}
// 定义子类
class ChildClass extends ParentClass {
constructor(param1, param2, childParam) {
super(param1, param2) // 调用父类构造函数,传递必要的参数
this.childProperty = childParam
}
// 继承父类方法并进行覆盖(重写)
method1() {
console.log('ChildClass.method1 called')
super.method1() // 调用父类的method1方法
}
// 定义子类特有的方法
childMethod() {
console.log('ChildClass.childMethod called')
}
}
// 使用子类
const childInstance = new ChildClass('value1', 'value2', 'childValue')
childInstance.method1() // 输出:ChildClass.method1 called
// ParentClass.method1 called
childInstance.childMethod() // 输出:ChildClass.childMethod called
- 使用
class
关键字定义类,如class ParentClass
和class ChildClass
。 - 在父类(
ParentClass
)中定义构造函数(constructor
),用于初始化实例属性。同时,可以定义类的方法(如method1
)。 - 子类(
ChildClass
)通过extends
关键字继承父类。在子类构造函数中,使用super
关键字调用父类构造函数,以继承父类的属性。 - 子类可以覆盖(重写)从父类继承的方法,如
method1
。在覆盖的方法内部,可以使用super.methodName()
调用父类的同名方法。 - 子类可以定义自己的特有方法,如
childMethod
。 - 通过
new ChildClass(...)
创建子类实例,并使用实例调用其方法。
通过这种方式,JavaScript的类继承机制实现了代码复用、层次化的对象结构以及多态等面向对象编程特性。需要注意的是,尽管JavaScript使用了class
关键字,但其底层仍然是基于原型链的继承机制。类语法提供了一种更易于理解和使用的封装,简化了面向对象编程的实现。