Когда мы закончили последний урок, мы обнаружили проблему с короткими стрелочными функциями. Не было возможности включить заявления; в частности объявление переменной.

Длинная форма на помощь:

let hello = name => {
  let greeting = "Hello "+name
  return greeting
}
> hello("Brad")
< "Hello Brad"

Два пункта, сделанные в приведенном выше коде, заключаются в том, что функции допускают объявления/операторы, а вызванное значение оценивается по выражению, указанному в операторе return.

Если оператор return не указан, значение вызываемой функции всегда будет неопределенным. Вот как вы можете обходить повторяющиеся блоки кода, не повторяя себя. Теперь мы можем передавать любой набор кода внутри функции; включая декларации. Кроме того, мы можем вызвать этот код для выполнения в любое время и выйти из его блока кода с помощью оператора return:

> let x =12
  let xDownTo8 = () => { 
    if (x>8){ 
      x=8 
      return; 
  /*This will return undefined, the significance here is that the         code block will break without executing the rest of the code within it */
  } else if (x===8){ 
    return "Money" 
  /* If this statement hits the code does not continue executing and   returns the string value*/
  } else {
    x+=5
  } 
  console.log("I made it through the if/else statement!")
  return x
}
> xDownTo8()
< undefined
> xDownTo8()
< "Money"
> x=2
< 2
> xDownTo8()
I made it through the if/else statement!
< 7

Теперь вы видите, что оператор return имеет две функции; разбить вызываемый блок кода и вернуть из него значение. Но вы могли также заметить, что переменная, с которой мы работали, не была определена внутри функции! Это связано с «лексической областью видимости», когда каждый блок имеет доступ к переменным более высоких блоков и расширяется, чтобы найти ее, если эта же переменная не определена в блоке. Но Javascript не будет углубляться во вложенные блоки для поиска переменных. Учтите следующее: (*Возможно, вам понадобится новая страница браузера, чтобы убедиться, что переменные еще не существуют в окне)

> { 
  const a = 1
  //no access to b or c here, a is always 1
  { 
    const b = 2
    const a = 4
    // no access to c, a is always 4, b is always 2
    {
      const c = 3
      // a is always 4, b is always 2, c is always 3
    }
  }
}
/* You can input your console logs anywhere to test this example */

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

> const g = 8
> g = 9
Uncaught TypeError: Assignment to constant variable.

Но помните, что переменные содержат ссылку на объекты, поэтому свойства по-прежнему можно назначать или переназначать:

> const h = {}
> const h = {a:1}
Uncaught SyntaxError: Identifier 'h' has already been declared
// but this works
> h.a = 1
< 1
> h
< {a:1}
/* If you want to keep an object from changing you can freeze it with the below method */
> Object.freeze(h)
> h.b = 2
< 2
/* this does not throw an error but the object will remain unchanged */
> h
< {a:1}

Мы собираемся вернуться к оператору if/else в приведенной выше функции и создать более функциональный подход с тернарным оператором:

xDownTo8 = () => { 
  if (x>8){ 
    x=8 
    return; 
  } else if (x===8){ 
    return "Money" 
  } else {
    x+=5
  } 
}
// now can become
xDownTo8 = () => ( 
  x>8 ? (x=8 && undefined) : 
  x===8 ? "Money" :
  x+=5
)

Разве тернарный оператор не прекрасен? Он читается так же, как оператор if/else, но является возвращаемым выражением!

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

> true && () => {}
Uncaught SyntaxError: Unexpected token )
// JavaScript read it like this:
> (true && ()) => {}
//To fix wrap arrow functions in parenthesis 
> true && (() => {})

Для следующего фрагмента кода мы будем использовать первую «асинхронную функцию». (Причудливое слово для «ваш код будет продолжать выполняться, а затем вернуться к нему позже». По этой причине его также называют неблокирующим, потому что он не будет блокировать выполнение остальной части вашего кода, пока он не завершится). Функция setTimeout принимает обратный вызов (причудливое слово для аргумента, который является функцией) и время ожидания в миллисекундах, прежде чем будет разрешено выполнение обратного вызова.

> let waiting = ()=>{
  setTimeout( ()=>console.log("Your Callback Has Arrived"), 3000)
  return "Wait for it…"
}
> waiting()
/*the last pair of parenthesis is to invoke the function */
< "Wait for it…"
Your Callback Has Arrived

Вы должны были увидеть возвращаемое значение «Подождите…» в консоли за 3 секунды до появления в журнале «Ваш обратный вызов поступил». Поймите, что функция оценивалась полностью, прежде чем вернуться к оценке обратного вызова, поэтому возвращаемое значение уже было доступно! Об асинхронном коде еще многое предстоит узнать, но пока знайте, что, учитывая задержку между запросом и поступлением данных по сети, вы будете часто использовать асинхронный код.

Теперь давайте перейдем к методам обработки повторяющегося кода. Код ниже будет отсчитывать от 10 за 10 секунд. (*с начальной задержкой в ​​одну секунду)

> setTimeout( ()=>console.log(10), 1000)
setTimeout( ()=>console.log(9), 2000)
setTimeout( ()=>console.log(8), 3000)
setTimeout( ()=>console.log(7), 4000)
setTimeout( ()=>console.log(6), 5000)
setTimeout( ()=>console.log(5), 6000)
setTimeout( ()=>console.log(4), 7000)
setTimeout( ()=>console.log(3), 8000)
setTimeout( ()=>console.log(2), 9000)
setTimeout( ()=>console.log(1), 10000)
setTimeout( ()=>console.log(0), 11000)

Мы можем улучшить этот код, определив функцию

> let count = num => setTimeout( 
   ()=>console.log(num), 
   (11-num)*1000
)
//Then write simpler code repeatitively
count(10)
count(9)
count(8)
count(7)
count(6)
count(5)
count(4)
count(3)
count(2)
count(1)
count(0)

Вы можете увидеть сохраненные нажатия клавиш, но тот факт, что мы все еще повторяемся, все равно должен вас беспокоить. Есть несколько способов решить эту проблему. Сначала мы рассмотрим решение для зацикливания. Затем мы рассмотрим альтернативное решение этой проблемы, которое является рекурсией:

/* The function we will call in the loop */
> let count = num => setTimeout( 
   ()=>console.log(num), 
   (11-num)*1000
)
/* Now let's loop */
let i = 10
while (i >= 0){
  count(i)
  --i
}

Цикл while просто повторяет один и тот же блок кода каждый раз, когда условие истинно.

Предупреждение! Ваш браузер легко заморозить бесконечным циклом! Всегда используйте условные операторы меньше или больше, чтобы избежать этих ошибок, в отличие от тестов на равенство. Они сломают ваш браузер!

Плохой код Беллоу!!!! — ›:

> let count = num => setTimeout( 
   ()=>console.log(num), 
   (11-num)*1000
)
let i = 10 /* 9.25, "string", false would all cause infinite loops!!! */
while (i !== 0){ 
   /*Never try to hit a conditional with exactness like this! */
   count(i)
   --i
}

Теперь давайте рассмотрим распространенный эквивалент цикла for:

> let count = num => setTimeout( 
   ()=>console.log(num), 
   (11-num)*1000
)
for (let i = 10; i >= 0; — i) {   
   count(i);
}

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

Обратите внимание, что правильность работы обоих этих операторов зависит от того, что я ввожу число 10. Мой цикл for должен быть специально адаптирован для функции или заключен в нее. Кроме того, функции подсчета потребуется дополнительный параметр, чтобы узнать общее время, оставшееся до установки тайм-аута. Я мог бы изменить это, обернув весь код функцией и добавив дополнительную логику, но я бы просто добавил больше кода, чтобы исправить тот факт, что мы используем неправильный инструмент для этого конкретного случая использования, потому что на этот раз нам действительно нужно функциональный эквивалент вложенного кода внутри себя. Мы будем использовать рекурсию для этого, и это будет иметь смысл через мгновение.

Чтобы использовать рекурсию, нам нужно подумать о нашем функциональном подходе. Во-первых, мы хотим, чтобы нашим параметром для нашей функции был счет. Во-вторых, мы хотим, чтобы наши выходные данные были журналом консоли (через одну секунду) и следующим вызовом функции (самим), который повторяет журнал консоли, если ввод больше или равен нулю.

Начните с вашего состояния:

> let countDown = (num) => {
   console.log(num)
   --num>=0 && countDown(num)
   /*Subtract from num then recurse if zero or greater */
}

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

Теперь давайте сделаем это асинхронным:

> let countDown = (num) => {
   console.log(num)
   --num>=0 && setTimeout(()=>countDown(num),1000)
}
> countDown(4)

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

> ()=>{
   console.log(4)
   setTimeout(()=>{
      console.log(3)
      setTimeout(()=>{
         console.log(2)
         setTimeout(()=>{
            console.log(1)
            setTimeout(()=>console.log(0),1000)
         },1000)
      },1000)
   },1000)
}

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

let countDown = (num) => {
   console.log(num)
   --num>=0 && setTimeout(()=>countDown(num),1000) /* the countdown callback value gets pulled at run time! */
}
let countDown2 = countDown // pass the function to another var
countDown = 3 /* Reset the var to a number. Oops, now 3 will be pulled as the callback, but is not a function! */ 
countDown2(4) // call the function
/* Throws an error because we are invoking a number as opposed to a function */

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

Так что пристегивайтесь и готовьтесь!