JavaScriptの継承の詳細解説(4)_jsオブジェクト指向

WBOY
リリース: 2016-05-16 18:50:00
オリジナル
721 人が閲覧しました
JavaScript における古典的な継承
Crockford は JavaScript 開発コミュニティで最もよく知られた権威であり、JSONJSLintJSMinADSafe の父は、『JavaScript: The Good Parts』の著者です。
彼は現在、Yahoo のシニア JavaScript アーキテクトであり、YUI の設計と開発に参加しています。 クロックフォードの生涯と著書について詳しく説明した 記事 はこちらです。
もちろん、クロックフォードは私たちや他の後輩たちの尊敬する人物でもあります。

メソッドの呼び出し

まず、Crockford スタイルの継承を使用した呼び出しメソッドを見てみましょう。
注: コード内のメソッド、継承、および uber はすべてカスタム オブジェクトであり、これについては後続のコード分析で詳しく説明します。

    // 定义Person类
    function Person(name) {
      this.name = name;
    }
    // 定义Person的原型方法
    Person.method("getName", function() {
      return this.name;
    }); 
    
    // 定义Employee类
    function Employee(name, employeeID) {
      this.name = name;
      this.employeeID = employeeID;
    }
    // 指定Employee类从Person类继承
    Employee.inherits(Person);
    // 定义Employee的原型方法
    Employee.method("getEmployeeID", function() {
      return this.employeeID;
    });
    Employee.method("getName", function() {
      // 注意,可以在子类中调用父类的原型方法
      return "Employee name: " + this.uber("getName");
    });
    // 实例化子类
    var zhang = new Employee("ZhangSan", "1234");
    console.log(zhang.getName());  // "Employee name: ZhangSan"
    
ログイン後にコピー

ここで言及しなければならない欠点がいくつかあります。

  • サブクラスが親クラスから継承するコードは、サブクラスと親クラスの両方が定義された後、サブクラスのプロトタイプ メソッドが定義される前に実行する必要があります。
  • サブクラスのメソッド本体は親クラスのメソッドを呼び出すことができますが、サブクラスのコンストラクタは親クラスのコンストラクタを呼び出すことはできません。
  • プロトタイプメソッドの定義や親クラスの呼び出し方法(直観的ではない)など、コードの書き方が洗練されていません。

もちろん、Crockford の実装では、次の例に示すように、パラメータを使用して親クラスのメソッドを呼び出すサブクラスのメソッドもサポートされています。

    function Person(name) {
      this.name = name;
    }
    Person.method("getName", function(prefix) {
      return prefix + this.name;
    });

    function Employee(name, employeeID) {
      this.name = name;
      this.employeeID = employeeID;
    }
    Employee.inherits(Person);
    Employee.method("getName", function() {
      // 注意,uber的第一个参数是要调用父类的函数名称,后面的参数都是此函数的参数
      // 个人觉得这样方式不如这样调用来的直观:this.uber("Employee name: ")
      return this.uber("getName", "Employee name: ");
    });
    var zhang = new Employee("ZhangSan", "1234");
    console.log(zhang.getName());  // "Employee name: ZhangSan"
    
ログイン後にコピー

コード分析

まず第一に、メソッド関数の定義は非常に簡単です:

    Function.prototype.method = function(name, func) {
      // this指向当前函数,也即是typeof(this) === "function"
      this.prototype[name] = func;
      return this;
    };
    
ログイン後にコピー
ここでの点に特に注意してください。これを見たとき、現在の関数だけに注目するのではなく、現在の関数がどのように呼び出されるのかを考える必要があります。 たとえば、この例では new を介してメソッドを呼び出すことはしないため、メソッド内の this は現在の関数を指します。

