Исторически сложилось, что за поведение интерактивных веб-страничек в конечном счёте отвечают программы, написанные на языке Javascript. Развитие веб-технологий привело даже к тому, что Javascript стали рассматривать, как одну из возможных «архитектур» ЭВМ, на которые нацеливают компиляторы разработчики языков программирования (ранее это случилось с JVM и CLR – виртуальными машинами, стоящими в основе языков Java и C#).
В случае с языком Javascript компилятор долго искать не нужно: в этой роли способен выступать любой достаточно современный браузер. Как и в случае большинства языков программирования, у Javascript есть два режима выполнения: интерактивный построчный (аналог ghci для Haskell) и неинтерактивный, исполняющий программу целиком (аналог runghc).
В качестве интерактивного режима может выступать Javascript-консоль, находящаяся среди Инструментов Разработчика (Developer Tools).
Выполнить программу целиком можно одим из следующих способов:
Включить внутрь html документа тег script. Внутри этого тега должна находиться программа на Javascript. Распознавание html для содержимого тега script отключено!
Использовать пустой тег script с аттрибутом src=путь_к_файлу
. Например, так:
<script src="foobar.js"></script>
Отметим также, что существует популярная альтернатива браузерам как Javascript-платформам. Обычно её используют тогда, когда по тем или иным причинам удобно исполнять один и тот же кусок кода как на стороне пользователя, просматривающего веб-страницу, так и на стороне сервера, обслуживающего эту страницу.
Есть замечательный ресурс на русском языке.
Наиболее актуальная информация обычно находится на сайте MDN. К сожалению, хотя переводы некоторых статей на русский язык там присутствуют, зачастую они неполные и немного устаревшие.
Javascript является слабо динамически типизированным функциональным языком. Напомним, что динамическая типизация означает, что типы проверяются только на этапе выполнения программы. Слабая же типизация означает, что даже выражения, которые не типизируются никаким адекватным образом (например, сумма числа и текста) всё равно вычисляются после неявного преобразование каких-то частей выражения к другому типу.
Отметим, что для тех случаев, когда хочется адекватной системы типов, позволяющей выражать часть инвариантов программы и проверять их на этапе компиляции, существует Typescript – система опциональной строгой статической типизации для Javascript.
Javascript получил своё название от языка Java, у которого он позаимствовал существенную часть синтаксиса (но не семантики). Язык Java, в свою очередь, синтаксис позаимствовал у C++.
Начнём с объявления переменных. Как ни странно, но это – весьма сложная часть языка. В Javascript есть три вида объявления переменных:
foo = 3; // глобальная переменная
var foo = 4; // переменная, локальная для функции
let foo = 5; // переменная, локальная для блока
Обратите внимание – объявление глобальной переменной по своему виду совпадает с операцией присваивания. Это – распространённый источник ошибок! Все var-объявления переменных, где бы они ни находились, переносятся в начало функции, их содержащей. Наконец, let-объявления ведут себя так, как нужно, но, к сожалению, поддерживаются только в версиях браузеров, появившихся не ранее 2015 года (к счастью, существуют специальные трансляторы, преобразующие современный Javascript к более древним его разновидностям).
Функции в Javascript задаются при помощи лямбда-выражений:
sayHello = function(name) { alert("Hello, " + name); };
Сразу скажем, что обе точки-с-запятой здесь необязательны: при их отсутствии компилятор сам поставит их там, где посчитает нужным. К сожалению, алгоритм расстановки точек-с-запятой довольно примитивен и неотключаем. Это приводит к проблемам вида:
addNumbers = function(x,y) {
return // точка-с-запятой будет поставлена здесь!
x+y;
};
или же
something = function(x) {
var foo = 2
( generateFoo(foo) )(x) // эта строчка является потенциальным продолжением предыдущей
// поэтому точка-с-запятой в конце предыдущей строчки поставлена не будет
}
Также допустимы объявления функций вида
function foo(x,y,z) { ... }
Все такие объявления обрабатываются компилятором на этапе чтения программы, поэтому, в частности, корректно работает следующее:
sayHello(); // код может встречаться и вне функций!
function sayHello() { ... }
Управляющие конструкции if
, while
, for
, switch
, break
, continue
, return
в Javascript работают почти полностью аналогично соответствующим конструкциям из C. Команда goto
отсутствует, зато есть возможность помечать циклы метками и выходить сразу из нескольких циклических уровней:
outer: for (var i = 0; i < 10; i += 1) {
for (var j = 0; j < 10; j += 1) {
console.log(i+j);
if (i > j) {break outer;}
}
}
В первом приближении в Javascript есть 6 «типов» данных: boolean
, null
, undefined
, number
, string
, object
.
Тип boolean
содержит два значения: true
и false
. Тем не менее, любой объект может быть преобразован к этому типу. Ложными считаются: false
, число 0, число -0, число NaN
, null
, undefined
, пустой текст. Всё остальное считается истинным.
Типы null
и undefined
содержат по одному (одноимённому) значению. Различие между ними следующее: предполагается, что null
– вполне корректное значение, которое программист может использовать в своих целях, а undefined
– обычно признак ошибки в коде.
Числа в Javascript бывают только одного типа – аналога double
из языка C. В связи с этим с ними нужно соблюдать осторожность: арифметика с плавающей точкой порой значительно отличается по поведению от арифметики целых чисел.
Тексты типа string
представляют собой неизменяемые массивы символов в кодировке UTF-16. К отдельным символам можно обращаться при помощи квадратных скобок или же метода charAt
(например "foobar".charAt(2)
). Длина текста хранится в поле length
("foobar".length
равно 6). Текстовые литералы можно обозначать как одинарными, так и двойными кавычками – разницы между ними нет.
Всё остальное (в том числе функции и массивы) считаются представителями типа object
. Поскольку объекты бывают изменяемыми, очень важно понимать семантику операции присваивания. В Javascript присваивание копирует указатель на объект (в точности, как и в Java):
a = [1,2,3];
b = a;
b[1] = 4; // теперь a[1] -- тоже 4, поскольку b и a указывают на один и тот же массив
В заключение пара слов о массивах. Литералы массивов выделяются с двух сторон квадратными скобками: ровно так же, как и списки в Haskell. Индексируются массивы с нуля. Длина массива хранится в поле length
. Доступ к элементам осуществляется при помощи квадратных скобок. Наиболее простой способ скопировать массив: воспользоваться методом slice
(foobar.slice()
создаёт копию массива foobar
). Метод push
позволяет добавить элементы в конец массива (например, foobar.push(1,2,3)
добавляет элементы 1, 2, 3 в конец массива foobar
).
Отдельный раздел мы посвятим операторам сравнения ==
и ===
(и их отрицаниям !=
и !==
). Различие между ними простое: ===
проверяет только равенство внутри одного типа; объекты разного типа он считает разными; ==
дополнительно производит преобразование типов. Поскольку запомнить семантику оператора ==
весьма сложно, не рекомендуется его использовать. Поэтому мы опишем только действие ===
.
null
равно null
, то же самое – с undefined
[1,2,3]
не равен [1,2,3]
!@ 2016 arbrk1, all rights reversed