webentwicklung-frage-antwort-db.com.de

Verwenden von "Object.create" anstelle von "new"

Javascript 1.9.3/ECMAScript 5 führt Object.create ein, zu dem Douglas Crockford unter anderem advocating seit langem gehört. Wie ersetze ich new im Code durch 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();

(Angenommen, MY_GLOBAL.nextId ist vorhanden).

Das Beste, was ich finden kann, ist:

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();

Es scheint keinen Vorteil zu geben, also verstehe ich es nicht. Ich bin wahrscheinlich zu neoklassisch. Wie soll ich mit Object.create den Benutzer 'bob' erstellen?

346
Graham King

Mit nur einer Vererbungsebene kann Ihr Beispiel die tatsächlichen Vorteile von Object.create nicht erkennen.

Diese Methoden ermöglichen die einfache Implementierung von differentieller Vererbung, bei der Objekte direkt von anderen Objekten erben können.

In Ihrem userB-Beispiel glaube ich nicht, dass Ihre init-Methode öffentlich sein sollte oder sogar existieren sollte. Wenn Sie diese Methode erneut für eine vorhandene Objektinstanz aufrufen, ändern sich die Eigenschaften id und name.

Mit Object.create können Sie Objekteigenschaften mit dem zweiten Argument initialisieren, z.

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
  }
});

Wie Sie sehen, können die Eigenschaften für das zweite Argument von Object.create initialisiert werden, wobei ein Objektliteral eine Syntax verwendet, die der von den Methoden Object.defineProperties und Object.defineProperty verwendeten Syntax ähnelt.

Hier können Sie die Eigenschaftsattribute festlegen (enumerable, writable oder configurable), was sehr nützlich sein kann.

236
CMS

Die Verwendung von Object.create(...) gegenüber new object bietet wirklich keinen Vorteil.

Die Befürworter dieser Methode weisen im Allgemeinen eher mehrdeutige Vorteile auf: "Skalierbarkeit" oder " - natürlicher für JavaScript " usw. 

Ich habe jedoch noch kein konkretes Beispiel gesehen, das zeigt, dass Object.createany Vorteile gegenüber der Verwendung von new hat. Im Gegenteil gibt es bekannte Probleme damit. Sam Elsamman beschreibt, was passiert, wenn verschachtelte Objekte vorhanden sind und Object.create(...) verwendet wird :

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!!!

Dies geschieht, weil Object.create(...) eine Praxis befürwortet, bei der data zum Erstellen neuer Objekte verwendet wird. Hier wird das Animal-Datum Teil des Prototyps von lion und bird und verursacht Probleme, wenn es gemeinsam genutzt wird. Bei Verwendung von new ist die prototypische Vererbung explizit:

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

In Bezug auf die optionalen Eigenschaftsattribute, die an Object.create(...) übergeben werden, können diese mit Object.defineProperties(...) hinzugefügt werden.

46
Noel Abrahams

Object.create ist bei einigen Browsern noch nicht Standard, zum Beispiel IE8, Opera v11.5, Konq 4.3 haben es nicht. Sie können Douglas Crockfords Version von Object.create für diese Browser verwenden, dies schließt jedoch nicht den zweiten Parameter für das Initialisierungsobjekt ein, der in der Antwort von CMS verwendet wird.

Für Cross-Browser-Code besteht eine Möglichkeit, die Objektinitialisierung in der Zwischenzeit zu erhalten, darin, Crockfords Object.create anzupassen. Hier ist eine Methode: -

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()
}

Auf diese Weise wird die prototypische Vererbung von Crockford aufrechterhalten, und es wird auch nach einer beliebigen Init-Methode im Objekt gesucht. Anschließend werden diese Parameter mit Ihren Parametern wie "new man" ("John", "Smith") ausgeführt. Ihr Code wird dann zu: -

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 erbt also die sayHello-Methode und verfügt nun über die eigenen Eigenschaften id = 1 und name = 'Bob'. Diese Eigenschaften sind natürlich sowohl beschreibbar als auch zahlreich. Dies ist auch eine viel einfachere Initialisierungsmethode als für ECMA Object.create, insbesondere wenn Sie sich nicht um die beschreibbaren, aufzählbaren und konfigurierbaren Attribute kümmern.

