How to implement JavaScript inheritance: 1. Use the construction prototype pattern; 2. Use dynamic prototypes. According to object-oriented design principles, all members of the type should be encapsulated in the class structure; 3. Use the factory pattern ;4. Use class inheritance, that is, call the parent class constructor in the subclass.
The operating environment of this tutorial: windows7 system, javascript version 1.8.5, Dell G3 computer.
JavaScript is an object-oriented development model based on objects, functions as models, and prototypes as inheritance. This section details how to define JavaScript types and common patterns for implementing inheritance.
Constructing prototypes
There are two problems in directly using prototype prototypes to design class inheritance.
Since the constructor is declared in advance and the prototype attributes are defined after the class structure is declared, parameters cannot be dynamically passed to the prototype through the constructor. In this way, the instantiated objects all look the same and have no personality. To change prototype property values, all instances will be affected.
When the value of the prototype attribute is reference type data, if the attribute value is modified in one object instance, it will affect all instances.
Example 1
Simply define the Book type and then instantiate it.
function Book () {}; //声明构造函数 Book.prototype.o = {x : 1, y : 2}; //构造函数的原型属性o是一个对象 var book1 = new Book (); //实例化对象book1 var book2 = new Book (); //实例化对象book2 console.log(book1.o.x); //返回1 console.log(book2.o.x); //返回1 book2.o.x = 3; //修改实例化对象book2中的属性x的值 console.log(book1.o.x); //返回3 console.log(book2.o.x); //返回3
Since the prototype attribute o is a reference value, the value of attribute o of all instances is a reference to the same object. Once the value of o changes, it will affect all instances.
Construction prototype is a hybrid design pattern that was born to solve the prototype pattern. It mixes the constructor pattern and the prototype pattern to avoid the above problems.
Implementation method: For prototype properties that may affect each other and properties that want to dynamically pass parameters, you can design them independently using the constructor pattern. For methods or attributes that do not require individual design and have common characteristics, you can use the prototype pattern to design.
Example 2
Follow the above design principles, design two of the properties as constructor mode, and the design method is prototype mode.
function Book (title, pages) { //构造函数模式设计 this.title = title; this.pages = pages; } Book.prototype.what = function () { //原型模式设计 console.log(this.title + this.pages); }; var book1 = new Book("JavaScript 程序设计", 160); var book2 = new Book("C语言程序设计", 240); console.log(book1.title); console.log(book2.title);
The Constructed Prototype pattern is the recommended standard for defining classes in ECMAScript. It is generally recommended to use the constructor pattern to define all properties and the prototype pattern to define all methods. This way all methods are created only once, and each instance can set property values as needed. This is also the most widely used design pattern.
Dynamic prototype
According to object-oriented design principles, all members of a type should be encapsulated in a class structure. For example:
function Book (title, pages) { //构造函数模式设计 this.title = title; this.pages = pages; Book.prototype.what = function () { //原型模式设计,位于类的内部 console.log(this.title + this.pages); }; }
But every time it is instantiated, the prototype methods contained in the Book class will be repeatedly created, generating a large number of prototype methods and wasting system resources. You can use if to determine whether the prototype method exists. If it exists, the method will not be created. Otherwise, the method will be created.
function Book (title, pages) { this.title = title; this.pages = pages; if (typeof Book.isLock == "undefined") { //创建原型方法的锁,如果不存在则创建 Book.prototype.what = function () { console.log(this.title + this.pages); }; Book.isLock = true; //创建原型方法后,把锁锁上,避免重复创建 } } var book1 = new Book("JavaScript 程序设计", 160); var book2 = new Book("C语言程序设计", 240); console.log(book1.title); console.log(book2.title);
typeof Book.isLock expression can detect the type of the attribute value. If it returns an undefined string, the attribute value does not exist, indicating that no prototype method has been created, and allows the creation of a prototype method to set this attribute. The value is true, so that there is no need to repeatedly create prototype methods. The class name Book is used here instead of this because the prototype belongs to the class itself, not the object instance.
Dynamic prototype mode and constructed prototype mode are equivalent in performance, users can choose freely, but constructed prototype mode is more widely used.
Factory pattern
Factory pattern is the most basic method of defining types and is also the most commonly used development pattern in JavaScript. It simply encapsulates object instantiation in a function, and then calls the function to achieve rapid and batch production of instance objects.
Example 1
The following example designs a Car type: it contains three attributes: car color, number of driving wheels, and fuel consumption per 100 kilometers, and also defines a method to display car color.
function Car (color, drive, oil) { //汽车类 var _car = new Object(); //临时对象 _car.color = color; //初始化颜色 _car.drive = drive; //初始化驱动轮数 _car.oil = oil; //初始化百公里油耗 _car.showColor = function () { //方法,提示汽车颜色 console.log(this.color); }; return _car; //返回实例 } var car1 = Car("red", 4, 8); var car2 = Car("blue", 2, 6); car1.showColor(); //输出“red” car2.showColor(); //输出“blue”
The above code is a simple factory pattern type. Using the Car class, you can quickly create multiple car instances. Their structures are the same, but their attributes are different. They can initialize different colors, number of driving wheels, and fuel consumption per 100 kilometers. .
Example 2
In a type, a method is a behavior or operation that can complete a specific task based on initialization parameters and has common characteristics. Therefore, you can consider placing the method outside the Car() function to avoid creating a function every time it is instantiated and let each instance share the same function.
function showColor () { //公共方法,提示汽车颜色 console.log(this.color); }; function Car (color, drive, oil) { //汽车类 var _car = new Object(); //临时对象 _car.color = color; //初始化颜色 _car.drive = drive; //初始化驱动轮数 _car.oil = oil; //初始化百公里油耗 _car.showColor = showColor; //引用外部函数 return _car; //返回实例 }
In the above rewritten code, the function showColor() is defined before the function Car(). Inside Car(), by referencing the external showColor() function, we avoid having to create a new function every time it is instantiated. Functionally, this solves the problem of repeatedly creating a function; but semantically, the function is less like an object method.
Class inheritance
The design method of class inheritance: call the parent class constructor in the subclass.
在 JavaScript 中实现类继承,需要注意以下 3 个技术问题。
在子类中,使用 apply 调用父类,把子类构造函数的参数传递给父类父类构造函数。让子类继承父类的私有属性,即 Parent.apply(this, arguments); 代码行。
在父类和子类之间建立原型链,即 Sub.prototype = new Parent(); 代码行。通过这种方式保证父类和子类是原型链上的上下级关系,即子类的 prototype 指向父类的一个实例。
恢复子类的原型对象的构造函数,即 Sub.prototype.constructor=Sub;语句行。当改动 prototype 原型时,就会破坏原来的 constructor 指针,所以必须重置 constructor。
示例1
下面示例演示了一个三重继承的案例,包括基类、父类和子类,它们逐级继承。
//基类Base function Base (x) { //构造函数Base this.get = function () { //私有方法,获取参数值 return x; } } Base.prototype.has = function () { //原型方法,判断get()方法返回值是否为0 return ! (this.get() == 0); } //父类Parent function Parent () { //构造函数Parent var a = []; //私有数组a a = Array.apply(a, arguments); //把参数转换为数组 Base.call(this, a.length); //调用Base类,并把参数数组长度传递给它 this.add = function () { //私有方法,把参数数组补加到数组a中并返回 return a.push.apply(a, arguments) } this.geta = function () { //私有方法,返回数组a return a; } } Parent.prototype = new Base(); //设置Parent原型为Base的实例,建立原型链 Parent.prototype.constructor = Parent; //恢复Parent类原型对象的构造器 Parent.prototype.str = function (){ //原型方法,把数组转换为字符串并返回 return this.geta().toString(); } //子类Sub function Sub () { //构造函数 Parent.apply(this, arguments); //调用Parent类,并把参数数组长度传递给它 this.sort = function () { //私有方法,以字符顺序对数组进行排序 var a = this.geta(); //获取数组的值 a.sort.apply(a, arguments); //调用数组排序方法 sort()对数组进行排序 } } Sub.prototype = new Parent(); //设置Sub原型为Parent实例,建立原型链 Sub.prototype.constructor = Sub; //恢复Sub类原型对象的构造器 //父类Parent的实例继承类Base的成员 var parent = new Parent (1, 2, 3, 4); //实例化Parent类 console.log(parent.get()); //返回4,调用Base类的方法get() console.log(parent.has()); //返回true,调用Base类的方法has() //子类Sub的实例继承类Parent和类Base的成员 var sub = new Sub (30, 10, 20, 40); //实例化Sub类 sub.add(6, 5); //调用Parent类方法add(),补加数组 console.log(sub.geta()); //返回数组30,10,20,40,6,5 sub.sort(); //排序数组 console.log(sub.geta()); //返回数组10,20,30,40,5,6 console.log(sub.get()); //返回4,调用Base类的方法get() console.log(sub.has()); //返回true,调用Base类的方法has() console.log(sub.str()); //返回10,20,30,40,5,6
【设计思路】
设计子类 Sub 继承父类 Parent,而父类 Parent 又继承基类 Base。Base、Parent、Sub 三个类之间的继承关系是通过在子类中调用的构造函数来维护的。
例如,在 Sub 类中,Parent.apply(this, arguments); 能够在子类中调用父类,并把子类的参数传递给父类,从而使子类拥有父类的所有属性。
同理,在父类中,Base.call(this, a.length); 把父类的参数长度作为值传递给基类,并进行调用,从而实现父类拥有基类的所有成员。
从继承关系上看,父类继承了基类的私有方法 get(),为了确保能够继承基类的原型方法,还需要为它们建立原型链,从而实现原型对象的继承关系,方法是添加语句行 Parent.prototype=new Base();。
同理,在子类中添加语句 Sub.prototype=new Parent();,这样通过原型链就可以把基类、父类和子类串连在一起,从而实现子类能够继承父类属性,还可以继承基类的属性。
示例2
下面尝试把类继承模式封装起来,以便规范代码应用。
function extend (Sub, Sup) { //类继承封装函数 var F = function () {}; //定义一个空函数 F.prototype = Sup.prototype; //设置空函数的原型为父类的原型 Sub.prototype = new F (); //实例化空函数,并把父类原型引用传给给子类 Sub.prototype.constructor = Sub; //恢复子类原型的构造器为子类自身 Sub.sup = Sup.prototype; //在子类定义一个私有属性存储父类原型 //检测父类原型构造器是否为自身 if (Sup.prototype.constructor == Object.prototype.constructor) { Sup.prototype.constructor = Sup; //类继承封装函数 } }
【操作步骤】
1) 定义一个封装函数。设计入口为子类和父类对象,函数功能是子类能够继承父类的所有原型成员,不涉及出口。
function extend (Sub, Sup) { //类继承封装函数 //其中参数Sub表示子类,Sup表示父类 }
2) 在函数体内,首先定义一个空函数 F,用来实现功能中转。设计它的原型为父类的原型,然后把空函数的实例传递给子类的原型,这样就避免了直接实例化父类可能带来的系统负荷。因为在实际开发中,父类的规模可能会很大,如果实例化,会占用大量内存。
3) 恢复子类原型的构造器为子类自己。同时,检测父类原型构造器是否与 Object 的原型构造器发生耦合。如果是,则恢复它的构造器为父类自身。
下面定义两个类,尝试把它们绑定为继承关系。
function A (x) { //构造函数A this.x = x; //私有属性x this.get = function () { //私有方法get() return this.x; } } A.prototype.add = function () { //原型方法add() return this.x + this.x; } A.prototype.mul = function () { //原型方法mul() return this.x * this.x; } function B (x) { //构造函数B A.call (this.x); //在函数体内调用构造函数A,实现内部数据绑定 } extend (B, A); //调用封装函数,把A和B的原型捆绑在一起 var f = new B (5); //实例化类B console.log(f.get()); //继承类A的方法get(),返回5 console.log(f.add()); //继承类A的方法add(),返回10 console.log(f.mul()); //继承类A的方法mul(),返回25
在函数类封装函数中,有这么一句 Sub.sup=Sup.prototype;,在上面代码中没有被利用,那么它有什么作用呢?为了解答这个问题,先看下面的代码。
extend (B, A); B.prototype.add = function () { //为B类定义一个原型方法 return this.x + "" + this.x; }
上面的代码是在调用封装函数之后,再为 B 类定义了一个原型方法,该方法名与基类中原型方法 add() 同名,但是功能不同。如果此时测试程序,会发现子类 B 定义的原型方法 add() 将会覆盖父类 A 的原型方法 add()。
console.log(f.add()); //返回字符串55,而不是数值10
如果在 B 类的原型方法 add() 中调用父类的原型方法 add(),避免代码耦合现象发生。
B.prototype.add = function () { //定义子类B的原型方法add() return B.sup.add.call(this); //在函数内部调用父类方法add() }
【相关推荐:javascript学习教程】
The above is the detailed content of What are the implementation methods of JavaScript inheritance?. For more information, please follow other related articles on the PHP Chinese website!