如何用模块模式优化JS代码?模块DOM获取异常问题咨询
为什么你的模块获取不到DOM元素?
你遇到的核心问题很明确:模块里的settings对象在脚本加载时就立即执行了DOM选择器,但此时浏览器还没渲染出HTML里的.gridster ul元素,所以$('.gridster ul')返回的是空集合。
你在控制台直接调用能拿到元素,是因为此时页面已经完全加载完成了;而模块的settings是在脚本解析阶段就赋值的,早于$(function(){})的DOM就绪事件,自然拿不到目标元素。
快速修复方案
把settings里的DOM选择器改成在init方法里赋值,等DOM就绪后再获取元素:
var gridsterModule = { settings: {}, // 先初始化空对象 init: function () { // 到这里DOM已经就绪,再查询元素就没问题了 this.settings.gridElement = $('.gridster ul li'); this.settings.gridList = $('.gridster ul'); this.simpleTest(); }, simpleTest: function () { console.log(this.settings.gridList); // 现在能拿到正确的元素集合了 } } $(function () { gridsterModule.init(); });
或者更灵活一点,把settings写成一个返回对象的函数,每次调用才执行选择器:
var gridsterModule = { getSettings: function () { return { gridElement: $('.gridster ul li'), gridList: $('.gridster ul') }; }, init: function () { this.settings = this.getSettings(); this.simpleTest(); }, simpleTest: function () { console.log(this.settings.gridList); } }
模块模式的正确用法
模块模式的核心是封装逻辑、隔离作用域、避免全局污染,让你的Gridster代码更易维护。我推荐用闭包版的模块模式,它支持私有成员,更安全:
// 用闭包封装私有变量和方法,只暴露公共接口 var gridsterModule = (function($) { // 私有变量:外部无法直接访问,避免被篡改 var _settings = {}; // 私有方法:只在模块内部复用的逻辑 var _refreshGridster = function() { $('.gridster').gridster().data('gridster').refresh(); }; // 公共接口:暴露给外部调用的方法 return { init: function() { // 初始化时才获取DOM元素 _settings.gridElement = $('.gridster ul li'); _settings.gridList = $('.gridster ul'); // 可以在这里绑定模块相关的事件,进一步封装 this.bindEvents(); }, bindEvents: function() { // 把事件绑定也放进模块,避免全局散落的事件监听 $('.add').on('click', function() { gridsterModule.insertWidgetToGrid({content: '新组件'}); }); }, applyJsonToGrid: function(gridData) { // 把JSON数据渲染到网格的逻辑 _settings.gridList.empty(); gridData.forEach(function(widget) { var $widget = $('<li>' + widget.content + '</li>'); _settings.gridList.append($widget); }); _refreshGridster(); }, insertWidgetToGrid: function(widgetOptions) { // 插入单个组件的逻辑,支持自定义参数 var defaultOpts = {content: '默认组件', sizeX:1, sizeY:1}; var opts = $.extend({}, defaultOpts, widgetOptions); var $widget = $('<li>' + opts.content + '</li>'); _settings.gridList.append($widget); $('.gridster').gridster().data('gridster').add_widget($widget, opts.sizeX, opts.sizeY); } }; })(jQuery); // 传入jQuery,避免依赖全局变量,压缩时还能缩短变量名 // 页面就绪后初始化模块 $(function() { gridsterModule.init(); });
这个版本的优势:
- 私有成员
_settings和_refreshGridster不会被外部篡改,更安全 - 所有Gridster相关的逻辑都集中在一个模块里,找代码、改逻辑都更方便
- 把jQuery作为参数传入,减少全局依赖,代码更健壮
模块模式的注意事项
- 永远不要在模块的顶层直接执行DOM操作,一定要等DOM就绪后(比如
init方法里)再查询元素 - 公共方法尽量保持单一职责:比如
applyJsonToGrid只负责渲染JSON数据,insertWidgetToGrid只负责插入组件,不要一个方法做N件事 - 常用的DOM元素要缓存起来,避免重复查询DOM(就像我们用
_settings做的那样),能提升页面性能
其他Gridster代码优化建议
1. 优化事件委托
你之前用$(document).on("click", ".add", ...)的写法没问题,但如果.add元素有静态的父容器,建议把事件委托绑定到最近的静态父元素,减少事件冒泡的层级,提升性能:
// 假设.add的父元素是.grid-toolbar(页面加载时就存在) $('.grid-toolbar').on('click', '.add', function() { gridsterModule.insertWidgetToGrid(); });
2. 抛弃全局函数
之前的applyJsonToGrid、insertWidgetToGrid都是全局函数,容易和其他代码冲突,全部放进模块里,通过模块的公共接口调用,减少全局变量污染。
3. 升级到ES6模块化(如果项目支持)
如果你的项目用了Webpack、Vite等构建工具,直接用ES6的import/export会更符合现代JS规范:
// gridsterModule.js import $ from 'jquery'; let settings = {}; function _refreshGridster() { $('.gridster').gridster().data('gridster').refresh(); } export function init() { settings.gridElement = $('.gridster ul li'); settings.gridList = $('.gridster ul'); bindEvents(); } function bindEvents() { $('.add').on('click', () => insertWidgetToGrid({content: '新组件'})); } export function applyJsonToGrid(gridData) { // 渲染逻辑... } export function insertWidgetToGrid(widgetOptions) { // 插入逻辑... } // 主文件 import * as gridsterModule from './gridsterModule.js'; $(function() { gridsterModule.init(); });
4. 参数化函数,提升灵活性
比如insertWidgetToGrid可以接受组件的内容、大小、位置等参数,让函数能适应不同场景的需求,而不是硬编码固定内容。
内容的提问来源于stack exchange,提问作者Alexander Groshev

