Кратко
СкопированоФункции — это объект первого класса. Это означает, что функцию можно использовать так же, как и другие типы данных: сохранять в переменную, передавать аргументом и возвращать из функции.
Технически, функция — это объект JavaScript, у которого есть внутренний метод Call
, который добавляет возможность вызова функции.
Если вы хотите узнать о синтаксисе функций, читайте статью function
.
Как понять
СкопированоВо многих языках функции — это специальные конструкции языка. Они не являются типом данных, и набор операций, которые с ними можно делать ограничен — их можно только объявлять и вызывать.
В JavaScript функция — это тип данных, примерно такой же как объект или строка. Это означает, что с ним можно работать так же, как и с любым другим типом данных — сохранять в переменную, передавать в качестве аргумента функции, возвращать из функций.
О функции удобно думать как об объекте, который поддерживает операцию вызова.
Хранение функции в переменной
СкопированоФункции можно объявлять различными способами. Объявление функции с помощью функционального выражения не что иное, как присваивание безымянной функции переменной:
const answer = function() { console.log('42!')}answer()// 42!
const answer = function() { console.log('42!') } answer() // 42!
Можно сохранять в переменную и функцию, объявленную другим способом. При этом оба имени функции будут работать:
function answerNumber() { console.log('42!')}const answer = answerNumberanswerNumber()// 42!answer()// 42!
function answerNumber() { console.log('42!') } const answer = answerNumber answerNumber() // 42! answer() // 42!
Переменная хранит ссылку на функцию, поэтому мы можем создавать столько переменных, сколько нам нужно и все они будут именами функции:
const answer = function() { console.log('42!')}const answerNumber = answerconst fn = answer
const answer = function() { console.log('42!') } const answerNumber = answer const fn = answer
Передача функции в вызов другой функции
СкопированоФункция может передаваться в качестве аргумента при вызове другой функции.
Например, функция, которая может выполнить произвольную операцию между двумя числами. Два числа хранятся внутри функции, а операция, которую нужно выполнить, передаётся при вызове:
function performOperation(operation) { const a = 10 const b = 99 return operation(a, b)}const sum = performOperation(function(one, two) { return one + two })console.log(sum)// 109const result = performOperation(function(num1, num2) { return num1 ** (num1 / num2)})console.log(result)// 1.2618568830660204
function performOperation(operation) { const a = 10 const b = 99 return operation(a, b) } const sum = performOperation(function(one, two) { return one + two }) console.log(sum) // 109 const result = performOperation(function(num1, num2) { return num1 ** (num1 / num2)}) console.log(result) // 1.2618568830660204
Таким образом логика операции может определяться вне функции, что делает её гибкой.
Функции, которые ожидают получить другую функцию в качестве параметра — стандартное явление в JavaScript. Даже встроенные методы, такие как for
и filter
используют этот подход.
Другой случай использования — колбэки в асинхронном коде. Иногда необходимо выполнить операцию после того, как закончится какое-то действие. Например, когда пользователь кликнет на кнопку. В этом случае используется метод add
, который принимает имя события, и колбэк, который нужно вызвать при его наступлении:
document.getElementsByTagName('button')[0].addEventListener('click', function() { console.log('Пользователь кликнул!')})
document.getElementsByTagName('button')[0].addEventListener('click', function() { console.log('Пользователь кликнул!') })
Возвращение функции как результат вызова
СкопированоФункцию можно вернуть как результат работы другой функции. Например, можно сохранить данные для математической операции, но не выполнять её сразу, а вернуть функцию, которая выполнит операцию над указанными числами:
function lazySum(a, b) { return function() { return a + b }}
function lazySum(a, b) { return function() { return a + b } }
Здесь очень легко запутаться во вложенности. При вызове lazy
мы передаём два аргумента. Эти аргументы не используются тут же — мы создаём новую функцию, которая складывает два числа и возвращаем её. После вызова lazy
мы можем сохранить эту функцию в переменную и использовать её, когда нужно:
const performSum = lazySum(99, 1)console.log(performSum)// function lazySum()console.log(performSum())// 100
const performSum = lazySum(99, 1) console.log(performSum) // function lazySum() console.log(performSum()) // 100
Обратите внимание, что значения параметров a
и b
остаются доступны внутри вложенной функции. Эта особенность связана с контекстом выполнения и лексическим окружением функции. Такой подход также активно используется при разработке на JavaScript.
На практике
Скопированосоветует Скопировано
🛠 Чтобы понять, что в переменной хранится функция, достаточно воспользоваться оператором typeof
. Для функций он возвращает строку 'function'
:
const answer = function() { console.log('42!')}console.log(typeof answer)// 'function'
const answer = function() { console.log('42!') } console.log(typeof answer) // 'function'
🛠 Так как функция технически является объектом, то у функции есть свойства и методы. Например, свойство length
вернёт количество параметров функции:
const answer = function() { console.log('42!')}console.log(answer.length)// 0const sum = function(a, b) { return a + b}console.log(sum.length)// 2
const answer = function() { console.log('42!') } console.log(answer.length) // 0 const sum = function(a, b) { return a + b } console.log(sum.length) // 2
🛠 Функциям можно добавлять свойства как обычным объектам. Такой код встречается редко, но не удивляйтесь, если увидите:
const calc = function() {}calc.type = 'numbers'console.log(calc.type)// numbers
const calc = function() {} calc.type = 'numbers' console.log(calc.type) // numbers
На собеседовании
Скопировано отвечает
СкопированоВ JavaScript функция — это объект, у которого есть метод to
, возвращающий исходный код в виде строки. Это позволяет нам проанализировать текст функции и извлечь закомментированный код.
А также для создания новой функции нам понадобится конструктор new
. Он принимает список параметров и тело функции в виде строки.
Решение можно разбить на несколько шагов:
1. Получение текстового представления функции
Сначала получаем код функции в виде строкового значения, используя метод to
. Это даст нам доступ ко всему телу функции, включая комментарии.
const fnText = fn.toString()
const fnText = fn.toString()
2. Удаление символов комментариев
Используем регулярное выражение для удаления символов комментария (/
) из всех строк, где они встречаются. Это превращает закомментированный код в обычный исполняемый код, не меняя его содержимого.
const uncommentedFnText = fnText.replace(/\/\/\s?/g, '')
const uncommentedFnText = fnText.replace(/\/\/\s?/g, '')
3. Извлечение параметров функции
Для корректной работы с функцией нам важно сохранить все её параметры. Извлекаем их из исходного кода функции.
const params = fnText.slice(fnText.indexOf('(') + 1, fnText.indexOf(')')).split(',').map(p => p.trim())
const params = fnText.slice(fnText.indexOf('(') + 1, fnText.indexOf(')')).split(',').map(p => p.trim())
4. Создание новой функции
Создаём новую функцию, передавая параметры и тело с раскомментированным кодом в конструктор new
.
return new Function(...params, `${uncommentedFnText.substring(uncommentedFnText.indexOf('{'))}`)
return new Function(...params, `${uncommentedFnText.substring(uncommentedFnText.indexOf('{'))}`)
Теперь соберём всё вместе. Вот полное решение:
function createFn(fn) { const fnText = fn.toString() const params = fnText.slice(fnText.indexOf('(') + 1, fnText.indexOf(')')).split(',').map(p => p.trim()) const uncommentedFnText = fnText.replace(/\/\/\s?/g, '') return new Function(...params, `${uncommentedFnText.substring(uncommentedFnText.indexOf('{'))}`)}
function createFn(fn) { const fnText = fn.toString() const params = fnText.slice(fnText.indexOf('(') + 1, fnText.indexOf(')')).split(',').map(p => p.trim()) const uncommentedFnText = fnText.replace(/\/\/\s?/g, '') return new Function(...params, `${uncommentedFnText.substring(uncommentedFnText.indexOf('{'))}`) }
Вот как это работает с функцией из вопроса:
const sourceFn = (a, b) => { // const c = a + 2 // return c * b return a + b}console.log(sourceFn(5, 3)) // Выведет: 8const uncommentedFn = createFn(sourceFn);console.log(uncommentedFn(5, 3)) // Выведет: 21
const sourceFn = (a, b) => { // const c = a + 2 // return c * b return a + b } console.log(sourceFn(5, 3)) // Выведет: 8 const uncommentedFn = createFn(sourceFn); console.log(uncommentedFn(5, 3)) // Выведет: 21
А вот пример с функцией без параметров:
const someFn = () => { console.log('Hello, World!') // console.log('Hello there!')}someFn() // Выведет: "Hello, World!"const fullSomeFn = createFn(someFn)fullSomeFn()// Выведет все сообщения:// "Hello, World!"// "Hello there!"
const someFn = () => { console.log('Hello, World!') // console.log('Hello there!') } someFn() // Выведет: "Hello, World!" const fullSomeFn = createFn(someFn) fullSomeFn() // Выведет все сообщения: // "Hello, World!" // "Hello there!"
Что важно помнить при использовании этого решения:
- Работает только с однострочными комментариями — многострочные
/* *
останутся нетронутыми;/ - При конфликтующих операциях (например, две переменные с одним именем) код может выдать ошибку.