Говоря о примитивных типах данных в JavaScript, большинство имеет в виду самые основные из них: String, Number и Boolean. Эти примитивы достаточно предсказуемы, обычно они работают так, как от них и ожидается. Однако речь в этой статье пойдет о менее обыденном типе данных — Undefined. Необычном, непонятном и в некотором смысле даже ужасном.
Помогаем
Содержание:
1. Концепция undefined
2. Что такое undefined в JavaScript?
3. Как работать с undefined?
4. Все еще страннее для массивов
5. Скрытый тормоз
Избегаем неопределенностей (Заключение)
1. Концепция undefined
В цифровом мире нулей и единиц, в мире логики, связей и процессов важно не иметь неопределенностей. Большинство языков программирования требуют, чтобы вы полностью определили то, с чем будете работать, иначе компилятор не сможет понять, какой код нужен в итоге. Потому в таких языках любая неопределенность — это, скорее всего, потенциальная ошибка, которую необходимо исправить еще до того, как запустится код. Но JavaScript в этом плане стоит особняком.
Поскольку исторически компиляция скриптов проходит в два прохода на первом обрабатываются декларации переменных, а на втором выполняется код, где уже и происходит присваивание значений, то вполне вероятны ситуации, когда произошла декларация, но отсутствует присвоенное значение. А это означает, что в коде надо предусматривать отсутствие значений. Для таких ситуаций в JavaScript предусмотрительно введено специальное состояние —
undefined.
Залучити нових та утримати наявних клієнтів за допомогою вау-сервіса – це можливо! Переходьте та дізнайтеся більше.
РЕЄСТРУЙТЕСЯ!
В этом можно провести параллель с особым объектом null, который мы рассматривали ранее («Null в JavaScript: как не сломать себе шею на ровном месте»). Однако смысл и значение undefined отличаются. Как — мы рассмотрим дальше.
2. Что такое undefined в JavaScript?
Итак, что же «специального» в состоянии undefined? Начнем с того, что общих правил компьютерного кода JavaScript не нарушает и нарушать не может, а значит то, что в других языках считается ошибкой с неопределенным типом/значением
так как приводит к ошибке выполнения машинного кода, в JavaScript каким-то образом трансформируется во вполне определенный тип.
Формально говоря, в JavaScript нет задекларированных и неопределенных переменных, так как в момент декларации им формально присваивается значение undefined. Нам — не англоговорящим — это понять легче, так как для нас undefined — это явно другое слово, нежели «неопределенный». А вот для англоговорящих тут может возникнуть путаница, так как на уровне формальной логики это выглядит как:
В JS для обозначения состояния неопределенного значения используется специальный тип глобальной переменной, определенный как «неопределенный» со значением, определенным как «неопределенное».
(На самом деле для англоязычных все еще запутаннее, так как в случае попытки использовать незадекларированный идентификатор получается ошибка «... is not defined», что по смыслу аналогично undefined, но по сути отражает совершенно другое состояние, которое к переменной undefined отношения не имеет).
Проверим в консоли браузера:
typeof undefined === ‘undefined’ // true
а поскольку все глобальные переменные являются свойствами глобального объекта window, то
undefined in window // true
И поскольку тип специфичен, то строгое сравнение истинно только при сравнении с самим собой (в этом он похож на null).
undefined === undefined // true undefined === false // false undefined === null // false undefined === ‘’ // false
Ну и надо обязательно сказать, что тип undefined — это примитив, который не имеет никаких признаков объекта то есть каких-либо свойств, которые можно адресовать. Есть только тип и значение. То есть любое действие, подобное
undefined.property, вызовет ошибку TypeError.
Особо пытливые могут заметить, что если уж undefined — это глобальная переменная (свойство window) и не константа так как была введена еще до ES6, то может возникнуть соблазн изменить ее значение. Ну что ж, попробуем:
window.undefined = 1; // 1, ошибки нет. Значит, не константа
window.undefined // undefined. Значит, значение не изменилось.
Вот такая себе неизменяемая переменная с особым типом. В отличие от null, который является специальным объектом, а не специальным типом.
3. Как работать с undefined?
Существует много споров, является гибкость и «толерантность» JavaScript его преимуществом или проклятием. Адепты строгих языков хватаются за голову при виде кода, позволяющего делать вызов неопределенной функции, а фронтендщики сладострастно потирают руки —
«ого, чо мы теперь можем мутить, теперь можем менять функции на ходу».
Что с JavaScript надо помнить постоянно, так это что гибкость требует разрешения неопределенности в каждой точке, где это может быть теоретически критично. Благо, присутствует конвертация типов, перехват ошибок, да и использование undefined не всегда приводит к ошибкам.
Давайте сначала посмотрим, во что может конвертироваться undefined.
Boolean(undefined) // false Number(undefined) // NaN String(undefined) // 'undefined'
Это значит, что в логических выражениях undefined будет работать как ложь (что очень удобно), в числовых — сделает результат NaN (что часто доставляет неудобства), а в строковых — добавит обычно нежелательный текст.
let x; x || false // false x + 5 // NaN x + ‘ mess’ // 'undefined mess'
Также необходимо помнить, что:
undefined == null // true undefined == 0 // false undefined == '' // false
и неочевидное
undefined == false // false
Такое неоднозначное поведение заставляет добавлять проверки на undefined везде, где теоретически возможно появление такого значения. Вот вам и плата за гибкость.
«Ну и что, — возразите вы, — разве сложно добавить проверки переменных?».
В целом не сложно, даже если учитывать замусоривание кода рутинными операциями. Однако этот же тип используется как индикатор отсутствия адресуемого поля объекта или индекса массива. И вот тут уже начинаются сложности, поскольку у объектов может быть много свойств.
Они не объявляются как переменные, они могут добавляться/удаляться по ходу дела, а проверять каждый раз наличие конкретного свойства затратно, плюс — свойство может само по себе иметь значение undefined, плюс — имя свойства может быть ‘undefined’ как в объекте window, но вполне уже изменяемое:
const x = {};
x.something // undefined
x.undefined // undefined
undefined in x // false
x.undefined = 5;
x.undefined // 5
undefined in x // true
Самое неприятное в этом то, что если вы ожидаете объект по какому-то свойству, а он отсутствует, то попытка адресовать ожидаемое свойство отсутствующего объекта вызовет ошибку типа:
const x = {};
x.something.any //TypeError: Cannot read properties of undefined
И получается, что вместо очень удобной записи цепочки свойств
x.something.any.value
приходится до свойства value добираться в несколько итераций. К счастью, в стандарт языка уже добавлен оператор опциональной последовательности ?. свежие версии всех браузеров его поддерживают, кроме Internet Explorer, который возвращает изящность адресации возможно отсутствующих объектов:
x?.something?.any?.value
Такая запись уже не вызовет сбоя, если свойство something или any оказалось undefined или null. Однако злоупотреблять таким оператором не стоит, поскольку надо помнить о дополнительных проверках у него «под капотом», что делает его медленнее стандартной адресации свойств.
4. Все еще страннее для массивов
Если для объектов отсутствие свойства означает, что оно буквально отсутствует и не может быть найдено в списке ключей, то у массива есть три варианта неопределенности:
- если индекс выходит за пределы размера массива;
- если индекс находится в пределах размера массива, но элемент не инициализирован. Такой элемент называется пустым слотом (
empty); - если индекс находится в пределах размера массива, и элемент имеет значение
undefined.
Во всех трех случаях обращение по индексу вернет значение undefined. Однако в методах массивов пустые слоты ведут себя иначе. В отличие от декларируемых переменных элементы массивов не имеют по умолчанию ссылки на глобальную переменную undefined, но имеют зарезервированное место.
Обращение к массиву по индексу пустого слота даст undefined, поскольку обращение к элементу массива — это функция и она должна вернуть один из существующих типов значений. Но внутри собственных методов массив различает отсутствие значения и пропускает такие слоты в принципе.
Получается еще один «undefined», но не тот, что undefined, и не тот, что «not defined»надеюсь, вы понимаете логику JavaScript.
Например:
const x = Array(2); // x -> [empty × 2] x.length // 2 x[0] // undefined x[1] // undefined x[1] = undefined // x -> [empty, undefined] x.map(d => +d) // [empty, NaN]
Как видите, пустой слот остается пустым до тех пор, пока ему явно не присвоят значение.
5. Скрытый тормоз
Есть с undefined типом неочевидная и в разных браузерах по-разному работающая вещь: это скорость выборки из объекта или массива.
Что не так с объектами: когда вы запрашиваете несуществующее свойство объекта, то чтобы вернуть значение undefined, движку придется проверить все существующие собственные свойства объекта, потом свойства его прототипа, потом прототипа прототипа и так до последней «инстанции» — типа Object. Если количество свойств объекта велико, задержка может быть очень существенной. Это также надо помнить и с оператором опциональной последовательности, который был упомянут выше.
Что не так с массивами: тут эффект менее очевиден, поскольку выборка идет по индексу и, казалось бы, какая разница, какое в массиве значение. Но современные JavaScript-движки кроме компиляции кода занимаются еще и его оптимизацией, в том числе и во время выполнения.
И если говорить упрощенно, когда в массиве только однотипные значения, оптимизатор выключает проверки типа для каждого элемента массива, а поскольку практически любая операция в JavaScript начинается с таких проверок, то на массовых операциях в массивах достигается очень существенная экономия по времени. Но если хоть один элемент массива имеет отличающийся тип (а undefined — это особый тип), то такая оптимизация не выполняется.
Избегаем неопределенностей (Заключение)
Учитывая неоднозначность приведения undefined к типам, проблем с ошибками TypeError, пагубное влияние на быстродействие, да и вообще смысл близкий к состоянию ошибки, логично предположить, что лучше избегать состояний undefined.
Делать это можно несколькими путями.
Планируемые структуры объектов по возможности должны содержать все ключи, по которым могут адресоваться свойства, а отсутствующие значения для этих свойств лучше кодировать специальными значениями по умолчанию.
Везде, где нет необходимости менять переменную, объявить ее как константу. Поскольку константа всегда должна быть инициализирована значением, компилятор подскажет вам на этапе запуска, где вы забыли это сделать.
const x = ‘значение’;
Отказаться от объявления переменных через var в пользу блоковых let. Так проще контролировать их использованиеда и на быстродействии сказывается. Mozilla, например, прямо рекомендует не использовать var.
for (let prop in obj) { … }
В функциях пользоваться параметрами по умолчанию.
function x(data = ‘значение’) { … }
Декомпозиция объектов также позволяет проще задавать значения по умолчанию, однако тут стоит помнить о скорости работы объектов с отсутствующими свойствами:
function x(data = {}) {
const { y = ‘значение’ } = data;
…
}
И, конечно, не забывать возможность рассматривать отсутствие свойства как ошибку, и превентивно обрабатывать ее через try/catch.
Когда новички начинают изучать JavaScript, то иногда сталкиваются с одной интересной проблемой. Они зачастую не могут понять разницу между undefined и null. Ведь null и undefined представляют собой пустые значения. Даже операция сравнения null == undefined выводит true. Давайте попробуем разобраться в том, что из себя всё-таки представляет undefined.
Содержание статьи:
- Введение.
- Что такое undefined.
- Что влияет на появление undefined.
- Undefined в массивах.
- Разница между null и undefined.
- Заключение.
Введение
Большинство современных языков вроде Ruby, Python и Java имеют только одно значение null (null или nill), что совершенно логично.
В случае с JavaScript интерпретатор возвращает undefined при попытке доступа к переменной или свойству объекта, которые ещё не былы заданы. Пример:
|
let company; company; // => undefined let person = { name: ‘John Snow’ }; person.age; // => undefined |
Что же касается null, это значение представляет собой ссылку на отсутствующий объект. В JavaScript не устанавливается значение null для переменных или свойств объекта.
Некоторые встроенные методы, такие как String.prototype.match(), могут возвращать значение null для обозначения отсутствующего объекта:
|
let array = null; array; // => null let movie = { name: ‘Starship Troopers’, musicBy: null }; movie.musicBy; // => null ‘abc’.match(/[0—9]/); // => null |
Поскольку в JavaScript у разработчиков есть возможность получить доступ к неинициализированным значениям, это может вызвать некоторые ошибки.
Часто такие действия приводят к ошибке undefined, что ломает весь скрипт. Наиболее часто при этом возникают следующие ошибки:
- typeError:
undefinedне является функцией; - typeError: невозможно прочитать
'<prop-name>'свойствоundefined; - другие подобные ошибки.
А иронию этой шутки может понять только разработчик JavaScript:
|
function undefined() { // проблема решена |
Чтобы избежать ошибок, связанных с undefined, нужно понимать, когда эта ошибка может возникать, и снизить риски ее появления в вашем приложении.
В JavaScript есть 6 примитивных типов:
- Boolean. У этого типа есть два значения:
true(истина) иfalse(ложь). Как правило, логический типbooleanиспользуется для хранения значения типа да/нет. - Number. Тип число используется как для целых, так и для дробных чисел (1, 6.7, 0xFF);
- String. В JavaScript любые текстовые данные являются строками. В JavaScript нет разницы между двойными и одинарными кавычками:
var text = "строка"/var texts = 'строки'; - Symbol. Новый примитивный тип данных
Symbolслужит для создания уникальных идентификаторов:Symbol("name")(начиная с ES2015); - Null. Значение
nullне относится ни к одному из типов выше, а образует свой отдельный тип, состоящий из единственного значенияnull; - Undefined. Значение
undefined, как иnull, образует свой собственный тип, состоящий из одного этого значения. Если переменная объявлена, но в неё ничего не записано, то её значение как раз и естьundefined(значение не присвоено).
Отдельно от всех стоит тип Object (объект), который используется для организации данных и объявления более сложных сущностей. Пример:
|
var human = { id: 12884002, age: 28, name: ‘Bob’, favorite_song: ‘Photograph, by Nickelback’ |
Объект может иметь любые свойства до тех пор, пока они находятся внутри {фигурных скобок}.
Среди этих 6 примитивных типов undefined является специальным значением. Согласно спецификации ECMAScript, undefined используется тогда, когда переменной не присвоено значение.
Этот стандарт четко определяет, что значение undefined можно получить при доступе к неинициализированным переменным, несуществующим свойствам объекта, несуществующим элементам массива и тому подобному. Например:
|
let number; number; // => undefined let movie = { name: ‘Interstellar’ }; movie.year; // => undefined let movies = [‘Interstellar’, ‘Alexander’]; movies[3]; // => undefined |
Пример выше показывает, что undefined выводится при попытке доступа к:
- неинициализированной переменной
number; - несуществующему свойству объекта
movie.year; - несуществующему элементу массива
movies[3].
Оператор typeof возвращает строку undefined для неопределенного значения:
|
typeof undefined === ‘undefined’; // => true |
Оператор typeof отлично подходит для проверки значения undefined у переменной:
|
let nothing; typeof nothing === ‘undefined’; // => true |
Что влияет на появление undefined
Неинициализированная переменная
Объявленная переменная, которая еще не имеет значения (неинициализированна), по умолчанию undefined.
Пример:
|
let myVariable; myVariable; // => undefined |
Переменная myVariable уже объявлена, но еще не имеет присвоенного значения. Попытка обратиться к переменной закончится выводом undefined.
Эффективный подход к решению проблем неинициализированных переменных — это по возможности присвойте им начальное значение. Чем меньше переменная существует в неинициализированном состоянии, тем лучше. В идеале вы сразу же присваиваете значение myVariable = 'Стартовое значение’, хотя это не всегда возможно.
Отдавайте предпочтение const и let, а от var нужно постепенно отказываться
Некоторые разработчики считают, что одной из лучших особенностей ECMAScript 2015 является новый способ объявления переменных путём использования const и let. Это большой шаг вперед, так как объявленные таким способом объекты/переменные находятся в области видимости, ограниченной текущим блоком кода (в отличие от старой функции var), и находятся во временной мертвой зоне до момента присвоения им конкретного значения.
При использовании неизменяемых данных (констант) рекомендуется инициализировать их как const. Это создаст неизменную связку.
Одна из приятных особенностей const заключается в том, что существует возможность назначить начальное значение константы. Пример:
|
const myVariable = ‘initial’ |
Таким образом, константа не подвергается неинициализированному состоянию, и получить значение undefined просто невозможно.
Давайте протестируем функцию, которая проверяет, является ли слово палиндромом:
|
function isPalindrome(word) { const length = word.length; const half = Math.floor(length / 2); for (let index = 0; index < half; index++) { if (word[index] !== word[length — index — 1]) { return false; return true; isPalindrome(‘madam’); // => true isPalindrome(‘hello’); // => false |
Переменным length и half значение присваивается один раз. В этом случае объявить их как const будет логично, так как их значение не будет меняться.
Если вам нужно будет менять значение переменной несколько раз, то обозначьте ее как let. По возможности сразу присваивайте ей начальное значение. Пример:
А когда использовать var? Некоторые разработчики, ссылаясь на стандарт ES2015, предлагают перестать использовать var.
Проблема использования var заключается в так называемом поднятии переменных (англ. variable hoisting). Где бы ни находилось объявление, это равнозначно тому, что переменную объявили в самом начале кода.
|
function bigFunction() { // код… myVariable; // => undefined // код… var myVariable = ‘Initial value’; // код… myVariable; // => ‘Initial value’ bigFunction(); |
В этом случае переменная myVariable содержит undefined до получения значения:
|
myVariable = ‘Initial value’ |
Если же переменную объявить как let, то она останется недоступной до момента присвоения ей значения. Это происходит из-за того, что переменная находится во временной мертвой зоне. В этом случае вероятность получения значения undefined близится к нулю.
Вышеприведенный пример с let (вместо var) выведет ReferenceError, потому что переменная во временной мертвой зоне недоступна.
|
function bigFunction() { // код… myVariable; // => выводится ‘ReferenceError: myVariable is not defined’ // код… let myVariable = ‘Initial value’; // код… myVariable; // => ‘Initial value’ bigFunction(); |
Использование const или let для неизменяемых привязок позволит снизить риски получения значения undefined при написании кода.
Усильте связность
Связность характеризует степень взаимосвязанности элементов модуля (пространства имен, класса, метода, блока кода). Есть два вида связанности — сильная и слабая.
Сильная связность предпочтительнее, поскольку она предполагает, что элементы модуля должны фокусироваться исключительно на одной задаче. Это поможет модулю быть:
- сфокусированным и понятным — становится легче понять, какую задачу выполняет модуль;
- легко поддерживаемым и поддающимся рефакторингу — изменение модуля влияет на меньшее количество элементов;
- многоразовым — если модуль ориентирован на выполнение одной задачи, то его легче использовать повторно;
- простым для тестирования — вам будет проще протестировать модуль, ориентированный на одну задачу.
Сильная связность, сопровождающаяся слабым зацеплением, является характеристикой хорошо спроектированной системы.
Блок кода сам по себе может считаться небольшим модулем. Чтобы извлечь выгоду из преимуществ сильной связности, нужно держать переменные как можно ближе к блоку кода, который их использует.
Например, если переменная нужна только для использования в определенном блоке кода, тогда можно объявить её и разрешить существовать только внутри нужного блока (используя const или let для объявления).
Одним из классических примеров излишнего срока жизни переменных является использование цикла for внутри функции:
|
function someFunc(array) { var index, item, length = array.length; // некоторый код… // некоторый код… for (index = 0; index < length; index++) { item = array[index]; // некоторый код… return ‘some result’; |
index, item и length — эти переменные объявляются в начале тела функции. Однако они используются только ближе к концу функции. Так в чем проблема с таким подходом?
Всё время между объявлением переменной в начале и до использования её в цикле for переменные index и item не инициализируются и выводят undefined. По этому примеру видно, что срок жизни переменной является необоснованно длинным.
Разумнее будет переместить переменные ближе к месту их применения:
|
function someFunc(array) { // некоторый код… // некоторый код… const length = array.length; for (let index = 0; index < length; index++) { const item = array[index]; // некоторый код return ‘some result’; |
Переменные index и item существуют только в области действия блока for. У них нет никакого значения за его пределами.
Переменная (const) length объявляется в месте ее использования.
Почему модифицированная версия лучше первоначальной? Посмотрим:
- значения не подвергаются неинициализированному состоянию, поэтому нет риска получения
undefined; - перемещение переменных ближе к месту их использования увеличивает читаемость кода;
- элементы с высокой связанностью кода легче рефакторить и извлекать при необходимости в отделенные функции.
Доступ к несуществующему свойству
При доступе к несуществующему свойству объекта JavaScript возвращает undefined.
Обратите внимание на пример:
|
let favoriteMovie = { title: ‘Blade Runner’ favoriteMovie.actors; // => undefined |
favoriteMovie — это объект с одним значением title. Доступ к несуществующему свойству actors путём использования favoriteMovie.actors приведет к выводу undefined.
Сам по себе доступ к несуществующему свойству не вызывает ошибки. Реальная проблема возникает при попытке получить значение из несуществующего свойства. Это сообщение об ошибке отражает наиболее распространенную ловушку, связанную с undefined:
|
TypeError: Cannot read property <prop> of undefined |
Немного изменим предыдущий фрагмент кода, чтобы пояснить поведение TypeError:
|
let favoriteMovie = { title: ‘Blade Runner’ favoriteMovie.actors[0]; // TypeError: Cannot read property ‘0’ of undefined |
В favoriteMovie нет свойства actors, поэтому использование favoriteMovie.actors приведет к undefined.
В результате доступ к элементу со значением undefined с помощью выражения favoriteMovie.actors [0] вызывает TypeError.
Возможности JavaScript, которые позволяют получить доступ к несуществующим свойствам, являются источником путаницы. Свойство может быть установлено или может отсутствовать. Идеальным решением этой проблемы будет установка правил для объекта, которые позволят ему содержать только свойства с явно заданными значениями.
К сожалению, не всегда возможно контролировать объекты, с которыми приходится работать. Такие объекты могут иметь разный набор свойств в различных сценариях. Таким образом, нужно обрабатывать все эти сценарии вручную.
Попробуем реализовать функцию append(array, toAppend), которая добавляет в начале и/или в конце массива новые элементы. Параметр toAppend принимает объект со свойствами:
first: элемент, вставленный в начале массива;last: элемент, вставленный в конец массива.
Функция возвращает новый экземпляр массива, не изменяя исходный (т.е. это чистая функция).
Функция append() может выглядеть так:
|
function append(array, toAppend) { const arrayCopy = array.slice(); if (toAppend.first) { arrayCopy.unshift(toAppend.first); if (toAppend.last) { arrayCopy.push(toAppend.last); return arrayCopy; append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5] append([‘Hello’], { last: ‘World’ }); // => [‘Hello’, ‘World’] append([8, 16], { first: 4 }); // => [4, 8, 16] |
Поскольку объект toAppend может удалять первые или последние свойства, необходимо проверить, существуют ли эти свойства в toAppend.
Доступ к свойству приведет к получению undefined, если его не существует. Можно проверить существует ли первое или последнее свойство, чтобы избежать undefined. Как вариант, это можно сделать с помощью условий if(toAppend.first){} и if(toAppend.last){}.
Но не будем торопиться. В этом подходе есть серьезный недостаток. undefined, также как false, null, 0, NaN и ' ' являются ложными значениями (falsy values).
В текущей реализации append() функция не позволяет вставлять ложные элементы:
|
append([10], { first: 0, last: false }); // => [10] |
0 и false — ложные значения, потому что if (toAppend.first) {} и if (toAppend.last) {} фактически сравниваются с ложными значениями и эти элементы не вставляются в массив. Функция возвращает исходный массив [10] без изменений.
Последующие подсказки объясняют, как правильно проверить существование свойства.
Проверьте, существует ли свойство
К счастью, JavaScript предлагает множество способов определить, имеет ли объект определенное свойство:
obj.prop !== undefinedпозволяет сравнивать объект напрямую сundefined;typeof obj.prop !== 'undefined'проверяет тип значения свойства;obj.hasOwnProperty('prop')проверяет объект на наличие собственного свойства;'prop' in objпроверяет объект на наличие собственного или унаследованного свойства.
Рекомендацией в этом случае будет использование in оператора. У него простой и понятный синтаксис. Присутствие оператора in указывает на четкое намерение проверить, имеет ли объект определенное свойство, не обращаясь к фактическому значению этого свойства.
Использование obj.hasOwnProperty('prop') — это также неплохое решение. Оно немного длиннее, чем оператор in , и проверяет только собственные свойства объекта.
Эти два способа сравнения с undefined могут сработать… Но кажется, что использование obj.prop !== undefined и typeof obj.prop !== 'undefined' является не очень хорошим решением и может привести к непонятным последствиям при прямом сравнении с undefined.
Давайте попробуем улучшить функцию append(array, toAppend), используя in оператора:
|
function append(array, toAppend) { Добавить в заметки чтобы посмотреть позже? const arrayCopy = array.slice(); if (‘first’ in toAppend) { arrayCopy.unshift(toAppend.first); if (‘last’ in toAppend) { arrayCopy.push(toAppend.last); return arrayCopy; append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5] append([10], { first: 0, last: false }); // => [0, 10, false] |
'first' in toAppend ( как и 'last' in toAppend) выводит true, независимо от существующего свойства. В других случаях выводится — false.
Использование оператора in устраняет проблему со вставкой ложных элементов 0 и false. Теперь добавление элементов в начале и в конце массива [10] приводит к ожидаемому результату [0, 10, false].
Выполните деструктурирование доступа к свойствам объекта
При доступе к свойству объекта иногда необходимо указать значение по умолчанию, если свойство не существует.
Можно использовать in с тернарным оператором, чтобы получить следующий результат:
|
const object = { }; const prop = ‘prop’ in object ? object.prop : ‘default’; prop; // => ‘default’ |
Использование синтаксиса тернарного оператора становится сложным, когда число проверяемых свойств увеличивается. Для каждого свойства нужно создать новую строку кода, чтобы обработать значение, увеличивая опасную кучу похожих тернарных операторов.
Чтобы использовать более простой подход, давайте познакомимся с функцией ES2015, называемой деструктурирование объекта.
Деструктурирование объекта позволяет напрямую извлекать значения свойств объекта непосредственно в переменные и устанавливать значение по умолчанию, если свойство не существует. Это удобно использовать для исключения прямого контакта с undefined.
Действительно, извлечение свойств теперь выглядит совершенно по-другому:
|
const object = { }; const { prop = ‘default’ } = object; prop; // => ‘default’ |
Чтобы увидеть это в действии, определяем полезную функцию, обертывающую строку в кавычки. quote(subject, config) принимает первый аргумент как строку, которую нужно обернуть. Второй аргумент config — это объект со следующими свойствами:
char— символ цитаты, например,'(одинарная кавычка) или"(двойная кавычка). По умолчанию —";skipIfQuoted— логическое значение, чтобы пропустить цитирование, если строка уже цитируется. По умолчанию —true.
Применяя преимущества деструктурирования объекта, давайте реализуем quote():
|
function quote(str, config) { const { char = ‘»‘, skipIfQuoted = true } = config; const length = str.length; if (skipIfQuoted && str[0] === char && str[length — 1] === char) { return str; return char + str + char; quote(‘Hello World’, { char: ‘*’ }); // => ‘*Hello World*’ quote(‘»Welcome»‘, { skipIfQuoted: true }); // => ‘»Welcome»‘ |
const { char = '"', skipIfQuoted = true } = config в одной строкe извлекает свойства char и skipIfQuoted из объекта config.
Если некоторые свойства недоступны в объекте config, деструктурирование задает значения по умолчанию: '"' для char и false для skipIfQuoted.
К счастью, функцию можно даже улучшить.
Давайте переместим деструктурирование прямо в раздел параметров. И установим значение по умолчанию (пустой объект { }) для параметра config, чтобы пропустить второй аргумент, когда будет достаточно значений по умолчанию.
|
function quote(str, { char = ‘»‘, skipIfQuoted = true } = {}) { const length = str.length; if (skipIfQuoted && str[0] === char && str[length — 1] === char) { return str; return char + str + char; quote(‘Hello World’, { char: ‘*’ }); // => ‘*Hello World*’ quote(‘Sunny day’); // => ‘»Sunny day»‘ |
Обратите внимание, что деструктурирующее присваивание заменяет параметр config в сигнатуре функции. Это позволяет сделать параметр quote() на одну строку короче. Параметр = {} в правой части деструктурирующее присваивание гарантирует, что используется пустой объект, если второй аргумент не указан вообще quote('Sunny day').
Деструктурирование объектов — это мощная функция, которая эффективно обрабатывает извлечение свойств из объектов. Это хорошо, когда есть возможность указать значение по умолчанию, которое будет возвращено при попытке доступа к несуществующему свойству. В результате вы избегаете возникновения значения undefined и всех проблем, которые могут быть связаны с ним.
Установите объекту свойства по умолчанию.
Если нет необходимости создавать переменные для каждого свойства, такого как назначение деструктурирования, объект, который пропускает некоторые свойства, может быть заполнен значениями по умолчанию.
Функция ES2015 Object.assign (target, source1, source2, ...) копирует значения всех перечисленных собственных свойств из одного или нескольких исходных объектов в целевой объект. После чего возвращает целевой объект.
Например, вам нужно получить доступ к данным объекта unsafeOptions, который не всегда содержит полный набор свойств.
Чтобы избежать появления значения undefined при доступе к несуществующему свойству из unsafeOptions, можно сделать следующие настройки:
- определить объект
defaults, который содержит значения свойств по умолчанию; - вызвать функцию
Object.assign({ }, defaults, unsafeOptions), чтобы создать новый объектoptions. Новый объект получает все свойства изunsafeOptions, недостающие берутся изdefaults.
|
const unsafeOptions = { fontSize: 18 const defaults = { fontSize: 16, color: ‘black’ const options = Object.assign({}, defaults, unsafeOptions); options.fontSize; // => 18 options.color; // => ‘black’ |
unsafeOptions содержит только свойство fontSize. Объект defaults указывает значения по умолчанию для fontSize и color.
Object.assign() принимает первый аргумент как целевой объект {}. Целевой объект получает значение свойства fontSize из объекта-источника unsafeOptions. А значение свойства color — из объекта-источника defaults, поскольку unsafeOptions не содержит color. Важен порядок, в котором перечислены исходные объекты: первый пришёл — первый ушёл.
Теперь возможно получить доступ к любому свойству объекта options, включая options.color, который изначально был недоступен в unsafeOptions.
К счастью, существует более простой и легкий способ установить значения по умолчанию для свойств объекта. Рекомендуется использовать новую функцию JavaScript, которая позволяет расширять свойства при инициализации объектов.
Вместо вызова Object.assign() используйте синтаксис распространения объекта, чтобы скопировать в целевой объект все собственные и перечисляемые свойства из исходных объектов:
|
const unsafeOptions = { fontSize: 18 const defaults = { fontSize: 16, color: ‘black’ const options = { ...defaults, ...unsafeOptions options.fontSize; // => 18 options.color; // => ‘black’ |
Инициализатор объекта распространяет свойства из исходных объектов defaults и unsafeOptions. Важен порядок, в котором указаны исходные объекты: свойства более позднего исходного объекта перезаписывают более ранние.
Заполнение неполного объекта значением по умолчанию является эффективной стратегией, обеспечивающей безопасность и стабильность вашего кода. Независимо от ситуации, объект всегда содержит полный набор свойств, и появление undefined невозможно.
Параметры функции
Параметры функции по умолчанию установлены на undefined.
Обычно функция, имеющая определенные параметры, должна вызываться с одинаковым количеством аргументов. В таком случае параметры получают ожидаемые значения:
|
function multiply(a, b) { a; // => 5 b; // => 3 return a * b; multiply(5, 3); // => 15 |
При вызове функции multiply(5, 3) параметры a и b получают соответствующие значения 5 и 3. Умножение рассчитывается как ожидаемое: 5 * 3 = 15.
Что происходит, когда пропускается аргумент при вызове? Параметр внутри функции получает значение undefined.
Давайте немного изменим предыдущий пример, вызвав функцию только с одним аргументом:
|
function multiply(a, b) { a; // => 5 b; // => undefined return a * b; multiply(5); // => NaN |
Функция multiply(a, b) { } определяется двумя параметрами: a и b. Вызов multiply(5) выполняется с помощью одного аргумента, в результате параметр равен 5, а параметр b получает значение undefined.
Используйте значение параметра по умолчанию
Иногда функция имеет не полный набор аргументов при ее вызове. В таком случае нужно установить значения по умолчанию для параметров, у которых нет значений.
Учитывая предыдущий пример, давайте сделаем некоторые улучшения. Если параметр b — undefined, то по умолчанию ему присваивается значение 2:
|
function multiply(a, b) { if (b === undefined) { b = 2; a; // => 5 b; // => 2 return a * b; multiply(5); // => 10 |
Функция вызывается одним аргументом multiply(5). Первоначально параметр a равен 2, а b соответствует undefined. Выражение проверяет, не равно ли b параметру undefined. Если это так, b получает значение по умолчанию 2.
Хотя представленный способ назначения значений по умолчанию работает, не рекомендуется сравнивать значения напрямую сundefined. Это уже будет выглядеть как хак.
Лучшим подходом является использование параметров по умолчанию из ES2015. Он краткий и не содержит в себе прямого сравнения значений с undefined.
Установка параметров по умолчанию для b не выводит значения undefined:
|
function multiply(a, b = 2) { a; // => 5 b; // => 2 return a * b; multiply(5); // => 10 multiply(5, undefined); // => 10 |
Значение b = 2 в сигнатуре функции гарантирует, что если b получит значение undefined, то по умолчанию параметр изменится на 2.
Функция параметров по умолчанию из ES2015 интуитивно понятна и выразительна. Всегда используйте ее для установки значений по умолчанию для необязательных параметров.
Возвращаемое значение функции
В JavaScript функция, которая не имеет оператора return, возвращает значение undefined:
|
function square(x) { const res = x * x; square(2); // => undefined |
Функция square() не возвращает результаты вычислений. Результат — undefined.
Такая же ситуация возникает, когда оператор return присутствует, но без какого-либо выражения рядом:
|
function square(x) { const res = x * x; return; square(2); // => undefined |
return; выполняется, но он не возвращает ничего. Результат вызова — undefined.
Указывая значение для return, можно получить желаемый результат:
|
function square(x) { const res = x * x; return res; square(2); // => 4 |
Теперь вызов функции выведет нужное значение.
Не доверяйте автоматической расстановке точки с запятой
Следующий список операторов в JavaScript должен заканчиваться точкой с запятой (;):
- пустой оператор;
- операторы:
let,const,var,import,export; - утверждение значения;
- утверждение отладчика;
- утверждения:
continue,break,throw,return.
Если вы используете одно из вышеуказанных утверждений, обязательно нужно ставить точку с запятой в конце:
|
function getNum() { // Notice the semicolons at the end let num = 1; return num; getNum(); // => 1 |
Как можно видеть, в конце объявленияlet и оператором return обязательно ставится точка с запятой.
Что происходит, когда вы не хотите указывать точки с запятой? Например, чтобы уменьшить размер исходного файла.
В такой ситуации ECMAScript предоставляет механизм автоматической установки точки с запятой (ASI), который сделает всю работу за вас.
Используя ASI, вы можете удалить точки с запятой из предыдущего примера:
|
function getNum() { // Notice that semicolons are missing let num = 1 return num getNum() // => 1 |
Вышеприведенный текст является допустимым кодом JavaScript. Отсутствующие точки с запятой автоматически вставлены за вас.
На первый взгляд, это выглядит довольно многообещающе. Механизм ASI позволяет пропустить ненужные точки с запятой. Вы можете сделать код JavaScript меньше и более простым для чтения.
Но тут существует один небольшой, но раздражающий момент, который может создать ASI. Если между return и возвращаемым выражением стоит новая строка return n expression, ASI автоматически вставляет точку с запятой return; n expression.
К чему приведет пустой оператор return ? Функция вернет значение undefined. Если вы не знаете, как работает механизм ASI, то неожиданное появление значения undefined может ввести в заблуждение.
Например, давайте изучим возвращаемое значение вызываемой функции getPrimeNumbers():
|
function getPrimeNumbers() { return [ 2, 3, 5, 7, 11, 13, 17 ] getPrimeNumbers() // => undefined |
Между return и значением массива есть новая строка. JavaScript автоматически вставляет точку с запятой после оператора return, интерпретируя код следующим образом:
|
function getPrimeNumbers() { return; [ 2, 3, 5, 7, 11, 13, 17 ]; getPrimeNumbers(); // => undefined |
В таком случае мы получим значение undefined.
Проблема решена путем удаления новой строки между return и массивом:
|
function getPrimeNumbers() { return [ 2, 3, 5, 7, 11, 13, 17 getPrimeNumbers(); // => [2, 3, 5, 7, 11, 13, 17] |
Оператор void
Оператор void выполняет выражение и возвращает undefined вне зависимости от результата:
|
void 1; // => undefined void (false); // => undefined void {name: ‘John Smith’}; // => undefined void Math.min(1, 3); // => undefined |
Одним из вариантов использования оператора void является переопределение результата выполнения выражения и возврат undefined, в случае возникновения побочных эффектов выполнения функции.
Undefined в массивах
Вы получаете undefined при попытке доступа к элементу массива с индексом вне пределов массива.
|
const colors = [‘blue’, ‘white’, ‘red’]; colors[5]; // => undefined colors[—1]; // => undefined |
Массив colors имеет 3 элемента, поэтому корректные индексы равны 0, 1 и 2.
Поскольку в индексах массива 5 и -1 нет элементов, значения colors[5] and colors[-1] получают значение undefined.
В JavaScript вы можете столкнуться с так называемыми разреженными массивами. Эти массивы имеют пробелы, то есть на некоторых индексах не определены никакие элементы.
Когда делаем попытку получить доступ к пустому значению в разреженном массиве, на выходе получаем undefined.
Следующий пример генерирует разреженные массивы и пытается получить доступ к их пустым элементам:
|
const sparse1 = new Array(3); sparse1; // => [<empty slot>, <empty slot>, <empty slot>] sparse1[0]; // => undefined sparse1[1]; // => undefined const sparse2 = [‘white’, ,‘blue’] sparse2; // => [‘white’, <empty slot>, ‘blue’] sparse2[1]; // => undefined |
sparse1 создается путем вызова конструктора Array с числовым первым аргументом. Он имеет 3 пустых элемента.
sparse2 создается с литералом массива, второй элемент которого отсутствует. В любом из этих разреженных массивов доступ к пустому элементу оценивается как undefined.
При работе с массивами, чтобы избежать появления значения undefined, обязательно используйте допустимые индексы массивов или вообще не создавайте разреженные массивы. А вообще не забывайте, что массивы начинаются с нуля.
Разница между undefined и null
Часто у людей возникает разумный вопрос: в чем основное отличие между undefined и null? Оба специальных значения означают пустое состояние.
Основное различие заключается в том, что undefined представляет значение переменной, которая еще не была инициализирована, а null представляет собой намеренное отсутствие объекта.
Рассмотрим разницу на примерах.
Переменная number определена, но ей не назначено начальное значение:
|
let number; number; // => undefined |
Числовая переменная не определена, что явно указывает на неинициализированную переменную.
То же самое произойдёт при попытке доступа к несуществующему свойству объекта:
|
const obj = { firstName: ‘Dmitri’ }; obj.lastName; // => undefined |
Поскольку свойство lastName не существует в obj, JavaScript корректно оценивает obj.lastName как undefined.
Нужно понимать, что переменная должна ожидать возвращение объекта функции. Но по какой-то причине создание объекта невозможно. В этом случае null является значимым индикатором недостающего объекта.
Например, clone() — это функция, которая клонирует простой объект JavaScript. Ожидается, что функция вернет объект:
|
function clone(obj) { if (typeof obj === ‘object’ && obj !== null) { return Object.assign({}, obj); return null; clone({name: ‘John’}); // => {name: ‘John’} clone(15); // => null clone(null); // => null |
Однако clone() может быть вызван с пустым аргументом: 15 или null (или вообще со значением null или undefined). В этом случае функция не может создать клон, поэтому возвращает null — индикатор отсутствующего объекта.
Оператор typeof делает различие между двумя значениями:
|
typeof undefined; // => ‘undefined’ typeof null; // => ‘object’ |
Строгий оператор равенства === правильно отличает undefined от null:
|
let nothing = undefined; let missingObject = null; nothing === missingObject; // => false |
Заключение
Существование undefined является следствием разрешительной природы JavaScript, которая позволяет использовать:
- неинициализированные переменные;
- несуществующие свойства или методы объекта;
- несуществующие индексы для доступа к элементам массива;
- вызов функции, которая ничего не возвращает.
В основном сравнение напрямую с undefined является плохой практикой, потому что вы, вероятно, полагаетесь на разрешенную, но плохую практику, упомянутую выше.
Стратегия борьбы с undefined заключается в уменьшении появления этого значения в вашем коде. И выработайте в себе привычку делать следующее:
- уменьшать использование неинициализированных переменных;
- делать жизненный цикл переменных коротким и близким к источнику их использования;
- по возможности назначать начальное значение переменным;
- стараться использовать
constилиlet; - использовать значения по умолчанию для некритичных параметров функции;
- проверять наличие свойств или заполнить небезопасные объекты по умолчанию;
- избегать использования разреженных массивов.
Время на прочтение
5 мин
Количество просмотров 397K
JavaScript может быть кошмаром при отладке: некоторые ошибки, которые он выдает, могут быть очень трудны для понимания с первого взгляда, и выдаваемые номера строк также не всегда полезны. Разве не было бы полезно иметь список, глядя на который, можно понять смысл ошибок и как исправить их? Вот он!
Ниже представлен список странных ошибок в JavaScript. Разные браузеры могут выдавать разные сообщения об одинаковых ошибках, поэтому приведено несколько примеров там, где возможно.
Как читать ошибки?
Перед самим списком, давайте быстро взглянем на структуру сообщения об ошибке. Понимание структуры помогает понимать ошибки, и вы получите меньше проблем, если наткнетесь на ошибки, не представленные в этом списке.
Типичная ошибка из Chrome выглядит так:
Uncaught TypeError: undefined is not a function
Структура ошибки следующая:
- Uncaught TypeError: эта часть сообщения обычно не особо полезна.
Uncaughtзначит, что ошибка не была перехвачена вcatch, аTypeError— это название ошибки. - undefined is not a function: это та самая часть про ошибку. В случае с сообщениями об ошибках, читать их нужно прямо буквально. Например, в этом случае, она значит то, что код попытался использовать значение
undefinedкак функцию.
Другие webkit-браузеры, такие как Safari, выдают ошибки примерно в таком же формате, как и Chrome. Ошибки из Firefox похожи, но не всегда включают в себя первую часть, и последние версии Internet Explorer также выдают более простые ошибки, но в этом случае проще — не всегда значит лучше.
Теперь к самим ошибкам.
Uncaught TypeError: undefined is not a function
Связанные ошибки: number is not a function, object is not a function, string is not a function, Unhandled Error: ‘foo’ is not a function, Function Expected
Возникает при попытке вызова значения как функции, когда значение функцией не является. Например:
var foo = undefined;
foo();
Эта ошибка обычно возникает, если вы пытаетесь вызвать функцию для объекта, но опечатались в названии.
var x = document.getElementByID('foo');
Несуществующие свойства объекта по-умолчанию имеют значение undefined, что приводит к этой ошибке.
Другие вариации, такие как “number is not a function” возникают при попытке вызвать число, как будто оно является функцией.
Как исправить ошибку: убедитесь в корректности имени функции. Для этой ошибки, номер строки обычно указывает в правильное место.
Uncaught ReferenceError: Invalid left-hand side in assignment
Связанные ошибки: Uncaught exception: ReferenceError: Cannot assign to ‘functionCall()’, Uncaught exception: ReferenceError: Cannot assign to ‘this’
Вызвано попыткой присвоить значение тому, чему невозможно присвоить значение.
Наиболее частый пример этой ошибки — это условие в if:
if(doSomething() = 'somevalue')
В этом примере программист случайно использовал один знак равенства вместо двух. Выражение “left-hand side in assignment” относится к левой части знака равенства, а, как можно видеть в данном примере, левая часть содержит что-то, чему нельзя присвоить значение, что и приводит к ошибке.
Как исправить ошибку: убедитесь, что вы не пытаетесь присвоить значение результату функции или ключевому слову this.
Uncaught TypeError: Converting circular structure to JSON
Связанные ошибки: Uncaught exception: TypeError: JSON.stringify: Not an acyclic Object, TypeError: cyclic object value, Circular reference in value argument not supported
Всегда вызвано циклической ссылкой в объекте, которая потом передается в JSON.stringify.
var a = { };
var b = { a: a };
a.b = b;
JSON.stringify(a);
Так как a и b в примере выше имеют ссылки друг на друга, результирующий объект не может быть приведен к JSON.
Как исправить ошибку: удалите циклические ссылки, как в примере выше, из всех объектов, которые вы хотите сконвертировать в JSON.
Unexpected token ;
Связанные ошибки: Expected ), missing ) after argument list
Интерпретатор JavaScript что-то ожидал, но не обнаружил там этого. Обычно вызвано пропущенными фигурными, круглыми или квадратными скобками.
Токен в данной ошибке может быть разным — может быть написано “Unexpected token ]”, “Expected {” или что-то еще.
Как исправить ошибку: иногда номер строки не указывает на правильное местоположение, что затрудняет исправление ошибки.
Ошибка с [ ] { } ( ) обычно вызвано несовпадающей парой. Проверьте, все ли ваши скобки имеют закрывающую пару. В этом случае, номер строки обычно указывает на что-то другое, а не на проблемный символ.
Unexpected / связано с регулярными выражениями. Номер строки для данного случая обычно правильный.
Unexpected; обычно вызвано символом; внутри литерала объекта или массива, или списка аргументов вызова функции. Номер строки обычно также будет верным для данного случая.
Uncaught SyntaxError: Unexpected token ILLEGAL
Связанные ошибки: Unterminated String Literal, Invalid Line Terminator
В строковом литерале пропущена закрывающая кавычка.
Как исправить ошибку: убедитесь, что все строки имеют правильные закрывающие кавычки.
Uncaught TypeError: Cannot read property ‘foo’ of null, Uncaught TypeError: Cannot read property ‘foo’ of undefined
Связанные ошибки: TypeError: someVal is null, Unable to get property ‘foo’ of undefined or null reference
Попытка прочитать null или undefined так, как будто это объект. Например:
var someVal = null;
console.log(someVal.foo);
Как исправить ошибку: обычно вызвано опечатками. Проверьте, все ли переменные, использованные рядом со строкой, указывающей на ошибку, правильно названы.
Uncaught TypeError: Cannot set property ‘foo’ of null, Uncaught TypeError: Cannot set property ‘foo’ of undefined
Связанные ошибки: TypeError: someVal is undefined, Unable to set property ‘foo’ of undefined or null reference
Попытка записать null или undefined так, как будто это объект. Например:
var someVal = null;
someVal.foo = 1;
Как исправить ошибку: это тоже обычно вызвано ошибками. Проверьте имена переменных рядом со строкой, указывающей на ошибку.
Uncaught RangeError: Maximum call stack size exceeded
Связанные ошибки: Uncaught exception: RangeError: Maximum recursion depth exceeded, too much recursion, Stack overflow
Обычно вызвано неправильно программной логикой, что приводит к бесконечному вызову рекурсивной функции.
Как исправить ошибку: проверьте рекурсивные функции на ошибки, которые могут вынудить их делать рекурсивные вызовы вечно.
Uncaught URIError: URI malformed
Связанные ошибки: URIError: malformed URI sequence
Вызвано некорректным вызовом decodeURIComponent.
Как исправить ошибку: убедитесь, что вызовы decodeURIComponent на строке ошибки получают корректные входные данные.
XMLHttpRequest cannot load some/url. No ‘Access-Control-Allow-Origin’ header is present on the requested resource
Связанные ошибки: Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at some/url
Эта проблема всегда связана с использованием XMLHttpRequest.
Как исправить ошибку: убедитесь в корректности запрашиваемого URL и в том, что он удовлетворяет same-origin policy. Хороший способ найти проблемный код — посмотреть на URL в сообщении ошибки и найти его в своём коде.
InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable
Связанные ошибки: InvalidStateError, DOMException code 11
Означает то, что код вызвал функцию, которую нельзя было вызывать в текущем состоянии. Обычно связано c XMLHttpRequest при попытке вызвать на нём функции до его готовности.
var xhr = new XMLHttpRequest();
xhr.setRequestHeader('Some-Header', 'val');
В данном случае вы получите ошибку потому, что функция setRequestHeader может быть вызвана только после вызова xhr.open.
Как исправить ошибку: посмотрите на код в строке, указывающей на ошибку, и убедитесь, что он вызывается в правильный момент или добавляет нужные вызовы до этого (как с xhr.open).
Заключение
JavaScript содержит в себе одни из самых бесполезных ошибок, которые я когда-либо видел, за исключением печально известной Expected T_PAAMAYIM_NEKUDOTAYIM в PHP. Большая ознакомленность с ошибками привносит больше ясности. Современные браузеры тоже помогают, так как больше не выдают абсолютно бесполезные ошибки, как это было раньше.
Какие самые непонятные ошибки вы встречали? Делитесь своими наблюдениями в комментариях.
P.S. Этот перевод можно улучшить, отправив PR здесь.
undefined в JavaScript — настоящий кошмар начинающего фронтендера. В чём отличие undefined и null, если даже сравнение null == undefined выводит true, и как обрабатывать ошибку undefined? Давайте разбираться.
Примечание Вы читаете улучшенную версию некогда выпущенной нами статьи.
- Что такое undefined в JavaScript
- Как избежать undefined в JavaScript
- Значение undefined в массивах
- Отличие null и undefined в JavaScript
- Заключение
undefined является специальным значением. Согласно спецификации ECMAScript, undefined в JavaScript можно получить при доступе к неинициализированным переменным, несуществующим свойствам объекта, несуществующим элементам массива, etc. Пример:
let number;
number; // => undefined
let movie = { name: 'Interstellar' };
movie.year; // => undefined
let movies = ['Interstellar', 'Alexander'];
movies[3]; // => undefined
Как видим, undefined выводится при попытке доступа к:
- неинициализированной переменной
number; - несуществующему свойству объекта
movie.year; - несуществующему элементу массива
movies[3].
Оператор typeof возвращает строку undefined для неопределённого значения:
typeof undefined === 'undefined'; // => true
Оператор typeof отлично подходит для проверки значения undefined в JavaScript:
let nothing;
typeof nothing === 'undefined'; // => true
Как избежать undefined в JavaScript
Неинициализированная переменная
Объявленная переменная, которая еще не имеет значения (не инициализирована), по умолчанию undefined. Пример:
let myVariable;
myVariable; // => undefined
Переменная myVariable уже объявлена, но ещё не имеет присвоенного значения. Попытка обратиться к ней закончится выводом undefined. Чтобы это исправить, достаточно присвоить переменной значение. Чем меньше переменная существует в неинициализированном состоянии, тем лучше.
Ниже приведены способы решения проблемы.
1. const и let вместо var
Объявленные таким образом объекты и переменные находятся в области видимости, ограниченной текущим блоком кода, и находятся во временной мёртвой зоне до момента присвоения им значения.
При использовании неизменяемых данных (констант) рекомендуется инициализировать их как const:
const myVariable = 'initial'
Константа не подвергается неинициализированному состоянию, и получить значение undefined в этом случае невозможно.
Если вам нужно менять значение переменной, то обозначьте её как let и также присваивайте ей начальное значение:
let index = 0
Проблема var заключается в поднятии переменных: где бы ни находилось объявление, это равнозначно тому, что переменную объявили в начале кода.
function bigFunction() {
// код...
myVariable; // => undefined
// код...
var myVariable = 'Initial value';
// код...
myVariable; // => 'Initial value'
}
bigFunction();
В этом случае переменная myVariable содержит undefined до получения значения:
myVariable = 'Initial value'
Если же переменную объявить как let, она останется недоступной до момента присвоения ей значения. Таким образом, использование const или let снизит риск получения значения undefined в JavaScript.
2. Усиление связности
Связность характеризует степень взаимосвязи элементов модуля (пространства имён, класса, метода, блока кода). Сильная связность предпочтительнее, поскольку предполагает, что элементы модуля должны фокусироваться исключительно на одной задаче. Это поможет модулю быть:
- сфокусированным и понятным;
- легко поддерживаемым и поддающимся рефакторингу;
- многоразовым;
- простым для тестирования.
Блок кода сам по себе может считаться небольшим модулем. Чтобы извлечь выгоду из преимуществ сильной связности, нужно держать переменные как можно ближе к блоку кода, который их использует.
Вот классический пример того, как не надо делать:
function someFunc(array) {
var index, item, length = array.length;
// некоторый код...
// некоторый код...
for (index = 0; index < length; index++) {
item = array[index];
// некоторый код...
}
return 'some result';
}
index, item и length объявляются в начале функции, но используются они лишь ближе к концу. Всё время между объявлением переменной в начале и до использования её в цикле index и item не инициализируются и выводят undefined. Разумнее переместить переменные ближе к месту их применения:
function someFunc(array) {
// некоторый код...
// некоторый код...
const length = array.length;
for (let index = 0; index < length; index++) {
const item = array[index];
// некоторый код
}
return 'some result';
}
Доступ к несуществующему свойству
Попытка получить доступ к несуществующему свойству объекта JavaScript заканчивается undefined. Пример:
let favoriteMovie = {
title: 'Blade Runner'
};
favoriteMovie.actors; // => undefined
favoriteMovie — объект с одним значением title. Доступ к несуществующему свойству actors приведёт к выводу undefined.
Сам по себе доступ к не вызовет ошибку, а вот при попытке получить значение из несуществующего свойства выведется ошибка:
TypeError: Cannot read property <prop> of undefined
Проблема в особенностях JavaScript: свойство может быть установлено или отсутствовать. Хорошее решение — установка правил, которые обязывают задать свойствам значения.
Но не всегда возможно контролировать объекты, с которыми приходится работать. Такие объекты могут иметь разный набор свойств в различных сценариях, и каждый из них нужно обрабатывать вручную.
Реализуем функцию append(array, toAppend), которая добавляет в начале и/или в конце массива новые элементы:
function append(array, toAppend) {
const arrayCopy = array.slice();
if (toAppend.first) {
arrayCopy.unshift(toAppend.first);
}
if (toAppend.last) {
arrayCopy.push(toAppend.last);
}
return arrayCopy;
}
append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5]
append(['Hello'], { last: 'World' }); // => ['Hello', 'World']
append([8, 16], { first: 4 }); // => [4, 8, 16]
Поскольку объект toAppend может удалять первые или последние свойства, необходимо проверить их существование с помощью условий if(toAppend.first){} и if(toAppend.last){}.
Вот только undefined, как false, null, 0, NaN и ' ', является ложными значением, а в текущей реализации функция append() не позволяет вставлять ложные элементы:
append([10], { first: 0, last: false }); // => [10]
0 и false — ложные значения, потому что if (toAppend.first){} и if (toAppend.last){} фактически сравниваются с ложными значениями, и эти элементы не вставляются в массив. Функция возвращает исходный массив [10] без изменений.
1. Наличие свойства
К счастью, JavaScript предлагает множество способов определить, имеет ли объект определённое свойство:
obj.prop !== undefinedв JavaScript позволяет проверитьundefined, сравнив с ним объект;typeof obj.prop !== 'undefined'проверяет тип значения свойства;obj.hasOwnProperty('prop')проверяет объект на наличие собственного свойства;'prop' in objпроверяет объект на наличие собственного или унаследованного свойства.
Рекомендацией в этом случае будет использование оператора in , чтобы проверить, имеет ли объект определённое свойство, не обращаясь к фактическому значению этого свойства:
function append(array, toAppend) {
const arrayCopy = array.slice();
if ('first' in toAppend) {
arrayCopy.unshift(toAppend.first);
}
if ('last' in toAppend) {
arrayCopy.push(toAppend.last);
}
return arrayCopy;
}
append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5]
append([10], { first: 0, last: false }); // => [0, 10, false]
'first' in toAppend ( как и 'last' in toAppend) выводит true, независимо от существующего свойства. В других случаях выводится — false.
Использование оператора in устраняет проблему со вставкой ложных элементов 0 и false. Теперь добавление элементов в начале и в конце массива [10] приводит к ожидаемому результату: [0, 10, false].
2. Деструктуризация доступа к свойствам объекта
Деструктуризация объекта позволяет устанавливать значение по умолчанию, если свойство не существует: удобно для исключения прямого контакта с undefined:
const object = { };
const { prop = 'default' } = object;
prop; // => 'default'
Применяя преимущества деструктуризации объекта, реализуем quote():
function quote(str, config) {
const { char = '"', skipIfQuoted = true } = config;
const length = str.length;
if (skipIfQuoted
&& str[0] === char
&& str[length - 1] === char) {
return str;
}
return char + str + char;
}
quote('Hello World', { char: '*' }); // => '*Hello World*'
quote('"Welcome"', { skipIfQuoted: true }); // => '"Welcome"'
const { char = '"', skipIfQuoted = true } = config в одной строкe извлекает свойства char и skipIfQuoted из объекта config.
Если некоторые свойства недоступны в объекте config, деструктуризация задаёт значения по умолчанию: '"' для char и false для skipIfQuoted. К счастью, функцию можно улучшить.
Переместим деструктуризацию в раздел параметров и установим значение по умолчанию (пустой объект { }) для параметра config, чтобы пропустить второй аргумент, когда будет достаточно значений по умолчанию:
function quote(str, { char = '"', skipIfQuoted = true } = {}) {
const length = str.length;
if (skipIfQuoted
&& str[0] === char
&& str[length - 1] === char) {
return str;
}
return char + str + char;
}
quote('Hello World', { char: '*' }); // => '*Hello World*'
quote('Sunny day'); // => '"Sunny day"'
Деструктурирующее присваивание гарантирует, что используется пустой объект, если второй аргумент не указан вообще. В результате вы избегаете возникновения значения undefined в JavaScript.
3. Свойство по умолчанию
Есть простой способ установить значения по умолчанию для свойств объекта, и имя ему Spread syntax:
const unsafeOptions = {
fontSize: 18
};
const defaults = {
fontSize: 16,
color: 'black'
};
const options = {
...defaults,
...unsafeOptions
};
options.fontSize; // => 18
options.color; // => 'black'
Инициализатор объекта распространяет свойства из исходных объектов defaults и unsafeOptions. Важен порядок, в котором указаны исходные объекты: свойства более позднего исходного объекта перезаписывают более ранние. Независимо от ситуации, объект всегда содержит полный набор свойств, и появление undefined невозможно.
Параметры функции
Функция, имеющая определённые параметры, должна вызываться с одинаковым количеством аргументов. В таком случае параметры получают ожидаемые значения:
function multiply(a, b) {
a; // => 5
b; // => 3
return a * b;
}
multiply(5, 3); // => 15
При вызове функции multiply(5, 3) параметры a и b получают соответствующие значения 5 и 3. Умножение рассчитывается как ожидаемое: 5 * 3 = 15.
Но что происходит, когда пропускается аргумент при вызове? Параметр внутри функции получает значение undefined. Как этого избежать?
Лучшим подходом является использование параметров по умолчанию из ES2015:
function multiply(a, b = 2) {
a; // => 5
b; // => 2
return a * b;
}
multiply(5); // => 10
multiply(5, undefined); // => 10
Значение b = 2 в сигнатуре функции гарантирует, что если b получит значение undefined, то по умолчанию параметр изменится на 2.
Возвращаемое значение функции
В JavaScript функция, которая не имеет оператора return, возвращает значение undefined:
function square(x) {
const res = x * x;
}
square(2); // => undefined
То же происходит, если return присутствует, но без какого-либо выражения рядом:
function square(x) {
const res = x * x;
return;
}
square(2); // => undefined
Указывая значение для return, можно получить желаемый результат:
function square(x) {
const res = x * x;
return res;
}
square(2); // => 4
Теперь вызов функции выведет нужное значение.
Оператор void
Оператор void выполняет выражение и возвращает undefined вне зависимости от результата:
void 1; // => undefined
void (false); // => undefined
void {name: 'John Smith'}; // => undefined
void Math.min(1, 3); // => undefined
Одним из вариантов использования оператора void является переопределение результата выполнения выражения и возврат undefined в случае возникновения неожиданных результатов выполнения функции.
Значение undefined в массивах
Вы получаете undefined при попытке доступа к элементу массива с индексом вне пределов массива.
const colors = ['blue', 'white', 'red'];
colors[5]; // => undefined
colors[-1]; // => undefined
Массив colors имеет 3 элемента, поэтому корректные индексы равны 0, 1 и 2. Поскольку в индексах массива 5 и -1 нет элементов, значения colors[5] и colors[-1] получают значение undefined.
В JavaScript вы можете столкнуться с так называемыми разрежёнными массивами. Эти массивы имеют пробелы, то есть на некоторых индексах не определены никакие элементы. Когда делаем попытку получить доступ к пустому значению в разрежённом массиве, на выходе получаем undefined:
const sparse1 = new Array(3);
sparse1; // => [<empty slot>, <empty slot>, <empty slot>]
sparse1[0]; // => undefined
sparse1[1]; // => undefined
const sparse2 = ['white', ,'blue']
sparse2; // => ['white', <empty slot>, 'blue']
sparse2[1]; // => undefined
sparse1 создается путем вызова конструктора Array с числовым первым аргументом. Он имеет 3 пустых элемента. sparse2 создается с литералом массива, второй элемент которого отсутствует. В любом из этих массивов доступ к пустому элементу оценивается как undefined.
Отличие null и undefined в JavaScript
Основное отличие в том, что undefined представляет значение переменной, которая ещё не была инициализирована, а null — намеренное отсутствие объекта.
Допустим, переменная number определена, но ей не назначено начальное значение:
let number;
number; // => undefined
То же самое произойдёт при попытке доступа к несуществующему свойству объекта:
const obj = { firstName: 'Dmitri' };
obj.lastName; // => undefined
Или переменная должна ожидать возвращение объекта функции, но по какой-то причине создание объекта невозможно. В этом случае null является значимым индикатором недостающего объекта. Например, clone() — это функция, которая клонирует простой объект JavaScript. Ожидается, что функция вернёт объект:
function clone(obj) {
if (typeof obj === 'object' && obj !== null) {
return Object.assign({}, obj);
}
return null;
}
clone({name: 'John'}); // => {name: 'John'}
clone(15); // => null
clone(null); // => null
Но clone() может быть вызван с пустым аргументом: 15 или null. В этом случае функция не может создать клон, поэтому возвращает null — индикатор отсутствующего объекта.
В JavaScript существуют проверки на null и undefined. Оператор typeof демонстрирует различие между двумя значениями:
typeof undefined; // => 'undefined'
typeof null; // => 'object'
Строгий оператор равенства === также отличает undefined от null:
let nothing = undefined;
let missingObject = null;
nothing === missingObject; // => false
Вам также может быть интересна наша статья про обработку ошибок в JavaScript.
Заключение
Стратегия борьбы с undefined в JavaScript:
- Уменьшить использование неинициализированных переменных.
- Сделать жизненный цикл переменных коротким и близким к источнику их использования.
- Назначить начальное значение переменным.
- Использовать
constилиlet. - Проверять наличие свойств или заполнить небезопасные объекты по умолчанию.
- Использовать значения по умолчанию для некритичных параметров функции.
- Избегать использования разрежённых массивов.
Перевод статьи «7 tips to handle undefined in JavaScript»
Ситуация: вы пишете скрипт, в котором вам нужно выяснить расстояние от верха экрана до начала нужного элемента. У этого элемента есть класс .entry-content, и вы используете jQuery, чтобы достучаться до него:
// …var dh;dh = $('.entry_content').offset().top;
После запуска скрипта браузер выдаёт ошибку:
❌ TypeError: ‘undefined’ is not an object
Странно, но вы точно знаете, что таким способом можно искать элемент по его классу. Более того, точно такой же код работает тремя строчками выше и без ошибок.
Что это значит: браузер при виде точки понимает, что перед ним — объект, у которого есть какие-то свойства. Эта ошибка означает, что вы пытаетесь обратиться к свойствам несуществующего объекта.
Когда встречается: когда программист делает опечатки в имени объекта, неправильно объявляет его или начинает работать с чем-то, что точно не объект.
В нашем примере программист неправильно указал имя раздела при вызове, и вот что произошло:
- Браузер знает, что на странице есть элемент с классом entry-content, который написан через дефис.
- Программист увлёкся и поставил знак подчёркивания вместо дефиса.
- Скрипт попытался найти этот класс, но не смог, поэтому вернул значение undefined.
- После этого команда стала выглядеть так: dh = undefined.offset().top, но undefined.offset() — это не объект, а что-то неопределённое, у которого нет свойства top.
- Браузер выдаёт ошибку, что вообще-то это не объект и работать с ним он не будет.
Что делать с ошибкой TypeError: ‘undefined’ is not an object
Эта ошибка означает, что вы пытаетесь использовать как объект то, что им не является. Чтобы исправить эту ошибку, проверьте:
- нет ли у вас опечаток в именах и названиях (скорее всего);
- правильно ли вы определили нужный объект;
- в jQuery нужно будет дополнительно проверить, не теряется ли значение this или $ при использовании внутри функции;
- есть ли внутри вашей переменной или объекта вообще что-нибудь, с чем можно работать.
Попробуйте сами
У вас есть страница с элементом <h1 id="text-test"></h1> и JavaScript-кодом. При повторном запуске анимации он падает с нашей ошибкой. Попробуйте выяснить, почему так происходит.
document.addEventListener('DOMContentLoaded', function (event) {
var dataText = ["Test", "Hello", "Salut", "Bonjour"];
function typeWriter(text, i, fnCallback) {
if (i < (text.length)) {
document.getElementById("text-test").innerHTML = text.substring(0, i + 1) + '<span aria-hidden="true"></span>';
setTimeout(function () {
typeWriter(text, i + 1, fnCallback)
}, 100);
}
else if (typeof fnCallback == 'function') {
setTimeout(fnCallback, 700);
}
}
function StartTextAnimation(i) {
if (typeof dataText[i] == 'undefined') {
setTimeout(function () {
StartTextAnimation(0);
}, 10000);
}
// ВОТ ТУТ ОШИБКА ↓
if (i < dataText[i].length) {
typeWriter(dataText[i], 0, function () {
StartTextAnimation(i + 1);
});
}
}
StartTextAnimation(0);
});
Когда новички начинают изучать JavaScript, то иногда сталкиваются с одной интересной проблемой. Они зачастую не могут понять разницу между undefined и null. Ведь null и undefined представляют собой пустые значения. Даже операция сравнения null == undefined выводит true. Давайте попробуем разобраться в том, что из себя всё-таки представляет undefined.
Содержание статьи:
- Введение.
- Что такое undefined.
- Что влияет на появление undefined.
- Undefined в массивах.
- Разница между null и undefined.
- Заключение.
Введение
Большинство современных языков вроде Ruby, Python и Java имеют только одно значение null (null или nill), что совершенно логично.
В случае с JavaScript интерпретатор возвращает undefined при попытке доступа к переменной или свойству объекта, которые ещё не былы заданы. Пример:
|
let company; company; // => undefined let person = { name: ‘John Snow’ }; person.age; // => undefined |
Что же касается null, это значение представляет собой ссылку на отсутствующий объект. В JavaScript не устанавливается значение null для переменных или свойств объекта.
Некоторые встроенные методы, такие как String.prototype.match(), могут возвращать значение null для обозначения отсутствующего объекта:
|
let array = null; array; // => null let movie = { name: ‘Starship Troopers’, musicBy: null }; movie.musicBy; // => null ‘abc’.match(/[0—9]/); // => null |
Поскольку в JavaScript у разработчиков есть возможность получить доступ к неинициализированным значениям, это может вызвать некоторые ошибки.
Часто такие действия приводят к ошибке undefined, что ломает весь скрипт. Наиболее часто при этом возникают следующие ошибки:
- typeError:
undefinedне является функцией; - typeError: невозможно прочитать
'<prop-name>'свойствоundefined; - другие подобные ошибки.
А иронию этой шутки может понять только разработчик JavaScript:
|
function undefined() { // проблема решена |
Чтобы избежать ошибок, связанных с undefined, нужно понимать, когда эта ошибка может возникать, и снизить риски ее появления в вашем приложении.
В JavaScript есть 6 примитивных типов:
- Boolean. У этого типа есть два значения:
true(истина) иfalse(ложь). Как правило, логический типbooleanиспользуется для хранения значения типа да/нет. - Number. Тип число используется как для целых, так и для дробных чисел (1, 6.7, 0xFF);
- String. В JavaScript любые текстовые данные являются строками. В JavaScript нет разницы между двойными и одинарными кавычками:
var text = "строка"/var texts = 'строки'; - Symbol. Новый примитивный тип данных
Symbolслужит для создания уникальных идентификаторов:Symbol("name")(начиная с ES2015); - Null. Значение
nullне относится ни к одному из типов выше, а образует свой отдельный тип, состоящий из единственного значенияnull; - Undefined. Значение
undefined, как иnull, образует свой собственный тип, состоящий из одного этого значения. Если переменная объявлена, но в неё ничего не записано, то её значение как раз и естьundefined(значение не присвоено).
Отдельно от всех стоит тип Object (объект), который используется для организации данных и объявления более сложных сущностей. Пример:
|
var human = { id: 12884002, age: 28, name: ‘Bob’, favorite_song: ‘Photograph, by Nickelback’ |
Объект может иметь любые свойства до тех пор, пока они находятся внутри {фигурных скобок}.
Среди этих 6 примитивных типов undefined является специальным значением. Согласно спецификации ECMAScript, undefined используется тогда, когда переменной не присвоено значение.
Этот стандарт четко определяет, что значение undefined можно получить при доступе к неинициализированным переменным, несуществующим свойствам объекта, несуществующим элементам массива и тому подобному. Например:
|
let number; number; // => undefined let movie = { name: ‘Interstellar’ }; movie.year; // => undefined let movies = [‘Interstellar’, ‘Alexander’]; movies[3]; // => undefined |
Пример выше показывает, что undefined выводится при попытке доступа к:
- неинициализированной переменной
number; - несуществующему свойству объекта
movie.year; - несуществующему элементу массива
movies[3].
Оператор typeof возвращает строку undefined для неопределенного значения:
|
typeof undefined === ‘undefined’; // => true |
Оператор typeof отлично подходит для проверки значения undefined у переменной:
|
let nothing; typeof nothing === ‘undefined’; // => true |
Что влияет на появление undefined
Неинициализированная переменная
Объявленная переменная, которая еще не имеет значения (неинициализированна), по умолчанию undefined.
Пример:
|
let myVariable; myVariable; // => undefined |
Переменная myVariable уже объявлена, но еще не имеет присвоенного значения. Попытка обратиться к переменной закончится выводом undefined.
Эффективный подход к решению проблем неинициализированных переменных — это по возможности присвойте им начальное значение. Чем меньше переменная существует в неинициализированном состоянии, тем лучше. В идеале вы сразу же присваиваете значение myVariable = 'Стартовое значение’, хотя это не всегда возможно.
Отдавайте предпочтение const и let, а от var нужно постепенно отказываться
Некоторые разработчики считают, что одной из лучших особенностей ECMAScript 2015 является новый способ объявления переменных путём использования const и let. Это большой шаг вперед, так как объявленные таким способом объекты/переменные находятся в области видимости, ограниченной текущим блоком кода (в отличие от старой функции var), и находятся во временной мертвой зоне до момента присвоения им конкретного значения.
При использовании неизменяемых данных (констант) рекомендуется инициализировать их как const. Это создаст неизменную связку.
Одна из приятных особенностей const заключается в том, что существует возможность назначить начальное значение константы. Пример:
|
const myVariable = ‘initial’ |
Таким образом, константа не подвергается неинициализированному состоянию, и получить значение undefined просто невозможно.
Давайте протестируем функцию, которая проверяет, является ли слово палиндромом:
|
function isPalindrome(word) { const length = word.length; const half = Math.floor(length / 2); for (let index = 0; index < half; index++) { if (word[index] !== word[length — index — 1]) { return false; return true; isPalindrome(‘madam’); // => true isPalindrome(‘hello’); // => false |
Переменным length и half значение присваивается один раз. В этом случае объявить их как const будет логично, так как их значение не будет меняться.
Если вам нужно будет менять значение переменной несколько раз, то обозначьте ее как let. По возможности сразу присваивайте ей начальное значение. Пример:
А когда использовать var? Некоторые разработчики, ссылаясь на стандарт ES2015, предлагают перестать использовать var.
Проблема использования var заключается в так называемом поднятии переменных (англ. variable hoisting). Где бы ни находилось объявление, это равнозначно тому, что переменную объявили в самом начале кода.
|
function bigFunction() { // код… myVariable; // => undefined // код… var myVariable = ‘Initial value’; // код… myVariable; // => ‘Initial value’ bigFunction(); |
В этом случае переменная myVariable содержит undefined до получения значения:
|
myVariable = ‘Initial value’ |
Если же переменную объявить как let, то она останется недоступной до момента присвоения ей значения. Это происходит из-за того, что переменная находится во временной мертвой зоне. В этом случае вероятность получения значения undefined близится к нулю.
Вышеприведенный пример с let (вместо var) выведет ReferenceError, потому что переменная во временной мертвой зоне недоступна.
|
function bigFunction() { // код… myVariable; // => выводится ‘ReferenceError: myVariable is not defined’ // код… let myVariable = ‘Initial value’; // код… myVariable; // => ‘Initial value’ bigFunction(); |
Использование const или let для неизменяемых привязок позволит снизить риски получения значения undefined при написании кода.
Усильте связность
Связность характеризует степень взаимосвязанности элементов модуля (пространства имен, класса, метода, блока кода). Есть два вида связанности — сильная и слабая.
Сильная связность предпочтительнее, поскольку она предполагает, что элементы модуля должны фокусироваться исключительно на одной задаче. Это поможет модулю быть:
- сфокусированным и понятным — становится легче понять, какую задачу выполняет модуль;
- легко поддерживаемым и поддающимся рефакторингу — изменение модуля влияет на меньшее количество элементов;
- многоразовым — если модуль ориентирован на выполнение одной задачи, то его легче использовать повторно;
- простым для тестирования — вам будет проще протестировать модуль, ориентированный на одну задачу.
Сильная связность, сопровождающаяся слабым зацеплением, является характеристикой хорошо спроектированной системы.
Блок кода сам по себе может считаться небольшим модулем. Чтобы извлечь выгоду из преимуществ сильной связности, нужно держать переменные как можно ближе к блоку кода, который их использует.
Например, если переменная нужна только для использования в определенном блоке кода, тогда можно объявить её и разрешить существовать только внутри нужного блока (используя const или let для объявления).
Одним из классических примеров излишнего срока жизни переменных является использование цикла for внутри функции:
|
function someFunc(array) { var index, item, length = array.length; // некоторый код… // некоторый код… for (index = 0; index < length; index++) { item = array[index]; // некоторый код… return ‘some result’; |
index, item и length — эти переменные объявляются в начале тела функции. Однако они используются только ближе к концу функции. Так в чем проблема с таким подходом?
Всё время между объявлением переменной в начале и до использования её в цикле for переменные index и item не инициализируются и выводят undefined. По этому примеру видно, что срок жизни переменной является необоснованно длинным.
Разумнее будет переместить переменные ближе к месту их применения:
|
function someFunc(array) { // некоторый код… // некоторый код… const length = array.length; for (let index = 0; index < length; index++) { const item = array[index]; // некоторый код return ‘some result’; |
Переменные index и item существуют только в области действия блока for. У них нет никакого значения за его пределами.
Переменная (const) length объявляется в месте ее использования.
Почему модифицированная версия лучше первоначальной? Посмотрим:
- значения не подвергаются неинициализированному состоянию, поэтому нет риска получения
undefined; - перемещение переменных ближе к месту их использования увеличивает читаемость кода;
- элементы с высокой связанностью кода легче рефакторить и извлекать при необходимости в отделенные функции.
Доступ к несуществующему свойству
При доступе к несуществующему свойству объекта JavaScript возвращает undefined.
Обратите внимание на пример:
|
let favoriteMovie = { title: ‘Blade Runner’ favoriteMovie.actors; // => undefined |
favoriteMovie — это объект с одним значением title. Доступ к несуществующему свойству actors путём использования favoriteMovie.actors приведет к выводу undefined.
Сам по себе доступ к несуществующему свойству не вызывает ошибки. Реальная проблема возникает при попытке получить значение из несуществующего свойства. Это сообщение об ошибке отражает наиболее распространенную ловушку, связанную с undefined:
|
TypeError: Cannot read property <prop> of undefined |
Немного изменим предыдущий фрагмент кода, чтобы пояснить поведение TypeError:
|
let favoriteMovie = { title: ‘Blade Runner’ favoriteMovie.actors[0]; // TypeError: Cannot read property ‘0’ of undefined |
В favoriteMovie нет свойства actors, поэтому использование favoriteMovie.actors приведет к undefined.
В результате доступ к элементу со значением undefined с помощью выражения favoriteMovie.actors [0] вызывает TypeError.
Возможности JavaScript, которые позволяют получить доступ к несуществующим свойствам, являются источником путаницы. Свойство может быть установлено или может отсутствовать. Идеальным решением этой проблемы будет установка правил для объекта, которые позволят ему содержать только свойства с явно заданными значениями.
К сожалению, не всегда возможно контролировать объекты, с которыми приходится работать. Такие объекты могут иметь разный набор свойств в различных сценариях. Таким образом, нужно обрабатывать все эти сценарии вручную.
Попробуем реализовать функцию append(array, toAppend), которая добавляет в начале и/или в конце массива новые элементы. Параметр toAppend принимает объект со свойствами:
first: элемент, вставленный в начале массива;last: элемент, вставленный в конец массива.
Функция возвращает новый экземпляр массива, не изменяя исходный (т.е. это чистая функция).
Функция append() может выглядеть так:
|
function append(array, toAppend) { const arrayCopy = array.slice(); if (toAppend.first) { arrayCopy.unshift(toAppend.first); if (toAppend.last) { arrayCopy.push(toAppend.last); return arrayCopy; append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5] append([‘Hello’], { last: ‘World’ }); // => [‘Hello’, ‘World’] append([8, 16], { first: 4 }); // => [4, 8, 16] |
Поскольку объект toAppend может удалять первые или последние свойства, необходимо проверить, существуют ли эти свойства в toAppend.
Доступ к свойству приведет к получению undefined, если его не существует. Можно проверить существует ли первое или последнее свойство, чтобы избежать undefined. Как вариант, это можно сделать с помощью условий if(toAppend.first){} и if(toAppend.last){}.
Но не будем торопиться. В этом подходе есть серьезный недостаток. undefined, также как false, null, 0, NaN и ' ' являются ложными значениями (falsy values).
В текущей реализации append() функция не позволяет вставлять ложные элементы:
|
append([10], { first: 0, last: false }); // => [10] |
0 и false — ложные значения, потому что if (toAppend.first) {} и if (toAppend.last) {} фактически сравниваются с ложными значениями и эти элементы не вставляются в массив. Функция возвращает исходный массив [10] без изменений.
Последующие подсказки объясняют, как правильно проверить существование свойства.
Проверьте, существует ли свойство
К счастью, JavaScript предлагает множество способов определить, имеет ли объект определенное свойство:
obj.prop !== undefinedпозволяет сравнивать объект напрямую сundefined;typeof obj.prop !== 'undefined'проверяет тип значения свойства;obj.hasOwnProperty('prop')проверяет объект на наличие собственного свойства;'prop' in objпроверяет объект на наличие собственного или унаследованного свойства.
Рекомендацией в этом случае будет использование in оператора. У него простой и понятный синтаксис. Присутствие оператора in указывает на четкое намерение проверить, имеет ли объект определенное свойство, не обращаясь к фактическому значению этого свойства.
Использование obj.hasOwnProperty('prop') — это также неплохое решение. Оно немного длиннее, чем оператор in , и проверяет только собственные свойства объекта.
Эти два способа сравнения с undefined могут сработать… Но кажется, что использование obj.prop !== undefined и typeof obj.prop !== 'undefined' является не очень хорошим решением и может привести к непонятным последствиям при прямом сравнении с undefined.
Давайте попробуем улучшить функцию append(array, toAppend), используя in оператора:
|
function append(array, toAppend) { Добавить в заметки чтобы посмотреть позже? const arrayCopy = array.slice(); if (‘first’ in toAppend) { arrayCopy.unshift(toAppend.first); if (‘last’ in toAppend) { arrayCopy.push(toAppend.last); return arrayCopy; append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5] append([10], { first: 0, last: false }); // => [0, 10, false] |
'first' in toAppend ( как и 'last' in toAppend) выводит true, независимо от существующего свойства. В других случаях выводится — false.
Использование оператора in устраняет проблему со вставкой ложных элементов 0 и false. Теперь добавление элементов в начале и в конце массива [10] приводит к ожидаемому результату [0, 10, false].
Выполните деструктурирование доступа к свойствам объекта
При доступе к свойству объекта иногда необходимо указать значение по умолчанию, если свойство не существует.
Можно использовать in с тернарным оператором, чтобы получить следующий результат:
|
const object = { }; const prop = ‘prop’ in object ? object.prop : ‘default’; prop; // => ‘default’ |
Использование синтаксиса тернарного оператора становится сложным, когда число проверяемых свойств увеличивается. Для каждого свойства нужно создать новую строку кода, чтобы обработать значение, увеличивая опасную кучу похожих тернарных операторов.
Чтобы использовать более простой подход, давайте познакомимся с функцией ES2015, называемой деструктурирование объекта.
Деструктурирование объекта позволяет напрямую извлекать значения свойств объекта непосредственно в переменные и устанавливать значение по умолчанию, если свойство не существует. Это удобно использовать для исключения прямого контакта с undefined.
Действительно, извлечение свойств теперь выглядит совершенно по-другому:
|
const object = { }; const { prop = ‘default’ } = object; prop; // => ‘default’ |
Чтобы увидеть это в действии, определяем полезную функцию, обертывающую строку в кавычки. quote(subject, config) принимает первый аргумент как строку, которую нужно обернуть. Второй аргумент config — это объект со следующими свойствами:
char— символ цитаты, например,'(одинарная кавычка) или"(двойная кавычка). По умолчанию —";skipIfQuoted— логическое значение, чтобы пропустить цитирование, если строка уже цитируется. По умолчанию —true.
Применяя преимущества деструктурирования объекта, давайте реализуем quote():
|
function quote(str, config) { const { char = ‘»‘, skipIfQuoted = true } = config; const length = str.length; if (skipIfQuoted && str[0] === char && str[length — 1] === char) { return str; return char + str + char; quote(‘Hello World’, { char: ‘*’ }); // => ‘*Hello World*’ quote(‘»Welcome»‘, { skipIfQuoted: true }); // => ‘»Welcome»‘ |
const { char = '"', skipIfQuoted = true } = config в одной строкe извлекает свойства char и skipIfQuoted из объекта config.
Если некоторые свойства недоступны в объекте config, деструктурирование задает значения по умолчанию: '"' для char и false для skipIfQuoted.
К счастью, функцию можно даже улучшить.
Давайте переместим деструктурирование прямо в раздел параметров. И установим значение по умолчанию (пустой объект { }) для параметра config, чтобы пропустить второй аргумент, когда будет достаточно значений по умолчанию.
|
function quote(str, { char = ‘»‘, skipIfQuoted = true } = {}) { const length = str.length; if (skipIfQuoted && str[0] === char && str[length — 1] === char) { return str; return char + str + char; quote(‘Hello World’, { char: ‘*’ }); // => ‘*Hello World*’ quote(‘Sunny day’); // => ‘»Sunny day»‘ |
Обратите внимание, что деструктурирующее присваивание заменяет параметр config в сигнатуре функции. Это позволяет сделать параметр quote() на одну строку короче. Параметр = {} в правой части деструктурирующее присваивание гарантирует, что используется пустой объект, если второй аргумент не указан вообще quote('Sunny day').
Деструктурирование объектов — это мощная функция, которая эффективно обрабатывает извлечение свойств из объектов. Это хорошо, когда есть возможность указать значение по умолчанию, которое будет возвращено при попытке доступа к несуществующему свойству. В результате вы избегаете возникновения значения undefined и всех проблем, которые могут быть связаны с ним.
Установите объекту свойства по умолчанию.
Если нет необходимости создавать переменные для каждого свойства, такого как назначение деструктурирования, объект, который пропускает некоторые свойства, может быть заполнен значениями по умолчанию.
Функция ES2015 Object.assign (target, source1, source2, ...) копирует значения всех перечисленных собственных свойств из одного или нескольких исходных объектов в целевой объект. После чего возвращает целевой объект.
Например, вам нужно получить доступ к данным объекта unsafeOptions, который не всегда содержит полный набор свойств.
Чтобы избежать появления значения undefined при доступе к несуществующему свойству из unsafeOptions, можно сделать следующие настройки:
- определить объект
defaults, который содержит значения свойств по умолчанию; - вызвать функцию
Object.assign({ }, defaults, unsafeOptions), чтобы создать новый объектoptions. Новый объект получает все свойства изunsafeOptions, недостающие берутся изdefaults.
|
const unsafeOptions = { fontSize: 18 const defaults = { fontSize: 16, color: ‘black’ const options = Object.assign({}, defaults, unsafeOptions); options.fontSize; // => 18 options.color; // => ‘black’ |
unsafeOptions содержит только свойство fontSize. Объект defaults указывает значения по умолчанию для fontSize и color.
Object.assign() принимает первый аргумент как целевой объект {}. Целевой объект получает значение свойства fontSize из объекта-источника unsafeOptions. А значение свойства color — из объекта-источника defaults, поскольку unsafeOptions не содержит color. Важен порядок, в котором перечислены исходные объекты: первый пришёл — первый ушёл.
Теперь возможно получить доступ к любому свойству объекта options, включая options.color, который изначально был недоступен в unsafeOptions.
К счастью, существует более простой и легкий способ установить значения по умолчанию для свойств объекта. Рекомендуется использовать новую функцию JavaScript, которая позволяет расширять свойства при инициализации объектов.
Вместо вызова Object.assign() используйте синтаксис распространения объекта, чтобы скопировать в целевой объект все собственные и перечисляемые свойства из исходных объектов:
|
const unsafeOptions = { fontSize: 18 const defaults = { fontSize: 16, color: ‘black’ const options = { ...defaults, ...unsafeOptions options.fontSize; // => 18 options.color; // => ‘black’ |
Инициализатор объекта распространяет свойства из исходных объектов defaults и unsafeOptions. Важен порядок, в котором указаны исходные объекты: свойства более позднего исходного объекта перезаписывают более ранние.
Заполнение неполного объекта значением по умолчанию является эффективной стратегией, обеспечивающей безопасность и стабильность вашего кода. Независимо от ситуации, объект всегда содержит полный набор свойств, и появление undefined невозможно.
Параметры функции
Параметры функции по умолчанию установлены на undefined.
Обычно функция, имеющая определенные параметры, должна вызываться с одинаковым количеством аргументов. В таком случае параметры получают ожидаемые значения:
|
function multiply(a, b) { a; // => 5 b; // => 3 return a * b; multiply(5, 3); // => 15 |
При вызове функции multiply(5, 3) параметры a и b получают соответствующие значения 5 и 3. Умножение рассчитывается как ожидаемое: 5 * 3 = 15.
Что происходит, когда пропускается аргумент при вызове? Параметр внутри функции получает значение undefined.
Давайте немного изменим предыдущий пример, вызвав функцию только с одним аргументом:
|
function multiply(a, b) { a; // => 5 b; // => undefined return a * b; multiply(5); // => NaN |
Функция multiply(a, b) { } определяется двумя параметрами: a и b. Вызов multiply(5) выполняется с помощью одного аргумента, в результате параметр равен 5, а параметр b получает значение undefined.
Используйте значение параметра по умолчанию
Иногда функция имеет не полный набор аргументов при ее вызове. В таком случае нужно установить значения по умолчанию для параметров, у которых нет значений.
Учитывая предыдущий пример, давайте сделаем некоторые улучшения. Если параметр b — undefined, то по умолчанию ему присваивается значение 2:
|
function multiply(a, b) { if (b === undefined) { b = 2; a; // => 5 b; // => 2 return a * b; multiply(5); // => 10 |
Функция вызывается одним аргументом multiply(5). Первоначально параметр a равен 2, а b соответствует undefined. Выражение проверяет, не равно ли b параметру undefined. Если это так, b получает значение по умолчанию 2.
Хотя представленный способ назначения значений по умолчанию работает, не рекомендуется сравнивать значения напрямую сundefined. Это уже будет выглядеть как хак.
Лучшим подходом является использование параметров по умолчанию из ES2015. Он краткий и не содержит в себе прямого сравнения значений с undefined.
Установка параметров по умолчанию для b не выводит значения undefined:
|
function multiply(a, b = 2) { a; // => 5 b; // => 2 return a * b; multiply(5); // => 10 multiply(5, undefined); // => 10 |
Значение b = 2 в сигнатуре функции гарантирует, что если b получит значение undefined, то по умолчанию параметр изменится на 2.
Функция параметров по умолчанию из ES2015 интуитивно понятна и выразительна. Всегда используйте ее для установки значений по умолчанию для необязательных параметров.
Возвращаемое значение функции
В JavaScript функция, которая не имеет оператора return, возвращает значение undefined:
|
function square(x) { const res = x * x; square(2); // => undefined |
Функция square() не возвращает результаты вычислений. Результат — undefined.
Такая же ситуация возникает, когда оператор return присутствует, но без какого-либо выражения рядом:
|
function square(x) { const res = x * x; return; square(2); // => undefined |
return; выполняется, но он не возвращает ничего. Результат вызова — undefined.
Указывая значение для return, можно получить желаемый результат:
|
function square(x) { const res = x * x; return res; square(2); // => 4 |
Теперь вызов функции выведет нужное значение.
Не доверяйте автоматической расстановке точки с запятой
Следующий список операторов в JavaScript должен заканчиваться точкой с запятой (;):
- пустой оператор;
- операторы:
let,const,var,import,export; - утверждение значения;
- утверждение отладчика;
- утверждения:
continue,break,throw,return.
Если вы используете одно из вышеуказанных утверждений, обязательно нужно ставить точку с запятой в конце:
|
function getNum() { // Notice the semicolons at the end let num = 1; return num; getNum(); // => 1 |
Как можно видеть, в конце объявленияlet и оператором return обязательно ставится точка с запятой.
Что происходит, когда вы не хотите указывать точки с запятой? Например, чтобы уменьшить размер исходного файла.
В такой ситуации ECMAScript предоставляет механизм автоматической установки точки с запятой (ASI), который сделает всю работу за вас.
Используя ASI, вы можете удалить точки с запятой из предыдущего примера:
|
function getNum() { // Notice that semicolons are missing let num = 1 return num getNum() // => 1 |
Вышеприведенный текст является допустимым кодом JavaScript. Отсутствующие точки с запятой автоматически вставлены за вас.
На первый взгляд, это выглядит довольно многообещающе. Механизм ASI позволяет пропустить ненужные точки с запятой. Вы можете сделать код JavaScript меньше и более простым для чтения.
Но тут существует один небольшой, но раздражающий момент, который может создать ASI. Если между return и возвращаемым выражением стоит новая строка return n expression, ASI автоматически вставляет точку с запятой return; n expression.
К чему приведет пустой оператор return ? Функция вернет значение undefined. Если вы не знаете, как работает механизм ASI, то неожиданное появление значения undefined может ввести в заблуждение.
Например, давайте изучим возвращаемое значение вызываемой функции getPrimeNumbers():
|
function getPrimeNumbers() { return [ 2, 3, 5, 7, 11, 13, 17 ] getPrimeNumbers() // => undefined |
Между return и значением массива есть новая строка. JavaScript автоматически вставляет точку с запятой после оператора return, интерпретируя код следующим образом:
|
function getPrimeNumbers() { return; [ 2, 3, 5, 7, 11, 13, 17 ]; getPrimeNumbers(); // => undefined |
В таком случае мы получим значение undefined.
Проблема решена путем удаления новой строки между return и массивом:
|
function getPrimeNumbers() { return [ 2, 3, 5, 7, 11, 13, 17 getPrimeNumbers(); // => [2, 3, 5, 7, 11, 13, 17] |
Оператор void
Оператор void выполняет выражение и возвращает undefined вне зависимости от результата:
|
void 1; // => undefined void (false); // => undefined void {name: ‘John Smith’}; // => undefined void Math.min(1, 3); // => undefined |
Одним из вариантов использования оператора void является переопределение результата выполнения выражения и возврат undefined, в случае возникновения побочных эффектов выполнения функции.
Undefined в массивах
Вы получаете undefined при попытке доступа к элементу массива с индексом вне пределов массива.
|
const colors = [‘blue’, ‘white’, ‘red’]; colors[5]; // => undefined colors[—1]; // => undefined |
Массив colors имеет 3 элемента, поэтому корректные индексы равны 0, 1 и 2.
Поскольку в индексах массива 5 и -1 нет элементов, значения colors[5] and colors[-1] получают значение undefined.
В JavaScript вы можете столкнуться с так называемыми разреженными массивами. Эти массивы имеют пробелы, то есть на некоторых индексах не определены никакие элементы.
Когда делаем попытку получить доступ к пустому значению в разреженном массиве, на выходе получаем undefined.
Следующий пример генерирует разреженные массивы и пытается получить доступ к их пустым элементам:
|
const sparse1 = new Array(3); sparse1; // => [<empty slot>, <empty slot>, <empty slot>] sparse1[0]; // => undefined sparse1[1]; // => undefined const sparse2 = [‘white’, ,‘blue’] sparse2; // => [‘white’, <empty slot>, ‘blue’] sparse2[1]; // => undefined |
sparse1 создается путем вызова конструктора Array с числовым первым аргументом. Он имеет 3 пустых элемента.
sparse2 создается с литералом массива, второй элемент которого отсутствует. В любом из этих разреженных массивов доступ к пустому элементу оценивается как undefined.
При работе с массивами, чтобы избежать появления значения undefined, обязательно используйте допустимые индексы массивов или вообще не создавайте разреженные массивы. А вообще не забывайте, что массивы начинаются с нуля.
Разница между undefined и null
Часто у людей возникает разумный вопрос: в чем основное отличие между undefined и null? Оба специальных значения означают пустое состояние.
Основное различие заключается в том, что undefined представляет значение переменной, которая еще не была инициализирована, а null представляет собой намеренное отсутствие объекта.
Рассмотрим разницу на примерах.
Переменная number определена, но ей не назначено начальное значение:
|
let number; number; // => undefined |
Числовая переменная не определена, что явно указывает на неинициализированную переменную.
То же самое произойдёт при попытке доступа к несуществующему свойству объекта:
|
const obj = { firstName: ‘Dmitri’ }; obj.lastName; // => undefined |
Поскольку свойство lastName не существует в obj, JavaScript корректно оценивает obj.lastName как undefined.
Нужно понимать, что переменная должна ожидать возвращение объекта функции. Но по какой-то причине создание объекта невозможно. В этом случае null является значимым индикатором недостающего объекта.
Например, clone() — это функция, которая клонирует простой объект JavaScript. Ожидается, что функция вернет объект:
|
function clone(obj) { if (typeof obj === ‘object’ && obj !== null) { return Object.assign({}, obj); return null; clone({name: ‘John’}); // => {name: ‘John’} clone(15); // => null clone(null); // => null |
Однако clone() может быть вызван с пустым аргументом: 15 или null (или вообще со значением null или undefined). В этом случае функция не может создать клон, поэтому возвращает null — индикатор отсутствующего объекта.
Оператор typeof делает различие между двумя значениями:
|
typeof undefined; // => ‘undefined’ typeof null; // => ‘object’ |
Строгий оператор равенства === правильно отличает undefined от null:
|
let nothing = undefined; let missingObject = null; nothing === missingObject; // => false |
Заключение
Существование undefined является следствием разрешительной природы JavaScript, которая позволяет использовать:
- неинициализированные переменные;
- несуществующие свойства или методы объекта;
- несуществующие индексы для доступа к элементам массива;
- вызов функции, которая ничего не возвращает.
В основном сравнение напрямую с undefined является плохой практикой, потому что вы, вероятно, полагаетесь на разрешенную, но плохую практику, упомянутую выше.
Стратегия борьбы с undefined заключается в уменьшении появления этого значения в вашем коде. И выработайте в себе привычку делать следующее:
- уменьшать использование неинициализированных переменных;
- делать жизненный цикл переменных коротким и близким к источнику их использования;
- по возможности назначать начальное значение переменным;
- стараться использовать
constилиlet; - использовать значения по умолчанию для некритичных параметров функции;
- проверять наличие свойств или заполнить небезопасные объекты по умолчанию;
- избегать использования разреженных массивов.
Описание, примеры
Эта глобальная переменная содержит элементарное неопределенное значение — то, которое имеет переменная, значение которой не указано.
Например, функция без директивы return возвращает undefined.
function a() {
alert('test')
}
result = a() // result является undefined
alert(result === undefined) // true
Обращение к несуществующему свойству объекта также даст undefined.
result = window.notexists alert(result === undefined) // true
Как видно из примеров, определить, является ли значение undefined, можно строгим (тройным) знаком равенства.
Например, можно проверить, существует ли глобальная переменная:
if (window.somevar !== undefined) { .... }
Обратите внимание — при сравнении идет обращение к свойству через заведомо существующий объект window. Поэтому интерпретатор не выдаст ошибку даже если свойства somevar не существует.
Также заметим, что нужен именно строгий оператор сравнения, т.к x==undefined возвратит true также если x является (null), а это уже другое значение.
Строгое сравнение является лучшей альтернативой использованию typeof:
Пример: проверка результата typeof
var x
//
if (typeof x == "undefined") {
...
}
Содержание
- Что такое ошибка «необъявленный идентификатор» и как ее исправить?
- Решение
- Отсутствует заголовок
- Переменная с ошибкой
- Неправильный объем
- Использовать до объявления
- stdafx.h не сверху (специфично для VS)
- Другие решения
- Что означает ошибка: TypeError: ‘undefined’ is not an object
- Что делать с ошибкой TypeError: ‘undefined’ is not an object
- Произошла ошибка Undefined в Яндекс почта
- Что за ошибка в почтовом сервисе Яндекса
- Как исправить ошибку Undefined в Яндекс почта
- Заключение
- Ошибки в JavaScript и как их исправить
- Как читать ошибки?
- Uncaught TypeError: undefined is not a function
- Uncaught ReferenceError: Invalid left-hand side in assignment
- Uncaught TypeError: Converting circular structure to JSON
- Unexpected token ;
- Uncaught SyntaxError: Unexpected token ILLEGAL
- Uncaught TypeError: Cannot read property ‘foo’ of null, Uncaught TypeError: Cannot read property ‘foo’ of undefined
- Uncaught TypeError: Cannot set property ‘foo’ of null, Uncaught TypeError: Cannot set property ‘foo’ of undefined
- Uncaught RangeError: Maximum call stack size exceeded
- Uncaught URIError: URI malformed
- XMLHttpRequest cannot load some/url. No ‘Access-Control-Allow-Origin’ header is present on the requested resource
- InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable
- Заключение
Что такое ошибка «необъявленный идентификатор» и как ее исправить?
Что такое необъявленные ошибки идентификатора? Каковы общие причины и как их исправить?
Пример текстов ошибок:
- Для компилятора Visual Studio: error C2065: ‘cout’ : undeclared identifier
- Для компилятора GCC: ‘cout’ undeclared (first use in this function)
Решение
Чаще всего они приходят из-за того, что забывают включить заголовочный файл, содержащий объявление функции, например, эта программа выдаст ошибку «необъявленный идентификатор»:
Отсутствует заголовок
Чтобы это исправить, мы должны включить заголовок:
Если вы написали заголовок и включили его правильно, заголовок может содержать неправильный включить охрану .
Переменная с ошибкой
Другой распространенный источник ошибки новичка возникает, когда вы неправильно написали переменную:
Неправильный объем
Например, этот код выдаст ошибку, потому что вам нужно использовать std::string :
Использовать до объявления
g не был объявлен до его первого использования. Чтобы это исправить, либо переместите определение g до f :
Или добавить декларацию g до f :
stdafx.h не сверху (специфично для VS)
Это зависит от Visual Studio. В VS нужно добавить #include «stdafx.h» перед любым кодом. Код до того, как он игнорируется компилятором, так что если у вас есть это:
#include будет проигнорировано Вам нужно переместить его ниже:
Не стесняйтесь редактировать этот ответ.
Другие решения
Рассмотрим похожую ситуацию в разговоре. Представьте, что ваш друг говорит вам: «Боб идет на ужин», а ты не представляешь, кто такой Боб. Вы будете в замешательстве, верно? Твой друг должен был сказать: «У меня есть коллега по работе по имени Боб. Боб подходит к обеду». Теперь Боб объявлен, и вы знаете, о ком говорит ваш друг.
Компилятор выдает ошибку «необъявленный идентификатор», когда вы пытаетесь использовать какой-то идентификатор (который будет именем функции, переменной, класса и т. Д.), И компилятор не видит объявления для него. То есть компилятор понятия не имеет, о чем вы говорите, потому что раньше его не видел.
Если вы получаете такую ошибку в C или C ++, это означает, что вы не сказали компилятору о том, что вы пытаетесь использовать. Объявления часто встречаются в заголовочных файлах, поэтому, скорее всего, это означает, что вы не включили соответствующий заголовок. Конечно, может случиться так, что вы просто не помните, чтобы объявить сущность вообще.
Некоторые компиляторы выдают более конкретные ошибки в зависимости от контекста. Например, пытаясь скомпилировать X x; где тип X не был объявлен с Clang скажет вам «неизвестное имя типа X «. Это гораздо полезнее, потому что вы знаете, что он пытается интерпретировать X как тип. Тем не менее, если у вас есть int x = y; , где y еще не объявлено, он скажет вам «использование необъявленного идентификатора y «потому что есть некоторая двусмысленность в том, что именно y может представлять.
У меня была такая же проблема с пользовательским классом, который был определен в пространстве имен. Я пытался использовать класс без пространства имен, вызывая ошибку компилятора «идентификатор» MyClass «не определен».
Добавление
или используя класс, как
В C и C ++ все имена должны быть объявлены перед использованием. Если вы попытаетесь использовать имя переменной или функции, которая не была объявлена, вы получите ошибку «необъявленный идентификатор».
Однако функции — это особый случай в C (и только в C), в котором вам не нужно сначала объявлять их. Компилятор C будет предполагать, что функция существует с числом и типом аргументов, как в вызове. Если фактическое определение функции не совпадает, вы получите еще одну ошибку. Этот особый случай для функций не существует в C ++.
Вы исправляете ошибки такого рода, проверяя, что функции и переменные объявлены до их использования. В случае printf вам нужно включить заголовочный файл (или же в C ++).
Для стандартных функций я рекомендую вам проверить, например, этот справочный сайт , и найдите функции, которые вы хотите использовать. Документация для каждой функции говорит вам, какой заголовочный файл вам нужен.
Эти сообщения об ошибках
означает, что вы используете имя printf но компилятор не видит, где было объявлено имя, и, соответственно, не знает, что это значит.
Любое имя, используемое в программе, должно быть объявлено до ее использования. Компилятор должен знать, что обозначает имя.
В этом конкретном случае компилятор не видит объявление имени printf , Как мы знаем (но не компилятор) это имя стандартной функции C, объявленной в заголовке в C или в заголовке в C ++ и размещены в стандарте ( std:: ) и глобальный ( :: ) (не обязательно) пространства имен.
Поэтому, прежде чем использовать эту функцию, мы должны предоставить объявление ее имени компилятору путем включения соответствующих заголовков.
Например
C:
C ++:
Иногда причиной такой ошибки является простая опечатка. Например, давайте предположим, что вы определили функцию PrintHello
но в основном вы сделали опечатку и вместо PrintHello ты напечатал printHello с строчной буквы «р».
В этом случае компилятор выдаст такую ошибку, потому что он не видит объявление имени printHello , PrintHello а также printHello два разных имени, одно из которых было объявлено, а другое не объявлено, но используется в теле основного
Другая возможная ситуация: доступ к родительскому элементу (классу шаблона) в классе шаблона.
Исправить метод: использование родительского члена класса по его полному имени (с помощью префикса this-> или же parentClassName:: на имя члена).
Это похоже на использование функции без ее объявления. заголовочный файл будет содержать
функция printf (). Включите заголовочный файл в вашу программу, это решение для этого.
Некоторые пользовательские функции могут также вызывать ошибки, если они не были объявлены перед использованием. Если
это используется во всем мире без проб.
В большинстве случаев, если вы уверены, что импортировали данную библиотеку, Visual Studio поможет вам с IntelliSense.
Источник
Что означает ошибка: TypeError: ‘undefined’ is not an object
Это значит, что браузер не может найти нужный объект.
Ситуация: вы пишете скрипт, в котором вам нужно выяснить расстояние от верха экрана до начала нужного элемента. У этого элемента есть класс .entry-content, и вы используете jQuery, чтобы достучаться до него:
// …
var dh;
dh = $(‘.entry_content’).offset().top;
После запуска скрипта браузер выдаёт ошибку:
❌ TypeError: ‘undefined’ is not an object
Странно, но вы точно знаете, что таким способом можно искать элемент по его классу. Более того, точно такой же код работает тремя строчками выше и без ошибок.
Что это значит: браузер при виде точки понимает, что перед ним — объект, у которого есть какие-то свойства. Эта ошибка означает, что вы пытаетесь обратиться к свойствам несуществующего объекта.
Когда встречается: когда программист делает опечатки в имени объекта, неправильно объявляет его или начинает работать с чем-то, что точно не объект.
В нашем примере программист неправильно указал имя раздела при вызове, и вот что произошло:
- Браузер знает, что на странице есть элемент с классом entry-content, который написан через дефис.
- Программист увлёкся и поставил знак подчёркивания вместо дефиса.
- Скрипт попытался найти этот класс, но не смог, поэтому вернул значение undefined.
- После этого команда стала выглядеть так: dh = undefined.offset().top, но undefined.offset() — это не объект, а что-то неопределённое, у которого нет свойства top.
- Браузер выдаёт ошибку, что вообще-то это не объект и работать с ним он не будет.
Что делать с ошибкой TypeError: ‘undefined’ is not an object
Эта ошибка означает, что вы пытаетесь использовать как объект то, что им не является. Чтобы исправить эту ошибку, проверьте:
- нет ли у вас опечаток в именах и названиях (скорее всего);
- правильно ли вы определили нужный объект;
- в jQuery нужно будет дополнительно проверить, не теряется ли значение this или $ при использовании внутри функции;
- есть ли внутри вашей переменной или объекта вообще что-нибудь, с чем можно работать.
Источник
Произошла ошибка Undefined в Яндекс почта
В середине декабря 2016 года (с 16 декабря и после) ряд пользователей электронных почтовых ящиков на Yandex массово столкнулись с невозможностью отправки своей электронной корреспонденции и соответствующими сообщениями «undefine error», «error data failed» и рядом других в Яндекс почте. При этом повторная попытка отправить электронную почту также оканчивается неудачно, система выдаёт ту же ошибку, и что далее делать в данной ситуации пользователь не знает и не представляет. В этой статье я расскажу, что это за ошибка Undefined в Яндексе, каковы могут быть её причины, и что делать, если произошла данная ошибка.
Что за ошибка в почтовом сервисе Яндекса
Ответ на вопрос о том, что это ошибка, следует начать с перевода текста самой ошибки. В переводе с английского языка «undefined» означает «неопределённый». Соответственно, под данным термином система Яндекса обозначает неопределённую ошибку, суть которой системе не известна (или, при использовании языка Javascript, такое значение получается при обращении к переменной или объекту, которые были созданы, но ещё не инициализированы).
Соответственно, если мы имеем дело с «неопределённой» ошибкой, то причины возникновения последней могут быть различными. Некоторые из специалистов связанных с «Яндекс» выдвигают версию о DOS-атаке, направленной на серверы Яндекса, к которой могли приложить руку зарубежные хакеры (выдвигается даже достаточно сомнительная версия о «мести» компании Гугл за скандальное антимонопольное дело, инициированное компанией «Яндекс»).
Тем не менее, сервис Яндекс.Почта, которым пользуются около 27 миллионов человек, примерно с 16 декабря стал регулярно выдавать ошибку «undefined error», а частная и корпоративная почта многих почтовых аккаунтов на Яндекс не была вовремя доставлена к месту назначения.
Как исправить ошибку Undefined в Яндекс почта
Поскольку, как я уже упоминал чуть выше, это ошибка не пользовательская, а самого сервиса Яндекс, это существенно повлиять на развитие ситуации вы вряд ли сможете. Тем не менее, могу порекомендовать вам ряд действий, способных помочь в вопросе устранения ошибки в почтовом сервисе Яндекса. Итак:
- Обратитесь за помощью в техническую поддержку Яндекса. По заверению специалистов техподдержки, они оперативно реагируют на подобные ошибки, и достаточно быстро исправляют ситуацию;
- Попробуйте очистить кэш и куки вашего браузера (к примеру, на Мозилла это делается переходом в «Настройки», затем во вкладку «Приватность», и нажатием на «Удалить вашу недавнюю историю» и «Удалить отдельные куки»);
- Попробуйте зайти на свой почтовый ящик и выполнить отправку своей почты с другого браузера, это поможет исправить Undefined в Yandex;
- Подождите какое-то время. Возможно, на почтовом сервере наблюдаются некоторые проблемы (или проводятся технические работы с целью устранения проблем), и через некоторое время корректная работа почтового сервиса будет восстановлена.
Заключение
Как уже упоминалось выше, если у вас случилась ошибка Undefined в Яндекс почта, то это ошибка самого почтового сервиса, и существенно повлиять на ситуацию вы можете лишь обратившись с соответствующим уведомлением в службу технической поддержки. По состоянию на дату написания данного материала подобные проблемы были уже в целом устранены, а почтовый сервис Яндекс ныне работает без заметных проблем.
Источник
Ошибки в JavaScript и как их исправить
JavaScript может быть кошмаром при отладке: некоторые ошибки, которые он выдает, могут быть очень трудны для понимания с первого взгляда, и выдаваемые номера строк также не всегда полезны. Разве не было бы полезно иметь список, глядя на который, можно понять смысл ошибок и как исправить их? Вот он!
Ниже представлен список странных ошибок в JavaScript. Разные браузеры могут выдавать разные сообщения об одинаковых ошибках, поэтому приведено несколько примеров там, где возможно.
Как читать ошибки?
Перед самим списком, давайте быстро взглянем на структуру сообщения об ошибке. Понимание структуры помогает понимать ошибки, и вы получите меньше проблем, если наткнетесь на ошибки, не представленные в этом списке.
Типичная ошибка из Chrome выглядит так:
Структура ошибки следующая:
- Uncaught TypeError: эта часть сообщения обычно не особо полезна. Uncaught значит, что ошибка не была перехвачена в catch , а TypeError — это название ошибки.
- undefined is not a function: это та самая часть про ошибку. В случае с сообщениями об ошибках, читать их нужно прямо буквально. Например, в этом случае, она значит то, что код попытался использовать значение undefined как функцию.
Другие webkit-браузеры, такие как Safari, выдают ошибки примерно в таком же формате, как и Chrome. Ошибки из Firefox похожи, но не всегда включают в себя первую часть, и последние версии Internet Explorer также выдают более простые ошибки, но в этом случае проще — не всегда значит лучше.
Теперь к самим ошибкам.
Uncaught TypeError: undefined is not a function
Связанные ошибки: number is not a function, object is not a function, string is not a function, Unhandled Error: ‘foo’ is not a function, Function Expected
Возникает при попытке вызова значения как функции, когда значение функцией не является. Например:
Эта ошибка обычно возникает, если вы пытаетесь вызвать функцию для объекта, но опечатались в названии.
Несуществующие свойства объекта по-умолчанию имеют значение undefined , что приводит к этой ошибке.
Другие вариации, такие как “number is not a function” возникают при попытке вызвать число, как будто оно является функцией.
Как исправить ошибку: убедитесь в корректности имени функции. Для этой ошибки, номер строки обычно указывает в правильное место.
Uncaught ReferenceError: Invalid left-hand side in assignment
Связанные ошибки: Uncaught exception: ReferenceError: Cannot assign to ‘functionCall()’, Uncaught exception: ReferenceError: Cannot assign to ‘this’
Вызвано попыткой присвоить значение тому, чему невозможно присвоить значение.
Наиболее частый пример этой ошибки — это условие в if:
В этом примере программист случайно использовал один знак равенства вместо двух. Выражение “left-hand side in assignment” относится к левой части знака равенства, а, как можно видеть в данном примере, левая часть содержит что-то, чему нельзя присвоить значение, что и приводит к ошибке.
Как исправить ошибку: убедитесь, что вы не пытаетесь присвоить значение результату функции или ключевому слову this .
Uncaught TypeError: Converting circular structure to JSON
Связанные ошибки: Uncaught exception: TypeError: JSON.stringify: Not an acyclic Object, TypeError: cyclic object value, Circular reference in value argument not supported
Всегда вызвано циклической ссылкой в объекте, которая потом передается в JSON.stringify .
Так как a и b в примере выше имеют ссылки друг на друга, результирующий объект не может быть приведен к JSON.
Как исправить ошибку: удалите циклические ссылки, как в примере выше, из всех объектов, которые вы хотите сконвертировать в JSON.
Unexpected token ;
Связанные ошибки: Expected ), missing ) after argument list
Интерпретатор JavaScript что-то ожидал, но не обнаружил там этого. Обычно вызвано пропущенными фигурными, круглыми или квадратными скобками.
Токен в данной ошибке может быть разным — может быть написано “Unexpected token ]”, “Expected <” или что-то еще.
Как исправить ошибку: иногда номер строки не указывает на правильное местоположение, что затрудняет исправление ошибки.
Ошибка с [ ] < >( ) обычно вызвано несовпадающей парой. Проверьте, все ли ваши скобки имеют закрывающую пару. В этом случае, номер строки обычно указывает на что-то другое, а не на проблемный символ.
Unexpected / связано с регулярными выражениями. Номер строки для данного случая обычно правильный.
Unexpected; обычно вызвано символом; внутри литерала объекта или массива, или списка аргументов вызова функции. Номер строки обычно также будет верным для данного случая.
Uncaught SyntaxError: Unexpected token ILLEGAL
Связанные ошибки: Unterminated String Literal, Invalid Line Terminator
В строковом литерале пропущена закрывающая кавычка.
Как исправить ошибку: убедитесь, что все строки имеют правильные закрывающие кавычки.
Uncaught TypeError: Cannot read property ‘foo’ of null, Uncaught TypeError: Cannot read property ‘foo’ of undefined
Связанные ошибки: TypeError: someVal is null, Unable to get property ‘foo’ of undefined or null reference
Попытка прочитать null или undefined так, как будто это объект. Например:
Как исправить ошибку: обычно вызвано опечатками. Проверьте, все ли переменные, использованные рядом со строкой, указывающей на ошибку, правильно названы.
Uncaught TypeError: Cannot set property ‘foo’ of null, Uncaught TypeError: Cannot set property ‘foo’ of undefined
Связанные ошибки: TypeError: someVal is undefined, Unable to set property ‘foo’ of undefined or null reference
Попытка записать null или undefined так, как будто это объект. Например:
Как исправить ошибку: это тоже обычно вызвано ошибками. Проверьте имена переменных рядом со строкой, указывающей на ошибку.
Uncaught RangeError: Maximum call stack size exceeded
Связанные ошибки: Uncaught exception: RangeError: Maximum recursion depth exceeded, too much recursion, Stack overflow
Обычно вызвано неправильно программной логикой, что приводит к бесконечному вызову рекурсивной функции.
Как исправить ошибку: проверьте рекурсивные функции на ошибки, которые могут вынудить их делать рекурсивные вызовы вечно.
Uncaught URIError: URI malformed
Связанные ошибки: URIError: malformed URI sequence
Вызвано некорректным вызовом decodeURIComponent .
Как исправить ошибку: убедитесь, что вызовы decodeURIComponent на строке ошибки получают корректные входные данные.
XMLHttpRequest cannot load some/url. No ‘Access-Control-Allow-Origin’ header is present on the requested resource
Связанные ошибки: Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at some/url
Эта проблема всегда связана с использованием XMLHttpRequest.
Как исправить ошибку: убедитесь в корректности запрашиваемого URL и в том, что он удовлетворяет same-origin policy. Хороший способ найти проблемный код — посмотреть на URL в сообщении ошибки и найти его в своём коде.
InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable
Связанные ошибки: InvalidStateError, DOMException code 11
Означает то, что код вызвал функцию, которую нельзя было вызывать в текущем состоянии. Обычно связано c XMLHttpRequest при попытке вызвать на нём функции до его готовности.
В данном случае вы получите ошибку потому, что функция setRequestHeader может быть вызвана только после вызова xhr.open .
Как исправить ошибку: посмотрите на код в строке, указывающей на ошибку, и убедитесь, что он вызывается в правильный момент или добавляет нужные вызовы до этого (как с xhr.open ).
Заключение
JavaScript содержит в себе одни из самых бесполезных ошибок, которые я когда-либо видел, за исключением печально известной Expected T_PAAMAYIM_NEKUDOTAYIM в PHP. Большая ознакомленность с ошибками привносит больше ясности. Современные браузеры тоже помогают, так как больше не выдают абсолютно бесполезные ошибки, как это было раньше.
Какие самые непонятные ошибки вы встречали? Делитесь своими наблюдениями в комментариях.
Источник
От автора: чтобы вернуть сообщество разработчиков, мы рассмотрели нашу базу данных по тысячам проектов и нашли 10 самых распространённых ошибок в JavaScript. Мы собираемся показать вам, что к ним приводит и как это предотвратить. Если ни одна ошибка JavaScript не встречается в вашем коде, это делает вас лучшим разработчиком.
Поскольку всем управляют данные, мы собрали, проанализировали и оценили первые 10 ошибок JavaScript. Rollbar собирает все ошибки из каждого проекта и суммирует, сколько раз каждая из них возникала. Мы делаем это, группируя ошибки в соответствии с их отпечатками пальцев . В принципе, группируется по две ошибки, если вторая — это просто повторение первой. Это дает пользователям хороший обзор вместо огромной свалки, какую вы видели в файле журнала.
Мы сосредоточились на ошибках, которые скорее всего повлияют на вас и ваших пользователей. Чтобы сделать это, мы оценили ошибки по количеству проектов, испытывающихся в разных компаниях. Если бы мы посмотрели только на общее количество раз, когда произошла каждая ошибка, то клиенты большого объема могли бы подавить набор данных с ошибками, которые не имеют отношения к большинству читателей.
Вот первые 10 ошибок JavaScript:

Профессия Frontend-разработчик PRO
Готовим Frontend-разработчиков с нуля
На курсе вы научитесь создавать интерфейсы веб-сервисов с помощью языков программирования и
дополнительных технологий. Сможете разрабатывать планировщики задач, мессенджеры, интернет-магазины…
Узнать подробнее
До 10 проектов в портфолио для старта карьеры
Подходит для новичков без опыта в программировании
Практика на вебинарах с разработчиками из крупных компаний
Ошибки были сокращены для удобства чтения. Давайте погрузимся глубже в каждую, чтобы определить, что может вызвать её, и как избежать их появления.
1. Uncaught TypeError: Cannot read property
Если вы разработчик JavaScript, то, вероятно, видели эту ошибку очень много раз. Это происходит в Chrome, когда вы читаете свойство или вызываете метод на неопределенный объект. Это можно очень легко проверить в консоли разработчика Chrome.
Это может произойти по многим причинам, но чаще всего это неправильная инициализация состояния при рендеринге компонентов пользовательского интерфейса. Давайте рассмотрим пример того, как это может произойти в реальном приложении. Мы выберем React, но те же принципы неправильной инициализации применимы и к Angular, Vue или любой другой структуре.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Quiz extends Component { componentWillMount() { axios.get(‘/thedata’).then(res => { this.setState({items: res.data}); }); } render() { return ( <ul> {this.state.items.map(item => <li key={item.id}>{item.name}</li> )} </ul> ); } } |
Здесь понимаются две важные вещи:
Состояние компонента (например, this.state ) начинает жизнь как undefined.
Когда вы извлекаете данные асинхронно, компонент будет отображаться как минимум один раз перед загрузкой данных — независимо от того, выбрана ли она в конструкторе componentWillMount или componentDidMount . Когда Quiz отображается впервые, this.state.items не определен. Это, в свою очередь, означает, что ItemList получает элементы как неопределенные, и вы получаете сообщение об ошибке «Uncaught TypeError: Невозможно прочитать карту свойств» в консоли.
Это легко исправить. Самый простой способ: инициализировать состояние с разумными значениями по умолчанию в конструкторе.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class Quiz extends Component { // Added this: constructor(props) { super(props); // Assign state itself, and a default value for items this.state = { items: [] }; } componentWillMount() { axios.get(‘/thedata’).then(res => { this.setState({items: res.data}); }); } render() { return ( <ul> {this.state.items.map(item => <li key={item.id}>{item.name}</li> )} </ul> ); } } |
Код в вашем приложении может отличаться, но мы надеемся, что дали вам достаточно информации, чтобы исправить или избежать этой проблемы. Если нет, продолжайте читать, потому что далее мы рассмотрим больше примеров связанных с ошибками.
2. TypeError: ‘undefined’ is not an object (evaluating
Эта ошибка возникает в Safari при чтении свойства или вызове метода для неопределенного объекта. Вы можете проверить это в консоли разработчика Safari. Это по сути то же самое, что и вышеприведенная ошибка для Chrome, только Safari использует другое сообщение об ошибке.
3. TypeError: null is not an object (evaluating
Это ошибка, которая возникает в Safari при чтении свойства или вызове метода для пустого объекта. Проверить это можно в консоли разработчика Safari.
Интересно, что в JavaScript значения null и undefined не совпадают, поэтому мы видим два разных сообщения об ошибках. Undefined обычно является переменной, которая не была назначена, а null означает, что значение пустое. Чтобы убедиться, что они не одно и то же, попробуйте использовать строгий оператор равенства:
Один из способов, которым эта ошибка может возникнуть в реальном мире — это попытка использовать элемент DOM в JavaScript перед загрузкой элемента. Это потому, что DOM API возвращает null для ссылок на пустые объекты.
Любой JS-код, который выполняет и обрабатывает элементы DOM, должен выполняться после создания элементов DOM. JS-код интерпретируется сверху вниз, как изложено в HTML. Итак, если перед элементами DOM есть тег, код JS в теге скрипта будет выполняться, когда браузер анализирует HTML-страницу. Вы получите эту ошибку, если элементы DOM не были созданы до загрузки сценария.
В этом примере мы можем решить проблему, добавив прослушиватель событий, который уведомит нас, когда страница будет готова. После addEventListener метод init() может использовать элементы DOM.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<script> function init() { var myButton = document.getElementById(«myButton»); var myTextfield = document.getElementById(«myTextfield»); myButton.onclick = function() { var userName = myTextfield.value; } } document.addEventListener(‘readystatechange’, function() { if (document.readyState === «complete») { init(); } }); </script> <form> <input type=«text» id=«myTextfield» placeholder=«Type your name» /> <input type=«button» id=«myButton» value=«Go» /> </form> |
4. (unknown): Script error
Ошибка скрипта возникает, когда ошибка неперехваченного JavaScript пересекает границы домена и нарушает политику перекрестного происхождения. Например, если вы размещаете свой код JavaScript на CDN, любые неперехваченные ошибки (ошибки, которые появляются в обработчике window.onerror, вместо того, чтобы быть пойманным в try-catch) будут переданы как просто «Script error» вместо того, чтобы содержать полезную информацию. Эта мера безопасности браузера предназначена для предотвращения передачи данных по доменам, которые в противном случае не были бы допущены к коммуникации.
Чтобы получить реальные сообщения об ошибках, выполните следующие действия:
1. Отправьте заголовок Access-Control-Allow-Origin
Установка заголовка Access-Control-Allow-Origin в * означает, что к ресурсу можно получить доступ из любого домена. Вы можете заменить * своим доменом, если необходимо: например, Access-Control-Allow-Origin: www.example.com . Если вы используете CDN из-за проблем с кэшированием, которые могут возникнуть, обработка нескольких доменов становится сложной и нельзя не приложить усилий. Подробнее см. здесь.
Вот несколько примеров того, как установить этот заголовок в различных средах:
Apache
В папках, где будут храниться ваши файлы JavaScript, создайте файл .htaccess со следующим содержимым:
|
Header add Access—Control—Allow—Origin «*» |
Nginx
Добавьте директиву add_header в блок местоположения, который служит файлам JavaScript:
|
location ~ ^/assets/ { add_header Access—Control—Allow—Origin *; } |
HAProxy
Добавьте в ресурс, где будут загружены файлы JavaScript:
|
rspadd Access—Control—Allow—Origin: * |
2. Установите crossorigin = «anonymous» в теге скрипта
В HTML-источнике для каждого из сценариев, где вы установите заголовок Access-Control-Allow-Origin, в теге SCRIPT установите crossorigin=»anonymous». Убедитесь, что заголовок отправляется для файла сценария, перед добавлением свойства crossorigin в тег скрипта. В Firefox, если атрибут crossorigin присутствует, но заголовок Access-Control-Allow-Origin отсутствует, сценарий не будет выполнен.
5. TypeError: Object doesn’t support property
Это ошибка, которая возникает в IE при вызове неопределенного метода. Вы можете проверить это в IE Developer Console.
Это эквивалентно ошибке «TypeError: ‘undefined’ is not a function» в Chrome. Да, разные браузеры могут иметь разные сообщения для одной и той же ошибки.
Это обычная проблема для IE в веб-приложениях, использующих пространство имен JavaScript. Когда это так, проблема в 99,9% случаев— это неспособность IE связать методы в текущем пространстве имен с ключевым словом this. Например, если у вас есть пространство имен имен Rollbar с помощью метода isAwesome. Обычно, если вы находитесь в пространстве имен Rollbar вы можете вызвать метод isAwesome со следующим синтаксисом:
Chrome, Firefox и Opera с радостью согласятся с этим синтаксисом. С другой стороны, IE не станет. Таким образом, самая безопасная ставка при использовании JS namespacing — это префикс с фактическим пространством имен.
6. TypeError: ‘undefined’ is not a function
Это ошибка, возникающая в Chrome при вызове неопределенной функции. Вы можете протестировать это в консоли разработчика Chrome и в Mozilla Firefox.
Поскольку на протяжении многих лет методы кодирования JavaScript и шаблоны проектирования становятся все более изощренными, наблюдается соответствующее увеличение распространения областей самореференции в обратных вызовах и закрытиях, которые являются довольно распространенным источником этой путаницы.
Рассмотрим фрагмент кода:
|
function clearBoard(){ alert(«Cleared»); } document.addEventListener(«click», function(){ this.clearBoard(); // what is “this” ? }); |
Выполнение вышеуказанного кода приводит к следующей ошибке: «Uncaught TypeError: undefined is not function». Причина, по которой вы получаете эту ошибку, заключается в том, что при вызове setTimeout() вы вызываете window.setTimeout(). В результате анонимная функция, передаваемая setTimeout(), определяется в контексте объекта окна, у которого нет clearBoard().
Традиционное решение, совместимое со старым браузером — просто сохранить ссылку на this в переменной, которая затем может быть унаследована закрытием. Например:
|
var self=this; // save reference to ‘this’, while it’s still this! document.addEventListener(«click», function(){ self.clearBoard(); }); |
Кроме того, в новых браузерах для передачи правильной ссылки вы можете использовать метод bind():
|
document.addEventListener(«click»,this.clearBoard.bind(this)); |
7. Uncaught RangeError: Maximum call stack
Это ошибка, которая возникает в Chrome при нескольких обстоятельствах. Одно из них — когда вы вызываете рекурсивную функцию, которая не завершается. Вы можете протестировать это в консоли разработчика Chrome.
Это также может произойти, если вы передадите значение функции, находящейся за пределами допустимого диапазона. Многие функции принимают только определенный диапазон чисел для своих входных значений. Например, Number.toExponential(digits) и N umber.toFixed(digits) принимают цифры от 0 до 20, а Number.toPrecision(digits) принимают цифры от 1 до 21.
|
var a = new Array(4294967295); //OK var b = new Array(—1); //range error var num = 2.555555; document.writeln(num.toExponential(4)); //OK document.writeln(num.toExponential(—2)); //range error! num = 2.9999; document.writeln(num.toFixed(2)); //OK document.writeln(num.toFixed(25)); //range error! num = 2.3456; document.writeln(num.toPrecision(1)); //OK document.writeln(num.toPrecision(22)); //range error! |
8. TypeError: Cannot read property ‘length’
Это ошибка, которая возникает в Chrome из-за свойства длины чтения для неопределенной переменной. Вы можете протестировать это в консоли разработчика Chrome.
Обычно вы определяете длину, определенную в массиве, но вы можете столкнуться с этой ошибкой, если массив не инициализирован или если имя переменной скрыто в другом контексте. Давайте рассмотрим эту ошибку на следующем примере.
|
var testArray= [«Test»]; function testFunction(testArray) { for (var i = 0; i < testArray.length; i++) { console.log(testArray[i]); } } testFunction(); |
Когда вы объявляете функцию с параметрами, эти параметры становятся локальными. Это означает, что даже если у вас есть переменные с именами testArray , параметры с одинаковыми именами внутри функции будут по-прежнему рассматриваться как локальные.
У вас есть два способа решить эту проблему:
1. Удалите параметры в объявлении функции (оказывается, вы хотите получить доступ к тем переменным, которые объявлены вне функции, поэтому вам не нужны параметры для вашей функции):
|
var testArray = [«Test»]; /* Precondition: defined testArray outside of a function */ function testFunction(/* No params */) { for (var i = 0; i < testArray.length; i++) { console.log(testArray[i]); } } testFunction(); |
2. Вызовите функцию, передав ей массив, который мы объявили:
|
var testArray = [«Test»]; function testFunction(testArray) { for (var i = 0; i < testArray.length; i++) { console.log(testArray[i]); } } testFunction(testArray); |
9. Uncaught TypeError: Cannot set property
Когда мы пытаемся получить доступ к неопределенной переменной, она всегда возвращает undefined, а мы не можем получить или установить любое свойство undefined. В этом случае приложение будет выбрасывать “Uncaught TypeError cannot set property of undefined.”
Например, в браузере Chrome:
Если объект test не существует, будет выдаваться ошибка: “Uncaught TypeError cannot set property of undefined.”
10. ReferenceError: event is not defined
Эта ошибка возникает при попытке получить доступ к переменной, которая не определена или находится за пределами текущей области. Вы можете легко протестировать её в браузере Chrome.
Если вы получаете эту ошибку при использовании системы обработки событий, убедитесь, что вы используете объект события, переданный в качестве параметра. Старые браузеры, такие как IE, предлагают событие глобальной переменной, но не поддерживаются во всех браузерах. Библиотеки, подобные jQuery, пытаются нормализовать это поведение. Тем не менее, лучше использовать тот объект, который передается в функцию обработчика событий.
|
document.addEventListener(«mousemove», function (event) { console.log(event); }) |
Вывод
Мы надеемся, что вы узнали что-то новое и сможете избежать ошибок в будущем или, что это руководство помогло вам решить возникшую проблему. Тем не менее, даже при использовании лучших практик в производстве появляются неожиданные ошибки. Важно иметь видимость ошибок, которые влияют на пользователей, и хорошие инструменты для их быстрого решения.
Rollbar предлагает уникальные возможности для приложений JavaScript, таких как телеметрия, которые сообщают вам, что произошло в браузере пользователя, что приводит к ошибке. Это прозрение, которого у вас нет за пределами локальной консоли разработчика.
Автор: Jason Skowronski
Источник: //rollbar.com/

Профессия Frontend-разработчик PRO
Готовим Frontend-разработчиков с нуля
На курсе вы научитесь создавать интерфейсы веб-сервисов с помощью языков программирования и
дополнительных технологий. Сможете разрабатывать планировщики задач, мессенджеры, интернет-магазины…
Узнать подробнее
До 10 проектов в портфолио для старта карьеры
Подходит для новичков без опыта в программировании
Практика на вебинарах с разработчиками из крупных компаний
Редакция: Команда webformyself.





















