§16. Дополнительные сведения по функциям.

Учимся программировать вместе с JavaScript
Глава II. Дополнительные сведения по базовому JavaScript.

§16. Дополнительные сведения по функциям.

Массив аргументов arguments в теле функции. Функции как объекты. Свойство функции length. Методы call() и apply(). Создание своих собственных свойств и методов функций. Статические методы. Свойство prototype. Проверка типа данных переменной. Оператор идентичности. Инструкция with. Рекурсия. Модули, пространство имён.

Массив аргументов arguments в теле функции. 

В JavaScript любой функции (методу) можно передавать любое количество аргументов, даже если они не объявлены. Например, до боли знакомая нам функция document.write всегда выводит все аргументы, сколько бы мы их ни отправили. 

Для доступа ко всем аргументам, даже к тем которые не объявлены, существует массив arguments, который автоматически создаётся в любой функции, и который содержит в себе все принятые функцией аргументы. Как и у любого массива, у него есть свойство length. Правда длина массива arguments не может быть изменена, соответственно и свойство length доступно только для чтения. 

Далее пример, в котором функция выводит в окно браузера все принятые аргументы: 

 

<script> 

function write(s)  

{ 

  for (var i = 0; i<arguments.length; i++) 

  document.write(arguments[i],'<br>'); 

  document.write(s); 

}; 

write('Привет','всем','!'); 

</script> 

 

 

Обратите внимание, аргумент s одновременно является и первым элементом массива arguments, соответственно доступ к нему может быть осуществлён двумя способами. 

 

Функции как объекты. 

Как это ни выглядело бы странным, но функции в JavaScript тоже являются своего рода объектами. Соответственно есть несколько методов и свойств, которыми можно пользоваться. 

 

Свойство функции length. 

Свойство функции length содержит в себе ожидаемое количество аргументов. Ожидаемое количество аргументов - это количество аргументов указанное при определении функции. Пример: 

 

<script> 

