Java и JavaScript — это не одно и тоже! Теперь вы знаете главную тайну масонов. В этой статье я хочу поделиться с вами мыслями и соображениями о языке JavaScript через глаза Java — разработчика. Проведу сравнение структур данных, типов, функций, объектов, классов и других общих возможностей языков Java и JavaScript.
Фундамент
JavaScript — это интерпретируемый, скриптовый язык для написания сценариев. Это значит, что тот код, который вы напишите, будет выполняться построчно, от инструкции в инструкции, от скрипта к скрипту. Java — язык компилируемый, это значит, что перед стартом программы на Java, компилятор должен перевести весь код, написанный вами, в специальный машинный код, понятный для JVM - bytecode.
Неудобства для Java-разработчика, начавшего писать на JS, связанные с этими отличиями будут следующие:
- Перед запуском компиляции вашего кода, написанного на Java, компилятор проведет за вас синтаксический и семантический анализ, и в случае проблем — оповестит вас об этом. Получается, что вы имеете возможность узнать об ошибке еще до запуска приложения. В JS, в силу отсутствия компилятора, такой проверки нет. И ошибки, допущенные вами в момент написания кода будут обнаружены только после запуска скрипта.
- Т.к. JavaScript выполняется построчно стиль написания функций, вызывающие другие функции, будет отличаться от принципов Чистого кода «дядюшки» Боба, проповедуемого для написания программ на Java. В JS, если из функция foo() необходимо вызвать функцию bar(), вы должны определить bar() до foo(). Если сделать наоборот, как в Java — приложениях, то вызов может закончится неудачно в некоторых старых браузерах.
// Пример на Java
public class JavaVsJS {
public static void foo(String name) {
bar(name);
}
private static void bar(String name) {
System.out.println(name);
}
public static void main(String[] args) {
JavaVsJS.foo("Igor Ivanovich");
}
}
// Пример на JavaScript
var name = "Igor Ivanovich";
// Функция bar() определена до своего вызова в foo()
function bar(name) {
document.write(name);
}
function foo(name) {
bar(name);
}
foo(name);
Переменные и их типы
JavaScript — слабо типизированный язык, в отличии от Java. С одной стороны, это дает больше гибкости, с другой — больше возможностей совершить выстрел в ногу. Для объявления переменной в JS достаточно использовать ключевое слово var, после которого указать имя переменной и, если необходимо, значение. На практике же необязательно даже использовать ключевое слово var.
var name_1 = "Igor Ivanovich";
name_2 = "Renat Raphaelevich";
Теперь о типах. В JS для целочисленных и чисел с плавающей точкой нет разных типов. Они объединены в типе number.String, boolean такие же как и в Java. В JS есть тип Object. Если в Java это суперкласс всех классов, то в JS это просто один из типов.
var a_number = 10; // number
var b_number = 10.01; // number
var a_string = "10"; // string
var b_string = "10.1"; // string
var tr = true; //boolean
var person = {name: "Igor", secondName: "Ivanovich"}; //object
Java-разработчика ничего не должно смущать. Теперь давайте попробуем провести операции между объектами разных типов и посмотрим что получится.
10 +1
> 11
10 +"1"
> "101"
true && false
> false
true && 1
> 1
true && 0
> 0
true && "1"
> "1"
false && "1"
> false
false && 0
> false
0.1 + 0.7
> 0.7999999999999999
Некоторые результаты могут немного смутить Java-разработчика. Например, возможность использовать тип boolean таким образом: true && 1 и при этом получать какой-то результат. В Java невозможно провести такую операцию т.к. компилятор выдаст ошибку о том, что нельзя использовать оператор && с типами не boolean. Теперь обратим внимание на еще одно отличие JS и Java: операторы === и !==. Это операции сравнения, необходимые слабо типизированному языку, такому как JS. === — вернет true, если сравниваемые объекты равны по значению и их типы совпадают. !==, соответственно, вернет true, если сравниваемые объекты не равны по значению или их типы не совпадают. Рассмотрим несколько примеров:
10 == 10
> true
10 == "10"
> true
10 === "10"
> false
10 != "10"
> false
10 !== "10"
> true
10 !== 10
> false
Функции
В JS, так же как и в Java, функции могут возвращать/не возвращать значение, быть с аргументами и без. Вызвать функцию можно как в коде JS, как было показано в примере выше, так и реакцией на определенное событие на элементе html-разметки.
<!-- Вызов функции на элементе по событию клика на кнопку -->
<input type="button" value="CLICK" onclick="foo();">
//Описание функции foo()
function foo() {
document.write("Calling foo");
}
При нажатии на кнопку на странице будет напечатано «Calling foo» как результат вызова функции. Теперь о странностях на которые может обратить внимания Java-разработчик. Вернемся к примеру выше, где функция foo() вызывает в себе функцию bar() — которая используется только как внутренняя функция. По идеи мы ожидаем того, что ее можно сделать приватной. Но в JS нет никаких селекторов доступа. Нет возможности сделать поле private простым добавлением этого слова перед объявлением функции. Пойдем дальше. Создадим объект — экземпляр класса со своими полями и одним методом.
function getLocation() {
if (this.person === "Igor" && this.age > 25) {
document.write("Your name " + this.person + ", location = Vishnevaia 1");
} else {
document.write("Your name " + this.person + ", location = rp Sokoloviy");
}
}
function Person(person, age) {
this.person = person;
this.age = age;
this.getLocation = getLocation;
}
var igor = new Person("Igor", 26);
igor.getLocation();
document.write("<br />");
getLocation();
Если смотреть на этот код глазами Java-разработчика, то можно отметить, что функция Person — является конструктором объектов класса Person и определением полей, и методов, входящих в класс. Функция getLocation() является функцией класса Person. Внутри нее мы используем обращение к полям экземпляра класса this.person и this.age. Логично, что эта функция, использующая текущей экземпляр класса Person, должна работать только с ним и последний вызов функции getLocation() не должен работать. Но, в JS это нормально, потому что понятия класса, функции, методов класса размыты. Слабая типизация во всем. Выполнив этот скрипт вы получите следующий вывод в окне браузера:
Your name Igor, location = Vishnevaia 1
Your name undefined, location = rp Sokoloviy
Однако, переписав код следующим образом, определив функцию внутри класса, ее вызов не для экземпляра класса будет недоступен:
function Person(person, age) {
this.person = person;
this.age = age;
this.getLocation = function () {
if (this.person === "Igor" && this.age > 25) {
document.write("Your name " + this.person + ", location = Vishnevaia 1");
} else {
document.write("Your name " + this.person + ", location = rp Sokoloviy");
}
};
}
var igor = new Person("Igor", 26);
igor.getLocation();
document.write("<br />");
getLocation();
Последний вызов приведет к ошибке, т.к. функция getLocation() не определена. Получается что хоть в JS и нет модификаторов доступа, но есть область видимости функций и переменных, управляемая с помощью фигурных скобок. JavaScript прекрасен огромным количеством вариантов совершить выстрел себе в ногу.
Массивы
Когда мы говорим о массивах, то представляем структуру данных, хранящую однотипные элементы, доступ к которым осуществляется по индексу. Это в Java. Когда же дело касается JS и его слабой типизации, то в дело вступает настоящая анархия. В следующим примере мы создаем 4 массива. В первом элементы разных типов, во втором только числа, в третьем boolean, в четвертом boolean и number:
var mix = [3, "Igor Ivanovich", "Renat Raphaelevich", "Sergey Sergeevich", 1, 12.3, true];
var numbers = [1,2,3,4,5];
var booleans = [false, false, true];
var mix2 = [false, 1, 2];
document.write("Type elements in mix: ");
for (element in mix) {
document.write(typeof mix[element] + " ");
}
document.write("<br /> Type elements in numbers: ");
for (element in numbers) {
document.write(typeof numbers[element] + " ");
}
document.write("<br /> Type elements in booleans: ");
for (element in booleans) {
document.write(typeof booleans[element] + " ");
}
document.write("<br /> Type elements in mix2: ");
for (element in mix2) {
document.write(typeof mix2[element] + " ");
}
var sum = numbers[0] + numbers[1];
document.write("<br /> sum numbers = " + sum);
После выполнения скрипта мы увидим тип каждого элемента каждого массива и сумму двух первых цифр из массива чисел.
Type elements in mix: number string string string number number boolean
Type elements in numbers: number number number number number
Type elements in booleans: boolean boolean boolean
Type elements in mix2: boolean number number
sum numbers = 12
Вывод
При первом касании с языком JavaScript у Java — разработчика могут возникнуть все вышеперечисленные замечания и вопросы. При моем первом знакомстве с JS я испытывал не самые веселые эмоции. Скорее это было вот так: «Что за ...?». Многие отличия и не понимания кроются в разнице типизаций двух языков. Я не знаю зачем JS нужна слабая типизация.
Возможно, есть выгоды почему так было сделано. Если вы знаете ответ, пишите в комментариях.
Да, есть TypeScript, который вроде как является типизированным, но в итоге он же будет переведен все в тот же JS. Лично я не сторонник слабой типизации, но мой коллега, недавно попробовавший JavaScript, почему-то был в восторге от нее. Возможно, это дело вкуса. А как считаете вы, что лучше слабая или сильная типизация?
Автор: Николай Грибанов