2014-06-16 9 views
15

In einem Vortrag namens "The Way Forward" teilt Douglass Crockford, dass er nicht mehr "neu" in seinem JavaScript verwendet, und sich von "diesem" absetzt. Er sagt, JavaScript's gute Eigenschaft sei weniger eine prototypische Vererbung als eine klassenfreie Vererbung. Er bietet folgende Snippet, das ist, wie er seine Objekte definiert:Ich verstehe nicht, Crockford auf JavaScript: Der Weg nach vorne

function constructor(init) { 
    var that = other_constructor(init), 
     member, 
     method = function() { 
      // init, member, method 
     }; 
    that.method = method; 
    return that; 
} 

Er erklärt:


ich eine Funktion haben, die einen gewissen Wert, es zu initialisieren zu nehmen. Ich empfehle, ein Objekt zu sein (auf diese Weise können Sie einen JSON-Text haben, mit dem Sie neue Instanzen erstellen können). Sie können einen anderen Konstruktor aufrufen, wenn Sie dessen Inhalt erben möchten. In jedem Fall werden Sie ein Objekt erstellen und es in eine Variable namens "das" einfügen. Sie werden alle Ihre Mitgliedsvariablen erstellen (Dinge, die Eigenschaften des Objekts werden, Ihrer Methodenvariablen, Dinge, die auf das Objekt wirken werden), diese Methoden werden Funktionen sein, die über den Initialisierungswert schließen über alle Mitgliedsvariablen und alle Methodenvariablen.

Es könnte auch "dies" und "das" verwenden, aber ich empfehle nicht. Der Grund dafür ist, dass wenn Sie "this" oder "that" nicht verwenden, Sie eine der Funktionen aus dem Objekt herausnehmen und sie unabhängig aufrufen können, und sie tun genau das Gleiche. Das erhöht die Zuverlässigkeit der Sprache. Es bedeutet auch, dass Sie alle diese Dinge weitergeben können, verwenden Sie es als Rückruf, ohne etwas binden zu müssen (es wird nur zuverlässiger). Dann müssen Sie alle Methoden, die öffentlich oder privilegiert sein müssen, einfach an das Objekt anhängen und das Objekt zurückgeben. Dies ist ein wirklich flexibles Muster - Sie können Mehrfachvererbung erhalten, Sie können Aspekte erhalten, Sie können Fabriken bekommen - viele Dinge, die Sie mit diesem Grundmuster tun können.


Warum machen diese Schnipsel folgende Zuordnung?

that.method = method 

Welchen Zweck erfüllt die Methode? Wie ist es initialisiert? Was ist der Zweck, Objekte auf diese Weise zu deklarieren?

Sein Vortrag war ziemlich allgemein, und er verbrachte nur eine Minute auf diesem Teil davon, ohne auf seine Argumentation oder das Zitieren von Mitteln zu arbeiten. Kann jemand die Motivation für diese Art der Programmierung etwas beleuchten?

Link zum Video: https://www.youtube.com/watch?v=3WgVHE5Augc um die Marke 37 Minuten beginnen

+1

Haben Sie einen Link dazu? Ich frage mich, ob endlich etwas veröffentlicht wird, wo er Konstruktorfunktionen und das neue Schlüsselwort korrekt implementiert. Wenn ich es so benutzen würde, wie ich es tue, gehe ich wahrscheinlich auch aus dem Weg, um es zu vermeiden. Der "korrekte" Weg, Konstruktorfunktionen zu benutzen, wird hier erklärt: http://stackoverflow.com/questions/16063394/prototypical-inheritance-writing-up/16063711#16063711 andere Muster zum Erstellen von Objektinstanzen können ihre Verwendung haben (außer Verwirrung Programmierer, die Ihren Code beibehalten) und tut dies auch mit neuen – HMR

+0

