One place for hosting & domains

      методов

      Определение методов в Go


      Введение

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

      Определение метода

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

      package main
      
      import "fmt"
      
      type Creature struct {
          Name     string
          Greeting string
      }
      
      func (c Creature) Greet() {
          fmt.Printf("%s says %s", c.Name, c.Greeting)
      }
      
      func main() {
          sammy := Creature{
              Name:     "Sammy",
              Greeting: "Hello!",
          }
          Creature.Greet(sammy)
      }
      

      Если вы запустите этот код, вывод будет выглядеть следующим образом:

      Output

      Sammy says Hello!

      Мы создали структуру с именем Creature с полями типа string для Name и Greeting. Эта структура Creature имеет один определенный метод Greet. В объявлении получателя мы присвоили экземпляр Creature для переменной с, чтобы мы могли обращаться к полям Creature, когда мы будем собирать сообщение приветствия в fmt.Printf.

      В других языках вызовы получателя метода обычно выполняются с помощью ключевого слова (например, this или self). Go рассматривает получателя как обычную переменную, поэтому вы можете использовать любое имя на ваше усмотрение. Сообществом для данного параметра используется стиль, согласно которому имя типа получателя должно начинаться со строчной буквы. В данном примере мы использовали c, поскольку типом получателя является Creature.

      Внутри тела main мы создали экземпляр Creature и указали значения для полей Name и Greeting. Здесь мы вызвали метод Greet, объединив имя типа и имя метода с помощью оператора . и предоставив экземпляр Creature в качестве первого аргумента.

      Go предоставляет другой, более удобный способ вызова методов для экземпляров структуры, как показано в данном примере:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name     string
          Greeting string
      }
      
      func (c Creature) Greet() {
          fmt.Printf("%s says %s", c.Name, c.Greeting)
      }
      
      func main() {
          sammy := Creature{
              Name:     "Sammy",
              Greeting: "Hello!",
          }
          sammy.Greet()
      }
      

      Если вы запустите его, вывод будет таким же, как и в предыдущем примере:

      Output

      Sammy says Hello!

      Этот пример идентичен предыдущему, но в этот раз мы использовали запись через точку для вызова метода Greet с помощью Creature, который хранится в переменной sammy как получатель. Это сокращенная форма записи для вызова функции в первом примере. Стандартная библиотека и сообщество Go предпочитают использовать этот стиль, так что вы редко будете видеть стиль вызова, показанный ранее.

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

      package main
      
      import "fmt"
      
      type Creature struct {
          Name     string
          Greeting string
      }
      
      func (c Creature) Greet() Creature {
          fmt.Printf("%s says %s!n", c.Name, c.Greeting)
          return c
      }
      
      func (c Creature) SayGoodbye(name string) {
          fmt.Println("Farewell", name, "!")
      }
      
      func main() {
          sammy := Creature{
              Name:     "Sammy",
              Greeting: "Hello!",
          }
          sammy.Greet().SayGoodbye("gophers")
      
          Creature.SayGoodbye(Creature.Greet(sammy), "gophers")
      }
      

      Если вы запустите этот код, вывод будет выглядеть следующим образом:

      Output

      Sammy says Hello!! Farewell gophers ! Sammy says Hello!! Farewell gophers !

      Мы изменили предыдущие примеры для введения другого метода под названием SayGoodbye, а также изменили Greet, который будет возвращать Creature, чтобы мы могли использовать дополнительные методы для данного экземпляра. В теле main мы вызываем методы Greet и SayGoodbye для переменной sammy, вначале используя запись с точкой, а потом используя стиль функционального вызова.

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

      Определение методов по типам имеет другое особое значение для языка программирования Go в отличие от определения функций, которые оперируют с определенным значением. Методы — это ключевая концепция, стоящая в основе интерфейсов.

      Интерфейсы

      Когда вы определили метод для любого типа в Go, этот метод добавляется в набор методов для типа. Набор методов — это коллекция функций, связанных с этим типом как методы и используемых компилятором Go для определения того, может ли определенный тип быть связан с переменной с типом интерфейса. Тип интерфейса — это спецификация методов, используемых компилятором для гарантии того, что тип обеспечивает реализацию этих методов. Любой тип, который имеет методы с тем же именем, теми же параметрами и теми же возвращаемыми значениями, что и методы, которые находятся в определении интерфейса, реализует этот интерфейс и может привязываться к переменным с данным типом интерфейса. Ниже приводится определение интерфейса fmt.Stringer из стандартной библиотеки:

      type Stringer interface {
        String() string
      }
      

      Чтобы тип реализовывал интерфейс fmt.Stringer, он должен иметь метод String(), который возвращает строку. Реализация этого интерфейса позволит вашему типу выводить данные так, как вы хотите (иногда это называется “наглядным выводом”), когда вы передаете экземпляры своего типа функциям, определенным в пакете fmt. Следующий пример определяет тип, который реализует этот интерфейс:

      package main
      
      import (
          "fmt"
          "strings"
      )
      
      type Ocean struct {
          Creatures []string
      }
      
      func (o Ocean) String() string {
          return strings.Join(o.Creatures, ", ")
      }
      
      func log(header string, s fmt.Stringer) {
          fmt.Println(header, ":", s)
      }
      
      func main() {
          o := Ocean{
              Creatures: []string{
                  "sea urchin",
                  "lobster",
                  "shark",
              },
          }
          log("ocean contains", o)
      }
      

      При запуске этого примера вы увидите следующие результаты:

      Output

      ocean contains : sea urchin, lobster, shark

      В данном примере определяется новый тип структуры под названием Ocean. Ocean, как сказано, реализует интерфейс fmt.Stringer, поскольку Ocean определяет метод под названием String, который не принимает никаких параметров и возвращает строку. В main мы определили новый экземпляр Ocean и передали его функции log, которая получает строку для вывода, после чего следует что-то, реализующее fmt.Stringer. Компилятор Go позволяет нам передавать o здесь, поскольку Ocean реализует все методы, запрашиваемые fmt.Stringer. Внутри log мы используем fmt.Println и вызываем метод String из Ocean, когда он получает fmt.Stringer в качестве одного из своих параметров.

      Если Ocean не предоставляет метод String(), Go будет генерировать ошибку компиляции, поскольку метод log запрашивает fmt.Stringer в качестве аргумента. Эта ошибка выглядит следующим образом:

      Output

      src/e4/main.go:24:6: cannot use o (type Ocean) as type fmt.Stringer in argument to log: Ocean does not implement fmt.Stringer (missing String method)

      Go также необходимо убедиться, что метод String(), который был предоставлен, полностью соответствует методу, запрашиваемому интерфейсом fmt.Stringer. Если это не так, будет сгенерирована ошибка, которая выглядит следующим образом:

      Output

      src/e4/main.go:26:6: cannot use o (type Ocean) as type fmt.Stringer in argument to log: Ocean does not implement fmt.Stringer (wrong type for String method) have String() want String() string

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

      Получатели по указателю

      Синтаксис для определения методов для получателя по указателю практически полностью идентичен определению методов для получателя по значению. Разница состоит в добавлении префикса к имени типа в объявлении получателя со звездочкой (*). Следующий пример определяет метод для получателя по указателю для типа:

      package main
      
      import "fmt"
      
      type Boat struct {
          Name string
      
          occupants []string
      }
      
      func (b *Boat) AddOccupant(name string) *Boat {
          b.occupants = append(b.occupants, name)
          return b
      }
      
      func (b Boat) Manifest() {
          fmt.Println("The", b.Name, "has the following occupants:")
          for _, n := range b.occupants {
              fmt.Println("t", n)
          }
      }
      
      func main() {
          b := &Boat{
              Name: "S.S. DigitalOcean",
          }
      
          b.AddOccupant("Sammy the Shark")
          b.AddOccupant("Larry the Lobster")
      
          b.Manifest()
      }
      

      Вы увидите следующий вывод при запуске этого примера:

      Output

      The S.S. DigitalOcean has the following occupants: Sammy the Shark Larry the Lobster

      Данный пример определяет тип Boat с полями Name и occupants. Мы хотим использовать код в других пакетах, чтобы добавить пассажиров с помощью метода AddOccupant, поэтому мы сделали поле occupants неэкспортируемым, указав первую букву имени поля строчной. Также мы должны убедиться, что вызов AddOccupant приводит к изменению экземпляра Boat, и поэтому мы определили AddOccupant для получателя по указателю. Указатель выступает в качестве ссылки на конкретный экземпляр типа, а не на копию этого типа. Зная, что AddOccupant будет вызываться с помощью указателя на Boat, вы гарантируете, что любые изменения будут сохранены.

      Внутри main мы определяем новую переменную b, которая будет хранить указатель на Boat (*Boat). Мы вызываем метод AddOccupant дважды для данного экземпляра, чтобы добавить двух пассажиров. Метод Manifest определяется для значения Boat, поскольку в определении получатель указан как (b Boat). В main мы все еще можем вызвать Manifest, поскольку Go может автоматически разыменовывать указатель для получения значения Boat. Здесь b.Manifest() является эквивалентом (*b). Manifest().

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

      Получатели по указателю и интерфейсы

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

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

      package main
      
      import "fmt"
      
      type Submersible interface {
          Dive()
      }
      
      type Shark struct {
          Name string
      
          isUnderwater bool
      }
      
      func (s Shark) String() string {
          if s.isUnderwater {
              return fmt.Sprintf("%s is underwater", s.Name)
          }
          return fmt.Sprintf("%s is on the surface", s.Name)
      }
      
      func (s *Shark) Dive() {
          s.isUnderwater = true
      }
      
      func submerge(s Submersible) {
          s.Dive()
      }
      
      func main() {
          s := &Shark{
              Name: "Sammy",
          }
      
          fmt.Println(s)
      
          submerge(s)
      
          fmt.Println(s)
      }
      

      При запуске этого примера вы увидите следующие результаты:

      Output

      Sammy is on the surface Sammy is underwater

      Данный пример определяет интерфейс под названием Submersible, который требует типы с методом Dive(). Затем мы определили тип Shark с полем Name и методом isUnderwater для отслеживания состояния Shark. Мы определили метод Dive() для получателя по указателю для типа Shark, который изменяет возвращаемое методом isUnderwater значение на true. Также мы определили метод String() получателя по значению, чтобы он мог полностью выводить на экран состояние Shark, используя fmt.Println путем применения интерфейса fmt.Stringer, принимаемого fmt.Println, который мы рассматривали ранее. Также мы использовали функцию submerge, которая получает параметр Submersible.

      Использование интерфейса Submersible вместо *Shark позволяет функции submerge опираться исключительно на поведение, предоставляемое по типу. Это делает функцию submerge более подходящей для повторного использования, поскольку вы не должны будете писать новые функции submerge для Submarine, Whale или любого будущего обитателя моря, которого у нас еще нет. Если они определяют метод Dive(), они могут использоваться с функцией submerge.

      Внутри main мы определили переменную s, которая указывает на Shark, и немедленно вывели s с помощью fmt.Println. В результате вы увидите первую часть вывода, Sammy is on the surface. Мы передали s в функцию submerge, а потом вызвали fmt.Println еще раз с s в качестве аргумента, чтобы увидеть вторую часть вывода на экране, Sammy is underwater.

      Если бы мы изменили s на Shark, а не на *Shark, компилятор Go выдал бы ошибку:

      Output

      cannot use s (type Shark) as type Submersible in argument to submerge: Shark does not implement Submersible (Dive method has pointer receiver)

      Компилятор Go говорит нам, что Shark имеет метод Dive, который был определен для получателя по указателю. Когда вы видите это сообщение в своем собственном коде, для устранения проблемы нужно передать указатель для типа интерфейса, используя оператор & перед переменной, где назначен тип значения.

      Заключение

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

      Если вы хотите узнать больше о языке программирования Go в целом, ознакомьтесь с нашей серией статей о программировании на языке Go.



      Source link

      Использование объектных методов в JavaScript


      Введение

      Объекты в JavaScript представляют собой наборы пар ключ/значение. Значения могут состоять из свойств и методов и содержать все другие типы данных JavaScript, в том числе строки, числа и логические операторы.

      Все объекты JavaScript происходят из родительского конструктора Object. Объект имеет множество полезных встроенных методов, позволяющих получать доступ к отдельным объектам и работать с ними напрямую. В отличие от используемых для экземпляров массивов методов прототипа Array, таких как sort() и reverse(), методы объекта используются напрямую в конструкторе Object, и используют в качестве параметра экземпляр объекта. Такие методы называются статическими.

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

      Предварительные требования

      Для эффективного усвоения этого обучающего модуля вы должны быть знакомы с принципами создания и изменения объектов и работы с объектами. Подробнее об этих принципах можно узнать из статьи «Понимание объектов в JavaScript».

      Дополнительные указания по работе с JavaScript можно найти в серии статей «Программирование на JavaScript».

      Object.create()

      Метод Object.create() используется для создания новых объектов и их привязки к прототипу существующего объекта.

      Мы можем создать экземпляр объекта job и расширить его до конкретного объекта.

      // Initialize an object with properties and methods
      const job = {
          position: 'cashier',
          type: 'hourly',
          isAvailable: true,
          showDetails() {
              const accepting = this.isAvailable ? 'is accepting applications' : "is not currently accepting applications";
      
              console.log(`The ${this.position} position is ${this.type} and ${accepting}.`);
          }
      };
      
      // Use Object.create to pass properties
      const barista = Object.create(job);
      
      barista.position = "barista";
      barista.showDetails();
      

      Output

      The barista position is hourly and is accepting applications.

      Сейчас объект barista имеет только одно свойство position, но все остальные свойства и метода объекта job доступны через прототип. Метод Object.create() полезен для сокращения кода за счет минимального дублирования.

      Object.keys()

      Метод Object.keys() создает массив, содержащий ключи объекта.

      Мы можем создать объект и распечатать массив ключей.

      // Initialize an object
      const employees = {
          boss: 'Michael',
          secretary: 'Pam',
          sales: 'Jim',
          accountant: 'Oscar'
      };
      
      // Get the keys of the object
      const keys = Object.keys(employees);
      
      console.log(keys);
      

      Output

      ["boss", "secretary", "sales", "accountant"]

      Метод Object.keys можно использовать для итерации ключей и значений объекта.

      // Iterate through the keys
      Object.keys(employees).forEach(key => {
          let value = employees[key];
      
           console.log(`${key}: ${value}`);
      });
      

      Output

      boss: Michael secretary: Pam sales: Jim accountant: Oscar

      Также метод Object.keys полезен для проверки длины объекта.

      // Get the length of the keys
      const length = Object.keys(employees).length;
      
      console.log(length);
      

      Output

      4

      С помощью свойства length мы можем подсчитать 4 свойства сотрудников.

      Object.values()

      Метод Object.values() создает массив, содержащий значения объекта.

      // Initialize an object
      const session = {
          id: 1,
          time: `26-July-2018`,
          device: 'mobile',
          browser: 'Chrome'
      };
      
      // Get all values of the object
      const values = Object.values(session);
      
      console.log(values);
      

      Output

      [1, "26-July-2018", "mobile", "Chrome"]

      Object.keys() и Object.values() позволяют получить данные объекта.

      Object.entries()

      Метод Object.entries() создает вложенный массив пар ключ-значение для объекта.

      // Initialize an object
      const operatingSystem = {
          name: 'Ubuntu',
          version: 18.04,
          license: 'Open Source'
      };
      
      // Get the object key/value pairs
      const entries = Object.entries(operatingSystem);
      
      console.log(entries);
      

      Output

      [ ["name", "Ubuntu"] ["version", 18.04] ["license", "Open Source"] ]

      После создания массивов с парами ключ-значение мы можем использовать метод forEach() для обработки результатов и работы с ними.

      // Loop through the results
      entries.forEach(entry => {
          let key = entry[0];
          let value = entry[1];
      
          console.log(`${key}: ${value}`);
      });
      

      Output

      name: Ubuntu version: 18.04 license: Open Source

      Метод Object.entries() возвращает только собственные свойства экземпляра объекта, а не свойства, унаследованные от прототипа.

      Object.assign()

      Метод Object.assign() используется для копирования значений одного объекта в другой объект.

      С помощью метода Object.assign() мы можем создать два объекта и объединить их.

      // Initialize an object
      const name = {
          firstName: 'Philip',
          lastName: 'Fry'
      };
      
      // Initialize another object
      const details = {
          job: 'Delivery Boy',
          employer: 'Planet Express'
      };
      
      // Merge the objects
      const character = Object.assign(name, details);
      
      console.log(character);
      

      Output

      {firstName: "Philip", lastName: "Fry", job: "Delivery Boy", employer: "Planet Express"}

      Также для этой задачи можно использовать оператор spread (...). В приведенном ниже примере кода мы изменим способ декларации объекта character, объединив объекты name и details.

      // Initialize an object
      const name = {
          firstName: 'Philip',
          lastName: 'Fry'
      };
      
      // Initialize another object
      const details = {
          job: 'Delivery Boy',
          employer: 'Planet Express'
      };
      
      // Merge the object with the spread operator
      const character = {...name, ...details}
      
      console.log(character);
      

      Output

      {firstName: "Philip", lastName: "Fry", job: "Delivery Boy", employer: "Planet Express"}

      Такой синтаксис оператора spread в буквальных константах объекта также называется неглубоким клонированием.

      Object.freeze()

      Метод Object.freeze() предотвращает изменение свойств и значений объекта, а также добавление или удаление свойств объекта.

      // Initialize an object
      const user = {
          username: 'AzureDiamond',
          password: 'hunter2'
      };
      
      // Freeze the object
      const newUser = Object.freeze(user);
      
      newUser.password = '*******';
      newUser.active = true;
      
      console.log(newUser);
      

      Output

      {username: "AzureDiamond", password: "hunter2"}

      В приведенном выше примере мы попытались заменить пароль hunter2 на *******, однако свойство password не изменилось. Также мы попытались добавить новое свойство active, но оно не было добавлено.

      Свойство Object.isFrozen() позволяет определить, заморожен ли объект, и возвращает логическое значение.

      Object.seal()

      Метод Object.seal() предотвращает добавление новых свойств к объекту, но допускает изменение существующих свойств. Этот метод похож на метод Object.freeze(). Чтобы избежать ошибок, обновите консоль перед добавлением приведенного ниже кода.

      // Initialize an object
      const user = {
          username: 'AzureDiamond',
          password: 'hunter2'
      };
      
      // Seal the object
      const newUser = Object.seal(user);
      
      newUser.password = '*******';
      newUser.active = true;
      
      console.log(newUser);
      

      Output

      {username: "AzureDiamond", password: "*******"}

      Новое свойство active не было добавлено к запечатанному объекту, но свойство password было успешно изменено.

      Object.getPrototypeOf()

      Метод Object.getPrototypeOf() используется для получения внутреннего скрытого свойства [[Prototype]] объекта, которое также доступно через свойство __proto__.

      В этом примере мы создадим массив, имеющий доступ к прототипу Array.

      const employees = ['Ron', 'April', 'Andy', 'Leslie'];
      
      Object.getPrototypeOf(employees);
      

      Output

      [constructor: ƒ, concat: ƒ, find: ƒ, findIndex: ƒ, pop: ƒ, …]

      В результатах мы видим, что прототип массива employees имеет доступ к методам pop, find и другим методам прототипа Array. Для проверки мы протестируем прототип employees с прототипом Array.prototype.

      Object.getPrototypeOf(employees) === Array.prototype;
      

      Output

      true

      Этот метод может быть полезен для получения дополнительной информации об объекте или обеспечения доступа к прототипу другого объекта.

      Связанный метод Object.setPrototypeOf() добавляет прототип к другому объекту. Вместо него рекомендуется использовать более быстрый и производительный метод Object.create().

      Заключение

      Объекты имеют много полезных методов, которые можно использовать для их изменения, защиты и итерации. В этом обучающем модуле мы научились создавать и назначать новые объекты, итеративно просматривать ключи и/или значения объекта, а также замораживать и запечатывать объекты.

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



      Source link