JavaScript基于原型的继承的好例子

89

我已经使用面向对象编程语言编程超过十年,但现在我正在学习JavaScript,这是我第一次遇到基于原型的继承。我倾向于通过研究好的代码来快速学习。有没有一个很好的JavaScript应用程序(或库)的例子,它正确地使用了原型继承?你能简要描述一下原型继承是如何/在哪里使用的吗,这样我就知道从哪里开始阅读了?


1
你有机会查看那个Base库吗?它真的很好,而且非常小。如果你喜欢它,请考虑将我的答案标记为答案。谢谢,roland。 - Roland Bouman
我想我和你处于同一条船上。我也想学习一些有关原型语言的知识,不仅局限于oop框架或类似的东西,尽管它们很棒,但我们需要学习,对吧?不只是让某个框架为我所用,即使我会使用它。而是学习如何用新的方式在新的语言中创建新的东西,打破常规思维。我喜欢你的风格。我会尝试帮助自己,也许能帮到你。一旦我找到了什么,我会告诉你的。 - marcelo-ferraz
11个回答

75

如前所述,Douglas Crockford的电影很好地解释了为什么以及涵盖了如何。但是,用几行JavaScript来表述:

// Declaring our Animal object
var Animal = function () {

    this.name = 'unknown';

    this.getName = function () {
        return this.name;
    }

    return this;
};

// Declaring our Dog object
var Dog = function () {

    // A private variable here        
    var private = 42;

    // overriding the name
    this.name = "Bello";

    // Implementing ".bark()"
    this.bark = function () {
        return 'MEOW';
    }  

    return this;
};


// Dog extends animal
Dog.prototype = new Animal();

// -- Done declaring --

// Creating an instance of Dog.
var dog = new Dog();

// Proving our case
console.log(
    "Is dog an instance of Dog? ", dog instanceof Dog, "\n",
    "Is dog an instance of Animal? ", dog instanceof Animal, "\n",
    dog.bark() +"\n", // Should be: "MEOW"
    dog.getName() +"\n", // Should be: "Bello"
    dog.private +"\n" // Should be: 'undefined'
);

然而,这种方法的问题在于每次创建对象时都会重新创建该对象。另一种方法是将对象声明在原型堆栈上,如下所示:

// Defining test one, prototypal
var testOne = function () {};
testOne.prototype = (function () {
    var me = {}, privateVariable = 42;
    me.someMethod = function () {
        return privateVariable;
    };

    me.publicVariable = "foo bar";
    me.anotherMethod = function () {
        return this.publicVariable;
    };

    return me;

}());


// Defining test two, function
var testTwo = ​function() {
    var me = {}, privateVariable = 42;
    me.someMethod = function () {
        return privateVariable;
    };

    me.publicVariable = "foo bar";
    me.anotherMethod = function () {
        return this.publicVariable;
    };

    return me;
};


// Proving that both techniques are functionally identical
var resultTestOne = new testOne(),
    resultTestTwo = new testTwo();

console.log(
    resultTestOne.someMethod(), // Should print 42
    resultTestOne.publicVariable // Should print "foo bar"
);

console.log(
    resultTestTwo.someMethod(), // Should print 42
    resultTestTwo.publicVariable // Should print "foo bar"
);



// Performance benchmark start
var stop, start, loopCount = 1000000;

// Running testOne
start = (new Date()).getTime(); 
for (var i = loopCount; i>0; i--) {
    new testOne();
}
stop = (new Date()).getTime();

console.log('Test one took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds');



// Running testTwo
start = (new Date()).getTime(); 
for (var i = loopCount; i>0; i--) {
    new testTwo();
}
stop = (new Date()).getTime();

console.log('Test two took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds');

在反思方面有一个小缺点。删除testOne会导致信息不太有用。此外,在testOne中的私有属性“privateVariable”在所有实例中共享,这也是shesek在回复中提到的。


3
请注意,在 testOne 函数中,privateVariable 只是 IIFE 作用域内的一个变量,并在所有实例之间共享,因此您不应该将实例特定的数据存储在其中。(在 testTwo 中,它是实例特定的,因为每次调用 testTwo() 都会创建一个新的、针对每个实例的作用域。) - shesek
我点赞了,因为你展示了另一种方法,并解释了为什么不要使用它,因为它会复制。 - Murphy316
每次重新创建对象的问题主要是由于每个新对象都会重新创建方法。但是,我们可以通过在Dog.prototype上定义方法来缓解这个问题。因此,我们可以在Dog函数之外使用Dot.prototype.bark = function () {...}而不是使用this.bark = function () {...}。(有关更多详细信息,请参见此答案 - Huang C.

49
道格拉斯·克罗克福德有一篇关于JavaScript原型继承的好文章:JavaScript Prototypal Inheritance

五年前,我写了JavaScript中的经典继承。它展示了JavaScript是一种无类、基于原型的语言,并且它具有足够的表达能力来模拟一个经典系统。我的编程风格自那时以来已经进化,就像任何好的程序员一样。我已经学会了完全接受原型主义,并从经典模型的限制中解放了自己。

迪恩·爱德华兹的Base.js、Mootools的Class或约翰·雷西格的简单继承是在JavaScript中进行经典继承的方式。


如果你想要一个无类的对象,为什么不直接使用 newObj = Object.create(oldObj); 呢?否则,将 oldObj 替换为构造函数的原型对象应该也可以工作。 - Cyker

26
function Shape(x, y) {
    this.x = x;
    this.y = y;
}

// 1. Explicitly call base (Shape) constructor from subclass (Circle) constructor passing this as the explicit receiver
function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r = r;
}

