Функция как объект js
Перейти к содержимому

Функция как объект js

  • автор:

Методы объектов JavaScript

В определении функции ключевое слово this ссылается на «владельца» функции.

Так, в примере выше this это объект person, который «владеет» функцией fullName.

Другими словами, this.firstName означает свойство firstName данного объекта.

Подробнее о ключевом слове this см. главу Ключевое слово this

Методы JavaScript

Методы JavaScript это действия, которые можно выполнить с объектами.

Метод JavaScript это свойство, содержащее определение функции.

Свойство Значение
firstName John
lastName Doe
age 50
eyeColor blue
fullName function()

Методы это функции, хранящиеся как свойства объекта.

Обращение к методам объекта

Чтобы вызвать метод объекта, используется следующий синтаксис:

Обычно обращение fullName() указывает на метод объекта person, а fullName на его свойство.

Свойство fullName будет выполняться (как функция), если его вызвать с круглыми скобками ().

В следующем примере мы обращаемся к методу fullName() объекта person:

 name = person.fullName(); 

Если вызвать свойство fullName, без круглых скобок (), то будет возвращено определение функции:

 name = person.fullName; 

Использование встроенных методов

В следующем примере используется метод toUpperCase() объекта String, чтобы преобразовать текст в верхний регистр:

 var message = "Hello world!"; var x = message.toUpperCase(); 

После выполнения кода этого примера, значением переменной x будет строка «HELLO WORLD!».

Добавление методов в объект

Добавить новый метод в объект очень просто:

 person.name = function () < return this.firstName + " " + this.lastName; >; 

Определение функции в JavaScript

Функции в JavaScript — это блоки кода, которые могут быть вызваны для выполнения определенных задач. Они представляют собой участки кода, которые могут принимать параметры (входные данные для описания функции), выполнять определенные операции и возвращать результат (выходные данные). Любая функция в JavaScript является объектом, а следовательно может быть передана в виде аргумента (входные данные для выполнения функции) другой функции, быть возвращена в качестве результата работы другой функции и присвоена в качестве значения переменной или свойства объекта. Далее мы рассмотри способы объявления функций и разницу между ними.

Function Declarations: базовое объявление функции

Функцию можно объявить с помощью ключевого слова function через пробел после которого идет название функции, скобки с перечисленными внутри параметрами и фигурные скобки с описанием кода, который будет срабатывать при вызове функции. При таком объявлении функция вернет значение только если оно указано после инструкции return а вызвать функцию можно из любой части кода (даже до объявления самой функции) указав ее имя и после него в скобках аргументы.

//вызываем функцию до объявления console.log(greeting("Stan"));//The name is Stan //в консоли так же выведется Hello Stan! function greeting(name) < //в фигурных скобках описывается код, //который будет исполнен при вызове функции console.log("Hello " + name + "!"); return "The name is " + name;>

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

function checkDay(day) < if (day === "Saturday" || day === "Sunday") < return "Weekend";//возвращаемое значение при >//выполнении условия return "Weekday";//возвращаемое значение при > //невыполнении условия

Function Expressions: функциональные выражения

Как мы уже знаем, функция это объект, а значит может быть задана в качестве значения переменной (” const a = function b()<> ”). При таком объявлении мы получаем возможность присвоить функцию в качестве свойства объекта и создавать безымянные функции, которые будут вызываться через имя переменной или свойства объекта, которым они присвоены как значения.

myObj.sayHello = function () ;//передадим эту функцию как значение свойства myObj.sayHello(); //Hello!

Важно помнить что функции, объявленые через Function Expressions, в отличии от Function Declarations можно вызвать только после их описания в коде. При этом вызвать их можно только через имя переменной или свойства, которой они присвоены. Имя самой функции доступно только внутри нее. Это полезно, например, для создания рекурсивных функций, которые вызывают сами себя до выполнения определенного условия.

a(); //Cannot access 'a' before initialization //нельзя получить доступ к переменной до ее создания const a = function sayHello() < console.log("Hello!"); sayHello();//функция доступна по имени внутри себя самой >; sayHello(); //sayHello is not defined //наша функция хранится только в переменной //и не может быть вызвана по своему имени

