Javascript 类 - 运行 每次实例化一个对象时函数?

Javascript classes - run function every time an object is instantiated?

我正在开发一个普通的 Javascript + jQuery 应用程序,它使用 classes 和 subclasses 用卡片填充浏览器,每个卡片都显示来自会话存储的数据。有一个 Box 父 class,然后是两个子 class 用于不同类型的框:class GroupBox extends Boxclass SubjectBox extends Box。应用程序的工作方式是,在初始页面加载时,所有组框都出现在页面上。用户可以单击一个组框以在数据中向下钻取导航到与每个组关联的主题框。

我写了一个 displayGroupBoxes 函数 运行 在页面加载时。它遍历会话存储中的数据,并为数据中的每个相关项生成一个 new GroupBox 对象并为其分配数据中的属性。

现在,每次此函数生成一个新的分组框对象时,我想 运行 另一个函数为每个框生成 HTML 标记并将它们附加到浏览器页面。我最初完成此操作的方法是在构造函数中编写所有 DOM 操作代码。这工作得很好,但一位更有经验的开发人员向我指出,所有这些代码都应该分离到它自己的函数中。所以我创建了一个 createGroupElements 函数并将 DOM 操作代码移到了那里。我现在从构造函数中调用该函数,这样每次实例化新对象时它都会 运行。

在我关于这个项目的 中,有人向我指出函数 " 不应该从构造函数内部调用。构造函数应该只关心自己的设置实例属性。这样,您的 subclass 可以在调用方法之前完成初始化。"

我的问题是,如果不在构造函数中,我在哪里可以调用 createGroupElements 函数,以便每次实例化一个新的 GroupBox 时它都会 运行 一次?

相关代码见下方

//////////////////////////////////////////////////////////////////////////////
//  CLASSES \
//////////////////////////////////////////////////////////////////////////////

//////////////////////
// Box Class (parent)
//////////////////////

/**
 * Represents a box.
 * @constructor
 * @param {object} boxOptions - An object to hold our list of constructor args
 * @param {string} name - Name associated with account 
 * @param {string} type - Type associated with account 
 * @param {string} logo - Logo associated with account 
 */

class Box {
  constructor(boxOptions) {
    this.name = boxOptions.name;
    this.type = boxOptions.type;
    this.logo = boxOptions.logo;
  }
}

///////////////////////////
// Group SubClass
//////////////////////////

/**
 * Represents a type "B" Group box. New group boxes are instantiated when the "displayGroupBoxes()" function is called.
 * @constructor
 * @param {object} groupOptions - An object to store our list of constructor args
 * @param {array} subjectBoxes - An array to store subject boxes so that once they've been instantiated, we can store them here and then toggle to hidden/visible.
 */

class GroupBox extends Box {
  constructor(groupOptions) {
    super({
      name: groupOptions.name,
      type: groupOptions.type,
      logo: groupOptions.logo,
    });

    // Use this array to store the subject boxes that have already been created, so if the user clicks on the same subject, we can grab those boxes and show them rather than rebuilding.
    this.subjectBoxes = [];

    this.name = groupOptions.name;
    this.type = groupOptions.type;
    this.logo = groupOptions.logo;
    this.gotoGroup = groupOptions.gotoGroup;
    this.container = groupOptions.container;
    this.subjects = groupOptions.subjects;
    this.subjectIcons = groupOptions.subjectIcons;

    // Create HTML Elements and Append To Page
    // ------ >>>> This should be called from outside the constructor?? <<<< ---- \
    this.createGroupBox();
  }

  // Function to create HTML tags and event listeners for group box elements, and append them to the page

  createGroupBox() {
    // Create container div for box
    const newGroupBox = document.createElement("div");
    newGroupBox.className = "group-box-container";

     //-----> a bunch of code to create html tags and append them goes here, removed for brevity and clarity!

    newGroupBox.append(mainContainer);

    ////////////////////////////////////////////////
               // EVENT LISTENER
    //////////////////////////////////////////////

    // If a user clicks on a group box, we want to rebuild the page with the relevant subject boxes (drilling down in the data), similar to how a SPA application would work.
 
    newGroupBox.addEventListener("click", () => {
      
   // ---> a bunch of code to hide the group boxes that were on the page before, change the header, etc.

   // --->> a bunch of code to generate the subject boxes (or simply toggle them to visible if they've already been created), create HTML tags, and append them to the page

    // Append group box to document inside "group-boxes" container
    this.container.append(newGroupBox);
  }
}

//////////////////////////////////
// Subject SubClass 
/////////////////////////////////

/**
 * Represents a "P" type Subject Box
 * @param {object} subjectOptions - An object to store our constructor args.
 * @param {number} subjectId - The Subject Id associated with a given subject box.
 * @param container - <div></div> element for the subject boxes to be placed in once they're instantiated.
 */
class SubjectBox extends Box {
  constructor(subjectOptions) {
    super({
      name: subjectOptions.name,
      type: subjectOptions.type,
      logo: subjectOptions.logo,
    });
    this.name = subjectOptions.name;
    this.type = subjectOptions.type;
    this.logo = subjectOptions.logo;
    this.subjectId = subjectOptions.subjectId;
    this.container = document.createElement("div");
  }
  show(isShow = true) {
    if (isShow) {
      $(this.container).show();
    } else {
      $(this.container).hide();
    }
  }
}

因此,实际生成新 GroupBox 的函数已经 运行 在页面加载时 document.ready 函数内。但是 GroupBox class 方法 createGroupElements,它为每个新的组框创建 HTML 标记并将它们附加到页面,目前正在从构造函数内部调用。我该如何解决这个问题?

不要公开揭露class。相反,公开一个 工厂函数 来实例化 class 并且 在 return 新创建的实例之前调用您的函数。这样,您就可以控制对象的创建方式。

如果 sidecar 函数(如果它不是同步的)在您 return 新创建的对象实例之前完成很重要,请 asyncawait sidecar 的完成。

"The constructor should only concern itself with setting up the instance properties"

这通常是一个很好的指导方针。您很少(如果有的话)想要使用构造函数来处理它们的外部副作用。利用@NicholasCarey 建议的工厂通常是解决该问题的好方法。

但是,当涉及到 UI class 组件时,我发现有 3 个基本的解决方案非常有效:

  1. 您让组件在您从调用者指定的沙盒(容器)元素中呈现自身。渲染发生在实例化时。构造函数中有副作用,但它们仅限于提供的允许保持模块化的沙箱元素。

    这里可能比较棘手的是,如果组件需要某种异步初始化,在这种情况下,您可以在完全 initialized/rendered.

    时分派一个异步事件
  2. 调用者通过调用 component.render(container) 来渲染组件。如果 render 被多次调用,该组件将抛出、不执行任何操作或者刷新它的视图。构造函数现在没有副作用,但调用者承担了启动渲染的责任(这很好)。

  3. 组件公开它的 DOM 元素,但不附加它。这可能是最灵活的方法。您可以在调用构造函数时直接初始化 DOM,也可以在访问 component.domElement 属性 时进行延迟初始化。组件不必知道使用它的上下文。

    调用者负责在文档中的任意位置添加 component.domElement,例如 mainContainer.appendChild(component.domElement).