Für die Initialisierung ohne eine init-Methode kann der folgende Crockford-Mod verwendet werden:

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()
}

Dadurch werden die eigenen Eigenschaften von userB in der Reihenfolge, in der sie definiert sind, mit den Object.gen-Parametern von links nach rechts nach dem userB-Parameter gefüllt. Es verwendet die for (prop in o) -Schleife, so dass nach ECMA-Standards die Reihenfolge der Eigenschaftsaufzählung nicht genauso garantiert werden kann wie die Reihenfolge der Eigenschaftsdefinition. Mehrere Codebeispiele, die in (4) großen Browsern getestet wurden, zeigen jedoch, dass sie identisch sind, vorausgesetzt, der hasOwnProperty-Filter wird verwendet, und manchmal sogar, wenn nicht.

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());

Etwas einfacher würde ich sagen als Object.build, da userB keine init-Methode benötigt. Auch userB ist kein spezifischer Konstruktor, sondern sieht aus wie ein normales Einzelobjekt. Mit dieser Methode können Sie also normale Objekte erstellen und initialisieren.

41
John

TL; DR:

new Computer() ruft die Konstruktorfunktion Computer(){} einmal auf, Object.create(Computer.prototype) jedoch nicht.

Alle Vorteile basieren auf diesem Punkt. 

Object.create kann aus Gründen der Optimierung der Engine innerhalb der Engine langsamer sein, was nicht intuitiv ist.

19
Nami WANG

Sie können die init-Methode dazu veranlassen, this zurückzugeben und die Aufrufe dann wie folgt zu verketten:

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');
13
samfrances

Eine weitere mögliche Verwendung von Object.create besteht darin, unveränderliche Objekte auf eine kostengünstige und effektive Weise zu klonen .

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}

Notes: Das obige Snippet erstellt einen Klon eines Quellobjekts (also keine Referenz, wie in cObj = aObj). Gegenüber der copy-properties-Methode (siehe 1 ) hat es den Vorteil, dass keine Objekt-Member-Eigenschaften kopiert werden. Es erstellt vielmehr ein anderes -destination- Objekt, dessen Prototyp für das Quellobjekt festgelegt ist. Wenn Eigenschaften am Zielobjekt geändert werden, werden sie "on the fly" erstellt, wodurch die Eigenschaften des Prototyps (src) maskiert werden. Dies ist eine schnelle und effektive Möglichkeit, unveränderliche Objekte zu klonen.

Der Nachteil hierbei ist, dass dies für Quellobjekte gilt, die nach der Erstellung nicht geändert werden sollten (unveränderlich). Wenn das Quellobjekt nach der Erstellung geändert wird, werden auch alle unmaskierten Eigenschaften des Klons geändert.

Geige hier ( http://jsfiddle.net/y5b5q/1/ ) (benötigt Object.create-fähigen Browser).

7
basos

Ich denke, der in Frage stehende Hauptpunkt besteht darin, den Unterschied zwischen new und Object.create Ansätzen zu verstehen. Entsprechend zu diese Antwort und zu dieses Videonew-Schlüsselwort hat folgende Dinge:

  1. Erzeugt ein neues Objekt.

  2. Verknüpft ein neues Objekt mit der Konstruktorfunktion (prototype).

  3. Macht die Variable this auf das neue Objekt.

  4. Führt eine Konstruktorfunktion unter Verwendung des neuen Objekts aus und implizit perform return this;

  5. Ordnet der Funktion des neuen Objekts constructor den Namen der Konstruktorfunktion zu.

Object.create führt nur 1st und 2nd Schritte aus !!!

In dem in Frage gestellten Codebeispiel ist es keine große Sache, aber im nächsten Beispiel ist es:

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);

Als Nebeneffekt wird das Ergebnis sein:

[ undefined, 'James' ]

wegen Guest.prototype = new SiteMember();
Wir müssen jedoch keine übergeordnete Konstruktormethode ausführen, sondern müssen nur die Methode getName in Guest ..__ verfügbar machen. Daher müssen Sie Object.create..__ verwenden.
Wenn Guest.prototype = new SiteMember();.__ ersetzen.
Guest.prototype = Object.create(SiteMember.prototype); Ergebnis sein:

[ 'James' ]
6
Vladimir Kovpak

Manchmal können Sie mit NEW kein Objekt erstellen, können jedoch weiterhin die CREATE-Methode aufrufen.

Zum Beispiel: Wenn Sie ein benutzerdefiniertes Element definieren möchten, muss es von HTMLElement abgeleitet sein.

proto = new HTMLElement  //fail :(
proto = Object.create( HTMLElement.prototype )  //OK :)
document.registerElement( "custom-element", { prototype: proto } )
5
Supersharp

Der Vorteil ist, dass Object.create in den meisten Browsern normalerweise langsamer ist als new

In diesem jsperf-Beispiel ist in einem Chromium der Browser new30-mal so schnell wie Object.create(obj), obwohl beide ziemlich schnell sind. Das ist alles ziemlich seltsam, weil new mehr tut (zum Beispiel den Aufruf eines Konstruktors), wobei Object.create nur ein neues Objekt mit dem übergebenen Objekt als Prototyp erstellen sollte (geheime Verbindung in Crockford-speak).

Vielleicht haben die Browser nicht nachgeholt, um Object.create effizienter zu machen (vielleicht basieren sie auf new unter den Covers ... sogar in nativem Code)

3
frooble

Sie müssen eine benutzerdefinierte Object.create()-Funktion erstellen. Eine, die Crockfords Bedenken anspricht und auch Ihre Init-Funktion aufruft.

Das wird funktionieren:

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();

Hier ist UserB wie Object.create, aber an unsere Bedürfnisse angepasst.

Wenn Sie möchten, können Sie auch anrufen:

var bob = new UserB('bob');
2
edwin

Zusammenfassung:

  • Object.create() ist eine Javascript-Funktion, die 2 Argumente akzeptiert und ein neues Objekt zurückgibt. 
  • Das erste Argument ist ein Objekt, das der Prototyp des neu erstellten Objekts ist
  • Das zweite Argument ist ein Objekt, bei dem es sich um die Eigenschaften des neu erstellten Objekts handelt

Beispiel:

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();

Praktische Anwendungen:

  1. Der Hauptvorteil beim Erstellen eines Objekts auf diese Weise ist, dass der Prototyp explizit definiert werden kann . Wenn Sie ein Objektliteral oder das Schlüsselwort new verwenden, haben Sie keine Kontrolle darüber (Sie können sie jedoch natürlich überschreiben).
  2. Wenn wir einen Prototyp haben wollen Das Schlüsselwort new ruft eine Konstruktorfunktion auf. Mit Object.create() ist es ist nicht nötig, eine Konstruktorfunktion aufzurufen oder gar zu deklarieren .
  3. Grundsätzlich kann es hilfreich sein, wenn Sie Objekte sehr dynamisch erstellen möchten. Wir können eine Objekt-Factory-Funktion erstellen, die abhängig von den erhaltenen Argumenten Objekte mit unterschiedlichen Prototypen erstellt.
1

Während Douglas Crockford ein eifriger Befürworter von Object.create () war und er im Grunde der Grund ist, warum dieses Konstrukt tatsächlich in Javascript verwendet wird, hat er diese Meinung nicht mehr.

Er hat aufgehört, Object.create zu verwenden, da er das this - Schlüsselwort nicht mehr verwendet, da es zu viele Probleme verursacht. Wenn Sie beispielsweise nicht vorsichtig sind, kann dies leicht auf das globale Objekt verweisen, was sehr schlechte Folgen haben kann. Und er behauptet, ohne dieses Object.create keinen Sinn mehr zu machen.

Sie können dieses Video von 2014 ansehen, wo er bei Nordic.js spricht:

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

 enter image description here

0
Vojtech Ruzicka

Ich bevorzuge eine Schließung.

Ich verwende immer noch new. Ich verwende keinen Object.create. Ich verwende keine this.

Ich benutze immer noch new, da ich die deklarative Natur davon mag.

Betrachten Sie dies für die einfache Vererbung.

