Array.prototype和链选择框插件之间的冲突 [英] Conflict between Array.prototype and a chain select box plugin

查看:79
本文介绍了Array.prototype和链选择框插件之间的冲突的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

链式选择框小提琴

链式选择框和Array.prototype函数提琴

我有一个链式选择框和一个Array.prototype函数将两个数组组合成一个关联的数组,它们用途不同且无关.但是,将它们放到脚本中时,它会给我undefined is not a function指出这部分this.forEach是错误的来源.我最终用这个函数替换了Array.prototype函数(示例小提琴):

I had a chained select box and an Array.prototype function to combine two arrays into an associated array.They are for different use and unrelated. But when they are put together in the script, it gives me undefined is not a function pointing to this part this.forEach as the source of error. I ended up replacing the Array.prototype function with this one (Example fiddle):

function associate(keys, values){
    return keys.reduce(function (previous, key, index) {
        previous[key] = values[index];
        return previous
    }, {})
} 

我很好奇为什么链式选择框和Array.prototype函数之间存在冲突?

I'm just curious why there is a conflict between the chained select box and the Array.prototype function?

代码如下:

$(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);

Github版本:

(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);

推荐答案

之所以引起此问题,是因为dynamicDropdown代码使用以下这种语法在数组上进行迭代:

The issue is caused because the dynamicDropdown code iterates over an array with this type of syntax:

for (prop in array)

包括数组的所有可迭代属性,而不仅仅是数组元素.在您的情况下,这将包括您添加到数组的associate方法,因为它是可迭代的,并且会引起问题.

which includes all iterable properties of the array, not just array elements. In your case, that will include the associate method that you added to the array because it is iterable and that will cause a problem.

有关迭代数组的参考,请参见正确和错误的迭代方法数组.

For a reference on iterating arrays, see Right and Wrong Ways to Iterate an Array.

这是一个经典示例,说明了为什么绝不应该使用for (prop in array)语法来迭代数组,而应该使用

This is a classic example for why arrays should never be iterated with the for (prop in array) syntax, but rather this

for (var i = 0; i < array.length; i++)

或者这个:

array.forEach()

或更现代的:

for (item of array)

,它也是将事物直接添加到Array.prototype时所承担的风险的经典示例.

and it's also a classic example of the risk you take when you add things directly to the Array.prototype as you've done.

您可以通过完全不扩展Array对象的原型(已发现的选项)来解决该问题.或者,您可以通过使用Object.defineProperty()来使新方法不可迭代,而不是直接分配给原型(只要您只支持IE9 +就可以了),这样dynamicDropdown库中的错误代码就不会在其数组迭代中查看您的新方法.

You can work-around it as you have done by not extending the prototype of the Array object at all (the option you've already discovered). Or, you could make your new method be non-iterable by using Object.defineProperty() rather than direct assignment to the prototype (as long as you're OK with only IE9+ support) so the bad code in the dynamicDropdown library won't see your new method in its array iteration.

您可以像这样使用Object.defineProperty()使该方法不可迭代:

You could use Object.defineProperty() like this to make the method be non-iterable:

Object.defineProperty(Array.prototype, "associate", {
    writable: false,
    configurable: false,
    enumerable: false,
    value: function(keys) {
        var result = {};

        this.forEach(function (el, i) {
          result[keys[i]] = el;
        });

        return result;
    }
});

这篇关于Array.prototype和链选择框插件之间的冲突的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