390

Javascript 1.9.3 / ECMAScript 5 引入Object.create了 Douglas Crockford 等人长期以来一直在提倡的。如何new在下面的代码中替换为Object.create

var UserA = function(nameParam) {
    this.id = MY_GLOBAL.nextId();
    this.name = nameParam;
}
UserA.prototype.sayHello = function() {
    console.log('Hello '+ this.name);
}
var bob = new UserA('bob');
bob.sayHello();

(假设MY_GLOBAL.nextId存在)。

我能想到的最好的是:

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};
var bob = Object.create(userB);
bob.init('Bob');
bob.sayHello();

似乎没有任何优势,所以我想我没有得到它。我可能太新古典主义了。我应该如何使用Object.create来创建用户'bob'?

4

15 回答 15

267

只有一层继承,您的示例可能无法让您看到Object.create.

这种方法可以让您轻松实现差异继承,其中对象可以直接从其他对象继承。

在您的userB示例中,我认为您的init方法不应该是公共的,甚至不应该存在,如果您在现有对象实例上再次调用此方法,则idandname属性将会改变。

Object.create允许您使用其第二个参数初始化对象属性,例如:

var userB = {
  sayHello: function() {
    console.log('Hello '+ this.name);
  }
};

var bob = Object.create(userB, {
  'id' : {
    value: MY_GLOBAL.nextId(),
    enumerable:true // writable:false, configurable(deletable):false by default
  },
  'name': {
    value: 'Bob',
    enumerable: true
  }
});

如您所见,属性可以在 的第二个参数上初始化,对象字面量使用类似于and方法Object.create使用的语法。Object.definePropertiesObject.defineProperty

它允许您设置属性属性(enumerablewritableconfigurable),这非常有用。

于 2010-04-25T20:18:42.683 回答
55

Object.create(...)使用over确实没有任何优势new object

提倡这种方法的人通常会说相当模糊的优点:“可扩展性”,或者“对 JavaScript 更自然”等。

但是,我还没有看到一个具体的例子表明Object.create使用new. 相反,它存在已知的问题。Sam Elsamman 描述了当存在嵌套对象并被Object.create(...)使用时会发生什么:

var Animal = {
    traits: {},
}
var lion = Object.create(Animal);
lion.traits.legs = 4;
var bird = Object.create(Animal);
bird.traits.legs = 2;
alert(lion.traits.legs) // shows 2!!!

这是因为Object.create(...)提倡使用数据创建新对象的做法;在这里,Animal基准成为 和 原型的一部分lionbird并在共享时引起问题。当使用 new 原型继承是明确的:

function Animal() {
    this.traits = {};
}

function Lion() { }
Lion.prototype = new Animal();
function Bird() { }
Bird.prototype = new Animal();

var lion = new Lion();
lion.traits.legs = 4;
var bird = new Bird();
bird.traits.legs = 2;
alert(lion.traits.legs) // now shows 4

关于传入的可选属性属性,Object.create(...)可以使用添加Object.defineProperties(...)

于 2012-10-03T09:03:26.990 回答
42

Object.create 在一些浏览器上还不是标准的,例如 IE8、Opera v11.5、Konq 4.3 都没有。您可以为这些浏览器使用 Douglas Crockford 的 Object.create 版本,但这不包括 CMS 答案中使用的第二个“初始化对象”参数。

对于跨浏览器代码,同时获取对象初始化的一种方法是自定义 Crockford 的 Object.create。这是一种方法:-

Object.build = function(o) {
   var initArgs = Array.prototype.slice.call(arguments,1)
   function F() {
      if((typeof o.init === 'function') && initArgs.length) {
         o.init.apply(this,initArgs)
      }
   }
   F.prototype = o
   return new F()
}

这维护了 Crockford 原型继承,并检查对象中的任何 init 方法,然后使用您的参数运行它,例如 new man('John','Smith')。然后您的代码变为:-

MY_GLOBAL = {i: 1, nextId: function(){return this.i++}}  // For example

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};
var bob = Object.build(userB, 'Bob');  // Different from your code
bob.sayHello();

所以 bob 继承了 sayHello 方法,现在有自己的属性 id=1 和 name='Bob'。当然,这些属性既可写又可枚举。这也是一种比 ECMA Object.create 更简单的初始化方法,尤其是在您不关心可写、可枚举和可配置属性的情况下。

