java2js.md 40 KB

Javascript для Jav-истов

JAVA !== JAVASCRIPT && JAVA != JAVASCRIPT

Отладка

Вместо IDE есть Developer Tools В браузере.

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

Для начала пошаговой отладки устанавливается точка останова - строка кода, с которой обычный режим выполнения прерывается и начинается отладка по шагам. В Developer Tools найдите вкладку Source, в ней свой файл c кодом, и кликните на номере нужной строки слева от текста кода.

Если вы используете repl.it, другие онлайн-IDE или console, то у вас будут определенные сложности с нахождением вашего кода и строки в нём. Поэтому вы можете вставить ключевое слово debugger в ваш код - это работает так же, как точка останова на строке в Developer Tools.

Отладка по шагам.

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

  • Step over next function call (F10) - следующий шаг/оператор в вашем коде. После выполнения каждой команды вы можете ознакомится со значениями переменных, наведя на них курсор мыши, написав их в консоли, или же используя вкладку Watch
  • Resume script execution (F8) - переход из режима отладки по шагам в обычный режим выполнения кода. Таким способом вы пропускаете хорошо отлаженные части кода. Этот режим может быть прерван следующей точкой останова или ключевым словом debugger в коде.
  • Step into next function call (F11) - сделать следующий шаг в функцию, "зайти" в неё.
  • Step out of current function (Shift-F11) - следующий шаг из функции, выйти из функции на уровень, который функцию вызвал.

Значение переменных и выражений

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

Пошаговая отладка и консоль.

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

Отладочный вывод

Вы всегда можете добавить console.log в место, где хотите получить информацию о состоянии программы. Этот способ хорош как дополнение к остальным. Так же вы можете написать определенное условие для отладки, вставить в него console.log и поставить точку останова.

Примеры ниже

...могут быть запущены в отладочном режиме по красной кнопке :-), если вы откроете Developer Tools. Без Developer Tools они тоже работают.

Подключение

"Корнем" HTML-документа является файл html, в котором указываются остальные части страницы - картинки, css, js файлы и код.

Подключение отдельных файлов и библиотек

<script src="/path/to/script.js"></script>

Подключение кода inline.

<script>
alert("AAaa");
</script>

use strict

Javascript-интерпретатор в браузере может работать в двух режимах: "обычном" и в "строгом":

  • Обычный режим предназначен для совместимости.
  • Строгий режим включает современный стандарт Javascript (ECMAScript 5 или новее)

В зависимости от режима поведение интерпретатора может меняться. Далее это будет упоминаться в тех или иных моментах. По умолчанию интерпретатор работает в обычном режиме, для включение строгого режима используется строка 'use strict' в начале Javascript-кода.

"use strict";

или

'use strict';

Семантика и её отличия от Java.

  • В Java код может исполняться многопоточно (однако каждый поток работает изолированно) и синхронно, т. е. код выполняется в том порядке, в котором он написан; в коде никаких фоновых и непоследовательных операций обычно не предусмотрено, ввод-вывод (запись и чтение файлов, сетевые запросы, запросы к СУБД) происходят в блокирующем режиме, т. е. Java-код останавливает выполнение и ожидает отправки или приема той или иной порции данных.
  • В JS код исполняется однопоточно (за редким исключением), и асинхронно, т. е. код представляет из себя не единую последовательность действий, а набор функций, которые могут исполнятся в произвольном порядке в качестве обработчиков событий на странице, событий, связанных с временными задержками или сетевых событий. Однако надо учитывать, что код, расположенный в функции, обычно работает синхронно и однопоточно, никакой другой обработчик события не может работать, пока работает другой код. Асинхронными являются только вызовы обработчиков событий из движка браузера, когда же вы вызываете функцию в своем коде, весь ваш код работает синхронно. Ввод-вывод так же происходит в асинхронной манере - вместо ожидания данных, интерпретатор освобождается для обработки других событий; по окончанию операции ввода-вывода вызывается соответствующая функция, обрабатывающая результаты.

Данные отличия приводят к сложности написания, отладки и понимания JS кода. Стандартные ошибки:

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

