跪拜 Guibai
← Back to the summary

The Singleton Pattern in 30 Lines of Vanilla JS: One Popup Manager to Rule Them All

Managing Popups with the Singleton Pattern: Understanding Singleton from a Piece of Vanilla JS

Summary

When multiple places on a page can open popups, the worst thing is to new an unrelated object each time—you can't "record all popups and close them all at once." This article uses a 30-line vanilla JS popup code snippet to break down the three-step template of the Singleton pattern, lazy loading, and the distinction between "static methods / instance methods," which is the easiest point for beginners to confuse. After reading, you will be able to write a globally unique Popup manager yourself.

Opening: A Popup That "Can't Be Managed"

You are building a webpage with an "Open New Page" button. Clicking it uses window.open to open a new tab. The functionality is simple, but when there are many popups, a problem arises: if each opening logic news its own object, they are unaware of each other, and unified management becomes impossible.

I also got stuck here when I first started learning design patterns. Singleton is one of the most fundamental patterns in object-oriented programming and one of the most commonly used in enterprise-level projects—it ensures that a class is instantiated only once in the system. Understanding it is not just about writing one less new; it's about understanding the mindset of "sharing the same state globally." This article will first run through this popup code to see the effect, then break down the three-step template of Singleton, then clarify lazy loading and the difference between "static methods / instance methods," and finally connect it to a DOM button. Reading this article only requires knowledge of JS class and basic DOM events.

Run It First: Two getInstance Calls Return the Same Object

const a = Popup.getInstance();
const b = Popup.getInstance();
console.log(a === b); // true

a and b are the same reference, so a === b is true. This is precisely the meaning of Singleton—there is only this one Popup object globally, and getting it anywhere means operating on the same data. For example, if you record a popup list in the instance, other places can read it and "close all at once," because they are fundamentally the same object.

Singleton Template in Three Steps: Store Instance → Check Existence → Unified Entry

class Popup {
  static ins; // Singleton instance, static property
  static getInstance() {
    if (!Popup.ins) {
      Popup.ins = new Popup();
    }
    return Popup.ins;
  }
  // ...
}

The Singleton template can be broken down into three steps:

  1. Store Instance: Use the static property static ins to hang the unique instance on the class, rather than storing it in an external variable.
  2. Check Existence: if (!Popup.ins) checks whether the instance exists.
  3. Unified Entry: Always get the object through getInstance(), never directly new.

The control flow inside getInstance() looks like this—the first time it's called, ins is empty, so it news one and saves it; subsequent calls find it already exists and return it directly:

flowchart TD
  A[Call getInstance] --> B{Instance exists?}
  B -->|Does not exist| C[new creates instance]
  B -->|Exists| D[Return directly]
  C --> E[Return same instance]
  D --> E

Lazy Loading: Only new on First Call, No Manual new Needed

Note the line if (!Popup.ins) { Popup.ins = new Popup(); }: the instance is not created at class definition time, but is newed only when getInstance() is called for the first time, and then reused thereafter. This is called lazy initialization—create only when needed, avoiding resource occupation from the start. So you never need to manually new Popup(); the first call to getInstance() will automatically create it for you.

Static Methods vs. Instance Methods: Why getInstance Can Be Called Directly with "ClassName."

const a = Popup.getInstance();        // Static method, directly Popup.xxx
a.open("https://www.baidu.com");       // Instance method, must first get instance a

The distinction here is the easiest for beginners to confuse: getInstance has static in front, making it a static method attached to the class, so it can be called directly as Popup.getInstance() without new; whereas open() does not have static, making it an instance method, which requires a specific instance a to call a.open(). Remember it in one sentence—static methods are called with "ClassName.", instance methods are called with "instance.".

Connecting to the DOM: getElementById and Event Binding

const openBtn = document.getElementById("openBtn");
openBtn.addEventListener("click", () => {
  a.open("https://www.baidu.com");
});

getElementById("openBtn") finds the <button id="openBtn"> element on the page by id; addEventListener binds a click event to it; clicking calls a.open(). Note that the Singleton a is used here, so no matter where on the page it's triggered, it goes through the same Popup instance.

The window.open(url, '_blank') Line Inside open

open(url) {
  window.open(url, '_blank');
}

window.open is used to open a new window/tab; the second parameter '_blank' means open in a new tab (rather than replacing the current page, or opening in a specifically named frame). Here, "which url to open" is passed in as a parameter, decided by the external caller.

Why Use Singleton to Manage Popups Instead of new Each Time

In real projects, popups often need to "record all, close uniformly." Singleton allows all opening logic to share the same instance, with the popup list naturally centralized in one place. The first time I wrote this kind of functionality, I took a shortcut and directly new Popup() twice, resulting in two buttons each holding different instances, and the close logic couldn't find each other—that's the pitfall of not using Singleton. After switching to Singleton, any entry point gets the same object, and centralized management immediately works.

Summary

Concept One-Sentence Explanation Key Code
Singleton Instantiated only once globally, shared everywhere static ins + getInstance()
Lazy Loading Only new on first call if (!Popup.ins) Popup.ins = new Popup()
Static Method Attached to class, called directly with class name static getInstance()
Instance Method Requires an instance first to call a.open(url)
window.open _blank Opens in a new tab window.open(url, '_blank')
getElementById Gets DOM element by id document.getElementById('openBtn')

Common Mistakes and Further Learning

Conclusion

After learning this, Singleton is no longer an abstract term—it is simply "locking a unique instance with a static property + a unified entry to get it." In scenarios like popup management that require globally shared state, it is much cleaner than newing everywhere. The next step is to look at module-level singletons and TypeScript implementations, applying this mindset to larger projects.