【问题标题】:Javascript classes - run function every time an object is instantiated?Javascript类 - 每次实例化对象时运行函数?
【发布时间】:2021-11-29 22:59:39
【问题描述】:

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

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

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

在我关于这个项目的previous S/O post 中,有人向我指出函数 " 不应该从构造函数内部调用。构造函数应该只关心设置实例属性。这样,你的子类可以在方法被调用之前完成初始化。”

我的问题是,如果不在构造函数中,我可以在哪里调用 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();
    }
  }
}

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

【问题讨论】:

  • 您已经有一个在页面加载时运行的函数来创建它们。在创建每个对象后,让它调用一个函数。

标签: javascript class oop subclass


【解决方案1】:

“构造函数应该只关心设置实例属性”

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

但是,当涉及到 UI 类组件时,我见过 3 个基本解决方案效果很好:

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

    这里可能比较棘手的是,如果组件需要某种异步初始化,在这种情况下,您可以在完全初始化/渲染时调度异步事件。

  2. 调用者通过调用 component.render(container) 来渲染组件。如果render 被多次调用,该组件将抛出、什么也不做或刷新它的视图。构造函数现在没有副作用,但调用者已经承担了启动渲染的责任(这很好)。

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

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

【讨论】:

    【解决方案2】:

    不要公开暴露课程。相反,公开一个工厂函数,它实例化类并且在返回新创建的实例之前调用你的函数。这样,您就可以控制对象的创建方式。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-04-04
      • 2018-12-09
      • 1970-01-01
      • 2010-11-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多