【问题标题】:JavaScript Extending ClassJavaScript 扩展类
【发布时间】:2013-02-18 00:28:10
【问题描述】:

我有一个基类:

function Monster() {
  this.health = 100;
}

Monster.prototype.growl = function() {
  console.log("Grr!");
}

我想扩展并创建另一个类:

function Monkey extends Monster() {
  this.bananaCount = 5;
}

Monkey.prototype.eatBanana {
  this.bananaCount--;
  this.health++; //Accessing variable from parent class monster
  this.growl();  //Accessing function from parent class monster
}

我已经进行了大量研究,似乎有许多复杂的解决方案可以在 JavaScript 中执行此操作。在 JS 中实现这一点的最简单和最可靠的方法是什么?

【问题讨论】:

标签: javascript oop object inheritance ecmascript-6


【解决方案1】:

下面为 ES6 更新

2013 年 3 月和 ES5

这个 MDN 文档很好地描述了扩展类:

https://developer.mozilla.org/en-US/docs/JavaScript/Introduction_to_Object-Oriented_JavaScript

特别是,现在他们处理它:

// define the Person Class
function Person() {}

Person.prototype.walk = function(){
  alert ('I am walking!');
};
Person.prototype.sayHello = function(){
  alert ('hello');
};

// define the Student class
function Student() {
  // Call the parent constructor
  Person.call(this);
}

// inherit Person
Student.prototype = Object.create(Person.prototype);

// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;

// replace the sayHello method
Student.prototype.sayHello = function(){
  alert('hi, I am a student');
}

// add sayGoodBye method
Student.prototype.sayGoodBye = function(){
  alert('goodBye');
}

var student1 = new Student();
student1.sayHello();
student1.walk();
student1.sayGoodBye();

// check inheritance
alert(student1 instanceof Person); // true 
alert(student1 instanceof Student); // true

注意Object.create() 在一些旧版浏览器中不受支持,包括 IE8:

如果您需要支持这些,链接的 MDN 文档建议使用 polyfill 或以下近似值:

function createObject(proto) {
    function ctor() { }
    ctor.prototype = proto;
    return new ctor();
}

Student.prototype = createObject(Person.prototype)这样使用new Person()更可取,因为它在继承原型时avoids calling the parent's constructor function,并且仅在调用继承者的构造函数时才调用父构造函数。

2017 年 5 月和 ES6

谢天谢地,JavaScript 设计人员听到了我们的求助请求,并采用了更合适的方式来解决此问题。

MDN 有另一个很好的 ES6 类继承示例,但我将展示与上面在 ES6 中复制的完全相同的类集:

class Person {
    sayHello() {
        alert('hello');
    }

    walk() {
        alert('I am walking!');
    }
}

class Student extends Person {
    sayGoodBye() {
        alert('goodBye');
    }

    sayHello() {
        alert('hi, I am a student');
    }
}

var student1 = new Student();
student1.sayHello();
student1.walk();
student1.sayGoodBye();

// check inheritance
alert(student1 instanceof Person); // true 
alert(student1 instanceof Student); // true

干净易懂,就像我们都想要的那样。请记住,虽然 ES6 很常见,但它是 not supported everywhere

【讨论】:

  • 如果 Person 构造函数需要参数,例如 Person(name) ... 怎么办?
  • @PhamHuyAnh 做类似function Person(n) { this.name = n; }的事情
  • Student.prototype = new Person(); 如果我访问超类中的参数,此行会导致错误。
  • Student.prototype = Object.create(Person.prototype); 经典的创建方法在这里工作
  • 我正在尝试创建扩展功能,它可以自己完成所有工作。是否有可能以某种方式移动“Person.call(this);”或来自“Monster.apply(this, arguments);”下面的示例到这样的功能?我在这样做时遇到了问题。
【解决方案2】:

ES6 现在让您有机会使用 classextends 关键字:

然后,您的代码将是:

你有一个基类:

class Monster{
       constructor(){
             this.health = 100;
        }
       growl() {
           console.log("Grr!");
       }

}

你想扩展和创建另一个类:

class Monkey extends Monster {
        constructor(){
            super(); //don't forget "super"
            this.bananaCount = 5;
        }


        eatBanana() {
           this.bananaCount--;
           this.health++; //Accessing variable from parent class monster
           this.growl(); //Accessing function from parent class monster
        }

}

【讨论】:

  • 这样干净多了,可以确认在 chrome 51.0, Firefox 47 下工作了。
  • 使用try{}catch(e){} 块来管理它,并告诉最终用户在需要时更新他的浏览器。
【解决方案3】:

试试这个:

Function.prototype.extends = function(parent) {
  this.prototype = Object.create(parent.prototype);
};

Monkey.extends(Monster);
function Monkey() {
  Monster.apply(this, arguments); // call super
}

编辑:我在这里放了一个快速演示 http://jsbin.com/anekew/1/edit。注意extends是JS中的保留字,在linting你的代码时可能会收到警告,你可以简单地把它命名为inherits,这是我通常做的。

有了这个助手并使用对象props作为唯一参数,JS中的继承变得更简单了:

Function.prototype.inherits = function(parent) {
  this.prototype = Object.create(parent.prototype);
};

function Monster(props) {
  this.health = props.health || 100;
}

Monster.prototype = {
  growl: function() {
    return 'Grrrrr';
  }
};

Monkey.inherits(Monster);
function Monkey() {
  Monster.apply(this, arguments);
}

var monkey = new Monkey({ health: 200 });

console.log(monkey.health); //=> 200
console.log(monkey.growl()); //=> "Grrrr"

【讨论】:

  • 使用这个,Monkey 不会继承 Monster 的属性 (health)。如果在调用 Monkey.extends(Monster) 之前未定义 Monkey,您还将得到 "ReferenceError: Monkey is not defined"
  • @Phil,这是一个函数声明,它被提升了,它应该可以工作。您将从该代码中得到的唯一“问题”是“扩展是保留字”,但您可以轻松地将其更改为任何其他标识符。
  • 谢谢哥们。这很棒。可以在 Node.js 中应用它来为构造函数等创建基类,因此我不必每次创建构造类时都创建 mongo 连接等
【解决方案4】:

如果您不喜欢原型方法,因为它实际上并不能以良好的 OOP 方式运行,您可以试试这个:

var BaseClass = function() 
{
    this.some_var = "foobar";

    /**
     * @return string
     */
    this.someMethod = function() {
        return this.some_var;
    }
};

var MyClass = new Class({ extends: BaseClass }, function()
{
    /**
     * @param string value
     */
    this.__construct = function(value)
    {
        this.some_var = value;
    }
})

使用轻量级库(2k 缩小):https://github.com/haroldiedema/joii

【讨论】:

  • 感谢您提供此库的链接!它看起来很棒:D 似乎这就是我一直在寻找的东西!
【解决方案5】:

我可以提出一种变体,只是在书上看过,似乎最简单:

function Parent() { 
  this.name = 'default name';
};

function Child() {
  this.address = '11 street';
};

Child.prototype = new Parent();      // child class inherits from Parent
Child.prototype.constructor = Child; // constructor alignment

var a = new Child(); 

console.log(a.name);                // "default name" trying to reach property of inherited class

【讨论】:

    【解决方案6】:

    这是 elclanrs 解决方案的扩展(请原谅双关语),包括实例方法的详细信息,以及对问题的这方面采取可扩展的方法;我完全承认,这要归功于 David Flanagan 的“JavaScript:权威指南”(针对此上下文进行了部分调整)。请注意,这显然比其他解决方案更冗长,但从长远来看可能会受益。

    首先我们使用 David 的简单“扩展”函数,它将属性复制到指定对象:

    function extend(o,p) {
        for (var prop in p) {
            o[prop] = p[prop];
        }
        return o;
    }
    

    然后我们实现他的子类定义工具:

    function defineSubclass(superclass,     // Constructor of our superclass
                              constructor,  // Constructor of our new subclass
                              methods,      // Instance methods
                              statics) {    // Class properties
            // Set up the prototype object of the subclass
        constructor.prototype = Object.create(superclass.prototype);
        constructor.prototype.constructor = constructor;
        if (methods) extend(constructor.prototype, methods);
        if (statics) extend(constructor, statics);
        return constructor;
    }
    

    在最后的准备工作中,我们使用 David 的新 jiggery-pokery 来增强我们的 Function 原型:

    Function.prototype.extend = function(constructor, methods, statics) {
        return defineSubclass(this, constructor, methods, statics);
    };
    

    在定义了我们的 Monster 类之后,我们执行以下操作(可重复用于我们想要扩展/继承的任何新类):

    var Monkey = Monster.extend(
            // constructor
        function Monkey() {
            this.bananaCount = 5;
            Monster.apply(this, arguments);    // Superclass()
        },
            // methods added to prototype
        {
            eatBanana: function () {
                this.bananaCount--;
                this.health++;
                this.growl();
            }
        }
    );
    

    【讨论】:

      【解决方案7】:

      对于传统的扩展,您可以简单地将超类编写为构造函数, 然后将此构造函数应用于您继承的类。

           function AbstractClass() {
            this.superclass_method = function(message) {
                // do something
              };
           }
      
           function Child() {
               AbstractClass.apply(this);
               // Now Child will have superclass_method()
           }
      

      angularjs 示例:

      http://plnkr.co/edit/eFixlsgF3nJ1LeWUJKsd?p=preview

      app.service('noisyThing', 
        ['notify',function(notify){
          this._constructor = function() {
            this.scream = function(message) {
                message = message + " by " + this.get_mouth();
                notify(message); 
                console.log(message);
              };
      
            this.get_mouth = function(){
              return 'abstract mouth';
            }
          }
        }])
        .service('cat',
        ['noisyThing', function(noisyThing){
          noisyThing._constructor.apply(this)
          this.meow = function() {
            this.scream('meooooow');
          }
          this.get_mouth = function(){
            return 'fluffy mouth';
          }
        }])
        .service('bird',
        ['noisyThing', function(noisyThing){
          noisyThing._constructor.apply(this)
          this.twit = function() {
            this.scream('fuuuuuuck');
          }
        }])
      

      【讨论】:

        【解决方案8】:

        对于自学者:

        function BaseClass(toBePrivate){
            var morePrivates;
            this.isNotPrivate = 'I know';
            // add your stuff
        }
        var o = BaseClass.prototype;
        // add your prototype stuff
        o.stuff_is_never_private = 'whatever_except_getter_and_setter';
        
        
        // MiddleClass extends BaseClass
        function MiddleClass(toBePrivate){
            BaseClass.call(this);
            // add your stuff
            var morePrivates;
            this.isNotPrivate = 'I know';
        }
        var o = MiddleClass.prototype = Object.create(BaseClass.prototype);
        MiddleClass.prototype.constructor = MiddleClass;
        // add your prototype stuff
        o.stuff_is_never_private = 'whatever_except_getter_and_setter';
        
        
        
        // TopClass extends MiddleClass
        function TopClass(toBePrivate){
            MiddleClass.call(this);
            // add your stuff
            var morePrivates;
            this.isNotPrivate = 'I know';
        }
        var o = TopClass.prototype = Object.create(MiddleClass.prototype);
        TopClass.prototype.constructor = TopClass;
        // add your prototype stuff
        o.stuff_is_never_private = 'whatever_except_getter_and_setter';
        
        
        // to be continued...
        

        使用 getter 和 setter 创建“实例”:

        function doNotExtendMe(toBePrivate){
            var morePrivates;
            return {
                // add getters, setters and any stuff you want
            }
        }
        

        【讨论】:

          【解决方案9】:

          总结:

          有多种方法可以解决在 Javascript 中使用原型扩展构造函数的问题。这些方法中的哪一个是“最佳”解决方案是基于意见的。但是,这里有两种常用的方法来扩展构造函数的函数原型。

          ES 2015 课程:

          class Monster {
            constructor(health) {
              this.health = health
            }
            
            growl () {
            console.log("Grr!");
            }
            
          }
          
          
          class Monkey extends Monster {
            constructor (health) {
              super(health) // call super to execute the constructor function of Monster 
              this.bananaCount = 5;
            }
          }
          
          const monkey = new Monkey(50);
          
          console.log(typeof Monster);
          console.log(monkey);

          上述使用ES 2015 类的方法只不过是javascript 中原型继承模式的语法糖。在我们评估typeof Monster 的第一个日志中,我们可以观察到这是函数。这是因为类只是底层的构造函数。尽管如此,您可能喜欢这种实现原型继承的方式,并且绝对应该学习它。在ReactJSAngular2+等主要框架中都有使用。

          使用Object.create()的工厂函数:

          function makeMonkey (bananaCount) {
            
            // here we define the prototype
            const Monster = {
            health: 100,
            growl: function() {
            console.log("Grr!");}
            }
            
            const monkey = Object.create(Monster);
            monkey.bananaCount = bananaCount;
          
            return monkey;
          }
          
          
          const chimp = makeMonkey(30);
          
          chimp.growl();
          console.log(chimp.bananaCount);

          此方法使用Object.create() 方法,该方法接受一个对象,该对象将是它返回的新创建对象的原型。因此,我们首先在这个函数中创建原型对象,然后调用Object.create(),它返回一个空对象,其中__proto__ 属性设置为Monster 对象。在此之后我们可以初始化对象的所有属性,在这个例子中我们将香蕉计数分配给新创建的对象。

          【讨论】:

            【解决方案10】:

            绝对最小(和正确,与上面的许多答案不同)版本是:

            function Monkey(param){
              this.someProperty = param;
            }
            Monkey.prototype = Object.create(Monster.prototype);
            Monkey.prototype.eatBanana = function(banana){ banana.eat() }
            

            就是这样。你可以阅读here the longer explanation

            【讨论】:

              猜你喜欢
              • 2020-07-22
              • 2011-01-16
              • 2017-11-21
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-06-25
              • 1970-01-01
              • 2019-11-18
              相关资源
              最近更新 更多