Оглавление цикла уроков по функциям в javascript.
Как я уже писал в предыдущем уроке: функции в javascript — это объекты, такие же, как, скажем, массивы. Их можно присваивать переменным или полям других объектов, можно у них создавать поля и задавать любое их содержимое (someFunc.field = «value»), можно передавать в качестве параметров при вызове других функций. Да, отличие функций от других объектов — их можно вызывать, передавая какие-то параметры. Но есть также небольшое отличие от других популярных языков программирования: в javascript функции не просто вызываются, а явно или неявно применяются к неким объектам.
Формально вызов функции выглядит следующим образом:
1 |
someFunc.apply(someObj, [param1, param2, ...]); |
или, что то же самое, но параметры передаются не массивом, а через запятую:
1 |
someFunc.call(someObj, param1, param2, ...); |
То есть у каждой функции (она ведь объект) есть методы apply и call, которые позволяют применять данную функцию к переданному в качестве первого параметра объекту (someObj). Это важно понимать, поскольку именно на этот объект ссылается ключевое слово this внутри нашей функции. «Переменная» this доступна всегда внутри функции.
Вы можете сказать «Но я просто вызываю функцию как someFunc() и не парюсь ни с какими apply и call». Да, так и есть, это называется «синтаксический сахар», вы можете вызвать функцию «по-простому», но на что тогда будет ссылаться this? На глобальный объект, то есть в случае выполнения вашего кода в браузере — на window:
1 2 3 4 5 |
function someFunc (text) { this.alert(text); } someFunc.call(window, "Hello!"); // формальный вариант someFunc("Hello!"); // более короткий, но идентичный вариант |
С ситуацией, когда функция вызывается как метод объекта, все проще и интуитивно понятнее:
1 2 3 4 5 6 7 8 9 10 11 |
var someObj = { objName: "My name", someFunc: function () { alert(this.objName); } }; someObj.someFunc.call(someObj); // формальный вариант var func = someObj.someFunc; func.call(someObj); // и так работает someObj.someFunc(); // более привычный вариант func(); // а так уже работать не будет |
Думаю, уже понятно, почему не будет работать последний вариант: в этом случае this внутри вызываемой функции ссылается по умолчанию на window, а не на someObj. Это типичная ошибка начинающих программистов на javascript — передавать обработчику события метод объекта «как есть», обработчик же вызывает данную функцию в контексте глобального объекта, и this в вашей функции не будет ссылаться на него. Решается проблема довольно просто, нужно лишь обернуть вызов метода в функцию, в примере выше, чтоб последний вариант работал, можно сделать так:
1 2 3 4 |
var func = function () { someObj.someFunc(); }; func(); // теперь работает корректно |
То же самое с обработчиками событий, например, в переменной button у нас ссылка на некую кнопку на странице (хотя так вешать обработчик событий нежелательно):
1 2 3 4 |
button.onclick = someObj.someFunc; // в нашем случае сработает неправильно button.onclick = function () { // теперь все в порядке someObj.someFunc(); }; |
Другой вариант сделать то же самое — написать универсальную функцию, которая привязывала бы нужную нам функцию к объекту. Вот как это выглядит:
1 2 3 4 5 |
function bind (func, obj) { return function () { func.apply(obj, arguments); }; } |
Во время привязки мы получаем «оберточную» функцию, вызывая которую в любом контексте и передавая ей необходимые параметры, мы гарантируем, что функция будет вызвана именно в контексте нужного нам объекта, и ей будут переданы все параметры (они находятся внутри arguments у этой функции). Имея теперь в наличии функцию bind(), мы можем переписать пример с обработчиками событий таким образом:
1 |
button.onclick = bind(someObj.someFunc, someObj); |
Функцию необязательно привязывать к объекту, методом которого она является изначально. Функция — это самодостаточный объект, который существует сам по себе и может быть привязан и применен к любому объекту, this зависит только от контекста вызова (применения). Например, создадим еще один объект, у которого нет методов, а поле objName имеет другое значение:
1 2 3 |
var anotherObj = { objName: "Another name" }; |
Теперь привяжем метод someFunc объекта someObj к нашему новому объекту. Самый простой вариант — добавить эту функцию в наш новый объект в качестве метода:
1 |
anotherObj.someFunc = someObj.someFunc; |
Или можем создать обертку с нужной привязкой:
1 2 |
button.onclick = bind(someObj.someFunc, anotherObj); // При нажатии выведется "Another name". |
Подводя итог, просто скажу еще раз, что this доступно при любом вызове функции и его содержимое зависит только от способа вызова, сама же функция самостоятельна и ничего не знает о том, методом какого объекта она является или живет своей жизнью.