对于没有 init 方法的初始化,可以使用以下 Crockford mod:-

Object.gen = function(o) {
   var makeArgs = arguments 
   function F() {
      var prop, i=1, arg, val
      for(prop in o) {
         if(!o.hasOwnProperty(prop)) continue
         val = o[prop]
         arg = makeArgs[i++]
         if(typeof arg === 'undefined') break
         this[prop] = arg
      }
   }
   F.prototype = o
   return new F()
}

这将按照定义的顺序填充 userB 自己的属性,在 userB 参数之后从左到右使用 Object.gen 参数。它使用 for(prop in o) 循环,因此根据 ECMA 标准,不能保证属性枚举的顺序与属性定义的顺序相同。但是,在 (4) 个主要浏览器上测试的几个代码示例表明它们是相同的,前提是使用了 hasOwnProperty 过滤器,有时即使没有使用。

MY_GLOBAL = {i: 1, nextId: function(){return this.i++}};  // For example

var userB = {
   name: null,
   id: null,
   sayHello: function() {
      console.log('Hello '+ this.name);
   }
}

var bob = Object.gen(userB, 'Bob', MY_GLOBAL.nextId());

我会说比 Object.build 更简单一些,因为 userB 不需要 init 方法。userB 也不是专门的构造函数,但看起来像一个普通的单例对象。因此,使用这种方法,您可以从普通的普通对象构造和初始化。

于 2011-07-04T12:10:39.023 回答
28

TL;博士:

new Computer()将调用构造函数Computer(){}一次,而Object.create(Computer.prototype)不会。

所有的优点都基于这一点。

关于性能的旁注:构造函数调用 likenew Computer()已被引擎大量优化,因此它可能比Object.create.

于 2014-09-12T09:15:28.053 回答
14

您可以使init方法 return this,然后将调用链接在一起,如下所示:

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
        return this;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};

var bob = Object.create(userB).init('Bob');
于 2012-08-03T16:37:59.860 回答
10

Object.create 的另一种可能用法是以廉价而有效的方式克隆不可变对象。

var anObj = {
    a: "test",
    b: "jest"
};

var bObj = Object.create(anObj);

bObj.b = "gone"; // replace an existing (by masking prototype)
bObj.c = "brand"; // add a new to demonstrate it is actually a new obj

// now bObj is {a: test, b: gone, c: brand}

注意:上面的代码片段创建了一个源对象的克隆(也不是引用,如 cObj = aObj)。它优于复制属性方法(参见1),因为它不复制对象成员属性。相反,它创建另一个目标对象,其原型设置在源对象上。此外,当在 dest 对象上修改属性时,它们是“动态”创建的,掩盖了原型(src)的属性。这构成了克隆不可变对象的快速有效方法。

这里需要注意的是,这适用于在创建后不应修改的源对象(不可变)。如果在创建后修改了源对象,则克隆的所有未屏蔽属性也将被修改。

