10일차 - 2022.03.10

안병욱·2022년 3월 10일
0

오늘 공부한 내용 요약

( 모던 JavaScript 튜토리얼 학습 )

1. 재귀와 스택

  • n == 1 일때 모든 절차가 간단해짐. 명확한 결과값
    -> 이를 재귀의 베이스(base)라 한다

  • 재귀단계 -> pox(x,n) = x * pox(x,n-1)

Ex) pox(2,4)를 계산 과정 
pox(2,4) = 2* pox(2,3)
pox(2,3) = 2*pox(2,2)
..
pox(2,1) = 2 
  • 재귀 깊이 -> 처음하는 호출을 포함한 호출의 최대개수
    pox(x,n)의 재귀깊이는 n


  • 실행 컨텍스트와 스택

실행 컨텍스트 - 실행중인 함수의 실행절차 정보 담긴 것
(함수 호출 1회당 하나의 실행 컨텍스트가 생성)

  • 중첩 호출이 있을 때 과정
  1. 현재 함수의 실행이 중단
  2. 중지된 함수와 연관 실행 컨텍스트는 실행 컨텍스트 스택이라는 특별한 자료구조에 저장됨
  3. 중첩 호출이 실행됨
  4. 중첩호출 실행이 끝난후 실행컨텍스트 스택에서 일시 중단한 함수의 실행 컨텍스트를 꺼내오고 , 중단된 함수의 실행을 다시 이어감

  • pox(2,3)이 실행될 때 일어나는 과정
Function pow(x,n) {
	if (n==1) {
	return x;
}  else {
	return x * pox(x,n-1);
}}

을 기본 문법으로


1. Pox (2,3)

실행 컨텍스트에
context : { x:2 , n:3 , 첫번째 줄}
n=1 을 만족시키지 못하므로 다음
context: { x: 2, n: 3, 다섯 번째 줄 }


2. Pox(2,2)

중첩 호출을 하기위해 실행 컨텍스트 스택에 현재 실행 컨텍스트를 저장

모든 함수에 대해 아래와 같이 적용됨
1. 스택 최상단에 현재 컨택스트가 기록
2. 서브 호출을 위한 새로운 컨텍스트가 기록
3. 서브호출이 완료되면 기존 컨텍스트를 스택에서 꺼내 실행을 이어감

위를 근거로 Pox(2,2) 실행시
context: { x: 2, n: 2, 첫 번째 줄 }
context: { x: 2, n: 3, 다섯 번째 줄 }
새 컨텍스트는 상단에 , 기존 컨텍스트는 하단으로 감


3. pox(2,1)

동일하게 새 컨텍스트는 상단으로 올라감

context: { x: 2, n: 1, 첫 번째 줄 }
context: { x: 2, n: 2, 다섯 번째 줄 }
context: { x: 2, n: 3, 다섯 번째 줄 }


4. 실행종료

이전과 달리 n==1을 만족시키므로 if문의 첫번째 분기가 실행
이젠 실행될 중복 호출이 없음 . 함수는 종료되고 2가 출력

함수가 종료되었기에 이에 상응하는 컨텍스트는 필요없으므로 삭제됨
스택 맨위엔 이전의 내용인

context: { x: 2, n: 2, 다섯 번째 줄 } 
context: { x: 2, n: 3, 다섯 번째 줄 }

이 되는데 다시 pox(2,2) 가 실행되고 4를 반환

그리고
context: { x : 2, n : 3, 다섯 번째 줄 } 이 남게되고
pox(2,3)이 실행되어 8이 출력




반복문 알고리즘을 사용시 메모리가 절약됨

function pow(x, n) {
  let result = 1;

  for (let i = 0; i < n; i++) {
    result *= x;
  }

  return result;
}

반복문을 통해 만든 pox는 실행컨텍스트를 1개만 만듬
i와 result가 변경되고 n에 의존적이지 않고 메모리가 적게 듬


  • 재귀를 이용해 작성한 코드는 반복문을 사용해 다시 작성할수 있고 필요 메모리를 줄일수 있는경우도 있지만 아닌 경우도 있다
    ex) 분기문이 복잡하게 얽혀있는 경우 등

  • 재귀를 사용하면 코드가 짧아지고 코드 이해도가 높아지며 유지보수에 이점이 있다.



  • 재귀적 순회
    예시문제 )

회사에 부서가 있고 부서안에 부서가 있을때 모든 직원의 급여의 값을 구해보자

let company = { 
  sales: [{name: 'John', salary: 1000}, {name: 'Alice', salary: 1600 }],
  development: {
    sites: [{name: 'Peter', salary: 2000}, {name: 'Alex', salary: 1800 }],
    internals: [{name: 'Jack', salary: 1300}]
  }
};

function sumSalaries(department) {
  if (Array.isArray(department)) { //   1번 경우 
    return department.reduce((prev, current) => prev + current.salary, 0);     // -> dapartment가 배열이면  결과 
  } else {
    let sum = 0;
    for (let subdep of Object.values(department)) {
      sum += sumSalaries(subdep); // 재귀 호출로 각 하위 부서 임직원의 급여 총합을 구함
    }
    return sum;
  }
}
  1. 직원 배열의 간단한 부서
    -> 반복문으로 급여합계 구할수잇음(재귀의 베이스)
  2. n개의 하위부서가 있는 값을 구하기 위해 n번의 재귀호출을 함


  • 연결 리스트
    빠르게 삽입 혹은 삭제를 해야할떄 사용가능

Value
next -> 다음 연결 리스트요소를 참조하는 프로퍼티 . 다음값이 없으면 null

let list = {
  value: 1,
  next: {
    value: 2,
    next: {
      value: 3,
      next: {
        value: 4,
        next: null
      }
    }
  }
};

= 

let list = { value: 1 };
list.next = { value: 2 };
list.next.next = { value: 3 };
list.next.next.next = { value: 4 };
list.next.next.next.next = null; 

list = { value: "new item", next: list }; 등으로 요소 추가 가능 (맨앞에 추가)


연결 리스트 장점
->배열과는 달리 대량으로 요소 번호를 재할당하지 않으므로 요소를 쉽게 재배열할 수 있다

연결 리스트의 단점
->번호(인덱스)로 쉽게 접근할수 없음. 처음부터 가야함


이전 요소를 참조하는 프로퍼티 prev를 추가해 이전 요소로 쉽게 이동하게 할 수 있고 리스트의 마지막 요소를 참조하는 변수 tail를 추가할 수 있습니다.


  • 예제1
    1 + 2 + ... + n을 계산하는 함수
1. For 반복문 활용
function sumTo(n) {
  let sum = 0;
  for(let i =0; i<=n ; i++) {
  return +=i;
  }
  return sum;
}


2. 재귀 사용하기 
function sumTo(n) {
  if (n == 1 ) {
    return 1;
  } else {
    return n + sumTo(n-1);
  }
}


3. 등차수열의 합공식 
sumTo(n) = n*(n+1)/2
function sumTo(n) {
  return n * (n + 1) / 2;
}

alert( sumTo(100) );


  • 예제2
    1,2번째 항이 1이고 이후의 항은 앞의 2개 항의 합인 경우인 n번째 항을 나타내는 fib(n) 구하기
1. 재귀방식 
Function fib(n) {
	return n=<1 ? n : fib(n-2) + fib(n-1);
}
이 방식도 가능하나  fib(70)같은 수를 구하려면 연산속도가 느려지는데 이는 수많은 서브호출이 일어나기 떄문 


2. 반복문 
- 저장한 값을 가져와 쓰는 반복문 사용해보면 
Function fib(n) {
	let a = 1;
	let b = 1;
	for (let i = 3 ; i <=n ; i++ ;
	let c =  a + b;
	a = b; 
	b = c;    
}
	return b;
}
 i가 n보다 클때 출력되는 값이 c가 아니라 b 인 이유는 초과된 값이 c이고 그전 항의 값이 b이기 때문에 

모든 경우에 해당되진 않지만 반복문과 재귀를 사용한 방법 중에는
반복문이 리소스를 좀 더 효율적으로 사용합니다. 두 방법의 반환 값은 같지만, 반복문을 사용한 방법에선 중첩 함수를 호출하는데 추가적인 리소스를 쓰지 않기 때문이다.
반면 재귀를 사용한 방법은 코드 길이가 짧고 이해하기 쉬우며 유지보수가 상대적으로 쉬운 장점이 있습니다





2. 나머지 매개변수와 스프레드 문법

...은 나머지 매개변수나 스프레드 문법으로 사용할 수 있다.

1) 나머지 매개변수

function sum(a, b) {
  return a + b;
}

alert( sum(1, 2, 3, 4, 5) );    // 3

이 경우 오류는 나지않고 앞의 인수 2개만 받음.
나머지 인수도 포함시키려면 …를 사용

예시)