Семантические последствия и особенности JS.

  • Язык унаследовал некоторые черты функциональных языков и языков высокого уровня абстракции (Scheme, Common Lisp)
  • Функциональный подход превалирует над "обычным" ООП-подходом, привычным после PHP/C++/Java.
  • Функции являются объектами первого класса, т. е. могут быть сохранены как переменные и переданы/возвращены в/из других функций.
  • Объект и ассоциативный массив - это одно и то же.
  • В ассоциативных массивах наряду с другими типами данных можно хранить функции, что превращает массивы в объекты. (инкапсуляция)
  • Функции можно заносить в объекты, запускать как методы, или как отдельные функции. this обычно равен объекту, указанному до ., однако есть возможность передать функции в качестве this любой объект. По факту this - еще один специфический параметр функции.
  • Можно менять и переопределять структуру почти всех объектов, включая стандартные, что позволяет кардинально менять их поведение.(полиморфизм) Таким образом можно сделать аналог примесей, интерфейсов и множественного наследования.
  • В JS реализовано так называемое прототипное ООП: вместо указания предка и обработки наследования на уровне интерпретатора, в JS в объекте есть ключ prototype, который является ссылкой на другой объект-прототип. В случае, когда тот или иной ключ (имя метода или поля) в объекте не найден, JS ищет ключ в объекте-прототипе, и далее по цепочке прототипов. Таким образом реализуется наследование.
  • private, protected и public нет.
  • Функция исполняется одновременно в динамическом (значение параметров при вызове) и лексическом (переменные из областей видимости декларации функции) контекстах. Таким образом реализуются замыкания: переменные области видимости завершенной функции сохраняются для использования функциями, определенными внутри, в случае если внутренние функции были переданы "наружу" как результат работы функции или другими способами остались существовать после окончания внешней функции. Таким образом реализуются аналог private полей объекта, а так же сохраняется контекст для отложенных операций или обработчиков событий. Замыкания являются объектами в общем смысле этого слова, так как инкапсулируют код и данные.
  • Синтаксис схож с Java, но это обманчивое сходство.
  • Язык с нестрогой динамической типизацией, т. е. переменные типа не имеют, однако тип имеют значения в этих переменных.
  • Структуры резиновые и могут быть изменены в любой момент

Типы данных

  • Целые и дробные числа представлены едиными типом Number
  • Под "массивом" в JS подразумевается массив с целочисленными ключами.
  • Ассоциативные массивы и объекты - это одно и то же;
  • Для переменных без значения и значений отсутствующих ключей в ассоциативных массивах вместо NULL используется аналогичный тип undefined
  • Так же существует тип null, который применяется программистами для задания "пустых" значений и/или в DOM.

;

В отличие от Java и многих других языков с C-подобным синтаксисом, в JS точка с запятой не является обязательной, однако нужна в некоторых случаях, например при написании операторов в одну строку:

var a = 5
var b = 6
var c = "string"
var d

a ++; b += a; d = c + a;

Во избежание ошибок просто добавляйте ; "как обычно", в конце строки

Комментарии

Как в Java.

Строки, переменные в них и конкатенация

alert(`случайное число ${Math.random()}`)
alert('ОДинарные кавычки ' + Math.random())
alert("Двойные ничем не отличаются " + Math.random())

Строки иммутабельны, как в Java в них есть методы.

+ и динамическая типизация.

Так как в JS нет отдельного оператора конкатенации, то + между числами в строках может вас удивить: суть

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

  • +"123". Простой и краткий способ для приведения строки к числу
  • parseInt("123") или parseFloat("123.45") работает схожим образом, однако обладает другими странностями и возможностями (например есть возможность задать систему счисления)

Операторы, условия, циклы.

Вызов функций

Большинство кода состоит из тех или иных вызовов функций. В JS они выглядят почти так же:

alert("as in Java");
alert("as in JS, without semicolon")

Во второй строке примера нет ;, в этом невеликое отличие.

Операторы

В большинстве своем повторяют обычный набор PHP, C, Java и так далее (+, -, *, /, %, ++, --, += ...).

&& и ||.

В отличие от Java, в JS И и ИЛИ возвращают оригинальное значение операнда, а не булевский результат логического оператора.

||

Результат || становится очевиден после первого же значения, интерпретируемого как true. ИЛИ слева направо поочередно приводит операнды к Boolean и возвращает первый операнд, интерпретируемый как true. Если такого операнда нет, ИЛИ возвращает последний (правый) операнд. Результатом оператора является не приведенное к типу Boolean значение, а оригинальное значение операнда

&&

Результат && становится очевиден после первого же значения, интепретируемого как false. И слева направо поочередно приводит операнды к Boolean и возвращает первый операнд, интерпретируемый как false. Если такого операнда нет, И возврщаает последний (правый) операнд. Результатом оператора является не приведенное к типу Boolean значение, а оригинальное значение операнда

Приведение к типу Boolean

