[JavaScript] 2. 기초: 변수, 자료형, 연산 & 함수

bien·2024년 1월 2일
0

javascript

목록 보기
3/7
post-thumbnail

JavaScript의 기본적인 문법을 살펴보는 section이다. JavaScript의 기본 지식을 접한적 없더라도 Java에 익숙하다면 낯설지 않은 정보들이 대부분이다. 다만 JavaScript에서만 독특하다고 느껴지는 부분들이 있어 해당 부분에만 📌 표시를 추가했다.


웹 사이트에 JavaScript 추가하기

파일 구조

- 프로젝트명
   - assets
       - sripts
           - vendor.js
           - app.js
       - styles
       ...

.js: JavaScript 파일 확장자

JavaScript 연동

1. HTML 파일의 <script>

  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Basics</title>
    <script>
      alert('This works - between script tags! ');
    </script>
  </head>
  • HTML 파일의 <head> 섹션에서 <script>태그를 추가
  • 스크립트가 길어지면 HTML 파일이 과도하게 길어진다. 짧은 스크립트가 아닌 경우 분리하여 가져오는 것이 좋다.

2. <script>의 source

  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Basics</title>
    <script src="assets/scripts/app.js"></script>
  </head>
  • <script src="가져올 파일 명"></script>
    • script의 클로징 태그가 필요하다. script는 셀프 클로징 태그(<script/>)를 제공하지 않는다.

  • head가 먼저 실행되어 페이지가 로드되기 전에 alert가 뜬다.

3. 페이지가 먼저 로드되고 script 실행

<body>
    <header>
      <h1>The Unconventional Calculator</h1>
    </header>

    <section id="calculator">
      <input type="number" id="input-number" />
      <div id="calc-actions">
        <button type="button" id="btn-add">+</button>
        <button type="button" id="btn-subtract">-</button>
        <button type="button" id="btn-multiply">*</button>
        <button type="button" id="btn-divide">/</button>
      </div>
    </section>
    <section id="results">
      <h2 id="current-calculation">0</h2>
      <h2>Result: <span id="current-result">0</span></h2>
    </section>
    <script src="assets/scripts/vendor.js"></script>
    <script src="assets/scripts/app.js"></script>
  </body>
  • <body>안의 코드가 실행되어 페이지가 로드 된 이후 스크립트 실행을 원하는 경우, 단순히 스트립트 코드를 body 끝부분으로 옮기면 된다.

  • 이때, app.js 파일의 코드가 vendor.js파일의 코드를 의존하므로, 의존되는 파일이 더 먼저 위치해야 한다. (순서가중요)


변수(Variables)와 상수(Constants)

변수(Variable)

let userName = 'Max';
키워드 변수명  = 변수에 저장할 값;

// 새로운 값 할당 (let 재사용 X)
userName = 'Manu';
  • 변수: (우리가 저장하고자 하는) 데이터의 컨테이너 / 저장소.
    • 변경될 수 있는 값.
  • let
    • 변수를 생성해 처음 도입할 때에만 사용.
    • JavaScript에게 새로운 변수 사용을 알리기 위해 사용된다.

상수(Constant)

const totalUsers = 15;

// 값 변경시 예외가 발생한다.
// totalUsers = 20;
  • 상수: 값을 변경할 수 없다. 고정된 값.
    • 왜 변경할수 없는 변수를 사용해야할까? 파일 전반의 여러 다른 위치에서 '고정된 변수'를 사용할 때, 이를 일괄적으로 한 곳에서 변경하도록 해 수정을 편리하게 해준다.
  • 상수를 사용함으로써 '변경 불가능'이라는 개발자의 의도를 보여줄 수 있으므로, 상수 사용이 필요한 곳은 적극적으로 사용하자!

변수 선언 & 정의

Varialbe Naming (변수 작명)

허용 ⭕️

  • Best Practice: camelCase
    • 소문자로 시작하고 단어의 구분은 공백이 아닌 대문자로 수행.
    • ex. userName
  • JavaScript는 대문자를 구분한다.
    • userName != UserName
  • 변수명으로 숫자나 글자를 사용할 수 있다.
    • let ageGroup5
  • 기호 $_ 를 사용할 수 있다. (변수 내 모든 위치에서)
    • $kindOfSpecial, _internalValue

금지(비권장) ❌

  • Bad Practice: snake_case
    • 사용 가능하나 권장되지 않음.
    • let user_name
  • 변수명, 상수명은 숫자로 시작할 수 없다.
    • let 21Players
  • $, _를 제외한 모든 특수 문자는 사용할 수 없다. (공백, '-'등)
    • let user-b
  • 변수명으로 키워드를 사용할 수 없다. (let, const 등)
    • let let

