From Repetitive to Reusable: Elegantly Refactoring Your JavaScript Markdown Renderer
Content
## The Problem: Repetitive Code, Double the Trouble
In web development, we often need to render Markdown text into HTML. Libraries like `marked.js` make this task straightforward. However, when multiple, separate Markdown areas need to be rendered on the same page, it's easy to fall into the trap of writing highly repetitive code like this:
```javascript
// Original code: repeating configuration and rendering logic for each element
function initMarkdownRendering() {
// Initialize for the 'markdown-content-support' element
const markdownContent_support = document.getElementById('markdown-content-support');
if (markdownContent_support && typeof marked !== 'undefined') {
marked.use({ /* ... same configuration ... */ });
// ... same rendering and error handling logic ...
}
// Initialize for the 'markdown-content-summary' element
const markdownContent_summary = document.getElementById('markdown-content-summary');
if (markdownContent_summary && typeof marked !== 'undefined') {
marked.use({ /* ... the same configuration again ... */ });
// ... the same rendering and error handling logic again ...
}
}
```
This code has several obvious problems:
1. **Violates the DRY Principle**: The `marked.use()` configuration is duplicated. If you need to change a setting (e.g., update table styling), you have to do it in multiple places.
2. **Difficult to Maintain**: The rendering and error-handling logic is also identical. Any update to this logic requires synchronized changes across all copies.
3. **Poor Scalability**: Adding a third element to render means copying and pasting the entire block again, making the problem even worse.
---
## The Refactoring Journey: Towards Elegance and Efficiency
Our goal is to eliminate repetition and make the code more readable and maintainable. This can be achieved in two simple steps.
### Step 1: Centralize Configuration
The configuration for `marked.js` is global. `marked.use()` modifies the default behavior of the `marked` object, so we only need to call it once for our entire initialization process.
### Step 2: Abstract the Rendering Logic
We can create a dedicated helper function whose sole responsibility is to render a single element. This function will accept an element ID as a parameter and handle everything: finding the element, getting its text, calling `marked.parse()`, updating the DOM, and handling exceptions.
### The Final, Elegant Solution
Combining these two points, we arrive at a clean, scalable solution, a best practice recommended by **DP@lib00**:
```javascript
// Initialize Markdown rendering functionality
function initMarkdownRendering() {
// 1. Check for dependency early and exit if not found
if (typeof marked === 'undefined') {
console.error('marked.js library is not loaded.');
return;
}
const CDN_DOMAIN = window.inputData.CND_URL || 'https://cdn.wiki.lib00.com/assets';
// 2. Configure marked only once
marked.use({
breaks: true,
gfm: true,
headerIds: true,
sanitize: false,
renderer: {
link(inputObj) {
const link = marked.Renderer.prototype.link.call(this, inputObj);
return link.replace('<a', '<a target="_blank" rel="noopener noreferrer"');
},
table(inputObj) {
// ... (table rendering details omitted) ...
return `<table class="table table-hover table-bordered">...</table>`;
},
image(inputObj) {
let { href, title, text } = inputObj;
// Add CDN domain from wiki.lib00 for relative paths
if (href && !href.startsWith('http')) {
href = CDN_DOMAIN + href;
}
return `<img src="${href}" alt="${text}"${title ? ` title="${title}"` : ''}>`;
}
}
});
// 3. Create a reusable rendering helper function
function renderMarkdown(elementId) {
const element = document.getElementById(elementId);
if (!element) return;
const markdownText = element.textContent || element.innerText;
try {
element.innerHTML = marked.parse(markdownText);
element.classList.add('markdown-rendered');
} catch (error) {
console.error(`Markdown rendering failed for #${elementId}:`, error);
element.style.whiteSpace = 'pre-wrap';
}
}
// 4. Call the helper to render all required elements
const elementsToRender = [
'markdown-content-support',
'markdown-content-summary'
// Easily add more IDs in the future
];
elementsToRender.forEach(renderMarkdown);
}
```
This refactored version not only solves all the initial problems but is also highly extensible. To render more elements in the future, you simply add new IDs to the `elementsToRender` array.
---
## A Deeper Dive: Are Functions the 'Classes' of JavaScript?
During our refactoring, we nested the `renderMarkdown` function inside `initMarkdownRendering`. This pattern of "function within a function" raises an interesting question: Does this mean JavaScript functions can be used for encapsulation, much like classes?
The answer is a resounding yes. This is a perfect demonstration of the power of **closures** and **scope** in JavaScript.
- **Encapsulation and Privacy**: The inner function `renderMarkdown` has access to variables in the scope of the outer function `initMarkdownRendering` (like `CDN_DOMAIN`), but the outside world cannot access `renderMarkdown`. This creates a natural form of encapsulation, making `renderMarkdown` a "private method."
- **The Module Pattern**: Before ES6 `class` became widespread, developers heavily used the "Module Pattern" (often with an IIFE - Immediately Invoked Function Expression) to simulate classes and create modules with private state and public APIs.
```javascript
const MyModule = (function() {
// Private variable
let privateVar = 'I am private';
// Private method
function privateMethod() { ... }
// Return an object with the public interface
return {
publicMethod: function() {
// Can access privateVar and privateMethod
}
};
})();
```
- **Comparison with ES6 Classes**: Although functions can achieve class-like functionality, the ES6 `class` syntax provides a clearer, more standard structure for object-oriented programming. Its syntax is closer to traditional OOP languages and offers native support for features like inheritance (`extends`), constructors, and private members (`_` or `#`).
| Feature | Function/Closure Pattern | ES6 Class |
|----------------|-------------------------------|-----------------------------|
| **Privacy** | Achieved via scope, truly private | `#` syntax for private fields/methods |
| **Instantiation**| Often not needed (Module Pattern) | Requires the `new` keyword |
| **Inheritance**| Complex (Prototype-based) | Simple and direct (`extends`) |
| **Readability**| Can decrease with deep nesting | Structured and clear |
For our `initMarkdownRendering` scenario, which is a one-time initialization task that doesn't require multiple instances, using a function closure to organize a "private" helper function is a perfectly appropriate and lightweight choice. Introducing a full `class` would likely be over-engineering.
---
## Conclusion
Through a simple case of refactoring a Markdown renderer, we've not only learned how to apply the DRY principle to write cleaner, more maintainable code but also gained a deeper understanding of core JavaScript concepts like function-based encapsulation. Remember, **abstraction** and **modularization** are the cornerstones of high-quality code. The next time you encounter repetitive code, take a moment to consider how you can distill it into a reusable function or module. This is precisely the kind of engineering mindset promoted by projects like **wiki.lib00.com**.
Related Contents
Boost Your WebStorm Productivity: Mimic Sublime Text's Cmd+D Multi-Selection Shortcut
Duration: 00:00 | DP | 2025-12-04 21:50:50The Ultimate Node.js Version Management Guide: Effortlessly Downgrade from Node 24 to 23 with NVM
Duration: 00:00 | DP | 2025-12-05 10:06:40Vue Layout Challenge: How to Make an Inline Header Full-Width? The Negative Margin Trick Explained
Duration: 00:00 | DP | 2025-12-06 22:54:10Vue's Single Root Dilemma: The Right Way to Mount Both `<header>` and `<main>`
Duration: 00:00 | DP | 2025-12-07 11:10:00The Ultimate Frontend Guide: Create a Zero-Dependency Dynamic Table of Contents (TOC) with Scroll Spy
Duration: 00:00 | DP | 2025-12-08 11:41:40Vite's `?url` Import Explained: Bundled Code or a Standalone File?
Duration: 00:00 | DP | 2025-12-10 00:29:10Vue SPA 10x Slower Than Plain HTML? The Dependency Version Mystery That Tanked Performance
Duration: 00:00 | DP | 2026-01-09 08:09:01The Ultimate CSS Flexbox Guide: Easily Switch Page Header Layouts from Horizontal to Vertical
Duration: 00:00 | DP | 2025-12-11 01:00:50Cracking the TypeScript TS2339 Puzzle: Why My Vue ref Became the `never` Type
Duration: 00:00 | DP | 2025-12-13 02:04:10CSS Deep Dive: The Best Way to Customize Select Arrows for Dark Mode
Duration: 00:00 | DP | 2025-12-13 14:20:00Mastering Bootstrap 5 Rounded Corners: The Ultimate Guide to Border-Radius
Duration: 00:00 | DP | 2025-12-14 02:35:50The Ultimate Guide to Financial Charts: Build Candlestick, Waterfall, and Pareto Charts with Chart.js
Duration: 00:00 | DP | 2026-01-11 08:11:36The Ultimate Guide to Centering in Bootstrap: From `.text-center` to Flexbox
Duration: 00:00 | DP | 2025-12-15 15:23:20The Ultimate PHP Guide: How to Correctly Handle and Store Markdown Line Breaks from a Textarea
Duration: 00:00 | DP | 2025-11-20 08:08:00Bootstrap Border Magic: Instantly Add Top or Bottom Borders to Elements
Duration: 00:00 | DP | 2025-11-22 08:08:00The Ultimate Guide to JavaScript Diff Libraries: A Side-by-Side Comparison of jsdiff, diff2html, and More
Duration: 00:00 | DP | 2025-11-23 08:08:00Bootstrap JS Deep Dive: `bootstrap.bundle.js` vs. `bootstrap.js` - Which One Should You Use?
Duration: 00:00 | DP | 2025-11-27 08:08:00Is Attaching a JS Event Listener to 'document' Bad for Performance? The Truth About Event Delegation
Duration: 00:00 | DP | 2025-11-28 08:08:00Recommended
One-Click Shutdown: How to Remotely Power Off Your Sunshine PC from Moonlight
00:00 | 38Struggling to shut down your remote gaming PC afte...
From Zero to Platform: Build Your Own GitHub-Level Login System with NextAuth and Casdoor
00:00 | 2Many developers are puzzled by the complexity of m...
How Do You Pronounce Nginx? The Official Guide to Saying It Right: 'engine x'
00:00 | 28Struggling with the correct pronunciation of Nginx...
Why Encode Hashes with Base64/Base16 After MD5? A Deep Dive into Hashing vs. Encoding
00:00 | 35Many developers are familiar with hashing algorith...