Возвращайте до 18% с пополнений рекламы
  • Все популярные рекламные сети в одном окне
  • Рекламные инструменты — бесплатно
  • Доступ к конструктору лендингов и WebApp-приложений
  • Закрывающие документы точно в срок
ring svg
  1. Главная >
  2. Блог >
  3. Как усовершенствовать код при помощи JavaScript

Как усовершенствовать код при помощи JavaScript

java

В этой статье пойдет речь об общих и не столь общих методах и шаблонах JavaScript, которые помогут улучшить эстетический вид кода, а также его размер. Чтобы разобраться в этом уроке, у вас должно быть хорошее понимание функционального программирования в JS и обычных выражениях.

JavaScript методы и шаблоны

Предположим, что вы уже знакомы с this и that, call, apply, bind и прототипами…

Длинные условия

Этот первый совет может показаться не совсем очевидным, но он поможет вам сократить длинные if заявления. Идея состоит в том, чтобы заменить логические операторы и сравнения на простой regex(регулярное выражение) тест.

Если вы делаете это…

if (val == 'a' || val == 'b' || val == 'c')

…то с regex оно будет выглядеть проще:
 

if (/a|b|c/.test(val))

Еще, возможно, вам придется иметь дело с несколькими переменными и AND логическим оператором, например:
 

if (a == 1 && b == 2 && c == 3 && d == 4 && e == 5)

Если у вас есть очень длинный список переменных, где вам нужно сравнить их значения, тогда вы можете создать таблицу с ожидаемыми значениями, а другую таблицу с заданными переменными и использовать every метод для сравнивания. every возвращает true, только если все условия были выполнены.

var vars = [a,b,c,d,e],
    vals = [1,2,3,4,5];

if (vals.every(function(val,i){ return vars[i] == val }))

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

function isTrue(vars, vals) {
  return vals.every(function(val,i){ return vars[i] == val });
}

if (isTrue([a,b,c,d,e], [1,2,3,4,5]))

В случае логического оператора OR нужно использовать some метод, который возвращает true, если хотя бы одно условие было выполнено. Но это мало вероятно, найти такие длинные OR модели; как правило они смешиваются с AND’s:
 

if (a == 1 && b == 2 || c == 3 && d == 4 || e == 5 && f == 6)

Манипуляция текстом

JavaScript инструменты для строк весьма ограничены по сравнению с другими языками. Рассмотрим случай, когда у нас есть несколько пунктов, разделенных точками, и мы хотим, чтобы первая буква каждого из них была заглавной. Использование циклов (loops) и методов работы со строками, мы сделаем что-то вроде этого:

var str = 'Небо голубое. Яблоки зеленые.';

var para = str.split('.'), // получите массив параграфов
    i = 0, len = para.length, p;

for (; i < len; i++) {
  p = para[i].trim(); // убедитесь, что нет никаких дополнительных пробелов
  para[i] = p.substr(0,1).toUpperCase() + p.substr(1, p.length);
}

para = para.join('. ').trim(); // Сложите все обратно

console.log(para);
//^ "Небо голубое. Яблоки зеленые."

Несомненно, этот способ работает отлично, но очевидным умным подходом здесь будет использование обычных выражений, и все станет невероятно красивым и лаконичным:

str = str.replace(/([^.]+\.\s?)/g, function(_, para) {
  return para.replace(/^\w/, function(first) {
    return first.toUpperCase();
  });
});

console.log(str);
// ^ "Небо голубое. Яблоки зеленые."

Рассмотрим еще один обычный случай, где вам нужно извлечь все телефонные номера из текста. Давайте предположим, что в нашей строке все телефонные номера имеют префикс tel:, таким образом мы cможем захватить только телефонные номера, а не какие либо другие цифровые значения, которые могут содержаться в строке. Ясное дело, что обычное выражение станет лучшим инструментом для решения этой задачи, и так как в тексте может быть несколько телефонных номеров, мы будем использовать глобальный regex (обычное выражение). Мы могли бы попробовать использовать match:
 

var str = 'Павел, 26 лет, tel: 555-000-0000. Юлия, 19 лет, tel:777-000-0000.';
var numbers = str.match(/tel:\s?([\d-]+)/g);
console.log(numbers); //=> ["tel: 555-000-0000", "tel:777-000-0000"]