diese ganze Funktion ist analog zu einem "Klasse" -Konstruktor. "other_constructor" ist ein Basisklassenkonstruktor. "Mitglied" ist eine private Mitgliedsvariable. "Methode" ist eine öffentliche oder private Methode. Die Zeile 'that.method = method' wandelt sie effektiv von einer privaten Methode in eine öffentliche Methode um, da sie nun öffentlich als Eigenschaft des zurückgegebenen Objekts verfügbar ist. andernfalls würde es wie "Mitglied" als Teil der Schließung privat bleiben. –

Antwort

19

Warum dieser Schnipsel folgende Zuordnung macht?

that.method = method 

Welchen Zweck erfüllt method? Wie ist es initialisiert?

Es wird als eine Variable ein paar Zeilen weiter oben initialisiert:

method = function() { ... } 

Dann wird die Linie, die Sie zitiert haben, ist der Wert dieser Variablen zugewiesen wird (ein Verweis auf die Funktion) auf eine Eigenschaft auf das Objekt that bezieht sich auf, so dass es als "Methode" des Objekts verwendet werden kann.So könnten Sie dies tun:

var x = constructor(42); 
x.method(); // <== Here's where we use the function assigned to the property as a method 

Mehr (auf meinem Blog): Mythical methods

Was ist der Zweck Objekte auf diese Weise zu erklären?

Crockford mag die Konstruktorfunktionen von JavaScript nicht und verwendet sie daher auch nicht. Also macht er das stattdessen. Einer der wirklich tollen Dinge über JavaScript ist, wie flexibel es ist. Sie können es als eine fast rein funktionale Sprache verwenden, Sie können es als eine prototypische Sprache verwenden, und Sie können es sogar eine Los wie eine klassenbasierte Sprache verwenden, obwohl es nicht klassenbasiert ist, weil es prototypische Features geben Sie fast alles, was Sie dafür brauchen (wie von ES6, es wird alles notwendig, um in einer klassenbasierten Weise verwendet werden, wenn man will). Und Sie können diese Ansätze kombinieren, wann immer Sie es für angebracht halten. Es ist nur so flexibel.

Im Gegensatz zu Crockford, mag ich Konstruktor Funktionen und die new Schlüsselwort. Aber ich auch wirklich wie nicht, sie zu verwenden, wenn sie nicht das richtige Werkzeug sind, die sie häufig nicht sind.


Re Sie Ihren Kommentar unten:

Möchten Sie zufällig in der Lage ein Beispiel für Crockford Konstruktorfunktion Schnipsel in der tatsächlichen Verwendung zur Verfügung zu stellen? Wird diese Methode verwendet, um dieses Mitglied zu initialisieren, oder bin ich hier völlig ausgeschaltet?

Es gibt keine that.member. member in dem Code, den Sie zeigten, ist keine Eigenschaft des Objekts, es ist nur eine Variable. Die method-Funktion, die innerhalb des Aufrufs von constructor erstellt wird, hat Zugriff auf diese Variable, weil es eine Schließung über den Kontext des Aufrufs an constructor ist, aber nichts, das nur Zugriff auf das zurückgegebene Objekt hat, kann member sehen. So member ist wirklich private zu den Funktionen innerhalb constructor erstellt. Zwei weitere Artikel, die hier nützlich sein können: Closures are not complicated von meinem Blog, der erklärt, was eine "Schließung" ist, und Crockfords Private Members in JavaScript, die beschreibt, wie private Informationen mit Objekten verknüpft sind, die er in dem von Ihnen zitierten Beispiel verwendet. (Ich schon erwähnt, eine andere Art und Weise auf Objekte am Ende dieser Antwort private Informationen zu haben.)

Was das Beispiel, das Sie zitiert haben tut demonstriert zwei weitgehend unabhängige Dinge:

  1. Ein Mittel zum Augmented Erzeugen (nicht abgeleitet) Objekte

  2. A mittels

Die patte mit diesen Objekten zugeordnet wirklich private Informationen mit er zeigt, ist nicht der einzige Weg, um diese Dinge zu tun, aber das ist es, was es zeigt.

Re ein konkretes Beispiel:

