3 Stimmen

Javascript Funktionsscope und das Finden von Eigenschaften eines Elternobjekts

Was ich versuche zu erreichen, ist die Erstellung von Funktionen, die auf Eigenschaften eines Objekts in JavaScript arbeiten, was eine ziemlich typische Sache ist.

Das Problem besteht darin, dass ich im Interesse der Flexibilität und Erweiterbarkeit nicht möchte, dass die Funktionen im Voraus über das Objekt Bescheid wissen. Soweit ich das beurteilen kann, ist dies genau die Art von Sache, für die JavaScript ziemlich gut geeignet sein sollte. Da ich jedoch noch nicht viel Zeit in funktionalen Sprachen verbracht habe, habe ich noch nicht herausgefunden, wie man das macht.

Also könnte ich in einer Situation wie dieser sein:

function Owner()
{
     var self=this;
     self.size = 100;
     self.writers = [];
}

function Writers()
{
    var Alice= function()
    {
        var that=this;
        that.name="alice";
        that.operate = function()
        {
            console.log( "Alice says: "+self.size );   
        }
    }
    this.alice=new Alice();
}

var owner = new Owner();
var writers = new Writers();
owner.writers.push( writers.alice );
owner.writers[0].operate();

Jetzt gibt dies Alice sagt: undefined zurück, was alles gut und schön ist, aber nicht genau das, was ich suche- am liebsten würde ich Alice sagt: 100 sehen. In einer klassischen Sprache müsste ich wahrscheinlich der Alice-Funktion einen Verweis auf den Owner geben (in Pseudo-C#):

public class Owner()
{
    private List writers;

    public Owner()
    {
        this.size=100;
        this.writers= List();
    }

    public int Size{ get; set; }

    public void AddWriter( Writer writer )
    {
         writer.Owner = this;
         this.writers.Add(writer);     
    }

}

public class Writer
{
   private Owner owner;
   private string name;

   public void Writer( string name )
   {
      this.name=name;
   }

   public void operate()
   {
        Console.WriteLine( "{0} sagt: {1}", name, owner.Size );
    }
}

Soweit ich das beurteilen kann, ist das nicht sehr Javascripty- es scheint, dass es einen besseren- oder zumindest einen idiomatischeren - Weg geben sollte, dies zu tun.

In Bezug auf das eigentliche Ziel des Codes, an dem ich arbeite (der grundsätzlich wie dieser, aber komplizierter ist), besteht das Ziel darin, beliebig viele "Writer"-Klassen hinzufügen zu können, die Operationen auf der "Owner"-Instanz durchführen können, an die sie angehängt sind. Dies ist im Grunde eine Implementierung des Decorator-Musters und ich kann mir viele Möglichkeiten vorstellen, wie ich das umsetzen könnte, aber wie gesagt, ich versuche zu verstehen, wie mir die funktionalen/prototypischen Eigenschaften von JavaScript in dieser Art von Situation helfen können, damit ich die Sprache besser verstehe.

Ich bin auch nicht ganz zufrieden damit, wie ich die Funktionen durch Instanzen herumreiche- ich habe das Gefühl, dass ich Funktionen wie Konfetti herumschmeißen können sollte. Daher wären Tipps dazu, wie dies mit dem Variablenscope interagiert, sehr hilfreich.

Wenn sich herausstellt, dass das, was ich versuche, rückwärts ist und es einen besseren Weg gibt, dieses Ziel zu erreichen, ist das auch in Ordnung. Ich versuche, die gute Sprache in JavaScript zu erreichen und es ist schwierig, aber ich lerne eine Menge dabei.

Bearbeitung: Mein Ziel ist es, einen Basistyp zu haben, bei dem ich bei der Gestaltung nicht die vollständige Liste der Operationen kennen muss, die er ausführen könnte, und bei dem ich nicht für jede verschiedene Kombination verfügbarer Operationen eine neue Unterklasse erstellen muss, um sicherzustellen, dass meine Operationen von meinen Kernarten entkoppelt sind.

Wenn ich also einen Tier-Typ habe und meine Operationen Fressen, Laufen, Wachsen usw. waren, würde ich idealerweise einen Aufruf vom Typ myAnimal.can("Fressen") haben (...). Wenn die Fressen-Aktion aufgerufen würde, würde dies die Eigenschaften des übergeordneten myAnimal-Objekts beeinflussen. Während der Lebensdauer von myAnimal könnten Fähigkeiten jederzeit hinzugefügt oder entfernt werden, aber solange man prüft, ob es etwas tun kann, sollte das für meine Bedürfnisse ausreichen.

3voto

Ein Weg, dies zu erreichen, besteht darin, Javascript Closure zu verwenden:

function Owner() {
    var $self = {};
    $self.size = 150;
    $self.writers = [];

    function Writers() {

        var Alice = function () {

            var that = this;
            that.name = "alice";
            that.operate = function () {
                console.log("Alice sagt: " + $self.size);
                console.log($self);
            }
        }
        this.alice = new Alice();
    }

    var getO = function () {
        var writers = new Writers();
        $self.writers.push(writers.alice);
        $self.writers[0].operate();
        $self.writers[0].operate();
    }
    return getO
}

var o = Owner();
o();

Hier ist der Fiddle.

Ein Closure zu haben, erstellt im Grunde genommen eine Umgebung, in der die 'inneren-äußeren' Funktionen und Variablen aus dem 'inneren-inneren' Teil einer zurückgegebenen Funktion zugänglich sind:

Dies sind die 'inneren-äußeren' Funktionen und Variablen:

var $self = {};
$self.size = 150;
$self.writers = [];

function Writers() {

    var Alice = function () {

        var that = this;
        that.name = "alice";
        that.operate = function () {
            console.log("Alice sagt: " + $self.size);
            console.log($self);
        }
    }
    this.alice = new Alice();
}

Im Inneren von getO befindet sich das 'inner-innere':

var getO = function () {
     //Hier sind die Funktionen, die Sie aufrufen möchten:

    var writers = new Writers();
    $self.writers.push(writers.alice);
    $self.writers[0].operate();
    $self.writers[0].operate();
 }

Wir geben getO zurück und haben so ein Closure erstellt!

Von nun an, wenn Sie realowner(); aufrufen, ist es, als würden Sie getO(); aufrufen, wobei alles in getO() Zugriff auf die 'inneren-äußeren' Variablen & Funktionen (z.B. $self) hat.

2voto

jfriend00 Punkte 632952

In dieser Zeile:

console.log( "Alice sagt: "+self.size ); 

self ist nicht das, was du willst, denn deine Definition von self ist nicht im Scope, daher bekommst du die globale Definition von self, die window ist und nicht das tut, was du willst.

Das Problem in deinem Design ist, dass ein Writers-Objekt nichts über ein Owner-Objekt weiß, daher kann es keine Methoden haben, die die Größe des Owner-Arrays melden können.

Du hast deinen Code viel komplizierter gemacht als nötig mit den unnötigen Definitionen von that und self und es ist unklar, warum ein Writers-Objekt über seinen Container Bescheid wissen sollte. Wenn du möchtest, dass das so ist, solltest du den Container an den Konstruktor des Writers-Objekts übergeben und es kann eine Referenz auf seinen Container speichern.

Wenn du besser beschreibst, welches Problem du wirklich lösen willst, könnten wir wahrscheinlich einen deutlich einfacheren und angemessen objektorientierten Vorschlag machen.


In Javascript musst du immer noch das Konzept im Auge behalten, dass Daten in Objekte eingekapselt sind und diese Objekte Methoden haben, die auf die Daten zugreifen können, und Eigenschaften, auf die zugegriffen werden kann. Wenn du auf die Daten in einem Objekt operieren möchtest (wie bei deiner .size-Eigenschaft), musst du eine Referenz auf das Objekt haben, das diese Eigenschaft enthält.

Eine Referenz auf ein Objekt (wie dein Owner-Objekt) kann aus verschiedenen Quellen stammen:

  1. Es kann als Instanzdaten in einem anderen Objekt gespeichert werden und somit von den Methoden dieses anderen Objekts verwendet werden (zum Beispiel eine Referenz auf den Owner in jedem Writers-Objekt speichern).
  2. Es kann über den Scope verfügbar sein (zum Beispiel definiert in der aktuellen Funktion oder einer übergeordneten Funktion, in der du verschachtelt bist). Definiere das Writers-Objekt innerhalb des Scopes des Owner-Konstruktors. Das würde dir Zugriff auf die Owner-Instanz geben, würde aber auch die Writers-Objekte nur innerhalb der Owner-Methoden verfügbar machen (nicht öffentlich verfügbar).
  3. Es kann als Argument an die Methode/Funktion übergeben werden, die es verwenden möchte. Du könntest den entsprechenden Owner an die .operate(curOwner)-Methode als Argument übergeben.
  4. Es kann global gespeichert werden und somit überall verfügbar sein. Wenn es nur einen Owner zu einem Zeitpunkt gibt (zum Beispiel im Singleton-Designmuster), könntest du einfach den Owner global speichern und global darauf zugreifen. Das ist weniger flexibel (du könntest in Zukunft keinen weiteren Owner für andere Zwecke verwenden), kann aber einfacher sein.

Was in deinem Code fehlt, ist eine Möglichkeit aus der .operate()-Methode des Writers-Objekts auf das entsprechende Owner-Objekt zuzugreifen. Es ist einfach nicht aus dem .operate()-Code heraus verfügbar. Wenn du möchtest, dass es dort verfügbar ist, musst du die Struktur deines Codes ändern, um mindestens eine der oben genannten Zugriffsmethoden zu implementieren oder eine neue Möglichkeit erfinden, auf die Owner-Instanz zuzugreifen.


Basierend auf deiner neuesten Bearbeitung zur Beschreibung des Problems, hier sind weitere Informationen. Eine Möglichkeit, die JavaScripts lose Typisierung gewährt ist, dass du ein Array von Objekten haben kannst und diese Objekte können beliebige Typen sein, die du möchtest. Solange sie alle eine gemeinsame Reihe von Methoden wie .eat(), .grow(), .run() haben, kannst du die Objekte alle gleich behandeln (diese Methoden auf ihnen aufrufen), auch wenn es sich um vollständig unterschiedliche Objekttypen handelt. Du musst keine gemeinsame Basisklasse erstellen und dann davon ableiten. Das ist nicht sehr "javascriptig" und nicht erforderlich. Erstelle einfach drei verschiedene Objekte, gib jedem die erforderliche Menge an Methoden, füge sie in das Array ein und rufe die Methoden bei Bedarf auf. Du kannst sogar testen, ob ein Objekt eine bestimmte Methode hat, bevor du sie aufrufst, wenn sie möglicherweise nicht alle die gleichen Methoden haben (obwohl dies ein weniger perfektes OO-Design ist - manchmal jedoch praktischer als andere Designs).

Zum Beispiel könntest du das hier haben:

// Definiere das Katzen-Objekt
function cat(name) {
   this.name = name;
}

cat.prototype = {
    eat: function(howMuch) {
        // Implementierung von eat hier
    },
    run: function(howFar) {
        // Implementierung von run hier
    },
    grow: function(years) {
        // Implementierung von grow hier
    }
};

// Definiere das Hunde-Objekt
function dog(name) {
    this.name = name;
}

dog.prototype = {
    eat: function(howMuch) {
        // Implementierung von eat hier
    },
    run: function(howFar) {
        // Implementierung von run hier
    },
    grow: function(years) {
        // Implementierung von grow hier
    }
};

Du kannst dann ein Array von Katzen und Hunden haben:

var animals = [];
animals.push(new cat("flauschig"));
animals.push(new dog("bowser"));
animals.push(new cat("schnurren"));

Du kannst dann Code haben, der auf die Inhalte des animals-Arrays ohne Rücksicht darauf, um welche Art von Tier es sich handelt, zugreift. Um jedes Tier also 30 Fuß laufen zu lassen, könntest du dies tun:

for (var i = 0; i < animals.length; i++) {
    animals[i].run(30);
}

Jetzt könntest du eine Basisklasse erstellen, die einen Standardkonstruktor hat, um den Namen zu speichern und do-nothing-abstrakte Methoden für eat, run und grow (im C++- oder C#-Stil) definiert, aber das ist in JS oft nicht wirklich notwendig, weil du nichts über den Typ des Objekts wissen musst, um es zu verwenden, solange sie alle eine gemeinsame Reihe von Methoden haben, die du aufrufen kannst.

1voto

GameAlchemist Punkte 18409

Ich habe deinen Pseudo-C#-Code in JavaScript übersetzt.
Beim Übersetzen habe ich die Benennungskonventionen ein wenig angepasst, um den gängigen JS-Standards zu entsprechen. (Führendes Großbuchstabe für Klassenname, Unterstrich für private Elemente). Ich habe Eigenschaften und Closures verwendet, um deiner Klassendefinition näher zu kommen.

Ich habe auch einige Signaturen geändert:
- Name und Größe sind wahrscheinlich schreibgeschützt.
- Ein Writer muss über seinen Besitzer Bescheid wissen.
- Ein Besitzer möchte möglicherweise, dass alle Writer tätig werden. (nur eine Vermutung)

Hoffentlich bringt dich das deinem Ziel näher.

Hier ist der Fiddle-Link: http://jsfiddle.net/gamealchemist/zPu8C/3/

function Owner() {
    var _writers = [];

    Object.defineProperty(this, 'size', {
        get: function () {
            return _writers.length
        },
        enumerable: true
    });

    this.addWriter = function (writer) {
        _writers.push(writer);
    }

    this.operateAll = function () {
        _writers.forEach(function (w) {
            w.operate();
        });
    };
}

function Writer(owner, name) {
    var _owner = owner;
    var _name = name;

    Object.defineProperty(this, 'name', {
        get: function () {
            return _name;
        },
        enumerable: true
    });

    this.operate = function () {
        console.log(this.name + " sieht " + _owner.size + " Personen");
    }

    _owner.addWriter(this);
}

var wonderlandOwner = new Owner();

var alice = new Writer(wonderlandOwner, 'Alice');
var rabbit = new Writer(wonderlandOwner, 'Rabbit');
var madHatter = new Writer(wonderlandOwner, 'Mad Hatter');

wonderlandOwner.operateAll();

// Ausgabe ist:
// Alice sieht 3 Personen 
// Rabbit sieht 3 Personen 
// Mad Hatter sieht 3 Personen 

madHatter.operate();

// Ausgabe ist:
// Mad Hatter sieht 3 Personen

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X