Но, как вы видите, output все еще содержит tel:, при всем том, что мы используем группы захвата, чтобы только получить номера. Проблема в том, что match не захватывает группы в глобальном regex, по этой причине вам нужно использовать exec и while петли шаблона, который вы видели раньше:

var re = /tel:\s?([\d-]+)/g,
    numbers = [], match;

while (match = re.exec(str)) {
  numbers.push(match[1]);
}

console.log(numbers); //=> ["555-000-0000", "777-000-0000"]

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

var numbers = [];
str.replace(/tel:\s?([\d\-]+)/g, function(_, number) {
  numbers.push(number);
});

console.log(numbers); //=> ["555-000-0000", "777-000-0000"]

Мы даже могли бы сделать этот код доступным для всех строк, как новый метод gmatch:
 

String.prototype.gmatch = function(regex) {
  var result = [];
  this.replace(regex, function() {
    // извлеките матчи(matches), удалив аргументы, которые нам не нужны
    var matches = [].slice.call(arguments, 1).slice(0,-2);
    result.push.apply(result, matches);
  });
  return result;
};

console.log(str.gmatch(/tel:\s?([\d\-]+)/g)) //=> ["555-000-0000", "777-000-0000"]

Генерация HTML разметки

Очень распространенным случаем при работе со строками HTML бывает создание списков таких как li, td, option и другие. Обычно вы бы сделали это:

var values = ['one', 'two', 'three', 'four', 'five'];

var html = '';
for (var i = 0; i < values.length; i++) {
  html += '<td>'+ values[i] +'</td>';
}

Этот код кажется достаточно эффективным, но for петли довольно уродливы. Мы можем переделать этот код с помощью join метода:

var html = '<td>'+ values.join('</td><td>') +'</td>';

Это выглядит получше, но что если нам нужен индекс? В этом случае, map метод может быть использован в сочетании с join:

var html = values.map(function(text, i) {
  return '<td>'+ text +': '+ i +'</td>'
}).join('');

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

function template(arr, html) {
  return arr.map(function(obj) {
    return html.join('').replace(
      /#\{(\w+)\}/g,
      function(_, match) { return obj[match]; }
    );
  }).join('');
}

Это может быть использовано следующим образом:

var people = [
  { name: 'John', age: 25, status: 'Single' },
  { name: 'Bill', age: 23, status: 'Married' },
  { name: 'Mika', age: 17, status: 'Single' }
];

var html = template(people, [
  '<div>',
    '<h1>Name: #{name}</h1>',
    '<h2>Age: #{age}, Status: #{status}</h2>',
  '</div>'
]);

console.log(html);
// ^
// "<div><h1>Name: John</h1><h2>Age: 25, Status: Single</h2></div>\
// <div><h1>Name: Bill</h1><h2>Age: 23, Status: Married</h2></div>\
// <div><h1>Name: Mika</h1><h2>Age: 17, Status: Single</h2></div>"

Полная функциональность

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

Все последние браузеров, включая IE9, имеют поддержку ECMAScript5 массива и методов объекта. Всякий раз, когда есть for или for..in циклы(loops), также есть альтернативное решение с одним из этих методов.

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

var arr = ['a','b','c'];
for (var i = 0; i < arr.length; i++) {
  setTimeout(function() { console.log(arr[i] +':'+ i); }, i * 1000);
}

Целью здесь является вход a:0..b:1..c:2 с промежутком в одну секунду, но результат заканчивается этим undefined:3..undefined:3..undefined:3. (неопределенная). Типичная проблема решается путем создания новой области:

for (var i = 1; i <= 3; i++) {
  setTimeout((function(i) { console.log(arr[i] +':'+ i); }(i)), i * 1000);
}

Теперь результат выглядет как ожидалось, но немного запутанный. Так как forEach цикл создает новую область и вещи становятся проще:

arr.forEach(function(v, i) {
  setTimeout(function() { console.log(v +':'+ i); }, i * 1000);
});

При работе с большими объемами данных, использование функционального подхода является отличным способом для улучшения читабельности. Возьмите данные вроде следующих:

var users = [
  { 
    Name: 'Саша', 
    Age: 25, 
    Job: 'Разработчик', 
    Subscription: 'Золотая'
  },
  {
    Name: 'Владимир', 
    Age: 28, 
    Job: 'Повар', 
    Subscription: 'Платиновая'
  },
  {
    Name: 'Зоя', 
    Age: 32, 
    Job: 'Визажист', 
    Subscription: 'Серебряная'
  },
  {
    Name: 'Елена', 
    Age: 35, 
    Job: 'Повар', 
    Subscription: 'Серебряная'
  }
];

С array методами можно фильтровать пользователей по любым критериям:
 

var over30 = users.filter(function(user) {
  return user.age >= 30;
});

var cook = users.filter(function(user) {
  return user.job == 'Повар';
});

Этот код работает и выглядит достаточно хорошо, но похоже, что нам придется повторять эту модель снова и снова, чтобы извлечь информацию из наших объектов. Для того, чтобы сделать наш код умнее, на этот раз мы будем использовать прототипы и функциональные шаблоны, которые мы рассматривали до этого. Мы создадим очень маленькие DSL для обработки наших данных.

Мы хотим иметь возможность сделать следующие:

var over30 = users.where('age').is('>=30');
var cooks = users.where('job').is('Повар');

Прежде всего, давайте посмотрим на код, а затем решим, что возможно сделать с этой маленькой библиотекой. Вам может быть сложно следовать за кодом, если у вас нет глубокого понимания функционального программирования в JavaScript, но мы надеемся, что наши комментарии сделают его немного понятней.

// An Immediately Invoked Function Expression (IIFE)
// это помешать утечки переменных в глобальную область
(function(win) {

  // Главный конструктор для MyStorage объекта
  function MyStorage(data) {
    this.data = data; // матрица содержащая наши данные
    this.length = this.data.length; // просто ярлык
  }

  // Ярлык, чтобы создать новые экземпляры MyStorage
  function stored(data) {
    return new MyStorage(data);
  }

  // простой наполнитель объекта
  function _extend(obj, target) {
    for (var o in obj) target[o] = obj[o];
    return target;
  }

  MyStorage.prototype = {

    // Приватно:

    // Обновите данные и верните новый экземпляр.
    // Нам нужно клонировать объекты, так как в противном случае
    // они будут переданы как рефералы
    _new: function(result) {
      result = result.map(function(o) { return _extend(o, {}); });
      this.length = this.data.length;
      return stored(result);
    },

    // Профильтруйте текущие свойства данных с функцией.
    // Свойство передается в качестве параметра в 'fn' callback
    _filter: function(fn) {
      return this._new(this.data.filter(function(user, i) {
        return fn.call(this, user[this.prop], i);
      }.bind(this)));
    },

    // Публично:

    // Получите массив с определенной опорой (prop) из каждого объекта
    // или вернуть сбор данных в противном случае
    get: function(prop) {
      if (prop) return this.map(function() { return this[prop]; });
      return this.data;
    },

    // Set the property to filter or compare to
    where: function(prop) { 
      return this.prop = prop, this; 
    },

    //Ярлык для семантики
    and: function(prop) { return this.where(prop); },

    // Сравнить текущее свойство с данным условием
    is: function(condition) {
      // Filter by regular expression or string/number
      var regex = condition instanceof RegExp ? condition :
        new RegExp('^'+ condition +'$');

      // Извлекайте символ в таких случаях как '>50' и '<=10'.
      // 'null' и '0' данны как значения по умолчанию
      // строка не содержит символ
      var symbol = (/^([<>=%]+)([\d.]+)/.exec(condition) || [0,null,0]);

      // Запустите символ, если он присутствует
      if (symbol[1]) return this[symbol[1]](+symbol[2]);

      return this._filter(function(prop) { 
        return regex.test(prop); 
      });
    },

    '>': function(v) {
      return this._filter(function(p) { return p > v; });
    },

    '>=': function(v) {
      return this._filter(function(p) { return p >= v; });
    },

    '<': function(v) {
      return this._filter(function(p) { return p < v; });
    },

    '<=': function(v) {
      return this._filter(function(p) { return p <= v; });
    },

    '%': function(v) {
      return this._filter(function(p) { return p % v === 0; });
    },

    // Сортируйте коллекция по данной функции
    // и верните новый экземпляр Templee
    sort: function(fn) {
      return this._new(this.data.sort(function() {
        return fn.apply(this, [].slice.call(arguments));
      }.bind(this)));
    },

    // Фильтруйте коллекция по индексу
    eq: function(index) {
      return this._new(this.data[index]);
    }

  };

  'forEach map slice every some reduce'.split(' ').forEach(function(method) {
    MyStorage.prototype[method] = function(fn) {
      return this.get()[method](function() { 
        return fn.apply(arguments[0], [].slice.call(arguments)); 
      }.bind(this));
    };
  });

  win.stored = stored; // покажите конструктор пользователям 
}(window));

