我认为差异已经在我的脑海中点击,但我只是想确定。
在Douglas Crockford页面上Prototypal Inheritance in JavaScript,他说
在原型系统中,对象继承自对象。 JavaScript中, 但是,缺少执行该操作的操作员。相反它 有一个新的运算符,这样新的f()就会产生一个新的对象 继承自f.prototype。
我真的不明白他在那句话中想说的是什么所以我进行了一些测试。在我看来,关键的区别在于,如果我在纯原型系统中基于另一个对象创建一个对象,那么所有父父成员应该在新对象的原型上,而不是在新对象本身上。
以下是测试:
var Person = function(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.toString = function(){return this.name + ', ' + this.age};
// The old way...
var jim = new Person("Jim",13);
for (n in jim) {
if (jim.hasOwnProperty(n)) {
console.log(n);
}
}
// This will output 'name' and 'age'.
// The pure way...
var tim = Object.create(new Person("Tim",14));
for (n in tim) {
if (tim.hasOwnProperty(n)) {
console.log(n);
}
}
// This will output nothing because all the members belong to the prototype.
// If I remove the hasOwnProperty check then 'name' and 'age' will be output.
我的理解是否正确,在测试对象本身的成员时,差异才会变得明显?
答案 0 :(得分:3)
你的假设是正确的,但道格拉斯并没有太多谈论的另一种模式 - 原型也可以用于属性。您的人员课程可以写成:
var Person = function(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.name = null; //default value if you don't init in ctor
Person.prototype.age = null;
Person.prototype.gender = "male";
Person.prototype.toString = function(){return this.name + ', ' + this.age;};
在这种情况下,如在您的示例中那样,迭代此类的实例的属性将不会为“性别”生成任何输出。属性。
编辑1: 在构造函数 do 中分配名称和年龄使得属性可以通过hasOwnProperty显示(感谢@matt提醒我这一点)。在有人在实例上设置之前,未分配的性别属性将不可见。
编辑2: 为了进一步补充这一点,我提出了另一种继承模式 - 我个人用于非常大的项目:
var inherits = function(childCtor, parentCtor) {
function tempCtor() {};
tempCtor.prototype = parentCtor.prototype;
childCtor.superclass = parentCtor.prototype;
childCtor.prototype = new tempCtor();
childCtor.prototype.constructor = childCtor;
};
var Person = function(name){
this.name = name;
}
Person.prototype.name = "";
Person.prototype.toString = function(){
return "My name is " + this.name;
}
var OldPerson = function(name, age){
OldPerson.superclass.constructor.call(this);
this.age = age
};
inherits(OldPerson, Person);
OldPerson.prototype.age = 0;
OldPerson.prototype.toString = function(){
var oldString = OldPerson.superclass.toString.call(this);
return oldString + " and my age is " + this.age;
}
这是一种相当普遍的模式,只有一点点扭曲 - 父类通过"超类"附加到孩子身上。允许您访问由孩子覆盖的方法/属性的属性。从技术上讲,您可以将OldPerson.superclass
替换为Person
,但这并不理想。如果您曾将OldPerson更改为从Person以外的类继承,则您还必须更新对Person的所有引用。
编辑3: 为了实现这个完整的循环,这里是"继承"的一个版本。利用Object.create和函数完全相同的函数:
var inherits = function(childCtor, parentCtor) {
childCtor.prototype = Object.create(parentCtor.prototype);
childCtor.superclass = parentCtor.prototype;
};
答案 1 :(得分:3)
在某些情况下,属性 在所有实例之间共享,每当您在原型上声明属性时,您都需要非常小心。考虑这个例子:
Person.prototype.favoriteColors = []; //Do not do this!
现在,如果您使用Object.create
或new
创建新的Person实例,则它无法正常工作...
var jim = new Person("Jim",13);
jim.favoriteColors.push('red');
var tim = new Person("Tim",14);
tim.favoriteColors.push('blue');
console.log(tim.favoriteColors); //outputs an array containing red AND blue!
这并不意味着您不能在原型上声明属性,但如果您这样做,那么您和每个开发代码的开发人员都需要了解这个陷阱。在这种情况下,如果您希望出于某种原因在原型上声明属性,您可以这样做:
Person.prototype.favoriteColors = null
并将其初始化为构造函数中的空数组:
var Person = function(name, age) {
...
this.favoriteColors = [];
}
使用此方法时的一般规则是,可以直接在原型上设置简单文字属性(字符串,数字,布尔值)的默认值,但是任何从Object继承的属性(包括数组和日期)都应设置为null,然后在构造函数中初始化。
更安全的方法是只在原型上声明方法,并始终在构造函数中声明属性。
无论如何,问题是关于Object.create ......
传递给Object.create的第一个参数被设置为新实例的原型。更好的用法是:
var person = {
initialize: function(name, age) {
this.name = name;
this.age = age;
return this;
},
toString: function() {
return this.name + ', ' + this.age;
}
};
var tim = Object.create(person).initialize("Tim",14);
现在输出与第一个示例中的输出相同。
正如您所看到的,这是一种与Javascript中更经典的OOP风格不同的哲学方法。使用Object.create,重点是从现有对象而不是构造函数创建新对象。然后初始化成为一个单独的步骤。
我个人对Object.create方法感到复杂;它非常适合继承,因为您可以使用第二个参数向现有原型添加其他属性,但它也更冗长,因此instanceof检查不再起作用(此示例中的替代方法是检查{{ 1}})。
我说Object.create的主要原因很简单是因为第二个参数,但是有一些有用的库可以解决这个问题:
https://github.com/Gozala/selfish
(和其他人)
我希望这比令人困惑更具启发性!