ホームページ > 記事 > ウェブフロントエンド > JavaScriptの継承の詳しい解説(3)_jsオブジェクト指向
注: この章の jClass の実装は、単純な JavaScript の継承 の実践を指します。
まず、第 1 章で紹介した例を確認してみましょう:
function Person(name) {<br> this.name = name;<br> }<br> Person.prototype = {<br> getName: function() {<br> return this.name;<br> }<br> }<br><br> function Employee(name, employeeID) {<br> this.name = name;<br> this.employeeID = employeeID;<br> }<br> Employee.prototype = new Person();<br> Employee.prototype.getEmployeeID = function() {<br> return this.employeeID;<br> };<br> var zhang = new Employee("ZhangSan", "1234");<br> console.log(zhang.getName()); // "ZhangSan" <br>
前の記事のコンストラクターの説明から、以下に示すように、Employee インスタンスのコンストラクターにはポインティング エラーがあることがわかります。
var zhang = new Employee("ZhangSan", "1234");<br> console.log(zhang.constructor === Employee); // false<br> console.log(zhang.constructor === Object); // true <br>簡単な修正が必要です。
function Employee(name, employeeID) {<br> this.name = name;<br> this.employeeID = employeeID;<br> }<br> Employee.prototype = new Person();<br> Employee.prototype.constructor = Employee;<br> Employee.prototype.getEmployeeID = function() {<br> return this.employeeID;<br> };<br> var zhang = new Employee("ZhangSan", "1234");<br> console.log(zhang.constructor === Employee); // true<br> console.log(zhang.constructor === Object); // false<br>
しかし一方で、継承を実装するにはこのメカニズムに依存する必要があります。 解決策は、コンストラクターでデータを初期化するのではなく、データを初期化するためのプロトタイプ メソッド (init など) を提供することです。
// 空的构造函数<br> function Person() {<br> }<br> Person.prototype = {<br> init: function(name) {<br> this.name = name;<br> },<br> getName: function() {<br> return this.name;<br> }<br> }<br> // 空的构造函数<br> function Employee() {<br> }<br> // 创建类的阶段不会初始化父类的数据,因为Person是一个空的构造函数<br> Employee.prototype = new Person();<br> Employee.prototype.constructor = Employee;<br> Employee.prototype.init = function(name, employeeID) {<br> this.name = name;<br> this.employeeID = employeeID;<br> };<br> Employee.prototype.getEmployeeID = function() {<br> return this.employeeID;<br> };<br>このメソッドでは、次のように、オブジェクトをインスタンス化した後に init 関数を手動で呼び出す必要があります。
var zhang = new Employee();<br> zhang.init("ZhangSan", "1234");<br> console.log(zhang.getName()); // "ZhangSan"<br>
init 関数を自動的に呼び出すにはどうすればよいですか?
クラスの構築時に init 関数を呼び出さないことと、オブジェクトのインスタンス化時に init 関数を自動的に呼び出すという 2 つの効果を達成する必要があります。空のコンストラクターを呼び出すときにステータスインジケーターが必要なようです。 しかし、これにはグローバル変数の導入が必要であり、これは悪い兆候です。
// 创建一个全局的状态标示 - 当前是否处于类的构造阶段<br> var initializing = false;<br> function Person() {<br> if (!initializing) {<br> this.init.apply(this, arguments);<br> }<br> }<br> Person.prototype = {<br> init: function(name) {<br> this.name = name;<br> },<br> getName: function() {<br> return this.name;<br> }<br> }<br> function Employee() {<br> if (!initializing) {<br> this.init.apply(this, arguments);<br> }<br> }<br> // 标示当前进入类的创建阶段,不会调用init函数<br> initializing = true;<br> Employee.prototype = new Person();<br> Employee.prototype.constructor = Employee;<br> initializing = false;<br> Employee.prototype.init = function(name, employeeID) {<br> this.name = name;<br> this.employeeID = employeeID;<br> };<br> Employee.prototype.getEmployeeID = function() {<br> return this.employeeID;<br> };<br><br> // 初始化类实例时,自动调用类的原型函数init,并向init中传递参数<br> var zhang = new Employee("ZhangSan", "1234");<br> console.log(zhang.getName()); // "ZhangSan"<br>
グローバル変数の初期化を回避するにはどうすればよいですか?
グローバル変数の導入を避けるために内部の詳細をカプセル化しながら、クラス作成プロセスを簡素化するためにグローバル関数を導入する必要があります。 jClass 関数を使用してクラスを作成し、クラスを継承するメソッド:
// 当前是否处于创建类的阶段<br> var initializing = false;<br> function jClass(baseClass, prop) {<br> // 只接受一个参数的情况 - jClass(prop)<br> if (typeof (baseClass) === "object") {<br> prop = baseClass;<br> baseClass = null;<br> }<br> // 本次调用所创建的类(构造函数)<br> function F() {<br> // 如果当前处于实例化类的阶段,则调用init原型函数<br> if (!initializing) {<br> this.init.apply(this, arguments);<br> }<br> }<br> // 如果此类需要从其它类扩展<br> if (baseClass) {<br> initializing = true;<br> F.prototype = new baseClass();<br> F.prototype.constructor = F;<br> initializing = false;<br> }<br> // 覆盖父类的同名函数<br> for (var name in prop) {<br> if (prop.hasOwnProperty(name)) {<br> F.prototype[name] = prop[name];<br> }<br> }<br> return F;<br> };<br>OK、クラスを作成およびインスタンス化する方法がより洗練されました。 しかし、ここには明らかな欠陥がまだあります。Employee の初期化関数 init は、親クラスの同じ名前のメソッドを呼び出すことができません。
var Person = jClass({<br> init: function(name) {<br> this.name = name;<br> },<br> getName: function() {<br> return this.name;<br> }<br> });<br> var Employee = jClass(Person, {<br> init: function(name, employeeID) {<br> this.name = name;<br> this.employeeID = employeeID;<br> },<br> getEmployeeID: function() {<br> return this.employeeID;<br> }<br> });<br><br> var zhang = new Employee("ZhangSan", "1234");<br> console.log(zhang.getName()); // "ZhangSan"<br>
親クラスで同じ名前のメソッドを呼び出すにはどうすればよいですか?
次のように、インスタンス化されたオブジェクトの基本属性を指定することで、親クラス (コンストラクター) のプロトタイプを指定できます。 メソッドの呼び出し:
// 当前是否处于创建类的阶段<br> var initializing = false;<br> function jClass(baseClass, prop) {<br> // 只接受一个参数的情况 - jClass(prop)<br> if (typeof (baseClass) === "object") {<br> prop = baseClass;<br> baseClass = null;<br> }<br> // 本次调用所创建的类(构造函数)<br> function F() {<br> // 如果当前处于实例化类的阶段,则调用init原型函数<br> if (!initializing) {<br> // 如果父类存在,则实例对象的base指向父类的原型<br> // 这就提供了在实例对象中调用父类方法的途径<br> if (baseClass) {<br> this.base = baseClass.prototype;<br> }<br> this.init.apply(this, arguments);<br> }<br> }<br> // 如果此类需要从其它类扩展<br> if (baseClass) {<br> initializing = true;<br> F.prototype = new baseClass();<br> F.prototype.constructor = F;<br> initializing = false;<br> }<br> // 覆盖父类的同名函数<br> for (var name in prop) {<br> if (prop.hasOwnProperty(name)) {<br> F.prototype[name] = prop[name];<br> }<br> }<br> return F;<br> };<br>
var Person = jClass({<br> init: function(name) {<br> this.name = name;<br> },<br> getName: function() {<br> return this.name;<br> }<br> });<br> var Employee = jClass(Person, {<br> init: function(name, employeeID) {<br> // 调用父类的原型函数init,注意使用apply函数修改init的this指向<br> this.base.init.apply(this, [name]);<br> this.employeeID = employeeID;<br> },<br> getEmployeeID: function() {<br> return this.employeeID;<br> },<br> getName: function() {<br> // 调用父类的原型函数getName<br> return "Employee name: " + this.base.getName.apply(this);<br> }<br> });<br><br> var zhang = new Employee("ZhangSan", "1234");<br> console.log(zhang.getName()); // "Employee name: ZhangSan"<br>
これまでのところ、第 1 章の継承の手動実装の欠点を修正してきました。 カスタム jClass 関数を通じてクラスとサブクラスを作成し、プロトタイプ メソッド init を通じてデータを初期化し、インスタンス属性ベースを通じて親クラスのプロトタイプ関数を呼び出します。
唯一の欠点は、親クラスを呼び出すコードが長すぎて理解しにくいことです。 のように呼び出すことができれば良いのではないかと思います。
var Employee = jClass(Person, {<br> init: function(name, employeeID) {<br> // 如果能够这样调用,就再好不过了<br> this.base(name);<br> this.employeeID = employeeID;<br> }<br> });<br>
jClass 関数の最適化
// 当前是否处于创建类的阶段<br> var initializing = false;<br> function jClass(baseClass, prop) {<br> // 只接受一个参数的情况 - jClass(prop)<br> if (typeof (baseClass) === "object") {<br> prop = baseClass;<br> baseClass = null;<br> }<br> // 本次调用所创建的类(构造函数)<br> function F() {<br> // 如果当前处于实例化类的阶段,则调用init原型函数<br> if (!initializing) {<br> // 如果父类存在,则实例对象的baseprototype指向父类的原型<br> // 这就提供了在实例对象中调用父类方法的途径<br> if (baseClass) {<br> this.baseprototype = baseClass.prototype;<br> }<br> this.init.apply(this, arguments);<br> }<br> }<br> // 如果此类需要从其它类扩展<br> if (baseClass) {<br> initializing = true;<br> F.prototype = new baseClass();<br> F.prototype.constructor = F;<br> initializing = false;<br> }<br> // 覆盖父类的同名函数<br> for (var name in prop) {<br> if (prop.hasOwnProperty(name)) {<br> // 如果此类继承自父类baseClass并且父类原型中存在同名函数name<br> if (baseClass &&<br> typeof (prop[name]) === "function" &&<br> typeof (F.prototype[name]) === "function") {<br><br> // 重定义函数name - <br> // 首先在函数上下文设置this.base指向父类原型中的同名函数<br> // 然后调用函数prop[name],返回函数结果<br><br> // 注意:这里的自执行函数创建了一个上下文,这个上下文返回另一个函数,<br> // 此函数中可以应用此上下文中的变量,这就是闭包(Closure)。<br> // 这是JavaScript框架开发中常用的技巧。<br> F.prototype[name] = (function(name, fn) {<br> return function() {<br> this.base = baseClass.prototype[name];<br> return fn.apply(this, arguments);<br> };<br> })(name, prop[name]);<br><br> } else {<br> F.prototype[name] = prop[name];<br> }<br> }<br> }<br> return F;<br> };<br>
var Person = jClass({<br> init: function(name) {<br> this.name = name;<br> },<br> getName: function() {<br> return this.name;<br> }<br> });<br> var Employee = jClass(Person, {<br> init: function(name, employeeID) {<br> this.base(name);<br> this.employeeID = employeeID;<br> },<br> getEmployeeID: function() {<br> return this.employeeID;<br> },<br> getName: function() {<br> return "Employee name: " + this.base();<br> }<br> });<br><br> var zhang = new Employee("ZhangSan", "1234");<br> console.log(zhang.getName()); // "Employee name: ZhangSan"<br>
これまでのところ、よりエレガントな方法で JavaScript でクラスと継承を実装するのに役立つ完全な関数 jClass を作成してきました。
次の章では、インターネット上で最も人気のある JavaScript クラスの実装と継承を続けて分析します。 ただし、すべては同じままであり、これらの実装は、よりエレガントな呼び出し方法を目的として、この章で説明した概念を揺るがす「誇大宣伝」にすぎません。