В ECMAScript поддерживаются пять примитивных типов данных:
- Number
- String
- Boolean
- Null
- Undefined
- Object
В текущей версии JavaScript присутствует функциональная область видимости. Это означает, что все переменные, объявленные c помощью ключевого слова var, будут видны в любом месте функции
var apples = 5;
if (true) {
var apples = 10;
alert(apples); // 10 (внутри блока)
}
alert(apples); // 10 (снаружи блока то же самое)
let apples = 5; // (*)
if (true) {
let apples = 10;
alert(apples); // 10 (внутри блока)
}
alert(apples); // 5 (снаружи блока значение не изменилось)
const apple = 5;
apple = 10; // ошибка
const user = {
name: "Вася"
};
user.name = "Петя"; // допустимо
user = 5; // нельзя, будет ошибка
Что такое область видимости?
В JS область видимости – это текущий контекст в коде. ОВ могут быть определены локально или глобально. Ключ к написанию пуленепробиваемого кода – понимание ОВ. Давайте разбираться, где переменные и функции доступны, как менять контекст в коде и писать более быстрый и поддерживаемый код (который и отлаживать быстрее). Разбираться с ОВ просто – задаём себе вопрос, в какой из ОВ мы сейчас находимся, в А или в Б?
// ОВ A: глобальная
var myFunction = function () {
// ОВ B: локальная
};
// глобальная ОВ
var name = 'Todd';
var myFunction = function () {
var name = 'Todd';
console.log(name); // Todd
};
console.log(name); // ReferenceError: name is not defined
var myFunction = function () {
var name = 'Todd';
var myOtherFunction = function () {
console.log('My name is ' + name);
};
console.log(name); // `Todd`
myOtherFunction(); // `My name is Todd`
};
(function () {
// здесь приватная ОВ
})();
(function () {
var myFunction = function () {
// делаем здесь, что нужно
};
})();
myFunction(); // Uncaught ReferenceError: myFunction is not defined
var Module = (function () {
var privateMethod = function () {
};
return {
publicMethod: function () {
}
};
})();
Каждая ОВ назначает своё значение переменной this, в зависимости от способа вызова функции. Мы все использовали ключевое слово this, но не все понимают, как оно работает и какие есть отличия при вызовах. По умолчанию, оно относится к объекту самой внешней ОВ, текущему окну. Пример того, как разные вызовы меняют значения this:
var myFunction = function () {
console.log(this); // this = глобальное, [объект Window]
};
myFunction();
var myObject = {};
myObject.myMethod = function () {
console.log(this); // this = текущий объект { myObject }
};
var nav = document.querySelector('.nav'); // <nav class="nav">
var toggleNav = function () {
console.log(this); // this = элемент <nav>
};
nav.addEventListener('click', toggleNav, false);
var pokemon = {
firstname: 'Pika',
lastname: 'Chu ',
getPokeName: function() {
var fullname = this.firstname + ' ' + this.lastname;
return fullname;
}
};
var pokemonName = function() {
console.log(this.getPokeName() + 'I choose you!');
};
var logPokemon = pokemonName.bind(pokemon); // creates new object and binds pokemon. 'this' of pokemon === pokemon now
logPokemon(); // 'Pika Chu I choose you!'
var pokemon = {
firstname: 'Pika',
lastname: 'Chu ',
getPokeName: function() {
var fullname = this.firstname + ' ' + this.lastname;
return fullname;
}
};
var pokemonName = function(snack, hobby) {
console.log(this.getPokeName() + ' loves ' + snack + ' and ' + hobby);
};
pokemonName.call(pokemon,'sushi', 'algorithms'); // Pika Chu loves sushi and algorithms
pokemonName.apply(pokemon,['sushi', 'algorithms']); // Pika Chu loves sushi and algorithms
function getDog(name, age){
return {
"name": name,
"age": age,
"talk": function(){
alert('Name: ' + this.name + ', Age: ' + this.age);
}
};
}
var rocky = getDog('Rocky', 5);
var jerry = getDog('Jerry', 3);
function Dog(name, age){
this['name'] = name;
this.age = age;
}
Dog.prototype = {
"talk": function(){
alert('Name: ' + this.name + ', Age: ' + this.age);
}
};
var rocky = new Dog('Rocky', 5);
var jerry = new Dog('Jerry', 3);
Date.prototype.getDayName = function(){
var days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
return days[this.getDay()];
}
var today = new Date();
alert(today.getDayName());
«Полифилл» (англ. polyfill) – это библиотека, которая добавляет в старые браузеры поддержку возможностей, которые в современных браузерах являются встроенными.
Один полифилл мы уже видели, когда изучали собственно JavaScript – это библиотека ES5 shim. Если её подключить, то в IE8- начинают работать многие возможности ES5. Работает она через модификацию стандартных объектов и их прототипов. Это типично для полифиллов.
function sum(arg1, arg2) {
return arg1 + arg2;
}
function sum(arg1, arg2, arg3) {
return arg1 + arg2 + arg3;
}
alert(sum(3, 4)); // NaN
alert(sum(3, 4, 5)); // 12
function sum(arg1, arg2, arg3) {
switch (typeof arg3) {
case "undefined":
return arg1 + arg2;
case "number":
return arg1 + arg2 + arg3;
default:
return arg1 + arg2 + " (" + arg3 + ")";
}
}
alert(sum(3, 4)); // 7
alert(sum(3, 4, 5)); // 12
alert(sum(3, 4, "!")); // "7 (!)"
В JavaScript-объекты можно добавлять свойства. Когда это делают после создания экземпляра объекта, объект необратимо изменяется. В следующем примере константа egg, объект, мутирует после того, как к ней добавляют свойство isBroken. Такие объекты (вроде egg) мы называем мутабельными (то есть, имеющими возможность мутировать, изменяться).
const egg = { name: "Humpty Dumpty" };
egg.isBroken = false;
console.log(egg);
// {
// name: "Humpty Dumpty",
// isBroken: false
// }
Предположим, создана константа с именем newEgg, в которую записан объект egg. Затем понадобилось изменить свойство name у newEgg:
const egg = { name: "Humpty Dumpty" };
const newEgg = egg;
newEgg.name = "Errr ... Not Humpty Dumpty";
Для того чтобы осознать смысл утверждения «объекты передаются по ссылке», сначала нужно понять то, что у каждого объекта в JavaScript есть уникальный идентификатор. Когда вы назначаете объект переменной, вы связываете переменную с идентификатором этого объекта (то есть, переменная теперь ссылается на объект) вместо того, чтобы записывать в переменную значение объекта, копировать его. Именно поэтому, сравнивая два разных объекта, даже содержащих одни и те же значения (или не содержащих их вовсе), мы получаем false.
console.log({} === {}); // false
Когда, в примере выше, константа egg была присвоена константе newEgg, в newEgg была записана ссылка на тот же объект, на который ссылалась константа egg. Так как egg и newEgg ссылаются на один и тот же объект, то, когда меняется newEgg, egg меняется автоматически.
console.log(egg === newEgg); // true
alert("NaN" == NaN); // false
alert(NaN == NaN); // false
alert(true == 1); // true
alert(true == 42); // false
alert(null == 0); // false
alert(0 == ""); // true
alert("" == 0); // true
alert("false" == false); // false
alert(false == 0); // true
alert(undefined == false); // false
alert(null == false); // false
alert(undefined == null); // true
alert(" \t\r\n " == 0); // true
function sumOfResults(callback) {
var result = 0;
for (var i = 1; i < arguments.length; i++) {
result += callback(arguments[i]);
}
return result;
}
var square = function(x) {
return x * x;
};
alert(sumOfResults(square, 3, 4)); // 25
var uniqueId = function() {
var id = 0;
return function() { return id++; };
}();
var aValue = uniqueId();
var anotherValue = uniqueId();
Каррирование (от англ. currying, иногда — карринг) — преобразование функции от многих аргументов в набор функций, каждая из которых является функцией от одного аргумента.
var multNumber = function(arg) {
return function(mul) {
return arg * mul;
};
};
var multFive = multNumber(5);
alert(multFive(7)); //35
3 - 1 // -> 2
3 + 1 // -> 4
'3' - 1 // -> 2
'3' + 1 // -> '31'
'' + '' // -> ''
[] + [] // -> ''
{} + [] // -> 0
[] + {} // -> '[object Object]'
{} + {} // -> '[object Object][object Object]'
'222' - -'111' // -> 333
[4] * [4] // -> 16
[] * [] // -> 0
[4, 4] * [4, 4] // NaN
💡 Explanation:
Number + Number -> addition
Boolean + Number -> addition
Boolean + Boolean -> addition
Number + String -> concatenation
String + Boolean -> concatenation
String + String -> concatenation
let { first: f, last: l } = { first: 'Jane', last: 'Doe' };
console.log(f); // 'Jane'
console.log(l); // 'Doe'
Создание объектов описанным в предыдущем разделе способом может быть непрактичным из-за необходимости дублировать код. Если в программе происходит манипуляция с большим количеством однотипных объектов, разработчик имеет возможность выбрать одну из используемых в языке техник:
- Фабрика объектов: функция, создающая объект и возвращающая его в качестве своего значения,
- Конструктор: функция: использующая ключевое слово this для формирования свойств объекта, создаваемого ей с помощью оператора new,
- Прототипный подход: задействование свойства prototype функции для вынесения общих свойств объектов,
// Supertype
class Person {
constructor(name) {
this.name = name;
}
describe() {
return "Person called " + this.name;
}
}
// Subtype
class Employee extends Person {
constructor(name, title) {
super.constructor(name);
this.title = title;
}
describe() {
return super.describe() + " (" + this.title + ")";
}
}
let jane = new Employee("Jane", "CTO");
jane instanceof Person; // true
jane instanceof Employee; // true
jane.describe(); // 'Person called Jane (CTO)'
module Math {
export function sum(x, y) {
return x + y;
}
export var pi = 3.141593;
// Не видна снаружи
function internal() {
...
}
}
// Импортирование модуля:
import Math.{sum, pi};
alert("2π = " + sum(pi, pi));
Arrow-функции немножко отличаются от обычных функций. В первую очередь тем, что в arrow-функциях this привязан к вышестоящему контексту.
let squares = [ 1, 2, 3 ].map(x => x * x);
// Код выше эквивалентен этому:
let squares = [ 1, 2, 3 ].map(function (x) { return x * x });
[...iterableObj, 4, 5, 6]
[a, b, ...iterableObj] = [1, 2, 3, 4, 5];
var parts = ['плечи', 'колени'];
var lyrics = ['голова', ...parts, 'и', 'пальцы'];
// ["голова", "плечи", "колени", "и", "пальцы"]
Копирование массива
var arr = [1, 2, 3];
var arr2 = [...arr]; // like arr.slice()
arr2.push(4);
// теперь arr2 содержит [1, 2, 3, 4]
// arr остается неизменным
А с использованием spread:
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1 = [...arr1, ...arr2];
Spread в литералах объектов
var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
var clonedObj = { ...obj1 };
// Object { foo: "bar", x: 42 }
var mergedObj = { ...obj1, ...obj2 };
// Object { foo: "baz", x: 42, y: 13 }