Какой из методов можно использовать подряд например object method method
Перейти к содержимому

Какой из методов можно использовать подряд например object method method

  • автор:

Класс: базовый синтаксис

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

На практике нам часто надо создавать много объектов одного вида, например пользователей, товары или что-то ещё.

Как мы уже знаем из главы Конструктор, оператор «new», с этим может помочь new function .

Но в современном JavaScript есть и более продвинутая конструкция «class», которая предоставляет новые возможности, полезные для объектно-ориентированного программирования.

Синтаксис «class»

Базовый синтаксис выглядит так:

class MyClass < // методы класса constructor() < . >method1() < . >method2() < . >method3() < . >. >

Затем используйте вызов new MyClass() для создания нового объекта со всеми перечисленными методами.

При этом автоматически вызывается метод constructor() , в нём мы можем инициализировать объект.

class User < constructor(name) < this.name = name; >sayHi() < alert(this.name); >> // Использование: let user = new User("Иван"); user.sayHi();

Когда вызывается new User(«Иван») :

  1. Создаётся новый объект.
  2. constructor запускается с заданным аргументом и сохраняет его в this.name .

…Затем можно вызывать на объекте методы, такие как user.sayHi() .

Методы в классе не разделяются запятой

Частая ошибка начинающих разработчиков – ставить запятую между методами класса, что приводит к синтаксической ошибке.

Синтаксис классов отличается от литералов объектов, не путайте их. Внутри классов запятые не требуются.

Что такое класс?

Итак, что же такое class ? Это не полностью новая языковая сущность, как может показаться на первый взгляд.

Давайте развеем всю магию и посмотрим, что такое класс на самом деле. Это поможет в понимании многих сложных аспектов.

В JavaScript класс – это разновидность функции.

class User < constructor(name) < this.name = name; >sayHi() < alert(this.name); >> // доказательство: User - это функция alert(typeof User); // function

Вот что на самом деле делает конструкция class User <. >:

  1. Создаёт функцию с именем User , которая становится результатом объявления класса. Код функции берётся из метода constructor (она будет пустой, если такого метода нет).
  2. Сохраняет все методы, такие как sayHi , в User.prototype .

При вызове метода объекта new User он будет взят из прототипа, как описано в главе F.prototype. Таким образом, объекты new User имеют доступ к методам класса.

На картинке показан результат объявления class User :

Можно проверить вышесказанное и при помощи кода:

class User < constructor(name) < this.name = name; >sayHi() < alert(this.name); >> // класс - это функция alert(typeof User); // function // . или, если точнее, это метод constructor alert(User === User.prototype.constructor); // true // Методы находятся в User.prototype, например: alert(User.prototype.sayHi); // sayHi() < alert(this.name); >// в прототипе ровно 2 метода alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi

Не просто синтаксический сахар

Иногда говорят, что class – это просто «синтаксический сахар» в JavaScript (синтаксис для улучшения читаемости кода, но не делающий ничего принципиально нового), потому что мы можем сделать всё то же самое без конструкции class :

// перепишем класс User на чистых функциях // 1. Создаём функцию constructor function User(name) < this.name = name; >// каждый прототип функции имеет свойство constructor по умолчанию, // поэтому нам нет необходимости его создавать // 2. Добавляем метод в прототип User.prototype.sayHi = function() < alert(this.name); >; // Использование: let user = new User("Иван"); user.sayHi();

Результат этого кода очень похож. Поэтому, действительно, есть причины, по которым class можно считать синтаксическим сахаром для определения конструктора вместе с методами прототипа.

Однако есть важные отличия:

    Во-первых, функция, созданная с помощью class , помечена специальным внутренним свойством [[IsClassConstructor]]: true . Поэтому это не совсем то же самое, что создавать её вручную. В отличие от обычных функций, конструктор класса не может быть вызван без new :

class User < constructor() <>> alert(typeof User); // function User(); // Error: Class constructor User cannot be invoked without 'new'

Кроме того, строковое представление конструктора класса в большинстве движков JavaScript начинается с «class …»

class User < constructor() <>> alert(User); // class User

