Основы JavaScript для начинающих разработчиков

https://medium.freecodecamp.org/learn-these-javascript-fundamentals-and-become-a-better-developer-2a031a0dc9cf
  • Перевод
Материал, перевод которого мы сегодня публикуем, посвящён основам JavaScript и предназначен для начинающих программистов. Его можно рассматривать и как небольшой справочник по базовым конструкциям JS. Здесь мы, в частности, поговорим о системе типов данных, о переменных, о массивах, о функциях, о прототипах объектов, и о некоторых других особенностях языка.



Примитивные типы данных


В JavaScript имеются следующие примитивные типы данных: number, boolean, string, undefined, null. Сразу нужно отметить, что, при работе с примитивными типами данных, например, со строковыми литералами, мы, даже не проводя явного преобразования, сможем обращаться к их методам и свойствам. Дело тут в том, что при попытке выполнения подобных операций литералы автоматически оснащаются соответствующей объектной обёрткой.

▍Числа


В JavaScript имеется лишь один тип чисел — это числа двойной точности с плавающей запятой. Это ведёт к тому, что результаты вычисления некоторых выражений арифметически неверны. Возможно, вы уже знаете, что в JS значение выражения 0.1 + 0.2 не равно 0.3. В то же время, при работе с целыми числами таких проблем не наблюдается, то есть, 1 + 2 === 3.

В JavaScript имеется объект Number, представляющий собой объектную обёртку для числовых значений. Объекты типа Number можно создавать либо используя команду вида var a = new Number(10), либо можно полагаться на автоматическое поведение системы, описанное выше. Это, в частности, позволяет вызывать методы, хранящиеся в Number.prototype в применении к числовым литералам:

(123).toString();  //"123"
(1.23).toFixed(1); //"1.2"

Существуют глобальные функции, предназначенные для преобразования значений других типов в числовой тип. Это — parseInt(), parseFloat() и конструкция Number(), которая в данном случае выступает в виде обычной функции, выполняющей преобразование типов:

parseInt("1")       //1
parseInt("text")    //NaN
parseFloat("1.234") //1.234
Number("1")         //1
Number("1.234")     //1.234

Если в ходе операции с числами получается нечто, не являющееся числом (в ходе неких вычислений, или при попытке преобразования чего-либо в число), JavaScript не выдаст ошибку, а представит результат подобной операции в виде значения NaN (Not-a-Number, не число). Для того, чтобы проверить, является ли некое значение NaN, можно воспользоваться функцией isNaN().

Арифметические операции JS работают вполне привычным образом, но надо обратить внимание на то, что оператор + может выполнять и сложение чисел, и конкатенацию строк.

1 + 1      //2
"1" + "1"  //"11"
1 + "1"    //"11"

▍Строки


Строки в JavaScript представляют собой последовательности символов Unicode. Строковые литералы создают, заключая текст, который нужно в них поместить, в двойные ("") или одинарные ('') кавычки. Как уже было сказано, при работе со строковыми литералами мы можем полагаться на соответствующую объектную обёртку, в прототипе которой имеется множество полезных методов, среди них — substring(), indexOf(), concat().

"text".substring(1,3) //ex
"text".indexOf('x')   //2
"text".concat(" end") //text end

Строки, как и другие примитивные значения, иммутабельны. Например, метод concat() не модифицирует существующую строку, а создаёт новую.

▍Логические значения


Логический тип данных в JS представлен двумя значениями — true и false. Язык может автоматически преобразовывать различные значения к логическому типу данных. Так, ложными, помимо логического значения false, являются значения null, undefined, '' (пустая строка), 0 и NaN. Всё остальное, включая любые объекты, представляет собой истинные значения. В ходе выполнения логических операций всё, что считается истинным, преобразуется к true, а всё, что считается ложным, преобразуется к false. Взгляните на следующий пример. В соответствии с вышеизложенными принципами пустая строка будет преобразована к false и в результате выполнения этого кода в консоль попадёт строка This is false.

let text = '';
if(text) {
  console.log("This is true");
} else {
  console.log("This is false");
}

Объекты


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

Объекты проще всего создавать, используя синтаксис объектных литералов:

let obj = {
  message : "A message",
  doSomething : function() {}
}

Свойства объекта можно, в любое время, читать, добавлять, редактировать и удалять. Вот как это делается:

  • Чтение свойств: object.name, object[expression].
  • Запись данных в свойства (если свойство, к которому обращаются, не существует, добавляется новое свойство с указанным ключом): object.name = value, object[expression] = value.
  • Удаление свойств: delete object.name, delete object[expression].

Вот несколько примеров:

let obj = {}; // создание пустого объекта
obj.message = "A message"; // добавление нового свойства
obj.message = "A new message"; // редактирование свойства
delete object.message; // удаление свойства

Объекты в языке реализованы в виде хэш-таблиц. Простую хэш-таблицу можно создать, используя команду Object.create(null):

let french = Object.create(null);
french["yes"] = "oui";
french["no"]  = "non";
french["yes"];//"oui"

Если объект нужно сделать иммутабельным, можно воспользоваться командой Object.freeze().

Для перебора всех свойств объекта можно воспользоваться командой Object.keys():

function logProperty(name){
  console.log(name); //имя свойства
  console.log(obj[name]); // значение свойства
}
Object.keys(obj).forEach(logProperty);

▍Сравнение значений примитивных типов и объектов


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

Переменные


В JavaScript переменные можно объявлять, используя ключевые слова var, let и const.

При использовании ключевого слова var можно объявить переменную, и, если надо — инициализировать её неким значением. Если переменная не инициализирована, её значением является undefined. Переменные, объявленные с использованием ключевого слова var, имеют функциональную область видимости.

Ключевое слово let очень похоже на var, разница заключается в том, что переменные, объявленные с ключевым словом let, имеют блочную область видимости.

Блочную область видимости имеют и переменные объявленные с помощью ключевого слова const, которые, учитывая то, что значения подобных переменных нельзя изменять, правильнее будет называть «константами». Ключевое слово const, которое «замораживает» значение переменной, объявленной с его использованием, можно сравнить с методом Object.freeze(), «замораживающим» объекты.

Если переменная объявлена за пределами какой-либо функции, её область видимости является глобальной.

Массивы


Массивы в JavaScript реализованы с использованием объектов. Как результат, говоря о массивах, мы, фактически, обсуждаем объекты, похожие на массивы. Работать с элементами массива можно, используя их индексы. Числовые индексы преобразуются в строки и используются как имена для доступа к значениям элементов массивов. Например, конструкция вида arr[1] аналогична конструкции вида arr['1'], и та и другая дадут доступ к одному и тому же значению: arr[1] === arr['1']. В соответствии с вышесказанным, простой массив, объявленный командой let arr = ['A', 'B', 'C'], представляется в виде объекта примерно следующего вида:

{
  '0': 'A',
  '1': 'B',
  '2': 'C'
}

Удаление элементов массива с использованием команды delete оставляет в нём «дыры». Для того чтобы избежать этой проблемы, можно использовать команду splice(), но работает она медленно, так как, после удаления элемента, перемещает оставшиеся элементы массива, фактически, сдвигая их к началу массива, влево.

let arr = ['A', 'B', 'C'];
delete arr[1];
console.log(arr); // ['A', empty, 'C']
console.log(arr.length); // 3

Методы массивов позволяют легко реализовывать такие структуры данных, как стеки и очереди:

// стек
let stack = [];
stack.push(1);           // [1]
stack.push(2);           // [1, 2]
let last = stack.pop();  // [1]
console.log(last);       // 2
// очередь
let queue = [];
queue.push(1);           // [1]
queue.push(2);           // [1, 2]
let first = queue.shift();//[2]
console.log(first);      // 1

Функции


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

Существует три способа объявления функций:

  • Классическое объявление функции (Function Declaration или Function Statement).
  • Использование функциональных выражений (Function Expression), которые ещё называют функциональными литералами (Function Literal).
  • Использование синтаксиса стрелочных функций (Arrow Function).

▍Классическое объявление функции


При таком подходе к объявлению функций действуют следующие правила:

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

Вот как выглядит классическое объявление функции:

function doSomething(){}

▍Функциональные выражения