Arrow Functions: стрелочные функции

У обоих предыдущих способов объявления функции есть недостатки. Во-первых значение объекта контекста this для таких функций определяется в зависимости от многих факторов (новый объект в случае конструктора, undefined в strict mode, контекстный объект, если функция вызвана как метод объекта, и т.д.) и это приходится отслеживать для каждой функции. Во-вторых длинный синтаксис с ключевым словом function и обязательным заключением тела функции в фигурные скобки. Оба недостатка исправлены в стрелочных функциях для которых this явно определяется окружающим функцию контекстом, а синтаксис в самом минимальном виде представляет собой набор параметров в скобках и “стрелка” из знаков равно и закрывающей угловой скобки (” ()⇒ ”).

const user = < name: "John", regularHello: function () < console.log("Hello, " + this.name); >, arrowHello: () => console.log("Hello, " + this.name), outsideRegularFunc: function () < const insideArrowFunc = () =>< console.log("Hello, " + this.name); >; insideArrowFunc(); >, >; user.regularHello(); //Hello, John //обычная функция получает в качестве this сам объект user //и поэтому this.name для нее равен 'John' user.arrowHello(); //Hello, undefined //для этой стрелочной функции this не указан явно //поэтому this.name неопределен (undefined) user.outsideRegularFunc(); //Hello, John //эта стрелочная функция берет this из своего контекста, //которым является функция outsideRegularFunc

Кроме того все стрелочные функции безымянны и в них могут быть объявлены выходные данные без явного указания инструкции `return`.

//попытка дать название стрелочной функции const myFunc = namedFunc () => <> //приведет к ошибке Malformed arrow function parameter list const square = (num) => < return num * num >; const square2 = (num) => num * num; //эти две записи идентичны

IIFE: Самовызывающиеся функции

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

Следует помнить что самовызывающиеся функции не выйдет использовать повторно в другом участке кода.

//самовызывающаяся Function Declarations (function sayHello(name) < console.log("Hello, " + name); >)("John"); //Hello, John //самовызывающаяся Function Expressions const myFunc = (function sayHello(name) < console.log("Hello, " + name); >)("Stan"); //Hello, Stan //самовызывающаяся стрелочная функция const sayHello = ((name) => < console.log("Hello, " + name); >)("Tom"); //Hello, Tom sayHello(); //Ошибка: sayHello is not a function myFunc(); //Ошибка: myFunc is not a function //при этом использовать эти функции впоследсивии //не выйдет потому что они не определены console.log(typeof sayHello); //undefined console.log(typeof myFunc); //undefined

Параметры и аргументы функций

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

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

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

function logger() < //используем в цикле arguments for (let i = 0; i < arguments.length; i++) < console.log(arguments[i]);>> logger(1, 2, 3, 4, 5); //выведет все числа по-очереди const power = (num, pow = 1) => num ** pow; power(2, 2); //4 //второй аргумент передан и 2 возведена во вторую степень power(2); //2 //второй аргумент не передаи и 2 возведена в первую степень

Заключение

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

Функция как объект js

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

function createUser(pName, pAge) < return < name: pName, age: pAge, print: function() < console.log(`Name: $Age: $`); > >; >; const tom = createUser("Tom", 26); tom.print(); const alice = createUser("Alice", 24); alice.print();

Здесь функция createUser() получает значения pName и pAge и по ним создает новый объект, который является возвращаемым результатом. Результат работы программы:

Name: Tom Age: 26 Name: Alice Age: 24

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

function createUser(pName, pAge) < if(pAge < 1 || pAge >110) < // если возраст меньше 1 или больще 110 console.log("Age is invalid") pAge=1; >return < name: pName, age: pAge, print: function() < console.log(`Name: $Age: $`); > >; >; const tom = createUser("Tom", 26); tom.print(); const alice = createUser("Alice", 12345); alice.print();

Здесь проверяется параметр pAge, который представляет возвраст пользователя. Понятно, что теоретически это может быть любое число, которое может выходить за разумные пределы, например, быть отрицательным. И в данном случае мы проверяем pAge на соответствие этому пределу. Если значение pAge не соответствует пределу, то присваиваем ему значение по умолчанию — в данном случае 1, и выводим диагностическое сообщение. Консольный вывод программы:

Name: Tom Age: 26 Age is invalid Name: Alice Age: 1

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

function getMinMax(numbers)< // если массив пуст, минимальное и максимальное значения неопределены if(numbers.length === 0) return ; let minNumber = numbers[0]; let maxNumber = numbers[0]; for(let i=1; i < numbers.length; i++)< if(minNumber >numbers[i]) minNumber = numbers[i]; if(maxNumber < numbers[i]) maxNumber = numbers[i]; >return ; > const nums = [1, 2, 3, 4, 5]; const result = getMinMax(nums); console.log("Min:", result.min); // Min: 1 console.log("Max:", result.max); // Max: 5

Здесь в функции getMinMax получаем массив. Если массив не содержит чисел, то возвращаем объект, где поля min и max имеют значения undefined. Иначе проходим по всему массиву и вычисляем максимальное и минимальное значения и возвращаем их в виде одного объекта.

Объект как параметр

Как и все другие значения, объект может передаваться в качестве параметра в функцию:

function printPerson(person) < console.log("Name:", person.name); console.log("Age:", person.age); >const tom = ; const alice = ; printPerson(tom); printPerson(alice);

Здесь в функцию printPerson передается объект, который, как предполагается, будет иметь два свойства: name и age.

При этом стоит учитывать, что объект — ссылочный тип, а переменная/константа/параметр, которые представляют объект, фактически хранят ссылку на объект в памяти, а не сам объект. Соответственно при передаче в функцию объекта параметру передается копия ссылки на этот объект. И через эту ссылку функция может изменять различные свойства объекта:

function setAge(person, pAge) < person.age = pAge; >const sam = ; console.log("Before setAge:", sam.age); setAge(sam, 30); console.log("After setAge:", sam.age);

Здесь сначала определяем константу sam, которая представляет объект со свойствами name и age:

const sam = ;

Фактически константа sam хранит ссылку на область памяти, где расположен объект.

Затем вызывается функция setAge, которая получает объект person и изменяет у него свойство age.

setAge(sam, 30);

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

Before setAge: 29 After setAge: 30

Но если параметру внутри функции присваивается другой объект, то фактически ссылка меняет свое значение и начинает указывать на дргую область памяти:

function setDefault(person)< person = ; > let sam = ; console.log("Before setDefault:", sam.name); setDefault(sam); console.log("After setDefault:", sam.name);

При передаче переменной sam в функцию setDefault параметр этой функции и переменная sam будут представлять две разные ссылки, но указывать на один и тот же обеъект в памяти:

setDefault(sam);

Но потом внутри функции мы изменяем значение параметра:

person = ;

В итоге ссылке, которая хранится в параметре person, присвается новый объект. Но поскольку переменная sam и параметр person представляют две разные ссылки, то это присваивание никак не затронет объект sam.

Объект функции, NFE

Как мы уже знаем, в JavaScript функция – это значение.

Каждое значение в JavaScript имеет свой тип. А функция – это какой тип?

В JavaScript функции – это объекты.

Можно представить функцию как «объект, который может делать какое-то действие». Функции можно не только вызывать, но и использовать их как обычные объекты: добавлять/удалять свойства, передавать их по ссылке и т.д.

Свойство «name»

Объект функции содержит несколько полезных свойств.

Например, имя функции нам доступно как свойство «name»:

function sayHi() < alert("Hi"); >alert(sayHi.name); // sayHi

Что довольно забавно, логика назначения name весьма умная. Она присваивает корректное имя даже в случае, когда функция создаётся без имени и тут же присваивается, вот так:

let sayHi = function() < alert("Hi"); >; alert(sayHi.name); // sayHi (есть имя!)

Это работает даже в случае присваивания значения по умолчанию:

function f(sayHi = function() <>) < alert(sayHi.name); // sayHi (работает!) >f();

В спецификации это называется «контекстное имя»: если функция не имеет name, то JavaScript пытается определить его из контекста.

Также имена имеют и методы объекта:

