【问题标题】:jQuery plugin with "local" variables belonging to the given plugin instance具有属于给定插件实例的“本地”变量的 jQuery 插件
【发布时间】:2019-05-13 19:23:59
【问题描述】:

我正在开发一个 jQuery 插件,但我遇到了变量“范围”的问题。每个插件都需要跟踪一个相当大的多维数组,以及 jQuery 插件附加到的根元素。

如下图,我在插件代码的顶部定义了var $rootEl = null;var multidArray = null;;但是,当我在多个元素上运行 $(anySelector).pluginName("value"); 时(或者当我使用不同的选择器调用 .pluginName() 两次时),这些变量似乎没有被沙盒化/作用于插件实例,因此第二个实例会覆盖这两个值并且第一个实例在 DOM 中看起来是空白的(因为所有 jQuery 操作都应用于 $rootEl,而原始值在第二个 jQuery 插件调用时被覆盖)。

(function ($) {

    var $rootEl = null;
    var multidArray = null;

    directChildren = function(uuid) {

        return $rootEl.children("[data-uuid=" + uuid + "]");

    },

    incrementCounter = function() {
        this.counter++;
    },

    resetGenIteration = function() {
        this.counter = 0;
    },

    process = function(item) { // Will be called from a callback(val) in another
        // javascript file but still needs to have access to the local variables
        // of the current jQuery plugin

        incrementCounter();

        ...

    },

    ...

    $.fn.pluginName = function(rootvar) {

        this.each(function() {

            $rootEl = $(this);

            resetGenIteration();
            populate(rootvar);

            $rootEl.addClass("pluginName").delegate("div", "click", divClick);

        });

    }

}(jQuery));

昨天我试图将插件转换为使用 init 和 this.varname 来存储值,它似乎为插件的每个实例正确地保留了 this.$rootEl 属性,但我不得不将我的所有函数移到里面的var PluginName = { ... } 并将它们从funcName = function() 切换到funcName: function() 以便能够看到插件实例的“本地”变量。然后我遇到了许多未定义函数的错误,因此我尝试在所有函数调用前加上this.PluginName.,分别读取为this.functionName()PluginName.functionName(),这对某些函数有效,但不是全部。我的一些函数是从另一个运行回调函数并将数据传递给它的 jQuery 插件调用的,在这些情况下,this.$rootEl 变为 undefined

(function ($){

    var PluginName = {

        init: function(options, rootEl) {

            this.options = $.extend({}, this.options, options);

            this.rootEl  = rootEl;
            this.$rootEl = $(rootEl);

            this.setListeners();

            return this;

        },

        options: {
            rootvar: "test"
        },

        ...

        setListeners:function (){

            var self = this;

            this.$rootEl.on("div", "click", function () {
                $.proxy(self.divClick, self);
            });

        }

    };

    ...

    $.fn.pluginName = function(options) {

        this.init = function(options, rootEl) {

            this.options = $.extend({}, this.options, options);

            this.rootEl  = rootEl;
            this.$rootEl = $(rootEl);
            this.multidArray = null;


            return this;

        };

        this.each(function() {

            var plugInstance = Object.create(PluginName);
            plugInstance.init(options, this);
            $.data(this, 'pluginName', plugInstance);

        });

    }

}(jQuery));

如何重构我的代码以存储多维数组和根元素,以使只有给定插件才能在内部检索和修改?我肯定不应该将.data() 用于大型数组吗?

谢谢!

【问题讨论】:

    标签: javascript jquery scope jquery-plugins


    【解决方案1】:

    插件只是一个附加到 jQuery 对象的函数。它没有多个实例,而是可以多次调用。每次调用都会创建一个新的作用域,但是,在pluginName 函数之外声明的变量将被每次调用共享。

    与您在第二次迭代中尝试的面向对象方法相比,我建议您重新使用多个函数。但是,您可以将所有必需的参数传递给这些函数,这样它们就不必依赖模棱两可的 this 值。

    由于您只包含了代码的部分 sn-p,因此我无法清楚地了解您要完成的工作,但我在下面包含了一个示例插件,它强调了如何正确使用范围。

    (function ($) {
      // declare variables that can be shared by every call of "pluginName" here
    
      var defaultColor = 'green'
    
      // passing the element as an argument means we don't have to rely on ambiguous 'this'.
      function someTransformation ($el, someColor) {
        return $el.css({backgroundColor: someColor})
      }
    
    
      $.fn.pluginName = function (color, idx) {
        // declare variables that should be reinitialized on every call of "pluginName" here
        var els = []
    
        this.each(function () {
          var $el = $(this)
          someTransformation($el, defaultColor)
          els.push($el)
        })
    
        // we can still use the array we stored
        if (els[idx]) {
          someTransformation(els[idx], color)
        }
      }
    })($)
    
    $('#first i').pluginName('red', 2)
    $('#second i').pluginName('blue', 0)
    i {
      display: inline-block;
      width: 50px;
      height: 50px;
      margin: 10px;
    }
    
    div {
     border: 1px solid black;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    
    <div id="first">
     <i></i><i></i><i></i><i></i>
    </div>
    
    <div id="second">
      <i></i><i></i><i></i><i></i>
    </div>

    如果您仍然遇到问题,请先在pluginName 函数中执行所有操作。没有声明额外的助手。这将帮助您确保所有逻辑都正常工作。然后开始将所有重复使用的代码移动到辅助函数中,这些函数接受它们运行所需的所有参数。

    有点相关但不是立即有用的挑剔: pluginName(例如directChildren)之外的函数表达式是全局的。确保以var 开头,以使它们的范围仅限于您的 IIFE。

    【讨论】:

    • 这个答案很有帮助,而且看起来很优雅。我奖励你解决方案 + 简洁的投票。
    【解决方案2】:

    正如@Damon 所说,jquery 插件没有什么特别之处——它只是一个函数,而 JS 就是关于函数的;)

    因此,您遇到的所有问题都是因为没有考虑到您的所有用法。 我不知道您有什么用途(或计划有什么用途),但我会这样做。这是一个为每个实例存储“bigArray”的插件。增加它并放入对象 html。之后的解释:

    (function($) {
            let _plugInstances = [];
    
            function PlugFactory(options) {
                let _plugInstances = [],
                    _create = function(options) {
                        let $el = $(this),
                            id = $el.data('PlugID') || (+new Date +''+Math.random()).replace('.', '_');
                        if (_plugInstances[id])
                            return _plugInstances[id];
                        $el.data('PlugID', id);
                        _plugInstances[id] = new Plug($el, options);
                        return _plugInstances[id];
                    };
    
                this.instantiate = function(options) {
                    let newPlugs = this.map(_create);
                    return newPlugs.length > 1 ? newPlugs : newPlugs[0];
                }
            }
            function Plug(rootEl, options) {
                let _$rootEl = rootEl,
                    _options = options,
                    _bigArr = [];
                this.add = (item) => {
                    _bigArr.push(item);
                    return this;
                };
                this.refresh = () => {
                    _$rootEl.html(_$rootEl.data('PlugID') + ":" + _bigArr.join('-'));
                    return this;
                }
            }
    
            $.extend({plug: new PlugFactory});
            // extend plugin scope
            $.fn.extend({
                plug: $.plug.instantiate
            });
        })(jQuery);
    
        // Usage
        $(document).ready(function () {
            $('div').plug({opt: 'whatever'});
            $('div').on('click', function() {
                $(this).plug()
                    .add(1)
                    .refresh();
            })
            $('#i4').plug({opt: 'whatever'}).add(2).refresh();
        });
    div{border: solid 2px gray;}
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div>1</div><div>2</div><div>3</div>
    <br/>
    <div id="i4">4</div>

    说明:

    如您所见,您在 $.fn.pluginName 中使用的东西已移至单独的函数(比如说类)(PluginFactory)

    它实际上是存储插件实例的函数。

    正如您可能注意到的,有一些变量从下划线开始,它们是private properties,问题是它们是closures,因此它们仅在定义它们的函数中可见,并且由于它们是变量,因此它们无法访问函数实例属性。

    另外请考虑 ES6 “上下文安全”的东西,例如 short function notationlet

    现在是实例。

    如您所见,我们为每个元素生成了一些 UID(您可以使用任何其他生成器,这只是 POC

    接下来我们使用new 运算符创建新实例

    并将其保存到 Map 以简化获取实例的操作。

    并将我们的UID保存在元素jquery数据(PliginID)中, 所以现在我们知道我们的元素是否已经有插件的实例了。

    现在是主要部分(插件功能)。

    这实际上是您的实例,它将在“.plug”的调用过程中持续存在。

    每次获得 $('#element').plug() 时,您都会收到最初创建的实例。

    这样你就可以访问你所有的私有成员,并设计你的公共方法。

    附言。我并不是说它是最好的,只是绝对是众多中的一个,作为如何解决 任务的示例“以只有给定插件可以在内部检索和修改的方式存储多维数组和根元素"

    希望它足够笼统,能有所帮助;)干杯!

    【讨论】:

    • 感谢您的深入解释。我奖励你是因为你澄清了一些我在对该主题进行自己的研究时不理解的令人困惑的事情。
    猜你喜欢
    • 1970-01-01
    • 2011-02-22
    • 2016-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多