ilyachalov (ilyachalov) wrote,
ilyachalov
ilyachalov

Category:

JavaScript: решение задачи добавления toString в словарь

Задача называется «Добавьте toString в словарь»:
https://learn.javascript.ru/task/dictionary-tostring

Это одна из двух задач к подразделу 8.4 «Методы прототипов, объекты без свойства __proto__» учебника по JavaScript.

По условиям задачи ученику дан следующий код:
let dictionary = Object.create(null);

// здесь напишите ваш код, который добавляет метод dictionary.toString

// добавляем немного данных
dictionary.apple = "Apple";
dictionary.__proto__ = "test"; // здесь __proto__ -- это обычный ключ

// только apple и __proto__ выведены в цикле
for(let key in dictionary) {
    alert(key);                // "apple", затем "__proto__"
}

// ваш метод toString в действии
alert(dictionary);             // "apple,__proto__"

Здесь в первой строке создается объект, ссылка на который записывается в переменную dictionary (по-русски это слово означает «словарь»). Далее в словарь записываются две пары «ключ/значение», это реализовано добавлением к объекту двух свойств. При этом имя свойства является ключом, а значение свойства — значением. После этого в цикле for..in последовательно выводятся на экран имена всех свойств (ключи) объекта-словаря. В конце делается попытка вывести на экран объект-словарь.

По замыслу автора этого кода сначала на экран последовательно должны быть выведены ключи, в данном случае apple и затем __proto__. При выводе на экран объекта-словаря должна быть выведена строка, содержащая ключи (имена свойств объекта) через запятую, то есть в данном случае apple,__proto__.

Если запустить этот код в браузере в заданном виде, то имена свойств (ключи) в цикле for..in будут выведены успешно. Однако, при попытке вывести на экран объект ничего не произойдет из-за ошибки (ее можно увидеть в консоли разработчика): «Uncaught TypeError: Cannot convert object to primitive value» (по крайней мере, так она показана в моем браузере «Microsoft Edge», который сейчас делают на базе движка «Chromium»).

Эта ошибка происходит из-за того, что в данном случае объект-словарь создан специальным образом, без прототипа. В его скрытое свойство [[Prototype]] записано специальное значение null с помощью метода Object.create. Если бы мы создали объект обычным способом (с помощью «литерального синтаксиса»), то ошибки при выводе объекта на экран не произошло бы. Например:

let dictionary = {}; // синтаксис «литерал объекта»
alert( dictionary );  // [object Object]

При создании объекта с помощью «литерального синтаксиса» в его скрытое свойство [[Prototype]] записывается ссылка на объект-прототип всех объектов в языке JavaScript, на который ссылается свойство Object.prototype встроенной функции-конструктора Object. В этом прототипе есть метод toString, который и возвращает строку [object Object] в приведенном примере, которую функция alert выводит на экран.

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

Мой первый вариант решения задачи был такой:
dictionary.toString = function() {
    return Object.keys(dictionary);
};

Тут два момента. Во-первых, после добавления метода toString в объект-словарь, на который указывает переменная dictionary, цикл for..in начал выдавать на экран не только имена свойств (ключи) объекта-словаря, но и имя метода toString, потому что методы тоже считаются своеобразными свойствами объекта. Но по условиям задачи цикл for..in должен игнорировать метод toString.

Во-вторых, вывода объекта на экран не произошло, а в консоли разработчика была выдана та же ошибка: «Uncaught TypeError: Cannot convert object to primitive value». Однако, на самом деле, здесь речь идет уже о другом объекте. Выражение Object.keys(dictionary) возвращает массив ключей объекта-словаря, а массив в языке JavaScript тоже является объектом. Я надеялся, что в данном случае сработает метод toString массива, но этого не произошло. Получается, что наш метод dictionary.toString обязательно должен возвратить строку, а не объект.

Учитывая всё вышеизложенное, я написал второй вариант решения задачи:
dictionary.toString = function() {
    return Object.keys(dictionary).join();
};

Object.defineProperty(dictionary, "toString", {
    enumerable: false
});

Выражение Object.keys(dictionary) возвращает массив с ключами объекта-словаря, после чего к этому массиву применяется метод join, который преобразует массив ключей в строку со значениями элементов массива (ключами), разделенными запятой (по умолчанию этот метод в качестве разделителя использует запятую, если разделитель не задан).

С помощью метода Object.defineProperty свойство-метод toString сделано неперечислимым и теперь будет проигнорировано циклом for..in. Об этом рассказывалось в подразделе 7.1 «Флаги и дескрипторы свойств» учебника.

В принципе, задача решена и всё работает так, как требуется.

Однако, в выражении Object.keys(dictionary) из тела нашего метода переменную dictionary по-хорошему следует заменить на переменную this. Для чего это нужно?

Дело в том, что переменная dictionary содержит лишь указатель на наш объект-словарь, а не является объектом-словарем. Если в промежутке между объявлением нашего метода toString и его применением указатель на объект-словарь будет переписан в другую переменную, а в переменную dictionary будет записано что-то другое, это приведет к ошибке при применении нашего метода toString. Например:

let dictionary = Object.create(null);

dictionary.toString = function() {
    return Object.keys(dictionary).join(); // первопричина ошибки
};

Object.defineProperty(dictionary, "toString", {
    enumerable: false
});

dictionary.apple = "Apple";
dictionary.__proto__ = "test";

let dictionary2 = dictionary; // указатель на объект-словарь сохранен в другой переменной
dictionary = {};              // в переменную dictionary сохранен указатель на пустой объект

// обе эти попытки вывода ключей объекта-словаря не приведут к успеху
alert(dictionary);            // [object Object]
alert(dictionary2);           // пустая строка

Переменная this же всегда будет равна той переменной, от которой метод toString запускается в данный момент. Таким образом, окончательное решение задачи у меня выглядит так:

dictionary.toString = function() {
    return Object.keys(this).join();
};

Object.defineProperty(dictionary, "toString", {
    enumerable: false
});
Tags: Образование, Программирование
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments