Перейти к содержимому

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

  • автор:

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

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

Например, мы можем создать функцию с помощью конструктора Function:

const square = new Function("n", "return n * n;"); console.log(square(5)); // 25

В конструктор Function может передаваться ряд параметров. Последний параметр представляет собой само тело функции в виде строки. Фактически строка содержит код javascript. Предыдущие аргументы содержат названия параметров. В данном случае определяется функция возведения числа в квадрат, которая имеет один параметр n.

Среди свойств объекта Function можно выделить следующие:

  • arguments : массив аргументов, передаваемых в функцию
  • length : определяет количество аргументов, которые ожидает функция
  • caller : определяет функцию, вызвавшую текущую выполняющуюся функцию
  • name : имя функции
  • prototype : прототип функции

С помощью прототипа мы можем определить дополнительные свойства:

function sayHello() < console.log("Hello"); >// изменяем прототип для всех функций Function.prototype.program ="Hello World"; console.log(sayHello.program); // Hello World

Среди методов надо отметить методы call() и apply() .

Метод call() вызывает функцию с указанным значением this и аргументами:

function sum(x, y) < return x + y; >const result = sum.call(this, 3, 8); console.log(result); // 11

this указывает на объект, для которого вызывается функция — в данном случае это глобальный объект window. После this передаются значения для параметров.

При передаче объекта через первый параметр, мы можем ссылаться на него через ключевое слово this :

function User (name, age) < this.name = name; this.age = age; >const tom = new User("Tom", 39); function print() < console.log("Name:", this.name); >print.call(tom); // Name: Tom

В данном случае передается только одно значение, поскольку функция print не принимает параметров. То есть функция будет вызываться для объекта tom.

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

function sum(x, y) < return x + y; >const result = sum.call(null, 3, 8); console.log(result); // 11

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

function sum(x, y) < return x + y; >const result = sum.apply(null, [3, 8]); console.log(result); // 11

Методы объекта, «this»

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

// Объект пользователя let user = < name: "John", age: 30 >;

И так же, как и в реальном мире, пользователь может совершать действия: выбирать что-то из корзины покупок, авторизовываться, выходить из системы, оплачивать и т.п.

Такие действия в JavaScript представлены функциями в свойствах.

Примеры методов

Для начала давайте научим нашего пользователя user здороваться:

let user = < name: "John", age: 30 >; user.sayHi = function() < alert("Привет!"); >; user.sayHi(); // Привет!

Здесь мы просто использовали Function Expression (функциональное выражение), чтобы создать функцию приветствия, и присвоили её свойству user.sayHi нашего объекта.

Затем мы можем вызвать ee как user.sayHi() . Теперь пользователь может говорить!

Функцию, которая является свойством объекта, называют методом этого объекта.

Итак, мы получили метод sayHi объекта user .

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

let user = < // . >; // сначала, объявляем function sayHi() < alert("Привет!"); >// затем добавляем в качестве метода user.sayHi = sayHi; user.sayHi(); // Привет!

Объектно-ориентированное программирование

Когда мы пишем наш код, используя объекты для представления сущностей реального мира, – это называется объектно-ориентированным программированием или сокращённо: «ООП».

ООП является большой предметной областью и интересной наукой самой по себе. Как выбрать правильные сущности? Как организовать взаимодействие между ними? Это – создание архитектуры, и на эту тему есть отличные книги, такие как «Приёмы объектно-ориентированного проектирования. Паттерны проектирования» авторов Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес или «Объектно-ориентированный анализ и проектирование с примерами приложений» Гради Буча, а также ещё множество других книг.

Сокращённая запись метода

Существует более короткий синтаксис для методов в литерале объекта:

// эти объекты делают одно и то же user = < sayHi: function() < alert("Привет"); >>; // сокращённая запись выглядит лучше, не так ли? user = < sayHi() < // то же самое, что и "sayHi: function()" alert("Привет"); > >;

Как было показано, мы можем пропустить ключевое слово «function» и просто написать sayHi() .

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

Ключевое слово «this» в методах

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

Например, коду внутри user.sayHi() может потребоваться имя пользователя, которое хранится в объекте user .

Для доступа к информации внутри объекта метод может использовать ключевое слово this .

Значение this – это объект «перед точкой», который используется для вызова метода.

let user = < name: "John", age: 30, sayHi() < // "this" - это "текущий объект". alert(this.name); >>; user.sayHi(); // John

Здесь во время выполнения кода user.sayHi() значением this будет являться user (ссылка на объект user ).

Технически также возможно получить доступ к объекту без ключевого слова this , обратившись к нему через внешнюю переменную (в которой хранится ссылка на этот объект):

let user = < name: "John", age: 30, sayHi() < alert(user.name); // "user" вместо "this" >>;

…Но такой код ненадёжен. Если мы решим скопировать ссылку на объект user в другую переменную, например, admin = user , и перезапишем переменную user чем-то другим, тогда будет осуществлён доступ к неправильному объекту при вызове метода из admin .

Это показано ниже:

let user = < name: "John", age: 30, sayHi() < alert( user.name ); // приведёт к ошибке >>; let admin = user; user = null; // перезапишем переменную для наглядности, теперь она не хранит ссылку на объект. admin.sayHi(); // TypeError: Cannot read property 'name' of null

Если бы мы использовали this.name вместо user.name внутри alert , тогда этот код бы сработал.