let user = < sayHi() < // . >, sayBye: function() < // . >> alert(user.sayHi.name); // sayHi alert(user.sayBye.name); // sayBye

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

// функция объявлена внутри массива let arr = [function() <>]; alert( arr[0].name ); // // здесь отсутствует возможность определить имя, поэтому его нет

Впрочем, на практике такое бывает редко, обычно функции имеют name .

Свойство «length»

Ещё одно встроенное свойство «length» содержит количество параметров функции в её объявлении. Например:

function f1(a) <> function f2(a, b) <> function many(a, b, . more) <> alert(f1.length); // 1 alert(f2.length); // 2 alert(many.length); // 2

Как мы видим, троеточие, обозначающее «остаточные параметры», здесь как бы «не считается»

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

Например, в коде ниже функция ask принимает в качестве параметров вопрос question и произвольное количество функций-обработчиков ответа handler .

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

  • Функцию без аргументов, которая будет вызываться только в случае положительного ответа.
  • Функцию с аргументами, которая будет вызываться в обоих случаях и возвращать ответ.

Чтобы вызвать обработчик handler правильно, будем проверять свойство handler.length .

Идея состоит в том, чтобы иметь простой синтаксис обработчика без аргументов для положительных ответов (наиболее распространённый случай), но также и возможность передавать универсальные обработчики:

function ask(question, . handlers) < let isYes = confirm(question); for(let handler of handlers) < if (handler.length == 0) < if (isYes) handler(); >else < handler(isYes); >> > // для положительных ответов вызываются оба типа обработчиков // для отрицательных - только второго типа ask("Вопрос?", () => alert('Вы ответили да'), result => alert(result));

Это частный случай так называемого Ad-hoc-полиморфизма – обработка аргументов в зависимости от их типа или, как в нашем случае – от значения length . Эта идея имеет применение в библиотеках JavaScript.

Пользовательские свойства

Мы также можем добавить свои собственные свойства.

Давайте добавим свойство counter для отслеживания общего количества вызовов:

function sayHi() < alert("Hi"); // давайте посчитаем, сколько вызовов мы сделали sayHi.counter++; >sayHi.counter = 0; // начальное значение sayHi(); // Hi sayHi(); // Hi alert( `Вызвана $ раза` ); // Вызвана 2 раза

Свойство не есть переменная

Свойство функции, назначенное как sayHi.counter = 0 , не объявляет локальную переменную counter внутри неё. Другими словами, свойство counter и переменная let counter – это две независимые вещи.

Мы можем использовать функцию как объект, хранить в ней свойства, но они никак не влияют на её выполнение. Переменные – это не свойства функции и наоборот. Это два параллельных мира.

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

function makeCounter() < // вместо // let count = 0 function counter() < return counter.count++; >; counter.count = 0; return counter; > let counter = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1

Свойство count теперь хранится прямо в функции, а не в её внешнем лексическом окружении.

Это хуже или лучше, чем использовать замыкание?

Основное отличие в том, что если значение count живёт во внешней переменной, то оно не доступно для внешнего кода. Изменить его могут только вложенные функции. А если оно присвоено как свойство функции, то мы можем его получить:

function makeCounter() < function counter() < return counter.count++; >; counter.count = 0; return counter; > let counter = makeCounter(); counter.count = 10; alert( counter() ); // 10

Поэтому выбор реализации зависит от наших целей.

Named Function Expression

Named Function Expression или NFE – это термин для Function Expression, у которого есть имя.

Например, давайте объявим Function Expression:

let sayHi = function(who) < alert(`Hello, $`); >;

И присвоим ему имя:

let sayHi = function func(who) < alert(`Hello, $`); >;

Чего мы здесь достигли? Какова цель этого дополнительного имени func ?

Для начала заметим, что функция всё ещё задана как Function Expression. Добавление «func» после function не превращает объявление в Function Declaration, потому что оно все ещё является частью выражения присваивания.

Добавление такого имени ничего не ломает.

Функция все ещё доступна как sayHi() :

let sayHi = function func(who) < alert(`Hello, $`); >; sayHi("John"); // Hello, John

