Краткий конспект по языку JavaScript

  • Tutorial
Я —.NET разработчик. Но в последнее время всё чаще сталкиваюсь с JavaScript. Причём, процентах в 50 случаев я что-то на нём пишу, в остальных 50 — разбираюсь с чужим кодом, да ещё и прошедшим через минификацию, а иногда и обфускацию. В этой статье захотелось поделиться теми моментами, которые мне показались важными для понимания языка и эффективной работы с ним. Тут не будет ничего нового или неизвестного для людей, уже имевших дело с языком, и не будет чего-то такого, чего нельзя найти в других источниках. Для меня статья будет полезна как способ лучше разобраться в предмете, для читателей, я надеюсь, — как повод освежить знания.

Брендан Айк упоминал, что JavaScript был создан за 10 дней. Думаю, идея вынашивалась дольше. Как бы то ни было, язык получился и с тех пор только набирает популярность. Особенно после появления AJAX.

JavaScript — язык со слабой динамической неявной типизацией, автоматическим управлением памятью и прототипным наследованием.

JavaScript состоит из трёх обособленных частей:

  • ядро (ECMAScript),
  • объектная модель браузера (Browser Object Model или BOM),
  • объектная модель документа (Document Object Model или DOM).


В статье, в основном, пойдёт речь о ядре. Конечно, в примерах кода будут использоваться элементы DOM и BOM, но заострять на них внимание не буду.

Система типов


Диаграмма типов JavaScript выглядит примерно так:

  • Number
  • String
  • Boolean
  • Object
    • Function
    • Array
    • Date
    • RegExp
  • Null
  • Undefined


Примерно — потому что ещё есть типы для ошибок, которые в эту диаграмму не вошли.

Из них 5 типов — примитивы:

  • Number
  • String
  • Boolean
  • Null
  • Undefined


Всё остальное — объекты. Примитивы Boolean, String и Number могут быть обёрнуты в соответствующие объекты. В таком случае объекты будут экземплярами конструкторов Boolean, String и Number соответственно.

console.log('Holly Golightly' == new String('Holly Golightly')); // true
console.log('Holly Golightly' === new String('Holly Golightly')); // false
console.log('Holly Golightly' === String('Holly Golightly')); // true


Примитивы не имеют свойств. Если мы, например, попытаемся получить значение свойства length у примитива String, примитив будет преобразован к объекту, у объекта будет получено значение свойства, после чего тот отправится куда-нибудь в сборщик мусора.

Примитивам нельзя добавить свойство.

var person = 'Paul Varjak';
person.profession = 'writer';
console.log(person.profession); // undefined


Что произошло? Примитив person был преобразован к объекту, у объекта добавилось свойство, после чего тот отправился в небытие.

Числа


Числа в JavaScript представлены типом Number, в языке нет разделения на целые, числа с фиксированной точкой, числа с плавающей точкой. Не стоит забывать, что операции над дробными числами не всегда точны. Например,

console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.2 + 0.3); // 0.5


В языке есть несколько специальных значений для типа Number: +Infinity, -Infinity и NaN.

console.log(Infinity === Infinity) // true


А теперь, внимание

console.log(NaN === NaN); // false


NaN вообще ничему не равен. На случай проверки на NaN в язык встроена функция isNaN. Ах да, функция isFinite тоже есть. NaN заразен — результат любых любых арифметических операций или функций из Math с NaN тоже равен NaN. А вот <<, >>, >>>, ||, |, ^, &,! (их даже больше, чем арифметических) могут уничтожить NaN.

console.log(NaN || 2); // 2
console.log(0 || NaN); // NaN
console.log(!NaN); // true


Нужно быть осторожным с функцией parseInt, в старых и новых браузерах она ведет себя по-разному при обработке строк, начинающихся с нуля. А лучше всегда указывать систему счисления. Старые браузеры воспринимают начальный ноль как признак восьмеричного числа.

Строки


Строки в JavaScript — не что иное, как последовательности Unicode-символов. Отдельного типа для одиночных символов не существует, вместо него используется строка длины 1.

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