📌 변수 선언

  • let currentResult
    • 아직 초기화 혹은 정의되지 않았으나, JavaScript가 해당 변수의 존재를 인식하도록 선언했다.
    • JavaScript에서는 꼭 변수에 값을 할당하여 초기화하지 않아도 괜찮다.
  • let currentResult = 0;
    • 변수에 초기값으로 0을 할당했다.
    • JavaScript에서 한 줄의 코드를 종료하기 위해 ;를 사용하는 것은 선택사항이다.
      • 단, 한 줄에 2개의 표현식이 들어가는 경우 필수 const a = 1; const b = 2

변수 & 연산자로 작업하기

Operators 연산자

  • + Add two numbers
  • - Substract two numbers
  • * Multiply two numbers
  • / Devide two numbers
  • % Devide two numbers, yield remainder
  • ** Exponentiation(제곱) (e.g. 2** 3 = 8)
  • = Assign value to variable
  • +=, -=, ... Perform calculation and re-assgin result to variable
  • ++, -- Increment/Decrement variable value & re-assign

축약 연산자는 위치에 따라 반환값을 다르게 반환한다.

// number = 0
alert(++number); // 1
alert(number++); //0

자료형(Data Types)

  • Numbers 숫자
    • 2, -3, 22.956
    • 계산이나 숫자로 작업해야 하는 경우에 사용
  • String (Text) 문자열
    • 'Hi', "Hi"
    • 결과 출력, 사용자 입력값 수집
      • "'모두 사용 가능. 단, 하나를 선택한 이후엔 유지해야 한다.
      • (섞어서 사용할 수 없음. = '로 시작해서 "로 끝낼 수 없음.)
  • Boolean 불리언
    • true, false
    • 조건부 코드 혹은 2가지 옵션이 있는 상황에 사용
  • Object 객체
    • 데이터의 그룹화.
    • 키-값 쌍을 포함한 중괄호로 생성.
{
	name: 'Max',
    age: 31
}
  • Arrays 배열
    • [1, 3, 5]
    • 숫자, 텍스트 등의 데이터 목록을 저장하는 경우 사용
    • 대괄호 내 쉼표로 데이터 구분

문자열 더보기

📌 Template literals : ``

  • 내장된 표현식을 허용하는 문자열: ` (backtic)으로 문자열을 제한한다.
  • 텍스트에 동적인 값 주입 or 임베딩 하기 위해 가장 자주 사용되는 형태.
  • 여러 행으로 된 문자열을 쉽게 작성할 수 있다.
  • 추가 정보 MDN 문서 참조

동적인 값 주입: Expression interpolation(표현식 삽입법)

  • 동적인 값을 택스트 내부에 주입하고자 할 때 사용할 수 있다.
    • ${expression}를 통해 표현식 삽입이 가능하다.
    • 백틱 사이에 ${expression} 코드가 있으면, JavaScript는 호출된 표현식의 값을 텍스트의 해당 부분에 출력하도록 지시받는다.
    • 다수의 문자열을 +로 조합하는 '수동 문자열 접합'에 비해 더 짧고 편리해 주로 이 방식을 사용한다.
let calculationDescription = '(' +  defaultResult + ' + 10) * 3 / 2 - 1';
let calculationDescription = `( ${defaultResult} + 10) * 3 / 2 - 1`;

여러행 문자열 작성: Multi-line strings

source내 삽입되는 newline characters(\n)은 template literal의 일부가 된다. 즉, 일반적인 문자열에서는 줄 바꿈을 위해 \n을 따로 작성해줘야만 했지만, 백틱을 사용한 template literal 내부에서는 줄 바꿈이 그대로 적용된다.

코드상의 여분의 공란, 줄 바꿈이 모두 그대로 문자열에 적용되므로 이를 주의해 사용해야 한다. (코드의 가독성을 위해 줄을 바꾸거나 띄어쓰면 안됨)

예시1

console.log("string text line 1\n" + "string text line 2");

console.log(`string text line 1
string text line 2`);
  • 두 로그가 동일하게 출력된다.

예시2

let calculationDescription = `(${defaultResult} + 10

* 3 / 2 - 1`;

h2 태그가 적용되어 렌더링된 화면에는 들어나지 않지만, 개발자 도구에서 코드를 확인해보면 (\n)를 사용하지 않았는데도 줄바꿈이 적용된것을 확인할 수 있다.

\ 문자열 탈출

  • 탈출: 뒤의 문자가 언어적 특성이 아닌\와 결합된 특별한 의미를 갖게 됨.
    • \n: \ + n = 언어적 n이 아닌 줄바꿈의 의미를 가짐
    • \': 문자 '가 문자열을 닫는 의미가 아닌 문자로 출력되어야 함을 의미.
    • \\: 문자 \를 출력.
      • \는 언제나 뒤의 문자를 탈출시키는 용도로 사용 되므로, 문자 출력시 앞에 \를 하나 더 붙여줘야 한다.

자료형 변환하기

  • 브라우저가 HTML 코드에서 입력값을 가져오는 경우 (설령 해당 변수가 숫자 유형이더라도) 항상 문자열이 제공된다.
  • 문자열 + 숫자 연산은, 항상 숫자를 문자열로 변환하고, 문자열을 접합시민다.
    • 15 + '05' = '1505'

숫자로 변환 (문자열 => 숫자)

  • parseInt(): 소숫점이 없는 경우
  • parseFloat(): 소숫점 아래가 있는 경우
    • 10입력시 10.0으로 구문 해석
parseInt(userInput.value); // 숫자변환

문자로 변환 (숫자 => 문자열)

  • .toString()
currentResult.toString()	// 문자변환

숫자 & 문자열 연산

  • 3 + '3' = '33'
    • + 연산자는 유일하게 문자열 접합 기능을 제공한다.
  • JavaScript는 모두 아래와 같은 연산을 수행할 수 있다. 산출된 값들은 모두 '숫자'이다. (문자x)
    • 3 - '3' = 0
    • 3 / '3' = 1
    • 3 * '3' = 9

📌 undefined, null & NaN

undefined

  • 초기화 되지 않은 변수의 기본값. 아무것도 없음을 의미.
    • 배열에서 "요소를 생성하지 않은 인덱스"에 접근할때 undefined가 반환된다. (해당 인덱스가 비어있어 아무것도 없으므로)
  • 숫자와 문자열처럼 하나의 데이터 유형.
  • 단, 값으로 할당해서는 안된다. (아무것도 없다는 의미로 =undefined이렇게 코드를 작성하면 안됨.

null

  • 데이터가 없다는 의미. (undefined와 유사)
  • 기본값일 수 없다.
  • null역시 하나의 데이터 유형.
  • 변수를 재설정하거나 정리하고 싶을 때 사용.

NaN

  • 기술적으로 숫자 유형이다. 따라서 숫자 계산에 사용 가능하다.
  • 일종의 오류 코드. 숫자가 포함되지 않은 것으로 계산하는 경우 결과로 NaN이 반환된다.
    • 즉, 계산의 결과가 유효하지 않다는 의미.


Typeof

런타임에서 변수 유형을 평가할 수 있게 해준다.

String

Number

  • NaN은 number 타입이다.

Object

  • undefined는 별개의 특수 유형이다.
  • null은 특수 유형이 아니라 객체의 한 종류이다.

상수 사용하기

JavaScript의 기본 작동 방식: 위에서 아래로

  • HTML 파일은 위에서부터 아래로 분석되고, JavaScript 파일을 import하면 파일 내의 코드 역시 위에서부터 아래로 실행된다.

상수의 값 변경

const defaultResult = 0;
let currentResult = 0;

defaultResult = (currentResult + 10) * 3 / 2 - 1;

  • chrom 브라우저의 개발자 도구를 통해 성수 변수 할당 오류를 확인할 수 있다.

함수 Functions

  • Functions(함수) = "Code on Demand" (추후에 실행 가능한 코드를 정의하도록 한다.)
  • 일부 기능을 아웃소싱하는데 함수를 사용한다.
    • 동일한 코드를 반복 실행해야 하는 경우, 이 코드를 함수로 한번 작성한 뒤 함수를 참조하여 여러번 실행하는 것이 좋다.

함수 정의 Define Function

키워드(function) 함수명(매개변수) {
	실행을 원하는 코드(함수본문)
}

function greetUser(name) {
	alert('Hi ' + name);
}    
  • 파라미터(매개변수), 반환값은 필요 시 사용 가능하다.
  • 함수 정의 위치
    • 브라우저는 전체 스크립트를 구문 분석할 때 위에서 부터 아래로 실행하는데, 바로 실행하는 것이 아니라 파일 내 함수를 찾게되면 함수를 맨 위로 끌고가서 인식한다.
    • 즉, 스크립트 실행전 모든 함수를 등록한다.
    • 따라서, 위에서 아래로 진행되는 다른 코드와 달리, 어느 위치에서든 자유롭게 정의 및 호출이 가능하다. (즉각적으로 실행되지 않고, 나중에 실행되는것 역시 가능하다.)

함수 호출 Call Function

greetUser('Max');

함수명(매개변수);
  • 얼마든 원하는 만큼, 코드 어디서든, 다양한 매개변수를 사용하여 호출할 수 있다.
  • 모든 함수의 실행은 이전 함수 실행과 무관하게 이루어진다.

예시

function add(num1, num2) {
    const result = num1 + num2;
    alert('The result is ' + result);
}

add(1, 2);
add(5, 5);

값 반환 return

function add(num1, num2) {
	const result = num1 + num2;
    return result;
}
  • return
    • 함수 실행을 종료시킨다. 이후 코드는 실행되지 않는다.
    • rerturn;을 통해 함수 실행을 종료할 수 있다. (반환값은 없다.)
    • 함수가 return을 통해 무언가를 반환하는 것은, 암묵적으로 함수가 특정 작업을 해 결과를 낼 뿐 그 외의 작업은 하지 않는다는 의미이다.
      • 예를 들어, 전역변수를 변경하는 것과 같은 작업은 하지 않는다.

전역 & 로컬 변수

// 전역
const defaultResult = 0;
let currentResult = defaultResult;

function add(num1, num2) {
	// 로컬 or 블록 스코프(block scope)
    const result = num1 + num2;
    return result;
}

currentResult = add(1, 2);

로컬 / 블록 스콥 (함수 내부)

  • 함수 내부에서 함수 외부에 정의된 전역 변수(혹은 상수)에 접근할 수 있다.
  • 함수 내부에서 전역 변수에 새로운 값을 부여할 수 있다.
    • 다만, 함수 내부에서 전역변수를 조작하는 것은 권장되지 않는다. (예측이 어렵다는 측면에서)
    • 함수 호출 시, 전역 데이터의 조작 없이, 내부 데이터만으로 작동해 값을 반환하는 순수 함수의 사용이 권장된다.
  • 함수 외부에서 함수 내부의 로컬 변수에 접근할 수 없다.
    • 함수 내부에서 정의한 내용은 블록 안에서만 유효하다.

비권장: 함수 내부에서 전역변수 조작

let result;

function add(num1, num2) {
	result = num1 + num2;
    return result;
}

권장: 내부 데이터로만 작동하는 순수 함수

function add(num1, num2) {
    const result = num1 + num2;
    return result;
}

currentResult = add(1, 2);

그림자(shadow) 변수

전역적으로 정의되어 있는 변수를 함수 내부에서 지역적으로도 생성하면 어떻게 될까?

변수를 두 번 이상 선언할 수 없다. 그러나 이는 동일한 수준/ 동일한 범위에서 허용되지 않는다. 함수의 변수는 자체 범위를 갖기 때문에 JavaScript는 섀도잉이라는 작업을 수행한다.

Shadowing 새도잉

  • 다른 수준(범위)에서 변수를 다시 선언한 경우, 변수를 덮어쓰거나 제거하지 않고, 다른 범위의 새 변수를 생성한다.
    • 두 변수는 동시에 공존한다.
    • 함수 내에서는 항상 함수 내부의 로컬 변수를 참조한다.
    • 해당 지역 변수가 존재하지 않는 경우에만 전역 변수를 참조한다.
let userName = 'Max';
function greetUser(name) {
  let userName = name;
  alert(userName);
}
userName = 'Manu';
greetUser('Max');
  • 이 코드를 시행하면 'Max' alert창이 뜬다.

함수의 "간접적" 실행

app.js

const defaultResult = 0;
let currentResult = defaultResult;

function add(num1, num2) {
    currentResult = currentResult + userInput.value;
    outputResult(currentResult, '');
}

addBtn.addEventListener('click', add);
  • addBtn.addEventListener('click', add);
    • addBtn을 클릭 시 add함수가 실행되도록 하는 코드.
    • 매개변수로 함수의 이름을 전달한다.
  • 버튼을 클릭할 때 마다 outputResult()가 실행되도록 함수 내부에 위치 시킨다.

'add' vs 'add()'

  • add(): 함수를 호출할 때 괄호를 추가하여 코드를 실행시킨다.
    • someButton.addEventListener('click', add());
      • 함수명에 괄호가 붙어있으므로, 자바스크립트는 이 코드를 인식하는 시점에서 바로 실행시킨다.
  • add: 함수를 직접 바로 실행하는 대신, 미래의 어느 시점(ex. 이벤트 발생 시)에 실행시키고 싶은 경우, JavaScript에게 함수의 이름(add)을 제공한다.
    • someButton.addEventListener('click', add); = "버튼이 클릭되면 add를 실행해줘"
    • 코드 어딘가에 함수명(add)만 추가하는 경우, JavaScript는 함수 이름으로 무엇을 해야하는지 알 수 없으므로, 해당 문장은 무시된다.
   let someVar = 5;
	add
	alert('Do something else...');
  • 이 코드에서 add는 무시된다.

주석

  • 코드로 포함되지 않는, 코드 이해를 돕기 위한 메모
  • 짧고 요점이 확실하게 작성
    • 명백한 내용, 너무 긴 주석, 정복되는 정보는 작성하지 않는다.

한줄

// Gets input from input field
function getUserNumberInput() {}

여러줄

/*	

*/

인라인 주석

function createAndWriteOutput() {
	outputResult() // from vendor.js file
}

프로젝트 코드 (계산기)

1) 함수로 코드 나누기

1. 기존 코드

const defaultResult = 0;
let currentResult = defaultResult;

function add() {
	currentResult = currentResult + parseInt(userInput.value);
    outputResult(currentResult, '');
}

addBtn.addEventListener('click', add);

2. 덧셈식 출력용 변수 설정

const defaultResult = 0;
let currentResult = defaultResult;

function add() {
	const calcDescription = '${currentResult} + ${userInput.value}';
	currentResult = currentResult + parseInt(userInput.value);
    outputResult(currentResult, '');
}

addBtn.addEventListener('click', add);
  • currentResult라는 값이 이후 변경되기 때문에, 변경되기 이전에 calcDescription라는 변수를 먼저 선언하여 덧셈식을 저장해둔다.

3. 중복 코드 제거

const defaultResult = 0;
let currentResult = defaultResult;

function add() {
	const enteredNumber = parseInt(userInput.value);
	const calcDescription = '${currentResult} + ${enteredNumber}';
	currentResult = currentResult + enteredNumber;
    outputResult(currentResult, '');
}

addBtn.addEventListener('click', add);
  • userInput이 반복 사용 된다. 이후 상수명이 변경되는 경우 모두 수정해야 하므로 스크립트가 긴 경우 귀찮은 작업이 될 수 있다.
    • 상수 enteredNumber를 새롭게 선언하여 반복되는 로직을 추출했다.

4. 함수에게 로직 아웃소싱

const defaultResult = 0;
let currentResult = defaultResult;

function getUserNumberInput() {
	return parseInt(userInput.value);
}

function add() {
	const enteredNumber = getUserNumberInput();
	const calcDescription = '${currentResult} + ${enteredNumber}';
	currentResult = currentResult + enteredNumber;
    outputResult(currentResult, '');
}

addBtn.addEventListener('click', add);
  • form에서 가져온 값을 숫자로 변경하는 작업을 함수 getUserNumberInput()에게 아웃소싱했다. 이제 해당 값이 필요한 경우, 수동으로 함수를 호출하여 얻을 수 있다.

2) 모든 버튼들을 함수에 연결하기

뺄셈 추가

const defaultResult = 0;
let currentResult = defaultResult;

function getUserNumberInput() {
	return parseInt(userInput.value);
}

function add() {
	const enteredNumber = getUserNumberInput();
	const calcDescription = '${currentResult} + ${enteredNumber}';
	currentResult = currentResult + enteredNumber;
    outputResult(currentResult, '');
}

function subtract() {
	const enteredNumber = getUserNumberInput();
	const calcDescription = '${currentResult} - ${enteredNumber}';
	currentResult = currentResult - enteredNumber;
    outputResult(currentResult, '');
}

addBtn.addEventListener('click', add);

'로그 텍스트 생성 & 결과 출력' 기능 함수로 추출

const defaultResult = 0;
let currentResult = defaultResult;

function getUserNumberInput() {
	return parseInt(userInput.value);
}

function createAndWriteOutput(operator, resultBeforeCalc, calcNumber) {
	const calcDescription = '${currentResult} ${operator} ${enteredNumber}';
	    outputResult(currentResult, calcDescription);
}

function add() {
	const enteredNumber = getUserNumberInput();
    const initialResult = currentResult;
	currentResult = currentResult + enteredNumber;
    createAndWriteOutput('+', initialResult, enteredNumber);
}

function subtract() {
	const enteredNumber = getUserNumberInput();
    const initialResult = currentResult;
	currentResult = currentResult - enteredNumber;
    createAndWriteOutput('-', initialResult, enteredNumber);
}

addBtn.addEventListener('click', add);
  • createAndWriteOutput(): 매개변수를 기반으로 출력문 생성 & 출력

곱하기, 나누기 함수 추기

const defaultResult = 0;
let currentResult = defaultResult;

function getUserNumberInput() {
	return parseInt(userInput.value);
}

function createAndWriteOutput(operator, resultBeforeCalc, calcNumber) {
	const calcDescription = '${currentResult} ${operator} ${enteredNumber}';
	    outputResult(currentResult, calcDescription);
}

function add() {
	const enteredNumber = getUserNumberInput();
    const initialResult = currentResult;
	currentResult = currentResult + enteredNumber;
    createAndWriteOutput('+', initialResult, enteredNumber);
}

function subtract() {
	const enteredNumber = getUserNumberInput();
    const initialResult = currentResult;
	currentResult = currentResult - enteredNumber;
    createAndWriteOutput('-', initialResult, enteredNumber);
}

function multiply() {
	const enteredNumber = getUserNumberInput();
    const initialResult = currentResult;
	currentResult = currentResult * enteredNumber;
    createAndWriteOutput('*', initialResult, enteredNumber);
}

function divide() {
	const enteredNumber = getUserNumberInput();
    const initialResult = currentResult;
	currentResult = currentResult / enteredNumber;
    createAndWriteOutput('/', initialResult, enteredNumber);
}

addBtn.addEventListener('click', add);
substractBtn.addEventListener('click', substract);
multiplyBtn.addEventListener('click', multiply);
divideBtn.addEventListener('click', divide);

3) 배열 사용하기

진행한 연산 작업을 로그하고 해당 로그 엔티리를 배열에 저장하자

const defaultResult = 0;
let currentResult = defaultResult;
let logEntries = [];

function getUserNumberInput() {
	return parseInt(userInput.value);
}

function createAndWriteOutput(operator, resultBeforeCalc, calcNumber) {
	const calcDescription = '${currentResult} ${operator} ${enteredNumber}';
	    outputResult(currentResult, calcDescription);
}

function add() {
	const enteredNumber = getUserNumberInput();
    const initialResult = currentResult;
	currentResult = currentResult + enteredNumber;
    createAndWriteOutput('+', initialResult, enteredNumber);
    logEntries.push(enteredNumber);
    console.log(logEntries);
}

// ...
  • 연산 작업을 저장할 logEntries 배열을 추가했다.
    • JavaScript 내부에 구축되어 있는 배열의 push함수를 통해 새로운 요소를 더해준다.
  • 배열의 인덱스는 0부터 시작된다.

4) 객체 생성하기

연산 작업 자체를 객체를 통해 데이터화 하여 저장해보자.

const defaultResult = 0;
let currentResult = defaultResult;
let logEntries = [];

function getUserNumberInput() {
	return parseInt(userInput.value);
}

function createAndWriteOutput(operator, resultBeforeCalc, calcNumber) {
	const calcDescription = '${currentResult} ${operator} ${enteredNumber}';
	    outputResult(currentResult, calcDescription);
}

function add() {
	const enteredNumber = getUserNumberInput();
    const initialResult = currentResult;
	currentResult = currentResult + enteredNumber;
    createAndWriteOutput('+', initialResult, enteredNumber);
    const logEntry = {
    	operation: 'ADD',
		prevResult: initialResult,
        number: enteredNumber,
        result: currentResult
	};    
    logEntries.push(logEntry);
    console.log(logEntries);
}

// ...

필요한 데이터를 logEntry 객체에 구조화하고, 이 객체를 배열에 저장한다.

  • {}를 통해 데이터를 구조화한다.
  • 세미콜론이 아닌 쉼표(,)로 키-값 쌍을 구분한다.
    • 객체 내부에 세미콜론(;)을 작성하면 구문 오류다.
  • 값은 콜론(:)으로 키/프로퍼티에 할당한다.
    • 객체 내부에 등호(=)를 쓰면 구문 오류다.
  • 점 표기법을 통해 객체 안의 프로퍼티에 접근할 수 있다.
    • ex. logEntry.operation
  • 따라서, 아래의 코드는 잘못된 문법이다.
const worstPossibleUser = {
    name = 'Max';
    age = 30;
};

5) 객체를 사용하는 재사용 함수 추가

const defaultResult = 0;
let currentResult = defaultResult;
let logEntries = [];

function getUserNumberInput() {
	return parseInt(userInput.value);
}

function createAndWriteOutput(operator, resultBeforeCalc, calcNumber) {
	const calcDescription = '${currentResult} ${operator} ${enteredNumber}';
	    outputResult(currentResult, calcDescription);
}

function writeToLog(
	operationIdentifier, 
    prevResult, 
    operationNumber, 
    newResult
) {
    const logEntry = {
    	operation: operationIdentifier,
		prevResult: prevResult,
        number: operationNumber,
        result: newResult
	};    
    logEntries.push(logEntry);
    console.log(logEntries);

 }

function add() {
	const enteredNumber = getUserNumberInput();
    const initialResult = currentResult;
	currentResult += enteredNumber;
    createAndWriteOutput('+', initialResult, enteredNumber);
	writeToLog('ADD', initialesult, enteredNumber, currentResult);
}

function subtract() {
	const enteredNumber = getUserNumberInput();
    const initialResult = currentResult;
	currentResult -= enteredNumber;
    createAndWriteOutput('-', initialResult, enteredNumber);
	writeToLog('SUBTRACT', initialesult, enteredNumber, currentResult);
}

// ...

사칙연산 함수에서 객체 생성 로직이 중복되므로, 이를 함수로 아웃소싱했다.


📌 스크립트 임포트: "defer" & "async"

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Basics</title>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:400,700&display=swap"
      rel="stylesheet"
    />
    <link rel="stylesheet" href="assets/styles/app.css" />
  </head>
  <body>
    <header>
      <h1>The Unconventional Calculator</h1>
    </header>

    <section id="calculator">
      <input type="number" id="input-number" />
      <div id="calc-actions">
        <button type="button" id="btn-add">+</button>
        <button type="button" id="btn-subtract">-</button>
        <button type="button" id="btn-multiply">*</button>
        <button type="button" id="btn-divide">/</button>
      </div>
    </section>
    <section id="results">
      <h2 id="current-calculation">0</h2>
      <h2>Result: <span id="current-result">0</span></h2>
    </section>
    <script src="assets/scripts/vendor.js"></script>
    <script src="assets/scripts/app.js"></script>
  </body>
</html>
  • app.js 파일의 이벤트 리스너를 연결하기 위해서는 브라우저가 모든 HTML 코드를 구문 분석하고 렌더링한 이후에 스크립트가 실행되어야 한다. 그래야만 연결하려는 BUTTON이 존재한다.
    • 따라서, script문을 html파일 가장 하단에 위치시켰다.

크롬 개발자도구: Performance

  • 페이지 렌더링 시 브라우저에서 실행하는 작업을 자세히 알 수 있다. 또한 스크립트가 어떻게 분석되고 실행됐는지, 문제 역시 확인 가능.
  • 시크릿 창으로 열어보는게 좋다: 확장자나 브라우저 플러그인으로 왜곡되는 것 방지

  • Record 버튼을 누른 후, 페이지를 새로고침한 후 (F5버튼으로) 기록을 중지한다.

Performance를 통해 브라우저 랜더링 확인

  • Network 탭을 보면, 파란색 부분으로 표시된 index.html파일을 다운로드 하고 나서 css파일과 스크립트 파일을 다운로드 한다.
  • 하단 부분에 브라우저의 작업이 자세히 표현된다.
    • CSS 파일(분홍색) 비교적 빨리 전송됐다: head 섹션에서 CSS 파일을 요청하기 때문.
    • JavaScript 파일(노란색) 조금 늦게 전송됐다: HTML 파일 하단에서 요청하기 때문.

  • html 구문 분석이 끝난 후 시간이 지나고나서 스크립트가 실행된다.
    • 파란색: HTML파일 구문 분석 (Parse HTML)
    • 노란색: 스크립트 실행

파악된 문제점

index.HTML파일 하단에 스크립트 import문이 있어 모든 코드가 구문 분석 될 때까지 기다렸다가 스크립트 로딩을 시작한다. 코드가 작고 서버를 거치지 않고 로컬에서 작업하고 있기 때문에 페이지가 빠르게 로드되었지만, 만약 스크립트가 길어진다면 시간이 많이 요구된다.

이를 해결하기 위해선, 빨리 스크립트를 로드하고 구문 전체가 분석된 후 실행하도록 하면 된다.

1. 문제 해결: script를 head로 이동

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Basics</title>
    <link rel="stylesheet" href="assets/styles/app.css" />
    <script src="assets/scripts/vendor.js"></script>
    <script src="assets/scripts/app.js"></script>
  </head>
  <body>
    <header>
      <h1>The Unconventional Calculator</h1>
    </header>

    <section id="calculator">
      <input type="number" id="input-number" />
      <div id="calc-actions">
        <button type="button" id="btn-add">+</button>
        <button type="button" id="btn-subtract">-</button>
        <button type="button" id="btn-multiply">*</button>
        <button type="button" id="btn-divide">/</button>
      </div>
    </section>
    <section id="results">
      <h2 id="current-calculation">0</h2>
      <h2>Result: <span id="current-result">0</span></h2>
    </section>

  </body>
</html>

스크립트를 head 섹션으로 옮겼다.

Performance를 통해 브라우저 랜더링 확인

  • html을 parse하던 것(파란색)을 잠깐 중단하고, 스크립트를 다운로드 한 이후, 스크립트 다운로드가 끝나면 다시 html을 분석(parse)한다.
  • HTML 구문 분석 시 먼저 스크립트를 다운로드하고, 다시 구문을 분석하여 스크립트를 실행한 후 구문 분석을 계속 한다.
    • 따라서 버튼 준비 전 상호작용으로 오류가 발생한다.

파악된 문제점

스크립트 다운로드를 먼저 시작한 것은 좋으나, 스크립트를 연결할 요소가 랜더링 되기 전, 다운로드 후 바로 스크립트가 실행된다는 것이 문제점이다.

이런 경우, script의 defer 속성을 이용할 수 있다.

2. 문제 해결: defer, async

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
	//...
    <script src="assets/scripts/vendor.js" defer></script>
    <script src="assets/scripts/app.js" defer></script>
  </head>
  //....

📌 defer

  • 스크립트를 바로 다운로드 하나, HTML 구문 분석도 중단하지 않아 HTML구문 분석이 끝난 후 스크립트가 실행되도록 한다.
    • 즉, HTML 구문 분석이 완료된 후에만 스크립트를 실행하도록 한다.
  • 실행순서가 고정되어 있다.
    • app.js가 먼저 설치되더라도 vendor.js가 먼저 실행된다.
  • 장점:
    1. 빠르게 로드(스크립트 다운로드와 html파일 구문분석 동시 시행)
    2. HTML 구문 분석 완료 후에만 실행 ()

Performance를 통해 브라우저 랜더링 확인

  • 하단의 html 구문 분석(파란색)을 진행하며 상단의 network에서 js파일의 다운로드와 병행되고 있다.
  • 하단의 스크립트 실행(노란색)이 html 구문 분석(파란색)이후 실행되고 있다.

📌 async

    <script src="assets/scripts/vendor.js" async></script>
    <script src="assets/scripts/app.js" async></script>
  • 스크립트 미리 로드 & 실행도 미리하는 경우 사용. (스크립트 다운로드 후 바로 실행)
    • cf. defer는 스크립트 실행이 HTML 구문 분석 이후 진행됨.
  • HTML 구문 분석이 중단되었다가 스크립트 실행이 종료된 이후 다시 실행된다.
  • HTML 코드에 의존하지 않아 연결을 하지 않아도 되는 경우
    • ex) 웹 페이지와 상호작용하지 않고 백그라운드 서버에 일부 데이터만 전송하는 경우 등
  • 다운로드되는 즉시 가능한 빠르게 실행된다. 즉, 실행순서가 확실하지 않다.
    • app.js 코드가 밑에 있지만, 다운로드가 먼저 된다면 vendor.js보다 먼저 실행된다.
    • cf. defer의 경우 실행 순서가 보장된다. (다운로드와 상관없이 vendor.js가 먼저 실행됨)

주의 사항: 외부 스크립트에서만 사용 가능

defer와 async는 외부 스크립트에서만 사용 가능하다. 그 외의 방식에서는 두 설정을 무시한다.

<!DOCTYPE html>
<html lang="en">
  <head>
	//...
    <script defer>
    	alert('Hi!');
    </script>
  </head>
  //....

스크립트가 HTML 파일에 포함되어 있으므로 HTML 파일 다운로드 시 사용 가능하다. 애초에 다운로드할 파일이 없어 defer, async 설정이 무시된다.

또한, 이 같은 스크립트 코드는 늘 바로 실행된다. 따라서 HTML 코드에 의존한다면 (첫번째 index.html과 같이) 함수 본문 섹션의 끝 부분으로 이동시켜야 한다.

Tip) HTML 파일에 중요하거나 긴 스크립트 파일을 포함시키는 것은 권장되지 않는다. 항상 외부 파일을 이용해 HTML 파일을 작고 집중적으로 유지해야 하며, 스크립트 수가 많아선 안된다.


Reference

profile
Good Luck!

0개의 댓글