Lassen Sie uns sagen, dass wir „Ding“ Objekte erstellen möchten. Es spielt keine Rolle, was sie sind, aber sie haben eine Eigenschaft namens "name" (die nicht privat sein muss). (Sie würden wahrscheinlich auch Methoden, aber die sind nicht wichtig, so werden wir sie der Kürze halber auslassen und Klarheit.) Also würden wir mit so etwas wie dies starten:

// Very simple Crockford-style constructor 
function createThing(name) { 
    return {name: name}; // Again, there'd probably be more to it, this is simple on purpose 
} 

// Usage 
var t = createThing("foo"); 
console.log(t.name); // "foo" 

So weit so gut. Nun wollen wir auch haben die Fähigkeit, Dinge zu erstellen, denen wir einen Zähler hinzufügen können, und eine Methode, die das Ding "verwendet" und zählt, dass verwenden, die neue Anzahl der Verwendungen zurückgibt. (Ja, das ist ein konstruiertes Beispiel.) Eine naive Version, wieder so etwas wie Crockford Art und Weise mit, dies zu tun, könnte wie folgt aussehen:

// Naive approach 
function createThingWithCounter(name) { 
    var that = createThing(name); 
    that.useCounter = 0; 
    that.use = function() { 
     // ...do something with `that`... 

     // Return the new number of times we've "used" the thing 
     return ++that.useCounter; 
    }; 
    return that; 
} 

// Usage 
var t = createThingWithCounter("foo"); 
console.log(t.name); // "foo" 
console.log(t.use()); // 1 
console.log(t.use()); // 2 

wieder so weit, so gut. Aber das Problem ist, useCounter ist eine öffentliche Eigenschaft auf dem Objekt. So können wir Durcheinander über mit ihm von außerhalb des createThingWithCounter Code:

var t = createThingWithCounter("foo"); 
console.log(t.name); // "foo" 
console.log(t.use()); // 1 
t.useCounter = 0; 
console.log(t.use()); // 1 -- uh oh! 

Wir wollen nicht useCounter Öffentlichkeit. Nun gibt es verschiedene Ansätze, um es privat zu machen, einschließlich der Tatsache, dass es nicht nur privat ist, sondern eine Benennungskonvention verwendet (typischerweise beginnt es mit einem Unterstrich, zB _useCounter) bedeutet "lass das alleine oder sonst!", Aber das Muster wir sind Betrachten lässt uns machen useCounterwirklich privat durch Nutzung der Tatsache, dass die use Methode ist eine Schließung über den Kontext des Aufrufs an createThingWithCounter. Das, plus ein wenig die Quelle der Neuanordnung besser die zitierte Muster passen, gibt uns dies:

function createThingWithCounter(name) { 
    var that = createThing(name), 
     useCounter = 0, 
     use = function() { 
      // ...do something with `that`... 

      // Return the new number of times we've "used" the thing 
      return ++useCounter; 
     }; 
    that.use = use; 
    return that; 
} 

Nun useCounter gar kein Eigentum an dem Objekt ist. Es ist wirklich privat, nichts außerhalb von createThingWithCounter kann es sehen oder ändern:

var t = createThingWithCounter("foo"); 
console.log(t.name); // "foo" 
console.log(t.use()); // 1 
t.useCounter = 0;  // <== Has absolutely no effect on the real counter 
console.log(t.use()); // 2 

Damit unser Beton ist (wenn erfundene) Beispiel. Hier ist, wie es Karten zum angezeigten Muster:

  • constructor = createThingWithCounter
  • otherConstructor = createThing
  • member = useCounter
  • method = use

Nun, ich will e mphasize, dass es oben nichts gibt, was Sie mit normalen Konstruktorfunktionen unter Verwendung von new nicht tun können. Es sieht nicht einmal so anders aus:

Sie sind nur verschiedene Möglichkeiten, ähnliche Ziele zu erreichen.

Es gibt auch einen anderen Weg: Ableitung, anstatt Augmentation.Und wie bei der Augmentation kann die Ableitung im Crockford-Stil oder über Standardkonstruktorfunktionen erfolgen. Es ist wieder diese wunderbare Flexibilität der Sprache. :-)

Eine letzte Anmerkung über private Informationen: In beiden Stilen der oben genannten ist die useCounter wirklich privat, aber es ist keine Eigenschaft des Objekts. Es ist überhaupt nicht auf dem Objekt. Und es gibt ein paar Kosten, die damit verbunden sind, wie wir diese Privatsphäre bekommen: Zuerst müssen wir eine use Funktion für jede Instanz erstellen. Diese Funktionen werden nicht zwischen Instanzen aufgeteilt, wie Funktionen, die an Prototypen (und verschiedene andere Arten) angebracht sind. Die Kosten dort sind 2014 ziemlich gering, moderne Motoren sind ziemlich schlau daran, das zu optimieren; es wäre viel teurer gewesen, 15 Jahre oder so zurück.

Die anderen Kosten sind, dass die use-Funktion nicht an anderer Stelle wiederverwendet werden kann, was sie mit der überwiegenden Mehrheit der Funktionen in JavaScript in Widerspruch setzt. Wenn man sich the spec betrachten, sehen Sie diese Sprache auf fast jedem vordefinierten JavaScript-Methode:

HINWEIS: Die xyz Funktion absichtlich allgemein ist; es erfordert nicht, dass sein dieser Wert ein Whatsit-Objekt ist. Daher kann es auf andere Arten von Objekten zur Verwendung als eine Methode übertragen werden.

Also, wenn ich etwas haben, dass viele wie ein Array ist, ist aber kein Array (wie das JavaScript arguments Objekt) kann ich Methoden von Array.prototype auf sie setzen und, sofern mein Objekt ist Array-artiges genug, werden sie gut funktionieren:

var arrayLikeObject = { 
    length: 2, 
    0: "one", 
    1: "two", 
    indexOf: Array.prototype.indexOf 
}; 
console.log(arrayLikeObject.indexOf("two")); // 1 

Unsere use Verfahren nicht auf diese Weise wiederverwendet werden können. Es ist an die "Sache mit Zähler" -Instanz gebunden, auf die es sich bezieht. Wenn wir tat versuchen, es auf diese Weise zu verwenden, würden wir mit seltsamen Übersprechen zwischen dem Objekt, das wir es auf die ursprüngliche "Sache mit Counter" -Objekt, aus dem wir es genommen haben, enden würde. Seltsames Übersprechen dieser Art ist eine großartige Weise, wirklich unangenehme, zeitraubende, peinliche Fehler in Projekten jeder Größe zu haben. Die nächste Version des JavaScript-Standards ECMAScript6 gibt uns die Möglichkeit, private Eigenschaften zu haben. Das heißt, tatsächliche Eigenschaften von Objekten, die privat sind (oder so privat wie Informationen in modernen Sprachen/Umgebungen). Da die Eigenschaften tatsächlich Eigenschaften des Objekts sind, müssen wir uns nicht darauf verlassen, dass die use-Funktion eine Schließung ist, und wir können sie für ein Prototypobjekt definieren und/oder für andere Objekte wiederverwenden die oben genannten Kosten gelten.

Und noch besser, das ziemlich clevere Muster, das sie verwenden, um diese Funktion hinzuzufügen, kann jetzt verwendet werden, um ~ 90% der Vorteile zu erhalten, die es bietet. Also, wenn das ein Thema ist, das dich interessiert, ein letzter Blogpost für dich: Private properties in ES6 -- and ES3, and ES5.

+2

Ich genoss den Artikel auf Ihrem Blog, es war sehr aufschlussreich und relevant für meine Frage auf eine sehr unerwartete Art und Weise. Vielen Dank! – ridthyself

+0

Würden Sie vielleicht ein Beispiel für Crockfords Konstruktorfunktions-Snippet in tatsächlicher Verwendung geben? Wird diese Methode verwendet, um dieses Mitglied zu initialisieren, oder bin ich hier völlig ausgeschaltet? – ridthyself

+1

@etothepowerofx: Ich habe ein Beispiel und weitere Diskussion des Musters zu der Antwort hinzugefügt. –

