【问题标题】:Javascript function scope and finding properties of a parent objectJavascript函数范围和查找父对象的属性
【发布时间】:2013-09-29 00:11:51
【问题描述】:

我想要实现的是在 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 类型,然后我的操作是EatRunGrow 等等,那么理想情况下我会调用myAnimal.can("Eat") 类型(我意识到这有点像hasOwnProperty,但我假设这些操作属于特定的基本类型),这将通知我该操作是否可用,然后如果它可以访问,则类似于myAnimal.actions.Eat("leaves")。当调用Eat 操作时,它会影响父myAnimal 对象的属性。在myAnimal 的生命周期中,它可能会在任何时候添加或删除能力,但只要检查它can 所做的事情应该足以满足我的需求。

【问题讨论】:

  • Owner 是什么?看不懂楼主怎么有大小和写手...OwnerWriters是什么关系?所以爱丽丝是作家?我不确定这些名字是否显而易见,也不确定它们之间的关系是什么。此外,self 超出了那里的范围,所以很明显你会得到undefinedthat=thisself=this 对您没有帮助,它们似乎没有必要。
  • Alice 函数不知道变量self
  • 除了self的问题,我看到的主要问题是数据没有正确建模,所以我们无法理解目标......Writers应该是Writer和方法应该在原型中。还应该与Owner 有明确的关系,因此您可以继承size 并在Writer 中使用它,但这意味着作家是所有者,嗯……我同意乔西蒙斯的观点,一些伪代码会帮助,或者用另一种语言对数据进行建模。
  • 您仍然在用计算机科学术语描述您正在尝试做的事情(例如实现装饰器模式),而不是告诉我们您真正想要解决的问题。对我来说,如果我能找到您试图解决的根本问题,而不是被问到如何将 C# 概念映射到 JS,那么更容易知道如何在 javascript 中最好地完成某些事情。映射通常是不完美且未优化的,因为语言具有不同的优势/劣势。如果您了解问题的真正目标,就更容易将语言的优势应用到问题上。
  • @glenatron - 计算机科学与 javascript 配合得很好。只是解决问题的“最佳”方法(用任何语言)取决于问题,而不是您正在考虑使用的算法。 SO上有太多问题询问某人正在寻求的解决方案有什么问题,而没有描述实际问题。如果不了解要解决的实际问题,而不仅仅是您的预期解决方案,我们永远无法为您提供最佳答案。

标签: javascript scope prototype-programming


【解决方案1】:

实现它的一种方法是使用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

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

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

    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 内部是'inner-inside':

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

【讨论】:

  • 这是一个非常好的答案,谢谢。闭包是我听过很多描述的事情之一,但直到现在还没有真正掌握到足以自己使用它们。我认为我开始更好地理解它们,这听起来确实像我的意思。
【解决方案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 对象。这将使您能够访问 Owner 实例,但也会使 Writers 对象仅在 Owners 方法中可用(不公开)。
  3. 它可以作为参数传递给想要使用它的方法/函数。您可以将适当的 Owner 作为参数传递给 .operate(curOwner) 方法。
  4. 它可以在全球范围内存储,因此可以在任何地方使用。如果一次只有一个所有者(例如,它是单例设计模式),您可以将所有者全局存储并全局访问。这不太灵活(将来某个时候不能将多个所有者用于其他目的),但可以更简单。

您的代码中缺少的是从Writers 对象的.operate() 方法到相应的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 中通常不是必需的因为您无需了解对象的类型即可使用它,只要它们都有一些您知道如何调用的通用方法集。

【讨论】:

  • 感谢您刚才的建设性意见!非常感谢!并且 +1!
  • 希望我的编辑以更清晰的方式描述了我正在尝试做的事情。我对我希望系统如何工作有一个相当清晰的认识——可以将任何选择的操作添加到父类型的对象,然后测试当前实例可以做什么而不是担心它 是什么 - 这是我开始理解 Javascript 应该擅长的事情之一。
  • 但是当我处理这个问题时,我会说变量范围是我最一致的问题——我发现 Javascript 范围层次结构无休止地混乱。
  • @glenatron - 看看我在回答中添加了什么。
  • @glenatron - 根据您对问题的最新编辑,我在答案末尾添加了一些代码示例。
【解决方案3】:

我将您的伪 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

【讨论】:

  • 嘿!为什么要删除?你的回答没有错!
  • 没关系,我意识到我可以做些什么:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-02-22
  • 2011-08-20
  • 2017-09-30
  • 2012-07-18
  • 1970-01-01
  • 1970-01-01
  • 2011-03-22
相关资源
最近更新 更多