Не стоит забывать, что функция replace заменяет только первое вхождение подстроки в строку, если первым параметром получает строку, а не регулярное выражение. Причем регулярное выражение должно быть глобальным (должно иметь модификатор g).

var str = "This is the house that Jack built. This is the malt That lay in the house that Jack built. This is the rat, That ate the malt That lay in the house that Jack built".replace("Jack", "Captain Jack Sparrow");
console.log(str);
// This is the house that Captain Jack Sparrow built. This is the malt That lay in the house that Jack built. This is the rat, That ate the malt That lay in the house that Jack built

var str = "This is the house that Jack built. This is the malt That lay in the house that Jack built. This is the rat, That ate the malt That lay in the house that Jack built".replace(/Jack/, "Captain Jack Sparrow");
console.log(str);
// This is the house that Captain Jack Sparrow built. This is the malt That lay in the house that Jack built. This is the rat, That ate the malt That lay in the house that Jack built

var str = "This is the house that Jack built. This is the malt That lay in the house that Jack built. This is the rat, That ate the malt That lay in the house that Jack built".replace(/Jack/g, "Captain Jack Sparrow");
console.log(str);
// This is the house that Captain Jack Sparrow built. This is the malt That lay in the house that Captain Jack Sparrow built. This is the rat, That ate the malt That lay in the house that Captain Jack Sparrow built


Кстати, callback-функцию тоже можно передать. Пример ниже может уберечь от разработки велосипедов.

var callback =  (function(i){
  return function(a){
    i++; 
    return a + i;
  };
})(0);
var str = "This is the house that Jack built. This is the malt That lay in the house that Jack built. This is the rat, That ate the malt That lay in the house that Jack built".replace(/Jack/g, callback);
console.log(str);
// This is the house that Jack1 built. This is the malt That lay in the house that Jack2 built. This is the rat, That ate the malt That lay in the house that Jack3 built


Важно помнить, что регулярные выражения хранят состояние, и результат работы методов test и exec зависит и от аргументов, и от состояния. Вот парочка примеров (спасибо, sekrasoft):

/a/g.test('aa') // true
/a/g.test('ab') // true

var re = /a/;
re.test('aa') // true
re.test('ab') // true

// но
var re = /a/g;
re.test('aa') // true
re.lastIndex // 1, в 'aa' больше одного вхождения a, конца строки не достигли
re.test('ab') // false
re.lastIndex // 0, т.к. достигли конца строки
re.test('ab') // true
re.lastIndex // 1


Кириллические строки лучше сравнивать функцией localeCompare.

"Ёлка" > "Арбуз" // false
"Елка" > "Арбуз" // true
"Ёлка".localeCompare("Арбуз") // 1
"Елка".localeCompare("Арбуз") // 1


Преждевременная оптимизация — зло. Вот парочка примеров:

jsperf.com/array-join-vs-connect

// Вариант 1
var arr = [];
for (var i = 0; i < 10000; i++) {
  arr.push(i);
}
var result = arr.join(', ')

// Вариант 2
var result = '';
for (var i = 0; i < 9999; i++) {
  result += i + ', ';
}
result += i;


По производительности выигрывает второй вариант.

jsperf.com/heera-string-literal-vs-object

// Вариант 1
var s = '0123456789';
for (var i = 0; i < s.length; i++) {
  s.charAt(i);
}

// Вариант 2
var s = new String('0123456789');
for (var i = 0; i < s.length; i++) {
  s.charAt(i);
}


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

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

null и undefined


null и undefined — примитивы, которые не имеют соответствующих им объектов. Поэтому попытка добавить свойство одному из этих примитивов или получить значение свойства, в отличие от строк, чисел и булевых значений, приведёт к TypeError.
Семантически null и undefined похожи, но и различия есть. null означает отсутствие объекта, undefined — отсутствие значения как такового. null — ключевое слово, undefined — свойство глобального контекста. Правда, в современных браузерах присвоить ему другое значение не получится. Любая не присвоенная переменная по-умолчанию имеет значение undefined.