function F(x,y 

{ 

  document.write('Функция ожидает ',F.length,' аргумента.<br>'); 

}; 

F(); 

</script> 

 

 

Методы call() и apply(). 

Методы call() и apply() позволяют вызвать функцию как метод какого либо объекта. В таком случае слово this в теле функции будет ссылкой на этот объект. В качестве первого аргумента этим методам необходимо передать нужный объект. Для метода apply() вторым аргументом является массив значений, который воспринимается методом как список аргументов. Для метода call() все последующие аргументы являются просто аргументами. Пример: 

 

<script> 

var ob = {x:3,y:4}; 

function F(i,j 

{ 

  document.write('this.x = ',this.x,'; '); 

  document.write('this.y = ',this.y,'; '); 

  document.write('i = ',i,'; '); 

  document.write('j = ',j,'.<br>'); 

}; 

document.write('Метод call:<br>'); 

F.call(ob,7,8); 

 

document.write('<br>Метод apply:<br>'); 

F.apply(ob,[7,8]); 

</script> 

 

 

Создание своих собственных свойств и методов функций. 

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

 

<script> 

F.str = 'Привет всем!'; 

function F()  

{ 

  document.write(F.str,'<br>'); 

}; 

F(); 

</script> 

 

 

Статические методы. 

Сам термин статический метод появился в других объектно-ориентированных языках и обозначает метод класса, доступный вне объектов. Т.к. в JavaScript классом можно назвать конструктор, то, создав метод функции конструктора, мы получим статический метод. Примером может служить статический метод String.fromCharCode() изученный в 13 параграфе. 

 

Свойство prototype. 

Особое внимание заслуживает свойство функций конструкторов prototype. Оно необходимо только в том случае, если функция используется совместно с оператором new. Т.е. при создании объектов. Данное свойство нужно для хранения общих свойств и методов всех объектов, созданных одним конструктором. Пример: 

 

<script> 

myObject.prototype.y = 44; 

function myObject(x) {this.x = x}; 

var ob_1 = new myObject(11); 

var ob_2 = new myObject(22); 

 

document.write('ob_1.y = ',ob_1.y,' ob_2.y = ',ob_2.y,'<br>'); 

ob_1.y = 55; 

document.write('ob_1.y = ',ob_1.y,' ob_2.y = ',ob_2.y,'<br>'); 

</script> 

 

 

Данный пример наглядно демонстрирует, что свойство y, созданное через свойство prototype функции конструктора, является общим для обоих объектов. При этом, изменив его в одном объекте, это изменение отразилось и во втором. 

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

Ещё один смысл использования общих свойств и методов заключается в том, что бы сэкономить пространство памяти. Когда мы создаём объект конструктором, то под все свойства и методы каждого объекта отводится место в памяти компьютера. Однако некоторые свойства могут быть константами, т.е. одинаковыми для всех объектов и не меняться со временем. В таком случае логично сделать их общими для всех. Так же большинство методов можно сделать общими, используя слово this для обращения к тому объекту, который вызвал метод, что в итоге сэкономит память. 

 

 

Проверка типа данных переменной. 

Для того, что бы узнать тип данных переменной существует оператор typeof. Далее в примере продемонстрировано, какие значения возвращает данный оператор при проверке различных типов переменных: 

<script> 

var x = 4; 

var s = ''; 

var b = true; 

var m = []; 

var date = new Date(); 

var F = function() {}; 

document.write('typeof x - ',typeof x,'<br>'); 

document.write('typeof s - ',typeof s,'<br>'); 

document.write('typeof b - ',typeof b,'<br>'); 

document.write('typeof m - ',typeof m,'<br>'); 

document.write('typeof date - ',typeof date,'<br>'); 

document.write('typeof F - ',typeof F,'<br>'); 

</script> 

 

 

Оператор идентичности. 

При сравнении оператором равенства (==) или неравенства (!=) переменных различного типа в JavaScript происходит автоматическое преобразование типов к какому либо одному. Например, если сравнить строку '7' и число 7 оператором равенства, то получим true. 

Иногда возникает необходимость сравнивать без автоматического преобразования типов. Для этого существуют операторы идентичности (===)  и не идентичности (!==). Пример: 

 

<script> 

document.write("'7' == 7 - ",'7' == 7,'<br>'); 

document.write("'7' === 7 - ",'7' === 7,'<br>'); 

</script> 

 

 

Инструкция with. 

Ещё одна тема из базового JavaScript, которую вы должны знать, это инструкция with. Использование данной инструкции позволяет сократить код программы. Она создаёт блок кода, в котором сокращается синтаксис обращения к каким либо свойствам объекта. Например, когда мы рисуем в тэге canvas нам для вызова любого метода для рисования необходимо каждый раз написать имя объекта контекста. При использовании инструкции with эта необходимость исчезает. 

Для примера возьмём метод square.draw() из решения задачи предыдущего параграфа: 

 

square.draw = function(l) 

{ 

  this.im.moveTo(this.x,this.y); 

  this.im.lineTo(this.x+l,this.y); 

  this.im.lineTo(this.x+l,this.y+l); 

  this.im.lineTo(this.x,this.y+l); 

  this.im.lineTo(this.x,this.y); 

  this.im.stroke(); 

 

 

Теперь перепишем метод с использованием инструкции with: 

 

square.draw = function(l) 

{ 

  with (this.im) 

  { 

    moveTo(this.x,this.y); 

    lineTo(this.x+l,this.y); 

    lineTo(this.x+l,this.y+l); 

    lineTo(this.x,this.y+l); 

    lineTo(this.x,this.y); 

    stroke(); 

  } 

} 

 

Рекурсия. 

До сих пор мы вызывали функцию где угодно только не в теле самой функции. Тем не менее, вызывать функцию из её же тела вполне допустимо. Это называется рекурсией. Для примера создадим функцию вычисляющую факториал числа: 

 

function fact(x) 

{ 

  if (x > 1)  

    return x*fact(x-1) 

  else  

    return 1; 

} 

 

fact(5);//Вернёт 120 

 

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

 

<script> 

function f(x) 

{ 

  document.write(x,'<br>'); 

  if (x<35942) f(++x); 

} 

 

f(1); 

</script> 

 

 

Как видно из результата работы программы, 35942-раз функция уже не смогла сама себя вызвать. 

 

Модули, пространство имён. 

Когда вы начнёте работать профессионально, ваши программы будут настолько большими, что в них будет трудно ориентироваться. Поэтому возникнет необходимость тематические блоки кода выносить в отдельные файлы. Так же вы будете пользоваться чужим готовым кодом, который тоже будет находиться в отдельном файле. 

В других языках программирования для выноса кода в отдельный файл существуют отдельные возможности языка, такие как, например, модули в языке Pascal. Связано это с тем, что, как правило, чужие коды в других языках распространяются в уже откомпилированном виде. 

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

В JavaScript, как правило, эта проблема решается созданием конструктора (класса) на один отдельный файл. При этом все функции и переменные станут методами и свойствами объекта. Так же можно сделать свойства и методы статическими. У объекта и у конструктора должно быть достаточно уникальное имя. Тогда вероятность совпадения идентификаторов сводится к нулю. 

Для примера, если вам понадобиться создать отдельный файл (модуль) с функциями, рисующими различные геометрические фигуры, то в этом файле вы создадите конструктор (класс) DVFigure (DV - ваши инициалы). Почему именно DVFigure, а не просто Figure, потому, что существует большая вероятность того, что кто-то уже написал конструктор Figure, и возможно вам придётся им когда-нибудь воспользоваться. 

Есть ещё одно понятие - пространство имён. В других языках, когда подключается модуль, то все имена, объявленные в нём, становятся доступными в теле программы. Этот модуль как раз и есть пространство для объявленных в нём имён. В нашем случае пространством имён будет не файл и не модуль, а конструктор, в котором все имена будут объявлены. 

 

Задачи. 

1. Написать функцию, выводящую строение объекта. Использовать для этого рекурсию. Для примера можете взять объект clas, созданный конструктором Clas, из программы демонстрирующей простейшую базу данных из 8 параграфа. У вас должно получиться следующее: 

 

 

 

2. Создать отдельный файл (модуль) с конструктором (классом) создающим объекты, рисующие геометрические фигуры. Можете воспользоваться готовыми наработками, созданными при решении задач предыдущего параграфа. Создать через свойство конструктора prototype общие свойства всех объектов длину и ширину тэга canvas, которые не будут давать рисоваться фигурам вне этого тэга. 

 

Примеры решений. 

1.  

.................................. 

//Функция выводит свойства объекта 

function write_ob(ob,tab) 

{ 

  for (prop in ob 

  { 

    document.write(tab,prop,' - ',typeof ob[prop],'<br>'); 

if (typeof ob[prop] == 'object') write_ob(ob[prop],tab+'&nbsp &nbsp'); 

  } 

} 

 

write_ob(clas,'');