Maison  >  Article  >  interface Web  >  Comment implémenter l'héritage en javascript

Comment implémenter l'héritage en javascript

青灯夜游
青灯夜游original
2021-06-30 18:00:503463parcourir

Javascript implémente des méthodes d'héritage : 1. Construire des prototypes et utiliser directement des prototypes pour concevoir l'héritage de classe ; 2. Utiliser des prototypes dynamiques pour implémenter l'héritage ; 3. Utiliser des modèles d'usine pour implémenter l'héritage ; dans les sous-classes Le constructeur de la classe parent est appelé dans la classe pour implémenter l'héritage.

Comment implémenter l'héritage en javascript

L'environnement d'exploitation de ce tutoriel : système Windows 7, JavaScript version 1.8.5, ordinateur Dell G3.

Plusieurs façons d'implémenter l'héritage dans JS

Construction de prototypes

Utiliser directement la conception de prototypes de prototypes Il y a deux problèmes avec l'héritage de classe.

Étant donné que le constructeur est déclaré à l'avance et que les propriétés du prototype sont définies après la déclaration de la structure de classe, les paramètres ne peuvent pas être transmis dynamiquement au prototype via le constructeur. De cette façon, les objets instanciés se ressemblent tous et n’ont aucune personnalité. Pour modifier les valeurs des propriétés du prototype, toutes les instances seront affectées.

Lorsque la valeur de l'attribut prototype est une donnée de type référence, si la valeur de l'attribut est modifiée dans une instance d'objet, cela affectera toutes les instances.

Exemple 1

Définissez simplement le type Book puis instanciez-le.

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

Puisque l'attribut prototype o est une valeur de référence, la valeur de l'attribut o de toutes les instances est une référence au même objet. Une fois la valeur de o modifiée, cela affectera toutes les instances.

Le prototype de construction est un modèle de conception hybride né pour résoudre le modèle de prototype. Il mélange le modèle de constructeur et le modèle de prototype pour éviter les problèmes ci-dessus.

Méthode d'implémentation : pour les attributs de prototype qui peuvent s'influencer mutuellement et les attributs qui souhaitent transmettre dynamiquement des paramètres, ils peuvent être conçus indépendamment à l'aide du modèle de constructeur. Pour les méthodes ou les attributs qui ne nécessitent pas de conception individuelle et qui présentent des caractéristiques communes, vous pouvez utiliser le modèle de prototype pour concevoir.

Exemple 2

Suivez les principes de conception ci-dessus, concevez deux des propriétés dans le modèle de constructeur et la méthode de conception est le modèle de prototype.

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);

Le modèle de prototype construit est la norme recommandée pour définir des classes dans ECMAScript. Il est généralement recommandé d'utiliser le modèle constructeur pour définir toutes les propriétés et le modèle prototype pour définir toutes les méthodes. De cette façon, toutes les méthodes ne sont créées qu'une seule fois et chaque instance peut définir les valeurs de propriété selon ses besoins. C’est également le modèle de conception le plus largement utilisé.

Prototype dynamique

Selon les principes de conception orientée objet, tous les membres d'un type doivent être encapsulés dans une structure de classe. Par exemple :

function Book (title, pages) {  //构造函数模式设计
    this.title = title;
    this.pages = pages;
    Book.prototype.what = function () {  //原型模式设计,位于类的内部
        console.log(this.title + this.pages);
    };
}

Mais à chaque fois qu'il est instancié, les méthodes prototypes contenues dans la classe Book seront créées à plusieurs reprises, générant un grand nombre de méthodes prototypes et gaspillant des ressources système. Vous pouvez utiliser if pour déterminer si la méthode prototype existe. Si elle existe, la méthode ne sera pas créée. L'expression

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 peut détecter le type de la valeur de l'attribut. Si elle renvoie une chaîne non définie, la valeur de l'attribut n'existe pas, indiquant qu'aucune méthode prototype n'a été créée, et permet la création d'un méthode prototype pour définir cet attribut. La valeur est vraie, de sorte qu'il n'est pas nécessaire de créer à plusieurs reprises des méthodes prototypes. Le nom de classe Book est utilisé ici à la place car le prototype appartient à la classe elle-même, et non à l'instance d'objet.

Le mode prototype dynamique et le mode prototype construit sont équivalents en termes de performances. Les utilisateurs peuvent choisir librement, mais le mode prototype construit est plus largement utilisé.

Modèle d'usine

Le modèle d'usine est le moyen le plus basique de définir des types et est également le modèle de développement le plus couramment utilisé en JavaScript. Il encapsule simplement l'instanciation d'objet dans une fonction, puis appelle la fonction pour réaliser une production rapide et par lots d'objets d'instance.

Exemple 1

L'exemple suivant conçoit un type de voiture : il contient trois attributs : la couleur de la voiture, le nombre de roues motrices et la consommation de carburant aux 100 kilomètres, ainsi que définit une méthode pour afficher la couleur de la voiture.

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”

Le code ci-dessus est un type de modèle d'usine simple. En utilisant la classe Car, vous pouvez créer rapidement plusieurs instances de voiture. Leurs structures sont les mêmes, mais leurs attributs sont différents. roues motrices et consommation de carburant aux 100 kilomètres.

Exemple 2

Dans un type, une méthode est un comportement ou une opération qui peut accomplir une tâche spécifique en fonction des paramètres d'initialisation et possède des caractéristiques communes. Par conséquent, vous pouvez envisager de placer la méthode en dehors de la fonction Car() pour éviter de créer une fonction à chaque fois qu'elle est instanciée et laisser chaque instance partager la même fonction.

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;  //返回实例
}

Dans le code réécrit ci-dessus, la fonction showColor() est définie avant la fonction Car(). Dans Car(), en référençant la fonction externe showColor(), nous évitons d'avoir à créer une nouvelle fonction à chaque fois qu'elle est instanciée. Fonctionnellement, cela résout le problème de la création répétée d’une fonction ; mais sémantiquement, la fonction ressemble moins à une méthode objet.

Héritage de classe

La méthode de conception de l'héritage de classe : appelez le constructeur de la classe parent dans la classe enfant.

Lors de l'implémentation de l'héritage de classe en JavaScript, vous devez faire attention aux 3 problèmes techniques suivants.

在子类中,使用 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学习教程

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn