Pull to refresh

Кунг-фу: стиль JavaScript

Reading time 5 min
Views 2.1K
Эта статья начиналась как комментарий к другой статье на habrahabr. После написания первого листа, я понял, комментарий слишком обширный получился :). Я решил написать, потому что хочу заострить внимание на моментах, которые, на мой взгляд, были упущены. Ограничение этой статьи — моя цель изложить всё максимально доступно, не ищите здесь математической точности в определении терминов, и всё же я прилагаю ссылки где математики найдут высококлассные понятные только им определения :)

Наверно каждую статью по JS принято начинать со слов о его недооцененности :) Это правда :) Когда я пару лет назад говорил о том что JS мой любимый язык на меня смотрели, как на школьника-переростка, который только что написал свою первую страницу на HTML, а те кто меня знал, как на гроссмейстера, который сказал что он только и знает как фигуры ходят :). Таких людей не стало намного меньше, увы :(

Итак, момент первый — ничего не сказано о том чем Javascript отличается принципиально от Java или С++. Отличие в подходе (парадигме) программирования взятой за основу при создании языка. Я не оцениваю «что лучше?», ни в коме случае, кому что удобнее :) или нравиться :)


Отличие традиционной класс-объектной от прототипной парадигмы


Большинство людей, которые смотрят на JS свысока говорят о нем, как не о ООП языке :). Начнем с того, что это разные вещи и их вообще нельзя сравнивать. Конечно можно программировать на JS пользуясь навыками полученными при программировании на C++ или Java, но это так не эффективно. JS работает иначе, в нём нет классов,  в нём есть объекты (не падайте в обморок, дочитайте статью). Это не означает, что на JS невозможно сделать абстракции, инкапсуляции, наследование или полиморфизма. Главные отличия в реализации инкапсуляции, наследования, и работе конструктора. В JS, как и почти во всех языках прототипного типа, нужно использовать термин инкапсуляции, есть замыкание.

Замыкание


Замечу, что на хабре уже была статья о замыканиях. Попробую объяснить «на пальцах» и добавлю пример.

Замыканиями в JS называют область видимости внутри которой переменная  обретает смысл :). Это может быть определение объекта или функции. Переменная может быть как примитивной, так и объектом или функцией. Уф… теперь, любимое, пример:
  1. function bicubic(count)
  2. {
  3.   var values = [];
  4.  
  5.   for (var i = 0; i < count; i++)
  6.   {
  7.      values[i] = (
  8.          function(n)
  9.          {
  10.             return function()
  11.             {
  12.               return Math.pow(n, 3);
  13.             }
  14.          }
  15.      )(i);
  16.   }
  17.  
  18.   return values;
  19. }
  20.  
  21. var vals = bicubic(10);
  22.  
  23. alert( vals[3]() );
* This source code was highlighted with Source Code Highlighter.
Если бы мне показали эти строки и попросили бы сказать «что это за хрень такая» я бы не одну минуту потратил, чтоб разобраться :) Именно это и придает столько силы сторонником JS в утверждении, что языки основанные на классах приводят при написании кода к излишней сфокусированности программистов на иерархичности и связях классов друг с другом, что по моему правда.

Давайте по порядку :)
  1. Создается функция bicubic которая выдает массив;
  2. Фишка в том, что является членами массива — это функции без параметра: Math.pow(n, 3). В данном случае n, благодаря чуду замыкания становиться значением i в момент прохода цикла, а не после него!
  3. Волшебство в скобках (...)(i) — это вызов функции с параметром i, а в скобках определение этой функции.

т.к после прохода циклом i = count, то без этих скобок, мы получим совсем другой ответ — постоянный 10'000, а с ними 27.

Кстати, этот приём называется разрывом замыкания.

Именно поэтому в jQuery рекомендуют писать плагины к этому фреймверку внутри конструкции (… ваш плагин...)();, это позволяет:
  1. Писать безопасный код
  2. Не перегружать браузер глобальными переменными

Экземпляр, конструктор, объект и прототип


Вот я недавно написал, что в JS нет классов, а есть объекты. Я имел в виду следующее:

В класс-ориентированных языках новый экземпляр создается через вызов конструктора класса (возможно, с набором параметров). Получившийся экземпляр имеет структуру и поведение, жёстко заданные его классом. Вот строчки из Википедии:
В прототип-ориентированных системах (например JS) предоставляется два метода создания нового объекта: клонирование существующего объекта, либо создание объекта «с нуля». Для создания объекта с нуля программисту предоставляются синтаксические средства добавления свойств и методов в объект. В дальнейшем, с получившегося объекта может быть получена полная копия, клон. В процессе клонирования копия наследует все характеристики своего прототипа, но с этого момента она становится самостоятельной и может быть изменена. В некоторых реализациях копии хранят ссылки на объекты-прототипы, делегируя им часть своей функциональности; при этом изменение прототипа может затронуть все его копии. В других реализациях новые объекты полностью независимы от своих прототипов.
Демонстрация:
  1. function win() {
  2.   this.open = function()
  3.   {
  4.      return "open 1";
  5.   }
  6. }
  7.  
  8. win.open = function() {
  9.   return "open 2";
  10. }
  11.  
  12. alert( win.open() ); // 1. open 2
  13.  
  14. win.prototype = {
  15.   save: function()
  16.   {
  17.      return "open 3";
  18.   }
  19. }
  20.  
  21. alert( win.save() ); // 2. error - нет save
  22.  
  23. win.prototype.open = function()
  24. {
  25.   return "open 4";
  26. }
  27.  
  28. alert( win.open() ); // 3. open 2
  29.  
  30. var vista = new win();
  31.  
  32. alert( vista.open() ); // 4. open 1
  33. alert( vista.save() ); // 5. open 3
  34.  
  35. vista.open = (
  36.   function()
  37.   {
  38.      return "open 5";
  39.   }
  40. )();
  41.  
  42. alert( vista.open ); // 6. open 5
  43.  
  44. alert( win.open() ); // 7. open 2
* This source code was highlighted with Source Code Highlighter.

«open 4» мы не получим, т.к. метод open у нас определен в самом объекте, что не касается save.

Внимательный читатель меня спросит — а где же в этой дьявольской смеси конструктор? Да, здесь его не было. Конструктором является сам объект, запускается при создании клона, с помощью оператора new или инициализации самого объекта. Демонстрация:
  1. function win()
  2. {
  3.   var str = "open 1";
  4.  
  5.   this.open = function()
  6.   {
  7.      return str;
  8.   }
  9. }
  10.  
  11. alert( (new win()).open() ); // open 1
* This source code was highlighted with Source Code Highlighter.
Создание объекта «с нуля» в JS, «это вещь»! Приведу пример, который помогает экономить мускульные усилия пальцев:
  1. function plugin(options)
  2. {
  3.   options = options || {};
  4.  
  5.   options.list = options.list || [];
  6.  
  7.   // далее можно смело работать
  8.   // options, проверять его свойства, например,
  9.   // а с list как с массивом
  10. }
* This source code was highlighted with Source Code Highlighter.


Прототип, «наследование» в javascript



очень сложно найти в тёмной комнате чёрную кошку, особенно если там её нет :)
© народная мудрость

В JS нет наследования и тем более множественного. У каждого объекта есть прототип, объект который можно сделать общим для нескольких объектов или их экземпляров, а потом менять :)


Итог


Ребятам молящимся на парадигму ООП, проснитесь, есть много других парадигм программирования. Нет лучшей, есть наиболее подходящие для определенных задач. JS один из языков на долго занявших свою нишу, в том числе потому что под ним очень мощный фундамет прототипной прарадигмы программирования.

Остальные, надеюсь узнали что-нибудь полезное :)

С уважением, Артур Дудник
самая ценная человеческая черта — стремление к самосовершенствованию ©

Tags:
Hubs:
+88
Comments 59
Comments Comments 59

Articles