Кратко
СкопированоФункции — это объект первого класса. Это означает, что функцию можно использовать так же, как и другие типы данных: сохранять в переменную, передавать аргументом и возвращать из функции.
Технически, функция — это объект 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!"
Что важно помнить при использовании этого решения:
- Работает только с однострочными комментариями — многострочные
/* *останутся нетронутыми;/ - При конфликтующих операциях (например, две переменные с одним именем) код может выдать ошибку.