【问题标题】:Javascript functional inheritance with prototypes带有原型的 Javascript 函数继承
【发布时间】:2011-01-27 07:29:10
【问题描述】:

在 Douglas Crockford 的 JavaScript: The Good Parts 中,他建议我们使用函数式继承。这是一个例子:

var mammal = function(spec, my) {
    var that = {};
    my = my || {};

    // Protected
    my.clearThroat = function() { 
        return "Ahem";
    };

    that.getName = function() {
        return spec.name;
    };

    that.says = function() {
        return my.clearThroat() + ' ' + spec.saying || '';
    };

    return that;
};

var cat = function(spec, my) {
    var that = {};
    my = my || {};

    spec.saying = spec.saying || 'meow';
    that = mammal(spec, my);

    that.purr = function() { 
        return my.clearThroat() + " purr"; 
    };

    that.getName = function() { 
        return that.says() + ' ' + spec.name + ' ' + that.says();
    };

    return that;
};

var kitty = cat({name: "Fluffy"});

我遇到的主要问题是,每次我创建 mammal 或 cat 时,JavaScript 解释器都必须重新编译其中的所有函数。也就是说,您无法在实例之间共享代码。

我的问题是:如何让这段代码更有效率?例如,如果我制作了数千个 cat 对象,那么修改此模式以利用 prototype 对象的最佳方法是什么?

【问题讨论】:

  • "必须重新编译其中的所有函数。也就是说,您不能在实例之间共享代码" - 不。代码是共享的,只需要创建具有不同范围值的不同函数对象。这不是一个巨大的开销。

标签: javascript inheritance performance prototypal-inheritance


【解决方案1】:

好吧,如果你打算制作大量 mammal 或 cat,你就不能那样做。而是以老式的方式(原型)并通过属性继承。您仍然可以按照上面的方式执行构造函数,但您可以使用隐式 this 和一些表示基类的变量(在本例中为 this.mammal),而不是 that 和 my。

cat.prototype.purr = function() { return this.mammal.clearThroat() + "purr"; }

我会使用除 my 之外的其他名称进行基本访问,并将其存储在 cat 构造函数中的 this 中。在此示例中,我使用了 mammal,但如果您希望 static 访问全局 mammal 对象,这可能不是最好的。另一种选择是将变量命名为base。

【讨论】:

  • 通过this.mammal 链接my 对象的问题是我会失去它的隐私,因为有人可以只做cat.mammal.clearThroat() 并访问受保护的方法。
  • 确实如此。如果您想要隐私,那么您需要按照惯例进行操作,例如,在受保护的方法之前加上下划线。如果这看起来太松散,请考虑即使这种“功能继承”模式本身也是一种约定。 Javascript 不是一种“整洁的怪胎”语言,它更加“粗糙且准备就绪”。正如您所发现的那样,让它以整洁的怪异方式工作会降低性能。但是,不要过分重视惩罚 - 以您的示例为例,需要创建数万个对象才能显着影响您的整体运行速度。
  • “有人可以只做cat.mammal.clearThroat() 并访问受保护的方法”——并不是每个问题都需要用代码来解决。恕我直言,您不是在编写 Windows。如果有人滥用你的代码,那太棒了,因为这意味着有人实际上在使用你的代码。更好地专注于编写有用且易于正确使用的代码,而不是防范假设性问题。
  • @Paul D. Waite:我明白你在说什么,但我问这个问题更多是为了更深入地了解 JavaScript。
  • @cdmckay 我来自函数式语言背景,Javascript 是一种基于 Scheme 的函数式语言。在具有类型系统的过程语言中,您需要 OO 语义来表达您绕过类型系统限制的方式。在 Javascript 中,不需要这种继承来创建多态性——没有类型系统,因此不需要向代码库表达 A 是 B 的类型。相反,任何两个共享一组属性的对象都可以被视为多态的。要共享代码,您可以通过原型和函数引用直接混入代码。
【解决方案2】:

如果您想要隐私并且不喜欢原型制作,您可能会也可能不喜欢这种方法:

(注意:它使用 jQuery.extend)

var namespace = namespace || {};

// virtual base class
namespace.base = function (sub, undefined) {

    var base = { instance: this };

    base.hierarchy = [];

    base.fn = {

        // check to see if base is of a certain class (must be delegated)
        is: function (constr) {

            return (this.hierarchy[this.hierarchy.length - 1] === constr);
        },

        // check to see if base extends a certain class (must be delegated)
        inherits: function (constr) {

            for (var i = 0; i < this.hierarchy.length; i++) {

                if (this.hierarchy[i] == constr) return true;
            }
            return false;
        },

        // extend a base (must be delegated)
        extend: function (sub) {

            this.hierarchy.push(sub.instance.constructor);

            return $.extend(true, this, sub);
        },

        // delegate a function to a certain context
        delegate: function (context, fn) {

            return function () { return fn.apply(context, arguments); }
        },

        // delegate a collection of functions to a certain context
        delegates: function (context, obj) {

            var delegates = {};

            for (var fn in obj) {

                delegates[fn] = base.fn.delegate(context, obj[fn]);
            }

            return delegates;
        }
    };

    base.public = {
        is: base.fn.is,
        inherits: base.fn.inherits
    };

    // extend a sub-base
    base.extend = base.fn.delegate(base, base.fn.extend);

    return base.extend(sub);
};

namespace.MyClass = function (params) {

    var base = { instance: this };

    base.vars = {
        myVar: "sometext"
    }

    base.fn = {
        init: function () {

            base.vars.myVar = params.myVar;
        },

        alertMyVar: function() {

            alert(base.vars.myVar);
        }

    };

    base.public = {
        alertMyVar: base.fn.alertMyVar
    };

    base = namespace.base(base);

    base.fn.init();

    return base.fn.delegates(base,base.public);
};

newMyClass = new namespace.MyClass({myVar: 'some text to alert'});
newMyClass.alertMyVar();

唯一的缺点是由于隐私范围,您只能扩展虚拟类而不是可实例类。

这是我如何扩展 namespace.base 以绑定/取消绑定/触发自定义事件的示例。

// virtual base class for controls
namespace.controls.base = function (sub) {

    var base = { instance: this };

    base.keys = {
        unknown: 0,
        backspace: 8,
        tab: 9,
        enter: 13,
        esc: 27,
        arrowUp: 38,
        arrowDown: 40,
        f5: 116
    }

    base.fn = {

        // bind/unbind custom events. (has to be called via delegate)
        listeners: {

            // bind custom event
            bind: function (type, fn) {

                if (fn != undefined) {

                    if (this.listeners[type] == undefined) {
                        throw (this.type + ': event type \'' + type + '\' is not supported');
                    }

                    this.listeners[type].push(fn);
                }

                return this;
            },

            // unbind custom event
            unbind: function (type) {

                if (this.listeners[type] == undefined) {
                    throw (this.type + ': event type \'' + type + '\' is not supported');
                }

                this.listeners[type] = [];

                return this;
            },

            // fire a custom event
            fire: function (type, e) {

                if (this.listeners[type] == undefined) {
                    throw (this.type + ': event type \'' + type + '\' does not exist');
                }

                for (var i = 0; i < this.listeners[type].length; i++) {

                    this.listeners[type][i](e);
                }

                if(e != undefined) e.stopPropagation();
            }
        }
    };

    base.public = {
        bind: base.fn.listeners.bind,
        unbind: base.fn.listeners.unbind
    };

    base = new namespace.base(base);

    base.fire = base.fn.delegate(base, base.fn.listeners.fire);

    return base.extend(sub);
};

