Функция как объект 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.
как передать объект в функцию js
Пример функции, в которую передается объект в качестве параметра:
function changeObject(obj) obj.name = 'Василий'; obj.age = 30; >
Эта функция принимает объект obj в качестве параметра. Затем она изменяет его свойства name и age .
Параметр obj передается по ссылке, а не по значению. Это означает, что функция получает не копию объекта, а ссылку на него. Если функция изменяет свойства объекта, эти изменения будут видны и вне функции.
Например, если объявить объект person и передать его в функцию changeObject , свойства name и age будут изменены:
const person = name: 'Алиса', age: 25 >; changeObject(person); console.log(person); //
Это может повлиять на другие части программы, которые работают с этим объектом. Если объект используется в нескольких местах, изменения, внесенные функцией, могут привести к неожиданным результатам.
В таком случае нужно быть осторожным при передаче объектов в качестве параметров функций и учитывать, и если необходимо, создавать копию объекта.
Функции
В общем случае, функция — это «подпрограмма», которую можно вызывать из внешнего (или внутреннего, в случае рекурсии) по отношению к функции кода. Как и сама программа, функция состоит из последовательности инструкций, называемой телом функции. Значения могут быть переданы в функцию, а функция вернёт значение.
В JavaScript функции являются объектами первого класса, то есть: они являются объектами и с ними можно взаимодействовать и передавать их точно так же как любой другой объект. Если быть точным, функции — это объекты Function .
Больше подробностей и примеров можно найти в руководстве по функциям в JavaScript.
Описание
Каждая функция в JavaScript — это объект Function. О свойствах и методах объектов Function можно прочитать в статье Function .
Функции — это не процедуры. Функция всегда возвращает значение, а процедура может возвращать, а может не возвращать.
Чтобы вернуть значение, отличное от значения по умолчанию, в функции должна быть инструкция return , которая указывает, что именно нужно вернуть. Функция без него вернёт значение по умолчанию. В случае конструктора, вызванного с ключевым словом new , значение по умолчанию — это значение его параметра this . Для остальных функций значением по умолчанию будет undefined .
Параметры вызова функции называются аргументами функции. Аргументы передаются в функцию по значению. Если функция изменяет значение аргумента, это изменение не отражается на глобальном состоянии или вызывающей функции. Однако ссылки на объекты — это тоже значения, и они отличаются тем, что если функция изменяет свойства объекта по ссылке, это изменение видно снаружи функции, как показано в примере ниже.
/* Объявляем функцию 'myFunc' */ function myFunc(theObject) theObject.brand = "Toyota"; > /* * Объявляем переменную 'mycar'; * создаём и инициализируем новый Object; * приравниваем 'mycar' к ссылке на него */ var mycar = brand: "Honda", model: "Accord", year: 1998, >; /* Выведет 'Honda' */ console.log(mycar.brand); /* Передаём ссылку на объект в функцию */ myFunc(mycar); /* * Выведет 'Toyota', так как значение свойства 'brand' * было изменено внутри функции. */ console.log(mycar.brand);
Ключевое слово this не ссылается на функцию, которая выполняется в данный момент, поэтому вы должны обращаться к объектами Function по имени, даже внутри тела самой функции.
Определение функций
Есть несколько способов определить функцию:
Объявление функции (инструкция function )
Специальный синтаксис для объявления функций (более подробно: function statement):
function name([param[, param[, . param]]])
Имя аргумента, передаваемого в функцию. У функции может быть не более 255 аргументов.
Инструкции, из которых состоит тело функции.
Функция-выражение (оператор function )
Функция-выражение похожа на определение функции и имеет такой же синтаксис (более подробно: function operator):
function [name]([param] [, param] [. param])
Имя функции. Может быть не указано, в таком случае функция становится анонимной.
Имя аргумента, передаваемого в функцию. У функции может быть не более 255 аргументов.
Инструкции, из которых состоит тело функции.
Стрелочная функция-выражение (=>)
Примечание: стрелочные функции являются экспериментальной технологией*,* частью спецификации ECMAScript 6 и пока что не поддерживаются всеми браузерами.
Стрелочные функции отличаются более кратким синтаксисом и тем, что они лексически связывают значение своего this (подробнее об этом в статье Стрелочные функции):
([param] [, param]) => < statements >param => expression
Имя параметра. Если параметров нет, вместо них нужно поставить (). Если параметров больше одного, их также нужно заключить в ().
Если инструкций несколько, их нужно заключить в <>. Для одного выражения фигурных скобок не требуется, а результат этого выражения будет возвращён функцией (то есть` функция x => 3 + 8 вернёт 11).«`
Конструктор Function
Примечание: Использовать конструктор Function не рекомендуется, так как он принимает тело функции в виде строки, а это может помешать оптимизациям, которые выполняют движки JavaScript, а также привести к другим проблемам.
Объекты Function можно создавать с помощью оператора new (как и любые другие объекты):
new Function (arg1, arg2, . argN, functionBody)
Ноль или больше имён параметров функции. Имя должно быть строкой, содержащей валидный идентификатор JavaScript. Если параметров несколько, они должны быть разделены запятыми. Например: » x «, » theValue «, или » a,b «.
Инструкции, из которых состоит тело функции.
Конструктор Function можно вызывать и без оператора new, эффект будет тем же.
Параметры функции
Примечание: Остаточные параметры и параметры по умолчанию — это экспериментальная технология, часть спецификации ECMAScript 6, и они пока ещё не получили широкой поддержки среди браузеров.
Параметры по умолчанию
Параметры функции по умолчанию позволяют инициализировать формальные параметры со значениями по умолчанию, если им не было передано значение, или было передано undefined . Подробнее о них можно узнать в статье Параметры по умолчанию.
Остаточные параметры
Синтаксис оставшихся параметров позволяет передать бесконечное число аргументов как массив. Подробности можно найти в статье Остаточные параметры.
Объект arguments
Внутри функции получить доступ к её аргументам можно через объект arguments.
- arguments (en-US): Объект, похожий на массив и содержащий все аргументы, переданные в текущую функцию.
- arguments.callee (en-US) Устарело : Функция, исполняемая в текущий момент.
- arguments.caller : Функция, которая вызвала текущую функцию.
- arguments.length (en-US): Число аргументов, переданных в функцию.
Определение методов
Геттеры и сеттеры
Можно определять геттеры (методы для чтения) и сеттеры (методы для изменения) для любого встроенного или пользовательского объекта, который поддерживает добавление новых свойств. Для этого используется синтаксис литерала объекта.
Связывает свойство объекта с функцией, которая будет вызвана при обращении к свойству.
Связывает свойство объекта с функцией, которая будет вызвана при попытке изменения свойства.
Синтаксис определения методов
Примечание: Определение методов — это экспериментальная технология, часть спецификации ECMAScript 6, и она пока ещё не получила широкой поддержки среди браузеров.
Начиная с ECMAScript 6, можно определять собственные методы, используют более краткий синтаксис, похожий на геттеры и сеттеры. Более подробно — в статье Определение методов.
var obj = foo() >, bar() >, >;
Сравнение конструкторов Function с объявлением функций и функциями-выражениями
Посмотрите на следующие примеры:
Функция, определённая через конструктор Function и приравненная к переменной multiply:
var multiply = new Function("x", "y", "return x * y");
Объявление функции multiply:
function multiply(x, y) return x * y; >
Анонимная функция-выражение, приравненная к переменной multiply:
var multiply = function (x, y) return x * y; >;
Функция-выражение с именем func_name , приравненное к переменной multiply:
var multiply = function func_name(x, y) return x * y; >;
Отличия
Во всех случаях результат примерно одинаков, но есть несколько нюансов:
Имя функции и переменная, к которой функция приравнена — это не одно и то же. Имя функции нельзя менять, а вот переменной, к которой приравнена функция, можно дать другое значение. В случае функции-выражения с именем, это имя может быть использовано только внутри самой функции. При попытке использовать его снаружи возникнет ошибка (а если ранее была объявлена переменная с таким именем, будет возвращено undefined ). Например:
var y = function x() >; alert(x); // выкинет ошибку
Также имя функции-выражения проявляется, если сериализовать функцию через метод Function.toString.
А вот переменная, к которой функция приравнена, ограничена только собственной областью видимости, которая включает ту область, где функция была объявлена.
Как показано в четвёртом примере, имя функции может отличаться от имени переменной, к которой функция приравнена, эти имена никак не связаны. Объявление функции (function declaration) также создаёт и переменную с именем, аналогичным имени функции. Таким образом:
- Если функция определена с помощью функции-выражения (function expression), её имя доступно только внутри самой функции.
- Если функция объявлена (function declaration), её имя доступно в той области видимости, где функция была определена.
У функции, определённой с помощью ‘ new Function’ , нет имени. Однако, в JavaScript движке SpiderMonkey, сериализованное представление функции отображается так, как будто оно имеет имя «anonymous». Например, , alert(new Function()) выдаст:
function anonymous() >
Так как на самом деле у функции нет имени, переменную anonymous нельзя использовать внутри функции. Например, следующий пример выкинет ошибку:
var foo = new Function("alert(anonymous);"); foo();
В отличии от функций, определённых через функцию-выражение или конструктор Function , функция, определённая через объявление, может быть использована перед тем, как была определена. Например:
foo(); // выведет FOO! function foo() alert("FOO!"); >
Функция, определённая через функцию-выражение, наследует текущую область видимости, то есть создаёт замыкание. А вот функция, созданная с помощью конструктора Function , не наследует ничего, кроме глобальной области видимости (её наследуют вообще все функции).
Функции, определённые через функцию-выражение и объявление функции парсятся только один раз, в отличии от функций, созданных с помощью конструктора. То есть строка, которая передаётся в конструктор Function , парсится при каждом вызове конструктора. И хотя функция-выражение каждый раз создаёт замыкание, тело функции при этом не парсится, и получается, что функции-выражение всё равно быстрее, чем » new Function(. ) «. Поэтому конструктора Function в большинстве случаев стоит избегать, если это возможно.
Стоит отметить, что функции-выражения и объявления функций внутри функции, созданной при парсинге конструктора Function , парсятся только один раз. Например:
var foo = new Function( "var bar = 'FOO!';\nreturn(function() );", )(); foo(); // "function() " Эта часть строки, составляющей тело функции, не парсится во второй раз.
Объявление функции можно очень легко (и часто случайно) превратить в функцию-выражение. Объявление функции перестаёт быть таковым, если оно:
- становится частью выражения
- не является «исходным элементом» функции или файла. Исходный элемент — это не вложенный элемент внутри функции или скрипта:
var x = 0; // исходный элемент if (x == 0) // исходный элемент x = 10; // не исходный элемент function boo() > // не исходный элемент > function foo() // исходный элемент var y = 20; // исходный элемент function bar() > // исходный элемент while (y == 10) // исходный элемент function blah() > // не исходный элемент y++; // не исходный элемент > >
Примеры
// объявление функции function foo() > // функция-выражение (function bar() >); // функция-выражение x = function hello() >; if (x) // функция-выражение function world() > > // объявление функции function a() // объявление функции function b() > if (0) // функция-выражение function c() > > >
Определение функции в зависимости от условия
Функции могут быть определены в зависимости от условий с помощью инструкции function (разрешённое расширение стандарта ECMA-262 Edition 3) или конструктора Function . Обратите внимание, что подобные инструкции запрещены в ES5 strict. Кроме того, эта возможность по-разному ведёт себя в разных браузерах, поэтому не стоит на неё рассчитывать.
В коде ниже функция zero никогда не будет определена и не может быть вызвана, потому что ‘ if (0) ‘ всегда расценивается как false :
if (0) function zero() document.writeln("This is zero."); > >
Если изменить условие на ‘ if (1) ‘, функция zero будет определена.
Заметьте, что хотя это выглядит как объявление функции, на самом деле, это функция-выражение (или инструкция), так как она вложена внутрь другой инструкции. Изучите разницу между объявлением функции и функцией-выражением.
Некоторые JavaScript-движки (но не SpiderMonkey), неверно считают любую функцию-выражение с именем за объявление функции. Это приводит к тому, что функция zero будет определена, даже если условие всегда false . Более безопасный способ определить функцию по условию — это сделать её анонимной и приравнять к переменной:
if (0) var zero = function () document.writeln("This is zero."); >; >
Примеры
Пример: возврат отформатированного числа
Эта функция возвращает строку, содержащую число с заданным количеством нулей перед ним:
function padZeros(num, totalLen) var numStr = num.toString(); // Инициализировать возвращаемое значение в виде строки var numZeros = totalLen - numStr.length; // Посчитать число нулей в начале for (var i = 1; i numZeros; i++) numStr = "0" + numStr; > return numStr; >
var result; result = padZeros(42, 4); // возвращает "0042" result = padZeros(42, 2); // возвращает "42" result = padZeros(5, 4); // возвращает "0005"
Пример: существует ли функция
Можно определить, существует ли функция с помощью оператора typeof . В следующем примере проверяется, есть ли у объекта window функция noFunc . Если есть, то она вызывается; если нет, выполняется какое-то другое действие.
if ("function" == typeof window.noFunc) // вызывать noFunc() > else // сделать что-то другое >
Заметьте, что в проверке условия используется ссылка на noFunc — после имени функции нет скобок, поэтому сама функция не вызывается.
Спецификации
Specification |
---|
ECMAScript Language Specification # sec-function-definitions |
Поддержка браузерами
BCD tables only load in the browser
Смотрите также
- Function
- Инструкция function
- Оператор function
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on 4 авг. 2023 г. by MDN contributors.
Your blueprint for a better internet.
MDN
Support
- Product help
- Report an issue
Our communities
Developers
- Web Technologies
- Learn Web Development
- MDN Plus
- Hacks Blog
- Website Privacy Notice
- Cookies
- Legal
- Community Participation Guidelines
Visit Mozilla Corporation’s not-for-profit parent, the Mozilla Foundation.
Portions of this content are ©1998– 2023 by individual mozilla.org contributors. Content available under a Creative Commons license.
Методы объекта, «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 становится объект перед точкой . .