Этот материал рассчитан на людей, которые уже знакомы с программированием на PHP и желают быстро разобраться с особенностями Javascript, без которого сейчас не обойдется ни один веб-программист.
В отличие от обычной для PHP ситуации: отсутствия отладчика, для JS в браузере есть отличная среда отладки. Доступен отладчик по F12, Ctrl-Shift-I или с помощью контекстного меню.
Developer Tools содержит в себе множество средств отладки кода. Для хорошего понимания управляющих конструкций (условий и циклов) полезно разобраться с пошаговой отладкой - выполнением кода по шагам. Это позволяет увидеть, какие блоки кода выполняются или не выполняются в условных операторах и каковы значения переменных в каждый момент времени выполнения кода.
Для начала пошаговой отладки устанавливается точка останова - строка кода, с которой обычный режим выполнения прерывается и начинается отладка по шагам. В Developer Tools найдите вкладку Source, в ней свой файл c кодом, и кликните на номере нужной строки слева от текста кода.
Если вы используете repl.it, другие онлайн-IDE или console, то у вас будут определенные сложности с нахождением
вашего кода и строки в нём. Поэтому вы можете вставить ключевое слово debugger
в ваш код - это работает так же, как точка останова на
строке в Developer Tools.
Пошаговая отладка позволяет детально заглянуть в процесс выполнения вашего кода - вы можете узнать всё ли работает так, как нужно, в любой строке и таким образом упростить поиск логических ошибок в коде. Основные операции:
debugger
в коде.Очень удобно использовать консоль и пошаговую отладку одновременно. Консоль предоставляет все переменные и их текущие значения, которые сейчас есть в отлаживаемом коде, таким образом вы можете поэкспериментировать с этими значениями и, например, запустить из консоли следующий проблемный оператор в коде или его часть для нахождения логической ошибки.
Вы всегда можете добавить console.log
в место, где хотите получить информацию о состоянии программы. Этот способ хорош как дополнение к остальным.
Так же вы можете написать определенное условие для отладки, вставить в него console.log
и поставить точку останова.
...могут быть запущены в отладочном режиме по красной кнопке :-), если вы откроете Developer Tools. Без Developer Tools они тоже работают.
"Корнем" HTML-документа является файл html, в котором указываются остальные части страницы - картинки, css, js файлы и код.
<script src="/path/to/script.js"></script>
<script>
alert("AAaa");
</script>
use strict
Javascript-интерпретатор в браузере может работать в двух режимах: "обычном" и в "строгом":
В зависимости от режима поведение интерпретатора может
меняться. Далее это будет упоминаться в тех или иных моментах. По умолчанию интерпретатор работает в обычном режиме, для включение строгого режима
используется строка 'use strict'
в начале Javascript-кода.
"use strict";
или
'use strict';
В JS код наоборот, работает длительное время (обычно всё время работы страницы), что приводит к другой семантике и организации кода в событийной среде.
В PHP код исполняется многопоточно (однако каждый поток работает изолированно, многопоточность зачастую реализуется как запуск нескольких копий интерпретатора в многозадачной ОС) и синхронно, т. е. код выполняется в том порядке, в котором он написан; в скрипте никаких фоновых и непоследовательных операций обычно не предусмотрено, ввод-вывод (запись и чтение файлов, сетевые запросы, запросы к СУБД) происходят в блокирующем режиме, т. е. интерпретатор останавливает выполнения и ожидает отправки или приема той или иной порции данных.
В JS код исполняется однопоточно (за редким исключением), и асинхронно, т. е. код представляет из себя не единую последовательность действий, а набор функций, которые могут исполнятся в произвольном порядке в качестве обработчиков событий на странице, событий, связанных с временными задержками или сетевых событий. Однако надо учитывать, что код, расположенный в функции, обычно работает синхронно и однопоточно, никакой другой обработчик события не может работать, пока работает другой код. Асинхронными являются только вызовы обработчиков событий из движка браузера, когда же вы вызываете функцию в своем коде, весь ваш код работает синхронно. Ввод-вывод так же происходит в асинхронной манере - вместо ожидания данных, интерпретатор освобождается для обработки других событий; по окончанию операции ввода-вывода вызывается соответствующая функция, обрабатывающая результаты.
Данные отличия приводят к сложности написания, отладки и понимания JS кода. Стандартные ошибки:
Scheme
, Common Lisp
)this
обычно равен объекту, указанному до .
, однако
есть возможность передать функции в качестве this
любой объект.prototype
, который является ссылкой на другой объект-прототип. В случае, когда тот или иной ключ (имя метода или поля) в объекте не найден,
JS ищет ключ в объекте-прототипе, и далее по цепочке прототипов. Таким образом реализуется наследование.private
, protected
и public
нет.private
полей объекта, а так же сохраняется контекст для отложенных операций или
обработчиков событий. Замыкания являются объектами в общем смысле этого слова, так как инкапсулируют код и данные.В отличие от PHP, Perl и Shell-интерпретаторов, с которых это и пошло, в Javascript переменные объявляются без знака $
:
a = 5;
$a = 5;
Такой код в обычном режиме Javascript декларирует глобальную переменную, которая становится полем объекта window
, даже если переменная
определена внутри функции:
a = 5;
function b(){
a = 10;
}
b()
alert(a);
Код выше аналогичен:
$a = 5;
function b(){
global $a;
$a = 10;
}
b();
echo($a);
Для декларации локальных переменных используется ключевое слово var
.
a = 5;
function b(){
var a = 10;
}
b();
alert(a);
Код выше аналогичен:
$a = 5;
function b(){
$a = 10;
}
b();
echo($a);
В строгом режиме определение переменных без
var
недопустимо и вызывает ошибку:
'use strict';
var a = 5; //без var тут бы была ошибка
function b(){
var a = 10;
}
b();
alert(a);
Во избежание ошибок ВСЕГДА определяйте переменные через var
.
Основные типы данных в JS совпадают с типами данных PHP, за некоторыми исключениями:
Number
NULL
используется аналогичный тип undefined
null
, который применяется программистами для задания "пустых" значений и/или в DOM.;
В отличие от PHP и многих других языков с C-подобным синтаксисом, в JS точка с запятой не является обязательной, однако нужна в некоторых случаях, например при написании операторов в одну строку:
var a = 5
var b = 6
var c = "string"
var d
a ++; b += a; d = c + a;
Во избежание ошибок просто добавляйте ;
"как обычно", в конце строки
Как в PHP.
В отличие от PHP, в JS нет разницы между одинарными и двойными кавычками:
var a = "\n";
var b = '\n';
alert(a == b);
$a = "\n";
$b = '\n';
echo (a == b);
В отличие от PHP, в JS нет подстановки переменных в строках в двойных кавычках:
var a = "\n";
alert("Тут нет a переноса a строк");
$a = "\n";
echo ("Тут есть $a перенос $a строки");
Таким образом, для добавления значения переменной в строку надо использовать конкатенацию
В отличие от PHP, в JS конкатенация делается с помощью оператора +
, а не .
:
var a = '\n';
alert("Тут есть" + a + "перенос" + a + "строки");
$a = "\n";
echo ("Тут есть" . $a . "перенос" . $a . "строки");
+
и динамическая типизация.Так как в JS нет отдельного оператора конкатенации (в PHP это .
), то +
между числами в строках может вас удивить:
Для того что бы избежать подобных ситуаций, приводите числа в строках перед использованием в математических операциях:
+"123"
. Простой и краткий способ для приведения строки к числуparseInt("123")
или parseFloat("123.45")
работает схожим образом, однако обладает другими странностями и возможностями (например есть возможность
задать систему счисления)Большинство кода состоит из тех или иных вызовов функций. В JS они выглядят почти так же:
alert("as in PHP");
alert("as in JS, without semicolon")
Во второй строке примера нет ;
, в этом невеликое отличие.
В большинстве своем повторяют обычный набор PHP, C, Java и так далее (+
, -
, *
, /
, %
, ++
, --
, +=
...). В JS нет and
, or
и not
, используйте &&
, ||
, !
.
&&
и ||
.В отличие от PHP, в 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;
PHP ведет себя по-другому:
echo var_dump(2 || "aaa");// bool(true)
echo var_dump(0 && "aaa");// bool(false)
if
и switch
.Работают аналогично PHP, однако не имеют своих длинных форм if-endif
и switch-endswitch
. Работают только обычные формы с фигурными скобками.
foreach
В JS нет foreach
, однако есть форма for
, схожая по функционалу:
var car = {brand: "Lada",
'model': "2101"};
for (var key in car){
alert(key + ": " + car[key]);
}
var $car = ["brand" => "Lada",
"model" => "2101"];
foreach ($car as $key => $value){
echo("$key: $value");
}
Как можно заметить, при этом в цикле нет переменной со значенинем, а только с ключем, по которому можно в цикле получить значение из итерируемого массива.
for
, while
и do-while
работают так же как в PHP:
for (var i=0;i<10;i++){
console.log(i);
}
for ($i=0;$i<10;$i++){
echo $i;
}
Аналогом ->
в JS является .
.
Даже встроенные типы данных. Аналоги стандартных функций PHP (процедурный стиль) являются методами объектов в JS.
Кроме того, что целые и дробные числа объединены в единый тип Number
, особых отличий с PHP нет:
console.log(1/0); //Infinity
console.log(-1/0);//-Infinity
console.log(1/"asdf");//NaN
echo(1/0); //INF + исключение Division By Zero
echo(-1/0);//-INF + исключение Division By Zero
echo(1/"asdf");//INF + исключение Division By Zero
PHP при приведении к числу превращает некорректные строки в 0:
echo intval("aaa100500");
JS в подобной ситуации возвращает NaN
:
console.log(+"aaa100500");
Также JS не выбрасывает исключение "Деление на ноль", а просто возвращает бесконечность.
С числами можно работать как с объектами:
console.log(5.123456.toFixed(2)); // "5.12"
Аналогично
echo(round(5.123456,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"
echo (strlen("12345")); //5
echo (strpos("aBcDe","D")); // 3
echo (substr("123string456",3,6)); // "string"
Работает почти как в PHP, однако:
true
и false
регистрозависимы. В PHP допустимы FALse
и tRuE
."true"
и "false"
, в PHP - 1
и пустая строка ""
.undefined
Аналог NULL
в PHP. В этом типе есть только одно значение - undefined
null
null
- это "undefined
для программиста", а не для интерпретатора. Так же используется в DOM для пустых/незаданных значений.
Под массивом подразумевается нумерованный целочисленными индексами массив. В PHP обычные массивы и ассоциативные объединены в единый тип array, в JS же для "обычных" массивов применяется объект типа Array, а для ассоциативных массивов - объекты как таковые.
var a1 = [1,2,3]
var a2 = new Array(4,5,6)
alert(a1[1]) //
a1.push(5)
alert(a1.indexOf(3)) // array_search
alert(a2.length) // count
//Можно добавить именованное поле в обычный массив как и в любой другой объект 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
(аналог array_key_exists
)
for (var key in arr)
для перебора всех элементов ассоциативного массиваfor (var key in person){
console.log(key+": "+person[key]);
}
Нарисовать HTML таблицу из двух колонок, в которой слева будут ключи, а справа - значения:
name | Ivan |
---|---|
surname | Ivanovv |
fatherName | Petrovich |
В семантике JS функциям отведена огромная роль:
Они могут использоваться
filter
, map
, reduce
, sort
)PHP тоже позволяет делать многое из этого, однако исторически такой подход редко используется в PHP-коде.
Основные синтаксические различия касаются параметров функции:
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));
В PHP7 трюк с b = b || 0
можно сделать с помощью ??
.
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"});
Функцию можно определить "как обычно"
a("Hello");
function a(text){
alert("function a: " + text);
}
var b = a; //сохраняем функцию в переменной b
b("Hi"); //работает
a = alert;
a("Hello"); //a заменена встроенной функцией alert
a = b; //возвращаем изначальную функцию в a
Обратите внимание, что:
var func = function(a){
alert(a);
};
func("Hello");
В примере выше написано выражение, которое создает анонимную функцию. И эта функция связывается с переменной func
. В дальнейшем её можно
использовать так же как и в Function Declaration, за одним исключением - нельзя обращаться к переменной до её определения:
func("Hello"); //ошибка, переменная func не определена.
var func = function(a){
alert(a);
};
Название немного сбивает с толку, так как намекает на рекурсию, однако рекурсии в этом нет. Суть в том, что если есть выражение, которое создает функцию, то есть возможность к ней прямо сразу и обратится, не занося её в переменную.
"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
, значения параметров) и лексическом (переменные из более высоких областей
видимости в месте определения функции). Динамический контекст - это контекст вызова функции - значение параметров и окружение на момент вызова;
лексический контекст - контекст определения функции, её вложенности в другие области видимости, доступ к которым функция имеет и после окончания
выполнения функций-владельцев этих областей видимости.
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());