0906 - 0912

0906

41일 차

1교시

  • 예습을 철저히 해라
    • 중요도 순은 예습 > 복습 > 과제 순
  • 공부를 남 보여주기 식으로 하지말자
  • 커뮤니티를 해라
  • 코드가 동작하면 장땡이라는 생각은 버리자
  • 되면 왜 되고, 안 되면 왜 안 되는지 알아야 한다.

2교시

자바스크립트는 엔진 내부에서 암묵적으로 처리해 주는 기능도 많다. 암묵적인 처리에 대한 이해가 없으면 코드를 잘못 읽게 될 수 있다.

이렇게 해도 되고 저렇게 해도 되는 경우가 많다. ex) 예를 들어 타입 변환의 경우 다른 언어는 방법이 하나인 반면, 자바스크립트는 String(a), a.toString(), a + '' 총 3가지가 있다.

에러는 우리에게 고마운 것이다. 런타임 에러는 사용자가 발생시키기 때문에 치명적이다.

자바스크립트는 동적 타입 언어이다. -> 타입 정보가 없다. 그래서 1이나 문자열 등을 할당할 수 있다.

코드를 독해할 수 있는 능력은 굉장히 중요하다. 코드 독해가 가능해야 코드를 작성할 수도 있다. 자바스크립트는 암묵적인 처리가 많기 때문에 배워야 할 게 많다.

프로그래밍이란 개발자와 컴퓨터의 커뮤니케이션 복잡한 문제를 단순하게 쪼갤 수 있는 능력이 중요 쪼갠 문제에는 순서가 있는데, 순서를 이어서 흐름을 만들어야 한다. -> 이때 필요한 것이 제어문

가독성 있는 코드가 좋은 코드인데, 그 점을 빗대어 봤을 때 제어문은 좋지 않다. 선언형의 대표적인 게 바로 리액트이다.

3교시

정식 사양은 아니지만 다음에 업데이트될 것이 확실시 된 것을 ES Next라고 한다.

한동안 var 키워드를 사용할 것인대, let 과 const가 왜 나왔는지를 알아야한다.

Asynchronous : 비동기 싱글 스레드이기 때문에

구글의 Gmail과 구글 맵스는 ajax와 함께 만들어졌다. 계속 데이터가 갱신되어야 하는데, 비동기 통신으로 필요한 맵 정보를 계속 불러왔다.

데스크탑 애플리케이션의 장점과 단점이 있다. 장점은 빠르다는 것 단점은 설치를 해야 한다는 것

웹 애플리케이션의 단점은 느렸다는 것이다. 웹 환경에서 애플리케이션을 사용하려면 브라우저의 렌더링 엔진과 JS의 엔진이 비약적으로 빨라야한다. 구글은 이때 v8 엔진을 공개했다.

노드 JS 가 갖는 의미는 브라우저 없이도 자바스크립트를 동작시킬 수 있는 환경을 만들 수 있다.

브라우저에서 동작할 수 있는 언어는 자바스크립트가 유일하다. 백엔드는 선택지가 다양하지만 클라이언트에서는 무조건 자바스크립트.

서버사이드 렌더링에서 렌더링은 페인팅하는 것까지를 말하는 것이 아니라 html을 만드는 것을 말한다.

CBD - 컴포넌트 html, css, js를 하나로 생각해서 뷰의 부품을 만들자.

변수는 값을, 함수는 로직을, 객체는 값과 로직을 재사용하기 위한 것

표준 사양을 얘기할 때는 Ecmascript라고 하고, 일상 생활에서 얘기할 때는 Javascript라고 한다. 자바스크립트는 코어 자바스크립트가 있고, 그 코어만 ecmat가 관리한다. 브라우저에서만 동작하는 것은 클라이언트 사이드 Web API가 있고, node.js에서만 동작하는 것도 있다.

4교시

과제 인터프리터 언어와 컴파일러 언어의 차이를 공부할 것 1바이트가 왜 8비트인지 힌트: 아스키코드, 유니코드 가비지 콜렉터

개발할 때 일단 효율성은 접어둘 것

for문은 가독성을 해치는 주범. for 문을 지양해야 함. map이나 reduce보다 for 문이 빠르다.

브라우저는 크게 신경쓰지 않아도 된다. babel을 쓰면 되기 때문에

변수란 ? 하나의 값을 저장하기 위해 확보한 메모리 공간 자체

메모리 값은 최소 8비트를 차지한다. 8비트보다 작은 값은 없다.

리터럴 : 사람과 컴퓨터 모두 이해할 수 있는 값을 생성하는 표기법

인풋이 있고 프로세스가 있고 아웃풋이 있다. 이것을 IPO 모델이라고 부른다. -> 동일한 인풋이 들어가면 동일한 아웃풋이 나와야 베스트

비순서 함수는 외부상태에 의존한다. ex) a + b + x 의 경우 x가 몇인지에 따라 값이 바뀐다.

자바스크립트가 os에 요청해서 메모리 주소를 어디에 저장할지 정한다.

변수 선언은 메모리 공간에 일부분을 확보하라는 뜻 그렇다면 메모리 공간은 몇바이트를 확보해야할까? undefined라는 값을 만들어서 그만큼 값을 확보했다. 브라우저마다 메모리 바이트 수가 다르기 때문에 undefined가 몇바이트인지 알 수 없다.

변수와 메모리 주소 값은 1:1 매칭된다.

함수형 언어에서는 함수가 값이다. 자바스크립트는 함수형 언어이다. 함수라는 것은 객체이다. 객체로서 메모리 안에 들어있기 때문에 함수 이름도 식별자이다.

식별자는 스코프 내에서 유니크해야한다는 것

확보 : allocate라는 말을 해석한 것. 이 메모리 공간은 내가 쓸 거야 다른 애들은 여기 들어오면 안돼. lock을 걸어놓는다는 말. os가 여기는 쓰고있다고 말하고 이 공간을 스킵한다는 말

5교시

가비지 콜렉터는 사용하고 있지 않은 값만 해제한다. -> 변수가 소멸한 값. 변수에도 라이프사이클이 있다. 나중에 스코프 얘기할 때

console.log(a)는 a 를 참조한다. 참조라는 뜻은 a에 저장된 메모리 주소에 접근하여 값을 본다.

여기서 a에 1을 할당(대입, 저장)하면, var a에서 변수를 선언하고 메모리 공간을 확보하여 undefined를 저장한다. 그리고 a에 새로운 값을 덮어 쓰는 것이 아니라 새로운 메모리 공간을 확보하여 1을 저장한다. 여기서 기존의 undefined는 가비지 콜렉터가 해제한다. 언제 해제할지는 모른다. 메모리에는 지운다는 개념이 없다. 대신 덮어 쓴다는 개념 모든 버그는 명확하지 않은 것에서 나온다. 버그가 발생하지 않으려면 단순한 로직을 써야한다.

원시값은 불변. 불변이라는 것은 메모리에서 덮어쓰지 못한다는 것이다.

var a;는 자바스크립트 엔진이 undefined로 암묵적으로 초기화 한 것이기 때문에 초기화가 되지 않았다. 초기화란 개발자가 명시적으로 하는 것이 초기화이다.

var a = 1; var a; a = 1; 위 첫 줄 식은 두 번 실행된다. 선언, 할당

브랜던 아이크는 왜 초기값을 undefined로 할당했을까 비 개발자가 사용할 때 초기화를 하지 않고 사용하면 이전에 저장되어 있던 쓰레기 값이 참조되어 오류를 유발할까봐

var는 변수 선언 전에 참조해도 호이스팅 때문에 에러가 발생하지 않음

let과 const는 선언문 이전에 참조하면 에러 그렇다면 let과 const는 호이스팅이 안되냐? 호이스팅 된다! 다만 호이스팅이 되지 않는 것처럼 동작

할당문 이후에 참조하면 할당된 값이 나오고 할당문 이전에 참조하면 undefined가 나온다.

메모리란 메인 메모리, RAM을 뜻한다. 프로그램 실행 시 필요한 주소, 정보들을 저장하고 가져다 사용할 수 있게 만드는 공간. 즉, 작업을 위해 사용되는 공간

0907

42일 차

6장 데이터 타입