При использовании функциональных выражений нужно учитывать следующее:

  • Ключевое слово function уже не является первым словом в строке объявления функции.
  • Наличие имени у функции необязательно. Возможно применение как анонимных, так и именованных функциональных выражений.
  • Команды вызова таких функций должны следовать за командами их объявления.
  • Такую функцию можно запустить сразу же после объявления, воспользовавшись синтаксисом IIFE (Immediately Invoked Function Expression — немедленно вызываемое функциональное выражение).

Функциональное выражение выглядит так:

let doSomething = function() {}

▍Стрелочные функции


Стрелочные функции, по сути, можно считать «синтаксическим сахаром» для создания анонимных функциональных выражений. Надо отметить, что у таких функций нет собственных сущностей this и arguments. Объявление стрелочной функции выглядит так:

let doSomething = () = > {};

▍Способы вызова функций


Функции можно вызывать различными способами.

Обычный вызов функции


doSomething(arguments)

Вызов функции в виде метода объекта


theObject.doSomething(arguments)
theObject["doSomething"](arguments)

Вызов функции в виде конструктора


new doSomething(arguments)

Вызов функции с использованием метода apply()


doSomething.apply(theObject, [arguments])
doSomething.call(theObject, arguments)

Вызов функции с использованием метода bind()


let doSomethingWithObject = doSomething.bind(theObject);
doSomethingWithObject();

Функции можно вызывать с большим или меньшим количеством аргументов, чем то количество параметров, которое было задано при их объявлении. В ходе работы функции «лишние» аргументы будут просто проигнорированы (хотя у функции будет доступ к ним), отсутствующие параметры получат значение undefined.

У функций есть два псевдо-параметра: this и arguments.

▍Ключевое слово this


Ключевое слово this представляет собой контекст функции. Значение, на которое оно указывает, зависит от того, как была вызвана функция. Вот какие значения принимает ключевое слово this в зависимости от способа вызова функции (они, с примерами кода, конструкции из которых используются здесь, описаны выше):

  • Обычный вызов функции — window/undefined.
  • Вызов функции в виде метода объекта — theObject.
  • Вызов функции в виде конструктора — новый объект.
  • Вызов функции с использованием метода apply()theObject.
  • Вызов функции с использованием метода bind()theObject.

▍Ключевое слово arguments


Ключевое слово arguments — это псевдопараметр, который даёт доступ ко всем аргументам, использованным при вызове функции. Он похож на массив, но массивом не является. В частности, у него нет методов массива.

function reduceToSum(total, value){
  return total + value;
}
  
function sum(){
  let args = Array.prototype.slice.call(arguments);
  return args.reduce(reduceToSum, 0);
}
sum(1,2,3);

Альтернативой ключевому слову arguments является новый синтаксис оставшихся параметров. В следующем примере args — это массив, содержащий всё, что передано функции при вызове.

function sum(...args){
  return args.reduce(reduceToSum, 0);
}

▍Оператор return


Функция, в которой отсутствует выражение return, возвратит undefined. Используя ключевое слово return, обращайте внимание на то, как работает механизм автоматической вставки точки с запятой. Например, следующая функция вернёт не пустой объект, а значение undefined:

function getObject(){ 
  return 
  {
  }
}
getObject()

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

function getObject(){ 
  return {
  }
}

Динамическая типизация


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

function log(value){
  console.log(value);
}
log(1);
log("text");
log({message : "text"});

Для выяснения типа данных, хранящихся в переменной, можно использовать оператор typeof():

let n = 1;
typeof(n);   //number
let s = "text";
typeof(s);   //string
let fn = function() {};
typeof(fn);  //function

Однопоточная модель выполнения


Среда выполнения JavaScript является однопоточной. Это, в частности, выражается в невозможности одновременного выполнения двух функций (если не учитывать возможности асинхронного выполнения кода, которые мы тут не затрагиваем). В среде выполнения имеется так называемая очередь событий (Event Queue), хранящая список заданий, которые нужно обработать. Как результат, для однопоточной схемы выполнения JS несвойственна проблема взаимных блокировок ресурсов, поэтому тут не нужен механизм блокировок. Однако, код, попадающий в очередь событий, должен выполняться быстро. Если перегрузить тяжёлой работой, в браузерном приложении, главный поток, страница приложения не будет реагировать на воздействия пользователя и браузер предложит закрыть эту страницу.

Обработка исключений


В JavaScript имеется механизм для обработки исключений. Работает он по вполне обычному для подобных механизмов принципу: код, который может вызвать ошибку, оформляют с использованием конструкции try/catch. Сам код находится в блоке try, ошибки обрабатываются в блоке catch.

Интересно отметить, что иногда JavaScript, при возникновении внештатных ситуаций, не выдаёт сообщений об ошибках. Это связано с тем фактом, что JS не выбрасывал ошибки до принятия стандарта ECMAScript 3.

Например, в следующем фрагменте кода попытка изменения «замороженного» объекта завершится неудачно, но исключение выдано не будет.

let obj = Object.freeze({});
obj.message = "text";

Некоторые из «молчаливых» ошибок JS проявляются в строгом режиме, включить его можно, воспользовавшись конструкцией "use strict";.

Система прототипов


В основе таких механизмов JS, как функции-конструкторы, команда Object.create(), ключевое слово class, лежит система прототипов.
Рассмотрим следующий пример:

let service = {
 doSomething : function() {}
}
let specializedService = Object.create(service);
console.log(specializedService.__proto__ === service); //true

Здесь, для создания объекта specializedService, прототипом которого нужно было сделать объект service, использована команда Object.create(). В результате оказывается, что метод doSomething() можно вызвать, обратившись к объекту specializedService. Кроме того, это означает, что свойство __proto__ объекта specializedService указывает на объект service.

Создадим теперь похожий объект с использованием ключевого слова class:

class Service {
 doSomething(){}
}
class SpecializedService extends Service {
}
let specializedService = new SpecializedService();
console.log(specializedService.__proto__ === SpecializedService.prototype);

Методы, объявленные в классе Service, будут добавлены в объект Service.prototype. Экземпляры класса Service будут иметь тот же прототип (Service.prototype). Все экземпляры будут делегировать вызовы методов к объекту Service.prototype. В результате оказывается, что методы объявляются лишь один раз, в Service.prototype, после чего «наследуются» всеми экземплярами класса.

▍Цепочка прототипов


Объекты могут быть «наследниками» других объектов. У каждого объекта есть прототип, методы которого ему доступны. Если попытаться обратиться к свойству, которого нет в самом объекте, JavaScript приступит к его поиску в цепочке прототипов. Этот процесс будет продолжаться до тех пор, пока свойство не будет найдено, или пока поиск не достигнет конца цепочки.

О функциональном программировании в JavaScript


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

Замыкание — это внутренняя функция, у которой есть доступ к переменным, объявленным внутри родительской функции, даже после выполнения родительской функции.

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

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

Итоги


Мощь JavaScript кроется в его простоте. Понимание базовых механизмов языка позволяет программисту, использующему JS, эффективнее применять эти механизмы и закладывает фундамент для его профессионального роста.

Уважаемые читатели! Как вы думаете, какие особенности JavaScript вызывают больше всего проблем у новичков?

RUVDS.com

929,00

RUVDS – хостинг VDS/VPS серверов

Поделиться публикацией

Похожие публикации