Использование Storage подобно тому, как вы бы использовали jQuery, очень интуитивно:

stored(users)
  .where('Age').is('>30')
  .and('Job').is('Повар').forEach(function(user) {
    console.log(user.name); //Елена
  });

Представьте себе возможность сочетания этого с системой HTML шаблонов, которую мы создали ранее:
 

var movies = [
  { title: 'Spiderman', score: 7, gross: 90e6 },
  { title: 'Aliens vs Predators', score: 5 , gross: 50e6 },
  { title: 'American Beauty', score: 9.5 , gross: 140e6 },
  { title: '500 Days of Summer', score: 8.5 , gross: 75e6 },
  { title: 'Drive', score: 7.5 , gross: 120e6 },
  { title: '127 Hours', score: 9 , gross: 78e6 }
];

var myMovies = stored(movies);

var featured = template(myMovies.where('score').is('>=8').get(), [
  '<div class="featured-movie">',
    '<h2>#{title}</h2>',
    '<h3>Score: #{score}, Gross: #{gross}</h3>',
  '</div>'
]);

var bigGross = template(myMovies.where('gross').is('>80000000').get(), [
  '<ul class="big-gross-movies">',
    '<li>#{title} <span class="gross">#{gross}</span></li>',
  '</ul>'
]);

$('body').append([
  '<h1>Featured Movies:</h1>'+ featured,
  '<h1>Big Gross Movies:</h1>'+ bigGross,
].join(''));

Другие более сложные примеры также возможны:
 

// Собрать все фильмы, которые начинаются с цифры
myMovies.where('title').is(/^\d/).get('title'); //=> ["127 Hours", 
"500 Days of Summer"]

// Превязывание
myMovies.where('gross').is('>100000000')
  .and('score').is('>9').get('title') //=> ["American Beauty"]

И конечно же, вы можете присоединить все те полезные array методы: forEach, map, slice, every, some, reduce

myMovies.forEach(function() {
  setTimeout(function() { console.log(this.title) }.bind(this), 1000);
});

С помощью функциональных методов мы можем получить очень чистый, абстрагируемый, модульный код. Такие библиотеки как jQuery и Zepto, используют некоторые из этих моделей, чтобы сделать свой код лучше.

Надеемся, вы подпитались свежими идеями, чтобы попробовать сделать код более разумным и эффективным.

Высоких конверсий!

blog comments powered by Disqus
Возвращайте до 18% с пополнений рекламы
  • Все популярные рекламные сети в одном окне
  • Рекламные инструменты — бесплатно
  • Доступ к конструктору лендингов и WebApp-приложений
  • Закрывающие документы точно в срок
ring svg
copyright © 2011–2025 Все права защищены
Запрещено любое копирование материалов ресурса без письменного согласия владельца — ООО "Центр рекламных бюджетов". ИНН:5902052888, КПП:590201001, ОГРН: 1195958009730, Пермь, ул. Окулова, д. 75 к. 8 офис 501Б

ООО «Центр рекламных бюджетов» — IT-компания с многолетним опытом работы, разрабатывающая инновационные решения для управления процессом лидогенерации (пост-клик маркетинг). Разработанное нами технологическое программное решение LPGENERATOR позволяет создавать целевые страницы в визуальном редакторе и управлять заявками (лидами) в CRM-системе в целях проведения эффективных, высококонверсионных рекламных кампаний