function sumAll(...args) {
  let sum = 0;
  for (let arg of args) 
  sum += arg;
  return sum;
}

alert(sumAll(1,2,3));    //6


  • 앞부분 인수로 받고 나머지는 배열로 받기
function showName(firstName, lastName, ...titles) {
  alert( firstName + ' ' + lastName );    // Bora Lee

  titles = ["Software Engineer", "Researcher"]
  alert( titles[0] ); // Software Engineer
  alert( titles.length ); // 2
}

showName("Bora", "Lee", "Software Engineer", "Researcher");

  • …rest는 항상 마지막에 있어야 합니다.
function f(arg1, ...rest, arg2) {}       // 에러  

  • arguments 객체

유사 배열 객체 이면서 이터러블(반복가능한) 객체이지만 배열은 아니기 떄문에 배열메서드가 사용불가임. ex) arguments.map(x)
인수 전체를 담기 때문에 일부의 인수만 사용불가

function showName() {
  alert( arguments.length );
  alert( arguments[0] );
  alert( arguments[1] );
}

showName("Bora", "Lee");     // 2, Bora, Lee
showName("Bora");           // 1, Bora, undefined



2) 스프레드 문법

  • 배열을 대상으로 Math.max 사용 원할시
let arr = [3, 5, 1];

alert( Math.max(arr) ); // NaN          
-> Math.max가 문자열이 아닌 숫자를 인수로 받기에 작동 x 
   이때 …arr을 사용하면 인수목록으로 확장됨 

alert( Math.max(...arr) );  // 5

-객체 여러개를 합치거나 평범한 값과 혼용도 가능 
alert (Math.max(6, ...arr));  /// 6

  • 배열이 아니고 이터러블 객체여도 스프레드 문법을 사용가능
let str = "Hello";

alert( [...str] );   // H,e,l,l,o 
alert( [str]);      // Hello

  • Array.from은 유사 배열 객체와 이터러블 객체 둘 다에 사용할 수 있습니다. 스프레드 문법은 이터러블 객체에만 사용할 수 있습니다.
    배열로 바꾸고자 할때는 Array.from 이 더많이 쓰임

  • 배열과 객체의 복사본
  1. 배열 복사
let arr = [1, 2, 3];
let arrCopy = [...arr]; 


