Статьи

JavaScript: ОО-программирование, использующее шаблон прототипа

В настоящее время я играю с SPA (одностраничными приложениями), поэтому для написания большого количества JavaScript. Один из принципов в ОО-программировании — не повторяйте себя. Вот почему я искал способы сделать это. В таких языках, как C #, Java, VB, … мы можем использовать классы для этого, но в JavaScript у нас нет ключевого слова class для определения классов. (За исключением случаев, когда вы используете Typescript , это языковая сборка поверх JavaScript, которая позволяет вам определять классы, как вы делаете в C # или Java. Вы должны действительно проверить это.)

Затворы

В JavaScript у вас есть только функции, но все эти функции имеют замыкания . Закрытие — это особый вид объекта, который объединяет две вещи: функцию и среду, в которой эта функция была создана. Среда состоит из любых локальных переменных, которые находились в области действия во время создания замыкания. В приведенном ниже примере переменная «obj» — это замыкание, которое имеет «Hello world!» и функция innerFunction в области видимости, когда замыкание было создано.

 function closure(){

     var hello = "Hello";

     function innerFunction(suffix){

         alert(hello + " " + suffix);

     }

     return innerFunction;

 }

  

 var obj = closure(); // Creates a new closure

 obj("world!"); // This will show the message box with "Hello world!"

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

 function a(){

     var c = "1"

     function b(c){

         alert(c);

     }

     return b;

 }

  

 var x = a();

 x("5"); // The message box will show 5.

Объекты

Чтобы создать новый объект в JavaScript, нам нужно сделать 2 вещи. Сначала сделайте определение класса, используя функцию. В приведенном ниже примере мы определяем класс в функции «Класс». Вы видите, что у нас могут быть личные поля и частные методы внутри. Поскольку ни один из этих 2 не возвращается в конце, они будут существовать только внутри замыкания и будут доступны только внутри него. В конце определения класса мы возвращаем литерал объекта . Этот литерал объекта будет содержать все открытые функции и поля объекта.

 function Class() {

     var privateField = "private"

     function privateFunction() {

         return privateField;

     }

  

     this.publicField = "public";

     this.publicFunction = function () {

         return privateFunction();

     };

 }

  

 var instance = new Class();

 alert(instance.publicField); // shows "public"

 alert(instance.publicFunction()); // shows "private"

Образец прототипа

В главе «Объекты» этого поста я показал вам 2 способа определения класса. Недостаток приведенного выше кода заключается в том, что все, что находится внутри определения класса, создается при создании нового экземпляра класса. При создании большого количества экземпляров вы можете себе представить, что через некоторое время у вас будут проблемы с памятью. Именно тогда прототип модели может прийти на помощь.

Шаблон прототипа — это шаблон творческого проектирования, используемый при разработке программного обеспечения, когда тип создаваемых объектов определяется прототипным экземпляром, который клонируется для создания новых объектов (Source Wikipedia ). Это означает, что вместо создания всего объекта каждый раз будет создаваться только состояние (которое делает экземпляры объекта уникальными), но все другие вещи, такие как методы, будут клонированы. Делая это, вы можете сэкономить много памяти.

В приведенном ниже примере вы можете увидеть образец прототипа в действии. Как уже упоминалось, в объекте сохраняется только состояние объекта, но каждая вещь, которой можно поделиться, будет клонирована при создании нового экземпляра. Это означает, что publicField, который у нас есть, всегда будет иметь значение «public» независимо от того, сколько экземпляров объекта вы создали, и если вы измените его значение, оно будет изменено для всех существующих и новых экземпляров. Это то же самое для publicFunction и тела функции, но в функции вы можете получить доступ к состоянию экземпляра. Таким образом, вы можете написать сигнатуру функции один раз, но получить доступ и изменить состояние экземпляра индивидуально.

 function Class(p) {

     var privateField = p;

     function privateFunction() {

         return privateField;

      }

  

     this._privateFunction = privateFunction;

 }

  

 Class.prototype.publicField = "public";

 Class.prototype.publicFunction = function () {

     return this._privateFunction();

 };

  

 var instance = new Class("private");

 alert(instance.publicField); // shows "public"

 alert(instance.publicFunction()); // shows "private"

В приведенном выше примере вы также можете создать второй экземпляр с «public» в качестве аргумента, и в этом случае publicFunction вернет «public».

Одним из недостатков этого подхода является тот факт, что все поля, к которым вы хотите получить доступ в общих функциях (определенных в прототипе), должны быть общедоступными в экземпляре. Это означает, что они должны быть возвращены или добавлены в область. Чтобы немного решить эту проблему, существует соглашение, что частные поля, которые должны быть доступны в общих частях, имеют префикс «_». Это не сделает их недоступными, но intellisense будет игнорировать их, так что это будет немного сложнее узнать.

Выявление прототипа

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


function Class() { var privateField = "private" function privateFunction() { return privateField; } this._privateFunction = privateFunction } Class.prototype = function () { var privateField = "Hello"; var publicField = "public"; function privateFunction() { return privateField + " " + this._privateFunction(); }; return { publicField: publicField, publicFunction: privateFunction }; }(); var instance = new Class(); alert(instance.publicField); // shows "public" alert(instance.publicFunction()); // shows "Hello private"

Вывод

Используя открывающий образец прототипа, вы получаете несколько преимуществ:

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

Конечно, есть и некоторые размеры вниз:

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

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