Содержание
Интерфейсы | JavaScript Camp
Одним из основных принципов TypeScript является то, что типизация основана на структуре объектов. Такой способ типизации называют неявной
или «утиной»
— объект относят к тому или иному типу (классу, интерфейсу), если он имеет (реализует) все его свойства и методы. Интерфейсы в TS применяются как раз для того, чтобы описывать нужные вам типы.
Простой пример
Лучший способ узнать, как работают интерфейсы — начасть с простого примера:
Пример
let giveFruit = (fruit: { name: string }) => console.log('Give to me ' + fruit.name)let myFruits = { name: 'Banana', sweetness: 7, bones: false }
giveFruit(myFruits)
Функция giveFruit()
имеет единственный параметр, который требует, чтобы переданный объект имел свойство с именем name
типа string
. Обратите внимание, что наш объект на самом деле имеет больше свойств, чем требуется, но компилятор только проверяет, присутствуют ли хотя бы те, которые необходимы, и соответствуют требуемым типам.
Напишем тот же пример, для проверки свойства name
с типом string
, но при помощи интерфейсов.
Пример
interface Fruit {
name: string
sweetness: number
bones: boolean
}let giveFruit = (fruit: Fruit) => console.log('Give to me ' + fruit.name)
let myFruits = { name: 'Banana', sweetness: 7, bones: false }
giveFruit(myFruits)
Интерфейс Fruit
— это имя, которое мы теперь можем использовать для описания требования в предыдущем примере. Обратите внимание, что нам не нужно было явно указывать, что объект, который мы передаем в функцию giveFruit()
, наследует этот интерфейс, как это может быть в других языках. Здесь важен только образец. Если объект, который мы передаем функции, соответствует перечисленным требованиям, то всё позволено.
Стоит отметить, что проверка типов не требует, чтобы эти свойства имели какой-либо порядок, а только то, что свойства, необходимые для интерфейса, присутствуют и имеют требуемый тип.
Необязательные свойства
Не все свойства интерфейса могут быть обязательными. Некоторые существуют при определенных условиях или могут вообще отсутствовать. Интерфейсы с необязательными свойствами записываются аналогично другим интерфейсам, где каждое необязательное свойство обозначается знаком ?
в конце имени свойства в декларации.
Пример
interface Fruit {
name: string
sweetness: number
bones: boolean
color?: number
}let banana: Fruit = {
name: 'Banana',
sweetness: 7,
bones: false,
color: 0xffe135
}let apple: Fruit = {
name: 'Apple',
sweetness: 5,
bones: true
}
Необязательные свойства популярны при создании шаблонов, таких как «option bags», в которых вы передаете объект в функцию, у которого заполнены только пара свойств.
Только для чтения
Некоторые свойства могут быть заданы только для чтения, а значение они получат при создании объекта. Этого можно добиться, поместив ключевое слово readonly перед именем свойства.
Пример
interface Point {
readonly x: number;
readonly y: number;
}let a1: Point = { x: 10, y: 40 }
console.log('Точка [' + a1.x + '; ' + a1.y + ']')
Можно создать переменную c типом Point
, присвоив ей литерал объекта. После этого значения свойств x
и y
изменять будет нельзя.
Лишние свойства
В нашем первом примере использования интерфейсов TypeScript позволил передать { name: string; sweetness: number, bones: boolean }
там, где ожидалось всего лишь { name: string }
. Также мы узнали о необязательных свойствах, и о том, как они могут быть полезны при передаче аргументов в функции. Рассмотрим пример.
Пример
interface Fruit {
name: string
sweetness?: number
bones?: boolean
color?: number
}function addFruit(x: Fruit): { name: string; color: number } {
// . ..
}let banana = addFruit({ name: 'banana', colour: 0xffe135 })
// error: 'colour' not expected in type 'Fruit'
Обратите внимание, что аргумент, передаваемый в addFruit()
, записан как colour
вместо color
. В чистом JavaScript подобные вещи не выдают ошибок, но и не работают так, как хотел бы разработчик.
Можно сказать, что данная программа корректна с точки зрения типов, так как типы свойств sweetness
совместимы, color
отсутствует, а наличие дополнительного свойства colour
не имеет никакого значения.
Однако TypeScript делает предположение, что в этом куске кода есть ошибка. Литералы объектов обрабатываются им по-особенному, и проходят проверку на наличие лишних свойств. Эта проверка делается, когда литералы либо присваиваются другим переменным, либо передаются в качестве аргументов. Если в литерале есть какие-либо свойства, которых нет в целевом типе, то это будет считаться ошибкой.
Обойти такую ошибку можно несколькими способами.
Первый способ
Использование приведение типов:
Пример
let banana = addFruit({ name: 'banana', colour: 0xFFE135 } as Fruit)
Второй способ
Добавление строкового индекса, его лучше использовать тогда, когда вы уверены, что объект может иметь дополнительные свойства.
Пример
interface Fruit {
name: string
color?: number
[propName: string]: any
}
В данном примере интерфейс Fruit
может иметь любое количество свойств. Если это не name
или color
, то тип свойства не имеет значения.
Третий способ
Присвоить объект другой переменной. Из-за присваивания объекта другой переменной он не будет проходить проверку на избыточные свойства, компилятор не выдаст ошибки.
Пример
let options = { name: 'banana', colour: 0xffe135 },
banana = addFruit(options)
Стоит иметь ввиду, что для простого кода не стоит обходить данные проверки свойств. Для более сложных литералов объектов, которые содержат в себе методы, параметры состояния и т.д., стоит держать в памяти данные способы обхода проверок, но все же большинство ошибок, связанных с проверкой лишних свойств, как правило, на самом деле являются ошибками. Если у вас возникает такая ошибка, возможно стоит пересмотреть объявление типа.
Типы функций
Помимо описания свойств, интерфейсы также позволяют описывать типы функций.
Для описания типа функции в интерфейсе, в нем нужно определить сигнатуру вызова. Это похоже на объявление функции только со списком параметров и типом возвращаемого значения. Каждый параметр в списке должен иметь имя и тип.
interface SearchFunc {
(source: string, subString: string): boolean
}
Определив такой интерфейс один раз, мы можем его использовать также как и все другие интерфейсы. Пример ниже показывает, как определить переменную с типом функции и присвоить ей значение.
Пример
interface SearchFunc {
(source: string, subString: string): boolean
}let mySearch: SearchFunc
mySearch = function (source: string, subString: string) {
let result = source. search(subString)
if (result == -1) {
return false
} else {
return true
}
}console.log(mySearch('banana lime apple', 'banana'))
Имена параметров не обязательно должны совпадать, чтобы функция прошла проверку на соответствие типов. Мы, к примеру, могли бы записать предыдущий пример — вот так:
Пример
interface SearchFunc {
(source: string, subString: string): boolean
}let mySearch: SearchFunc
mySearch = (src: string, sub: string): boolean => {
let result = src.search(sub)
if (result == -1) {
return false
} else {
return true
}
}console.log(mySearch('banana lime apple', 'banana'))
Параметры функций проверяются друг за другом, и типы параметров, находящихся на соответствующих позициях, сравниваются попарно. Если вы не хотите указывать типы для аргументов, то TypeScript сможет вывести типы из контекста, основываясь на том, что функция присваивается переменной, тип которой — SearchFunc. В следующем примере тип возвращаемого значения функции тоже выводится: это делается на основании значений, которые она возвращает false
и true
. Если бы функция возвращала числа или строки, то компилятор во время проверки типов предупредил бы, что тип возвращаемого значения не совпадает с типом, указанным в интерфейсе SearchFunc.
Пример
interface SearchFunc {
(source: string, subString: string): boolean;
}let mySearch: SearchFunc
mySearch = (src, sub) => {
let result = src.search(sub)
if (result == -1) {
return false
} else {
return true
}
}console.log(mySearch('banana lime apple', 'banana'))
Индексируемые типы
Аналогично тому, как мы можем использовать интерфейсы для описания типов функций, мы также можем описывать типы, в которые мы можем «индексировать», например, a[10]
или ageMap["daniel"]
. Индексируемые типы имеют сигнатуру индекса, которая описывает типы, которые мы можем использовать для индексации объекта, вместе с соответствующими типами возврата при индексации.
Пример
interface StringArray {
[index: number]: string
}let myArray: StringArray
myArray = ['Bob', 'Fred']let myStr: string = myArray[0]
console.log(myArray[0])
Здесь у нас есть интерфейс StringArray
, у которого есть сигнатура индекса. Эта сигнатура говорит о том, что, когда StringArray
индексируется числом, возвращается строка.
Расширение интерфейсов
Интерфейсы могут расширять друг друга. Это позволяет вам копировать элементы одного интерфейса в другой, что дает вам больше гибкости в том, как вы разделяете свои интерфейсы на повторно используемые компоненты.
Пример
interface Shape {
color: string
}interface PenStroke {
penWidth: number
}// множественное расширение
interface Square extends Shape, PenStroke {
sideLength: number
}let square = {} as Square
square.color = 'blue'
square.sideLength = 10
square. penWidth = 5.0
Гибридные типы
Как мы упоминали ранее, интерфейсы могут описывать более сложные типы, присутствующие в реальном мире JavaScript. Из-за динамического и гибкого характера JavaScript вы можете случайно встретить объект, который работает как комбинация некоторых типов, описанных выше.
Одним из таких примеров является объект, который действует как функция и объект с дополнительными свойствами:
Пример
interface Counter {
(start: number): string
interval: number
reset(): void
}function getCounter(): Counter {
let counter = function (start: number) {} as Counter
counter.interval = 123
counter.reset = function () {}
return counter
}let c = getCounter()
c(10)
c.reset()
c.interval = 5.0
Вопросы
Как называется способ типизации, используемый в TypeScript?
- явный
- утиный
- строгий
С помощью какого ключевого слова объявляется интерфейс?
- interface
- class
- function
С помощью какого символа объявляется необязательное свойство?
!
?
-
Для чего используется readonly
?
- Только для чтения
- Только для записи
- Незнаю
Позволяют ли интерфейсы описывать типы функций?
true
false
С помощью какого ключевого слова расширяются интерфейсы?
- yield
- extends
- export
Теперь мы готовы с вами изучать TypeScript, но для того чтобы понять на сколько вы усвоили этот урок пройдите тест в мобильном приложении в нашей школы по этой теме.
Ссылки
- TypeScriptLang
- Интерфейсы
Contributors ✨
Thanks goes to these wonderful people (emoji key):
IIo3iTiv 📖 | Dmitriy Vasilev 💵 |
Интерфейсы в TypeScript / Песочница / Хабр
Что такое интерфейсы и зачем они вообще нужны? TypeScript ввел новый тип данных, определяемый с помощью конструкции называемой "interface"
. Интерфейсы в TypeScript служат для именования типов данных, и являются способом определения соглашений внутри кода. Другими словами, создавая интерфейс мы создаем некоторый тип данных, который в основном служит для объектов или классов, где мы указываем какие поля, какие функции и какие вообще элементы должны присутствовать у этих объектов. Давайте рассмотрим вот такой простой пример:
interface IUser { readonly id: number, name: string, color?: string, size:{ width: number, height: number } }
Некоторые свойства должны изменяться только один раз, при создании объекта. Этого можно добиться, ограничив свойство только для чтения, применив модификатор “readonly”
, как мы с делали это полем “id”
.
Не всегда все свойства интерфейса должны являться обязательными. Сделать свойство необязательным для заполнения путем добавления знака “?”
после его именования, как это сделано у поля “color”
в примере.
Также в интерфейсе можно указывать, какие-то другие объекты, как это сделано у поля “size”
.
После создания интерфейса, для нас открывается возможность создавать различные объекты которые будут являться типом нашего интерфейса, а именно “IUser”
. Давайте
посмотрим на примере как это работает:
const user: IUser = { id: 123, name: 'Чак Норрис', color: '#ccc', size:{ width: 20, height: 30 } }
В примере мы создали переменную “user”
, указав ее тип “IUser”
. В следующем примере мы проделаем подобные действия, но уже не добавляя свойство “color”
, так как оно указано как не обязательное, но добавляя его ниже созданной переменной.
const user: IUser = { id: 123, name: 'Чак Норрис', size:{ width: 20, height: 30 } } user.color = “red”
Единственное, что здесь может смутить, это то что переменная “user”
, является константой, а в последствии мы ее изменяем. Но в конечном итоге, так как, мы работаем в JavaScript, то мы можем изменять внутреннее состояние констант, если они является объектом или массивом.
Так же мы можем указывать к какому типу будет относиться объект. Вот два примера:
const user = {} as IUser const user1 = <IUser> {} // более старая запись
TypeScript позволяет, создавать интерфейсы включающие в себя комбинации других интерфейсов, что позволяет настроить очень гибкое взаимодействие между интерфейсами. Давайте рассмотрим это на примере:
interface IUserWidthArea extends IUser { getArea:()=> number } const user2: IUserWidthArea = { id: 123, name: 'Чак Норрис', size:{ width: 20, height: 30 }, getArea(): number{ return this. size.width * this.size. height } }
Интерфейс, который мы создали будет наследоваться от интерфейса “IUser”
. В него мы можем добавлять какие-то новые поля. В нашем случае мы обязываем его реализовывать функцию “getArea()”
, которая будет рассчитывать площадь.
Так же интерфейсы могут взаимодействовать с классами. Давайте
это рассмотрим на примере интерфейса “IClock”
:
interface IClock{ time: Date, setTime(date: Date): void } сlass Clock implements IClock { time: Date = new Date() setTime(date: Date): void{ this.time = date } }
Еще что бы я хотел рассказать об интерфейса в рамках это статья, это то что бывают ситуации, когда необходимо создать интерфейс для объекта у которого будет большое количество динамических ключей, например:
const css = { padding: '10px 40px 10px 40px', position: 'relative', margin: '10px 60px 10px 60px', height: '90px', // и так далее }
Создавая интерфейс для данный переменной мы не можем перечислить всевозможные свойства, это будет крайне неэффективно, поэтому для таких ситуаций существует специальный синтаксис:
interface IStyles{ [key:string]: string }
Где в квадратных скобках мы указываем тип ключа и тип его значения.
javascript — «Интерфейс» в документации MDN для JS?
Задавать вопрос
спросил
Изменено
1 год, 1 месяц назад
Просмотрено
1к раз
Я наткнулся на слово « интерфейс «так много раз, читая документацию MDN для JS, но никогда до конца не понимал, к чему это относится.
Я знаю, что в JS нет такого понятия, как «интерфейс», как в Java, C#, TypeScript и во всех остальных. другие подобные языки ООП
Насколько мне известно, интерфейс — это просто набор определений свойств и методов (не реализаций), которые должен реализовать определенный класс, чтобы соответствовать определенному интерфейсу, но это не так, еще раз , чехол для JS.
Теперь давайте представим, что MDN говорит о некотором свойстве некоторого объекта, скажем, geolocation
свойство window. navigator
возвращает объект GeolocationPosition/GeolocationPositionError
. Затем мы читаем, что эти объекты соответствуют интерфейсу(ам) GeolocationPosition/GeolocationPositionError
.
Проверка интерфейсов GeolocationPosition/GeolocationPositionError
через console.dir
возвращает, что они являются функциями. Значит ли это, что GeolocationPosition/GeolocationPositionError
Объекты, возвращенные при доступе к свойству geolocation
в window.navigator
, являются просто объектами, созданными функциями GeolocationPosition/GeolocationPositionError
?
Итак, относится ли термин интерфейс
в документации MDN просто к функции, которая создает объекты с определенными свойствами и методами, как обычные функции-конструкторы?
Тогда почему я не могу создать новый GeolocationPosition/GeolocationPositionError
объект(ы), использующие эти функции GeolocationPosition/GeolocationPositionError
и новый оператор
?
- javascript
- браузер
- интерфейс
3
Интерфейс на самом деле имеет четко определенное значение в контексте веб-API браузера.
Интерфейс в этом контексте является интерфейсом WebIDL. WebIDL — это «язык спецификации интерфейса», на основе которого строятся такие вещи, как HTML и спецификации DOM.
Например, window.navigator
реализует интерфейс навигатора:
[Exposed=Window] навигатор интерфейса { // объекты, реализующие этот интерфейс, также реализуют интерфейсы, указанные ниже }; Навигатор включает в себя NavigatorID; Navigator включает NavigatorLanguage; Navigator включает NavigatorOnLine; Navigator включает NavigatorContentUtils; Navigator включает NavigatorCookies; Navigator включает плагины NavigatorPlugins; Navigator включает NavigatorConcurrentHardware;
Интерфейсы аналогичны тому, что вы ожидаете от «OO-языков»:
Фрагменты
IDL используются для описания объектно-ориентированных систем. В таких системах объекты — это объекты, обладающие идентичностью и являющиеся инкапсуляцией состояния и поведения. Интерфейс — это определение (соответствующее интерфейсу InterfaceRest), которое объявляет некоторое состояние и поведение, которые будет предоставлять объект, реализующий этот интерфейс.
Интерфейс — это спецификация набора членов интерфейса (соответствующих InterfaceMembers). Это члены, которые появляются между фигурными скобками в объявлении интерфейса.
Интерфейсы в Web IDL описывают, как ведут себя объекты, реализующие интерфейс. В привязках для объектно-ориентированных языков ожидается, что объект, реализующий конкретный интерфейс IDL, предоставляет способы проверки и изменения состояния объекта и вызова поведения, описанного интерфейсом.
Интерфейс может быть определен для наследования от другого интерфейса. Если за идентификатором интерфейса следует символ U+003A COLON («:») и идентификатор, то этот идентификатор идентифицирует унаследованный интерфейс. Объект, реализующий интерфейс, наследуемый от другого, также реализует этот унаследованный интерфейс. Таким образом, объект также будет иметь элементы, соответствующие элементам интерфейса из унаследованного интерфейса.
Все из спецификации WebIDL.
Что касается того, почему вы не можете создавать свои собственные экземпляры реализаций, которые браузер предоставляет для этих интерфейсов?
Я подозреваю, что GeolocationPositionError
, например, не может быть создан, потому что он не отображается в окне
. Если это полезно для пользователей, вы можете открыть запрос функции в репозитории whatwg/html.
0
В документах MDN слово 9Интерфейс 0108 часто используется, чтобы говорить об API (интерфейс прикладного программирования). Действительно, единственное, о чем вам как разработчику нужно беспокоиться, — это API (а не детали реализации, лежащие в основе API).
Интерфейсы являются концептуальными только в JavaScript, но они существуют в некоторых расширяющих языках, таких как TypeScript.
4
Зарегистрируйтесь или войдите в систему
Зарегистрируйтесь с помощью Google
Зарегистрироваться через Facebook
Зарегистрируйтесь, используя адрес электронной почты и пароль
Опубликовать как гость
Электронная почта
Обязательно, но не отображается
Опубликовать как гость
Электронная почта
Требуется, но не отображается
интерфейсов JavaScript в MDN — Qaru
Однако я часто вижу в онлайн-документации MDN некоторые объекты, описанные как «интерфейсы», такие как Storage здесь:
https://developer. mozilla.org/en-US/docs/Web/API/Storage
Действительно, window.Storage существует и является функцией, но не конструктором или фабрикой, и при вызове происходит сбой. У него нет элементов, подобных тем, которые описаны на этой странице, вместо этого есть window.localStorage.
Страница
https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
указывает window.Storage как объект, к которому обращается window.localStorage. Я понимаю, что window.localStorage — нативный объект, но хотелось бы понять роль window.Storage и почему MDN обращается к нему как к «интерфейсу», в каком смысле: это спецификация, которой придерживаются разработчики браузеров, а не строгий «интерфейс», как это предусмотрено в парадигме, основанной на классах?
Заранее спасибо, извините за странный вопрос. Просто пытаюсь углубить свое понимание ES.
- javascript
- интерфейс
5
Это не класс с конструктором, экземпляр которого можно создать, поэтому мы не называем его классом. Это также не объект-прототип.
MDN использует термин интерфейс в общем значении ООП, которое не ограничивается наследованием на основе классов, но относится к определению типа с сигнатурами методов.
Однако он также использует термин интерфейс в очень специфическом контексте языка определения веб-интерфейса, который спецификация веб-хранилища использует для определения Storage
как интерфейса
. Эти WebAPI могут быть реализованы на нескольких языках (называемых «привязками»), хотя чаще всего используется JS. Спецификация WebIDL даже определяет, как такой интерфейс должен быть представлен в JavaScript («привязка ECMAScript»), в частности, что линейное наследование интерфейсов реализовано с использованием наследования прототипов между объектами интерфейса и их .prototype
s (в основном, как если бы использовалось расширение класса Storage…
). Это означает, что localStorage instanceof Storage
и Storage.