Chained select box and an Array.prototype function小提琴
我有一个链式选择框和一个Array.prototype
函数将两个数组组合成一个相关的数组。它们用于不同的用途和不相关的。但是当它们在脚本中放在一起时,它会让undefined is not a function
指向此部分this.forEach
作为错误来源。我最终用这个(Example fiddle)替换了Array.prototype
函数:
function associate(keys, values){
return keys.reduce(function (previous, key, index) {
previous[key] = values[index];
return previous
}, {})
}
我只是好奇为什么链式选择框与Array.prototype
功能之间存在冲突?
以下是代码:
$(document).ready(function(){
var data = [
{
"bigcat": "Sport",
"cat": "mainstream",
"choice": "football"
},
{
"bigcat": "Sport",
"cat": "mainstream",
"choice": "basketball"
},
{
"bigcat": "Sport",
"cat": "niche",
"choice": "MMA"
},
{
"bigcat": "Sport",
"cat": "niche",
"choice": "wrestling"
}
]
var $select = $('select');var $option="";
$.each(data, function (index, i) {
$option = $("<option/>").attr("value", i.choice).text(i.bigcat + "@" +( i.cat || "") +"@" + i.choice);
$select.append($option);
});
$select.dynamicDropdown({"delimiter":"@"});
});
Array.prototype.associate = function (keys) {
var result = {};
this.forEach(function (el, i) {
result[keys[i]] = el;
});
return result;
};
var animals = ['Cow', 'Pig', 'Dog', 'Cat'];
var sounds = ['Moo', 'Oink', 'Woof', 'Miao'];
console.dir(sounds.associate(animals));
动态下拉框脚本
(function($) {
$.dynamicDropdown = {
/**
* Escape quotation marks and slashes
* @param {String} String to format
* @return {String}
*/
escapeQuotes : function(str) {
return str.replace(/([""\\])/g, "\\$1");
},
/**
* Build a <select> box from options
* @param {Array} Options
* @return {jQuery}
*/
buildSelectDropdown : function(options) {
var select = $(document.createElement("select"));
var option = null;
// Add options
for (var i in options) {
option = $(document.createElement("option"))
.val($.isArray(options[i]) ? i : options[i])
.html(i)
.appendTo(select);
}
return select;
}
};
$.fn.dynamicDropdown = function(options) {
var settings = {
"delimiter" : " ?",
"className" : "dynamic-dropdown"
};
$.extend(settings, options);
return $(this).each(function() {
/**
* Main dropdown (this)
* @type jQuery
*/
var mainDropdown = $(this);
/**
* Position of initial value of main dropdown
* @type Array
*/
var initialPosition = [];
/**
* Main array of all elements
* @type Array
*/
var data = [];
/**
* Array of all <select> boxes
* @type Array
*/
var selectElements = [];
/**
* Flag denoting whether the dropdown has been initialized
* @type Boolean
*/
var isInitialized = false;
/**
* Prepare a dropdown for use as a dynamic dropdown
* @param {jQuery|string} Dropdown
* @param {jQuery|HTMLElement} Sibling
* @param {Number} Level
* @param {Number} Position in the main array
* @return {jQuery}
*/
var prepareDropdown = function(dropdown, sibling, level, position) {
return $(dropdown)
.addClass(settings.className)
.data("level", level)
.data("position", position)
.insertAfter(sibling)
.each(buildDynamicDropdown)
.change(buildDynamicDropdown);
};
/**
* Initialize the dynamic dropdown <select> boxes
* @return {jQuery}
*/
var buildDynamicDropdown = function() {
var level = $(this).data("level") + 1;
var position = "";
// Get the position in the main data array
if (!isInitialized) {
for (var i = 0; i < level; i++) {
position += "[\"" + initialPosition[i] + "\"]";
}
} else {
position = $(this).data("position") + "[\"" + $.dynamicDropdown.escapeQuotes($(this).val()) + "\"]";
// Remove old <select> boxes
for (var i = selectElements.length; i > level; i--) {
selectElements.pop().remove();
}
}
var selectionOptions = eval("data" + position);
if ($.isArray(selectionOptions)) {
// Build the next dropdown
selectElements.push($.dynamicDropdown.buildSelectDropdown(selectionOptions));
if (!isInitialized) {
$(this).val(initialPosition[level - 1]);
}
prepareDropdown(selectElements[selectElements.length - 1], this, level, position);
} else if (!isInitialized) {
// Set the final value
$("option:contains('" + initialPosition[level - 1] + "')", selectElements[selectElements.length - 1]).attr("selected", "selected");
isInitialized = true;
} else {
// Set the value
mainDropdown.val($(this).val());
}
return $(this);
};
// Build the dynamic dropdown data
mainDropdown.children().each(function() {
var parts = $(this).html().split(settings.delimiter);
var name = "data";
var value = null;
// Set the initial position
if ($(this).is(":selected")) {
initialPosition = parts;
}
// Build the position of the current item
for (var i in parts) {
if(typeof parts[i] != "string") continue;
name += "[\"" + $.dynamicDropdown.escapeQuotes(parts[i]) + "\"]";
value = eval(name);
if (!value) {
// Set the level to have an empty array to be filled
eval(name + " = [];");
} else if (!$.isArray(value)) {
// Add data to the array
eval(name + " = [" + eval(name) + "];");
}
}
// Set the final index to have the value
eval(name + " = \"" + $(this).val() + "\";");
});
// Build the dynamic dropdown
selectElements[0] = $.dynamicDropdown.buildSelectDropdown(data);
prepareDropdown(selectElements[0], this, 0, "");
}).hide();
};
})(jQuery);
(function($)
{
$.fn.dynamicDropdown = function(options) {
var settings = {
"delimiter" : " » ",
"className" : "",
"levels" : [
{'markup':"{dd}",'class':false,'id':false,'disabled':false},
{'markup':"{dd}"}
]
};
$.extend(settings, options);
return $(this).each(function() {
//the original dropdown element
var mainDropdown = $(this);
var defaultSelection = false;
var levels = {};
//insert dropdown into markup, and finally place it in the DOM, attaching events, etc.
var insertSelectDropdown = function(dd, level, sibling, position){
var markup = settings.levels[level] && settings.levels[level].markup ? settings.levels[level].markup : '{dd}';
//to support markup both placing the dropdown within a container and without a container,
//its necessary to use a little silly dom magic
var container = $('<div>'+settings.levels[level].markup.replace('{dd}',$('<div></div>').append(dd.addClass('ddlevel-'+level)).html())+'</div>').children()['insert'+position](sibling);
var select = container.parent().find('select.ddlevel-'+level).removeClass('ddlevel-'+level);
if (settings.levels[level]['class']){
select.addClass(settings.levels[level]['class']);
}
if (settings.levels[level].id){
select.attr('id',settings.levels[level].id);
}
if (settings.levels[level].disabled){
select.prop('disabled','disabled');
}
return select.data('level',level).data('container',container).data('levels',dd.data('levels')).change(updateDropdowns);
}
//produce markup for select element
var buildSelectDropdown = function(options, selected) {
var select = $('<select></select>').data('levels',options);
// Add options
$.each(options,function(index,value){
var option = $('<option></option>').html(index);
if (typeof(value) != 'object'){
option.val(value);
}
if (selected && index == selected){
option.attr('selected','selected');
}
select.append(option);
});
return select;
};
//the event function that runs each time a select input value changes
var updateDropdowns = function(){
var current = $(this).children(':selected').html();
var options = $(this).data('levels')[current];
//a non-object means this is the end of the line, set the value
if (typeof(options) != 'object'){
mainDropdown.val($(this).val());
}
else {
//remove any dds after the one that just changed
var dd = $(this);
while (dd.data('next')){
dd = dd.data('next');
dd.data('container').detach();
}
var level = $(this).data('level') + 1;
//add new dds
$(this).data('next',insertSelectDropdown(buildSelectDropdown(options, defaultSelection[level]), level, $(this).data('container').last(), 'After').change());
}
};
//build levels from initial dropdown
mainDropdown.children().each(function() {
var options = $(this).html().split(settings.delimiter);
if ($(this).is(":selected")){
defaultSelection = options;
}
var level = levels;
for (var i=0; i < options.length; i++) {
if (!level[options[i]]){
//either an option is an object pointing to other objects/values,
//or some other type value, indicating that the user has made a selection
level[options[i]] = ((i+1)==options.length) ? $(this).val() : {};
}
level = level[options[i]];
}
});
//if no default selection, use first value
if (!defaultSelection){
defaultSelection = mainDropdown.children().first().html().split(settings.delimiter);
}
insertSelectDropdown(buildSelectDropdown(levels,defaultSelection[0]), 0, mainDropdown, 'Before').change();
//hide initial dropdown
}).hide();
};
})(jQuery);
答案 0 :(得分:1)
问题是由于dynamicDropdown
代码使用这种语法迭代数组而引起的:
for (prop in array)
包括数组的所有可迭代属性,而不仅仅是数组元素。在您的情况下,这将包括您添加到数组的associate
方法,因为它是可迭代的,这将导致问题。
这是一个典型的例子,说明为什么不应该使用for (prop in array)
语法迭代数组,而是使用
for (var i = 0; i < array.length; i++)
或者
array.forEach()
它也是您在完成后直接向Array.prototype
添加内容时所冒的风险的典型示例。
您可以通过不扩展Array
对象的原型(您已经发现的选项)来解决这个问题。或者,您可以使用Object.defineProperty()
使新方法不可迭代,而不是直接分配给原型(只要您只使用IE9 +支持就可以了),所以{{1中的错误代码库没有在数组迭代中看到你的新方法。
您可以像这样使用dynamicDropdown
使该方法不可迭代:
Object.defineProperty()