04. Объекты

Контрольные вопросы и задания

  1. Испытательный стенд Какое значение получит переменная x. Ответ объясните.

    
    var ob = {};
    for (var i=0; i<5; i++) ob["t"+2*i] = i-1;
    var t = ob.t8/3;
    var x = ob.t4+ob["t"+(t+5-0)];
    
    Ответ

    Ответ: 3. В результате работы цикла получен объект:
    ob = {t0:-1,t2:0,t4:1,t6:2,t8:3}

    Вычисляем t и x:
    t = ob.t8/3 = 3/3 = 1
    ob.t4+ob["t"+(t+5-0)] = 1+ob["t"+6] = 1+ob["t6"] = 1+2 = 3

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

    
    function getProperties(object)
    {                  
    }
    var ob = {x:10,y:"Роботландия",z:true};
    var n = getProperties(ob); // Равно строке "x:10, y:Роботландия, z:true"
                               // Заметьте, запятой после последней записи нет
    
    Ответ
    
    function getProperties(object)
    {
      var str = "";
      for(var x in object) str += x + ":" + object[x] + ",";
      if (str) str = str.substring(0, str.length-1);
      return str;
    }
    var ob = {x:10,y:"Роботландия",z:true};
    var n = getProperties(ob); // Равно строке "x:10, y:Роботландия, z:true"
                               // Заметьте, запятой после последней записи нет
    
  3. Испытательный стенд Какое значение получит переменная x. Ответ объясните.

    
    var ob = {x:2,y:3};
    ob.f = function (x) { return this.x + x; }
    var x = ob["y"] + ob.f(5);
    
    Ответ

    Ответ: число 10.

    ob["y"] = ob.y = 3
    ob.f(5) = this.x + 5 = 2+5 = 7

    Пояснение: this внутри функции ссылается на объект в контексте которого функция вызывается.

  4. Испытательный стенд Какие значения получат переменные x, y, z? Ответ объясните.

    
    var k = 10; 
    var p = {x:k--/5,y:k/3};
    var c = {p:p, color:"red"};
    var ob = {"p":p,"c":c};
    var x = ob.p.x;
    var y = ob.c.p.y;
    var z = ob.c.color.substring(2).concat(ob.p.y);
    
    Ответ

    x равно 2
    y равно 3
    z равно "d3"

    Пояснения

    Для начала перепишем объект p со свойствами-константами:

    p равно {x:k--/5,y:k/3} равно {x:2,y:3}.

    Оператор «запятая» выполняется слева направо, значит, сначала вычисляем x:k--/5. Выражение k-- выполняется так: сначала k участвует в выражении, затем уменьшается на 1. После вычисления x:k--/5 получим x:2, а k станет равным 9. Теперь вычисляем y:k/3, получаем y:3. Итак, p равно {x:2,y:3}.

    Оператор «точка» выполняется слева направо.

    Для x:

    
    ob.p;           // Указывает на  {x:2,y:3}
    ob.p.x;         // Указывает на 2
    var x = ob.p.x; // Копирует 2 в переменную x
    

    Для y:

    
    ob.c;             // Указывает на {p:p, color:"red"} 
                      // которое есть {p:{x:2,y:3}, color:"red"}
    ob.c.p;           // Указывает на {x:2,y:3}
    ob.c.p.y;         // Указывает на 3
    var y = ob.c.p.y; // Копирует 3 в переменную y
    

    Для z:

    
    ob.c // Указывает на {p:{x:2,y:3}, color:"red"} 
    ob.c.color // Указывает на "red"
    ob.c.color.substring(2) // Равно "d"
    ob.c.color.substring(2).concat(ob.p.y) // Равно "d".concat(3) равно "d3"
    var z = ob.c.color.substring(2).concat(ob.p.y); // Копирует "d3" в z
    

    Дополнительное пояснение

    Обратите внимание, запись вида ob.p есть операция доступа к свойству объекта. Оператор точка — двухместный. Слева должен быть объект или выражение, значением которого является ссылка на объект, а справа должен располагаться идентификатор (выражение не допускается) — точное имя свойства объекта.

    Результат выполнения операции точка — ссылка на значение этого свойства (неважно, какого типа это свойство, элементарного или объектного).

    Оператор точка имеет самый высокий приоритет (среди всех других операторов) и выполняется слева направо. Так:

    
    ob.p            // Указывает на объект  {x:2,y:3}
    ob.p.x;         // Указывает на число 2, эквивалентно (ob.p).x
    

    А вот инструкция

    
    var x = ob.p.x; 
    

    копирует число 2 в переменную x. Естественно, копируется само число, а не ссылка. Если теперь изменить свойство x:

    
    ob.p.x = 3;
    

    то это не изменит значение переменной x, оно по-прежнему будет равно 2.

    Если бы объект ob.p имел, например, вид {x:[2],y:3}, то по инструкции

    
    var x = ob.p.x; // ссылка на массив [2]
    

    в переменную x был бы скопирован не массив [2], а ссылка на него. И если теперь изменить свойство:

    
    ob.p.x[0] = 3;
    

    то это будет заметно и через переменную x — ссылка останется прежней, но она будет относится к массиву [3].

    Рассмотрим для примера ещё раз цепочку вычислений

    
    ob.c.color.substring(2).concat(ob.p.y) 
    

    Вычисляем «точку» слева направо:

    
    ob.c // Указывает на свойство c, которое есть  
         // объект {p:{x:2,y:3}, color:"red"} 
         // Свойство с, конечно, содержит не сам объект, а ссылку на него,
         // но это не важно сейчас (пока нет копирования, передачи в  
         // функцию или сравнения).
         // То есть ob.c указывает на объект {p:{x:2,y:3}, color:"red"} 
    ob.c.color // Указывает на "red", эквивалентно (ob.c).color
    ob.c.color.substring(2) // Равно "d", эквивалентно 
                            // (((ob.c).color).substring)(2)
                            // В игру вступил новый оператор () -- вызов 
                            // функции (метода, в данном случае).
                            // Функция вернула "d", и эта строка стала  
                            // теперь значением всего выражения 
                            // ob.c.color.substring(2)
    ob.c.color.substring(2).concat(ob.p.y) 
    

    Последняя строка в нашем случае эквивалентна:

    
    "d".concat(ob.p.y) // а это эквивалентно ("d".concat)(ob.p.y)
                       // то есть сначала вычисляется ("d".concat),
                       // результат -- ссылка на метод, а затем 
                       // вызывается сам метод при помощи оператора ()
    

    Результат вычисления последнего выражения — строка "d3". Заметим попутно, что при выполнении операции "d".concat для строки "d" создаётся объект-обёртка класса String, которая после выполнения операции уничтожается.

  5. Испытательный стенд Создайте конструктор объекта Complex для работы с комплексными числами вида x+yi. После определения конструктора должен правильно работать следующий код:

    
    var n = new Complex(1,3); // Создали объект для 1+3i
    var m = new Complex(3);   // Создали объект для 3+0i
    var p = n.add(m);         // Получили объект для 4+3i
    var s = p.toString();     // Получили строку "4+3i" 
    var m = p.valueOf();      // Получили число 5 (модуль комплексного числа)
    

    Что нужно знать о комплексных числах:

    
    (x1+y1i) + (x2+y2i) = (x1+x2) + (y1+y2)i       
    модуль числа x+yi равен квадратному корню от (x*x + y*y)
    
    Ответ
    
    // Конструктор объекта "комплексное число x+yi"
    function Complex(x,y)
    {
      // Свойства 
      this.x = x;
      this.y = y || 0;
      // Методы 
      this.add = function (t)  
      {
        var z = new Complex(this.x,this.y);
        z.x += t.x; 
        z.y += t.y;
        return z;
      }; 
      this.toString = function ()
      {
        return this.x + "+" + this.y + "i"; 
      };
      this.valueOf = function ()
      {
        return Math.sqrt(this.x*this.x + this.y*this.y); 
      };
    }
    var n = new Complex(1,3); // Создали объект для 1+3i
    var m = new Complex(3);   // Создали объект для 3+0i
    var p = n.add(m);         // Получили объект для 4+3i
    var s = p.toString();     // Получили строку "4+3i" 
    var m = p.valueOf();      // Получили число 5 (модуль комплексного числа)
    

    Пояснения

    В функции add есть «подозрительная» инструкция:

    
    var z = new Complex(this.x,this.y);
    

    Как это так? Внутри конструктора Complex есть ссылка на сам конструктор Complex! Похоже на бесконечную рекурсию. Будет ли это работать?

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

    Посмотрим по шагам.

    Шаг 1
    
    var n = new Complex(1,3); // Создали объект для 1+3i
    
    1. Оператор new создает пустой объект (где-то в памяти) и передает ссылку на этот объект через ключевое слово this конструктору Complex.
    2. Конструктор Complex делает следующее:
      • По инструкции this.x = x; создает в объекте свойство x и копирует в него 1.
      • По инструкции
        
        this.add = function (t)  
        {
          var z = new Complex(this.x,this.y);
          z.x += t.x; 
          z.y += t.y;
          return z;
        }; 
        
        создает в объекте свойство add и копирует в него ссылку на текст кода функции, которую он размещает где-то в памяти.
      • Инструкция
        
        this.toString = function ()
        {
          return this.x + "+" + this.y + "i"; 
        };
        
        не приводит к созданию нового свойства. Свойство toString в объекте уже есть, оно наследуется от Object. Но старое значение toString стирается и записывается новое — ссылка на текст кода функции, который размещается где-то в памяти.
      • Инструкция
        
        this.valueOf = function ()
        {
          return Math.sqrt(this.x*this.x + this.y*this.y); 
        };
        
        не приводит к созданию нового свойства. Свойство valueOf в объекте уже есть, оно наследуется от Object. Но старое значение valueOf стирается и записывается новое — ссылка на текст кода функции, который размещается где-то в памяти.
    3. Ссылка на созданный (где-то в памяти) объект записывается в переменную n.

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

    Шаг 2
    
    var m = new Complex(3);   // Создали объект для 3+0i
    

    Аналогично создается новый объект Complex и ссылка на него помещается в переменную m.

    Шаг 3

    И вот оно, самое интересное:

    
    var p = n.add(m);         // Получили объект для 4+3i
    

    В объекте n запускается код функции add:

    
    this.add = function (t)  
    {
      var z = new Complex(this.x,this.y);
      z.x += t.x; 
      z.y += t.y;
      return z;
    }; 
    
    1. Работает инструкция
      
      var z = new Complex(this.x,this.y);
      
      Она создает (где-то в памяти) новый объект Complex (как подробно было описано ранее) и записывает ссылку на него в локальную переменную z.
    2. Выполняются действия:
      
      z.x += t.x; 
      z.y += t.y;
      
    3. Возвращается ссылку на объект и уничтожается локальная переменная z (сам объект, конечно, остается в памяти).

    Переменная p получает ссылку на объект Complex, полученный из метода n.add.