【问题标题】:simple Javascript inheritance example简单的 Javascript 继承示例
【发布时间】:2013-11-30 09:41:55
【问题描述】:

我试图在不使用任何外部库的情况下在 JavaScript 中实现简单的继承概念。这是我的代码。

<script>
    BaseClass = function () {
        this.doSomething = function () {
            console.log("base class");
        };
    };

    DerivedClass1 = function () {
        var bc = new BaseClass();
        bc.doSomething = function () {
            console.log("derived class");
        };
        return bc;
    };

    DerivedClass2 = function () {
        var bc = new BaseClass();
        return bc;
    }


    var cls1 = new DerivedClass1();
    cls1.doSomething();

    var cls2 = new DerivedClass2();
    cls2.doSomething();


</script>

它是否属于任何已知的设计模式?

【问题讨论】:

  • 共享方法应该在prototype和construtor上的实例属性上,然后你可以用Object.create继承并像BaseClass.apply(this, arguments)一样调用super
  • 有很多关于原型和构造函数的知识。我试图在这里给出一个完整的答案:stackoverflow.com/a/16063711/1641941 DerivedClass2 没有做任何事情,你也可以使用 new BaseClass() ,因为这就是它返回的内容。

标签: javascript


【解决方案1】:

继承是一种将类创建为一个或多个类的专用版本的方法( 仅支持单一继承)。
专门的类通常称为子类,而另一个类通常称为父类。在JavaScript 中,您通过将父类的一个实例分配给子类,然后对其进行专门化来实现这一点。在现代浏览器中,您还可以使用Object.create 来实现继承。

注意:JavaScript 不检测子类prototype.constructor (见Object.prototype),所以我们必须手动声明。见 问题Why is it necessary to set the prototype constructor?

在下面的示例中,我们将 Student 类定义为 Person 的子类。
然后我们重新定义sayHello()方法并添加sayGoodBye()方法。

// Define the Person constructor
var Person = function(firstName) {
  this.firstName = firstName;
};

// Add a couple of methods to Person.prototype
Person.prototype.walk = function(){
  console.log("I am walking!");
};

Person.prototype.sayHello = function(){
  console.log("Hello, I'm " + this.firstName);
};

// Define the Student constructor
function Student(firstName, subject) {
  // Call the parent constructor, making sure (using call)
  // that "this" is set correctly during the call
  Person.call(this, firstName);

  // Initialize our Student-specific properties
  this.subject = subject;
}

// Create a Student.prototype object that inherits from Person.prototype.
// Note: A common error here is to use "new Person()" to create the
// Student.prototype. That's incorrect for several reasons, not least 
// that we don't have anything to give Person for the "firstName" 
// argument. The correct place to call Person is above, where we call 
// it from Student.
Student.prototype = Object.create(Person.prototype); // See note below

// Set the "constructor" property to refer to Student
Student.prototype.constructor = Student;

// Replace the "sayHello" method
Student.prototype.sayHello = function(){
  console.log("Hello, I'm " + this.firstName + ". I'm studying "
              + this.subject + ".");
};

// Add a "sayGoodBye" method
Student.prototype.sayGoodBye = function(){
  console.log("Goodbye!");
};

// Example usage:
var student1 = new Student("Janet", "Applied Physics");
student1.sayHello();   // "Hello, I'm Janet. I'm studying Applied Physics."
student1.walk();       // "I am walking!"
student1.sayGoodBye(); // "Goodbye!"

// Check that instanceof works correctly
console.log(student1 instanceof Person);  // true 
console.log(student1 instanceof Student); // true

