jQuery扩展和闭包

4

我正在学习JavaScript,并且想更深入地了解jQuery。我创建了一个非常简单的JS“表单控制器”,所以当我将表单作为参数传递给对象时,它会连接事件并劫持提交:

var FormController = function (form) {

    // private field
    var _form = $(form);
    var _url = _form.attr('action');
    var _isValid = false;

    $(form).submit(function (e) {
        submitForm();
        e.preventDefault();
    });

    var disableAll = function () {
        _form.find('select,input,textarea').attr('disabled', 'disabled')
    };

    var enableAll = function () {
        _form.find('select,input,textarea').removeAttr('disabled')
    };

    var submitForm = function () {

        disableAll();
        _isValid = true;

        if (_isValid) {
            $.ajax({
                url: _url,
                type: 'POST',
                success: function (data, textStatus, jqXHR) {
                    enableAll();
                    var obj = jQuery.parseJSON(jqXHR.responseText);
                    print(data.Result ? data.Result : "[No Result]");
                    print(textStatus.toString());
                },
                error: function (data, textStatus, jqXHR) {
                    print(textStatus);
                    _form.css('border', '1px solid Red');
                    enableAll();
                }
            });
        }
    };

    // public fields and functions
    return {
        getIsValid: function () { return _isValid; },
        submit: function () { return submitForm(); }
    };
};

一切都按预期进行。现在我想创建一个jQuery扩展,以便将该对象应用于结果:

$.fn.formController = function () {
    return this.each(function (i, item) {
        new FormController(item);
    });
};

我的代码也按预期运行,但是... GC会收集那个对象吗?或者因为事件被连接了,它算作被引用的吗?

现在,我想保留我的控制器实例,这样我就可以在创建后操作它们,例如调用方法。像这样:

$('#myForm').formController('submit');

我已经尝试了几种方法,但是我无法实现它。我曾尝试使用一个闭包和一个跟踪项目的对象,等等...但是我只是在"this"上混乱了。
什么是正确的做法?我认为即使它能工作,到目前为止我所做的一切都可能是错误的。
谢谢。
更新
如果问题不够清楚,我很抱歉。我想要的是一种方法,可以这样做:$('#myForm').formController('submit');,然后该函数将查找与该HTML对象关联的"FormController"对象,并调用此"submit"成员。
我想实现类似于此的功能:
$.fn.formController = function (name) {

    if(!document._items)
        document._items = [];

    if (name) {
        return this.each(function (i, item) {
            var id = $(item).data('form-controller');

            if (id) {
                var fc = document._items[id];
                var member = fc[name];
                if (member) {
                    if (typeof member == 'function')
                        member();
                }
            }
        });
    }
    else {    
        return this.each(function (i, item) {
            var id = document._items.push(new FormController(item)) - 1;
            $(item).data('form-controller', id.toString());
        });
    }
};

在这种方法中的问题是我将集合作为全局对象来保留,而我想要的是使其成为内部对象。我尝试使用闭包,但是我遇到了“this”(指向DOM窗口)或“_items”变量为空的问题。
正确的方法是什么?
3个回答

2
您可以使用jQuery UI的Widget工厂,它为您提供了这个功能的开箱即用解决方案。
$.widget("vtortola.formcontroller", {
    _create: function() {
         // Use `this.element` to initialize the element.
         // You can also use this.options to get the options object that was passed.
    },
    _destroy: function() {
        // You can unbind events here to remove references from the DOM, so is'll get
        // deleted by the garbage collector.
    },
    submit: function() { /* ... */ }
});

// Example
$('#form').formcontroller({foo: 123}); // Calls `_create()` with the supplied options
$('#form').formcontroller('submit'); // Calls the `submit` method

小部件工厂还提供了选项设置器/获取器、默认选项和其他很酷的功能。有关详细信息,请参见官方文档bililite的教程。如果您没有使用jQuery UI的其他内容,可以将小部件工厂代码作为独立的代码使用,它不依赖于jQuery UI的其他部分。
如果您喜欢不使用jQuery UI的小部件工厂,可以直接在.data()上存储对象(这也是小部件工厂所做的),无需手动保持它们的集合和跟踪。您的代码应该类似于:
$.fn.formController = function () {
    var args = arguments;
    return this.each(function (i, item) {
        var $this = $(this), _obj;
        // When calling `$(..)`.formController() with arguments when the object already
        // exists, it treats the first argument as the method name and passes the rest
        // as arguments to that method.
        if (arguments.length && (_obj = $this.data('FormController'))) {
            _obj[args[0]].apply(_obj, Array.prototype.slice.call(args, 1));
        } else {
            $this.data('FormController', new FormController(item));
        }
    });
};

1
哦,这里的 arguments 是指传递给 each() 函数的参数。我已经更新了代码来修复这个问题。 - shesek
1
代码已更新,之前的小部件名称缺少命名空间。 - shesek
太棒了!非常感谢。这个例子将帮助我更好地理解js和jQuery,并开始编写自己的扩展。再次感谢 :) - vtortola
我得等到明天才能给你打赏 :P - vtortola
1
你好,我很高兴能够帮助你。我强烈建议你尝试将扩展程序编写为小部件,它提供了面向对象代码的良好基础,并为开发者提供了许多常用实用工具,这可以加快开发速度。 - shesek
显示剩余2条评论

0

使用

(function($){
    $.fn.formController = function(parameter1, parameter2){
            //this = #myForm
    }
)(jQuery);

并阅读this


我感兴趣的是方法内部,我自己找到了那里:D 例如,我想知道如何在该方法中创建一个带有数组的闭包,或者另一种实现我想要的功能的技术。 - vtortola
@vtortola:我没听懂你的意思,你在里面想做什么? - genesis
好的,正如您所看到的,当我调用“ $('#theForm').formController()”时,我创建了一个名为“ FormController”的对象。我想要的是之后调用该对象的方法的一种方法。例如,“ $('#theForm').formController('submit')”。为此,我需要在内部保留一个哈希表,其中包含“ FormController”的所有实例,但我不知道可以使用什么作为键,并且我不知道应将集合放置在哪里或如何执行它。 - vtortola
@vtortola:我已经为您发布了一段代码,它正好符合您的需求!this指的是#theForm。 - genesis
是的,但我如何从表单对象中获取之前创建的FormController实例? - vtortola
我已经更新了问题,如果之前不够清晰,对不起。 - vtortola

0

我也尝试过将集合创建为函数本身的属性。

它可以工作,但我不知道它是否正确/适当 :D

$.fn.formController = function (name) {

    if (name) {
        return this.each(function (i, item) {
            var id = $(item).data('form-controller');

            if (id) {
                var fc = $.fn.formController.Items[id];
                print('get: ' + id + ' ' + (fc?'found':'not found'));
                var member = fc[name];
                if (member) {
                    if (typeof member == 'function')
                        member();
                }
            }
        });
    }
    else {

        return this.each(function (i, item) {
            var id = $.fn.formController.Items.push(new FormController(item)) - 1;
            print('add: ' + id);
            $(item).data('form-controller', id.toString());
        });
    }
};
$.fn.formController.Items = [];

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