07. Функции. Часть 1. Общие вопросы
Контрольные задания
-
При работе с объектами стандартная функция alert малопригодна. Она выводит на экран результат, возвращаемый методом toString объекта. Метод toString наследуется от Object и преобразует объект в строку "[оbject Object]". Создайте функцию alertob, которая выводила бы на экран свойства объектов и их значения в удобном для восприятия виде. Используйте в дальнейшем эту функцию при отладке кодов, которые работают с объектами.
Решениеfunction alertob(ob) { var a = []; // Поместим элементы объекта в массив, for(p in ob) a.push(p+":"+ob[p]); // а затем преобразуем массив методом alert("{"+a.join(",")+"}"); // join в строку с разделителем "," } // Тест var ob1 = {}; alertob(ob1); // {} var ob2 = {x:1,y:"киса"}; alertob(ob2); // {x:1,y:киса}
-
Напишите функцию, которая добавляет значения свойств объекта в массив (в предположении, что значения свойств объекта имеют элементарный тип данных).
// Добавить значения свойств объекта в массив и вернуть этот массив. // Если массив не задан, создать его function addObjectToArray ( ob, // объект, значения свойств которого добавляются в массив a // массив, в который идет добавление (необязательный аргумент) ) { ... } var m = [1,2]; var d = {x:10,y:20,z:"лиса"} var x = addObjectToArray(d,m); // [1,2,10,20,"лиса"] var y = addObjectToArray(d); // [10,20,"лиса"]
Решение// Добавить значения свойств объекта в массив и вернуть этот массив. // Если массив не задан, создать его function addObjectToArray ( ob, // объект, значения свойств которого добавляются в массив a // массив, в который идет добавление (необязательный аргумент) ) { a = a || []; for(var i in ob) a.push(ob[i]); return a; } var m = [1,2]; var d = {x:10,y:20,z:"лиса"} var x = addObjectToArray(d,m); // [1,2,10,20,"лиса"] var y = addObjectToArray(d); // [10,20,"лиса"]
Отметим, что написанная функция addObjectToArray будет корректно работать только для объектов, содержащих элементарные типы данных (числа, строки, логические значения). Только такие данные будут реально копироваться методом push. Если ob — объект, push(ob) будет копировать ссылку на ob, а не сам объект.
var m = [1,2]; var d = {x:10,y:{t:"кот"}}; var x = addObjectToArray(d,m); // [1,2,10,{t:"кот"}] d.y.t="лиса"; x[3].t == "лиса"; // Равно true
-
Напишите функцию sumObject, которая возвращает объект, объединяющий объекты, передаваемые в функцию в качестве аргументов. Объединение получается следующим образом: в результирующий объект добавляются те свойства (вместе со значениями), которых в результирующем объекте еще нет.
Примеры использования этой функции:
sumObject(); // Равно {} var ob1 = {x:1}; sumObject(ob1); // Равно {x:1} var ob2 = {x:10,y:20}; sumObject(ob1,ob2); // Равно {x:1,y:20} sumObject(ob2,ob1); // Равно {x:10,y:20} var ob3 = {x:100,y:200,z:"кот"}; sumObject(ob1,ob2,ob3); // Равно {x:1,y:20,z:"кот"}
Решениеfunction sumObject() { var sum = {}; // Цикл по числу фактических аргументов for(var i=0; i<arguments.length; i++) { // Цикл по числу свойств в текущем объекте for(var p in arguments[i]) // Если этого свойства в объекте sum еще нет, добавить // его вместе с его значением if (!(p in sum)) sum[p] = arguments[i][p]; } return sum; } sumObject(); // Равно {} var ob1 = {x:1}; sumObject(ob1); // Равно {x:1} var ob2 = {x:10,y:20}; sumObject(ob1,ob2); // Равно {x:1,y:20} sumObject(ob2,ob1); // Равно {x:10,y:20} var ob3 = {x:100,y:200,z:"кот"}; sumObject(ob1,ob2,ob3); // Равно {x:1,y:20,z:"кот"}
Комментарий
Будет ли корректно работать эта функция, если некоторые свойства объекта будут являться объектами? Например, как будет работать следующий код:
var ob1 = {x:1}; var ob2 = {x:10,y:{z:1}}; var ob3 = sumObject(ob1,ob2); // Равно ли ob3 {x:1,y:{z:1}} ?
Да, ob3, действительно, равен {x:1,y:{z:1}}, хотя инструкция sum[p] = arguments[i][p]; копирует, конечно, не объект {z:1}, а ссылку на него.
А что будет, если мы теперь запишем delete ob2.y?
Проверим. По прежнему ob3 равен {x:1,y:{z:1}}.
Вот как это объясняется. Инструкция var ob2 = {x:10,y:{z:1}}; создала переменную ob2 и записала в нее ссылку на объект {x:10,y:{z:1}}, который разместила где-то в памяти.
Свойство у в этом объекте само содержит ссылку на объект {z:1}, который размещается где-то в памяти.
После работы функции, инструкция var ob3 = sumObject(ob1,ob2); записывает в переменную ob3 ссылку на созданный функцией объект. В этом объекте есть свойство y, и в него записана ссылка на объект {z:1} — тот же самый, на который ссылается свойство y в объекте ob2.
Получается, что на один и тот же объект, расположенный где-то в памяти, ссылаются два разных свойства: ob2.y и ob3.y
Применяем инструкцию delete ob2.y Она уничтожает свойство y объекта ob2. Уничтожается ссылка на объект {z:1}, но не сам объект! Объект в памяти сохраняется потому, что существует вторая ссылка на него ob3.y.
Вот если бы на объект {z:1} ссылок больше не было, сборщик мусора JavaScript уничтожил бы объект {z:1} автоматически. Но ссылка есть, объект в памяти сохраняется.
Однако надо помнить, что функция sumObject реально объекты не копирует, а копирует только ссылки на них. И если объект меняется по первой ссылке, изменения будут видны и по второй.
var ob1 = {x:1}; var ob2 = {x:10,y:{z:1}}; var ob3 = sumObject(ob1,ob2); // Равно {x:1,y:{z:1}} ob2.y.z="лист"; ob3.y.z == "лист"; // Равно true
Таким образом, написанная функция sumObject честно работает только для объектов, содержащих элементарные типы данных (числа, строки, логические значения). Если объект-аргумент сам содержит объект, в результирующий объект копируется ссылка.
Можно ли написать функцию sumObject, которая реально копировала бы объекты (а не ссылки на них)? Можно, конечно, но эта задача существенно сложнее. Кроме того, она рекурсивная.
-
Написать функцию goExecutor, которая выполняла бы программу, записанную для некоторого исполнителя.
// Выполнить программу исполнителя // Аргументы: // program -- строка, содержащая программу исполнителя // Программа представляет собой последовательность // команд исполнителя из его СКИ. Команды отделяются // друг от друга одним или несколькими пробелами. // Начальные и конечные пробелы в строке игнорируются. // Команды исполнителя -- это идентификаторы (без параметров) // executor -- объект, реализующий среду исполнителя и его СКИ. // СКИ исполнителя задаётся объектом ski, состоящим из пар // "команда:функция_которая_эту_команду_выполняет" // Возвращаемое значение: // 0 -- программа выполнена // 1 -- ошибочная команда ("Не понял") // 2 -- ошибка при выполнении программы ("Не могу"). // Эта ошибка возвращается функциями из СКИ. // Если ошибок при выполнении не было, функции // СКИ возвращают 0. function goExecutor(program, executor) { // Здесь код тела функции, который вы должны написать }
Тестирование
Проверим работу построенной функции на исполнителе Кукарача, который описан ниже.
Среда Кукарача — клетчатое поле. В начальный момент исполнитель расположен в левом верхнем углу своего поля. СКИ исполнителя состоит из четырёх команд: ВЛЕВО, ВПРАВО, ВНИЗ и ВВЕРХ. Команды перемещают исполнителя на одну клетку, соответственно, влево, вправо, вниз и вверх. Если команда пытается переместить исполнителя за пределы поля, возникает ошибка времени исполнения ("Не могу"). Опишем исполнителя в виде объекта Cockroach. Так как исполнитель предполагается в одном экземпляре, объект построим не с помощью функции-конструктора, а с помощью литерала:
// Исполнитель Кукарача // -------------------- var Cockroach = { // Среда x:0, // Координата исполнителя по горизонтали y:0, // Координата исполнителя по вертикали xLength:5, // Число клеток по горизонтали yLength:5, // Число клеток по вертикали // СКИ ski: { "ВЛЕВО": function() // Если исполнитель не в первом столбце, { // переместить его влево и вернуть 0, var rez=0; // иначе вернуть 2. this.x>0 ? this.x-- : rez=2; return rez; }, "ВПРАВО": function() // Если исполнитель не в последнем столбце, { // переместить его вправо и вернуть 0, var rez=0; // иначе вернуть 2. this.x<this.xLength-1 ? this.x++ : rez=2; return rez; }, "ВВЕРХ": function() // Если исполнитель не в первой строке, { // переместить его вверх и вернуть 0, var rez=0; // иначе вернуть 2. this.y>0 ? this.y-- : rez=2; return rez; }, "ВНИЗ": function() // Если исполнитель не в последней строке, { // переместить его вниз и вернуть 0, var rez=0; // иначе вернуть 2. this.y<this.yLength-1 ? this.y++ : rez=2; return rez; } }, // Сообщения mess: [ "Выполнено", // Программа успешно выполнена "Не понял", // Команда не входит в СКИ "Не могу" // Попытка переместить исполнителя за пределы поля ], // Переместить исполнителя в начальное положение home: function () {this.x = 0; this.y = 0;}, // Показать среду исполнителя (подготовить строку для alert) show: function () { var str = ""; for(var i=0; i<this.yLength; i++) { str += "\n"; for(var j=0; j<this.xLength; j++) str += (i==this.y && j==this.x) ? "!" :"."; } return str; } };
Подготовим тестовый код:
// Тестирование кода var rez = goExecutor(" ВПРАВО ВПРАВО ВНИЗ ", Cockroach); alert(Cockroach.mess[rez]+Cockroach.show()); rez = goExecutor(" ВЛЕВО ВНИЗ ", Cockroach); alert(Cockroach.mess[rez]+Cockroach.show()); rez = goExecutor("ВНИЗ В ПРАВО", Cockroach); alert(Cockroach.mess[rez]+Cockroach.show()); Cockroach.home(); rez = goExecutor("ВНИЗ ВНИЗ ВНИЗ ВНИЗ ВНИЗ", Cockroach); alert(Cockroach.mess[rez]+Cockroach.show());
Вот что мы должны увидеть на экране во время выполнения этого кода:
Выполнено . . . . . . . ! . . . . . . . . . . . . . . . . . Выполнено . . . . . . . . . . . ! . . . . . . . . . . . . . Не понял . . . . . . . . . . . . . . . . ! . . . . . . . . Не могу . . . . . . . . . . . . . . . . . . . . ! . . . .
Решение// Выполнить программу исполнителя // Аргументы: // program -- строка, содержащая программу исполнителя // Программа представляет собой последовательность // команд исполнителя из его СКИ. Команды отделяются // друг от друга одним или несколькими пробелами. // Начальные и конечные пробелы в строке игнорируются. // Команды исполнителя -- это идентификаторы (без параметров) // executor -- объект, реализующий среду исполнителя и его СКИ. // СКИ исполнителя задаётся объектом ski, состоящим из пар // "команда:функция_которая_эту_команду_выполняет" // Возвращаемое значение: // 0 -- программа выполнена // 1 -- ошибочная команда ("Не понял") // 2 -- ошибка при выполнении программы ("Не могу"). // Эта ошибка возвращается функциями из СКИ. // Если ошибок при выполнении не было, функции // СКИ возвращают 0. function goExecutor(program, executor) { // Предварительная обработка program // --------------------------------- // Удалить начальные и конечные пробелы program = program.replace(/^\s+|\s+$/g,''); // Преобразовать program в массив команд // -------------------------------------- var commands = program.split(/\s+/); var rez = 0; // Выполнить программу for(var i=0; i<commands.length && !rez; i++) // Цикл по командам // Выполнить очередную команду, если она есть в CКИ rez = (commands[i] in executor.ski) ? executor.ski[commands[i]].call(executor) : 1; return rez; // Возвращаем флаг выполнения } // Исполнитель Кукарача // -------------------- var Cockroach = { // Среда x:0, // Координата исполнителя по горизонтали y:0, // Координата исполнителя по вертикали xLength:5, // Число клеток по горизонтали yLength:5, // Число клеток по вертикали // СКИ ski: { "ВЛЕВО": function() // Если исполнитель не в первом столбце, { // переместить его влево и вернуть 0, var rez=0; // иначе вернуть 2. this.x>0 ? this.x-- : rez=2; return rez; }, "ВПРАВО": function() // Если исполнитель не в последнем столбце, { // переместить его вправо и вернуть 0, var rez=0; // иначе вернуть 2. this.x<this.xLength-1 ? this.x++ : rez=2; return rez; }, "ВВЕРХ": function() // Если исполнитель не в первой строке, { // переместить его вверх и вернуть 0, var rez=0; // иначе вернуть 2. this.y>0 ? this.y-- : rez=2; return rez; }, "ВНИЗ": function() // Если исполнитель не в последней строке, { // переместить его вниз и вернуть 0, var rez=0; // иначе вернуть 2. this.y<this.yLength-1 ? this.y++ : rez=2; return rez; } }, // Сообщения mess: [ "Выполнено", // Программа успешно выполнена "Не понял", // Команда не входит в СКИ "Не могу" // Попытка переместить исполнителя за пределы поля ], // Переместить исполнителя в начальное положение home: function () {this.x = 0; this.y = 0;}, // Показать среду исполнителя (подготовить строку для alert) show: function () { var str = ""; for(var i=0; i<this.yLength; i++) { str += "\n"; for(var j=0; j<this.xLength; j++) str += (i==this.y && j==this.x) ? "!" :"."; } return str; } }; // Тестирование кода var rez = goExecutor(" ВПРАВО ВПРАВО ВНИЗ ", Cockroach); alert(Cockroach.mess[rez]+Cockroach.show()); rez = goExecutor(" ВЛЕВО ВНИЗ ", Cockroach); alert(Cockroach.mess[rez]+Cockroach.show()); rez = goExecutor("ВНИЗ В ПРАВО", Cockroach); alert(Cockroach.mess[rez]+Cockroach.show()); Cockroach.home(); rez = goExecutor("ВНИЗ ВНИЗ ВНИЗ ВНИЗ ВНИЗ", Cockroach); alert(Cockroach.mess[rez]+Cockroach.show());
Скопируйте этот код в испытатель и запустите.
Пояснения, пожалуй, заслуживает лишь одна инструкция:
rez = (commands[i] in executor.ski) ? executor.ski[commands[i]].call(executor) : 1;
Во-первых, здесь переменной rez присваивается результат выполнения условного трехместного оператора ?. Напомню его формат:
(логическое_выражение) ? выражение1 : выражение2
Работает так: проверяется логическое_выражение. Если оно истинно, результатом операции ? является значение, полученное при вычислении выражения 1, иначе — значение, полученное при вычислении выражения 2.
В нашем случае (логическое_выражение) есть (commands[i] in executor.ski) Проверяется, входит ли свойство commands[i] в состав объекта executor.ski Если входит, вычисляется выражение executor.ski[commands[i]].call(executor).
Это выражение — вызов соответствующей функции из СКИ в контексте объекта executor.
Заметим, что если бы мы записали вызов функции обычным образом: executor.ski[commands[i]](), то this в этой функции относился бы к объекту executor.ski, а нам нужно, чтобы он относился к объекту executor, в котором заданы переменные среды исполнителя. Именно поэтому используется метод call для явного указания контекста исполнения функции.
-
Придумать и построить (в виде объекта) нового исполнителя (отличного от Кукарачи) и проверить работу построенной в задании 4 функции goExecutor на этом исполнителе.
Решение// Выполнить программу исполнителя // Аргументы: // program -- строка, содержащая программу исполнителя // Программа представляет собой последовательность // команд исполнителя из его СКИ. Команды отделяются // друг от друга одним или несколькими пробелами. // Начальные и конечные пробелы в строке игнорируются. // Команды исполнителя -- это идентификаторы (без параметров) // executor -- объект, реализующий среду исполнителя и его СКИ // СКИ исполнителя в объекте задаётся объектом ski, // состоящим из пар "команда:функция_которая_эту_команду_выполняет" // Возвращаемое значение: // 0 -- программа выполнена // 1 -- ошибочная команда ("Не понял") // 2 -- ошибка при выполнении программы ("Не могу"). // Эта ошибка возвращается функциями из СКИ. // Если ошибок при выполнении не было, функции // СКИ возвращают 0. function goExecutor(program, executor) { // Предварительная обработка program // --------------------------------- // Удалить начальные и конечные пробелы program = program.replace(/^\s+|\s+$/g,''); // Преобразовать program в массив команд // -------------------------------------- var commands = program.split(/\s+/); var rez = 0; // Выполнить программу for(var i=0; i<commands.length && !rez; i++) // Цикл по командам // Выполнить очередную команду, если она есть в CКИ rez = (commands[i] in executor.ski) ? executor.ski[commands[i]].call(executor) : 1; return rez; // Возвращаем флаг выполнения } // Исполнитель Вычислитель // ----------------------- // Среда исполнителя -- ячейка памяти (number) // Исполнитель умеет увеличивать содержимое ячейки на 1 ("Добавить") // и умножать содержимое ячейки на 2 ("Умножить") var Numerator = { // Среда number:0, // Число // СКИ ski: { "Добавить": function() // Добавить к числу 1 { this.number++; return 0; }, "Умножить": function() // Умножить число на 2 { this.number *= 2; return 0; } }, // Сообщения mess: [ "Выполнено", // Программа успешно выполнена "Не понял", // Команда не входит в СКИ "Не могу" // Попытка переместить исполнителя за пределы поля ], // Переместить исполнителя в начальное положение home: function () {this.number = 0;}, // Показать среду исполнителя (подготовить строку для alert) show: function () { return "\nСодежимое ячейки: "+this.number; } }; // Тестирование кода var rez = goExecutor(" Добавить Добавить Умножить", Numerator); alert(Numerator.mess[rez]+Numerator.show()); rez = goExecutor("Добавить Умножить", Numerator); alert(Numerator.mess[rez]+Numerator.show()); Numerator.home(); rez = goExecutor("Добавить Умножить", Numerator); alert(Numerator.mess[rez]+Numerator.show());