Как false интерпретируются:

  • false
  • 0 // 0 как число
  • "" //пустая строка
  • null
  • undefined
  • NaN

Как true интерпретируются все остальные значения, в том числе:

  • Infinity
  • -Infinity
  • "0" //строка не пуста. однако +"0" уже 0 как число, а значит false
  • {} //пустой объект - всё равно true
  • [] //пустой массив [] == false, но в остальных случаях работает как true

Для проверки используйте !!, то есть двойное отрицание: !!null равен false, таким образом мы можем почти всегда проверить как интерпретируется то или иное значение.

В общем случае объект является true, за исключением null и [] == false

Примеры

2
1+1
2*1
// bool type cast
!!2
!!0
!!1
// or
2 || 1
2 || 0
//and
2 && 1
1 && 2
0 && 2
// or and and difference
0 || 1 || 2
0 && 1 && 2
2 || 1 || 0
2 && 1 && 0
//null, undefined, so on
null || 2
undefined && 1
//brackets and complex expressions
(undefined || 2) && (2 || 0)
(2 && 1) || (null && 0)
(2 > 1) && "greater"
(2 < 1) && null
null && (2 < 1)
// ternary operator
1 ? "one" : "not one"
0 ? "zero" : "not zero"
"0" ? "\"zero\"" : "not `zero`"
parseInt("0") ? 'true' : 'false'
("" || 2) && (3 || "3.5") || (4 && 5)
(-1 + 1) && "zero"
"-1" + 1 && "oups"
(typeof null === 'object') ? "null is object" : "null is null"
// ternary && ||
Math.random() < 0.5 && 'less' || 'more'
(a = Math.random()) < 0.5 && 'less: '+a || 'more: '+a
//in for array
[2,3,5,7,11].indexOf(7) > -1 ? 'prime' : 'not found'
'random' in Math
'indexOf' in []
var a = b = c = d = 5;

Java ведет себя по-другому:

2 || "aaa";// bool(true)
0 && "aaa";// bool(false)

Условия if и switch.

Работают аналогично Java.

Циклы

for ... in

var car = {brand: "Lada",
           'model': "2101"};

for (var key in car){
    alert(key + ": " + car[key]);
}

Как можно заметить, при этом в цикле нет переменной со значенинем, а только с ключем, по которому можно в цикле получить значение из итерируемого массива.

for, while и do-while

работают так же как в Java:

for (var i=0;i<10;i++){
    console.log(i);
}

Типы данных и Объектная модель.

Обращение к полям и методам объектов JS

Как и в Java, через точку ..

Числа.

Кроме того, что целые и дробные числа объединены в единый тип Number, особых отличий с Java нет:

console.log(1/0); //Infinity
console.log(-1/0);//-Infinity
console.log(1/"asdf");//NaN

Приведение к числу из строки:

console.log(+"aaa100500"); //NaN
console.log(+"100500"); //100500

Также JS не выбрасывает исключение "Деление на ноль", а просто возвращает бесконечность.

Число как объект

С числами можно работать как с объектами:

console.log(5.123456.toFixed(2)); // "5.12"

Тем не менее, многие математические операции вынесены в глобальный объект-коллекцию Math (например, Math.round, Math.ceil, Math.random и другие)

Строки

Большинство строковых операций являются методами объекта-строки:

console.log("12345".length); //5
console.log("aBcDe".indexOf("D")); // 3
console.log("123string456".substr(3,6)); // "string"

Boolean

Работает как в Java

undefined

Аналог null в Java. В этом типе есть только одно значение - undefined

null

null - это "undefined для программиста", а не для интерпретатора. Так же используется в DOM для пустых/незаданных значений.

Массивы

Под массивом подразумевается нумерованный целочисленными индексами массив. Массив резиновый и нетипизированный.

var a1 = [1,2,3]
var a2 = new Array(4,5,6)

alert(a1[1]) // 
a1.push(5) 
alert(a1.indexOf(3)) // 
alert(a2.length) // 

//Можно добавить именованное поле в обычный массив как и в любой другой объект JS, но...

a2.someKey = 15;
a1["someOtherKey"] = 25;

//от этого они не появятся среди других нумерованных элементов

alert(a2);
alert(a1);

//но будут доступны наряду со стандартными полями объекта Array (length, indexOf и другие)
alert(a2["someKey"]);
alert(a1.someOtherKey);

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

Ассоциативные массивы.

В JS ассоциативные массивы и объекты - это одно и тоже. Так же любой объект, даже встроенный, может быть изменен в любой момент.

