Программная инженерия

Разработка JavaScript: наука позади

Концепции JavaScript, которые должен знать каждый программист

Недавно я писал о JS в статье Концепции JavaScript, которые должен знать каждый программист, и отзывы сообщества были великолепны.



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

Итак, давайте копать!

Опытный образец

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

Так, например, объект Date наследует Date.prototype, Array наследует Array.prototype, а объект Person наследует Person.prototype.

Кроме того, все эти объекты наследуют Object.prototype

Это в основном полезно для внутреннего устройства JavaScript и позволяет нам работать с JavaScript, как мы знаем, но в целом приятно понимать, что все свойства объекта и методы, которые мы используем, определены на уровне прототипа. И вы можете проверить это самостоятельно, просто выйдя из прототипа следующим образом:

# Log prototype on type
console.log(Array.prototype);
# Log prototype on instance
let fruits = new Array("apple", "strawberry");
console.log(fruits.__proto__);

Зарегистрировав прототип массива, вы сможете увидеть его свойства и методы, такие как:

keys: ƒ keys()
entries: ƒ entries()
every: ƒ every()
filter: ƒ filter()
forEach: ƒ forEach()
indexOf: ƒ indexOf()
length: value
...

Если вам интересно, что такое свойство __proto__, это просто геттер/сеттер, и оно определено в Object.prototype, которое вы также можете проверить, войдя в консоль.

Классы

Многие не вспомнят, но несколько лет назад JavaScript в основном рассматривался как функциональный (не сегодняшнее значение функционального) язык, и в основном из-за отсутствия функций, основанных на классах. Не было способа определить классы, и если бы вы захотели, вы бы в основном злоупотребляли функциями, и вы бы придумали что-то вроде этого:

function Person(name) {
    this.name = name
}
var person = new Person("Medium");

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

Чтобы инкапсулировать свойства и функции в единый шаблон, вы должны объявить и использовать такой класс:

class Person {
    constructor(firstname, lastname) {
        this.firstname = firstname;
        this.lastname = lastname;
    }
    
    get name(){
        return this.firstname + this.lastname;
    }
    
    sayHello(){
        return `Hello my name is ${this.name}!`
    }
}
let person = new Person("Medium", ".com");
let fullname = person.name;
let hello = person.sayHello();

Очевидно, что занятий намного больше, обязательно ознакомьтесь с:

Я настоятельно рекомендую прочитать о классах в Mozilla's MDN Web Docs.

Передача по значению или ссылке

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

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

Часть «в основном» в приведенном выше абзаце связана с тем, что это верно для примитивных значений. Например, в приведенном ниже коде показано, как действительно копируются значения, а изменения, внесенные в переменные firstname и lastnameнеотражаются вне функции.

# Pass by value for primitives
function changeName(firstname, lastname) {
    firstname = "something";
    lastname = "else";
}
let firstname = "First";
let lastname = "Last";
changeName(firstname, lastname);
console.log(firstname, lastname); // Still prints "First" and "Last"

Для сложных типов все немного иначе — объекты, которые мы передаем в функции, не будут скопированы и сами их данные.

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

# Passing an object results in copying the reference only
function changePerson(obj) {
     obj.name = "New Name";
     obj = {name: "Another New Name"};
}
let person = {name: "Medium"};
changePerson(person);
console.log(person); // Prints {name: "New Name"}

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

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

# Passing an object results in copying the reference only
function changePerson(obj) {
     obj.name = "New Name";
     console.log(person === obj); // Results in true
     obj = {name: "New Name"};
     console.log(person === obj); // Results in false
}
let person = {name: "Medium"};
changePerson(person);
console.log(person); // Prints {name: "New Name"}

Первая проверка даст результат true, так как это тот же рассматриваемый объект, а не копия, даже если поле было изменено, а вторая проверка даст false потому что объекты, на которые указывают person и obj, больше не совпадают.

Коллекции

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

Как на самом деле JavaScript отличается от других — какие коллекции мы можем использовать?

Массивы — или мне следует называть это списками

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

# JS Array is more like a List
let fruits = ["apple", "strawberry"];
console.log(fruits.__proto__); // join, filter, map, forEach etc.

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

# Arrays and Objects
let fruits = ["apple", "strawberry"];
let fruitsObj = {0: "apple", 1: "strawberry"};

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

Наборы

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

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

Тем не менее, Set в JavaScript поддерживает порядок вставки, и это стоит отметить, и это совсем не то, что можно было бы ожидать.

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

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

# Sets
let fruits = new Set(["apple", "strawberry"]);
fruits.add("pear");
let containsPear = fruits.has("pear"); // true
fruits.delete("apple");
let containsApple = fruits.has("apple"); // false
fruits.clear();
let size = fruits.size; // 0

Помимо приведенного выше примера, и, к счастью, для прототипов вы также можете использовать методы forEach, entries, keys, values для наборов!

Карты

Лично моя любимая коллекция — Map

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

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

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

И сколько итераций и операций нужно сделать? Ну, на массиве из тысячи длин — возможно, тысяча итераций. Это в основном то, что происходит, и не только с циклами for в массивах, но и с большинством методов поиска, таких как find или includes..

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

# Maps
let john = {name: 'John', dob: '1990-10-12'};
let mary = {name: 'Mary', dob: '2001-06-09'};
let alex = {name: 'Alex', dob: '2000-05-10'};
let persons = new Map();
persons.set(john.name, john);
persons.set(mary.name, mary);
persons.set(alex.name, alex);
let name = 'John';
let person = persons.get(name);

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

Также традиционно — много лет назад в качестве карт использовались объекты. Вы получаете аналогичные функции сопоставления значений со строками, но некоторые из преимуществ сопоставления:

  • Ключи в картах могут иметь любое значение, не только строки
  • Карты имеют свойство размера, а на объектах приходится его рассчитывать

Я настоятельно рекомендую прочитать больше о картах, например, в Mozilla's MDN Docs, так как очень важно иметь их в своем наборе навыков не только в JavaScript, но и в любом языке программирования.

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

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

Спасибо за чтение! 🎉

Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter и LinkedIn. Посетите наш Community Discord и присоединитесь к нашему Коллективу талантов.