alert(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true


alert(arr === arrCopy); // false (참조가 다름)

// 참조가 다르므로 기존 배열을 수정해도 복사본은 영향을 받지 않습니다.
arr.push(4);
alert(arr); // 1, 2, 3, 4
alert(arrCopy); // 1, 2, 3

  1. 객체 복사
let obj = { a: 1, b: 2, c: 3 };
let objCopy = { ...obj }; 

alert(JSON.stringify(obj) === JSON.stringify(objCopy)); // true

alert(obj === objCopy); // false (참조가 다름)

// 참조가 다르므로 기존 객체를 수정해도 복사본은 영향을 받지 않습니다.
obj.d = 4;
alert(JSON.stringify(obj)); // {"a":1,"b":2,"c":3,"d":4}
alert(JSON.stringify(objCopy)); // {"a":1,"b":2,"c":3}

  • 요약

...이 함수 매개변수의 끝에 있으면 인수 목록의 나머지를 배열로 모아주는 '나머지 매개변수’
...이 함수 호출 시 사용되거나 기타 경우엔 배열을 목록으로 확장해주는 '스프레드 문법’

사용 패턴:
인수 개수에 제한이 없는 함수를 만들 때 나머지 매개변수를 사용합니다.
다수의 인수를 받는 함수에 배열을 전달할 때 스프레드 문법을 사용합니다.



3.변수의 유효범위와 클로저

  • 코드 블록 {...} 안에서 선언한 변수는 블록 안에서만 사용할 수 있다
{ 
	let message = "안녕하세요."; 
  	alert(message); // 안녕하세요.
}

alert(message);  //  오류

  • 이미 선언된 변수와 동일한 이름을 가진 변수를 별도의 블록 없이 let으로 선언하면 에러가 발생합니다.
let message = "안녕하세요.";
alert(message);

let message = "안녕히 가세요."; 
alert(message);       
// 오류     2개 중에 하나 블록으로 묶어줘야함
  • If, for, while 에서도 {} 안에서 선언한 변수는 바깥에서 접근불가

  • 중첩 함수
    -함수 내부에서 선언한 함수



  • 렉시컬 환경 (내부 숨김 연관 객체)

< 1단계 - 변수 >

-자바스크립트에선 실행중인 함수, 블록, 스크립트 전체는 렉시컬 환경이라 불리는 내부 숨김 연관 객체를 갖는다

-렉시컬 환경 객체는 환경레코드 + 외부 렉시컬 환경
환경레코드(모든 지역변수를 코드로 저장하고 있는 객체 , 특수내부객체 )
외부 렉시컬 환경 (외부 코드와 연결)

-변수는 특수 내부객체인 환경레코드의 프로퍼티이다. 즉 변수를 가져오거나 변경하는것은 환경레코드의 프로퍼티를 가져오거나 변경하는 것

-전역 렉시컬 환경
스크립트 전체와 연관된 렉시컬 환경 / 외부 참조를 갖지 않음

			    -> name : uninitialized   (자바스크립트 엔진은 uninitialized 상태의 변수를 인지하긴 하지만, let을 만나기 전까진 이 변수를 참조할 수 없습니다.)
let name;		-> name : undefined     ( name은 이 시점부터 사용할수 있다)
name = “me”;	-> name : me
name = “you”;	-> name : you

< 2단계 - 함수선언문 >

-함수 선언문으로 선언한 함수는 일반 변수와 달리 바로 초기화 된다

-렉시컬 환경이 만들어지는 즉시 사용할 수 있다

-변수는 let을 만나 선언되기까지 사용 불가능
함수 선언 전에도 함수를 사용할 수 있는 것은 이 때문임

-주의할 점은 let say = function(name)...같이 함수를 변수에 할당하는
함수 표현식은 이에 해당하지 않는다


< 3단계 - 외부와 내부 렉시컬 환경 >

함수를 실행하면 새로운 렉시컬 환경이 자동으로 만들어짐
-> 함수호출시 넘겨받은 매개변수와 함수의 지역 변수가 저장됨

-내부 렉시컬 환경은 외부 렉시컬 환경에 대한 참조를 갖습니다.

-코드에서 변수에 접근할 땐,
1. 먼저 내부 렉시컬 환경을 검색 범위로 잡습니다.
2. 내부 렉시컬 환경에서 원하는 변수를 찾지 못하면
3. 검색 범위를 내부 렉시컬 환경이 참조하는 외부 렉시컬 환경으로 확장합니다.
4. 이 과정은 검색 범위가 전역 렉시컬 환경으로 확장될 때까지 반복됩니다.

-전역 렉시컬 환경에 도달할때까지 변수를 찾지 못하면 엄격모드에서는 에러 발생



< 4단계 - 함수를 반환하는 함수 >

function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}

let counter = makeCounter();

-예시를 참고하면 makeCounter()를 호출하면 호출할때마다 새로운 렉시컬 환경 객체가 만들어지고 여기에 makeCounter를 실행하는데 필요한 변수들이 저장된다
-모든 함수는 함수가 생성된 곳의 렉시컬 환경을 기억한다
-함수는 [environmnet]라 불리는 숨김 프로퍼티를 갖는데 여기에 함수가 만들어진 렉시컬 환경에 대한 참조가 저장됨
-[environmnet]는 함수가 만들어질때 딱 한번 세팅되고 절대 변하지 않는다




  • 가비지 컬렉션

-함수 호출이 끝나면 함수에 대응하는 렉시컬 환경이 메모리에서 제거됨
-자바스크립트의 모든 객체는 도달가능한 상태일때 메모리에서 유지
-호출이 끝난 뒤에도 도달가능한 중첩함수가 있을 수 있는데
이 중첩함수의 [[Environment]] 프로퍼티에 외부 함수 렉시컬 환경에 대한 정보가 저장됩니다. 
-중첩 함수가 메모리에서 삭제되고 난 후에야, 이를 감싸는 렉시컬 환경도 메모리에서 제거됩니다



  • 예제 1
let phrase = "Hello";

if (true) {
  let user = "John";

  function sayHi() {
    alert(`${phrase}, ${user}`);
  }
}

sayHi();
// if 문 안에서 sayhi를 정의햇기에  if문 안에서만 사용가능 / 

  • 예제 2
sum(a)(b) = a+b와 같은 연산을 해주는 함수sum

Function sum(a) {
	return function(b) {
	return  a+b;
};
}

alert( sum(a)(b) );  // 3

공부사이트

코어 자바스크립트


위의 내용은 공부중 본인이 이해한 내용으로 몇몇 틀린 내용이 있을 수 있습니다.
회독중 발견시 수정하겠습니다

profile
working hard

0개의 댓글