Объекты


Объекты в JavaScript — ассоциативные массивы.

Пустой объект можно создать несколькими способами.

var obj = {};
var obj1 = new Object();
var obj2 = Object.create(null);


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

Создать объект можно и через функцию-конструктор.

function Company(name, address){
  this.name = name;
  this.address = address;
}

var company = new Company('Sing-Sing', 'Ossining, Westchester County, New York, United States');


Обратиться к свойствам созданного объекта можно двумя основными способами.

obj.name = 'Tiffany'
var name = obj.name;


и

obj['name'] = 'Tiffany';
var name = obj['name'];


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

var obj = {};
obj[1] = 1;
obj[null] = 2;
obj[{}] = 3;
obj[{a: 1}] = 4;
var val = obj[{}] // 4
Object.getOwnPropertyNames(obj); // ["1", "null", "[object Object]"]


Можно видеть, что {} и {a: 1} были приведены к одному и тому же значению — "[object Object]", а число и null были конвертированы в соответствующие строки.

В языке есть возможность создать свойства с геттерами и сеттерами. Есть несколько способов сделать это.

Литеральный:

var consts = {
  get pi(){
    return 3.141592;
  }, set pi(){
    throw new Error('Property is read only');
  }
};


С помощью функции Object.defineProperty:

var consts = {};
Object.defineProperty(consts, ‘pi’, {
  get : function () {
    return 3.14159265359;
  }, set: function(){
    throw new Error('Property is read only');
  }
});


С помощью функции Object.defineProperties:

var consts = {};
Object.defineProperties(consts, {'pi': {
  get : function () {
    return 3.14159265359;
  }, set: function(){
    throw new Error('Property is read only');
  }}, 'e': {
  get : function () {
    return 2.71828182846;
  }, set: function(){
    throw new Error('Property is read only');
  }
}});


Функции


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

Функции бывают именованные:

function getAnswer(){
  return 42;
}


и анонимные:

var getAnswer = function(){
  return 42;
}


В функцию можно передать сколько угодно параметров. Все они будут доступны через объект arguments. Кроме того, в этом объекте будут свойства length — количество аргументов и callee — ссылка на саму функцию.

function toArray(){
  return Array.prototype.slice.call(arguments);
}

toArray(1, 2, 3, 6, 'Tiffany'); // [1, 2, 3, 6, "Tiffany"]


А ссылка на саму функцию позволяет создавать рекурсивные анонимные функции.

var fibonacci = function(n){
  if (n <= 1){
    return n;
  } else { 
    return arguments.callee(n - 2) + arguments.callee(n - 1);
  }
}

console.log(fibonacci(22)); // 17711


В последней редакции стандарта это свойство было удалено. Зато можно написать так:

var fibonacci = function f(n){
  if (n <= 1){
    return n;
  } else { 
    return f(n - 2) + f(n - 1);
  }
}

console.log(fibonacci(22)); // 17711


Область видимости переменных, объявленных через var, в JavaScript ограничена функцией. Ключевое слово let на подходе, оно будет задавать блочную область видимости, но пока браузеры поддерживают его неохотно.

Часто делают так. Это называется самовызывающейся функцией (self executing function или immediately invoked function expression).

(function(){
  var person = new Person();
  // do something
})();


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

Ключевое слово this


Значение this в JavaScript не зависит от объекта, в котором создана функция. Оно определяется во время вызова.

В глобальном контексте:

console.log(this); // Window


Как свойство объекта:

var obj= {
  data: 'Lula Mae'
};
function myFun() {
  console.log(this);
}
obj.myMethod = myFun;
obj.myMethod(); // Object {data: "Lula Mae", myMethod: function} 


Как обычная функция:

var obj = {
  myMethod : function () {
    console.log(this);
  }
};
var myFun = obj.myMethod;
myFun(); // Window


Выполнение через eval (не используйте eval):

function myFun() {
  console.log(this);
}
var obj = {
  myMethod : function () {
    eval("myFun()");
  }
};
obj.myMethod(); // Window


С использованием методов call или apply:

function myFunc() {
  console.log(this);
}
var obj = {
  someData: "a string"
};
myFunc.call(obj); // Object {someData: "a string"}


В конструкторе:

var Person = function(name){
  this.name = name;
  console.log(this);
}

var person = new Person('Lula Mae'); // Person {name: "Lula Mae"}


Кстати, функции-конструкторы принято называть с заглавной буквы.

А ещё недавно появился метод bind, который привязывает функцию к контексту. Точнее, не просто привязывает функцию к контексту, он создаёт новую функцию с указанным контекстом, в отличие от call и apply.

var myFunc = function() {
  console.log(this);
}.bind(999);
myFunc(); // Number {[[PrimitiveValue]]: 999}


Замыкания


JavaScript устроен так, что вложенные функции имеют доступ к переменным внешних функций. Это и есть замыкание.

Вернёмся к примеру с Джеком.

var callback =  (function(i){
  return function(a){
    i++; 
    return a + i;
  };
})(0);
var str = 'Jack Jack Jack'.replace(/Jack/g, callback);
console.log(str); // Jack1 Jack2 Jack3


Получилось так, что функция, которая принимает аргумент a, имеет доступ к переменным внешней функции. И каждый раз при вызове внутренней функции мы увеличиваем на 1 переменную-счётчик i.

Пример попроще:

function add(a) {
  var f = function(b) {
    return a+b;
  };
  return f;
}

console.log(add(5)(7)); // 12


Давайте разберёмся, что произошло.

При вызове функции создаётся её контекст. Удобно считать его просто объектом, в котором каждой переменной функции соответствует свойство с её именем. А при вызове вложенных функций они получают ссылку на контекст внешней функции, При обращении к переменной происходит поиск в контексте функции, потом — в контексте внешней функции и так далее.

Вызов add(5)

  1. Создаётся [[scope]] = { a: 5 }
  2. Создаётся функция f = function(b) { return a+b; }
  3. Функция f получает ссылку на [[scope]]
  4. Ссылка на функцию f добавляется в [[scope]]
  5. Возвращается ссылка на функцию f


Вызов add(5)(7)

  1. Создаётся [[scope2]] = { b: 7 }
  2. Производится поиск свойства a в объекте [[scope2]]. Не найдено.
  3. Производится поиск свойства a в объекте [[scope]]. Найдено, значение равно 5.
  4. Производится поиск свойства b в объекте [[scope2]]. Найдено, значение равно 7.
  5. Складываются 5 и 7.
  6. Возвращается результат сложения — число 12.


Передача параметров


function myFunc(a, b){
  console.log('myFunc begins');
  console.log('myFunc ' + a);
  console.log('myFunc ' + b);
}
function getArgument(arg){
  console.log('getArgument ' + arg);
  return arg;
}
myFunc(getArgument(5), getArgument(7));
// getArgument 5
// getArgument 7
// myFunc begins
// myFunc 5
// myFunc 7 


И что с того? Во-первых, видно, что аргументы вычисляются до их передачи в функцию, это так называемая строгая стратегия обработки параметров… Во-вторых, вычисляются они слева направо. Такое поведение определено стандартом и не зависит от реализации.

А по ссылке или по значению передаются значения? Примитивы, кроме строк, передаются по значению. Строки передаются по ссылке, а сравниваются по значению. Так как строки неизменяемы, это позволяет экономить память и не имеет каких-то побочных эффектов. Объекты передаются по ссылке. Тут следует оговориться, что перед передачей аргумента по ссылке в функцию создаётся копия ccылки, которая существует только внутри вызываемой функции. То есть, другими словами, объекты передаются по значению ссылки. Посмотрим на примере:

var a = { data: 'foo' };
var b = { data: 'bar' };

function change(arg){
  arg.data = 'data';
}

function swap(x, y){
  var tmp = x;
  x = y;
  y = tmp;
}

change(a);
swap(a, b);

console.log(a); // Object {data: "data"} 
console.log(b); // Object {data: "bar"}


Получается, свойство объекта изменить можно — копия ссылки ссылается на тот же объект. А при присвоении нового значения ссылке оно действует только на копию, которая была передана в функцию, а не на исходную ссылку.

Всплытие переменных и разрешение имен


Давайте посмотрим на такой код.

var a = 1; 
function b(){
  console.log(a); 
  if (false){
    var a = 2;
  }
} 
b(); // undefined


Почему не 1? Дело в том что у переменных, объявленных через var, область видимости ограничивается функцией, а ещё в том, что существует механизм всплытия переменных. Интерпретатор языка всегда переносит объявление всех переменных в начало области видимости. При этом переносится только объявление, а присвоение значения не переносится. Код выше эквивалентен следующему:

var a = 1; 
function b(){
  var a;
  console.log(a); 
  if (false){
    a = 2;
  }
} 
b();


Алгоритм поиска объекта по имени такой:

  1. Искать среди предопределённых языком переменных. Если найден — использовать его.
  2. Искать среди формальных параметров функции. Если найден — использовать его.
  3. Искать среди объявленных функций текущей области видимости. Если найден — использовать его.
  4. Искать среди объявленных переменных текущей области видимости. Если найден — использовать его.
  5. Перейти на область видимости выше и начать сначала.


Исключение подтверждает существование общего правила, где эти исключения не оговорены. Переменная arguments — как раз такое исключение. Хоть это и предопределённая языком переменная, формальный параметр arguments имеет приоритет при поиске значения.

function a(){
  console.log(arguments);
}
function b(arguments){
  console.log(arguments);
}

a(); // []
b(); // undefined


Наследование


В отличие от таких языков, как Java, C#, C++, в JavaScript наследуются не классы, а объекты. Тем не менее, class — зарезервированное слово, назвать так переменную нельзя.

Каждый объект содержит ссылку на другой объект, который называется прототипом. Прототип содержит ссылку на свой прототип и так далее. В какой-то момент находится объект с прототипом null, и цепочка заканчивается.

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

Уже упоминалось, что объекты в JavaScript — просто ассоциативные словари свойств. Теперь выясняется, что есть ещё скрытое свойство, будем обозначать его [[Prototype]], которое нельзя использовать в коде, и которое служит “запасным” источником свойств. Давайте разберёмся, как происходит поиск свойства в таком случае.

Пусть у нас есть такая цепочка прототипов

{a:1, b:2} ---> {b:3, c:4} ---> null

console.log(o.a); // 1


В объекте есть собственное свойство a, поэтому просто берём его значение.

console.log(o.b); // 2


В объекте есть собственное свойство b, поэтому просто берём его значение. В прототипе такое свойство тоже есть, но мы его не проверяем. Это называется перекрытием свойств.

console.log(o.c); // 4


В объекте нет свойства с. Зато оно есть в прототипе, используем свойство прототипа.

console.log(o.d); // undefined


В объекте нет свойства d. Ищем в прототипе, там его тоже нет. Продолжаем поиск в прототипе прототипа, а он равен null. Прекращаем поиск, свойство не найдено, возвращаем undefined.

С методами всё происходит точно так же. Ещё бы, методы — это тоже свойства объекта. Один нюанс — ключевое слово this в функции будет указывать на объект, а не на прототип, даже если функция найдена в прототипе. В принципе, это логично.

Как же назначить прототип объекту? Есть несколько способов.

Во-первых, прототипы назначаются автоматически при создании объектов, массивов, функций.

var o = {a: 1}; // o ---> Object.prototype ---> null
var a = ["horse", "table"]; // a ---> Array.prototype ---> Object.prototype ---> null

function getRandomNumber(){
  return 4;
}

// getRandomNumber ---> Function.prototype ---> Object.prototype ---> null


Во-вторых, прототипы можно назначать при создании объектов через конструктор. В таком случае прототипом объекта становится прототип конструктора.

function Animal(){
  this.eat = function(){
    console.log('eat called');
  };
}

function Cat(name){
  this.name = name;
};

Cat.prototype = new Animal();

