将DOM元素包装在JavaScript对象中

7

我注意到在我写的JavaScript代码中有一个常见的模式,想知道是否已经有一个类似最佳实践的模式存在?本质上,它是如何获取DOM元素并将其包装在/与JavaScript对象关联的。以这个例子为例,在您的Web应用程序中需要一个过滤器。您的页面如下所示:

<html>
<head></head>
<body>
    <div id="filter"></div>
</body>
</html>

然后您可以这样包装元素:

var myFilter = new Filter({
    elem: document.getElementById('filter'),
    prop: 'stacks-test',
    someCallback: function() {
        // specify a callback
    }
});

以下是与构造函数传递给的对象spec相关的JavaScript代码:

var Filter = function(spec) {
    this.elem = spec.elem;
    this.prop = spec.prop;
    this.callback = spec.someCallback;
    this.bindEvents();
};

Filter.prototype.bindEvents = function() {
    var self = this;
    $(this.elem).click(function(e) {
        self.updateFeed();
    };
};

Filter.prototype.updateFeed = function() {
    this.prop; // 'stacks-test'
    this.callback();
    // ...
    // code to interact with other JavaScript objects
    // who in turn, update the document
};

这种方法被称为什么,最佳实践和注意事项是什么?

我不想听起来像一个盲目的jQuery粉丝,但是似乎你最好使用jQuery或类似的框架。虽然它的主要目的是帮助你进行DOM选择和操作,但整个库都是基于你所谈论的DOM包装模式构建的,因此它的插件功能和其他辅助函数自然也使你能够轻松地这样做。 - David Tang
我仍然需要使用构造函数,因为一个文档中会有多个过滤器。您能否向我展示如何使用jQuery和Filter对象的示例? - Matty F
我马上会发布答案。同时,下次记得在评论中加入@Box9,这样我才能收到通知。我只是偶然又碰到了这个问题。 - David Tang
2个回答

2
如果我理解您的问题正确,您可能会对Dojo的小部件库Dijit感兴趣 - 它基本上可以做您所要求的事情,还可以做更多其他事情。
在Dijit中,小部件基本上封装了一个DOM节点、它的内容、定义其行为的任何JavaScript以及(单独导入的)CSS来样式化其外观。
小部件有自己的生命周期、注册表和事件(包括许多仅映射到小部件内节点上的DOM事件,例如myWidget.onClick可以有效地调用myWidget.domNode.onclick)。
小部件可以(但不必)在单独的HTML模板文件中定义其初始内容,通过该文件,还可以将模板中的节点上的事件绑定到小部件方法上,并设置引用模板中特定节点的小部件属性。
我只是浅尝辄止。如果您想阅读更多相关信息,可以从以下参考页面开始: 总之,我不知道您最终的目标是什么,也许这对您来说有点过于繁琐(因为我向您介绍了整个库),但我认为这可能会激起您的兴趣。

+1,不可否认,jQuery在这个领域远不如其他库先进。 - David Tang
不错的库,它确实引起了我的兴趣。然而,我正在尝试找出这种模式的低级实现细节。如果我将DOM元素(例如小部件容器元素)的引用作为构造函数对象的属性保留,有哪些挑战和需要记住的事情?周末我读到的一件事是在单击函数上使用bind方法,以减少这些事件的作用域链占用。本质上,我希望找到这种模式的名称,以便可以找到更多的阅读材料。 - Matty F

1
继续我对问题的评论,jQuery是一个潜力巨大的工具,因为它已经提供了一些你所追求的基础。然而,值得一提的是,它也带来了一些自身的复杂性,并且并非所有的"jQuery方式"都是相同的。我会建议一种使用jQuery作为你的"对象模型"的方式,但它可能适合你的需求,也可能不适合。

首先要明确的是。 jQuery 的哲学是,你首先通过使用 $() 或等效的 jQuery() 选择元素。所有操作从概念上都从这里开始。这与创建包装元素并保留对该包装器的引用的对象的方式略有不同,但本质上这就是 jQuery 为您完成的工作。调用 $('#some-id') 将获取 ID 为“some-id”的元素,并将其包装在 jQuery 对象中。


一种方法:编写“过滤器”插件。

initFilter() jQuery方法替换您的构造函数。您可以通过修改jQuery原型并使用jQuery对象作为包装器来实现此目的。jQuery的原型由jQuery.fn引用,因此:

jQuery.fn.initFilter = function (prop, callback) {
    // Save prop and callback
    this.data('filter-prop', prop);
    this.data('filter-callback', callback);

    // Bind events (makes sense to do this during init)
    this.click(function () {
        $(this).updateFeed();
    });
};

然后对于updateFeed()做类似的事情:

jQuery.fn.updateFeed = function () {
    this.data('filter-prop');
    this.data('filter-callback')();
});

然后像这样使用:

$('#filter').initFilter(prop, callback);

请注意,可以将updateFeed内联到单击处理程序中,以防止不必要地污染jQuery命名空间。然而,使用jQuery的一个优点是,如果稍后需要调用对象上的某个函数,由于jQuery将所有引用都绑定到实际元素,因此您无需保留对该对象的引用。如果您想以编程方式调用updateFeed,那么:
$('#filter').updateFeed();

然后将在正确的对象上调用。


考虑的一些事情

这种方法有一些缺点。其中一个是,我们使用 .data() 保存到元素上的所有属性都在所有操作该元素的jQuery函数之间共享。我试图通过在属性名称前加上“filter-”来减轻这个缺点,但基于您的对象的复杂程度,这可能不太适合。

此外,这种确切的方法可能不适用于需要进行大量操作的对象(即具有许多函数的对象),因为所有这些函数对所有jQuery对象都是公共的。有方法可以封装所有这些内容,但我不会在这里详细介绍它们。jQuery-ui通过其小部件实现了这一点,而我正在创建的库中正在尝试另一种替代方案。

然而,总体而言,我最初建议使用jQuery的唯一原因是您的过滤器对象似乎与DOM紧密相关。它将事件绑定到DOM上,根据用户交互修改DOM,基本上它似乎存在于DOM中,所以请使用基于DOM的东西,例如jQuery。


2
我认为这里最大的问题是,正如您所说,所有数据属性都有单一范围,更糟糕的是,所有方法都有单一范围。它使用jQuery.fn作用域来存储页面中可能抛入的所有对象使用的每个函数,降低了模块的可移植性。如果您想要使用多个库构建页面,则必须确保它们所有的方法名称都不相同。使用JS对象模式,您可以拥有myapp.module.Filter.updateFilter和myapp.module.SpecialFilter.updateFilter而不必担心两者会在jQuery.fn.updateFilter上发生冲突。 - Matty F

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接