데이터 타입은 값의 종류를 말한다. 자바스크립트의 모든 값은 데이터 타입을 갖는다.

  • 자바스크립트(ES6)는 7개의 데이터 타입을 제공한다.
  • 원시 타입과 객체 타입으로 분류할 수 있는다.
    • 원시 타입
      • 숫자 타입
      • 문자열 타입
      • 불리언 타입
      • undefined 타입
      • null 타입
      • 심벌 타입
    • 객체 타입 (배열, 함수, 객체 등)
  • 확보해야 할 메모리 공간의 크기도 다르고 메모리에 저장되는 2진수도 다르며, 읽어 들여 해석하는 방식도 다른다.

숫자 타입

자바스크립트는 하나의 숫자 타입만 존재한다.

  • ECMAScript 사양에 따르면 숫자 타입의 값은 배정밀도 64비트 부동소수점 형식을 따른다.
  • 모든 수를 실수로 처리하며, 정수만 표현하기 위한 데이터 타입이 별도로 존재하지 않는다.
  • 정수, 실수, 2진수, 8진수, 16진수 리터럴은 모두 메모리에 배정밀도 64비트 부동소수점 형식의 2진수로 저장된다.
    • 2진수, 8진수, 16진수를 표현하기 위한 데이터 타입을 제공하지 않기 때문에 값을 참조하면 모두 10진수로 해석된다.
  • 정수로 표시된다 해도 사실은 실수이기 때문에 정수로 표시되는 수끼리 나누더라도 실수가 나올 수 있다.
/ 숫자 타입은 모두 실수로 처리된다.
console.log(1 === 1.0); // true
console.log(4 / 2); // 2
console.log(3 / 2); // 1.5
  • 숫자 타입은 세 가지 특별한 값도 표현할 수 있다.
    • Infinity : 양의 무한대
    • -Infinity : 음의 무한대
    • NaN : 산술 연산 불가 (Not a Number)
      • 대소문자를 구별하기 때문에 주의해야 한다.

문자열 타입

텍스트 데이터를 나타내는 데 사용한다.
0개 이상의 16비트 유니코드 문자의 집합으로 전 세계 대부분의 문자를 표현할 수 있다.

  • 작은 따옴표, 큰 따옴표, 백틱으로 텍스트를 감싼다. 작은따옴표가 가장 일반적
  • 작은 따옴표 내의 큰 따옴표, 큰 따옴표 내의 작은 따옴표는 문자열로 인식된다.
  • 문자열을 따옴표로 감싸는 이유는 키워드나 식별자 같은 토큰과 구분하기 위해서다.
    • 감싸지 않으면 JS 엔진은 키워드나 식별자 같은 토큰으로 인식한다.
  • 따옴표로 문자열을 감싸지 않으면, 스페이스와 같은 공백 문자도 포함시킬 수 없다.
  • 자바스크립트의 문자열은 원시 타입이며, 변경 불가능한 값이다.

템플릿 리터럴

