jQuery 插件模板 - 最佳实践、约定、性能和内存影

时间:2023-03-27
本文介绍了jQuery 插件模板 - 最佳实践、约定、性能和内存影响的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经开始编写一些 jQuery 插件,并认为使用 jQuery 插件模板设置我的 IDE 会很好.

I've started to write few jQuery plugins and figured it'd be nice to setup my IDE with a jQuery plugin template.

我一直在阅读本网站上与插件约定、设计等相关的一些文章和帖子.我想我会尝试整合所有这些内容.

I have been reading some articles and posts on this site related to plugin convention, design, etc.. and thought I'd try and consolidate all of that.

下面是我的模板,我希望经常使用它,所以很想确保它总体上符合 jQuery 插件设计约定,以及拥有多个内部方法(甚至它的一般设计)的想法是否会影响性能并且容易发生内存问题.

Below is my template, I am looking to use it frequently so was keen to ensure it generally conforms to jQuery plugin design convention and whether the idea of having multiple internal methods (or even its general design) would impact performance and be prone to memory issues.

(function($)
{
    var PLUGIN_NAME = "myPlugin"; // TODO: Plugin name goes here.
    var DEFAULT_OPTIONS =
    {
        // TODO: Default options for plugin.
    };
    var pluginInstanceIdCount = 0;

    var I = function(/*HTMLElement*/ element)
    {
        return new Internal(element);
    };

    var Internal = function(/*HTMLElement*/ element)
    {
        this.$elem = $(element);
        this.elem = element;
        this.data = this.getData();

        // Shorthand accessors to data entries:
        this.id = this.data.id;
        this.options = this.data.options;
    };

    /**
     * Initialises the plugin.
     */
    Internal.prototype.init = function(/*Object*/ customOptions)
    {
        var data = this.getData();

        if (!data.initialised)
        {
            data.initialised = true;
            data.options = $.extend(DEFAULT_OPTIONS, customOptions);

            // TODO: Set default data plugin variables.
            // TODO: Call custom internal methods to intialise your plugin.
        }
    };

    /**
     * Returns the data for relevant for this plugin
     * while also setting the ID for this plugin instance
     * if this is a new instance.
     */
    Internal.prototype.getData = function()
    {
        if (!this.$elem.data(PLUGIN_NAME))
        {
            this.$elem.data(PLUGIN_NAME, {
                id : pluginInstanceIdCount++,
                initialised : false
            });
        }

        return this.$elem.data(PLUGIN_NAME);
    };

    // TODO: Add additional internal methods here, e.g. Internal.prototype.<myPrivMethod> = function(){...}

    /**
     * Returns the event namespace for this widget.
     * The returned namespace is unique for this widget
     * since it could bind listeners to other elements
     * on the page or the window.
     */
    Internal.prototype.getEventNs = function(/*boolean*/ includeDot)
    {
        return (includeDot !== false ? "." : "") + PLUGIN_NAME + "_" + this.id;
    };

    /**
     * Removes all event listeners, data and
     * HTML elements automatically created.
     */
    Internal.prototype.destroy = function()
    {
        this.$elem.unbind(this.getEventNs());
        this.$elem.removeData(PLUGIN_NAME);

        // TODO: Unbind listeners attached to other elements of the page and window.
    };

    var publicMethods =
    {
        init : function(/*Object*/ customOptions)
        {
            return this.each(function()
            {
                I(this).init(customOptions);
            });
        },

        destroy : function()
        {
            return this.each(function()
            {
                I(this).destroy();
            });
        }

        // TODO: Add additional public methods here.
    };

    $.fn[PLUGIN_NAME] = function(/*String|Object*/ methodOrOptions)
    {
        if (!methodOrOptions || typeof methodOrOptions == "object")
        {
            return publicMethods.init.call(this, methodOrOptions);
        }
        else if (publicMethods[methodOrOptions])
        {
            var args = Array.prototype.slice.call(arguments, 1);

            return publicMethods[methodOrOptions].apply(this, args);
        }
        else
        {
            $.error("Method '" + methodOrOptions + "' doesn't exist for " + PLUGIN_NAME + " plugin");
        }
    };
})(jQuery);

提前致谢.

推荐答案

7 个月后

引用自 github 项目

Quoting from the github project

jQuery不行,jQuery插件也不怎么模块化代码.

jQuery is no good, and jQuery plugins is not how do modular code.

说真的,jQuery 插件"不是一个合理的架构策略.编写硬依赖 jQuery 的代码也很愚蠢.

Seriously "jQuery plugins" are not a sound architecture strategy. Writing code with a hard dependency on jQuery is also silly.

[原创]

由于我对这个模板提出了批评,我将提出一个替代方案.

Since I gave critique about this template I will propose an alternative.

为了让生活更轻松,这依赖于 jQuery 1.6+ 和 ES5(使用 ES5 垫片).

To make live easier this relies on jQuery 1.6+ and ES5 (use the ES5 Shim).