window.Quad = (function() {

    function Quad() {

        const wheels = 4;
        const drivingWheels = 2;

        function getWheelCount() {
            return wheels;
        }

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

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

0
p0wdr.com

new und Object.create dienen unterschiedlichen Zwecken. new soll eine neue Instanz eines Objekttyps erstellen. Object.create soll einfach ein neues Objekt erstellen und seinen Prototyp festlegen. Warum ist das nützlich? Um die Vererbung zu implementieren, ohne auf die __proto__-Eigenschaft zuzugreifen. Der Prototyp einer Objektinstanz, der als [[Prototype]] bezeichnet wird, ist eine interne Eigenschaft der virtuellen Maschine, auf die nicht direkt zugegriffen werden kann. Der einzige Grund, warum es tatsächlich möglich ist, direkt auf [[Prototype]] als __proto__-Eigenschaft zuzugreifen, besteht darin, dass sie seit jeher ein De-facto-Standard für die Implementierung von ECMAScript auf jeder großen virtuellen Maschine ist und an diesem Punkt bereits viel Code zerstört werden würde.

Als Antwort auf die Antwort von 7ochem sollte der Prototyp von Objekten niemals auf das Ergebnis einer new-Anweisung gesetzt werden, nicht nur weil es nicht sinnvoll ist, denselben Prototypkonstruktor mehrmals aufzurufen, sondern auch, weil zwei Instanzen derselben Klasse enden können mit unterschiedlichem Verhalten, wenn der Prototyp nach der Erstellung geändert wird. Beide Beispiele sind einfach schlechter Code, weil das beabsichtigte Verhalten der Prototyp-Vererbungskette missverstanden und gebrochen wurde.

Anstatt auf __proto__ zuzugreifen, sollte der Prototyp einer Instanz beschrieben werden, wenn ein mit Object.create oder danach mit Object.setPrototypeOf erstellt wird, und mit Object.getPrototypeOf oder Object.isPrototypeOf gelesen werden.

Wie aus der Mozilla-Dokumentation von Object.setPrototypeOf hervorgeht, ist es keine gute Idee, den Prototyp eines Objekts zu ändern, nachdem es aus Leistungsgründen erstellt wurde, zusätzlich zu der Tatsache, dass der Prototyp eines Objekts geändert wird, nachdem er erstellt wurde erstellt kann undefiniertes Verhalten verursachen, wenn ein bestimmter Code, auf den zugegriffen wird, vor OR ausgeführt werden kann, nachdem der Prototyp geändert wurde, es sei denn, der Code prüft den aktuellen Prototyp sehr genau oder greift auf keine Eigenschaften zu, die sich zwischen den beiden unterscheiden .

Gegeben

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

Der folgende VM - Pseudocode entspricht der Anweisung const x0 = new X(1);:

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

Obwohl der Konstruktor einen beliebigen Wert zurückgeben kann, ignoriert die new-Anweisung immer den Rückgabewert und gibt einen Verweis auf das neu erstellte Objekt zurück.

Der folgende Pseudo-Code entspricht der Anweisung const x1 = Object.create(X.prototype);:

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

Wie Sie sehen können, besteht der einzige Unterschied zwischen den beiden darin, dass Object.create den Konstruktor nicht ausführt, der tatsächlich einen Wert zurückgeben kann, sondern lediglich die neue Objektreferenz this zurückgibt, sofern nichts anderes angegeben ist.

Nun, wenn wir eine Unterklasse Y mit der folgenden Definition erstellen wollten:

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

Dann können wir es so von X erben lassen, indem wir in __proto__ schreiben:

Y.prototype.__proto__ = X.prototype;

Während dasselbe erreicht werden könnte, ohne mit __proto__ zu schreiben:

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

Im letzteren Fall muss die Konstruktoreigenschaft des Prototyps so festgelegt werden, dass der richtige Konstruktor von der new Y-Anweisung aufgerufen wird. Andernfalls ruft new Y die Funktion X auf. Wenn der Programmierer möchte, dass new YX aufruft, wird dies in Ys Konstruktor mit X.call(this, u) besser ausgeführt.

0
paulyc