【讨论】:

    【解决方案2】:

    TL;DR

    var BaseClass = function (bar) {
        this.foo = bar;
    };
    BaseClass.prototype.doSomething = function () {
        console.log("from base class");
    };
    
    var DerivedClass = function () {
        BaseClass.apply(this, arguments);
    };
    DerivedClass.prototype = Object.create(BaseClass.prototype);
    DerivedClass.prototype.doSomething = function () {
    
        BaseClass.prototype.doSomething.call(this);
        console.log("from derived class");
    };
    
    var cls1 = new DerivedClass('I am cls1');
    
    cls1.doSomething();
    // -> from base class
    // -> from derived class
    
    console.log(cls1.foo);
    // -> "I am cls1"
    

    更新

    感谢@HMR 的评论,我正在更新我的答案(请参阅下面的评论,非常有用):

    1. “在设置DerivedClass 的原型时,您不应该创建BaseClass 的新实例,请使用Object.create(如果需要,还可以填充它)”
    2. “您还忘记初始化 BaseClass 并通过拥有 BaseClass.apply(this,arguments) 来获取它的实例变量的所有权”

    1/ 使用Object.create

    var BaseClass = function () {
        this.foo = 'bar';
    };
    BaseClass.prototype.doSomething = function () {
        console.log("base class");
    };
    
    var DerivedClass = function () {
    
    };
    DerivedClass.prototype = Object.create(BaseClass.prototype);
    

    注意事项

    • Object.createBase 的原型“复制”到Derived
    • this.foo 的公共属性没有复制到 Derived(因为它不是原型的一部分)- 见下文第 2 点/

    更多关于Object.createhere的信息。

    2/BaseClass.apply(this, arguments)

    如上所述,this.foo 不适用于Derived 实例。为了使其可用,我们需要将 Base 构造函数应用到 Derived 构造函数中。

    所以Base (this.foo,..) 的所有privileged 属性都应用于Derived 的新实例。

    var DerivedClass = function () {
    
        // Calls `Base` constructor with `Derived` context
        BaseClass.apply(this, arguments);
    };
    DerivedClass.prototype = Object.create(BaseClass.prototype);
    

    关于流行的HMR's answer中的javascript继承的更多细节。


    为了比较和教育目的,我留下了我原来的答案。

    您的技术的问题(虽然它可以按预期工作)是doSomething 方法被复制到BaseClass 的每个实例(因为它被声明为简单的public 属性)。

    为避免这种情况,从而共享doSomething 方法在BaseClass 的所有实例中,您应该将其添加到prototypeBaseClass

    var BaseClass = function () {
    
    };
    
    BaseClass.prototype.doSomething = function () {
        console.log("base class");
    };
    

    您不会注意到最终结果有任何差异,但这样一来,doSomething 方法是“继承”,而不是复制。

    现在知道了,用Javascript实现prototypal inheritance

    // Derived Class 1
    var DerivedClass1 = function () {
    
    };
    DerivedClass1.prototype = new BaseClass();
    
    var cls1 = new DerivedClass1();
    cls1.doSomething();
    
    // -> "base class"
    
    
    // Derived Class 2
    var DerivedClass2 = function () {
    
    };
    DerivedClass2.prototype = new BaseClass();
    DerivedClass2.prototype.doSomething = function () {
        console.log("derived class (2)");
    };
    
    var cls2 = new DerivedClass1();
    cls2.doSomething();
    
    // -> "derived class (2)"
    

    奖励,如果你想从 DerivedClass 调用 parent 方法:

    // Derived Class 3
    var DerivedClass3 = function () {
    
    };
    DerivedClass3.prototype = new BaseClass();
    DerivedClass3.prototype.doSomething = function () {
    
        BaseClass.prototype.doSomething.call(this);
    
        console.log("derived class (3)");
    };
    
    var cls3 = new DerivedClass1();
    cls3.doSomething();
    
    // -> "base class"
    // -> "derived class (3)"
    

    【讨论】:

    • 在设置 Child 的原型时不应该创建新的 Parent 实例,使用 Object.create (如果需要,可以使用 polyfill)。或者使用辅助函数。您还忘记了通过 Parent.apply(this,arguments) 来初始化 Parent 并获得它的实例变量的所有权因为使用原型时需要注意很多事情,并且每天会问 2 次这样的问题,所以我添加了这个答案:@ 987654327@
    • 感谢您的见解!我玩弄了console,并更深入地了解了正在发生的事情。我更新了我的答案以反映您的评论。如果我错过了什么,请告诉我。
    • 哇,你很快就明白了,并给出了一个很好的答案+1。为了您的第一个代码块的完整性,我通常使用DerivedClass.prototype.constructor = DerivedClass; 作为构造函数,因为有时可以在实例中使用构造函数来指向创建它们的函数(它始终存在于原型上,但在设置继承时会被覆盖)。我明白为什么 Douglas Crockford 不是构造函数的忠实粉丝,因为它复杂且容易出错。但由于它是在 JS 中完成的方式,所以我个人坚持使用它,并希望 JS 解释器因此能够更好地运行我的代码
    • 注意一点:在 JavaScript 中,深度复制对象并不容易,而 Object.create 会生成所谓的浅拷贝。可以在此处找到为什么这可能是一个问题的示例:stackoverflow.com/a/19879651/1641941(带有注释的第二个代码块:“继承将成为问题:”)。没有在原型答案中添加它,因为我想坚持基础知识并且它已经足够长了。当您使用复杂的模式时可能会出现问题,但是您可能会使代码过于复杂,并且可以在其他地方解决问题。
    • 谢谢。很有启发性。
    【解决方案3】:

    您最好使用原型链在 JavaScript 中进行继承。 在此示例中,您正在为每次实例化重新定义对象。使用原型执行此操作更有效,因为您可以定义一次类型并重用定义..

    这里有更多信息: http://net.tutsplus.com/tutorials/javascript-ajax/prototypes-in-javascript-what-you-need-to-know/

    【讨论】:

      猜你喜欢
      • 2011-08-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-07
      • 2015-10-12
      • 1970-01-01
      • 2023-03-22
      相关资源
      最近更新 更多