1. Object inheritance mechanism
This example uses UML to explain the inheritance mechanism very well.
The simplest way to illustrate the inheritance mechanism is to use a classic example of geometric shapes. In fact, there are only two types of geometric shapes, ellipses (which are circular) and polygons (which have a certain number of sides). A circle is a type of ellipse that has only one focus. Triangles, rectangles, and pentagons are all types of polygons with varying numbers of sides. A square is a type of rectangle with all sides of the same length. This constitutes a perfect inheritance relationship and explains the object-oriented inheritance mechanism well.
In this example, the shape is the base class of ellipse and polygon (usually we can also call it the parent class, and all classes inherit from it). The ellipse has a foci, which indicates the number of foci the ellipse has. Circle inherits from ellipse, so circle is a subclass of ellipse and ellipse is the superclass of circle. Likewise, triangles, rectangles, and pentagons are all subclasses of polygon, which is their superclass. Finally, the square inherits the rectangle.
It is best to use a diagram to explain this inheritance relationship, which is where UML (Unified Modeling Language) comes in. One of the main uses of UML is to visually represent complex object relationships like inheritance. The following diagram is a UML diagram explaining the relationship between a shape and its subclasses:
In UML, each box represents a class, described by the class name. The line segments at the top of triangles, rectangles, and pentagons come together to point to shapes, indicating that these classes inherit from shapes. Likewise, the arrow pointing from the square to the rectangle illustrates the inheritance relationship between them.
2. Implementation of ECMAScript inheritance mechanism
To implement the inheritance mechanism with ECMAScript, you can start with the base class you want to inherit from. All developer-defined classes can serve as base classes. For security reasons, native and host classes cannot serve as base classes. This prevents public access to compiled browser-level code that could be used for malicious attacks.
After selecting the base class, you can create its subclasses. It's entirely up to you whether to use a base class or not. Sometimes, you may want to create a base class that cannot be used directly, but is only used to provide common functions to subclasses. In this case, the base class is considered an abstract class. Although ECMAScript does not define abstract classes as strictly as other languages, it does sometimes create classes that are not allowed to be used. Usually, we call this class an abstract class.
The created subclass will inherit all properties and methods of the superclass, including the implementation of constructors and methods. Remember, all properties and methods are public, so subclasses can access these methods directly. Subclasses can also add new properties and methods that are not found in the superclass, or they can override properties and methods of the superclass. Since JS is not an orthodox object-oriented language, some nouns also need to be changed.
3. ECMAScript inheritance methods
In the ECMAScript language, the inherited class (base class) is called a supertype, and the subclass (or derived class) is called a subtype. As with other features, ECMAScript implements inheritance in more than one way. This is because the inheritance mechanism in JavaScript is not explicitly specified, but implemented through imitation. This means that not all inheritance details are entirely handled by the interpreter. As the developer, you have the right to decide the inheritance method that works best for you. Here are some specific inheritance methods for you.
(1) Prototype chain method
This form of inheritance was originally used for the prototype chain in ECMAScript. The previous blog post has introduced the prototype method of creating objects. Prototype chains extend this approach to implement the inheritance mechanism in an interesting way. The prototype object is a template, and the objects to be instantiated are based on this template. In summary, any properties and methods of the prototype object are passed to all instances of that class. The prototype chain utilizes this functionality to implement the inheritance mechanism. Let’s look at an example:
function A() {//超类型A中必须没有参数 this.color = "red"; this.showColor = function () { return this.color; }; }; function B() {//子类型B this.name = "John"; this.showName = function () { return this.name; }; }; B.prototype = new A();//子类型B继承了超类型A,通过原型,形成链条 var a = new A(); var b = new B(); document.write(a.showColor());//输出:blue document.write(b.showColor());//输出:red document.write(b.showName());//输出:John
In the prototype chain, the instanceof operator also operates in a unique way. For all instances of B, instanceof returns true for both A and B. This is an extremely useful tool in the weakly typed world of ECMAScript, but it cannot be used when using object impersonation. For example:
var b = new B(); document.write(b instanceof A);//输出:true document.write(b instanceof B);//输出:true
使用原型链方式实现了继承,但是这种方式无法共享和子类型给超类型传递参数。我们可以借用构造函数方式(也就是对像冒充)的方式来解决这两个问题。
(2)对象冒充方式
对象冒充方式的其原理如下:构造函数使用this关键字给所有属性和方法赋值(即采用对象声明的构造函数方式)。因为构造函数只是一个函数,所以可使A构造函数成为B的方法,然后调用它。B就会收到A的构造函数中定义的属性和方法。例如,用下面的方式改写上面的例子创建对象A和B:
call()方法
function A(Color) {//创建超类型A this.color = Color; this.showColor = function () { return this.color; }; }; function B(Color,Name) {//创建子类型B A.call(this, Color);//对象冒充,给超类型传参 this.name = Name;//新添加的属性 this.showName = }; var a = new A("blue"); var b = new B("red", "John"); document.write(a.showColor());//输出:blue document.write(b.showColor());//输出:red document.write(b.showName());//输出:John
apply()方法
和上面call()方法唯一的区别就是在子类型B中的代码:
A.call(this,arguments);//对象冒充,给超类型传参
当然,只有超类型中的参数顺序与子类型中的参数顺序完全一致时才可以传递参数对象。如果不是,就必须创建一个单独的数组,按照正确的顺序放置参数。
使用对象冒充方式虽然解决了共享和传参的问题,但是没有原型,复用就更不可能了,所以我们组合上述的两种方式,即原型链方式和对象冒充的方式实现JS的继承。
(3)混合方式
这种继承方式使用构造函数定义类,并非使用任何原型。对象冒充的主要问题是必须使用构造函数方式,这不是最好的选择。不过如果使用原型链,就无法使用带参数的构造函数了。开发者如何选择呢?答案很简单,两者都用。由于这种混合方式使用了原型链,所以instanceof运算符仍能正确运行。
在上一篇文章,创建对象的最好方式是用构造函数定义属性,用原型定义方法。这种方式同样适用于继承机制,用对象冒充继承构造函数的属性,用原型链继承prototype对象的方法。用这两种方式重写前面的例子,代码如下:
function A(Color) { this.color = Color; }; A.prototype.showColor = function () { return this.color; }; function B(Color, Name) { A.call(this, Color);//对象冒充 this.name = Name; }; B.prototype = new A();//使用原型链继承 B.prototype.showName = function () { return this.name; }; var a = new A("blue"); var b = new B("red", "John"); document.write(a.showColor());//输出:blue document.write(b.showColor());//输出:red document.write(b.showName());//输出:John
继承的方式和创建对象的方式有一定的联系,推荐使用的继承方式还时原型链和对象冒充的混合方式。使用这种混合方式可以避免一些不必要的问题。
看这篇文章的时候,必须看一下前面的创建对象的方式:详解JavaScript基于面向对象之创建对象(1)和详解JavaScript基于面向对象之创建对象(2)。
以上就是本文的全部内容,希望对大家的学习有所帮助。