我使用OOP语言编程已有10多年了,但是现在我正在学习JavaScript,这是我第一次遇到基于原型的继承。我倾向于通过学习良好的代码来学习最快。正确使用原型继承的JavaScript应用程序(或库)的写得很好的示例是什么?并且您能(简要地)描述如何/在何处使用原型继承,所以我知道从哪里开始阅读?
我使用OOP语言编程已有10多年了,但是现在我正在学习JavaScript,这是我第一次遇到基于原型的继承。我倾向于通过学习良好的代码来学习最快。正确使用原型继承的JavaScript应用程序(或库)的写得很好的示例是什么?并且您能(简要地)描述如何/在何处使用原型继承,所以我知道从哪里开始阅读?
Answers:
Douglas Crockford在JavaScript Prototypal Inheritance上有一个漂亮的页面:
五年前,我用JavaScript 编写了古典继承。它表明JavaScript是一种无类的原型语言,并且它具有足够的表达能力来模拟经典系统。从那以后,我的编程风格得到了发展,就像任何优秀程序员一样。我学会了完全接受原型主义,并摆脱了古典模型的束缚。
Dean Edward的Base.js,Mootools的Class或John Resig的Simple Inheritance作品是在JavaScript中进行经典继承的方法。
newObj = Object.create(oldObj);
如果您希望它不上课呢?否则,用oldObj
构造函数的原型对象替换应该可以工作吗?
如前所述,道格拉斯·克罗克福德(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的答复中也有帮助提及。
privateVariable
它只是IIFE 范围内的变量,并且在所有实例之间共享,因此您不应在其上存储实例特定的数据。(在testTwo上,它是特定于实例的,因为每次调用testTwo()都会创建一个新的,按实例的作用域)
Dog.prototype
。因此this.bark = function () {...}
,Dot.prototype.bark = function () {...}
除了使用,我们还可以在Dog
函数之外进行操作。(查看此答案的更多详细信息)
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);
我将看一下YUI和Dean Edward的Base
库:http : //dean.edwards.name/weblog/2006/03/base/
对于YUI,您可以快速浏览一下lang模块 esp。该YAHOO.lang.extend方法。然后,您可以浏览一些小部件或实用程序的源代码,并查看它们如何使用该方法。
lang
是半断的。有人愿意为YUI 3修复它吗?
还有Microsoft的ASP.NET Ajax库,http://www.asp.net/ajax/。
关于MSDN的文章也很多,包括使用面向对象技术创建高级Web应用程序。
这是我在Mixu的Node书(http://book.mixu.net/node/ch6.html)中找到的最清晰的示例:
我偏爱继承而不是继承:
组成-对象的功能通过包含其他对象的实例由不同类的集合组成。继承-对象的功能由其自身的功能以及其父类的功能组成。如果必须继承,请使用普通的旧JS
如果必须实现继承,至少应避免使用另一个非标准实现/魔术函数。这是您可以在纯ES3中实现合理的继承传真的方式(只要您遵循从不定义原型属性的规则):
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();
这与经典继承不同,但是它是标准的,可理解的Javascript,并且具有人们通常会寻求的功能:可链接的构造函数和调用超类方法的能力。
ES6 class
和extends
ES6 class
和ES6 extends
仅是以前可能进行的原型链操作的语法糖,因此可以说是最规范的设置。
首先.
在以下网址了解有关原型链和属性查找的更多信息:https : //stackoverflow.com/a/23877420/895245
现在让我们解构发生的事情:
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:
// /programming/22528967/es6-class-variable-alternatives
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
我建议看一下PrototypeJS的Class.create:
第83行@ http://prototypejs.org/assets/2009/8/31/prototype.js
我见过的最好的例子是Douglas Crockford的JavaScript:The Good Parts。绝对值得购买,以帮助您对这种语言有一个平衡的看法。
Douglas Crockford负责JSON格式,并在Yahoo担任JavaScript专家。
有一段基于JavaScript原型的继承,带有ECMAScript版本特定的实现。它将根据当前运行时自动选择在ES6,ES5和ES3实现之间使用哪个。
在Javascript中添加基于原型的继承的示例。
// Animal Class
function Animal (name, energy) {
this.name = name;
this.energy = energy;
}
Animal.prototype.eat = function (amount) {
console.log(this.name, "eating. Energy level: ", this.energy);
this.energy += amount;
console.log(this.name, "completed eating. Energy level: ", this.energy);
}
Animal.prototype.sleep = function (length) {
console.log(this.name, "sleeping. Energy level: ", this.energy);
this.energy -= 1;
console.log(this.name, "completed sleeping. Energy level: ", this.energy);
}
Animal.prototype.play = function (length) {
console.log(this.name, " playing. Energy level: ", this.energy);
this.energy -= length;
console.log(this.name, "completed playing. Energy level: ", this.energy);
}
// Dog Class
function Dog (name, energy, breed) {
Animal.call(this, name, energy);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function () {
console.log(this.name, "barking. Energy level: ", this.energy);
this.energy -= 1;
console.log(this.name, "done barking. Energy level: ", this.energy);
}
Dog.prototype.showBreed = function () {
console.log(this.name,"'s breed is ", this.breed);
}
// Cat Class
function Cat (name, energy, male) {
Animal.call(this, name, energy);
this.male = male;
}
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
Cat.prototype.meow = function () {
console.log(this.name, "meowing. Energy level: ", this.energy);
this.energy -= 1;
console.log(this.name, "done meowing. Energy level: ", this.energy);
}
Cat.prototype.showGender = function () {
if (this.male) {
console.log(this.name, "is male.");
} else {
console.log(this.name, "is female.");
}
}
// Instances
const charlie = new Dog("Charlie", 10, "Labrador");
charlie.bark();
charlie.showBreed();
const penny = new Cat("Penny", 8, false);
penny.meow();
penny.showGender();
ES6使用构造函数和超级关键字使用继承的实现要容易得多。