멀티라인 문자열, 표현식 삽입, 태그드 템플릿 등 편리한 문자열 처리 기능을 제공한다.

  • 런타임에 일반 문자열로 변환되어 처리된다.
  • 일반적인 따옴표 대신 백틱 `을 사용해 표현한다.

멀티라인 문자열

  • 일반 문자열 내에서는 줄바꿈이 허용되지 않는다.
  • 일반 문자열 내에서 줄바꿈 등의 공백을 표현하려면 백슬래시로 시작하는 이스케이프 시퀀스를 사용해야 한다.
  • 템플릿 리터럴 내에서는 이스케이프 시퀀스를 사용하지 않고도 줄바꿈이 허용되며, 모든 공백도 있는 그대로 적용된다.

표현식 삽입

  • 문자열은 문자열 연산자 +를 사용해 연결할 수 있다.
    • 연산자는 피연산자 중 하나 이상이 문자열인 경우 문자열 연결 연산자로 동작한다. 그 외는 덧셈 연산자로 동작
  • 템플릿 리터럴 내에서는 표현식 삽입을 통해 간단히 문자열을 삽입할 수 있다.
  • 표현식을 삽입하려면 ${}로 표현식을 감싼다.
    • 표현식의 평가 결과가 문자열이 아니더라도 문자열로 타입이 강제로 변환되어 삽입된다.
  • 표현식 삽입은 반드시 템플릿 리터럴 내에서 사용해야 된다. 일반 문자열에서의 표현식 삽입은 문자열로 취급된다.

라인 피드와 캐리지 리턴

개행 문자에는 라인 피드(LF)와 캐리지 리턴(CR)이 있다.
라인 피드는 커서를 정지한 상태에서 종이를 한 줄 올리는 것, 캐리지 리턴은 종이를 움직이지 않고 커서를 맨 앞줄로 이동하는 것이다.
현대의 운영체제는 서로 다른 체계의 방식을 사용하기 때문에 서로 다른 운영체제에서 작성한 텍스트 파일은 인식하지 못한다.
다만 대부분의 텍스트 에디터는 운영체제에 맞게 개행 문자를 자동으로 변환해준다.

불리언 타입

불리언 타입의 값은 논리적 참, 거짓을 나타내는 true와 false뿐이다.

undefined 타입

undefined 타입의 값은 undefined가 유일하다.
변수 선언에 의해 확보된 메모리 공간을 처음 할당이 이뤄질 때까지 빈 상태로 내버려두지 않고 JS 엔진이 undefined로 초기화한다.
자바스크립트 엔진이 변수를 초기화하는 데 사용하는 undefined를 개발자의 의도적으로 변수에 할당한다면 undefined의 본래 취지와 어긋날뿐더러 혼란을 줄 수 있기 때문에 지양하자. -> 대신 null을 할당한다.

선언과 정의

undefined의 정의되지 않았다는 뜻은 변수에 값을 할당하여 변수의 실체를 명확히 하지 않았다는 것
C에서 선언과 정의는 컴파일러에게 식별자의 존재만 알리는 것은 선언이고, 컴파일러가 변수를 생성해서 식별자와 메모리 주소가 연결되면 정의로 구분한다.

null 타입

null 타입의 값은 null이 유일하다.

  • 프로그래밍 언어에서 null은 변수에 값이 없다는 것을 의도적으로 명시할 때 사용한다.
  • 변수에 null을 할당하는 것은 변수가 이전에 참조하던 값을 더 이상 참조하지 않겠다는 의미다.
    • 이는 이전에 할당되어 있던 값에 대한 참조를 명시적으로 제거하는 것을 의미
  • 함수가 유효한 값을 반환할 수 없는 경우 명시적으로 null을 반환하기도 한다.
    • ex) document.querySelector 메서드는 조건에 부합하는 HTML 요소를 검색할 수 없는 경우 에러 대신 null을 반환

심벌 타입

ES6에서 추가한 7번째 타입으로, 변경 불가능한 원시 타입의 값이다.

  • 다른 값과 중복되지 않는 유일무이한 값
  • 주로 이름이 충돌할 위험이 없는 객체의 유일한 프로퍼티 키를 만들기 위해 사용
  • 심벌 이외의 원시 값은 리터럴을 통해 생성하지만 심벌은 Symbol 함수를 호출해 생성한다.
    • 이때 생성된 심벌 값은 외부에 노출되지 않으며, 다른 값과 절대 중복되지 않는다.
// 심벌 값 생성
var key = Symbol("key");
console.log(typeof key); // symbol

객체 타입

자바스크립트를 이루고 있는 거의 모든 것이 객체
앞의 6가지 데이터 타입 이외의 값은 모두 객체 타입이다.

데이터 타입의 필요성

데이터 타입에 의한 메모리 공간의 확보와 참조

  • 메모리에 값을 저장하려면 먼저 확보해야 할 메모리 공간의 크기를 결정해야 한다.
    • 자바스크립트 엔진은 데이터 타입에 따라 정해진 크기의 메모리 공간을 확보한다.
  • 값을 참조하려면 한 번에 읽어 들여야 할 메모리 공간의 크기, 즉 메모리 셀의 개수를 알아야 한다.
    • 자바스크립트 엔진은 데이터 타입에 따라 정해진 크기 단위로 메모리 공간에 저장된 값을 읽어들인다.
    • 저장된 단위로 읽어 들이지 않으면 값이 훼손된다.

데이터 타입에 의한 값의 해석

메모리에 저장된 값은 데이터 타입에 따라 다르게 해석될 수 있다. -> 2진수, 즉 비트의 나열로 저장되어 있기 때문에

  • 참조 시에 변수에 할당된 값의 타입을 통해 메모리 공간의 주소에서 읽어 들인 2진수를 해석한다.

정리하면 데이터 타입이 필요한 이유는

  1. 값을 저장할 때 확보해야 하는 메모리 공간의 크기를 결정하기 위해
  2. 값을 참조할 때 한 번에 읽어 들여야 할 메모리 공간의 크기를 결정하기 위해
  3. 메모리에서 읽어 들인 2진수를 어떻게 해석할지를 결정하기 위해

동적 타이핑

동적 타입 언어와 정적 타입 언어

정적 타입 언어 특징

  • 정적 타입 언어는 데이터 타입을 사전에 선언해야 한다.
  • 변수의 타입을 변경할 수 없으며, 선언한 타입에 맞는 값만 할당할 수 있다.
  • 컴파일 시점에 타입 체크를 수행하여 통과하지 못하면 에러를 발생시키고 프로그램 실행 자체를 막는다.

자바스크립트의 타입

  • 자바스크립트의 변수는 선언이 아닌 할당에 의해 타입이 결정된다.
  • 재할당에 의해 변수의 타입은 언제든지 동적으로 변할 수 있다. -> 동적 타이핑

변수는 타입을 가질까?
기본적으로 변수는 타입을 갖지 않지만, 값이 타입을 가지기 때문에 현재 변수에 할당되어 있는 값에 의해 변수의 타입이 동적으로 결정된다.

동적 타입 언어와 변수

  • 변수 값은 언제든지 변경될 수 있기 때문에 복잡한 프로그램에서는 변화하는 변수 값을 추적하기 어려울 수 있다.
  • 값의 변경에 의해 변수의 타입도 언제든지 변경될 수 있기 때문에 동적 타입 언어의 변수는 값을 확인하기 전에 타입을 확신할 수 없다.
  • 자바스크립트 엔진에 의해 암묵적으로 타입이 자동으로 변환되기도 한다. -> 동적 타입 언어는 유연성은 높지만 신뢰성은 떨어진다.

변수를 사용할 때 주의사항

  • 변수는 꼭 필요한 경우에 한해 제한적으로 사용한다.
  • 변수의 유효 범위(스코프)는 최대한 좁게 만들어 변수의 부작용을 억제한다.
  • 전역 변수는 최대한 사용하지 않도록 한다.
  • 변수보다는 상수를 사용해 값의 변경을 억제한다.
  • 변수 이름은 변수의 목적이나 의미를 파악할 수 있도록 네이밍한다.

7장 연산자

하나 이상의 표현식을 대상으로 산술, 할당, 비교, 논리, 타입, 지수 연산 등을 수행해 하나의 값을 만든다.
이때 연산의 대상을 피연산자라 한다. 피연산자는 값으로 평가될 수 있는 표현식이어야 한다.

산술 연산자

피연산자를 대상으로 수학적 계산을 수행해 새로운 숫자 값을 만든다.
산술 연산이 불가능한 경우, NaN을 반환한다.

이항 산술 연산자

  • ex) +, -, *, /, %
  • 2개의 피연산자를 산술 연산하여 숫자 값을 만든다.
  • 모든 이항 산술 연산자는 피연산자의 값을 변경하는 부수 효과가 없다.
    • 어떤 산술 연산을 해도 피연산자의 값이 바뀌는 경우가 없고 새로운 값을 만든다.

단항 산술 연산자

  • ex) +, -, ++, –
  • 1개의 피연산자를 산술 연산하여 숫자 값을 만든다.
  • 증가/감소 연산자는 피연산자의 값을 변경하는 부수 효과가 있다.
    • 증가/감소 연산을 하면 피연산자의 값을 변경하는 암묵적 할당이 이뤄진다.
  • 증가/감소 연산자는 위치에 의미가 있다.
    • 피연산자 앞에 위치할 경우 피연산자의 값을 변화시킨 후, 다른 연산 수행
    • 피연산자 뒤에 위치할 경우 다른 연산을 수행한 후, 피연산자의 값을 변화
  • 숫자 타입이 아닌 피연산자에 + 단항 연산자를 사용하면 숫자 타입으로 변환하여 반환한다.
    • 피연산자를 변경하는 것은 아니고 변환한 값을 생성해서 반환하므로 부수 효과는 없다.
    • 단항 연산자는 피연산자의 부호를 반전한 값을 반환하는데, + 단항 연산자와 마찬가지로 숫자 타입이 아닌 피연산자에 사용하면 피연산자를 숫자 타입으로 변환하여 반환한다.
  • +나 - 단항 연산자를 숫자로 타입 변환할 수 없는 경우 NaN을 반환한다.

문자열 연결 연산자

    • 연산자는 피연산자 중 하나 이상이 문자열인 경우 문자열 연결 연산자로 동작한다.
// 문자열 연결 연산자
"1" + 2; //  '12'
1 + "2"; //  '12'
// 산술 연산자
1 + 2; //  3
// true는 1로 타입 변환된다.
1 + true; //  2
// false는 0으로 타입 변환된다.
1 + false; //  1
// null은 0으로 타입 변환된다.
1 + null; //  1
// undefined는 숫자로 타입 변환되지 않는다.
+undefined; //  NaN
1 + undefined; //  NaN
  • JS 엔진에 의해 암묵적으로 타입이 자동 변환되기도 한다.
  • 이를 암묵적 타입 변환 또는 타입 강제 변환이라고 한다.

할당 연산자

  • ex) =, +=, -=, *=, /=, %=
  • 우항에 있는 피연산자의 평가 결과를 좌항에 있는 변수에 할당한다.
  • 좌항의 변수에 값을 할당하므로 변수 값이 변하는 부수 효과가 있다.
  • 할당문은 값으로 평가되는 표현식인 문으로서 할당된 값으로 평가된다.

비교 연산자

좌항과 우항의 피연산자를 비교한 다음 그 결과를 불리언 값으로 반환한다.

동등/일치 비교 연산자

  • ex) ==, ===, !=, !==
  • 동등 비교 연산자는 느슨한 비교를 하지만 일치 비교 연산자는 엄격한 비교를 한다.
  • 동등 비교 연산자는 비교할 때 먼저 암묵적 타입 변환을 통해 타입을 일치시킨 후 같은 값인지 비교한다.
    • 좌항과 우항의 피연산자가 타입이 다르더라도 암묵적 타입 변환 후에 같은 값일 수 있다면 true를 반환한다.
    • 예측하기 어려운 결과를 만들어 내기 때문에 사용하지 않는 편이 좋다.
  • 일치 비교 연산자는 좌항과 우항의 피연산자가 타입도 같고 값도 같은 경우에 한하여 true를 반환한다.
  • 다만 NaN은 자신과 일치하지 않는 유일한 값이다. 숫자가 NaN인지 조사하려면 빌트인 함수 Number.isNaN을 사용한다.
  • 양의 0과 음의 0을 비교하면 true를 반환한다.
  • Object.is 메서드는 예측 가능한 정확한 비교 결과를 반환한다. 그 외에는 일치 비교 연산자와 동일하게 동작
// Number.isNaN 함수는 지정한 값이 NaN인지 확인하고 그 결과를 불리언 값으로 반환한다.
Number.isNaN(NaN); // true
Number.isNaN(10); // false
Number.isNaN(1 + undefined); // true

-0 === +0; // true
Object.is(-0, +0); // false
NaN === NaN; // false
Object.is(NaN, NaN); // true

대소 관계 비교 연산자

  • ex) >, <, >=, <=
  • 피연산자의 크기를 비교하여 불리언 값을 반환

삼항 조건 연산자

조건식 ? 조건식 true일 때 반환할 값 : 조건식 false일 때 반환할 값

  • 조건식의 평가 결과에 따라 반환할 값을 결정한다. 부수 효과는 없다.
  • ? 앞의 피연산자는 불리언 타입의 값으로 평가될 표현식인대, 평가 결과가 불리언 값이 아니면 불리언 값으로 암묵적 타입 변환된다.
  • 삼항 조건 연산자 표현식은 값으로 평가할 수 있는 표현식인 문이다.
  • 반면, if else 문은 값처럼 사용할 수 없다.

논리 연산자

  • ex) ||, &&, !
  • 우항과 좌항의 피연산자(부정 논리 연산자의 경우 우항의 피연산자)를 논리 연산한다.
  • 논리 부정 연산자는 언제나 불리언 값을 반환한다.
  • 피연산자가 반드시 불리언 값일 필요는 없다. 불리언 값이 아니면 불리언 타입으로 암묵적 타입 변환된다.
  • 논리합, 논리곱 연산자 표현식의 평가 결과는 불리언 값이 아닐 수도 있다.
    • 언제나 2개의 피연산자 중 어느 한쪽으로 평가된다.

쉼표 연산자

왼쪽 피연산자부터 차례대로 피연산자를 평가하고 마지막 피연산자의 평가가 끝나면 마지막 피연산자의 평가 결과를 반환한다.

var a, b, c;
(a = 1), (b = 2), (c = 3); // 3

그룹 연산자

소괄호로 피연산자를 감싸는 그룹 연산자는 자신의 피연산자인 표현식을 가장 먼저 평가한다.
그룹 연산자를 사용하여 우선순위를 조절할 수 있다.

10 * 2 + 3; // 23
// 그룹 연산자를 사용하여 우선순위를 조절
10 * (2 + 3); // 50

typeof 연산자

  • typeof 연산자는 피연산자의 데이터 타입을 문자열로 반환한다.
  • 7가지 문자열 “string”, “number”, “boolean”, “undefined”, “symbol”, “object”, “function” 중 하나를 반환한다.
  • “null”을 반환하는 경우는 없으며 함수는 “function”을 반환한다. 이처럼 typeof 연산자가 반환하는 문자열은 7개의 데이터 타입과 정확히 일치하지는 않는다.
  • null 값을 연산해보면 “object”를 반환한다. 자바스크립트의 첫 번째 버전의 버그다.
  • typeof 연산자로 선언하지 않은 식별자를 연산해보면 ReferenceError가 발생하지 않고, undefined를 반환한다.

지수 연산자

  • 좌항의 피연산자를 밑으로, 우항의 피연산자를 지수로 거듭 제곱하여 숫자 값을 반환한다.
  • 지수 연산자는 ES7에 도입되었는데, 이전에는 Math.pow 메서드를 사용했다.
  • 음수의 거듭제곱을 밑으로 사용해 계산하려면 괄호로 묶어야 한다.
  • 지수 연산자도 다른 산술 연산자와 마찬가지로 할당 연산자와 함께 사용할 수 있다.
2 ** 3; // 8

var x = 5;
x **= 2; // 25

연산자의 부수 효과

  • 대부분의 연산자는 다른 코드에 영향을 주지 않지만, 일부 연산자는 다른 코드에 영향을 주는 부수 효과가 있다.
  • ex) 할당 연산자, 증감 연산자, delete 연산자

연산자 우선 순위

여러 개의 연산자로 이뤄진 문이 실행될 때 연산자가 실행되는 순서. 우선순위가 높을수록 먼저 실행된다.

연산자 결합 순서

연산자의 어느쪽부터 평가를 수행할 것인지를 나타내는 순서를 말한다.

8장 제어문

조건에 따라 코드 블록을 실행하거나 반복 실행할 때 사용한다.
일반적으로 코드는 위에서 아래 방향으로 순차적으로 실행되지만 제어문을 사용하면 코드의 실행 흐름을 인위적으로 제어할 수 있다.
때문에 직관적인 코드의 흐름을 혼란스럽게 만들어서 가독성을 해치는 단점이 있다.

블록문

0개 이상의 문을 중괄호를 묶은 것으로, 코드 블록 또는 블록이라고 부르기도 한다.
자바스크립트는 블록문을 하나의 실행 단위로 취급한다.
블록문은 언제나 문의 종료를 의미하는 자체 종결성을 갖기 때문에 불록문의 끝에는 세미콜론을 붙이지 않는다.

조건문

주어진 조건식의 평가 결과에 따라 코드 블록의 실행을 결정한다.
조건식은 불리언 값으로 평가될 수 있는 표현식이다.

if… else 문

if 문의 조건식이 불리언 값이 아닌 값으로 평가되면 JS 엔진에 의해 암묵적으로 불리언 값으로 강제 변환된다.

switch 문

  • 주어진 표현식을 평가하여 그 값과 일치하는 표현식을 갖는 case 문으로 실행 흐름을 옮긴다.
  • case 문은 상황을 의미하는 표현식을 지정하고 콜론으로 마친다. 그리고 그 뒤에 실행할 문들을 위치시킨다.
  • switch 문의 표현식과 일치하는 case 문이 없다면 실행 순서는 default 문으로 이동한다. (선택사항)
  • case문으로 실행 흐름이 이동하여 문을 실행하고 break로 switch문을 탈출하지 않으면 switch문이 끝날 때까지 이후 모든 문을 실행한다. 이를 폴스루라 한다.
  • 조건이 너무 많아서 switch문을 사용했을 때 가독성이 더 좋은 경우 사용하자

반복문

  • 조건식의 평가 결과가 참인 경우 코드 블록을 실행한다.
  • 그 후 조건식을 다시 평가하여 여전히 참인 경우 코드 블록을 다시 실행한다. (조건식이 거짓일 때까지 반복)

for 문

  • for 문의 변수 선언문, 조건식, 증감식은 모두 옵션이므로 반드시 사용할 필요는 없다.
  • 단, 어떤 식도 선언하지 않으면 무한루프가 된다.
for (;;) { ... }

while 문

  • while 문은 주어진 조건식의 평가 결과가 참이면 코드 블록을 계속해서 반복 실행한다.
  • for 문은 반복 횟수가 명확할 때 주로 사용하고, while 문은 반복 횟수가 명확하지 않을 때 주로 사용한다.
  • 조건식의 결과가 불리언 값이 아니면 불리언 값으로 강제 변환하여 논리적 참, 거짓을 구별한다.

break 문

  • 레이블 문, 반복문, switch 문의 코드 블록을 탈출한다.
  • 이외에 break 문을 사용하면 문법 에러가 발생한다.
  • 레이블 문은 식별자가 붙은 문을 말한다.
foo: console.log("foo");
  • 레이블 문은 프로그램 실행 순서를 제어하는 데 사용한다.
  • switch 문의 case 문과 default 문도 레이블 문이다.
  • 레이블 문을 탈출하려면 break 문에 레이블 식별자를 지정한다.
  • 레이블 문을 사용하면 프로그램의 흐름이 복잡해져서 가독성이 나빠지고 오류를 발생시킬 가능성이 높아지기 때문에 중첩된 for문 정도에서만 사용한다.

continue 문

  • 반복문의 코드 블록 실행을 현 지점에서 중단하고 반복문의 증감식으로 실행 흐름을 이동시킨다.
  • if문 내에서 실행되어야 할 코드가 길다면 continue를 사용하면 코드 depth를 한 단계 낮출 수 있다. (코딩스킬)

9장 타입 변환과 단축 평가

타입 변환이란?

개발자가 의도적으로 값의 타입을 변환하는 것을 명시적 타입 변환 또는 타입 캐스팅이라 한다.
개발자의 의도와 상관없이 표현식을 평가하는 도중에 JS 엔진에 의해 암묵적으로 타입이 자동 변환되는 것을 암묵적 타입 변환또는 강제 타입 변환이라 한다.

  • 명시적 타입 변환이나 암묵적 타입 변환이 기존 원시 값을 직접 변경하는 것은 아님
    • 원시 값은 변경 불가능한 값
  • 타입 변환이란 기존 원시 값을 사용해 다른 타입의 새로운 원시 값을 생성하는 것이다.

암묵적 타입 변환

JS 엔진은 표현식을 평가할 때 개발자의 의도와는 상관없이 코드의 문맥을 고려해 암묵적으로 데이터 타입을 강제 변환할 때가 있다.
암묵적 타입 변환이 발생하면 문자열, 숫자, 불리언과 같은 원시 타입 중 하나로 타입을 자동 변환한다.

문자열 타입으로 변환

1 + "2"; // '12'
  • 피연산자 중 하나 이상이 문자열이므로 문자열 연결 연산자로 동작한다.
  • 문자열 연결 연산자의 모든 피연산자는 문맥상 모두 문자열 타입이어야 한다.
    • 따라서 문자열이 아닌 피연산자를 문자열로 암묵적 타입 변환한다.
  • 연산자 표현식의 피연산자만이 암묵적으로 타입 변환되는 것은 아니고, 예를 들어 템플릿 리터럴에서 표현식 삽입은 평가 결과를 문자열 타입으로 암묵적 타입 변환한다.

숫자 타입으로 변환

1 - "1"; // 0
1 * "10"; // 10
1 / "one"; // NaN
  • 모두 산술 연산자이므로 모든 피연산자는 코드 문맥상 모두 숫자 타입이어야 한다.
  • JS 엔진은 산술 연산자 표현식을 평가하기 위해 산술 연산자의 피연산자 중에 숫자 타입이 아닌 피연산자를 숫자 타입으로 암묵적 타입 변환한다.
  • 피연산자를 숫자 타입으로 변환할 수 없는 경우는 산술 연산을 수행할 수 없으므로 NaN이 표현식의 평가 결과가 된다.
"1" > 0; // true
  • 비교 연산자는 피연산자의 크기를 비교하므로 모든 피연산자의 코드는 문맥상 숫자 타입이어야 한다.
    • 암묵적 타입 변환 발생
    • 단항 연산자는 피연산자가 숫자 타입의 값이 아니면 숫자 타입의 값으로 암묵적 타입 변환을 수행한다.
  • 빈 문자열 ,빈 배열, null, false는 0으로, true는 1로 변환된다.
  • 객체와 빈 배열이 아닌 배열, undefined는 변환되지 않아 NaN이 된다.

불리언 타입으로 변환

  • if문이나 for 문과 같은 제어문 또는 삼항 조건 연산자의 조건식은 불리언 값으로 평가되어야 하는 표현식이다.
  • JS 엔진은 조건식의 평가 결과를 불리언 타입으로 암묵적 타입 변환한다.
  • JS 엔진은 불리언 타입이 아닌 값을 Truthy 값 (참으로 평가되는 값) 또는 Falsy 값(거짓으로 평가되는 값)으로 구분한다.
  • Falsy 값 이외의 모든 값은 모두 true로 평가되는 Truthy 값이다.

Falsy 값

  • false
  • 0, -0
  • ’’
  • undefined
  • null
  • NaN

명시적 타입 변환

개발자의 의도에 따라 명시적으로 타입을 변경하는 방법은 다양하다.

  • 표준 빌트인 생성자 함수(String, Boolean, Number)를 new 연산자 없이 호출하는 방법
  • 빌트인 메서드를 사용하는 방법
  • 암묵적 타입 변환을 이용하는 방법

  • 표준 빌트인 생성자 함수와 표준 빌트인 메서드는 JS에서 기본 제공하는 함수다.
  • 표준 빌트인 생성자 함수는 객체를 생성하기 위한 함수이며 new 연산자와 함께 호출한다.
  • 표준 빌트인 메서드는 자바스크립트에서 기본 제공하는 빌트인 객체의 메서드다.

문자열 타입으로 변환

  1. String 생성자 함수를 new 없이 호출하는 방법
  2. Object,prototype.toString 메서드를 사용하는 방법
  3. 문자열 연결 연산자를 이용하는 방법

숫자 타입으로 변환

  1. Number 생성자 함수를 new 연산자 없이 호출하는 방법
  2. parseInt, parseFloat 함수를 사용하는 방법(문자열만 숫자 타입으로 변환 가능)
    • 단항 산술 연산자를 이용하는 방법
    • 산술 연산자를 이용하는 방법

불리언 타입으로 변환

  1. Boolean 생성자 함수를 new 연산자 없이 호출하는 방법
  2. ! 부정 논리 연산자를 두 번 사용하는 방법

단축 평가

논리 연산자를 사용한 단축 평가

논리합 또는 논리곱 연산자 표현식의 평가 결과는 불리언 값이 아닐 수도 있다. 논리합 또는 논리곱 연산자 표현식은 언제나 2개의 피연산자 중 어느 한쪽으로 평가된다.

"Cat" && "Dog"; // "Dog"
  • 논리곱(&&) 연산자는 두 개의 피연산자가 모두 true로 평가될 때 true를 반환
  • 첫 번째 피연산자는 Truthy한 값이므로 true로 평가된다.
  • 두 번째 피연산자까지 평가해 보아야 위 표현식을 평가할 수 있다.
    • 즉 두 번째 피연산자가 논리곱 연산자 표현식의 평가 결과를 결정한다.
  • 논리합(||) 연산자는 두 개의 피연산자 중 하나만 true로 평가되어도 true를 반환한다.
    • 첫 번째 피연산자가 Truthy한 값이므로, 논리 연산의 결과를 결정한 첫 번째 피연산자를 반환한다.

단축 평가 규칙

true || anything : true 반환
false || anything : anything 반환
true && anything : anything 반환
false && anything : false 반환

단축 평가 활용

  • 어떤 조건이 Truthy 값일 때 무언가를 해야 한다면 논리곱(&&) 연산자 표현식으로 대체할 수 있다.
  • 어떤 조건이 Falsy 값일 때 무언가를 해야 한다면 논리합(||) 연산자 표현식으로 대체할 수 있다.
var done = true;
var message = "";

if (done) message = "완료";

message = done && "완료";

객체를 가리키기를 기대하는 변수가 null 또는 undefined가 아닌지 확인하고 프로퍼티를 참조할 때

객체를 가리키기를 기대하는 변수의 값이 객체가 아니라 null 또는 undefined인 경우 객체의 프로퍼티를 참조하면 타입 에러가 발생한다.

var elem = null;

val value = elem && elem.value;

함수 매개변수에 기본값을 설정할 때

함수를 호출할 때 인수를 전달하지 않으면 매개변수에는 undefined가 할당된다. 이때 단축 평가를 사용해 매개변수의 기본값을 설정하면 undefined로 인해 발생할 수 있는 에러를 방지할 수 있다.

function getStringLength(str) {
  str = str || "";
  return str.length;
}
  • 매개변수를 넘기지 않더라도 기본값이 설정된다.

옵셔널 체이닝 연산자

ES11에서 도입한 옵셔널 체이닝 연산자 ?.는 좌항의 피연산자가 null 또는 undefined인 경우 undefined를 변환하고, 그렇지 않으면 우항의 프로퍼티 참조를 이어간다.

var elem = null;

var value = elem?.value;
console.log(value); // undefined
  • 옵셔널 체이닝 연산자 도입 이전에는 논리곱 연산자를 사용한 단축 평가를 통해 변수가 null 또는 undefined인지 확인했다.
  • 논리 연산자 &&은 좌항 피연산자가 false로 평가되는 값이면 좌항 피연산자를 그대로 반환한다.
    • 하지만 0이나 ‘‘은 객체로 평가될 때도 있다.
  • 하지만 옵셔널 체이닝 연산자 ?.는 좌항 피연산자가 Falsy 값이라도 null 또는 undefined가 아니면 우항의 프로퍼티 참조를 이어간다.

null 병합 연산자

ES11에서 도입된 null 병합 연산자 ??는 좌항의 피연산자가 null 또는 undefined인 경우 우항의 피연산자를 반환하고, 그렇지 않으면 좌항의 피연산자를 반환한다.

var foo = null ?? "default string";
console.log(foo); // "default string"
  • null 병합 연산자 ??는 변수에 기본값을 설정할 때 유용하다.
  • null 병합 연산자 도입 이전에는 논리 연산자 ||를 사용한 단축 평가를 통해 변수에 기본값을 설정했다.
  • 옵셔널 체이닝과 때와 마찬가지로 단축 평가의 경우 좌항의 피연산자가 Falsy 값인 0이나 ‘‘도 기본값으로서 유효하다면 예기치 않은 동작이 발생할 수 있다.
  • 하지만 null 병합 연산자는 좌항의 피연산자가 Falsy 값이라도 null 또는 undefined가 아니면 좌항의 피연산자를 그대로 반환한다.

출처

모던 자바스크립트 Deep Dive

0908 TIL

43일 차

1교시

모던한 인터프리터들은 컴파일러를 내장하고 있다. 하지만 인터프리터는 실행 파일을 생성하지 않기 때문에 컴파일러와 다르다.

2교시

처음에 vscode에 1을 쓰면 그것은 리터럴이다. 실행시키면 평가되어 값이 된다.

a.js를 실행시키면 자바스크립트 엔진이 텍스트를 읽어들인다.
그리고 리터럴을 파싱한다.

리터럴 1은 (실제 값이 아닌 표기법) 평가되어 실제 숫자 값 1이 된다. 표현식인 문을 평가하는 것

다른 언어와 다르게 프로그래밍 언어는 문법 지식을 모르면 명령 자체를 할 수없다.
더해서 문법을 필요할 때 꺼내써야 할 줄 알아야 한다. (코딩 스킬) 전제는 문법이 머리속에 있어야 한다는 것

컴퓨터 사이언스에서 부수 효과는 좋지 않은 것이다. 부수 효과를 억제해야 한다.

3교시

console.log() 는 표준 ECMAScript에 써있지 않다. 그런데 node.js, 브라우저 모두에서 동작한다. 즉, node.js에서 출력하는 console.log()와 브라우저에서 출력하는 console.log()가 다를 경우가 있다.

자바스크립트는 숫자형 타입을 8바이트로 고정해서 사용한다. 또한 문자열을 배열이나 객체가 아닌 원시타입으로 문자열을 사용할 수 있도록 해준다. 자바스크립트의 어려운 점은 그 문자열에 .(참조연산자)을 찍으면 암묵적으로 객체로 만들어준다.

백틱은 개행을 해야할 때, 표현식을 삽입해야할 때만 사용한다.

  • 일관성 측면
  • 파서의 추가 동작으로 인한 미세한 성능 차이

식별자 선언과 동시에 초기화를 해주는 습관을 들여야 한다.

의도적 부재 변수에 값이 없다는 것을 의도적으로 명시

일반적인 변수, 함수명은 카멜케이스 클래스나 생성자는 파스칼 케이스 자바스크립트 빌트인 함수가 위의 케이스를 따르기 때문에 컨벤션을 따른다.

자바스크립트는 객체 프로퍼티의 키가 중복되는 것을 허용한다. 이 때 키가 지워지는 것을 방지하기 위해 심볼 값을 주면 다른 프로퍼티를 덮어쓰지 않는다.

식별자는 사실 메모리 주소 + 최신에 들어온 값의 데이터 타입 정보도 관리해야한다.

동적타이핑? 다른 타입을 할당할 수 있는 것 유연하지만 안정성이 떨어진다.

재할당을 최대한 지양하라 어떻게 지양하는가? 새로 변수를 만들어라 새로 변수를 많이 만들면 안좋다고 하지 않았느냐? 변수를 얼른 소멸시켜라. -> 스코프를 최대한 좁게

const -> 재할당이 금지된 변수 그래서 웬만하면 변수를 생성할 때 const로 생성하는 것이다. 라이브러리, 프레임워크 -> 왜쓰는지 알고 써라

변수이름은 명사, 함수이름은 동사가 기본

a++, a– 증감 연산자는 안티패턴 일단 앞에 연산자를 붙였을 때랑, 뒤에 붙였을 때 동작이 다르다. 그리고 부수 효과가 발생한다. -> a = a + 1;로 바꿔쓰자

delete도 안티패턴 프로퍼티를 지울 이유가 없다. 애초에 생성하지 않으면 될 것

쉼표 연산자도 안티패턴

함수 호출로 해도 되고 연산자로 해도 되는 것이 있으면 연산자로 해야 한다. -> 함수 호출은 인수로 어떤 값을 줄 지 알아야 하고 순서도 알아야 한다.

a = 1;

  1. a라는 식별자가 존재하는지 찾아야 한다.
  2. 값을 메모리에 저장하고, a로 그 선두 주소를 가리킨다. 

알고리즘

sort()

  • a, b로 연산한 반환값이 0보다 작으면 a를 먼저, 0보다 크면 b를 먼저 정렬한다.

0909 TIL

44일 차

마음가짐 다잡기, 되돌아보기

하루가 찝찝한 이유 -> 효율적으로 시간을 쓰지 못하고 마음만 급하다

  1. 잠을 적게 자는 것이 과연 하루를 효율적으로 보내는데 좋을까? -> 6시간은 자자
  2. 깨어있는 동안 나는 하루를 알차게 보내나? -> 시간을 분할해서 사용하자
  3. 어떻게 분할할 것인가? -> 시간별 목표를 정해서 목표를 달성하자.

현 시점에서 중요한 요소는 자바스크립트와 알고리즘 알고리즘을 위해 평일에 3시간, 주말에 7시간 30분씩 시간을 투자하자

자바스크립트 학습 중에서 현재 문제점은 첫 번째 예습에 너무 오랜 시간이 걸린다는 것이다.

  1. 집중력의 문제
  2. 첫 예습의 속도를 단축하고, 복습 위주의 학습을 하자.
  • 러버덕 시간에 조금 더 집중하자
  • 대중 교통으로 이동하는 시간에는 알고리즘 문제 어설프게 풀지 말고, 무조건 복습 -> 효율이 좋다.
  • 오후 자습 시간에는 무조건 자바스크립트 예습. 남이 뭐하든 신경 끄기
  • 잡담할 시간에 공부해라. 집중력 흐트러진다.
  • 자기 전 or 일찍 일어나 1시간 30분 알고리즘 학습
  • 저녁 자습 때 알고리즘 1시간 30분 활용할 것

0910 TIL

45일차

1교시

리턴문은 맨 마지막에 위치해야 한다.

가비지 컬렉터는 제품이 아니다. 쓰레기를 수집하러 다니는 프로그램이 가비지 컬렉터
가비지 컬렉터가 메모리를 언제 해제하는지 주기를 알 수 없는 이유는 사람들마다 효율적이라고 생각하는 방식이 다르기 때문이다.
실행컨텍스트 객체에 저장된 함수 스코프 단위로 지워준다.

2교시

초창기 프로그래밍은 다 절차형, 명령형
명령형은 어떤 값을 만들어내는 절차를 나열한 것이다.

함수를 쪼개야 테스트하기가 좋은데, 함수가 하나의 일만 하고 작아야 하고, 순수 함수여야 한다.
그리고 함수명을 짓기가 힘들다면 쪼개야 한다.

function sumEven(numbers) {}

eslint에서 빨간 줄 링크를 타고 들어가면 eslint 페이지로 가게 되는데, 그것을 읽어보는 것이 큰 도움이 된다.

  • 함수명과 매개변수명은 명확하게
  • 제어문을 쓰다보면 아이러니하게 변수를 더 많이 쓰게 되는 상황이 발생한다.
  • 그리고 문들의 흐름을 인위적으로 조작하여 가독성을 해친다.

  • 이러한 절차형 언어의 단점을 해결한 것이 선언형인 리액트
  • 선언형이라는 것 안에 함수형이 들어가있다.
  • 선언형은 절차가 숨겨져있고, 결국 뭘 하고 싶은지를 영어 쓰듯이 한다.
  • 자바스크립트는 객체 지향보다는 함수형으로 작성하는 것이 아름답다.
  • reduce, map, filter는 함수형 언어에서 꼭 나온다.
function sumEven(numbers) {
  return numbers.reduce((acc, cur) => (cur % 2 === 0 ? acc + cur : acc), 0);
}
  • 이렇게 쓰면 한 줄에 해결할 수 있다.
  • 내부적으로는 for문이 돌아간다.
  • 이러한 로직이 내부에는 감춰지는 것 -> 선언형

3교시

  • var 키워드는 함수에서 선언해도 전역으로 참조 가능하다.
  • 레이블 문은 사용 가능하긴 하지만 보통 함수에서 실행하기 때문에 return을 사용하면 된다.

  • 타입 변환에서 중요한 것은 문맥이 있다는 것

  • String() 생성자 함수는 시맨틱에 어긋난다.
  • 스트링은 + ‘’, 숫자형은 + 단항 연산자, 불리언은 !!

  • 삼항 연산자는 if else 문을 대체
  • if 문을 대체하는 것은 && 와  
// 단축 평가를 사용한 매개변수의 기본값 설정
function getStringLength(str) {
  str = str || "";
  return str.length;
}
getStringLength(); // 0
getStringLength("hi"); // 2
// ES6의 매개변수의 기본값 설정
function getStringLength(str = "") {
  return str.length;
}
getStringLength(); // 0
getStringLength("hi"); // 2

과연 사용자가 잘못된 인수로 호출했을 때 기본값을 빈 문자열로 해주는 것이 옳은 것인가?
-> 에러를 발생시켜야 한다.

4교시

하나의 값을 저장하는 방식이 변수
여러개의 값을 저장하는 것이 자료구조 ex) 배열
배열은 인덱스를 가지고 있다.
배열은 값에 접근하기 위해 인덱스를 사용하고, 객체는 값에 접근하기 위해 키를 사용한다.
배열은 순서가 있고, 객체는 순서가 없다는게 가장 큰 차이점
그리고 객체는 행위(메서드)를 포함시킬 수 있다.
for문에 딱 맞는 자료구조가 배열

유사배열
함수로 배열을 받아서 쓰면 되는데 왜 메서드를 포함 시켜서 사용하느냐?
-> 강사님의 주관
자바스크립트는 객체 지향 보다는 함수가 낫다.
왜? -> 앞으로 차차 많은 이야기를 나누게 될 것

객체지향의 단점은 설계에 맞춰서 개발을 진행하는데 요구사항이 변했을 때 빠르게 대응하기가 힘들다는 것이다.

5교시

프로퍼티 키는 사실 문자열 또는 심볼이다.
프로퍼티 값으로 함수가 와도 사실 메서드가 아니다 -> 나중에 설명할 것

순수함수는 테스트하기가 쉽다.
객체지향의 메서드들은 대부분 비순수함수다.

키워드는 명령어. 예약어는 내부적으로 사용하는 것

0911

13장 스코프

스코프란?

변수는 자신이 선언된 위치에 의해 자신이 유효한 범위, 즉 다른 코드가 변수 자신을 참조할 수 있는 범위가 결정된다.
모든 식별자는 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할 수 있는 유효 범위가 결정된다.
이를 스코프라고 한다. 즉, 스코프는 식별자가 유효한 범위를 말한다.

코드의 가장 바깥 영역과 함수 내부에 같은 이름을 갖는 x 변수를 선언했다고 가정해보자

  • JS엔진은 이름이 같은 두 개의 변수 중에서 어떤 변수를 참조해야 할 것인지를 결정해야 하는데, 이를 식별자 결정이라고 한다.
  • 스코프를 통해 이를 결정하기 때문에 스코프는 엔진이 식별자를 검색할 때 사용하는 규칙이라고도 할 수 있다.
  • JS 엔진은 코드를 실행할 때 코드의 문맥을 고려한다. 코드가 어디서 실행되며 주변에 어떤 코드가 있는지에 따라 다른 결과를 만들어 낸다.
    • “코드가 어디서 실행되며 주변에 어떤 코드가 있는지”를 렉시컬 환경이라고 부른다.
    • 코드의 문맥은 렉시컬 환경으로 이뤄지고 이를 구현한 것이 실행 컨텍스트이며, 모든 코드는 실행 컨텍스트에서 평가되고 실행되고 평가된다.
  • 스코프를 통해 식별자인 변수 이름의 충돌을 방지하여 같은 이름의 변수를 사용할 수 있게 한다.
  • 스코프 내에서 식별자는 유일해야 하지만 다른 스코프에는 같은 이름의 식별자를 사용할 수 있다. 즉, 스코프는 네임스페이스이다.

var 키워드로 선언한 변수의 중복 선언

function foo() {
  var x = 1;
  // var 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언을 허용한다.
  // 아래 변수 선언문은 자바스크립트 엔진에 의해 var 키워드가 없는 것처럼 동작한다.
  var x = 2;
  console.log(x); // 2
}
foo();
  • var 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언이 허용된다. 이는 의도치 않게 변수값이 재할당되어 변경되는 부작용을 발생시킨다.

스코프의 종류

코드는 전역과 지역으로 구분할 수 있다. 전역은 코드의 가장 바깥 영역을 말하고, 지역은 함수 몸체 내부를 말한다.

  • 전역은 전역 스코프를 만들고, 전역에 변수를 선언하면 전역 스코프를 갖는 전역 변수가 된다.
  • 전역 변수는 어디서든지 참조할 수 있다.
  • 지역은 지역 스코프를 만들고, 지역에 변수를 선언하면 지역 스코프를 갖는 지역 변수가 된다.
  • 지역 변수는 자신의 지역 스코프와 하위 지역 스코프에서만 유효하다.

스코프 체인

함수는 중첩될 수 있으므로 함수의 지역 스코프도 중첩될 수 있다. 이는 스코프가 함수의 중첩에 의해 계층적 구조를 갖는다는 것을 의미한다. 중첩 함수의 지역 스코프는 외부 함수의 지역 스코프와 계층적 구조를 갖는데, 이때 외부 함수의 지역 스코프를 중첩 함수의 상위 스코프라 한다.

  • 모든 스코프는 계층적 구조로 연결되며, 모든 지역 스코프의 최상위 스코프는 전역 스코프다.
  • 스코프가 계층적으로 연결된 것을 스코프 체인이라고 한다.
  • 변수를 참조할 때 JS 엔진은 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작하여 상위 스코프 방향을 이동하며 선언된 변수를 검색한다.
    • 이를 통해 상위 스코프에서 선언한 변수를 하위 스코프에서도 참조할 수 있다.
  • 스코프 체인은 물리적인 실제로 존재한다. JS 엔진은 코드를 실행하기에 앞서 렉시컬 환경을 실제로 생성한다.
  • 변수 선언이 실행되면 변수 식별자가 이 자료구조(렉시컬 환경)에 키로 등록되고, 변수 할당이 일어나면 이 자료구조의 변수 식별자에 해당하는 값을 변경한다. 검색도 이 자료구조 상에서 이뤄진다.

  • 스코프 체인은 실행 컨텍스트의 렉시컬 환경을 단방향으로 연결한 것이다. 전역 렉시컬 환경은 코드가 로드되면 곧바로 생성되고 함수의 렉시컬 환경은 함수가 호출되면 곧바로 생성된다.

스코프 체인에 의한 변수 검색

  • 스코프 체인을 따라 변수를 참조하는 코드의 스코프에서 시작해서 상위 스코프 방향으로 이동하며 선언된 변수를 검색한다.
  • 상위 스코프에서 유효한 변수는 하위 스코프에서 자유롭게 참조할 수 있지만 하위 스코프에서 유효한 변수를 상위 스코프에서 참조할 수 없다.

스코프 체인에 의한 함수 검색

  • 함수도 식별자에 할당되기 때문에 스코프를 갖는다.
  • 식별자에 함수 객체가 할당된 것 외에는 일반 변수와 다를 바 없다.
  • 스코프를 “변수를 검색할 때 사용하는 규칙”이라고 표현하기보다는 “식별자를 검색하는 규칙”이라고 표현하는 편이 좀 더 적합하다.

함수 레벨 스코프

  • 대부분의 프로그래밍 언어는 함수 몸체만이 아니라 모든 코드 블록(if, for, while)이 지역 스코프를 만든다. -> 블록 레벨 스코프
  • 하지만 var 키워드로 선언된 변수는 오로지 함수의 코드 블록만을 지역 스코프로 인정한다. -> 함수 레벨 스코프
  • ES6에서 도입한 let, const 키워드는 블록 레벨 스코프를 지원한다.

렉시컬 스코프

var x = 1;

function foo() {
  var x = 10;
  bar();
}

function bar() {
  console.log(x);
}

foo(); // ?
bar(); // ?

위의 실행 결과는 bar 함수의 상위 스코프가 무엇인지에 따라 결정된다.

  1. 함수를 어디서 호출했는지에 따라 함수의 상위 스코프를 결정한다.
  2. 함수를 어디서 정의했는지에 따라 함수의 상위 스코프를 결정한다.
  • 첫 번째 방식을 동적 스코프라고 한다. 함수를 정의하는 시점에는 함수가 어디서 호출될지 알 수 없다. 함수가 호출되는 시점에 동적으로 상위 스코프를 결정해야 해서 동적 스코프라 부른다.
  • 두 번째 방식을 렉시컬 스코프 또는 정적 스코프라고 한다. 함수 정의가 평가되는 시점에 상위 스코프가 정적으로 결정되기 때문에 정적 스코프라 부른다.
  • 자바스크립트는 렉시컬 스코프를 따르므로 함수의 호출된 위치는 상위 스코프 결정에 어떠한 영향을 주지 않는다. 즉, 함수의 상위 스코프는 언제나 자신이 정의된 스코프다.
  • 함수가 호출될 때마다 함수의 상위 스코프를 참조할 필요가 있기 때문에 함수 정의가 실행되어 생성된 함수 객체는 이렇게 결정된 상위 스코프를 기억한다.

출처

모던 자바스크립트 Deep Dive

0912

처음으로 작성한 기술 블로그의 첫 글!
처음이다보니 부족한 점도 많겠지만 새벽에 열심히 포스팅을 했다…
점점 발전해나가겠지


이전에 자바를 공부하면서도 만난적이 있고, 자바스크립트 공부를 하면서 다시 만나게 된 개념인 얕은 복사깊은복사
직감적으로 중요한 개념이라는 생각이 들어서 정리해보고자 한다 🙃

우선 두 개념에 학습하기 전에 짚고 넘어가야 할 부분이 있다
관점에 따라서 얕은 복사와 깊은 복사의 개념이 다르게 해석될 수 있다는 점이다

원시 값을 할당한 변수를 다른 변수에 할당하는 것을 깊은 복사, 객체를 할당한 변수를 다른 변수에 할당하는 것을 얕은 복사라고 부르기도 하기 때문이다


원시 값을 할당한 변수를 다른 변수에 할당한 경우

let x = 1;
let y = x;

x = 2;

console.log(x); // 2
console.log(y); // 1

위의 경우에 대해 간단하게 설명하자면 원시 값을 할당한 변수를 다른 변수에 할당하는 경우, 새로운 메모리 공간에 값을 저장하고 해당 메모리 공간의 주소를 가리키게 한다. 그렇기 때문에 x값을 변경하여도 y에는 영향이 가지 않게 된다.
즉, y는 x와는 다른 별개의 값을 가리킨다.


객체를 할당한 변수를 다른 변수에 할당한 경우

let obj = { x: 1 };
let obj2 = obj;

console.log(obj); // { x: 1 }
console.log(obj2); // { x: 1 }

obj.x = 2;

console.log(obj); // { x: 2 }
console.log(obj2); // { x: 2 }

반면 객체를 할당한 변수를 다른 변수에 할당한 경우, 객체를 가리키는 참조값을 복사한다. 그렇게 되면 obj와 obj2는 같은 객체를 가리키게 되고, obj의 프로퍼티를 변경하게 되면 같은 객체를 가리키는 obj2의 프로퍼티 역시 변하게 된다.
즉, obj와 obj2는 같은 값을 가리키고 이는 의도치 않은 변경을 발생시킬 수 있다.

내가 이해하기에 깊은 복사란 대상을 완전하게 복사하여 둘 중 하나를 변경해도 영향을 주지 않도록 값을 할당하는 것을 의미한다. 반대로 얕은 복사란 대상의 값을 복사하였으나 참조값을 일부 공유하여 둘 중 하나의 값을 변경한 경우 영향이 발생하도록 값을 할당하는 것을 의미한다.

이러한 개념을 기반으로 객체에서의 얕은 복사와 깊은 복사에 대해 알아보자 🤔

우리는 위에서 객체를 할당 연산자인 =으로 복사했을 때, 완전히 복사가 되지 않는다는 사실을 알게되었다
사실 복사라는 단어보다는 새로운 식별자가 똑같은 객체를 참조했다고 볼 수 있다 🙁

그렇다면 서로 변경했을 때 영향을 주지 않도록 복사하기 위해서는 어떻게 해야할까?

얕은 복사 (Shallow Copy)

객체의 얕은 복사는 객체를 프로퍼티 값으로 갖는 객체의 경우 한 단계(1 depth)까지만 복사하는 것을 말한다.
얕은 복사 방법을 소개하고, 예시를 통해 깊은 복사와의 차이점을 보여주겠다.

Object.assign() 메서드를 사용한 복사

Object.assign() 메서드는 출처 객체로부터 모든 열거할 수 있는(enumerable) 하나 이상의 속성들을 목표 객체로 복사한다.

Object.assign(target, …sources)

const data = { x: { xx: 1 }, y: 2 };
const copy = Object.assign({}, data);

console.log(data === copy); // false

data.y = 3;
console.log(data); // { x: { xx: 1 }, y: 3 }
console.log(copy); // { x: { xx: 1 }, y: 2 }

할당 연산자로 객체를 복사했을 때와는 다르게 기존의 데이터를 변경하여도 복사본에는 영향이 가지 않는다.

전개 연산자 (스프레드 연산자)를 사용한 복사

const data = { x: { xx: 1 }, y: 2 };
const copy = { ...data };

console.log(data === copy); // false

마찬가지로 ES6의 스프레드 문법을 사용했을 경우에도 원하는대로 객체가 복사되었다.


과연 정말 원하는대로 복사되었을까❓

const data = { x: { xx: 1 }, y: 2 };
const copy = { ...data };

console.log(data.x === copy.x); // true

data.x.xx = 2;
console.log(data); // { x: { xx: 2 }, y: 2 }
console.log(copy); // { x: { xx: 2 }, y: 2 }

예상과는 다르게 객체의 프로퍼티 값으로 객체가 할당된 경우, 완전히 복사하지 못하고 참조값을 복사한 것을 확인할 수 있었다
Object.assign() 메서드를 사용했을 때와 스프레드 연산자를 사용했을 때 모두 객체의 depth가 1일 때까지만 변경에 영향없이 완전히 복사되고 depth가 2 이상일 때부터는 원치 않는 변경이 발생하였다

그렇다면 객체의 중첩 정도와 관계없이 완전한 객체 복사본을 만들기 위해서는 어떻게 해야할까 🙄



깊은 복사 (Deep Copy)

재귀 함수를 이용한 복사

const deepCopy = function (obj) {
  var clone = {};
  for (let key in obj) {
    if (typeof obj[key] == "object" && obj[key] != null) {
      clone[key] = deepCopy(obj[key]);
    } else {
      clone[key] = obj[key];
    }
  }

  return clone;
};

const data = { x: { xx: 1 }, y: 2 };
const copy = deepCopy(data);

console.log(data.x === copy.x); // false

data.x.xx = 2;
console.log(data); // { x: { xx: 2 }, y: 2 }
console.log(copy); // { x: { xx: 1 }, y: 2 }

복사의 대상이 객체이면 재귀적으로 함수를 호출하여 프로퍼티 값으로 객체가 온 경우까지 완전히 복사한다
직접 코드를 구현하여 해결하는 방법


lodash의 cloneDeep() 사용한 복사

lodash를 사용하기 위해서는 터미널에서 npm install lodash를 설치한 후, Node.js 환경에서 실행해야 한다

const lodash = require("lodash");

const data = { x: { xx: 1 }, y: 2 };
const copy = lodash.cloneDeep(data);

console.log(data.x === copy.x); // false

재귀 함수를 이용한 복사와 마찬가지로 깊은 복사가 원하는대로 실행됐다 🥳



❗️의문점

얕은 복사와 깊은 복사를 학습하면서 들었던 가장 궁금한 점은 ‘얕은 복사가 활용될 일이 있을까?‘였다
얕은 복사는 의도치 않은 변경이 생겨 에러를 발생시킬 수 있다
하지만 개발자의 의도하에 얕은 복사를 활용해서 프로그래밍 하는 경우가 있을 수도 있지 않을까 하는 생각이 들었다
만약 그것이 아니라면 얕은 복사는 지양해야 하는 것이고, 깊은 복사를 사용하는 법만 배우면 될테니 말이다



reference
이웅모, 모던 자바스크립트 Deep Dive https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/assign > https://falsy.me/javascript-6-%EA%B0%9D%EC%B2%B4%EC%9D%98-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%AC%EC%99%80-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%AC%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B4%85%EB%8B%88%EB%8B%A4/

태그:

카테고리:

업데이트: