JavaScript扩展类


76

我有一个基类:

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中完成此工作的最简单,最可靠的方法是什么?


Answers:


147

以下针对ES6更新

2013年3月和ES5

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

https://developer.mozilla.org/zh-CN/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)不支持该功能:

Object.create浏览器支持

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

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

使用like这样Student.prototype = createObject(Person.prototype)比使用like更可取new Person(),因为它避免了在继承原型时调用父级的构造函数,而仅在调用继承者的构造函数时才调用父级构造函数。

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非常普遍,但并非所有地方都支持它:

ES6浏览器支持


3
如果Person构造函数需要参数,例如Person(name)...怎么办?
Phung D.

2
@PhamHuyAnh就像function Person(n) { this.name = n; }
Oliver Spryn

Student.prototype = new Person();如果我访问超类中的参数,此行将导致错误。
最多

1
Student.prototype = Object.create(Person.prototype);古典create方法在这里工作
马克斯

1
我正在尝试创建扩展功能,以自己完成所有功能。是否可以通过某种方式移动“ Person.call(this);” 或来自下面的示例“ Monster.apply(this,arguments);” 这样的功能?我在这样做时遇到问题。
sebastian_t 2015年

18

ES6现在为您提供了使用class扩展关键字的机会:

然后,您的代码将是:

您有一个基类:

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
        }

}

2
这是如此干净,可以确认可以在chrome 51.0,Firefox 47中正常工作
。– Reahreic

1
使用try{}catch(e){}块进行管理,并告知最终用户是否需要更新他的浏览器。
Abdennour TOUMI '16

10

试试这个:

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中的保留字,在整理代码时可能会收到警告,您可以简单地命名它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)。如果在调用之前未定义,您还将得到“ ReferenceError:未定义猴子”MonkeyMonkey.extends(Monster)
Phil

@Phil,它是一个已悬挂的函数声明,它应该可以工作。您将从此代码中获得的唯一“问题”是“扩展是保留字”,但是您可以轻松地将其更改为任何其他标识符。
Elclanrs

1
谢谢,兄弟。这很棒。可以在Node.js中应用它来为构造函数等创建基类,因此我不必每次创建构造类时都创建mongo连接
Mattijs 2014年

6

如果您不喜欢原型方法,因为它实际上并不是以一种很好的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似乎这就是我寻找了这么长时间!<3
x3ro

1

我可以提出一个变体,只是在书中读过,这似乎是最简单的:

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

1

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

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

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

然后我们实现他的Subclass定义实用程序:

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的新拼图游戏来增强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();
        }
    }
);

0

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

     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');
    }
  }])

0

对于自动整理:

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
    }
}

0

概要:

有多种方法可以解决用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可以观察到这是函数。这是因为类只是内部的构造函数。但是,您可能会喜欢这种实现原型继承的方式,并且一定要学习它。它用于诸如ReactJS和的主要框架中Angular2+

工厂功能使用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对象。此后,我们可以初始化对象的所有属性,在本示例中,我们将bananacount分配给新创建的对象。


0

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

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

就这样。您可以在这里阅读更长的解释

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.