在这里摆弄(http://jsfiddle.net/y5b5q/1/)(需要支持 Object.create 的浏览器)。

于 2012-09-06T16:26:45.063 回答
7

我认为有问题的要点 - 是理解newObject.create方法之间的区别。根据这个答案这个视频 new关键字做接下来的事情:

  1. 创建新对象。

  2. 将新对象链接到构造函数 ( prototype)。

  3. 使this变量指向新对象。

  4. 使用新对象执行构造函数并隐式执行return this

  5. 将构造函数名称分配给新对象的属性constructor

Object.create只执行1st2nd步骤!!!

在有问题的代码示例中,这没什么大不了的,但在下一个示例中是:

var onlineUsers = [];
function SiteMember(name) {
    this.name = name;
    onlineUsers.push(name);
}
SiteMember.prototype.getName = function() {
    return this.name;
}
function Guest(name) {
    SiteMember.call(this, name);
}
Guest.prototype = new SiteMember();

var g = new Guest('James');
console.log(onlineUsers);

由于副作用结果将是:

[ undefined, 'James' ]

因为Guest.prototype = new SiteMember();
但是我们不需要执行父构造方法,我们只需要让方法getName在Guest中可用。因此我们必须使用Object.create.
如果替换结果是Guest.prototype = new SiteMember();
Guest.prototype = Object.create(SiteMember.prototype);

[ 'James' ]
于 2017-07-27T19:39:38.867 回答
6

有时您无法使用 NEW 创建对象,但仍然能够调用 CREATE 方法。

例如:如果你想定义一个自定义元素,它必须从 HTMLElement 派生。

proto = new HTMLElement  //fail :(
proto = Object.create( HTMLElement.prototype )  //OK :)
document.registerElement( "custom-element", { prototype: proto } )
于 2015-08-04T09:34:42.713 回答
4

优点是Object.create通常比new大多数浏览器慢

在这个 jsperf 示例中,在 Chromium 中,浏览器的速度是浏览器new30 倍,尽管Object.create(obj)两者都非常快。这一切都很奇怪,因为 new 做了更多的事情(比如调用构造函数),其中 Object.create 应该只是用传入的对象作为原型创建一个新对象(Crockford-speak 中的秘密链接)

也许浏览器还没有赶上提高Object.create效率(也许他们是在new幕后……甚至在本机代码中)

于 2014-06-12T22:27:46.087 回答
4

概括:

  • Object.create()是一个 Javascript 函数,它接受 2 个参数并返回一个新对象。
  • 第一个参数是一个对象,它将是新创建对象的原型
  • 第二个参数是一个对象,它将是新创建对象的属性

例子:

const proto = {
  talk : () => console.log('hi')
}

const props = {
  age: {
    writable: true,
    configurable: true,
    value: 26
  }
}


let Person = Object.create(proto, props)

console.log(Person.age);
Person.talk();

实际应用:

  1. 以这种方式创建对象的主要优点是可以显式定义原型。当使用对象文字或new关键字时,您无法控制它(当然,您可以覆盖它们)。
  2. 如果我们想要一个原型new关键字调用一个构造函数。无需调用甚至声明构造Object.create()函数。
  3. 当您想以非常动态的方式创建对象时,它基本上是一个有用的工具。我们可以创建一个对象工厂函数,根据接收到的参数创建具有不同原型的对象。
于 2018-08-19T08:41:20.473 回答
3

您必须制作自定义Object.create()功能。一种解决 Crockfords 问题并调用您的 init 函数的方法。

这将起作用:

var userBPrototype = {
    init: function(nameParam) {
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};


function UserB(name) {
    function F() {};
    F.prototype = userBPrototype;
    var f = new F;
    f.init(name);
    return f;
}

var bob = UserB('bob');
bob.sayHello();

这里的 UserB 类似于 Object.create,但根据我们的需要进行了调整。

如果你愿意,你也可以打电话:

var bob = new UserB('bob');
于 2010-04-25T22:37:26.103 回答
3

new操作员

  • 这用于从构造函数创建对象
  • new关键字也执行构造函数
function Car() {
  console.log(this) // this points to myCar
  this.name = "Honda";
}

var myCar = new Car()
console.log(myCar) // Car {name: "Honda", constructor: Object}
console.log(myCar.name) // Honda
console.log(myCar instanceof Car) // true
console.log(myCar.constructor) // function Car() {}
console.log(myCar.constructor === Car) // true
console.log(typeof myCar) // object

对象.create

  • 也可以Object.create用来创建新对象
  • 但是,它不执行构造函数
  • Object.create用于从另一个对象创建一个对象
const Car = {
  name: "Honda"
}

var myCar = Object.create(Car)
console.log(myCar) // Object {}
console.log(myCar.name) // Honda
console.log(myCar instanceof Car) // ERROR
console.log(myCar.constructor) // Anonymous function object
console.log(myCar.constructor === Car) // false
console.log(typeof myCar) // object

于 2020-03-15T08:21:20.027 回答
3

虽然 Douglas Crockford 曾经是 Object.create() 的热心拥护者,而且他基本上是这个构造实际上是在 javascript 中的原因,但他不再有这种观点。

他停止使用 Object.create,因为他完全停止使用这个关键字,因为它会造成太多麻烦。例如,如果您不小心,它很容易指向全局对象,这可能会产生非常糟糕的后果。他声称不使用这个Object.create 不再有意义。

您可以查看 2014 年他在 Nordic.js 上演讲的这段视频:

https://www.youtube.com/watch?v=PSGEjv3Tqo0

在此处输入图像描述

于 2018-02-01T19:30:35.740 回答
3

newObject.create服务于不同的目的。new旨在创建对象类型的新实例。Object.create旨在简单地创建一个新对象并设置其原型。为什么这很有用?__proto__在不访问属性的情况下实现继承。对象实例的原型被称为[[Prototype]]是虚拟机的内部属性,并不打算直接访问。实际上可以直接访问[[Prototype]]__proto__属性的唯一原因是因为它一直是每个主要虚拟机的 ECMAScript 实现的事实标准,此时删除它会破坏许多现有代码。

针对 7ochem 的上述回答,对象绝对不应该将其原型设置为new语句的结果,不仅因为多次调用同一个原型构造函数没有意义,而且因为同一类的两个实例最终可能会如果一个人的原型在创建后被修改,则会出现不同的行为。由于误解和破坏了原型继承链的预期行为,这两个示例都只是糟糕的代码。

而不是访问__proto__,实例的原型应该在创建时写入Object.create或之后使用orObject.setPrototypeOf读取,并使用Object.getPrototypeOfor读取Object.isPrototypeOf

此外,正如Object.setPrototypeOf 的 Mozilla 文档指出的那样,出于性能原因,在创建对象后修改其原型是一个坏主意,此外,在创建对象后修改其原型可能会导致未定义如果访问它的给定代码片段可以在修改原型之前或之后执行,则行为,除非该代码非常小心地检查当前原型或不访问两者之间不同的任何属性。

给定

const X = function (v) { this.v = v }; X.prototype.whatAmI = 'X'; X.prototype.getWhatIAm = () => this.whatAmI; X.prototype.getV = () => this.v;

以下 VM 伪代码等价于语句const x0 = new X(1);

const x0 = {}; x0.[[Prototype]] = X.prototype; X.prototype.constructor.call(x0, 1);

注意虽然构造函数可以返回任何值,但new语句始终忽略其返回值并返回对新创建对象的引用。

并且下面的伪代码等价于语句const x1 = Object.create(X.prototype);

const x0 = {}; x0.[[Prototype]] = X.prototype;

正如你所看到的,两者之间的唯一区别是Object.create不执行构造函数,构造函数实际上可以返回任何值,但this如果没有另外指定,则只是返回新的对象引用。

现在,如果我们想创建一个具有以下定义的子类 Y:那么我们 可以通过写入 来

const Y = function(u) { this.u = u; } Y.prototype.whatAmI = 'Y'; Y.prototype.getU = () => this.u;

使其继承 X设置原型的构造函数属性,以便语句调用正确的构造函数,否则将调用函数。如果程序员确实想调用,那么在 Y 的构造函数中使用__proto__

Y.prototype.__proto__ = X.prototype;

__proto__

Y.prototype = Object.create(X.prototype); Y.prototype.constructor = Y;

new Ynew YXnew YXX.call(this, u)

于 2018-12-08T21:04:24.183 回答
2

I prefer a closure approach.

I still use new. I don't use Object.create. I don't use this.

I still use new as I like the declarative nature of it.

Consider this for simple inheritance.

window.Quad = (function() {

    function Quad() {

        const wheels = 4;
        const drivingWheels = 2;

        let motorSize = 0;

        function setMotorSize(_) {
            motorSize = _;
        }

        function getMotorSize() {
            return motorSize;
        }

        function getWheelCount() {
            return wheels;
        }

        function getDrivingWheelCount() {
            return drivingWheels;
        }
        return Object.freeze({
            getWheelCount,
            getDrivingWheelCount,
            getMotorSize,
            setMotorSize
        });
    }

    return Object.freeze(Quad);
})();

window.Car4wd = (function() {

    function Car4wd() {
        const quad = new Quad();

        const spareWheels = 1;
        const extraDrivingWheels = 2;

        function getSpareWheelCount() {
            return spareWheels;
        }

        function getDrivingWheelCount() {
            return quad.getDrivingWheelCount() + extraDrivingWheels;
        }

        return Object.freeze(Object.assign({}, quad, {
            getSpareWheelCount,
            getDrivingWheelCount
        }));
    }

    return Object.freeze(Car4wd);
})();

let myQuad = new Quad();
let myCar = new Car4wd();
console.log(myQuad.getWheelCount()); // 4
console.log(myQuad.getDrivingWheelCount()); // 2
console.log(myCar.getWheelCount()); // 4
console.log(myCar.getDrivingWheelCount()); // 4 - The overridden method is called
console.log(myCar.getSpareWheelCount()); // 1

Feedback encouraged.

于 2018-02-08T08:54:50.913 回答