6

我认为差异已经在我脑海中响起,但我想确定一下。

在 Douglas Crockford 页面Prototypal Inheritance in JavaScript上,他说

在原型系统中,对象继承自对象。然而,JavaScript 缺少执行该操作的运算符。相反,它有一个 new 运算符,这样 new 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.

我的理解是否正确,只有在测试对象本身的成员时差异才会变得明显?

4

2 回答 2

3

您的假设是正确的,但是 Douglas 没有过多谈论另一种模式 - 原型也可以用于属性。你的 person 类可以写成:

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:在构造函数中分配 name 和 age确实使 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.superclassPerson,但这并不理想。如果您曾经将 OldPerson 更改为从 Person 以外的类继承,则您还必须更新对 Person 的所有引用。

编辑 3:只是为了把这个完整的循环,这里是一个“继承”函数的版本,它利用了 Object.create 和功能与我之前描述的完全相同:

var inherits = function(childCtor, parentCtor) {
    childCtor.prototype = Object.create(parentCtor.prototype);
    childCtor.superclass = parentCtor.prototype; 
};
于 2012-04-28T09:16:44.640 回答
3

编辑:这个答案最初是对@jordancpaul 的回答的回应,他已经更正了。我将留下有助于解释原型属性和实例属性之间重要区别的部分答案:

在某些情况下,属性在所有实例之间共享的,每当您在原型上声明属性时都需要非常小心。考虑这个例子:

Person.prototype.favoriteColors = []; //Do not do this!

现在,如果您使用Object.createor创建一个新的 Person 实例new,它不会像您预期的那样工作......

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 检查不再起作用(本示例中的替代方法是 check person.isPrototypeOf(tim))。

我说 Object.create 冗长的主要原因是因为第二个参数,但是那里有一些有用的库可以解决这个问题:

https://github.com/Gozala/selfish

https://github.com/Raynos/pd

(和别的)

我希望这比令人困惑更具有启发性!

于 2012-11-10T15:12:24.140 回答