Ошибки при деструктуризации ES6

Перевод интересной заметки Николаса Закаса1.

Несмотря на все прелести нового ситакса в ECMAScript 6, Вы обязательно будете находить что-то удивительное (как будто вы ищите каждую ошибку).

Недавно заметил всплеск появления отчетов об ошибках определенного типа, которые относятся к деструктуризации2 с использованием объектной модели.

Основы деструктуризации

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

var node = {  
    type: "Identifier",
    value: "foo"
};
var { type } = node;  
console.log(type);      // "Identifier"  

В этом примере создается переменная type и ей присваивается значение node.type используя деструктуризацию. Вы также можете захватить value, если хотите:

var node = {  
    type: "Identifier",
    value: "foo"
};

var { type, value } = node;  
console.log(type);      // "Identifier"  
console.log(value);     // "foo"  

Деструкция позволяет извлекать свойства из объекта. На первый взгляд, все это довольно просто и не требует объяснений.

Различные имена переменных

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

var node = {  
    type: "Identifier",
    value: "foo"
};

var { type: myType } = node;  
console.log(myType);      // "Identifier"  
console.log(type);        // error: type is not defined  

Здесь создается переменная под именем myType и ей присваивается значение node.type. Этот синтаксис немного запутанный, поскольку он похож на синтаксис объектных литералов. Переменная type не создается.

Значения по умолчанию

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

var node = {  
    type: "Identifier",
    value: "foo"
};

var anotherNode = {};

var { type: myType = "Unknown" } = anotherNode;  
console.log(myType);      // "Unknown"  

Этот пример создает локальную переменную myType, которой присваивается значение node.type, если оно определено. В противном случае, значение myType будет строка "Unknown".

Вложенная деструктуризация

Вы можете ещё усложнить этот пример с помощью вложенной деструктуризации. Это означает, что можно извлекать значения из объектов внутри объектов, таких как:

var node = {  
    type: "Identifier",
    value: "foo",
    loc: {
        start: {
            line: 1,
            column: 5
        },
        end: {
            line: 1,
            column: 8
        }
    }
};

var { loc: { start: { line }} } = node;  
console.log(line);      // 1  
console.log(loc);       // error: loc is undefined  

В этом примере будет создана только локальная переменная line со значением 1. Символы loc и start внутри объектной модели просто служат в качестве информации о местоположении, чтобы найти line.

Ошибки

Вот здесь и начинается путаница. Теперь, когда у Вы прошли этот ускоренный курс деструктуризации, какое поведение Вы будете ждать от следующего кода?

var node = {  
    type: "Identifier",
    value: "foo",
    loc: {
        start: {
            line: 1,
            column: 5
        },
        end: {
            line: 1,
            column: 8
        }
    }
};

var { loc: {} } = node;  
console.log(loc);       // ?  

Вы удивитесь узнав, что на самом деле console.log(loc) выдаст ошибку, поскольку переменная loc не определена.

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

Это сбивает с толку, потому что это выглядит как объектный литерал, свойству loc которого присваивается пустой объект, но на самом деле это не так.

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

var node = {  
    type: "Identifier",
    value: "foo",
    loc: {
        start: {
            line: 1,
            column: 5
        },
        end: {
            line: 1,
            column: 8
        }
    }
};

var { loc = {} } = node;  
console.log(loc);       // [Object object]  

Здесь loc всегда будет новым объектом, вне зависимости существует ли node.loc.

Вывод

Вложенная деструктуризация может быть довольно запутанной, особенно если Вы используйте значения по умолчанию. Во всех случаях идентификатор {} в значении деструктора является ошибкой: он ничего не делает. Такая ошибка может случиться, если вы решили назначить значение по умолчанию для переменной.

В ESLint просто добавили правило new-empty-pattern3, чтобы выявлять эту проблему, поэтому я бы рекомендовал включить его немедленно, если вы используете деструктуризацию в коде.

  1. Оригинал by Nicholas C. Zakas

  2. Destructuring by Nicholas C. Zakas on leanpub.com

  3. Disallow empty destructuring patterns