如何在 JavaScript 中保持双向 object 关系的一致性?

How can I maintain consistency in a 2-way object relationship in JavaScript?

如果我在 2 objects 之间有双向关系,例如 ABBA 相关,我如何保持这种一致性,以便 2 objects 始终相互引用?

我很难用语言表达我非常简单的问题,所以这里有一个非常简单的例子。我从 HusbandWife:

开始
function Husband() { this.wife; }
function Wife() { this.husband; }

var harry = new Husband();
var wendy = new Wife();

harry.wife = wendy;
wendy.husband = harry;

从逻辑上讲,如果哈利的妻子是温蒂,那么温蒂的丈夫就是哈利。

我需要一种方法来保持这种关系的一致性。因此,我在 Husband 上创建了一个 setter 方法,并通过在 wife 变量前加上下划线来表示应该将其视为私有变量。

function Husband() {
    this._wife;
    this.setWife = function(wife) {
        this._wife = wife;
        wife.husband = this;
    }
}

现在描述这种关系很简单并且鼓励一致性:

harry.setWife(wendy);

同样,如果有相反的选项就好了:

wendy.setHusband(harry);

为此,我在 Wife 上创建了一个 setHusband 方法,并根据需要调整 Husband

function Husband() {
    this._wife;
    this.setWife = function(wife) {
        this._wife = wife;
        // wife.husband = this; // <-- husband is now _husband (private)...
        wife.setHusband(this);  // <-- switching to a public method
    }
}

function Wife() {
    this._husband;
    this.setHusband = function(husband) {
        this._husband = husband;
        husband._wife = this;  // <-- Oops! _wife is private!
        husband.setWife(this); // <-- Oops! this results in an infinite loop!
    }
}

此时我运行陷入困境。我的新 setHusband 方法需要能够保持一致性,但是 wife 现在是 _wife(私有),并且调用 setWife 会导致无限循环,因为它们相互往复。

我可以创建另一组方法,例如 reallyJustSetHusband,但这看起来很傻。

我的难题并不是 JavaScript 所特有的,但我已经在问题中提到了它,以防需要特殊的方法。

在这 2 objects 之间实现一致性的最佳方法是什么?有什么我忽略的吗?

DOM

中的相似模式

在DOM中,如果调用parent.appendChild(child),则child.parentNode === parent。它们从不矛盾。如果 parent 有一个 child,则 child 有相同的 parent。 nextSibling等其他关系也保持一致。

像这样的事情是完成您所要求的一种方法:

function Husband() {
  this.marry = function(wife) {
    this.wife = wife;
    wife.husband = this;
  }

  this.say = function() {
    if (this.wife) {
      console.log('Hi! I\'m married to a wife!');
    } else {
      console.log('Hi! I\'m single, no wife.');
    }
  }

}

function Wife() {
  this.marry = function(husband) {
    this.husband = husband;
    husband.wife = this;
  }

  this.say = function() {
    if (this.husband) {
      console.log('Hi! I\'m married to a husband!');
    } else {
      console.log('Hi! I\'m single. No husband.');
    }
  }
}

var h = new Husband();
var w = new Wife();
h.marry(w);
w.say();
h.say();

一种简单的方法是只检查冗余值并提前中止:

function Husband() {
    this._wife;
    this.setWife = function(wife) {
        if(this._wife == wife) return; // prevents recursion
        this._wife = wife;
        wife.setHusband(this);
    }
}

function Wife() {
    this._husband;
    this.setHusband = function(husband) {
        if(this._husband == husband) return; // prevents recursion
        this._husband = husband;
        husband.setWife(this); 
    }
}

您还可以将外部状态管理器(redux、sql 等)与更新事件一起使用,或者使用不需要 setter 的直接属性并注意保持数据更新。

._wife 不一定是私人的 - 妻子认识她的丈夫,应该被允许设置他的属性。也可以轻松跳出死循环:

class Wife() {
  constructor() {
    this._husband=null;
  }
  get husband() { return this._husband }
  set husband(h) {
    if (this._husband) this._husband._wife = null;
    this._husband = h;
    if (h && h.wife != this) h.wife = this;
  }
}

如果您不想要这些内幕知识,您需要使用一个管理关系(可以创建、查询和断开)的中间体 class Marriage

class Marriage {
  constructor(h, w) {
    this.husband = h;
    this.wife = w;
  }
  divorce() {
    this.husband.marriage = null;
    this.wife.marriage = null;
  }
}
class Wife() {
  constructor() {
    this.marriage = null;
  }
  get husband() {
    return this.marriage && this.marriage.husband;
  }
  set husband(h) {
    if (this.marriage) this.marriage.divorce();
    if (h.marriage) h.marriage.divorce();
    this.marriage = h.marriage = new Marriage(h, this);
  }
}