«this» не является фиксированным

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

В следующем примере нет синтаксической ошибки:

function sayHi()

Значение this вычисляется во время выполнения кода, в зависимости от контекста.

Например, здесь одна и та же функция назначена двум разным объектам и имеет различное значение «this» в вызовах:

let user = < name: "John" >; let admin = < name: "Admin" >; function sayHi() < alert( this.name ); >// используем одну и ту же функцию в двух объектах user.f = sayHi; admin.f = sayHi; // эти вызовы имеют разное значение this // "this" внутри функции - это объект "перед точкой" user.f(); // John (this == user) admin.f(); // Admin (this == admin) admin['f'](); // Admin (нет разницы между использованием точки или квадратных скобок для доступа к объекту)

Правило простое: если вызывается obj.f() , то во время вызова f , this – это obj . Так что, в приведённом выше примере это либо user , либо admin .

Вызов без объекта: this == undefined

Мы даже можем вызвать функцию вообще без объекта:

function sayHi() < alert(this); >sayHi(); // undefined

В строгом режиме ( «use strict» ) в таком коде значением this будет являться undefined . Если мы попытаемся получить доступ к this.name – это вызовет ошибку.

В нестрогом режиме значением this в таком случае будет глобальный объект ( window в браузерe, мы вернёмся к этому позже в главе Глобальный объект). Это – исторически сложившееся поведение this , которое исправляется использованием строгого режима ( «use strict» ).

Обычно подобный вызов является ошибкой программирования. Если внутри функции используется this , тогда она ожидает, что будет вызвана в контексте какого-либо объекта.

Последствия свободного this

Если вы до этого изучали другие языки программирования, то вы, вероятно, привыкли к идее «фиксированного this » – когда методы, определённые в объекте, всегда имеют this , ссылающееся на этот объект.

В JavaScript this является «свободным», его значение вычисляется в момент вызова метода и не зависит от того, где этот метод был объявлен, а скорее от того, какой объект вызывает метод (какой объект стоит «перед точкой»).

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

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

У стрелочных функций нет «this»

Стрелочные функции особенные: у них нет своего «собственного» this . Если мы ссылаемся на this внутри такой функции, то оно берётся из внешней «нормальной» функции.

Например, здесь arrow() использует значение this из внешнего метода user.sayHi() :

let user = < firstName: "Ilya", sayHi() < let arrow = () =>alert(this.firstName); arrow(); > >; user.sayHi(); // Ilya

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

Итого

  • Функции, которые находятся в свойствах объекта, называются «методами».
  • Методы позволяют объектам «действовать»: object.doSomething() .
  • Методы могут ссылаться на объект через this .

Значение this определяется во время исполнения кода.

  • При объявлении любой функции в ней можно использовать this , но этот this не имеет значения до тех пор, пока функция не будет вызвана.
  • Функция может быть скопирована между объектами (из одного объекта в другой).
  • Когда функция вызывается синтаксисом «метода» – object.method() , значением this во время вызова является object .

Также ещё раз заметим, что стрелочные функции являются особенными – у них нет this . Когда внутри стрелочной функции обращаются к this , то его значение берётся извне.

Задачи

Использование «this» в литерале объекта

важность: 5

Здесь функция makeUser возвращает объект.

Каким будет результат при обращении к свойству объекта ref ? Почему?

function makeUser() < return < name: "John", ref: this >; > let user = makeUser(); alert( user.ref.name ); // Каким будет результат?

Ответ: ошибка.

function makeUser() < return < name: "John", ref: this >; > let user = makeUser(); alert( user.ref.name ); // Error: Cannot read property 'name' of undefined

Это потому, что правила, которые определяют значение this , никак не смотрят на объявление объекта. Важен лишь момент вызова.

Здесь значение this внутри makeUser() равно undefined , потому что оно вызывается как функция, а не через «точечный» синтаксис как метод.

Значение this одно для всей функции, блоки кода и объектные литералы на него не влияют.

Таким образом, ref: this фактически принимает текущее this функции makeUser() .

Мы можем переписать функцию и вернуть то же самое this со значением undefined :

function makeUser() < return this; // на этот раз нет литерала объекта >alert( makeUser().name ); // Error: Cannot read property 'name' of undefined

Как вы можете видеть, результат alert( makeUser().name ) совпадает с результатом alert( user.ref.name ) из предыдущего примера.

Вот противоположный случай:

function makeUser() < return < name: "John", ref() < return this; >>; > let user = makeUser(); alert( user.ref().name ); // John

Теперь это работает, поскольку user.ref() – это метод. И значением this становится объект перед точкой . .

Объект функции, 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; >

Функция в JS это объект?

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

https://learn.javascript.ru
5d5fa596bd649387538704.jpeg
https://developer.mozilla.org:
5d5fa59f7b155861351198.jpeg

Тоесть если верить документации функция есть обьект.

Но я для интереса написал функцию и вывел ее:

function show() < alert(this.message); >console.log(show);

5d5fa666465d9444199864.jpeg

но никакого обьекта я не увидел там:

5d5fa740e8969682197828.jpeg

если б он был то должно было бы быть что то похожее на это но с другими свойствами и значениями:

Но такого нет.
Функция в JS это объект или нет?

  • Вопрос задан более трёх лет назад
  • 2549 просмотров

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

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