Есть две важные особенности имени func , ради которого оно даётся:

  1. Оно позволяет функции ссылаться на себя же.
  2. Оно не доступно за пределами функции.

Например, ниже функция sayHi вызывает себя с «Guest» , если не передан параметр who :

let sayHi = function func(who) < if (who) < alert(`Hello, $`); > else < func("Guest"); // использует func, чтобы снова вызвать себя же >>; sayHi(); // Hello, Guest // А вот так - не cработает: func(); // Ошибка, func не определена (недоступна вне функции)

Почему мы используем func ? Почему просто не использовать sayHi для вложенного вызова?

Вообще, обычно мы можем так поступить:

let sayHi = function(who) < if (who) < alert(`Hello, $`); > else < sayHi("Guest"); >>;

Однако, у этого кода есть проблема, которая заключается в том, что значение sayHi может быть изменено. Функция может быть присвоена другой переменной, и тогда код начнёт выдавать ошибки:

let sayHi = function(who) < if (who) < alert(`Hello, $`); > else < sayHi("Guest"); // Ошибка: sayHi не является функцией >>; let welcome = sayHi; sayHi = null; welcome(); // Ошибка, вложенный вызов sayHi больше не работает!

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

Необязательное имя, которое можно вставить в Function Expression, как раз и призвано решать такого рода проблемы.

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

let sayHi = function func(who) < if (who) < alert(`Hello, $`); > else < func("Guest"); // Теперь всё в порядке >>; let welcome = sayHi; sayHi = null; welcome(); // Hello, Guest (вложенный вызов работает)

Теперь всё работает, потому что имя «func» локальное и находится внутри функции. Теперь оно взято не снаружи (и недоступно оттуда). Спецификация гарантирует, что оно всегда будет ссылаться на текущую функцию.

Внешний код все ещё содержит переменные sayHi и welcome , но теперь func – это «внутреннее имя функции», таким образом она может вызвать себя изнутри.

Это не работает с Function Declaration

Трюк с «внутренним» именем, описанный выше, работает только для Function Expression и не работает для Function Declaration. Для Function Declaration синтаксис не предусматривает возможность объявить дополнительное «внутреннее» имя.

Зачастую, когда нам нужно надёжное «внутреннее» имя, стоит переписать Function Declaration на Named Function Expression.

Итого

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

  • name – имя функции. Обычно берётся из объявления функции, но если там нет – JavaScript пытается понять его из контекста.
  • length – количество аргументов в объявлении функции. Троеточие («остаточные параметры») не считается.

Если функция объявлена как Function Expression (вне основного потока кода) и имеет имя, тогда это называется Named Function Expression (Именованным Функциональным Выражением). Это имя может быть использовано для ссылки на себя же, для рекурсивных вызовов и т.п.

Также функции могут содержать дополнительные свойства. Многие известные JavaScript-библиотеки искусно используют эту возможность.

Они создают «основную» функцию и добавляют множество «вспомогательных» функций внутрь первой. Например, библиотека jQuery создаёт функцию с именем $ . Библиотека lodash создаёт функцию _ , а потом добавляет в неё _.clone , _.keyBy и другие свойства (чтобы узнать о ней побольше см. документацию). Они делают это, чтобы уменьшить засорение глобального пространства имён посредством того, что одна библиотека предоставляет только одну глобальную переменную, уменьшая вероятность конфликта имён.

Таким образом, функция может не только делать что-то сама по себе, но также и предоставлять полезную функциональность через свои свойства.

Задачи

Установка и уменьшение значения счётчика

важность: 5

Измените код makeCounter() так, чтобы счётчик мог уменьшать и устанавливать значение:

  • counter() должен возвращать следующее значение (как и раньше).
  • counter.set(value) должен устанавливать счётчику значение value .
  • counter.decrease() должен уменьшать значение счётчика на 1.

Посмотрите код из песочницы с полным примером использования.

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

В решении использована локальная переменная count , а методы сложения записаны прямо в counter . Они разделяют одно и то же лексическое окружение и также имеют доступ к текущей переменной count .

function makeCounter() < let count = 0; function counter() < return count++; >counter.set = value => count = value; counter.decrease = () => count--; return counter; >

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *