具有属于给定插件实例的“本地”变量的jQuery插件

时间:2018-12-12 16:23:11

标签: javascript jquery scope jquery-plugins

我正在使用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()吗?

谢谢!

2 个答案:

答案 0 :(得分:1)

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

我建议您不要再使用多个功能,而应该在第二次迭代中尝试使用面向对象的方法。但是,您可以将所有必需的参数传递给这些函数,这样它们就不必依赖模糊的this值。

由于您仅包含了部分代码片段,因此我无法清楚地了解您要完成的工作,但是我在下面提供了一个示例插件,重点介绍了如何正确使用范围。

(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函数中的一切。无需宣布额外的帮助者。这将帮助您确保所有逻辑均正常运行。然后开始将所有重用的代码移到接受它们需要运行的所有参数的辅助函数中。

nitpick有点相关,但不是马上有用: pluginName之外的函数表达式(例如directChildren)是全局变量。请确保以var开头,以使它们的范围限于您的IIFE。

答案 1 :(得分:0)

就像@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()时,您都会收到最初创建的实例。

因此,您将可以访问所有私人成员,并设计公共方法。

PS。我并不是说这绝对是最好的,它只是如何解决”任务的示例,它以一种仅给定插件可以在内部检索和修改的方式存储多维数组和根元素“

希望它足够通用;)加油!