2

Der Zweck der Zuweisung ist, die Methode "öffentlich" zu machen. Ohne diese Zuordnung ist die Methode "privat" für die "Klasse".

Vielleicht kann ich versuche, den Code verständlicher zu machen:

function constructor(init) { 
    // Call the mother constructor. Or just do that = init. 
    var that = other_constructor(init); 

    // Private members 
    var member1, member2; 

    // Methods 
    var method1 = function() { 
     do_stuff(); 
    }; 

    var method2 = function() { 
     do_stuff(); 
    }; 

    // Make some method public. 
    that.method1 = method1; 

    return that; 
} 

Dann in Sie Code, können Sie Ihren Konstruktor wie diese verwenden:

var obj = constructor(other_obj); 
obj.method1(); 
+0

Ist es möglich, mehrere andere Konstruktoren zu haben? – evolutionxbox

1

Offensichtlich einige andere gute Antworten hier, aber TL: DR:

Warum macht dieses Snippet die folgende Zuordnung?

that.method = Methode

er "erstreckt" die that Objekt, eine lokal definierte Verfahren das Hinzufügen (leider genannt) method. Es ist nicht komplexer als das.

function animal_constructor(init){ 
 
    return {}; 
 
} 
 

 
function dog_constructor(init) { 
 
    // Create a generic animal object 
 
    var dog = animal_constructor(init), 
 
     // Define a local function variable called 'bark' 
 
     bark = function() { 
 
      alert('woof!'); 
 
     }; 
 

 
    // Add the 'bark' function as a property of the generic animal 
 
    dog.bark = bark; 
 
    // Return our now-fancy quadriped 
 
    return dog; 
 
} 
 

 
// Try it out 
 
var init = {}, 
 
    animal = animal_constructor(init), // generic animals can't bark 
 
    yeller = dog_constructor(init);  // fancy dog-animals can! 
 

 
yeller.bark(); // > woof! 
 
animal.bark(); // > Uncaught TypeError: undefined is not a function

+0

Was bedeutet TL, DR? – ridthyself

+0

@etothepowerofx "Zu lange; habe nicht gelesen" (eine Zusammenfassung) –

1

Ich glaube, ich diesen Konstruktor etwas in dieser Richtung verwenden würde:

Constructor für Tier:

function Animal (specs) { 
    // starting with an empty object, since I don´t want to inherit 
    var that = {}, 

     // private attribute(s) 
     name = specs.name, 

     // public method(s) 
     setName = function (newName) { 
     name = newName; 
     }, 
     getName = function() { 
     return name; 
     }; 

    // setting up my public interface 
    // everything else is private 
    that.setName = setName; 
    that.getName = getName; 

    return that; 
} 

Erstellen mein Tier:

var anymal = Animal({ name: "Yoshi" }); 
console.log(anymal.name); // undefined, since it´s private 
console.log(anymal.getName()); // "Yoshi" 
anymal.setName("Sanic"); 
console.log(anymal.getName()); // "Sanic" 
Hund 0

Constructor für:

function Dog (specs) { 
    // now I want to inherit from Animal 
    // the constructor will return the public interface for Animal 
    var that = Animal(specs), 

     // private attribute(s) 
     bark = specs.bark, 

     // public method(s) 
     setBark = function (newBark) { 
     bark = newBark; 
     }, 
     getBark = function() { 
     return bark; 
     }, 
     barks = function() { 
     console.log(bark); 
     }; 

    // these methods will be added to the Animal public interface 
    that.setBark = setBark; 
    that.getBark = getBark; 
    that.barks = barks; 

    return that; 
} 

Erstellen von meinem Hund:

var anydog = new Dog({ name: "Klonoa", bark: "Wah who!" }); 
console.log(anydog.getName()); // "Klonoa" 
anydog.barks(); // "Wah who!" 

Es ist interessant, dass eine Instanz von Hund zu bemerken Referenz wird zwei separate Verschluss Bereiche, eine von der Funktion Tiere und der andere von der Funktion Hund.

Hoffe es hilft!