【问题标题】:Update an instance's property, from another instance从另一个实例更新实例的属性
【发布时间】:2017-06-24 01:01:03
【问题描述】:

我正在寻找在对象实例中进行更新的最佳方式,例如本例中的 myParentObjects 的 name 属性。我理解实现这一点的唯一方法是将父对象的引用作为参数传递给子对象实例作为构造函数中的new myChildObj(this,name),或myChildObj 实例的方法,如myChildObj.updateParentProperty(name)

我无法想象一个嵌套在 4-5 层以下的子对象,并且必须更新其父对象的属性并传递 (parent1,parent2,parent3,etc) 它的参数,这将是一场管理噩梦!必须有更好的方法来更新父属性!

function myParentObj(){
    this.name = 'jordan'
    this.names = ['jordan','danny','cassie'];
    this.init=()=>{
       this.names.forEach((name)=>{
          var childObj = new myChildObj(this,name);
          childObj.updateParentProperty();
       })
    }
}
function myChildObj(parentObj,name){
    this.parent = parentObj;
    this.name = name;
    this.updateParentProperty=()=>{
        this.parent.name = this.name;
    };
}
function init(){
     var parentObj = new myParentObj();
     parentObj.init();
}
document.addEventListener('DOMContentLoaded',init);

问题:更新父对象参数的最佳方法是什么?

【问题讨论】:

  • "有没有办法将first 实例传递给second 实例,以便它可以操纵第一个属性,而不是new mySecondObj(first)" --这正是你正在做的事情
  • 还有其他方法,即使没有将第一个实例传递给您的第二个实例(例如观察者模式或事件总线)。
  • @cyrix huh 我得去看看,你有任何示例或链接可以作为答案发布
  • 为什么需要这样做?这感觉有点像 xy 问题。一个“孩子”可能不应该以你展示的方式更新一个“父母”。也许您想传入一个回调并让“父母”决定做什么?
  • @EamonnMcEvoy 这是一个基本的例子来证明这一点,我永远不会使用这样的东西。

标签: javascript object inheritance


【解决方案1】:

让我困惑的是:

this.init=()=>{
   this.names.forEach((name)=>{
      var childObj = new myChildObj(this,name);
      childObj.updateParentProperty();
   })
}

因为一旦创建了所有三个孩子,您的父母就会有一个名称​​cassie(因为这是最后一个调用updateParentProperty 的孩子)。那和你的标签就是为什么我倾向于声称你正在寻找经典继承,就像这样:

经典继承

function myParentObj (name) {
    this.name = name;
    this.names = ['jordan','danny','cassie'];
    this.init=()=>{
       this.names.forEach((name)=>{
          var childObj = new myChildObj(name);
          console.log(childObj);
       });
    }
}

function myChildObj (name) {
	myParentObj.call(this, name);
  
  // Other stuff, only available for myChildObj
}

function init(){
     var parentObj = new myParentObj();
     parentObj.init();
}
document.addEventListener('DOMContentLoaded',init);

var parent = new myParentObj();
console.log(parent);

这意味着子级从其父级继承属性,但当他们自己的名称更改时,当然不会为父级更新它们。

一些简单的观察者模式

好的,您的示例只有一个属性,应注意更改。 我认为像下面示例中的观察者模式有点过分,但对于更大的应用程序来说它非常有用。。观察者模式与 JavaScript 不同(参见 enter link description here)。基本上,您有一个需要监控更改的对象(observable)和一个或多个想要根据这些更改执行操作的对象(observers)。实现可以非常简单(如我的演示)或非常复杂。基本思想是让 observable 维护其观察者的列表。该示例使用自己的简单实现。也有可用的库,我们使用knockout,它为您的页面提供 MVVM 结构,甚至可以根据更改更新 DOM。

function myParentObj(){
    this.name = 'jordan'
    this.names = ['jordan','danny','cassie'];
    this.init=()=>{
       this.names.forEach((name)=>{
          var childObj = new myChildObj(name);
          childObj.subscribe('name', newValue => this.name = newValue);
       })
    }
}
function myChildObj(name){
    this.subscribers = {};
    
    Object.defineProperty(this, 'name', {
      set: newValue => {
        this._name = newValue;
        this.notify('name');
      },
      get: () => this._name
    });
    
    this.subscribe = function (prop, callback) {
      if (!this.subscribers.hasOwnProperty(prop)) {
        this.subscribers[prop] = [];
      }
      
      this.subscribers[prop].push(callback);
    };
    
    this.notify = function (prop) {
      if (this.subscribers[prop]) {
        this.subscribers[prop].forEach(callback => callback(this[prop]));
      }
    };
}
function init(){
     var parentObj = new myParentObj();
     parentObj.init();
}
document.addEventListener('DOMContentLoaded',init);

var parent = new myParentObj();
var child = new myChildObj('bob');
child.subscribe('name', newName => parent.name = newName);
console.log(parent.name);
child.name = 'cassie';
console.log(parent.name);

使用Object.assign

您也可以使用Object.assign,它将所有可枚举属性从一个对象复制到另一个对象。不过,您应该注意,它会复制 所有 属性,即使是那些您可能不想更改的属性。

function myParentObj(){
    this.name = 'jordan'
    this.names = ['jordan','danny','cassie'];
    this.init=()=>{
       this.names.forEach((name)=>{
          var childObj = new myChildObj(this,name);
          childObj.updateParentProperty();
       })
    }
}
function myChildObj(name){
    this.name = name;
}

