jQuery 个具有属于给定插件实例的 "local" 个变量的插件
jQuery plugin with "local" variables belonging to the given plugin instance
我正在开发 jQuery 插件,但我遇到了 "scope" 变量的问题。每个插件都需要跟踪一个相当大的多维数组,以及 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()
才能看到插件实例的 "local" 变量。然后我遇到了许多未定义函数的错误,所以我尝试在所有函数调用前加上 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()
吗?
谢谢!
插件只是附加到 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
函数中执行 所有操作。无需声明额外的助手。这将帮助您确保所有逻辑都正常工作。然后开始将任何重复使用的代码移动到辅助函数中,这些辅助函数接受它们需要的所有参数 运行.
有点相关但不是立即有用的吹毛求疵:
pluginName
之外的函数表达式(例如 directChildren
)是全局变量。确保在前面加上 var
以将它们限定在您的 IIFE 范围内。
正如@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 中使用的内容已移至单独的函数(比方说 class)(PluginFactory)
实际上是存储插件实例的函数。
正如您可能注意到的,有些变量是从下划线开始的,它们是 private properties the thing is that they are closures 因此它们仅在定义它们的函数内部可见,并且由于它们是变量,因此它们不能作为函数实例访问 属性.
另外请考虑 ES6 "context safe" 诸如 short function notation and let.
之类的东西
现在是实例。
如您所见,我们为每个元素生成了一些 UID(您可以使用任何其他生成器,这只是一个 POC)
接下来我们使用 new 运算符创建新实例
并将其保存到 Map 中以简化获取实例的过程。
并将我们的UID保存在元素jquery数据中(PliginID),
所以现在我们知道我们的元素是否已经有一个插件实例。
现在是主要部分(插件功能)。
这实际上是您的实例,它会在“.plug”的整个调用过程中存在。
每次您获得 $('#element').plug() 时,您都会收到您最初创建的实例。
因此您将可以访问所有私有成员,并设计您的 public 方法。
PS。我并不是说它绝对是最好的,只是其中之一,作为如何解决 "store the multi-dimensional array and root element in a way that only that given plugin can internally retrieve and modify"
任务的示例
希望它足够通用,对您有所帮助 ;) 干杯!
我正在开发 jQuery 插件,但我遇到了 "scope" 变量的问题。每个插件都需要跟踪一个相当大的多维数组,以及 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()
才能看到插件实例的 "local" 变量。然后我遇到了许多未定义函数的错误,所以我尝试在所有函数调用前加上 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()
吗?
谢谢!
插件只是附加到 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
函数中执行 所有操作。无需声明额外的助手。这将帮助您确保所有逻辑都正常工作。然后开始将任何重复使用的代码移动到辅助函数中,这些辅助函数接受它们需要的所有参数 运行.
有点相关但不是立即有用的吹毛求疵:
pluginName
之外的函数表达式(例如 directChildren
)是全局变量。确保在前面加上 var
以将它们限定在您的 IIFE 范围内。
正如@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 中使用的内容已移至单独的函数(比方说 class)(PluginFactory)
实际上是存储插件实例的函数。
正如您可能注意到的,有些变量是从下划线开始的,它们是 private properties the thing is that they are closures 因此它们仅在定义它们的函数内部可见,并且由于它们是变量,因此它们不能作为函数实例访问 属性.
另外请考虑 ES6 "context safe" 诸如 short function notation and let.
之类的东西现在是实例。
如您所见,我们为每个元素生成了一些 UID(您可以使用任何其他生成器,这只是一个 POC)
接下来我们使用 new 运算符创建新实例
并将其保存到 Map 中以简化获取实例的过程。
并将我们的UID保存在元素jquery数据中(PliginID), 所以现在我们知道我们的元素是否已经有一个插件实例。
现在是主要部分(插件功能)。
这实际上是您的实例,它会在“.plug”的整个调用过程中存在。
每次您获得 $('#element').plug() 时,您都会收到您最初创建的实例。
因此您将可以访问所有私有成员,并设计您的 public 方法。
PS。我并不是说它绝对是最好的,只是其中之一,作为如何解决 "store the multi-dimensional array and root element in a way that only that given plugin can internally retrieve and modify"
任务的示例希望它足够通用,对您有所帮助 ;) 干杯!