ホームページ > 記事 > ウェブフロントエンド > JavaScriptで継承を実装する方法
Javascript は継承メソッドを実装します: 1. プロトタイプを構築し、プロトタイプ プロトタイプを直接使用してクラス継承を設計します; 2. 動的プロトタイプを使用して継承を実装します; 3. ファクトリ パターンを使用して継承を実装します; 4. クラス継承を使用して引き渡します親クラスのコンストラクターは、継承を実装するためにクラス内で呼び出されます。
このチュートリアルの動作環境: Windows7 システム、JavaScript バージョン 1.8.5、Dell G3 コンピューター。
プロトタイプの構築
プロトタイプのプロトタイプ設計を直接使用するクラスの継承には 2 つの問題があります。
コンストラクタは事前に宣言され、クラス構造の宣言後にプロトタイプの属性が定義されるため、コンストラクタを介してプロトタイプに動的にパラメータを渡すことはできません。このように、インスタンス化されたオブジェクトはすべて同じように見え、個性がありません。プロトタイプのプロパティ値を変更すると、すべてのインスタンスが影響を受けます。
プロトタイプ属性の値が参照型データの場合、1 つのオブジェクト インスタンスで属性値を変更すると、すべてのインスタンスに影響します。
例 1
Book タイプを定義してインスタンス化するだけです。
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
プロトタイプ属性 o は参照値であるため、すべてのインスタンスの属性 o の値は同じオブジェクトへの参照になります。o の値が変更されると、すべてのインスタンスに影響します。
コンストラクション プロトタイプは、プロトタイプ パターンを解決するために生まれたハイブリッド デザイン パターンであり、上記の問題を回避するために、コンストラクター パターンとプロトタイプ パターンを混合します。
実装方法: 相互に影響を与える可能性のあるプロトタイププロパティや動的にパラメータを渡したいプロパティは、コンストラクタパターンを使用して独立して設計できます。個別に設計する必要がなく、共通の特性を持つメソッドや属性については、プロトタイプパターンを使用して設計できます。
例 2
上記の設計原則に従い、2 つのプロパティをコンストラクター モードとして設計します。設計方法はプロトタイプ モードです。
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);
構築されたプロトタイプ パターンは、ECMAScript でクラスを定義するための推奨標準です。一般に、コンストラクター パターンを使用してすべてのプロパティを定義し、プロトタイプ パターンを使用してすべてのメソッドを定義することをお勧めします。この方法では、すべてのメソッドが 1 回だけ作成され、各インスタンスは必要に応じてプロパティ値を設定できます。これは最も広く使用されているデザイン パターンでもあります。
動的プロトタイプ
オブジェクト指向設計原則によれば、型のすべてのメンバーはクラス構造にカプセル化される必要があります。例:
function Book (title, pages) { //构造函数模式设计 this.title = title; this.pages = pages; Book.prototype.what = function () { //原型模式设计,位于类的内部 console.log(this.title + this.pages); }; }
しかし、インスタンス化されるたびに、Book クラスに含まれるプロトタイプ メソッドが繰り返し作成され、大量のプロトタイプ メソッドが生成され、システム リソースが浪費されます。 if を使用して、プロトタイプ メソッドが存在するかどうかを判断できます。存在する場合、メソッドは作成されません。存在しない場合、メソッドは作成されます。
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 式は属性値の型を検出できます。未定義の文字列が返された場合、属性値は存在せず、プロトタイプ メソッドが作成されていないことを示し、この属性を設定するには、プロトタイプ メソッドを使用します。値は true なので、プロトタイプ メソッドを繰り返し作成する必要はありません。プロトタイプはオブジェクト インスタンスではなくクラス自体に属しているため、ここではクラス名 Book が代わりに使用されています。
動的プロトタイプ モードと構築プロトタイプ モードはパフォーマンスが同等であり、ユーザーは自由に選択できますが、構築プロトタイプ モードの方が広く使用されています。
ファクトリ パターン
ファクトリ パターンは型を定義する最も基本的な方法であり、JavaScript で最も一般的に使用される開発パターンでもあります。オブジェクトのインスタンス化を関数にカプセル化し、その関数を呼び出して、インスタンス オブジェクトの迅速なバッチ生成を実現します。
例 1
次の例では、車のタイプを設計します。これには、車の色、駆動輪の数、100 キロメートルごとの燃料消費量の 3 つの属性が含まれています。車の色を表示するメソッドを定義します。
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”
上記のコードは、単純なファクトリ パターン タイプです。Car クラスを使用すると、複数の車のインスタンスをすばやく作成できます。それらの構造は同じですが、属性が異なります。異なる色、数を初期化できます。駆動輪と100kmあたりの燃料消費量。
例 2
型において、メソッドは、初期化パラメータに基づいて特定のタスクを完了できる動作または操作であり、共通の特性を持っています。したがって、メソッドを Car() 関数の外側に配置して、インスタンス化されるたびに関数を作成することを避け、各インスタンスが同じ関数を共有できるようにすることを検討できます。
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; //返回实例 }
上記の書き換えられたコードでは、関数 showColor() が関数 Car() の前に定義されています。 Car() 内で外部の showColor() 関数を参照することにより、インスタンス化されるたびに新しい関数を作成する必要がなくなります。機能的には、これにより関数を繰り返し作成する問題は解決されますが、意味的には、この関数はオブジェクト メソッドとは異なります。
#クラス継承
クラス継承の設計方法: サブクラスで親クラスのコンストラクターを呼び出します。 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学习教程】
以上がJavaScriptで継承を実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。