var parent = new myParentObj();
var child = new myChildObj('cassie');
Object.assign(parent, child);
console.log(child);

【讨论】:

  • 这个例子只是为了演示父/子的概念。你的第一个和第三个例子都没用,我很好奇你的观察者模式,你能给我一个更好的用例例子,然后使用我使用的例子。
  • @JordanDavis 我稍微更新了文本。由于观察者模式本身与 JS 没有区别(en.wikipedia.org/wiki/Observer_pattern),我可以想到数千个示例。这是一个非常普遍的概念。如果你在搜索引擎中输入“观察者模式”,你会找到比我提供的更多的例子。
  • 听起来观察者模式的唯一用例是您使用 WebSockets(或 Node.js),或者您还不如在参数中传递父级?
  • 观察者模式很有用,只要某个对象必须根据某个其他对象的某个状态的变化来做某事。这也可能包括复杂的链(bool 为真,A 通知 B,B 从服务器获取数据并通知 C,C 用它做一些事情......)。很公平,您也可以通过传递实例作为参数来实现这种行为。但是,一旦你有类似“D 订阅 A、B 和 C 并且 C 订阅 E...”之类的内容,它就会变得肮脏(这种关系很常见)。
【解决方案2】:

经典继承

我看到你混入了一些 ES6,所以你可能会像这样使用ES6 standards of object-oriented inheritance 来寻找答案:

class Parent {
  constructor(name = 'jordan') {
    this.name = name
    this.names = ['jordan', 'danny', 'cassie']
  }
  
  init() {
    return this.names.map((name) => {
      return new Child(name)
    })
  }
}

class Child extends Parent {
  constructor(name) {
    super(name)
    
    // other stuff for Child
  }
}

let parent = new Parent()
console.log(parent)
let children = parent.init()
children.forEach((child) => console.log(child))

在 ES5 中,inheritance was usually implemented like this 代替,虽然这不是 完全 ES6 class 所做的事情,因为 ES6 做了一些事情,比如使实例方法不可枚举,等等。

function Parent(name) {
  name = arguments.length > 0 ? name : 'jordan'

  this.name = name
  this.names = ['jordan', 'danny', 'cassie']
}

// member methods
Parent.prototype = {
  init: function init() {
    return this.names.map(function (name) {
      return new Child(name)
    })
  }
}

function Child(name) {
  // basically, super(name)
  Parent.call(this, name)
  
  // other stuff for Child
}


// extending Parent
Child.prototype = Object.create(Parent.prototype)
Parent.prototype.constructor = Parent

var parent = new Parent()
console.log(parent)
var children = parent.init()
children.forEach(function (child) { console.log(child) })

以下几乎是第一个示例中 ES6 代码的 ES5 等效代码:

function Parent() {
  var name = arguments.length > 0 && name !== undefined ? name : 'jordan'

  this.name = name
  this.names = ['jordan', 'danny', 'cassie']
}

// member methods
Object.defineProperties(Parent.prototype, {
  init: {
    configurable: true,
    value: function init() {
      return this.names.map(function (name) {
        return new Child(name)
      })
    },
    writable: true
  }
})

function Child(name) {
  // basically, super(name)
  Parent.call(this, name)
  
  // other stuff for Child
}


// extending Parent
Child.prototype = Object.create(Parent.prototype, {
  constructor: {
    configurable: true,
    value: Child,
    writable: true
  }
})

var parent = new Parent()
console.log(parent)
var children = parent.init()
children.forEach(function (child) { console.log(child) })

事件发射器

查看其他答案下方的 cmets 后,您似乎对观察者类型的模式最感兴趣。 EventEmitter,在 Node.js 中原生定义,是观察者模式使用最广泛的实现。下面是客户端 JavaScript 的 polyfill 演示:

class Parent extends EventEmitter {
  constructor(name = 'jordan') {
    super()

    this.name = name
  }
}

class Child {
  constructor(parent, name = parent.name) {
    this.parent = parent
    this._name = name
  }

  set name(name) {
    console.log('setting child name')

    this._name = name

    this.parent.emit('name', name)
  }
  
  get name() {
    return this._name
  }
}

let parent = new Parent()

parent.on('name', function (name) {
  console.log('setting parent name')

  this.name = name
}.bind(parent))

let child = new Child(parent)

console.log('parent', parent.name)
console.log('child',  child.name)

child.name = 'danny'

console.log('parent', parent.name)
console.log('child',  child.name)
<script src="https://cdn.rawgit.com/mudge/5830382/raw/a4bc230f5bce01ea9a34b0d42247256531b97945/eventemitter.js"></script>

代理

JavaScript 拥有的另一个简洁的 API 称为 Proxy,它允许元编程,即重新定义 JavaScript 在孤立情况下的工作方式:

var parent = {}
var child = {}

var proxy = new Proxy(child, {
  set: (target, property, value) => {
    // target === child
    target['child-' + property] = typeof value + ' ' + value
    parent['parent-' + property] = typeof value + ' ' + value
    
    return value
  }
})

proxy.name = 'jordan'
proxy.age = 54

console.log(parent)
console.log(child)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-16
    • 2019-11-15
    • 1970-01-01
    • 2019-10-14
    相关资源
    最近更新 更多