БлогNot. 8 хороших способов написания функций на JavaScript

8 хороших способов написания функций на JavaScript

На JS удобно не только обрабатывать массивы, но и писать приложения на любую тему, как небольшие, так и довольно сложные.

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

Все мы занятые люди, поэтому только главные приёмы и, по возможности, компактно.

Коды можно выполнить в браузере, просто скопировав их в файлы .html в кодировке Юникода UTF-8.

1. Просто функция, имеет значение аргумента по умолчанию (arg2), проверку типа аргумента (arg1)
<script>
 function foo(arg1,arg2='') { //Если первый аргумент - число, дописывает его ко второму и возвращает результат
  let n = parseInt(arg1);
  if (isNaN(n) || !isFinite(n)) return '';
  return arg2+n;
 }
 let s = foo(1,'str1 ');
 s += foo(2,'str2 ');
 s += foo('3','str3 '); //str1 1str2 2str3 3
 s += foo(1e9999,'str4 '); //нет, так как переполнение
 s += foo('e99','str5 '); //нет, так как не число
 alert(s);
</script>

Или можно было писать функцию в виде:

var foo = function(arg1,arg2='') {
 //...
}

и потом динамически менять имя функции в коде:

<script>
 var foo = function(arg1,arg2='') {
  let n = parseInt(arg1);
  if (isNaN(n) || !isFinite(n)) return '';
  return arg2+n;
 }
 var foo2 = function(arg1,arg2='') { //Просто сцеплят аргументы как строки, в обратном порядке, чем foo
  return String(arg1)+String(arg2);
 }

 let f = foo;
 let s = '';
 s = f(1,'s');
 f = foo2;
 s += f(2,'d');
 alert (s); //s12d
</script>
2. Массив аргументов для функции, как делать перебор аргументов в цикле
<script>
 function sum (...vals) { //Ищет сумму своих аргументов
  if (vals==undefined) return 0;
  let s = 0;
  for (let i in vals) {
   s += (isNaN(parseFloat(vals[i])) ? 0 : parseFloat(vals[i]));
  }
  return s;
 }
 let s = [];
 s[0] = sum(); //0
 s[1] = sum([]); //0
 s[2] = sum([1,2,3]); //1, т.к. значения передаются по одному!
 s[3] = sum(1,2,3); //6
 s[4] = sum(4); //4
 alert (s.join(','));
</script>
3. Доступ к аргументам функции через свойство arguments
<script>
 function cnt () { //Считает количество допустимых чисел в списке своих аргументов
  let n = 0;
  for (let i in arguments) n += isNaN(arguments[i]) ? 0 : 1;
  return n;
 }
 let s = [];
 s[0] = cnt(); //0
 s[1] = cnt('1'); //1
 s[2] = cnt(1,'aaa',2); //2
 s[3] = cnt(1e3,'aaa',-2.055,9999); //3
 alert (s.join(','));
</script>
4. Функция-замыкание - рекомендуется всегда решать любую составную (состоящую из подзадач) задачу замыканием
<div id="result"></div>
<script>
 function calc (id,n) { //Функция-оболочка, может вызывать вложенные функции
  function nf(n) { //Вложенная функция, вычисляет факториал от n
   let v = parseInt(n);
   if (isNaN(n) || n<0) return 0;
   let p = 1;
   for (let i=1; i<=n; i++) p *= i;
   return p;
  }
  document.getElementById(id).innerHTML = nf(n); //120
 } 
 //Весь код инкапсулирован в calc, ничего не мешает "снаружи" и не грозит конфликтом имён
 calc('result',5);
</script>
5. Вызов функции по загрузке страницы и отложенный вызов через таймаут
<div id="result"></div>
<script>
 function calc (id,i,n,s) {
  let elem = document.getElementById (id);
  if (elem == null) { alert ('Нет элемента '+id); return; }
  s += i;
  elem.innerHTML = s;
  if (i<n) setTimeout(calc,300,id,i+1,n,s);//функция, время мс, аргументы функции
 }
 window.addEventListener ('load', function (e) {
  calc('result',0,10,0); 
 });  
</script>
6. Стрелочные функции

Позволяют сократить запись функций из вида

function (arg1, arg2) { /* ... */ return val; }

до

(arg1, arg2) => { /* ... */ return val; }

Особенно это удобно, если тело функции состоит из одного оператора, тогда и операторные скобки тела функции плюс слово return писать не нужно. Сравните вот это вычисление характеристик массива с помощью метода reduce

<div id="res"></div>
<script>
 //Функция reduce - расчёт любой характеристики массива
 function sum(arr) { //Сумма элементов массива
  return arr.reduce(function(prev, curr){ return prev+curr; },0);
 }
 function avg(arr) { //Арифм.среднее элементов массива
  return arr.length ? sum(arr)/arr.length : undefined;
 }
 function prod(arr) { //Произведение элементов массива
  return arr.reduce(function(prev, curr){ return prev*curr; },1);
 }
 function cntMoreThan(arr,val) { //Количество элементов > val
  return arr.reduce(function(prev, curr){ 
   return curr>val ? prev+1 : prev; },0);
 }
 let elem = document.getElementById('res');
 let arr = [];
 elem.innerHTML = "Массив arr="+arr.join(',')+"<br>\n";
 elem.innerHTML += "Среднее="+avg(arr) + "<br>\n"; //undefined
 let arr2 = [1,5,3,2,4];
 elem.innerHTML += "Массив arr2="+arr2.join(',')+"<br>\n";
 elem.innerHTML += "Среднее="+avg(arr2) + "<br>\n"; //3
 elem.innerHTML += "Произведение="+prod(arr2) + "<br>\n"; //120
 elem.innerHTML += "Элементов>3="+cntMoreThan(arr2,3) + "<br>\n"; //2
</script>

вот с такой короткой записью (показаны только тела изменённых функций):

<script>
 //Стрелочные функции позволяют записывать код callback-функций 
 //короче, без ключевых слов "function" и "return"
 function sum(arr) { //Сумма элементов массива
  return arr.reduce( (prev, curr) => prev+curr, 0);
 }
 function prod(arr) { //Произведение элементов массива
  return arr.reduce( (prev, curr) => prev*curr, 1);
 }
 function cntMoreThan(arr,val) { //Количество элементов > val
  return arr.reduce((prev, curr) => (curr>val ? prev+1 : prev),0);
 }
</script>

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

7. Реализация задачи на моделирование объекта в виде класса (приведём полный листинг валидного файла .html)

Обратите внимание, что не нужно инициализировать в конструкторе все свойства, их можно добавлять просто динамически.

Также не нужно писать "function" перед определением методов класса.

Как и в других языках, механизм классов позволяет определять произвольное количество экземпляров класса, а не просто конструировать уникальные объекты (правда, их потом можно клонировать через Object.assign).

Проверено в MS Edge и современных версиях других распространённых браузеров. В IE11 или других браузерах, выпущенных до 2015 года, работать не будет.

<!DOCTYPE html>
<html lang="ru">
<head>
 <meta charset="utf-8">
 <title>Задача (класс с описанием флешки)</title>
</head>
<body>

<script>
 class Flash {
  constructor(volume, name='') { //Конструктор, ставим только нужные свойства
   this.volume = volume; //Объём в Гб
   this.free = volume; //Свободно
   this.name = name; //Название
   this.files = []; //Список файлов
  }
  toString() { //Переопределили встроенный прототип метода для преобразования объекта класса в строку
   return (this.name.length ? this.name : 'noName') + ', ' + this.volume + ' Gb' + 
   ' ('+this.free.toFixed(3) +' free, '+this.files.length+' file(s))';
  }
  error (n) { //Метод для вывода сообщений об ошибках, простейший
   let msg = [
    '',
    'Не передан массив файлов',
    'Не передано имя файла',
    'Недопустимый размер файла',
    'Недопустимая величина измерения размера файла',
    'Нет места на носителе',
    'Файл не найден в списке'
   ];
   this.errors++;
   console.log('Error '+n+': '+msg[n]);
  }
  write(...files) { //Записать файл(ы), каждый файл - массив [имя,размер,единица_измерения_размера]
   this.errors = 0;
   for (let i in files) {
    if (Array.isArray(files[i])==false) { this.error(1); continue; }
    let name = files[i][0].trim(); if (name.length==0) { this.error(2); continue; }
    let size = parseFloat(files[i][1]); if (isNaN(size) || size<0) { this.error(3); continue; }
    let measures = ['b','kb','mb','gb'];
    let measure = files[i][2];
    if (measures.includes(measure)==false) { this.error(4); continue; }
    let n = measures.indexOf(measure);
    for (let j=n; j<measures.length-1; j++) size /= 1024;
    if (this.free - size < 0) { this.error(5); return this.errors; }
    this.free -= size;
    this.files.push([name,size]);
   }
   return this.errors;
  }
  remove(...files) { //Удалить файл(ы), имена которых переданы
   this.errors = 0;
   for (let i in files) {
    let name = files[i].trim(); if (name.length==0) { this.error(2); continue; }
    let n = this.files.findIndex(function(item) { return item[0] == name; });
    if (n == -1) { this.error(6); continue; }
    this.free += this.files[n][1];
    if (this.free > this.volume) this.free = this.volume;
    this.files.splice(n,1); 
   }
   return this.errors;
  }
  list() {
   return this.files.join('; ');
  }
 } //class Flash

 let f1 = new Flash (16,'Silicon Power');
 console.log ('f1= '+f1);
 let errors = f1.write (
  ['file1.txt',1,'kb'],
  [' file2.dat',2,'gb'],
  ['file3.cpp',1024,'kb'],
  ['file4.dat',500,'gb']
 );
 console.log ('Write errors='+errors);
 console.log ('f1= '+f1);
 console.log ('list= '+f1.list());
 errors = f1.remove ( 'file2.dat' );
 console.log ('Remove errors='+errors);
 console.log ('f1= '+f1);
 let f2 = new Flash (4); //Теперь можно создавать любое количество объектов класса
 console.log ('f2= '+f2);
</script>
<noscript>
 <p>Включите JavaScript в браузере для работы приложения!</p>
</noscript> 

<p>Вывод приложения - в консоли...</p>

</body></html>
8. Функция, получающая аргументом другую функцию

Делается просто, и метод eval не нужен.

<div id="table"></div>
<script>
 function calcStr (f,x) {
  return f(x).toFixed(3).toString();
 }
 let f1 = function (x) {
  return Math.sin(x);
 }
 let f2 = function (x) {
  return Math.cos(x);
 }
 let div = document.getElementById('table');
 let func = f1;
 div.innerHTML = calcStr(func,Math.PI/2); //sin(pi/2)
 func = f2;
 div.innerHTML += '<br>'+calcStr(func,Math.PI/2); //cos(pi/2)
</script>

09.03.2020, 17:56 [1685 просмотров]


теги: программирование учебное javascript список

показать комментарии (2)