Javascript 函数作用域和查找父对象的属性

Javascript function scope and finding properties of a parent object

本文关键字:对象 属性 查找 函数 作用域 Javascript      更新时间:2023-09-26

我试图实现的是创建一些在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做一些足以满足我需求的事情。

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

在这一行中:

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

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

设计中的问题是Writers对象不知道Owner对象的任何信息,因此不能有报告Owner数组大小的方法。

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

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


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

对对象(如Owner对象(的引用可以来自多个位置:

  1. 它可以作为实例数据存储在某个其他对象中,因此可以从该其他对象方法根据需要使用(例如,在每个Writers对象中存储对所有者的引用。
  2. 它可以通过作用域使用(例如,在当前函数或嵌套的任何父函数中定义(。 在所有者构造函数的作用域内定义 Writers 对象。 这将使您能够访问 Owner 实例,但也会使 Writers 对象仅在 Owners 方法中可用(不公开可用(。
  3. 它可以作为参数传递给想要使用它的方法/函数。 可以将相应的所有者作为参数传递给 .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 中通常不是真正必要的,因为你不必知道任何对象的类型来使用它,只要它们都有一些你知道如何调用的通用方法集。

我把你的伪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