// 2. Use Object.create to construct the subclass prototype object to avoid calling the base constructor
Circle.prototype = Object.create(Shape.prototype);

3
或许在你的回答中添加这个链接会更加完整:https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/create - Dynom
不要忘记更正Circle.prototype.constructor属性,它将指向错误的东西(Shape而不是Circle)。最后添加:Circle.prototype.constructor = Circle; - T.J. Crowder

14

YUI 2已于2011年被弃用,因此“lang”的链接已经半失效。有人愿意为YUI 3修复它吗? - ack
在 YUI 3 中,似乎 lang 没有 extend 方法。但由于答案打算使用实现作为示例,所以版本并不重要。 - eMBee

5

这是我发现的最清晰的例子,来自Mixu的Node书(http://book.mixu.net/node/ch6.html):

I favor composition over inheritance:

Composition - Functionality of an object is made up of an aggregate of different classes by containing instances of other objects. Inheritance - Functionality of an object is made up of it's own functionality plus functionality from its parent classes. If you must have inheritance, use plain old JS

If you must implement inheritance, at least avoid using yet another nonstandard implementation / magic function. Here is how you can implement a reasonable facsimile of inheritance in pure ES3 (as long as you follow the rule of never defining properties on prototypes):

function Animal(name) {
  this.name = name;
};
Animal.prototype.move = function(meters) {
  console.log(this.name+" moved "+meters+"m.");
};

function Snake() {
  Animal.apply(this, Array.prototype.slice.call(arguments));
};
Snake.prototype = new Animal();
Snake.prototype.move = function() {
  console.log("Slithering...");
  Animal.prototype.move.call(this, 5);
};

var sam = new Snake("Sammy the Python");
sam.move();

This is not the same thing as classical inheritance - but it is standard, understandable Javascript and has the functionality that people mostly seek: chainable constructors and the ability to call methods of the superclass.


5

ES6 classextends

ES6 classextends只是为先前可能的原型链操作提供语法糖,因此可以说是最经典的设置。

首先要了解有关原型链和 . 属性查找的更多信息,请访问:https://dev59.com/a3RB5IYBdhLWcg3wn4UL#23877420

现在让我们来分解发生的事情:

class C {
    constructor(i) {
        this.i = i
    }
    inc() {
        return this.i + 1
    }
}

class D extends C {
    constructor(i) {
        super(i)
    }
    inc2() {
        return this.i + 2
    }
}

// Inheritance syntax works as expected.
(new C(1)).inc() === 2
(new D(1)).inc() === 2
(new D(1)).inc2() === 3

// "Classes" are just function objects.
C.constructor === Function
C.__proto__ === Function.prototype
D.constructor === Function
// D is a function "indirectly" through the chain.
D.__proto__ === C
D.__proto__.__proto__ === Function.prototype

// "extends" sets up the prototype chain so that base class
// lookups will work as expected
var d = new D(1)
d.__proto__ === D.prototype
D.prototype.__proto__ === C.prototype
// This is what `d.inc` actually does.
d.__proto__.__proto__.inc === C.prototype.inc

// Class variables
// No ES6 syntax sugar apparently:
// https://dev59.com/YGEh5IYBdhLWcg3wVCPK
C.c = 1
C.c === 1
// Because `D.__proto__ === C`.
D.c === 1
// Nothing makes this work.
d.c === undefined

简化的图示,没有所有预定义的对象:

      __proto__
(C)<---------------(D)         (d)
| |                |           |
| |                |           |
| |prototype       |prototype  |__proto__
| |                |           |
| |                |           |
| |                | +---------+
| |                | |
| |                | |
| |                v v
|__proto__        (D.prototype)
| |                |
| |                |
| |                |__proto__
| |                |
| |                |
| | +--------------+
| | |
| | |
| v v
| (C.prototype)--->(inc)
|
v
Function.prototype

5

1

0

我见过的最好的例子在Douglas Crockford的JavaScript: The Good Parts中。它绝对值得购买,以帮助您获得对该语言的平衡看法。

Douglas Crockford负责JSON格式,并在雅虎担任JavaScript大师。


7
负责?这听起来几乎像是“有罪”的意思 :) - Roland Bouman
@Roland 我认为JSON是一种非常好的非冗长格式来存储数据。但他并不是发明者,早在2002年Steam的配置设置中就已经存在了这种格式。 - Chris S
Chris S,我也这么认为 - 越来越多的时候,我希望我们可以跳过XML作为交换格式,直接使用JSON。 - Roland Bouman
3
没什么可发明的:JSON是JavaScript自身对象字面语法的一个子集,该语法自1997年左右就已经存在于这门语言中了。 - Tim Down
@Time 很好的观点 - 我没有意识到它从一开始就在那里。 - Chris S
不是从一开始就有的:我想对象字面量最初出现在 Netscape 4 中,这是在 1997 年发布的,而 Netscape 2 是第一个在 1995 年支持 JavaScript 的浏览器。 - Tim Down

0

这里有一段代码片段 JavaScript 基于原型的继承 ,包含了ECMAScript 版本特定的实现。它会根据当前运行时自动选择使用 ES6、ES5 或 ES3 实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接