Также в дополнение к основной, описанной выше, функциональности, синтаксис class даёт ряд других интересных возможностей, с которыми мы познакомимся чуть позже.

Class Expression

Как и функции, классы можно определять внутри другого выражения, передавать, возвращать, присваивать и т.д.

Пример Class Expression (по аналогии с Function Expression):

let User = class < sayHi() < alert("Привет"); >>;

Аналогично Named Function Expression, Class Expression может иметь имя.

Если у Class Expression есть имя, то оно видно только внутри класса:

// "Named Class Expression" // (в спецификации нет такого термина, но происходящее похоже на Named Function Expression) let User = class MyClass < sayHi() < alert(MyClass); // имя MyClass видно только внутри класса >>; new User().sayHi(); // работает, выводит определение MyClass alert(MyClass); // ошибка, имя MyClass не видно за пределами класса

Мы даже можем динамически создавать классы «по запросу»:

function makeClass(phrase) < // объявляем класс и возвращаем его return class < sayHi() < alert(phrase); >; >; > // Создаём новый класс let User = makeClass("Привет"); new User().sayHi(); // Привет

Геттеры/сеттеры, другие сокращения

Как и в литеральных объектах, в классах можно объявлять вычисляемые свойства, геттеры/сеттеры и т.д.

Вот пример user.name , реализованного с использованием get/set :

class User < constructor(name) < // вызывает сеттер this.name = name; >get name() < return this._name; >set name(value) < if (value.length < 4) < alert("Имя слишком короткое."); return; >this._name = value; > > let user = new User("Иван"); alert(user.name); // Иван user = new User(""); // Имя слишком короткое.

При объявлении класса геттеры/сеттеры создаются на User.prototype , вот так:

Object.defineProperties(User.prototype, < name: < get() < return this._name >, set(name) < // . >> >);

Пример с вычисляемым свойством в скобках [. ] :

JavaScript: Цепочка вызовов

Попробуйте ответить на вопрос, заработает ли следующий код — и если да, то что он напечатает на экран?

const name = 'Tirion'; console.log(name.length.toString()); 

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

Самый простой способ понять как работает этот код — разбить цепочку на отдельные операции:

const name = 'Tirion'; const len = name.length; console.log(len.toString()); 

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

Ещё один пример для закрепления:

const name = 'Tirion'; console.log(name.toUpperCase().toLowerCase()); 

Подобный код требует небольших умственных усилий. Важно понимать, что .toLowerCase() применяется к результату вызова метода, который находится левее. А метод toUpperCase() возвращает строку. Новички часто делают ошибки в цепочках с методами, забывая ставить вызов:

const name = 'Tirion'; // Этот код отработает неверно! console.log(name.toUpperCase.toLowerCase()); 

Продолжая эту идею, возможно строить бесконечно длинные (хотя, в данном случае, бесполезные) цепочки:

// Чему равен результат такого вызова? console.log(name.toUpperCase().toLowerCase().length.toString().length); 

С функциями подобный трюк не сработает, так как при обычном использовании они вкладываются друг в друга f(f(f())), что значительно ухудшает анализ. Но это не значит, что нельзя сделать красиво — можно и даже нужно. В других языках это реализуется через композицию функций или пайплайн-оператор, который, кстати говоря, постепенно начинает использоваться и в самом JavaScript: https://github.com/tc39/proposal-pipeline-operator.

Задание

С помощью метода slice() получите часть предложения, записанного в константу text , c 5 по 15 символы включительно. Полученную подстроку обработайте методом .trim() и выведите на экран длину итоговой подстроки. Выполните эти методы подряд в цепочке без создания промежуточных переменных.

Упражнение не проходит проверку — что делать? ��

Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:

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

В моей среде код работает, а здесь нет ��

Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.

Мой код отличается от решения учителя ��

Это нормально ��, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.

В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.

Прочитал урок — ничего не понятно ��

Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.

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

Определения

  • Метод — это функция или процедура, принадлежащая какому-то классу или объекту.

12 концепций JavaScript, о которых нужно знать

JavaScript — это сложный язык. Если вы, на любом уровне, занимаетесь JavaScript-разработкой, это значит, что вам жизненно необходимо понимать базовые концепции этого языка. В материале, перевод которого мы сегодня публикуем, рассмотрены 12 важнейших концепций JavaScript. Конечно, JavaScript-разработчику нужно знать гораздо больше, но без того, о чём мы будем сегодня говорить, ему точно не обойтись.

1. Переменные, хранящие значения и ссылки

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

JavaScript, если некая сущность имеет один из примитивных типов (в частности — это типы Boolean , null , undefined , String и Number ), всегда работает со значением этой сущности. То есть, в соответствующую переменную записывается именно значение. Если же речь идёт об объекте (это, например, типы Object , Array , Function ), то, при назначении его переменной, в неё записывается ссылка на него, адрес, по которому он расположен в памяти.

Рассмотрим пример. В следующем фрагменте кода в переменную var1 записана строка. После этого в переменную var2 записано значение переменной var1 . Так как переменная var1 имеет примитивный тип ( String ), то в var2 будет записана копия строки, имеющейся в var1 . Это позволяет рассматривать var2 как переменную, полностью независимую от var1 , хотя и хранящую то же значение, что и var1 . Запись в var2 нового значения на var1 не влияет.

let var1 = 'My string'; let var2 = var1; var2 = 'My new string'; console.log(var1); // 'My string' console.log(var2); // 'My new string'

Теперь рассмотрим пример работы с объектами.

let var1 = < name: 'Jim' >let var2 = var1; var2.name = 'John'; console.log(var1); // < name: 'John' >console.log(var2); //

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

2. Замыкания

Замыкание — это важный паттерн проектирования в JavaScript, который позволяет организовать защищённую работу с переменными. В следующем примере функция createGreeter() возвращает анонимную функцию, у которой есть доступ к предоставленному исходной функции аргументу greeting , содержащему строку Hello . Ссылка на эту анонимную функцию записывается в переменную sayHello . После этого, сколько раз бы мы ни вызывали функцию sayHello() , у неё всегда будет доступ к значению greeting . При этом доступ к greeting будет только у анонимной функции, ссылка на которую записана в sayHello .

function createGreeter(greeting) < return function(name) < console.log(greeting + ', ' + name); >> const sayHello = createGreeter('Hello'); sayHello('Joe'); // Hello, Joe

Это был очень простой пример. Если же рассмотреть нечто, более близкое к реальному миру, то можно представить себе, например, функцию для подключения к некоему API (назовём её apiConnect() ), которой, при первом её вызове, передаётся ключ доступа к API. Эта функция, в свою очередь, возвращает объект, содержащий несколько методов, которые пользуются переданным apiConnect() ключом доступа к API. При этом ключ хранится в замыкании и при вызове этих методов упоминать его больше не требуется.

function apiConnect(apiKey) < function get(route) < return fetch(`$?key=$`); > function post(route, params) < return fetch(route, < method: 'POST', body: JSON.stringify(params), headers: < 'Authorization': `Bearer $` > >) > return < get, post >> const api = apiConnect('my-secret-key'); // Использовать ключ доступа к API больше уже не нужно api.get('http://www.example.com/get-endpoint'); api.post('http://www.example.com/post-endpoint', < name: 'Joe' >);

3. Деструктурирующее присваивание

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

const obj = < name: 'Joe', food: 'cake' >const < name, food >= obj; console.log(name, food); // 'Joe' 'cake'

Если извлечённым свойствам нужно присвоить имена, отличающиеся от тех, которые они имеют в объекте, можно поступить так:

const obj = < name: 'Joe', food: 'cake' >const < name: myName, food: myFood >= obj; console.log(myName, myFood); // 'Joe' 'cake'

В следующем примере деструктурирование используется для аккуратной передачи значений, хранящихся в свойствах объекта person , функции introduce() . Это — пример того, как данная конструкция используется при объявлении функции для извлечения данных из переданного ей объекта с параметрами. Кстати, если вы знакомы с React, то вы, вероятно, уже такое видели.

const person = < name: 'Eddie', age: 24 >function introduce(< name, age >) < console.log(`I'm $and I'm $ years old!`); > console.log(introduce(person)); // "I'm Eddie and I'm 24 years old!"

4. Оператор spread

Оператор spread — это довольно простая конструкция, которая может показаться неподготовленному человеку непонятной. В следующем примере есть числовой массив, максимальное значение, хранящееся в котором, нам нужно найти. Мы хотим использовать для этого метод Math.max() , но он с массивами работать не умеет. Он, в качестве аргументов, принимает самостоятельные числовые значения. Для того чтобы извлечь из массива его элементы мы используем оператор spread, который выглядит как три точки.

const arr = [4, 6, -1, 3, 10, 4]; const max = Math.max(. arr); console.log(max); // 10

5. Оператор rest

Оператор rest позволяет преобразовывать любое количество аргументов, переданных функции, в массив.

function myFunc(. args) < console.log(args[0] + args[1]); >myFunc(1, 2, 3, 4); // 3

6. Методы массивов

Методы массивов часто дают разработчику удобные инструменты, позволяющие красиво решать самые разные задачи по преобразованию данных. Я иногда отвечаю на вопросы на StackOverflow. Среди них часто попадаются такие, которые посвящены чему-то вроде тех или иным способов работы с массивами объектов. Именно в таких ситуациях методы массивов особенно полезны.

Здесь мы рассмотрим несколько таких методов, объединённых по принципу их схожести друг с другом. Надо отметить, что тут я расскажу далеко не обо всех методах массивов. Найти их полный список можно на MDN (кстати, это — мой любимый справочник по JavaScript).

▍Методы map(), filter() и reduce()

Методы массивов map() , filter() и reduce() позволяют трансформировать массивы или сводить массивы к одному значению (которое может быть объектом).

Метод map() возвращает новый массив, содержащий трансформированные значения обрабатываемого массива. То, как именно они будут трансформированы, задаётся в передаваемой этому методу функции.

const arr = [1, 2, 3, 4, 5, 6]; const mapped = arr.map(el => el + 20); console.log(mapped); // [21, 22, 23, 24, 25, 26]

Метод filter() возвращает массив элементов, проверяя значения которых функция, переданная этому методу, возвратила true .

const arr = [1, 2, 3, 4, 5, 6]; const filtered = arr.filter(el => el === 2 || el === 4); console.log(filtered); // [2, 4]

Метод reduce() возвращает некое значение, представляющее собой результат обработки всех элементов массива.

const arr = [1, 2, 3, 4, 5, 6]; const reduced = arr.reduce((total, current) => total + current); console.log(reduced); // 21

▍Методы find(), findIndex() и indexOf()

Методы массивов find() , findIndex() и indexOf() легко перепутать друг с другом. Ниже даны пояснения, помогающие понять их особенности.

Метод find() возвращает первый элемент массива, соответствующий заданному критерию. Этот метод, найдя первый подходящий элемент, не продолжает поиск по массиву.

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const found = arr.find(el => el > 5); console.log(found); // 6

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

Метод findIndex() очень похож на find() , но он, вместо того, чтобы возвращать первый подходящий элемент массива, возвращает индекс такого элемента. Для того чтобы лучше понять этот метод — взгляните на следующий пример, в котором используется массив строковых значений.

const arr = ['Nick', 'Frank', 'Joe', 'Frank']; const foundIndex = arr.findIndex(el => el === 'Frank'); console.log(foundIndex); // 1

Метод indexOf() очень похож на метод findIndex() , но он принимает в качестве аргумента не функцию, а обычное значение. Использовать его можно в том случае, если при поиске нужного элемента массива не нужна сложная логика.

const arr = ['Nick', 'Frank', 'Joe', 'Frank']; const foundIndex = arr.indexOf('Frank'); console.log(foundIndex); // 1

▍Методы push(), pop(), shift() и unshift()

Методы push() , pop() , shift() и unshift() применяются для добавления в массивы новых элементов и для извлечения из массивов уже имеющихся в них элементов. При этом работа производится с элементами, находящимися в начале или в конце массива.

Метод push() позволяет добавлять элементы в конец массива. Он модифицирует массив, и, после завершения работы, возвращает элемент, добавленный в массив.

let arr = [1, 2, 3, 4]; const pushed = arr.push(5); console.log(arr); // [1, 2, 3, 4, 5] console.log(pushed); // 5

Метод pop() удаляет из массива последний элемент. Он модифицирует массив и возвращает удалённый из него элемент.

let arr = [1, 2, 3, 4]; const popped = arr.pop(); console.log(arr); // [1, 2, 3] console.log(popped); // 4

Метод shift() удаляет из массива первый элемент и возвращает его. Он тоже модифицирует массив, для которого его вызывают.

let arr = [1, 2, 3, 4]; const shifted = arr.shift(); console.log(arr); // [2, 3, 4] console.log(shifted); // 1

Метод unshift() добавляет один или большее количество элементов в начало массива. Он, опять же, модифицирует массив. При этом, в отличие от трёх других рассмотренных здесь методов, он возвращает новую длину массива.

let arr = [1, 2, 3, 4]; const unshifted = arr.unshift(5, 6, 7); console.log(arr); // [5, 6, 7, 1, 2, 3, 4] console.log(unshifted); // 7

▍Методы slice() и splice()

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

Метод splice() меняет содержимое массива, удаляя существующие элементы или заменяя их на другие элементы. Он умеет и добавлять в массив новые элементы. Этот метод модифицирует массив.

Следующий пример, если описать его обычным языком, выглядит так: нужно, в позиции массива 1 , удалить 0 элементов и добавить элемент, содержащий b .

let arr = ['a', 'c', 'd', 'e']; arr.splice(1, 0, 'b')

Метод slice() возвращает неглубокую копию массива, содержащую его элементы, начиная с заданной начальной позиции и заканчивая позицией, предшествующей заданной конечной позиции. Если при его вызове задана только начальная позиция, то он вернёт весь массив, начиная с этой позиции. Этот метод не модифицирует массив. Он лишь возвращает описанную при его вызове часть этого массива.

let arr = ['a', 'b', 'c', 'd', 'e']; const sliced = arr.slice(2, 4); console.log(sliced); // ['c', 'd'] console.log(arr); // ['a', 'b', 'c', 'd', 'e']

▍Метод sort()

Метод sort() выполняет сортировку массива в соответствии с условием, заданным переданной ему функцией. Эта функция принимает два элемента массива (например, они могут быть представлены в виде параметров a и b ), и, сравнивая их, возвращает, в том случае, если элементы менять местами не надо, 0, если a нужно поставить по меньшему индексу, чем b — отрицательное число, а если b нужно поставить по меньшему индексу, чем a — положительное число.

let arr = [1, 7, 3, -1, 5, 7, 2]; const sorter = (firstEl, secondEl) => firstEl - secondEl; arr.sort(sorter); console.log(arr); // [-1, 1, 2, 3, 5, 7, 7]

Если вы не можете, впервые ознакомившись с этими методами, их запомнить — ничего страшного. Самое главное это то, что вы теперь знаете о том, что умеют стандартные методы массивов. Поэтому, если вы и не можете сходу вспомнить особенности того или иного метода, то, что вы о нём знаете, позволит вам быстро найти то, что нужно, в документации.

7. Генераторы

Генераторы в JavaScript объявляют, используя символ звёздочки. Они позволяют задавать то, какое значение будет возвращено при очередном вызове метода next() . Генераторы могут быть рассчитаны на возврат ограниченного количества значений. Если подобный генератор возвратил все такие значения, то очередной вызов next() вернёт undefined . Можно создавать и генераторы, рассчитанные на возврат неограниченного количества значений с использованием циклов.

Вот генератор, рассчитанный на возврат ограниченного числа значений:

function* greeter() < yield 'Hi'; yield 'How are you?'; yield 'Bye'; >const greet = greeter(); console.log(greet.next().value); // 'Hi' console.log(greet.next().value); // 'How are you?' console.log(greet.next().value); // 'Bye' console.log(greet.next().value); // undefined

А вот генератор, рассчитанный на возврат бесконечного количества значений посредством цикла.

function* idCreator() < let i = 0; while (true) yield i++; >const ids = idCreator(); console.log(ids.next().value); // 0 console.log(ids.next().value); // 1 console.log(ids.next().value); // 2 // и так далее. 

8. Операторы проверки равенства (==) и строгого равенства (===) значений

Любому JS-разработчику чрезвычайно важно понимать разницу между операторами равенства ( == ) и строгого равенства ( === ). Дело в том, что оператор == , перед сравнением значений, выполняет преобразование их типов (что может приводить к странным, на первый взгляд, последствиям), а оператор === преобразование типов не производит.

console.log(0 == '0'); // true console.log(0 === '0'); // false

9. Сравнение объектов

Мне периодически приходится видеть, как новички в JS-программировании совершают одну и ту же ошибку. Они пытаются напрямую сравнивать объекты. Переменные, в которых «хранятся» объекты, содержат в себе ссылки на них, а не сами эти объекты.

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

const joe1 = < name: 'Joe' >; const joe2 = < name: 'Joe' >; console.log(joe1 === joe2); // false

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

const joe1 = < name: 'Joe' >; const joe2 = joe1; console.log(joe1 === joe2); // true

Один из методов настоящего сравнения объектов заключается в их предварительном преобразовании в формат JSON-строк. Правда, у такого подхода есть одна проблема, которая заключается в том, что в полученном строковом представлении объекта не гарантируется определённый порядок следования его свойств. Более надёжный способ сравнения объектов заключается в использовании специальной библиотеки, содержащей средства для глубокого сравнения объектов (например — это метод isEqual() библиотеки lodash).

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

10. Функции обратного вызова

Функции обратного вызова — это довольно простая концепция JavaScript, с которой у новичков иногда возникают сложности. Рассмотрим следующий пример. Здесь функция console.log (именно так — без скобок) передаётся функции myFunc() в качестве функции обратного вызова. Эта функция устанавливает таймер, по срабатыванию которого вызывается console.log() и переданная функции myFunc() строка выводится в консоль.

function myFunc(text, callback) < setTimeout(function() < callback(text); >, 2000); > myFunc('Hello world!', console.log); // 'Hello world!'

11. Промисы

После того, как вы освоите функции обратного вызова и начнёте всюду их использовать, вы очень скоро можете обнаружить себя в так называемом «аду коллбэков». Если вы и правда там оказались — взгляните на промисы. Асинхронный код можно обернуть в промис, и, после его успешного выполнения, сообщить системе об успешном разрешении промиса, вызвав соответствующий метод, а если что-то пойдёт не так — вызвать метод, указывающий на это и отклонить промис. Для того чтобы обработать результаты, возвращаемые промисом, воспользуйтесь методом then() , а для обработки ошибок — методом catch() .

const myPromise = new Promise(function(res, rej) < setTimeout(function()< if (Math.random() < 0.9) < return res('Hooray!'); >return rej('Oh no!'); >, 1000); >); myPromise .then(function(data) < console.log('Success: ' + data); >) .catch(function(err) < console.log('Error: ' + err); >); // Если Math.random() вернёт значение, меньшее, чем 0.9, в консоль попадёт следующее: // "Success: Hooray!" // Если Math.random() вернёт значение, большее, чем 0.9, или 0.9, в консоль попадёт следующее: // "Error: On no!"

12. Конструкция async/await

После того, как вы поработаете с промисами, то вам, вполне возможно, захочется чего-то большего. Например — освоить конструкцию async/await. Она представляет собой «синтаксический сахар» для промисов. В следующем примере мы создаём, с помощью ключевого слова async , асинхронную функцию, и в ней, пользуясь ключевым словом await , организуем ожидание выполнения промиса greeter .

const greeter = new Promise((res, rej) => < setTimeout(() =>res('Hello world!'), 2000); >) async function myFunc() < const greeting = await greeter; console.log(greeting); >myFunc(); // 'Hello world!'

Итоги

Если то, о чём мы здесь говорили, было вам до этого незнакомо, то вы, скорее всего, хотя бы немного, но выросли над собой, прочитав эту статью. Если же вы не нашли тут ничего нового, тогда хочется надеяться, что этот материал дал вам возможность попрактиковаться и укрепить ваши знания JavaScript.

Уважаемые читатели! Какие ещё концепции JavaScript вы добавили бы в эту статью?

Какой из методов можно использовать подряд например object method method

Иногда при взаимодействии потоков встает вопрос о извещении одних потоков о действиях других. Например, действия одного потока зависят от результата действий другого потока, и надо как-то известить один поток, что второй поток произвел некую работу. И для подобных ситуаций у класса Object определено ряд методов:

  • wait() : освобождает монитор и переводит вызывающий поток в состояние ожидания до тех пор, пока другой поток не вызовет метод notify()
  • notify() : продолжает работу потока, у которого ранее был вызван метод wait()
  • notifyAll() : возобновляет работу всех потоков, у которых ранее был вызван метод wait()

Все эти методы вызываются только из синхронизированного контекста — синхронизированного блока или метода.

Рассмотрим, как мы можем использовать эти методы. Возьмем стандартную задачу из прошлой темы — «Производитель-Потребитель» («Producer-Consumer»): пока производитель не произвел продукт, потребитель не может его купить. Пусть производитель должен произвести 5 товаров, соответственно потребитель должен их все купить. Но при этом одновременно на складе может находиться не более 3 товаров. Для решения этой задачи задействуем методы wait() и notify() :

public class Program < public static void main(String[] args) < Store store=new Store(); Producer producer = new Producer(store); Consumer consumer = new Consumer(store); new Thread(producer).start(); new Thread(consumer).start(); >> // Класс Магазин, хранящий произведенные товары class Store < private int product=0; public synchronized void get() < while (product<1) < try < wait(); >catch (InterruptedException e) < >> product--; System.out.println("Покупатель купил 1 товар"); System.out.println("Товаров на складе: " + product); notify(); > public synchronized void put() < while (product>=3) < try < wait(); >catch (InterruptedException e) < >> product++; System.out.println("Производитель добавил 1 товар"); System.out.println("Товаров на складе: " + product); notify(); > > // класс Производитель class Producer implements Runnable < Store store; Producer(Store store)< this.store=store; >public void run() < for (int i = 1; i < 6; i++) < store.put(); >> > // Класс Потребитель class Consumer implements Runnable < Store store; Consumer(Store store)< this.store=store; >public void run() < for (int i = 1; i < 6; i++) < store.get(); >> >

Итак, здесь определен класс магазина, потребителя и покупателя. Производитель в методе run() добавляет в объект Store с помощью его метода put() 5 товаров. Потребитель в методе run() в цикле обращается к методу get объекта Store для получения этих товаров. Оба метода Store — put и get являются синхронизированными.

Для отслеживания наличия товаров в классе Store проверяем значение переменной product . По умолчанию товара нет, поэтому переменная равна 0 . Метод get() — получение товара должен срабатывать только при наличии хотя бы одного товара. Поэтому в методе get проверяем, отсутствует ли товар:

while (product<1)

Если товар отсутсвует, вызывается метод wait() . Этот метод освобождает монитор объекта Store и блокирует выполнение метода get, пока для этого же монитора не будет вызван метод notify() .

В методе put() работает похожая логика, только теперь метод put() должен срабатывать, если в магазине не более трех товаров. Поэтому в цикле проверяется наличие товара, и если товар уже есть, то освобождаем монитор с помощью wait() и ждем вызова notify() в методе get() .

И теперь программа покажет нам другие результаты:

Производитель добавил 1 товар Товаров на складе: 1 Производитель добавил 1 товар Товаров на складе: 2 Производитель добавил 1 товар Товаров на складе: 3 Покупатель купил 1 товар Товаров на складе: 2 Покупатель купил 1 товар Товаров на складе: 1 Покупатель купил 1 товар Товаров на складе: 0 Производитель добавил 1 товар Товаров на складе: 1 Производитель добавил 1 товар Товаров на складе: 2 Покупатель купил 1 товар Товаров на складе: 1 Покупатель купил 1 товар Товаров на складе: 0

Таким образом, с помощью wait() в методе get() мы ожидаем, когда производитель добавит новый продукт. А после добавления вызываем notify() , как бы говоря, что на складе освободилось одно место, и можно еще добавлять.

А в методе put() с помощью wait() мы ожидаем освобождения места на складе. После того, как место освободится, добавляем товар и через notify() уведомляем покупателя о том, что он может забирать товар.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *