Страницы

суббота, 14 февраля 2015 г.

JavaScript runtime code evaluation

Не помню чтобы мне приходилось применять способы интерпретации кода во время выполнения на практике.  Но вчера, в пятницу, в самом конце рабочего дня (только так я могу объяснить причину), как говорится Остапа понесло :). Дальше расскажу как я до этого "докатился", пошагово, с картинками. Просьба не принимать всерьез приведенные листинги, хотя наверняка существуют задачи для решения которых подобные приемы можно считать вполне актуальными. Вот так замысловато я пытаюсь сообщить о том, что в моем случае это был перебор.

А началось все с того, что я написал код, где в цикле создавались анонимные функции. Что-то вроде этого:
(function(arr) {
  arr.forEach(function(item) {
    Object.keys(item).forEach(function(key) {
      if (item[key] === null) delete item[key];
    });
  });
  console.log(arr);
})([{a: 1, b: null}, {a: 2, b: null}]);

Результат выполнения в консоли браузера выглядит примерно так:

На первый взгляд никакого криминала, но то, что в первом цикле каждый раз содается анонимная функция для использовани ее во вложенном цикле мне не понравилось, поэтому я решил сделать проще:
(function(arr) {
  arr.forEach(function(item) {
    for (var key in item) {
      if (item[key] === null) delete item[key];
    }
  });
  console.log(arr)
})([{a: 1, b: null}, {a: 2, b: null}]);

На этом мои претензии к качеству закончились, но по ходу появилось новое условие в виде дополнительного аргумента:
(function(arr, arg) {
  arr.forEach(function(item) {
    for (var key in item) {
      if (item[key] === null) delete item[key];
      if (typeof arg === 'number') item.c = arg;
    }
  });
  console.log(arr)
})([{a: 1, b: null}, {a: 2, b: null}], 0);

И здесь мне не понравилось то, что во вложенном цикле я каждый раз проверяю тип параметра arg, зная о его наличии еще до начала первого цикла. В результате появилось подобное чудовище:
(function(arr, arg) {
  arr.forEach(typeof arg === 'number' ? function(item) {
    for (var key in item) {
      if (item[key] === null) delete item[key];
      item.c = arg;
    }
  } : function(item) {
    for (var key in item) {
      if (item[key] === null) delete item[key];
    }
  });
  console.log(arr)
})([{a: 1, b: null}, {a: 2, b: null}], 0);

Работает оно как надо:

Но есть и более "изящный" вариант с использованием глобального метода eval():
(function(arr, arg) {
  arr.forEach(eval(
    '(function(item) {' +
      'for (var key in item) {' +
        'if (item[key] === null) delete item[key];' +
        (typeof arg === 'number' ? 'item.c = arg;' : '') +
      '}' +
    '})'
  ));
  console.log(arr)
})([{a: 1, b: null}, {a: 2, b: null}], 0);

Вот такая история. Тем кого заинтересовала эта тема предлагаю подумать почему вариант с использованием конструктора Function() не прокатит:
(function(arr, arg) {
  arr.forEach(new Function('item',
    'for (var key in item) {' +
      'if (item[key] === null) delete item[key];' +
       (typeof arg === 'number' ? 'item.c = arg;' : '') +
    '}'
  ));
  console.log(arr)
})([{a: 1, b: null}, {a: 2, b: null}], 0);

Подсказка - все дело в замыканиях.

На этом прощаюсь. Всем мир.