【讨论】:

    【解决方案3】:

    让我向您介绍从不使用prototype 的经典继承。这是一个糟糕的编码练习,但会教你真正的经典继承,它总是与原型继承相比:

    制作一个 custructor:

    function Person(name, age){
      this.name = name;
      this.age = age;
      this.sayHello = function(){return "Hello! this is " + this.name;}
    }
    

    创建另一个继承自它的 cunstructor:

    function Student(name, age, grade){
      Person.apply(this, [name, age]);
      this.grade = grade
    }
    

    非常简单! Student 使用 name 和 age 参数调用(应用)Person 自己处理 grade 参数。

    现在让我们创建一个Student 的实例。

    var pete = new Student('Pete', 7, 1);
    

    pete 对象现在将包含 name、age、grade 和 sayHello 属性。它拥有所有这些属性。它们没有通过原型上传到Person。如果我们将Person 更改为:

    function Person(name, age){
      this.name = name;
      this.age = age;
      this.sayHello = function(){
        return "Hello! this is " + this.name + ". I am " this.age + " years old";
      }
    }
    

    pete 不会收到更新。如果我们调用pete.sayHello,ti 将返回Hello! this is pete。它不会得到新的更新。

    【讨论】:

      【解决方案4】:

      要正确使用基于 Javascript 原型的继承,您可以使用 fastClass https://github.com/dotnetwise/Javascript-FastClass

      你有更简单的inheritWith 风格:

        var Mammal = function (spec) {
          this.spec = spec;
      }.define({
          clearThroat: function () { return "Ahem" },
          getName: function () {
              return this.spec.name;
          },
          says: function () {
              return this.clearThroat() + ' ' + spec.saying || '';
          }
      });
      
      var Cat = Mammal.inheritWith(function (base, baseCtor) {
          return {
              constructor: function(spec) { 
                  spec = spec || {};
                  baseCtor.call(this, spec); 
              },
              purr: function() {
                  return this.clearThroat() + " purr";
              },
              getName: function() {
                  return this.says() + ' ' + this.spec.name + this.says();
              }
          }
      });
      
      var kitty = new Cat({ name: "Fluffy" });
      kitty.purr(); // Ahem purr
      kitty.getName(); // Ahem Fluffy Ahem
      

      如果您非常关心性能,那么您有fastClass 风格:

      var Mammal = function (spec) {
          this.spec = spec;
      }.define({
          clearThroat: function () { return "Ahem" },
          getName: function () {
              return this.spec.name;
          },
          says: function () {
              return this.clearThroat() + ' ' + spec.saying || '';
          }
      });
      
      var Cat = Mammal.fastClass(function (base, baseCtor) {
          return function() {
              this.constructor = function(spec) { 
                  spec = spec || {};
                  baseCtor.call(this, spec); 
              };
              this.purr = function() {
                  return this.clearThroat() + " purr";
              },
              this.getName = function() {
                  return this.says() + ' ' + this.spec.name + this.says();
              }
          }
      });
      
      var kitty = new Cat({ name: "Fluffy" });
      kitty.purr(); // Ahem purr
      kitty.getName(); // Ahem Fluffy Ahem
      

      顺便说一句,您的初始代码没有任何意义,但我从字面上尊重它。

      fastClass 实用程序:

      Function.prototype.fastClass = function (creator) {
          var baseClass = this, ctor = (creator || function () { this.constructor = function () { baseClass.apply(this, arguments); } })(this.prototype, this)
      
          var derrivedProrotype = new ctor();
      
          if (!derrivedProrotype.hasOwnProperty("constructor"))
              derrivedProrotype.constructor = function () { baseClass.apply(this, arguments); }
      
          derrivedProrotype.constructor.prototype = derrivedProrotype;
          return derrivedProrotype.constructor;
      };
      

      inheritWith 实用程序:

      Function.prototype.inheritWith = function (creator, makeConstructorNotEnumerable) {
          var baseCtor = this;
          var creatorResult = creator.call(this, this.prototype, this) || {};
          var Derrived = creatorResult.constructor ||
          function defaultCtor() {
              baseCtor.apply(this, arguments);
          }; 
          var derrivedPrototype;
          function __() { };
          __.prototype = this.prototype;
          Derrived.prototype = derrivedPrototype = new __;
      
          for (var p in creatorResult)
              derrivedPrototype[p] = creatorResult[p];
      
          if (makeConstructorNotEnumerable && canDefineNonEnumerableProperty) //this is not default as it carries over some performance overhead
              Object.defineProperty(derrivedPrototype, 'constructor', {
                  enumerable: false,
                  value: Derrived
              });
      
          return Derrived;
      };
      

      define 实用程序:

      Function.prototype.define = function (prototype) {
          var extendeePrototype = this.prototype;
          if (prototype)
              for (var p in prototype)
                  extendeePrototype[p] = prototype[p];
          return this;
      }
      

      [* 免责声明,我是开源包的作者,未来可能会重命名方法本身的名称` *]

      【讨论】:

        猜你喜欢
        • 2013-10-02
        • 2021-11-14
        • 2014-05-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-28
        • 2018-02-21
        相关资源
        最近更新 更多