Статьи

Функции JavaScript, которые определяют и переписывают сами

Ниже приведен небольшой отрывок из нашей новой книги « JavaScript: новичок для ниндзя», второе издание , написанной Дарреном Джонсом. Это руководство для начинающих по JavaScript. Члены SitePoint Premium получают доступ к своему членству, или вы можете купить копию в магазинах по всему миру.

Динамическая природа JavaScript означает, что функция способна не только вызывать себя, но и определять себя и даже переопределять себя. Это делается путем присвоения анонимной функции переменной, имя которой совпадает с именем функции .

Рассмотрим следующую функцию:

function party(){
console.log('Wow this is amazing!');
party = function(){
    console.log('Been there, got the T-Shirt');
}
}
            

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

 function party() {
console.log('Been there, got the T-Shirt');
}
            

Каждый раз, когда функция вызывается после первого раза, она будет записывать сообщение «Там был, получил футболку»:

 party();
<< 'Wow this is amazing!'

party();
<< 'Been there, got the T-Shirt'

party();
<< 'Been there, got the T-Shirt'
            

Если функция также назначена другой переменной, эта переменная будет поддерживать исходное определение функции и не будет перезаписываться. Это связано с тем, что исходная функция назначается переменной, а затем внутри функции переменная с тем же именем, что и функция, назначается другой функции. Вы можете увидеть пример этого, если мы создадим переменную beachPartyparty()до того, как она будет вызвана в первый раз и переопределена:

 function party(){
console.log('Wow this is amazing!');
party = function(){
    console.log('Been there, got the T-Shirt');
}
}

const beachParty = party; // note that the party function has not been invoked

beachParty(); // the party() function has now been redefined, even though it hasn't been called explicitly
<< 'Wow this is amazing!'

party(); 
<< 'Been there, got the T-Shirt'

beachParty(); // but this function hasn't been redefined
<< 'Wow this is amazing!'

beachParty(); // no matter how many times this is called it will remain the same
<< 'Wow this is amazing!'
            

Потерять свойства

Будьте осторожны: если в функции ранее были установлены какие-либо свойства, они будут потеряны, когда функция переопределит себя. В предыдущем примере мы можем установить music

 function party() {
console.log('Wow this is amazing!');
party = function(){
console.log('Been there, got the T-Shirt');
}
}

party.music = 'Classical Jazz'; // set a property of the function

party();
<< "Wow this is amazing!"

party.music; // function has now been redefined, so the property doesn't exist
<< undefined

Это называется Lazy Definition Pattern и часто используется, когда при первом вызове требуется некоторый код инициализации. Это означает, что инициализация может быть выполнена при первом вызове, затем функция может быть переопределена так, как вы хотите, чтобы она была для каждого последующего вызова.

Начальное время ветвления

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

Давайте возьмем пример нашего вымышленного объекта unicorn В последней главе мы рассмотрели, как мы можем использовать обнаружение функций, чтобы проверить, поддерживается ли это. Теперь мы можем сделать еще один шаг: мы можем определить функцию на основе того, поддерживаются ли определенные методы. Это означает, что нам нужно проверять поддержку только при первом вызове функции:

 function ride(){
    if (window.unicorn) { 
        ride = function(){
        // some code that uses the brand new and sparkly unicorn methods
        return 'Riding on a unicorn is the best!';
    }
    } else {
        ride = function(){
        // some code that uses the older pony methods
        return 'Riding on a pony is still pretty good';
    }
    }
    return ride();
}
            

После того, как мы проверили, существует ли объект window.unicornride() В самом конце функции мы вызываем ее снова, чтобы переписанная функция теперь вызывалась и возвращалось соответствующее значение. Следует помнить, что функция вызывается дважды в первый раз, хотя она становится более эффективной при каждом последующем вызове. Давайте посмотрим, как это работает:

 ride(); // the function rewrites itself, then calls itself
<< 'Riding on a pony is still pretty good'
            

Как только функция была вызвана, она переписывается на основе возможностей браузера. Мы можем проверить это, проверив функцию, не вызывая ее:

 ride
<< function ride() {
    return 'Riding on a pony is still pretty good';
    }
            

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

Рекурсивные функции

Рекурсивная функция — это функция, которая вызывает себя до тех пор, пока не будет выполнено определенное условие. Это полезный инструмент для использования, когда задействованы итерационные процессы. Типичным примером является функция, которая вычисляет факториал числа:

 function factorial(n) {
if (n === 0) {
    return 1;
} else {
    return n * factorial(n - 1);
}
}
            

Эта функция вернет 10 Функция будет продолжать вызывать себя до тех пор, пока, наконец, аргумент не станет равным 01 Это приведет к умножению 1, 2, 3 и всех чисел до исходного аргумента.

Другой пример из мира математики — гипотеза Коллатца . Эта проблема проста для постановки, но до сих пор не решена. Он включает в себя взятие любого натурального числа и соблюдение следующих правил:

  • Если число четное, разделите его на два

  • Если число нечетное, умножьте его на три и добавьте одно

Например, если мы начнем с цифры 18, у нас будет следующая последовательность:

18, 9, 28, 14, 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1, 4, 2, 1,…

Как вы можете видеть, последовательность в конце застревает в цикле, повторяя цикл «4,2,1». Гипотеза Коллатца утверждает, что каждое положительное целое число будет создавать последовательность, которая заканчивается в этом цикле. Это было проверено для всех чисел до 5 × 2⁶⁰, но нет никаких доказательств того, что оно будет оставаться верным для всех целых чисел выше этого. Чтобы проверить гипотезу, мы можем написать функцию, которая использует рекурсию, чтобы продолжать вызывать функцию, пока она не достигнет значения 1

 function collatz(n, sequence=[n]) {
if (n === 1){
    return `Sequence took ${sequence.length} steps. It was ${sequence}`;
}

if (n%2 === 0) {
    n = n/2;
} else { 
    n = 3*n + 1;
}

return collatz(n,[...sequence,n]);
}
            

Эта функция принимает число в качестве параметра, а также другой параметр, называемый sequence Второй параметр используется только тогда, когда функция вызывает себя рекурсивно.

Первое, что делает функция, это проверяет, имеет ли n Если он не достиг 1, он проверяет, является ли значение nn Новая последовательность создается путем помещения старой последовательности и значения n

Давайте посмотрим, что происходит с номером 18:

 collatz(18);
<< 'Sequence took 21 steps. It was 18,9,28,14,7,22,11,34,17,52,26,13,40,20,10,5,16,8,4,2,1'
            

Как вы можете видеть, он занимает 21 шаг, но в итоге он заканчивается на 1.

Попробуйте использовать функцию и посмотрите, сможете ли вы найти значение выше 5 × 2⁶⁰, которое не заканчивается на 1 — если вы это сделаете, вы будете известны!