Комментарии 19
    +3
    Так делать плохо:
    var a = new Number(10)
    

    потому что
    var a = new Number(10)
    var b = new Number(10)
    console.log(a != b)
    // true
    

    Но если не хочется полагаться на «автоматическое поведение системы», то лучше так
    var a = Number(10)
    

      0
      Так делать плохо:


      Это не плохо, если знаешь зачем так делать.
      Создается не примитивное значение а объект.
        +2
        Напишите хоть один пример, когда так делать лучше чем:
        var a = Number(10)
        

        ?
      • НЛО прилетело и опубликовало эту надпись здесь
          0
          Да уж, вдобавок:

          typeof new Number(10); // "object"
          typeof Number(10); // "number"
          
          +1
          А Symbol больше не примитив? Первый абзац.
            0
            Не структурированная статья, к примеру в абзаце про объекты появились функции… Много неточностей в каждом абзаце. Смысл делать перевод статьи такого уровня?
              –1
              В оригинале все нормально, это перевод хромает.
              0
              Вот прямо с порога:
              Возможно, вы уже знаете, что в JS значение выражения 0.1 + 0.2 не равно 0.3.
              Удивился, неужто freecodecamp сходит с ума?

              Заглянулв в оригинал понял, что это перевод такой крутой.
              Дальше можно не читать.
                +2
                а как будет правильно?
                  +1
                  Но ведь автор и переводчик прав. См.
                  Скрытый текст
                  image

                  И это немного напрягает т.к. в подавляющем большинстве случаев работа с числами в стиле fixed приводит к ожидаемым результатам и разработчик немного расслабляется
                    +2
                    maxfarseer, apapacy

                    Вижу, что вам не лень вбить пример в консоль, но заглянуть в оригинал видимо лень.
                    Ок, я принес сюда:
                    As you may already know, 0.1 + 0.2 does not make 0.3. But with integers, the arithmetic is exact, so 1+2 === 3 .

                    тут же для сравнения перевод:
                    Возможно, вы уже знаете, что в JS значение выражения 0.1 + 0.2 не равно 0.3. В то же время, при работе с целыми числами таких проблем не наблюдается, то есть, 1 + 2 === 3.

                    Разницу не замечаете?

                    Java: ideone.com/X7wB53 (на всякий случай уточню, это не JavaScript, а та Java, большая)
                    Паскаль: ideone.com/hsfMkZ
                    Ruby: ideone.com/KJXLZw
                    Rust: ideone.com/WOB5D6

                    Они все сговорились, или просто придерживаются стандарта IEEE 754 при представлении чисел двойной точности?

                    Авторы с freecodecamp знают, что придерживаются. А переводчик строит фразу так, что человек, который только знакомится с языком подумает «этот глючный js не может два числа сложить, ха-ха!». При том что статья как-бы и нацелена на начинающих.
                      0
                      Я спрашивал просто про перевод, думал может make не так переводится в данном случае или какая-то еще тонкость языка, так как я бы так же фразу перевел.

                      p.s. про то, что там не 0.3 и почему — речи нет, это понятно. Я именно переводом интересовался и спросил, как бы этот абзац перевели вы.
                        +4
                        В оригинале не "в JS значение выражения 0.1 + 0.2 не равно 0.3", а "везде значение выражения 0.1 + 0.2 не равно 0.3"
                        Многие почему-то думают, что эта проблема специфична только для js
                          0
                          Супер, не заметил. Спасибо за пояснение.
                  0
                  В JavaScript имеется лишь один тип чисел — это числа двойной точности с плавающей запятой.

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


                  С введением всеобщей поддержки typed arrays и asm.js это поведение теперь доступно для пользовательского кода явно. Например, x>>>0 приведет x к unsigned int32, а x|0 приведет к signed int32. В дальнейшем компилятор будет исходить из этого предположения типа, пока код не деоптимизируется. С TypedArray все еще проще в плане приведений типов.

                    +2
                    Стрелочные функции, по сути, можно считать «синтаксическим сахаром» для создания анонимных функциональных выражений. Надо отметить, что у таких функций нет собственных сущностей this и arguments. Объявление стрелочной функции выглядит так:

                    let doSomething = () = > {};


                    Это всё, что надо знать новичку про стрелочные функции?

                    В общем, главная проблема новичков — это статьи для новичков. Не стоит терять на них время.
                      +1
                      Особенно нравится, что стрелочные функции — это, оказывается, тоже синтаксический сахар)
                      Хорошо, что хоть let и const синтаксическим сахаром не назвали, ага)
                      0
                      Чаще всего у новичков возникает вопрос — как и куда вставить свой код, функцию. Как разбить программу на файлы и т.д. будь то веб или как еще разработка. В начальных книгах очень много времени отдано реальному изучению самого языка, но я думаю, что нехватает и небольшой книжки, что то вроде — область применения, с подробным описаниме возможностей внедрения скриптов, написания каких то программ или серверов для примера, потому что, вроду когда гуглишь все можно найти, но иногда проще уже гуглить, когда тебя наталкивают на какие то более рациональные мысли. А вообще спасибо вам всем, люблю читать habr.

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

                      Самое читаемое