var person = {
    name: "Ivan",
    surname: "Ivanovv",
    "fatherName": "Petrovich",
}

typeof person

Нет разницы, определять ключи литерально или через строку (в кавычках "fatherName").

person.fatherName
person["name"]

Обратите внимание, что person.fatherName работает так же как и person["name"], несмотря на то, что определены наоборот.

Для обращения через ключ в переменной используется нотация с квадратными скобками:

var key = "surname";

person[key]
person.key

Если просто написать person.key, то JavaScript будет искать ключ key литерально, а не по значению переменной key ("surname")

Вы можете определить новый элемент массива просто присвоив ему значение:

person.age = 98;

person

Также можно создавать массив через конструктор Object:


var a = new Object();
a.name = "Petr"
a.surname = "Petrov";
a["age"] = 17;

Получить ключи ассоциативного массива можно с помощью функции Object.keys:

Object.keys(person)

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

var tree = {
    name: "Ivan",
    fatherName: "Petrovich",
    surname: "Ivanov",
    children: [
        {
            name: "Maria",
            fatherName: "Ivanovna",
            surname: "Ivanova",
        },
        {
            name: "Petr",
            fatherName: "Ivanovich",
            surname: "Ivanov",
        },
        {
            name: "Nikolay",
            fatherName: "Ivanovich",
            surname: "Ivanov",
        },
    ]
}

Нет сложностей с циклическими ссылками:

var a = {}
var b = {}
a.b = b
b.a = a

b.b = b
a.a = a

a.name = "A"
b.name = "B"

for (var i=0,child=tree.children[i];     i<tree.children.length;  i++,child=tree.children[i]){
    child.father = tree;
}

in

Ключевое слово in используется для двух задач:

  • проверка наличия ключа в ассоциативном массиве
"fatherName" in a
"age" in person
  • конструкция for (var key in arr) для перебора всех элементов ассоциативного массива
for (var key in person){
    console.log(key+": "+person[key]);
}

Задание

Нарисовать HTML таблицу из двух колонок, в которой слева будут ключи, а справа - значения:

nameIvan
surnameIvanovv
fatherNamePetrovich

Функции

В семантике JS функциям отведена огромная роль:

Они могут использоваться

  • Как обычные функции в процедурном стиле написания кода
  • Как объекты первого класса, т. е. как переменные. Это позволяет их передавать в другие функции, сохранять в переменных и так далее:
    • Функциональный подход (filter, map, reduce, sort)
    • Функции, сохраненные в ассоциативном массиве становятся методами
    • Асинхронное программирование - обработчики событий и длительных операций ввода-вывода передаются в виде функций.
    • Замыкания.

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

Основные синтаксические различия c Java

Основные синтаксические различия касаются параметров функции:

  • В функциях в JS нет параметров по умолчанию (в ES6 - есть)
  • Набор параметров при вызове функции может отличаться от набора параметров при декларации, и это не вызывает ошибок
  • Вы всегда можете узнать актуальный набор параметров в форме псевдомассива arguments.
function add2(a,b){
    if (typeof a === 'undefined'){
        a = 0;
    }
    b = b || 0;
    return a + b;
}
alert(add2());
alert(add2(5));
alert(add2(5,7));
function add(){
    var result = 0;
    for (var i=0;i<arguments.length;i++){
        result += arguments[i];
    }
    return result;
}
alert(add());
alert(add(5,6,7));
alert(add(1,2,3,4,5,6,7));

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

function someJQueryPlugin(options){
    alert(options.size);   
    alert(options.selector);   
}

someJQueryPlugin({size: 15, selector: "#id"});

Определение функций

Function Declaration

Функцию можно определить "как обычно"

a("Hello");

function a(text){
    alert("function a: " + text);
}

var b = a; //сохраняем функцию в переменной b
b("Hi"); //работает

a = alert;
a("Hello"); //a заменена встроенной функцией alert

a = b;      //возвращаем изначальную функцию в a

Обратите внимание, что:

  • Имя функции - это такая же переменная что и все остальные переменные
  • При определении "как обычно" (Function Declaration) вы получаете возможность вызывать функцию до её определения.

Function Expression

var func = function(a){
    alert(a);
};

func("Hello");

В примере выше написано выражение, которое создает анонимную функцию. И эта функция связывается с переменной func. В дальнейшем её можно использовать так же как и в Function Declaration, за одним исключением - нельзя обращаться к переменной до её определения:

func("Hello"); //ошибка, переменная func не определена.
var func = function(a){
    alert(a);
};

Self-Invoked Function

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

"abcdef".indexOf("f") //создаем строку и тут же используем её
{"name": "Ivan", surname: "Petroff"}.name // создаем ассоциативный массив и тут же достаем из него поле name

(function(param){
    alert("param: " + param);
})("lalala"); //создаем функцию и тут же её вызываем с параметром "lalala"

Обычно это используется для создания обособленной области видимости и/или замыканий.

call и apply и чуть-чуть ООП.

Эти два метода объекта Function позволяют вызвать функцию, указав this и параметры:

function Person(name, surname){
    this.name    = name
    this.surname = surname

    this.getFullName = function(){
        return this.name + (this.fatherName ? " " + this.fatherName + " " : " ") + this.surname
    }
}

function say(greet, aftergreet){
    alert(greet + " " + this.getFullName() + " " +  aftergreet)
}

var person        = new Person("Ivan", "Petroff")

say.call(person,"Hello", "!!111!!!")
say.apply(person,["Hi", "by apply"]) //feel the difference

То, что apply принимает массив в качестве набора параметров функции помогает творить чудеса:

var someArray = [1,5,7,-17,100500];

Math.min(someArray) //Math.min так не умеет
Math.min(1,5,7,-17,100500,-123) // зато умеет так
Math.min.apply(Math,someArray) // ...и мы этим воспользуемся...

// или же сделаем свой min в someArray:

someArray.min = function(){
    return Math.min.apply(Math,someArray);
}

someArray.min()

// или же сделаем свой min для всех массивов 

Array.prototype.min = function(){ 
    return Math.min.apply(Math, this) 
};



[100, 500,  -100500].min()

Замыкания, приватные методы и данные и функциональное ООП.

Функция в JS находится сразу в двух контекстах: динамическом (this, значения параметров) и лексическом (переменные из более высоких областей видимости в месте определения функции). Динамический контекст - это контекст вызова функции - значение параметров и окружение на момент вызова; лексический контекст - контекст определения функции, её вложенности в другие области видимости, доступ к которым функция имеет и после окончания выполнения функций-владельцев этих областей видимости.

Let Over Lambda (LOL)

{
    let randomValue = Math.random()
    var getRandomValue = () => randomValue
}
alert(getRandomValue())

makeAdder

function makeAdder(x){
    function adder(y){
        return x + y;
    }
    return adder
}

var add5 = makeAdder(5);
var greeter = makeAdder("Hi, ");

alert(add5(2));
alert(greeter("John"));

В примере выше 5,"Hi, ", 2 и "John" находятся в динамическом контексте вызова функции. А вот значение x в adder находится в лексическом контексте. На момент запуска функций add5 и greeter функции makeAdder уже давно отработали, однако значение x и область видимости продолжают присутствовать в функциях add5 и greeter. Это называется замыканием - данные в локальной области видимости отработавшей функции и код, который использует эти данные впоследствии. В широком смысле замыкание является объектом, так как хранит в единой сущности код и данные.

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


function makeCounter(){
    var counter = 0;

    return function(){
        return counter++;
    }
}

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


function makeCounter(){
    var counter = 0;

    function inc(){
        return counter++;
    }

    function dec(){
        return counter--;
    }

    function read(){
        return counter;
    }

    return [inc,dec,read];
}

Результатом выполнения makeCounter будет массив функций. Однако намного более наглядным будет создание именованного массива, т. е. объекта:


function makeCounter(){
    var counter = 0;

    function inc(){
        return counter++;
    }

    function dec(){
        return counter--;
    }

    function read(){
        return counter;
    }

    return {inc: inc, dec: dec, read: read};
}

... что во многом схоже с обычным объектом сделанным через функцию-конструктор:

function Person(name, surname){
    this.name    = name
    this.surname = surname

    var age      = 0

    function checkNewAge(){
        return (!isNaN(this.newAge) && Number.isInteger(this.newAge) && (this.newAge > 0) && (this.newAge < 100))
    }

    this.setAge = function(newAge){
        this.newAge = +newAge
        if (checkNewAge()){
            age = this.newAge;
        }

        return age
    }

    this.getAge = function(){
        return age
    }
    
    this.getOriginalName = function(){
        return name;
    }

    this.getOriginalSurname = function(){
        return surname;
    }
}

var person        = new Person("Ivan", "Petroff")

alert(person.setAge(50))
alert(person.setAge(125))
alert(person.setAge(25))

person.name = "John";
person.surname = "Doe";

alert(person.getOriginalName());
alert(person.getOriginalSurname());