我花了一些时间重新设计您提供的插件模板并推出了我自己的.

I've spend some time re-designing the plugin template you've given and rolled out my own.

链接:

  • Github
  • 文档
  • 单元测试 确认通过FF4、Chrome和IE9(IE8 和 OP11 死机.已知 错误).
  • 带注释的源代码
  • PlaceKitten 示例插件
  • Github
  • Documentation
  • Unit tests Confirmed to pass in FF4, Chrome and IE9 (IE8 & OP11 dies. known bug).
  • Annotated Source Code
  • The PlaceKitten example plugin

比较:

我对模板进行了重构,使其分为样板代码 (85%) 和脚手架代码 (15%).目的是您只需要编辑脚手架代码,就可以保持样板代码不变.为此,我使用了

I've refactored the template so that it's split into boilerplate (85%) and scaffolding code (15%). The intention is that you only have to edit the scaffolding code and you can keep leave boilerplate code untouched. To achieve this I've used

  • 继承 var self = Object.create(Base) 而不是直接编辑 Internal 类,你应该编辑一个子类.您所有的模板/默认功能都应该在一个基类中(在我的代码中称为 Base).
  • 约定 self[PLUGIN_NAME] = main; 按照惯例,jQuery 上定义的插件默认会调用 self[PLUGIN_NAME] 上的方法定义.这被认为是 main 插件方法,为了清楚起见,它有一个单独的外部方法.
  • 猴子补丁 $.fn.bind = function _bind ... 使用猴子补丁意味着事件命名空间会在后台自动为您完成.此功能是免费的,不会以可读性为代价(一直调用 getEventNS).
  • inheritance var self = Object.create(Base) Rather then editing the Internal class you have directly you should be editing a sub class. All your template / default functionality should be in a base class (called Base in my code).
  • convention self[PLUGIN_NAME] = main; By convention the plugin defined on jQuery will call the method define on self[PLUGIN_NAME] by default. This is considered the main plugin method and has a seperate external method for clarity.
  • monkey patching $.fn.bind = function _bind ... Use of monkey patching means that the event namespacing is done automatically for you under the hood. This functionality is free and does not come at the cost of readability (calling getEventNS all the time).

OO 技术

最好坚持正确的 JavaScript OO 而不是经典的 OO 仿真.为此,您应该使用 Object.create.(ES5 只是使用 shim 来升级旧浏览器).

It's better to stick to proper JavaScript OO rather then classical OO emulation. To achieve this you should use Object.create. (which ES5 just use the shim to upgrade old browsers).

var Base = (function _Base() {
    var self = Object.create({}); 
    /* ... */
    return self;
})();

var Wrap = (function _Wrap() {
    var self = Object.create(Base);
    /* ...  */
    return self;
})();

var w = Object.create(Wrap);

这不同于人们习惯的基于标准new.prototype的OO.这种方法是首选,因为它再次强化了 JavaScript 中只有对象的概念,并且它是一种典型的 OO 方法.

This is different from the standard new and .prototype based OO people are used to. This approach is preferred because it re-inforces the concept that there are only Objects in JavaScript and it's a prototypical OO approach.

[getEventNs]

如前所述,此方法已通过重写 .bind.unbind 进行重构,以自动注入命名空间.这些方法在 jQuery 的私有版本上被覆盖 $.sub().被覆盖的方法与命名空间的行为方式相同.它基于插件和围绕 HTMLElement 的插件包装器实例唯一地命名事件(使用 .ns.

As mentioned this method has been refactored away by overriding .bind and .unbind to automatically inject namespaces. These methods are overwritten on the private version of jQuery $.sub(). The overwritten methods behave the same way as your namespacing does. It namespaces events uniquely based on plugin and instance of a plugin wrapper around a HTMLElement (Using .ns.

[getData]

此方法已替换为 .data 方法与 jQuery.fn.data 具有相同的 API.它是相同的 API 的事实使它更易于使用,它基本上是一个带有命名空间的 jQuery.fn.data 的薄包装.这允许您设置仅为该插件立即存储的键/值对数据.多个插件可以并行使用此方法而不会发生任何冲突.

This method has been replaced with a .data method that has the same API as jQuery.fn.data. The fact that it's the same API makes it easier to use, its basically a thin wrapper around jQuery.fn.data with namespacing. This allows you to set key/value pair data that is immediatley stored for that plugin only. Multiple plugins can use this method in parallel without any conflicts.

[publicMethods]

publicMethods 对象已被在 Wrap 上定义的任何方法替换为自动公开.您可以直接在 Wrapped 对象上调用任何方法,但您实际上无权访问 Wrapped 对象.

The publicMethods object has been replaced by any method being defined on Wrap being automatically public. You can call any method on a Wrapped object directly but you do not actually have access to the wrapped object.

[$.fn[PLUGIN_NAME]]

这已被重构,因此它公开了更标准化的 API.这个api是

This has been refactored so it exposes a more standardized API. This api is

$(selector).PLUGIN_NAME("methodName", {/* object hash *