在Javascript中,'Object.create'和'new'之间的区别

时间:2012-04-28 08:13:30

标签: javascript inheritance prototype prototypal-inheritance

我认为差异已经在我的脑海中点击,但我只是想确定。

在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.

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

2 个答案:

答案 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)

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

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

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

现在,如果您使用Object.createnew创建新的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

https://github.com/Raynos/pd

(和其他人)

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