Javascript函数范围和查找父对象的属性

时间:2013-09-29 00:11:52

标签: javascript scope prototype-programming

我想要实现的是创建一些在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”类,可以对“所有者”执行操作“他们所依附的实例。这基本上是Decorator模式的一个实现,我可以想到很多方法可以实现这个,但正如我所说,我正在寻找理解Javascript的功能/原型本质如何帮助我这种情况所以我可以更好地处理语言。

我对通过实例传递函数的方式也不是很满意 - 我觉得我应该像五彩纸屑那样抛出函数,所以关于如何与变量范围交互的任何提示都是很有帮助。

如果事实证明我正在尝试做的事情是向后倾斜并且有更好的方法来实现这种目标,那也没关系。我正试图在Javascript中使用优秀的语言,这证明很棘手,但我学到了很多东西。

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

因此,如果我有Animal类型,然后我的操作是EatRunGrow等等,我理想情况下会有myAnimal.can("Eat")类型调用(我意识到这有点像hasOwnProperty,但我假设这些操作是特定的基类型),它会告诉我该操作是否可用,然后是myAnimal.actions.Eat("leaves")之类的内容它是可访问的。调用Eat操作时,它会影响父myAnimal对象的属性。在myAnimal的生命周期内,它可能会在任何时候添加或删除功能,但只要检查它can执行的操作应该足以满足我的需要。

3 个答案:

答案 0 :(得分:3)

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

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

这是fiddle

有一个闭包基本上创建了一个环境,其中'inner-outside'函数和变量可以从已返回的函数的'inner-inside'访问:

这些是“内外”功能和变量:

    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()中的所有内容都可以访问“内外”变量&amp;函数(例如$self)。

答案 1 :(得分:2)

在这一行:

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

self不是您想要的,因为您对self的定义不在范围内,因此您获得self的全局定义window并且不会做你想做的事。

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

您已经使代码变得比thatself的不必要定义复杂得多,并且不清楚为什么Writers对象应该知道它的容器。如果您希望如此,那么您应该将它的容器传递给Writers对象的构造函数,它可以存储对它的容器的引用。

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


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

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

  1. 它可以作为实例数据存储在某个其他对象中,因此可以根据需要使用其他对象方法(例如,在每个Writers对象中存储对所有者的引用。
  2. 它可以通过范围获得(例如,在当前函数中定义或嵌套在其中的任何父函数)。在Owner的构造函数范围内定义Writers对象。这将使您可以访问所有者实例,但也会使Writers对象仅在Owners方法中可用(不公开)。
  3. 它可以作为参数传递给想要使用它的方法/函数。您可以将适当的所有者作为参数传递给.operate(curOwner)方法。
  4. 它可以全球存储,因此可以在任何地方使用。如果一次只有一个所有者(例如,它是单一设计模式),您可以让所有者全局存储并全局访问。这样不太灵活(将来某个时候不能使用多个所有者用于其他目的),但可以更简单。
  5. 您的代码中缺少的是从.operate()对象的Writers方法到达相应的Owner对象的任何方式。它只是在.operate()代码中无法使用。如果您希望它在那里可用,则必须更改代码的结构以至少制作上述访问方法之一,或者发明一种新方法来访问所有者实例。


    根据您最新的编辑来描述问题,这里有更多信息。您可以利用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中通常不是必需的因为你不必知道任何关于要使用它的对象类型的信息,只要它们都有一些你知道如何调用的通用方法。

答案 2 :(得分: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