Три реализации карт в JavaScript. Какой из них лучше? - PullRequest
4 голосов
/ 08 октября 2009

Я написал простую реализацию карты для какой-то задачи. Затем из любопытства я написал еще два. Мне нравится map1, но код довольно сложный для чтения. Если кому-то интересно, я бы оценил простой обзор кода.

Какой из них лучше? Знаете ли вы какой-либо другой способ реализовать это в JavaScript?

var map = function(arr, func) {
  var newarr = [];
  for (var i = 0; i < arr.length; i++) {
    newarr[i] = func(arr[i]);
  }
  return newarr;
};

var map1 = function(arr, func) {
  if (arr.length === 0) return [];
  return [func(arr[0])].concat(funcmap(arr.slice(1), func));
};

var map2 = function(arr, func) {
  var iter = function(result, i) {
    if (i === arr.length) return result;
    result.push(func(arr[i]));
    return iter(result, i+1);
  };
  return iter([], 0);
};

Спасибо!

EDIT

Я думаю о такой функции в целом.

Например, сейчас я собираюсь использовать его для итерации следующим образом:

map(['class1', 'class2', 'class3'], function(cls) { 
    el.removeClass(cls);
});

или

ids = map(elements, extract_id); 
/* elements is a collection of html elements, 
   extract_id is a func that extracts id from innerHTML */

Ответы [ 6 ]

5 голосов
/ 08 октября 2009

Что касается реализации map , изначально используемой в Firefox и SpiderMonkey, я думаю, что это очень просто:

if (!Array.prototype.map) {
  Array.prototype.map = function(fun /*, thisp*/)   {
    var len = this.length >>> 0;  // make sure length is a positive number
    if (typeof fun != "function") // make sure the first argument is a function
      throw new TypeError();

    var res = new Array(len);  // initialize the resulting array
    var thisp = arguments[1];  // an optional 'context' argument
    for (var i = 0; i < len; i++) {
      if (i in this)
        res[i] = fun.call(thisp, this[i], i, this);  // fill the resulting array
    }

    return res;
  };
}

Если вы не хотите расширять Array.prototype, объявите его как нормальное выражение функции.

3 голосов
/ 08 октября 2009

Для справки, карта реализована следующим образом в jQuery

map: function( elems, callback ) {
    var ret = [];

    // Go through the array, translating each of the items to their
    // new value (or values).
    for ( var i = 0, length = elems.length; i < length; i++ ) {
        var value = callback( elems[ i ], i );

        if ( value != null )
            ret[ ret.length ] = value;
    }

    return ret.concat.apply( [], ret );
}

, который кажется наиболее похожим на вашу первую реализацию. Я бы сказал, что первое предпочтительнее, поскольку его проще всего читать и понимать. Но если производительность - ваша забота, профилируйте их.

2 голосов
/ 08 октября 2009

Этот первый самый подходящий. Повторение одного уровня для каждого элемента массива может иметь смысл на функциональном языке, но на процедурном языке без оптимизации хвостового вызова это безумие.

Однако на Array уже есть функция map: она определена в пятом издании ECMA-262 и, как встроенная функция, будет оптимальным выбором. Используйте это:

alert([1,2,3].map(function(n) { return n+3; })); // 4,5,6

Единственная проблема заключается в том, что пятое издание поддерживается не всеми современными браузерами: в частности, расширения Array отсутствуют в IE. Но вы можете исправить это, немного поработав над прототипом Array:

if (!Array.prototype.map) {
    Array.prototype.map= function(fn, that) {
        var result= new Array(this.length);
        for (var i= 0; i<this.length; i++)
            if (i in this)
                result[i]= fn.call(that, this[i], i, this);
        return result;
    };
}

Эта версия, в соответствии со стандартом ECMA, позволяет передавать необязательный объект для привязки к this в вызове функции и пропускает все пропущенные значения (в JavaScript допустимо иметь список длины 3, где второго пункта нет).

2 голосов
/ 08 октября 2009

Я думаю, это зависит от того, что вы хотите сделать от карты, когда func может изменить массив. Я хотел бы ошибиться в сторону простоты и длины выборки один раз.

Вы всегда можете указать размер вывода, как в

var map = function(arr, func) {
  var n = arr.length & 0x7fffffff;  // Make sure n is a non-neg integer
  var newarr = new Array(n);  // Preallocate array size
  var USELESS = {};
  for (var i = 0; i < n; ++i) {
    newarr[i] = func.call(USELESS, arr[i]);
  }
  return newarr;
};

Я использовал форму func.call () вместо просто func (...), потому что мне не нравится вызывать пользовательский код без указания, что это такое, но YMMV.

1 голос
/ 08 октября 2009

Что-то не так во втором методе. 'funcmap' не должен быть изменен на 'map1'?

Если так - этот метод проигрывает, поскольку concat () метод дорогой - создает новый массив из заданных, поэтому должен выделить дополнительную память и выполнить в O (array1.length + array2.length) .

Мне больше нравится ваша первая реализация - она ​​определенно проще для понимания и кажется мне быстрой в исполнении. Никакого дополнительного объявления (как в третьем способе), дополнительных вызовов функций - только один для циклов и присвоений array.length.

0 голосов
/ 08 октября 2009

Я бы сказал, что первый выигрывает в простоте (и немедленной понятности); производительность будет сильно зависеть от того, что оптимизирует данный движок, поэтому вам придется профилировать движки, которые вы хотите поддерживать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...