3

我想要实现的是在 JavaScript 中创建一些对对象属性进行操作的函数,这是相当典型的事情。

问题是出于灵活性和可扩展性的考虑,我不希望函数提前知道对象。据我所知,这正是 Javascript 应该非常擅长的事情,但是没有花很多时间在函数式语言上,我还没有掌握如何去做。

所以我可能会遇到这样的情况:

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

现在,这Alice Says: undefined一切都很好,但并不完全是我想要的——我显然希望看到Alice Says: 100。在经典语言中,我可能不得不向 Alice 函数提供对所有者的引用(在伪 C# 中):

public class Owner()
{
    private List<Writer> writers;



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

    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} says: {1}", name, owner.Size );
    }
}

据我所知,这不是非常 Javascripty - 似乎应该有更好的 - 或者至少更惯用的 - 这样做的方式。

就我正在使用的代码的实际目标而言(基本上是这样,但更复杂),目标是能够添加任意数量的“Writer”类,这些类可以对“Owner”实例执行操作以它们是附加的。这基本上是该Decorator模式的一种实现,我可以想出很多方法来实现它,但正如我所说,我正在寻找了解 Javascript 的功能/原型性质如何在这种情况下帮助我,所以我可以更好地掌握语言。

我也不完全满意我通过实例传递函数的方式——我觉得我应该能够像五彩纸屑一样在周围扔函数,所以任何关于如何与变量范围交互的提示都会非常有帮助。

如果事实证明我正在尝试做的是倒退,并且有更好的方法来实现这种目标,那也很好。我正在尝试在 Javascript 中使用好的语言,但事实证明这很棘手,但我学到了很多东西。

编辑:我的目标是有一个基本类型,我在设计它时不必知道它可能必须执行的操作的完整列表,并且我不必为每个不同的可用组合创建一个新的子类操作,确保我的操作与我的核心类型分离。

因此,如果我有一个Animal类型,然后我的操作是Eat,等等,理想情况下我会进行Run类型调用(我意识到这有点像,但我假设这些操作属于特定的基本类型)通知我该操作是否可用,然后告诉我是否可以访问。当动作被调用时,它会影响父对象的属性。在它的生命周期中,可能会在任何时候添加或删除能力,但只要检查它是否做了足以满足我需要的事情。GrowmyAnimal.can("Eat")hasOwnPropertymyAnimal.actions.Eat("leaves")EatmyAnimalmyAnimalcan

4

3 回答 3

3

实现它的一种方法是使用javascript 闭包

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 says: " + $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();

这是小提琴

拥有一个闭包基本上会创建一个环境,在该环境中,可以从已返回的函数的“内部”访问“内部外部”函数和变量:

这些是“内外”函数和变量:

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

    function Writers() {

        var Alice = function () {

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

getO 内部是“内部内部”:

    var getO = function () {
         //Here are the functions you want to call:

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

返回 getO 并且我们创建了一个闭包!

从现在开始,当您调用时,realowner();您会喜欢调用能够访问“内部-外部”变量和函数(例如)的getO();所有内容。getO()$self

于 2013-09-29T00:59:22.707 回答
2

在这一行:

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

self不是你想要的,因为你的定义self不在范围内,因此你得到的全局定义selfwindow和不会做你想做的事。

您设计中的问题是Writers对象对对象一无所知Owner,因此不能具有报告Owner数组大小的方法。

你已经使你的代码比它需要的复杂得多,that而且self还不清楚为什么一个Writers对象应该知道它的容器。如果你希望这样,那么你应该将它的容器传递给Writers对象的构造函数,它可以存储对其容器的引用。

如果你能更好地描述你真正想要解决的问题,我们可能会提出一个更简单、更恰当的面向对象的建议。


在 Javascript 中,您仍然需要牢记将数据封装到对象中的概念以及那些具有可以对可访问的数据和属性进行操作的方法的对象。如果要对对象中的数据(如在您的.size属性中)进行操作,则必须具有对包含该属性的对象的引用。

对对象(如您的Owner对象)的引用可以来自多个地方:

  1. 它可以作为实例数据存储在某个其他对象中,因此可以从其他对象中获得根据需要使用的方法(例如,在每个Writers对象中存储对所有者的引用。
  2. 它可以通过作用域获得(例如,在当前函数或您嵌套的任何父函数中定义)。在 Owner 的构造函数范围内定义 Writers 对象。这将使您可以访问 Owner 实例,但也会使 Writers 对象仅在 Owners 方法中可用(不公开可用)。
  3. 它可以作为参数传递给想要使用它的方法/函数。您可以将适当的所有者.operate(curOwner)作为参数传递给该方法。
  4. 它可以在全球范围内存储,因此可以在任何地方使用。如果一次只有一个所有者(例如,它是单例设计模式),您可以将所有者全局存储并全局访问。这不太灵活(将来某个时候不能将多个所有者用于其他目的),但可以更简单。

您的代码中缺少的是从对象的.operate()方法Writers到相应Owner对象的任何方式。它根本无法从.operate()代码中获得。如果您希望它在那里可用,则必须更改代码的结构以至少使用上述访问方法之一或发明一种新方法来获取 Owner 实例。


根据您描述问题的最新编辑,这里有更多信息。您可以利用 javascript 的松散类型的一种方法是您可以拥有一个对象数组,并且这些对象可以是您想要的任何类型。只要它们都有一组共同的方法,例如.eat(), .grow(),.run(),即使它们是完全不同类型的对象,您也可以一视同仁地对待这些对象(对它们调用这些方法)。您不必创建一个公共基类,然后从中继承子类。这不是很javascripty,也不是必需的。只需制作三个不同的对象,为每个对象提供所需的方法集,将它们放入数组中,然后根据需要调用这些方法。你甚至可以在调用之前测试一个对象是否有一个特定的方法,如果它们可能不都有相同的方法(尽管这是一个不太完美的 OO 设计——尽管有时比其他设计更实用)。

例如,你可以有这个:

// define the cat object
function cat(name) {
   this.name = name;
}

cat.prototype = {
    eat: function(howMuch) {
        // implementation of eat here
    },
    run: function(howFar) {
        // implementation of run here
    },
    grow: function(years) {
        // implementation of grow here
    }
};

// define the dog object
function dog(name) {
    this.name = name;
}

dog.prototype = {
    eat: function(howMuch) {
        // implementation of eat here
    },
    run: function(howFar) {
        // implementation of run here
    },
    grow: function(years) {
        // implementation of grow here
    }
};

然后,您可以拥有一系列猫和狗:

var animals = [];
animals.push(new cat("fluffy"));
animals.push(new dog("bowser"));
animals.push(new cat("purr"));

然后,您可以拥有对animals数组内容进行操作的代码,而无需考虑它是哪种动物。所以,要让每只动物跑 30 英尺,你可以这样做:

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

现在,你可以创建一个基类,它有一个默认构造函数来保存名称并定义什么都不做吃、运行和增长的抽象方法(在 C++ 或 C# 风格中),但这在 JS 中通常不是必需的,因为你不需要不必了解对象的类型即可使用它,只要它们都有一些您知道如何调用的通用方法集。

于 2013-09-29T00:34:53.137 回答
1

我将您的伪 C# 代码翻译成 javascript。
在翻译时,我修改了一些命名约定以适应常见的 js 标准。(仅类的前导大写,私有成员的下划线)。我使用属性和闭包来更接近您的类描述。

我还修改了一些签名:
- 我猜名称和大小是只读的。
- 作家必须了解它的主人。
- 所有者可能希望让所有写入器都运行。(只是猜测)。

希望这将引导您实现目标。

小提琴在这里: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 + " sees " + _owner.size + " people");
    }

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

// outuput is :
// Alice sees 3 people 
// Rabbit sees 3 people 
// Mad Hatter sees 3 people 

madHatter.operate();

// output is :
// Mad Hatter sees 3 people
于 2013-09-29T21:48:48.703 回答