【问题标题】:In Javascript, the difference between 'Object.create' and 'new'在 Javascript 中,“Object.create”和“new”之间的区别
【发布时间】:2026-01-28 14:55:01
【问题描述】:

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

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

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

【问题讨论】:

  • 我想我昨天看到了这个问题,但我并不清楚答案。现在我已经进行了测试并输入了我的问题,很清楚!

标签: javascript inheritance prototype prototypal-inheritance


【解决方案1】:

您的假设是正确的,但是 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: 在构造函数 do 中分配 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.superclass 替换为Person,但这并不理想。如果您曾经将 OldPerson 更改为从 Person 以外的类继承,则还必须更新对 Person 的所有引用。

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

var inherits = function(childCtor, parentCtor) {
    childCtor.prototype = Object.create(parentCtor.prototype);
    childCtor.superclass = parentCtor.prototype; 
};

【讨论】:

    【解决方案2】:

    编辑:这个答案最初是对@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 检查不再起作用(本示例中的替代方法是检查 @987654331 @)。

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

    https://github.com/Gozala/selfish

    https://github.com/Raynos/pd

    (和其他人)

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

    【讨论】:

    • 我刚刚写了一个 very 简单的库来帮助继承更多的传统原型继承方法,但它在扩展“类”时使用 Object.create:@987654323 @
    • 是的,我自己也是Class.prototype.property = [] 陷阱的受害者。我已经用更多信息更新了我的原始帖子 - 也许你以前见过这种模式。