var cat = new Cat('No name cat');
cat.eat(); // eat called
console.log(cat.name); // No name cat
console.log(cat.constructor); // Animal


Свойству [[Prototype]] присваивается значение Cat.prototype при выполнении new Cat(). Вместе с тем свойству constructor объекта cat присвоилось значение Animal. Можно исправить код, чтобы конструктор оставался правильным. Добавим строчку Cat.prototype.constructor = Cat;

function Animal(){
  this.eat = function(){
    console.log('eat called');
  };
}

function Cat(name){
  this.name = name;
};

Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;

var cat = new Cat('No name cat');
cat.eat(); // eat called
console.log(cat.name); // No name cat
console.log(cat.constructor); // Cat


В-третьих, прототип можно назначить при создании объекта при помощи метода Object.create. Прототип указывается в первом аргументе этого метода.

var a = {a: 1}; // a ---> Object.prototype ---> null
var b = Object.create(a); // b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1


Просто так присвоить прототип нельзя. prototype — свойство конструктора, а не объекта.

var o = { a: 1 };
o.prototype = { b: 2 };
console.log(o.b); // undefined


Зато можно изменить прототипы встроенных типов, например, Object. Это плохая практика. Единственным приемлемым случаем для изменения встроенных прототипов может быть только эмуляция возможностей из более новых версий языка.

Strict Mode


Этот режим включается директивой

'use strict';


Эта директива означает, что код будет выполняться в соответствии со стандартом ECMAScript 5. То есть, некоторые вещи будут работать по-другому. Возможно, более логично и более правильно, но не так, как раньше. Директива может применяться к скрипту целиком или к отдельной функции, включая вложенные функции. Под вложенными понимаются функции, объявленные внутри функции. Если же функция объявлена в другом месте, а внутри “строгой” функции только выполняется, то директива на неё не действует. На примере хорошо видно это:

function a(){
  console.log(arguments.callee);
}

(function() {
  "use strict";
  function b(){
    console.log(arguments.callee);
  }
	
  a(); // function a(){...}
  b(); // TypeError
})();


А самовызывающаяся функция тут для того, чтобы пример работал в консоли браузера. “use strict” в консоли не работает вне функции.

Что изменится? Во-первых, больше не будет доступно свойство arguments.callee, об этом уже упоминалось.

Во-вторых, this не будет подменяться на глобальный объект в случае null или undefined или оборачиваться в экземпляр конструктора в случае примитива.

(function() {
  "use strict";
  var a = function(){
    console.log(this);
  }.bind(null)	
  a(); // null
})();

(function() {
  var a = function(){
    console.log(this);
  }.bind(null)	
  a(); // Window
})();


В-третьих, будет нельзя создавать глобальные переменные без их явного объявления.

(function() {
  "use strict";
  a = 0; // ReferenceError: a is not defined
})();


В-четвертых, больше не будет поддерживаться конструкция with(obj){}

В-пятых, нельзя будет создать объект с одинаковыми ключами.

(function() {
  "use strict";
  var o = { p: 1, p: 2 }; // SyntaxError: Duplicate data property in object literal not allowed in strict mode
})();


Это не всё, но перечислять всё не буду.

Приватное и публичное


В языке нет ключевых слов private и public, но разделять приватные и публичные данные можно. Дня этого есть несколько способов, например Module Pattern:

blackBox = (function(){
  var items = ['table', 'horse', 'pen', 48];
  return {
    pop: function(){
      return items[~~(Math.random() * items.length)];
    }
  };
})();

console.log(blackBox.pop()); // 48
console.log(blackBox.items); // undefined


Или можно сделать так:

function BlackBox(){
  var items = ['table', 'horse', 'pen', 48];
  this.pop = function(){
    return items[~~(Math.random() * items.length)];
  };
}

var blackBox = new BlackBox();

console.log(blackBox.pop()); // "pen"
console.log(blackBox.items); // undefined


Отладка


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

Например, если приходится разбираться, откуда появилось всплывающее сообщение, можно выполнить в консоли:

window.alert = function(){
  debugger;
};