継承関数の定義は少し複雑です:

    Function.method('inherits', function (parent) {
      // 关键是这一段:this.prototype = new parent(),这里实现了原型的引用
      var d = {}, p = (this.prototype = new parent());
      
      // 只为子类的原型增加uber方法,这里的Closure是为了在调用uber函数时知道当前类的父类的原型(也即是变量 - v)
      this.method('uber', function uber(name) {
        // 这里考虑到如果name是存在于Object.prototype中的函数名的情况
        // 比如 "toString" in {} === true
        if (!(name in d)) {
          // 通过d[name]计数,不理解具体的含义
          d[name] = 0;
        }    
        var f, r, t = d[name], v = parent.prototype;
        if (t) {
          while (t) {
            v = v.constructor.prototype;
            t -= 1;
          }
          f = v[name];
        } else {
          // 个人觉得这段代码有点繁琐,既然uber的含义就是父类的函数,那么f直接指向v[name]就可以了
          f = p[name];
          if (f == this[name]) {
            f = v[name];
          }
        }
        d[name] += 1;
        // 执行父类中的函数name,但是函数中this指向当前对象
        // 同时注意使用Array.prototype.slice.apply的方式对arguments进行截断(因为arguments不是标准的数组,没有slice方法)
        r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
        d[name] -= 1;
        return r;
      });
      return this;
    });
    
ログイン後にコピー
継承関数には小さなバグがあることに注意してください。つまり、コンストラクターのポインターが再定義されていないため、次のエラーが発生します。 :
    var zhang = new Employee("ZhangSan", "1234");
    console.log(zhang.getName());  // "Employee name: ZhangSan"
    console.log(zhang.constructor === Employee);  // false
    console.log(zhang.constructor === Person);   // true
    
ログイン後にコピー

改善のための提案

前回の分析から、個人的にはメソッド関数は必要ないと感じていますが、視線が混乱しやすいです。 継承メソッドは、ある程度のスリム化を行うことができ (Crockford はより多くの状況を考慮する可能性があるためです。元の記事では継承の使用方法がいくつか紹介されていますが、そのうちの 1 つにのみ焦点を当てます)、コンストラクターのポインティング エラーを修正します。

    Function.prototype.inherits = function(parent) {
      this.prototype = new parent();
      this.prototype.constructor = this;
      this.prototype.uber = function(name) {
        f = parent.prototype[name];
        return f.apply(this, Array.prototype.slice.call(arguments, 1));
      };
    };
    
ログイン後にコピー
呼び出しメソッド:
    function Person(name) {
      this.name = name;
    }
    Person.prototype.getName = function(prefix) {
      return prefix + this.name;
    };
    function Employee(name, employeeID) {
      this.name = name;
      this.employeeID = employeeID;
    }
    Employee.inherits(Person);
    Employee.prototype.getName = function() {
      return this.uber("getName", "Employee name: ");
    };
    var zhang = new Employee("ZhangSan", "1234");
    console.log(zhang.getName());  // "Employee name: ZhangSan"
    console.log(zhang.constructor === Employee);  // true
    
ログイン後にコピー

興味深い

記事の最後で、クロックフォード氏は実際に次のように述べています。

私は 8 年間 JavaScript を書いてきましたが、uber 関数を使用する必要性を感じたことは一度もありません。スーパー アイデアは古典的なパターンではかなり重要ですが、プロトタイプおよび関数型パターンでは不要のようです。
Crockford が JavaScript でのオブジェクト指向プログラミングを否定し、JavaScript はプログラミングのプロトタイプと関数のパターンに従うべきだと主張していることがわかります。
しかし、私個人としては、複雑なシナリオではオブジェクト指向のメカニズムがあれば、はるかに便利だと思います。
しかし、それを誰が保証できるでしょうか? jQuery UI のようなプロジェクトですら継承を使用しません。一方、Extjs や Qooxdoo のようなプロジェクトはオブジェクト指向 JavaScript を強く主張しています。 Cappuccino プロジェクトは、オブジェクト指向 JavaScript を実装するための Objective-J 言語も発明しました。
関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!