Теперь вместо сообщения запустится отладчик, а выполнение скрипта остановится в месте вызова alert.

Иногда бывает полезно логировать стек вызовов какой-нибудь функции. Сделать это можно так:

function a(){
  console.log(new Error().stack);
}

function b(){
  a();
}

function c(){
  b();
}

c();
Метки:
Поделиться публикацией
Похожие публикации
Комментарии 20
  • –5
    Тут не будет ничего нового или неизвестного для людей, уже имевших дело с языком, и не будет чего-то такого, чего нельзя найти в других источниках.

    Ну и зачем тогда дублировать информацию, тем более, что рядом идёт перевод неплохой книги?
    • +12
      Думаю, формат книги и формат конспекта — разные форматы. На чтение книги нужен не один вечер, и задача её — научить, дать полное и глубокое представление о предмете. Здесь я ставил другую цель — помочь быстро освежить знания об особенностях языка, если вдруг давно не имел с ним дела. Показать, что нужно гуглить, если какой-то пример непонятен. Просмотреть статью можно за минут 8-10, я думаю. С книгой так не получится.
      • +6
        Ну вот конспект. Я понимаю, это субъективный взгляд на самое главное, но из-за подобных статей Хабр превращается в ЛЖ. Но мы ведь всё-таки не личные дневники ведём, верно?
        • –1
          Да, это субъективный взгляд на самое главное. Думаете, субъективным точкам зрения на Хабре не место?
          • +4
            Нет, что вы. Без субъективного никуда. Просто если вопрос не раз поднимался и есть достаточно много открытых источников той же самой информации, то зачем. Вы ведь сами говорите, что здесь ничего нового. Я не спорю, написано качественно и доходчиво.
      • +7
        Стоит отметить, что статья довольно качественная.
      • 0
        Не в ту ветку.
        • +4
          Я люблю такие статьи, они, как правило, рассчитаны на новичков. В них всегда чувствуется боль человека, который проходит обучение языку без наставника, самостоятельно. Поэтому я традиционно рекомендую в комментариях:
          habrahabr.ru/post/120192/
          и
          habrahabr.ru/post/124327/
          • +9
            >Диаграмма типов JavaScript выглядит примерно так:

            Диаграмма типов JavaScript выглядит примерно как логотип хабра.
            • +9
              причём тот, который помнят только олдфаги, а не эта стилизованная буквочка.
            • +3
              Спасибо за статью! )
              • +3
                Хорошая статья, спасибо! Она не конкурирует, безусловно, с книгами, не пытается заменить Крокфорда. Но ее несомненная и весомая польза в том, что она уменьшает общую энтропию вокруг предмета изучения.
                • –1
                  > Нужно быть осторожным с функцией parseInt, в старых и новых браузерах она ведет себя по-разному при обработке строк, начинающихся с нуля.
                  Кто-нибудь видел в реальной жизни, числа в строках, начинающиеся с нуля? Да ещё тогда, когда надо применять именно parseInt, а не Number? Благо с внедрением ES5 про эти никому не нужные восьмеричные числа можно забыть.

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

                  > jsperf.com/array-join-vs-connect
                  Проверять всё же надо на реальных данных, а не на искусственных циклах, которые умный компилятор может и развернуть. И что мы тестируем: компилятор или тип записи уже непонятно.

                  > undefined — отсутствие значения как такового.
                  Не понял, что это значит. Может быть разных случая, когда будет undefined.
                  Первый когда нет значения:

                  var o = { a: undefined }
                  o.a // undefined
                  'a' in o // true

                  Второй, когда нет свойства:

                  var o = {}
                  o.a // undefined
                  'a' in o // false

                  Это может иметь значение, например, когда создаёте пустой массив некой длины, то у него нет свойств, и методы вроде map не сработают ни разу. Однако, если свойства есть, но имеют значение undefined, то методы их обработают.
                  Так называемые «дырки», когда нет свойств, плохо влияют на оптимизации, потому что надо идти по цепочке прототипов и т.п.
                  • 0
                    Кто-нибудь видел в реальной жизни, числа в строках, начинающиеся с нуля? Да ещё тогда, когда надо применять именно parseInt, а не Number? Благо с внедрением ES5 про эти никому не нужные восьмеричные числа можно забыть.

                    С восьмеричными числами столкнулся, когда пришлось разбирать дату и время в нестандартном формате. Получилось, что баг воспроизводится только в определённое время (8 или 9 секунд, минут, часов) и не во всех браузерах. Поэтому посчитал не лишним предупредить. Я тоже надеюсь, что скоро наступит время, когда это предупреждение будет ни к чему.

                    Если это никак не проверить, то откуда информация про передачу строк по ссылке? Да и зачем это здесь, если никаких практических следствий это не несёт?

                    Хм… Из практических последствий — длинные строки передаются в функцию так же быстро, как короткие. Хотя, сказать по правде, так себе оправдание. Это скорее информация о том, что разработчики браузеров сами позаботились об оптимизации работы со строками, от разработчика ничего не требуется в большинстве случаев. Может быть, и не стоило об этом писать.

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

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

                    Не понял, что это значит. Может быть разных случая, когда будет undefined.

                    Спасибо за пример со свойствами. У меня представление об разнице этой тоже скорее интуитивное, чем формальное. Но, по крайней мере, есть закономерность: undefined — для отсутствующих свойств и неинициализированных переменных, null — для конца цепочки прототипов и присвоенных объектов, у которых отсутствует значение. typeof null === 'object', typeof undefined === 'undefined'.
                    • 0
                      > Получилось, что баг воспроизводится только в определённое время (8 или 9 секунд, минут, часов) и не во всех браузерах.
                      Я недаром упомянул Number. Тут он идеально подходит, не надо пихать parseInt куда ни попадя.
                  • +2
                    быть жадным (должно иметь модификатор g)


                    В данном случае g обозначает global, а не greedy. Но само понятие «жадные» присутствует в регулярных выражениях. Видимо, вы перепутали.
                  • 0
                    Статья неплохая, много полезного для новичков и даже для уже повидавших js. Но есть несколько косяков.

                    Строки в качестве аргументов функции передаются по ссылке, а не по значению.

                    Не верно, все примитивы в js передаются по значению. В статье несколько раз упоминается неверная информация и это плохо…

                    self executed function

                    Общепринятое название immediately invoked function expression (IIFE). Раз уж решили писать для новичков, то чтобы они потом не путались в терминологии лучше так.

                    А ещё недавно появился метод bind, который привязывает функцию к контексту.

                    Он не просто привязывает функцию к контексту, он создаёт новую функцию с указанным контекстом, в отличие от call и apply.
                    • +1
                      Спасибо, уточнил спорные моменты.

                      Не совсем согласен с передачей строк по значению, дело в том, что случае со строками нет никакого способа это проверить, потому что строки неизменяемы. Разве что косвенно — сравнить скорость передачи длинных и коротких строк в функцию и предположить, что передача по значению будет медленнее за счет копирования длинной строки. Получается, нет никаких причин передавать строки по значению, и разработчики JavaScript движков, насколько я знаю, как смогли оптимизировали этот момент. Так что с точки зрения результата выполнения кода правильно будет сказать, что строки неизменяемы, и как передаются — проверить невозможно. А с точки зрения скорости работы — что строки всё-таки передаются по ссылке. По крайней мере, много где об этом пишут, например тут. А сравниваются строки по значению, это конечно.
                      • 0
                        Не совсем согласен с передачей строк по значению, дело в том, что случае со строками нет никакого способа это проверить, потому что строки неизменяемы.

                        JS не какой-то странный зверь, которого надо поймать и наблюдать за ним, чтобы понять как он ведёт себя. Это такой же инструмент как и C#, для которого есть документация. Поэтому не нужно ничего «предполагать/проверять/искать причины поведения».
                        Например, есть отличная статья по стандарту ECMA, в которой объясняется подробно о том как работает всё это в JS (в заключении есть прямая фраза, что примитивы (к которым строки относятся) передаются по значению).

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.