[TIL] Unit 10. 원시 & 참조 자료형, 스코프, 클로저, ES6 문법

string_main·2022년 5월 14일
0

JavaScript

목록 보기
4/22
post-thumbnail

🌱 원시 자료형 & 참조 자료형


  • 원시 자료형(primitive data type) : 객체가 아니면서 메서드를 가지지 않는 타입. 각 변수 간 데이터 값을 복사하기 때문에 기존 데이터에는 영향이 가지 않는다. (string, number, bigint, boolean, undefined, symbol, null)

    • 값 자체에 대한 변경이 immutable 하지만, 변수에 다른 데이터를 재할당하여 변경할 수는 있다. (const는 재할당 불가)
  • 참조 자료형(reference data type) : 원시 자료형이 아닌 모든 것은 참조 자료형. 값 대신 저장소의 주소를 복사하여 참조하기 때문에 기존 데이터에도 영향이 간다. (배열, 객체, 함수)

    • 변수에 넣을 수 있는 데이터 크기가 제한되기 때문에 참조형 자료 구조가 등장하게 되었다.
    • 참조 자료형을 읽을 때, 미리 주소값과 메모리 값을 잡아둔다.
    • 힙(heap) : 필요에 의해 동적으로 메모리를 할당하는 영역 (동적 데이터 저장소)
    • ===(strict equality) : 참조 자료형에서 ===는 주소값이 같은지를 확인한다.
console.log('hi' === 'hi'); // true
console.log(3.14 === 3.14); // true
console.log([1,2,3] === [1,2,3]); // false
console.log({ name: 'hazel' } === { name: 'hazel' }); // false

변수가 많아질 때, 찾고 삭제하는 활동은 비효율적이다.

➡️ 힙이라는 빈 공간을 두어 변수에 값 대신 힙 영역의 주소를 넣어 관리한다. (원소가 많아져도 하나의 주소지에서 삽입, 삭제 등의 연산을 할 수 있다.)

🌱 스코프(Scope)


배틀 그라운드에서 목표물을 정확히 조준하기 위한 아이템인 스코프가 떠오른다!

  • 정의 : 변수의 유효 범위
    • 바깥쪽 스코프에서 선언한 변수는 안쪽 스코프에서 사용 가능하지만, 안쪽 스코프에서 선언한 변수는 바깥쪽 스코프에서 사용 불가능.
    • 스코프는 중첩이 가능하다. (울타리 역할)
    • 가장 바깥의 스코프는 전역 스코프(Global scope)라고 한다. (↔️ 지역 스코프(Local scope))
// 바깥쪽 스코프
let username = 'hazel';
if (username) { // 안쪽 스코프
  let message = `Hello, ${username}!`;
  console.log(message); // Hello hazel!
}

console.log(message); // ReferenceError
  • 전역 변수 : 전역 스코프에 선언한 변수
  • 지역 변수 : 지역 스코프에 선언한 변수
    • 지역 변수는 전역 변수보다 더 높은 우선 순위를 가진다.
    • 변수 은닉화(variable shadowing) : 동일한 변수 이름으로 바깥쪽 변수가 안쪽 변수에 의해 가려지는 현상
    • side effect : 전역 변수를 많이 선언했을 때 의도되지 않은 변경이 발생하는 현상
let name = 'hazel';

function showName() {
  let name = 'daisy'; // 지역 변수
  console.log(name); // 두 번째 출력
}

console.log(name); // hazel
showName(); // daisy
console.log(name); // hazel
  • 블록 스코프(block scope) : 중괄호를 기준으로 범위 구분
  • 함수 스코프(function scope) : 함수 선언식 및 표현식이 만드는 스코프 (화살표 함수는 블록 스코프로 취급 됨)
// 블록 스코프
let getAge = user => {
  return user.age;
}

// 함수 스코프
let getAge = function (user) {
  return user.age;
}
  • var : 블록 스코프를 무시하고 함수 스코프만 따르며 재선언을 해도 에러가 나지 않음. (선언 키워드 없으면 var로 취급, 화살표 함수의 블록 스코프는 무시하지 않음)
  • let : 블록 단위로 스코프를 구분했을 때, 훨씬 예측 가능한 코드 작성이 가능하며 재선언을 방지해서 권장되는 키워드이다.
  • const : 블록 스코프를 따르며 값이 변하지 않는 상수를 정의할 때 쓰는 키워드로 재할당이 불가능하다. (의도하지 않은 값의 변경을 방지하며, 재할당 시 TypeError 발생)
  • 'use strict'; : Strict Mode는 브라우저를 엄격하게 작동하도록 만들어 에러를 알려준다.
// var의 특성
for(var i = 0; i < 5; i++) {
  console.log(i); // 5번 출력
}
console.log('final i: ', i); // 5

// var, let 재선언 비교
var myName = 'hazel';
var myName = 'hazel'; // 아무 에러가 나지 않음

let myName = 'hazel';
let myName = 'hazel'; // SyntaxError 발생

// const 재할당
const pi = 3.14;
pi = 3.1415; // TypeError 발생
letconstvar
유효 범위블록 스코프 및 함수 스코프블록 스코프 및 함수 스코프함수 스코프
값 재할당가능불가능가능
재선언불가능불가능가능
  • window 객체 : 브라우저 창을 대표하는 객체. var로 선언된 전역 변수 및 함수는 window 객체에 속하게 된다.
var myName = 'hazel';
console.log(window.myName); // hazel

function foo() {
  console.log('bar');
}

console.log(foo === window.foo); // true

🌱 클로저(Closure)


  • 정의 : 함수와 함수가 선언된 어휘적(lexical) 환경(변수 및 함수 선언의 형태)의 조합, 외부 함수의 변수에 접근할 수 있는 내부 함수, 자신이 선언될 때의 맥락을 기억하는 함수

  • 특징 :

    • 리턴하는 함수에 의해 스코프가 구분된다. (특정 데이터를 스코프 안에 가두어 둔 채로 계속 사용 가능)
    • 내부 함수는 외부 함수에 선언된 변수에 접근 가능하다.
    • 외부 함수의 실행이 끝나도 변수가 메모리 상에 저장된다. (어휘적 환경을 메모리에 저장하기 때문)
    • 캡슐화, 모듈화에 용이 (정보의 접근 제한 및 함수 재사용성 극대화)
    • 클로저를 통해 불필요한 전역 변수 사용을 줄이고, 스코프를 이용해 값을 보다 안전하게 다룰 수 있다.
    • 일반 함수였다면 함수 실행 종료 후 가비지 컬렉션 대상이 되었을 객체가 클로저에서는 메모리 상에 남아 있어 클로저를 남발할 경우 퍼포먼스 저하가 발생할 수 있다.
// 1. 화살표 함수를 이용한 덧셈 함수
const add = (x, y) => x + y;
add(5, 7);

// 2. 함수 호출이 두 번 발생하게 구현
const adder = x => y => x + y;
adder(5)(7); // 12

typeof adder(5); // 'function'

adder(5); // y => x + y

// 3. 클로저 함수의 기본 형태: 함수를 리턴하는 함수 (2번과 동일하게 작동)
const adder = function (x) { // 외부 함수
  return function (y) { // 내부 함수, 리턴값이 함수의 형태
    return x + y;
  }
}

// 4. 데이터를 보존하는 함수: 함수 실행이 끝나도 어휘적 환경이 저장되어 계속해서 변수 사용 가능
const adder = function (x) {
  return function (y) {
    return x + y;
  }
}

const add5 = adder(5);
add5(7); // 12
add5(10); // 15

// 5. HTML 문자열 생성기 (실용적인 예제)
const tagMaker = tag => content => `<${tag}>${content}</${tag}>`;

const divMaker = tagMaker('div');
divMaker('hello'); //'<div>hello</div>

const anchorMaker = tagMaker('a');
anchorMaker('nice'); //'<a>nice</a>'

// 6. 클로저 모듈 패턴 (정보의 접근 제한)
const makeCounter = () => {
  let value = 0;
  
  return {
    increase: () => { // 내부 함수를 여러 개 만들 수 있음
      value = value + 1
    },
    decrease: () => {
      value = value - 1
    },
    getValue: () => value // 객체에 담아 여러 개의 내부 함수 리턴 가능
  }
}

// 모듈화: 독립적인 부품 형태로 분리하여 함수 재사용성 극대화
const counter1 = makeCounter(); // { increase: f, decrease: f, getValue: f }를 리턴하여 counter1은 함수 여러개를 포함 한 객체이다.
counter1.increase();
counter1.increase();
counter1.decrease();
counter1.getValue(); // 1

const counter2 = makeCounter(); // 독립적으로 value를 가지게 됨 (서로에게 영향을 끼치지 않음)
counter1.decrease();
counter2.decrease();
counter1.decrease();
counter2.getValue(); // -3

  • 종합 퀴즈

// 1번
let x = 10;

function outer () {
  x = 20;

  function inner () {
    let x // undefined
    x = x + 20; // undefined + 20 = NaN
    return x;
  }
  inner();
}

outer();
let result = x;

// 2번: seenYet이 리턴하고 있는 익명 함수가 클로저로 간주된다.
let seenYet = function() {
  let archive = {};
  return function(val) {
    if (archive[val]) {
      return true;
    } 
    archive[val] = true;
    return false;
  }
}

// 3번
let add = function(x) {
  let sum = function(y) {
    return x + y;
  }
  return sum;
}

let foo = add(1); // 1 + y
foo(3); // 할당이 되지 않아 아무런 영향을 끼치지 않음
let total = foo(6); // 1 + y

// 4번: 리턴 함수가 x에 접근할 수 있기 때문에 multiplyByX만 클로저를 사용하고 있다고 볼 수 있다.
let multiplyByX = function(x) {
  return function(y) {
    return x * y;
  }
}

let multiplyBy5;
multiplyBy5 = multiplyByX(5);
multiplyBy5(4);

/*******************************/

let multiplyByFive = function() {
  return function(y) {
    return 5 * y;
  }
}

let multiplyBy5 
multiplyBy5 = multiplyByFive();
multiplyBy5(4);

// 5번
var a = 0;
function foo() {
    var b = 0;
    return function() {
        console.log(++a, ++b);
    };
}

var f1 = foo();
var f2 = foo();

f1(); // --> 1 1
f1(); // --> 2 2
f2(); // --> 3 1
f2(); // --> 4 2

// 6번
<!DOCTYPE html>
<html>
<body>
  <button class="toggle">toggle</button>
  <div class="box" style="width: 100px; height: 100px; background: red;"></div>

  <script>
    var box = document.querySelector('.box');
    var toggleBtn = document.querySelector('.toggle');

    var toggle = (function () {
      var isShow = false;

      // TODO: ① 클로저를 반환하는 함수를 작성하세요.
      return function () {
        box.style.display = isShow ? 'block' : 'none';
        // TODO: ③ isShow 변수의 상태를 변경하는 코드를 작성하세요.
        isShow = !isShow;
      };
    })();

    // ② 이벤트 프로퍼티에 클로저를 할당
    toggleBtn.onclick = toggle;
  </script>
</body>
</html>

🌱ES6 주요 문법


  • Spread syntax(전개 구문) : ...로 사용하며, 배열이나 문자열과 같이 반복 가능한 문자를 인수나 요소로 확장하여 키-값 쌍의 객체로 확장시킬 수 있다. 기존 배열은 변경하지 않는다. (immutable)
function sum(x, y, z) {
  return x + y + z;
}

const numbers = [1, 2, 3];

sum(...numbers) // 6
  • Rest parameters(나머지 매개변수) :
    함수가 정해지지 않은 수의 매개변수를 배열의 형태로 받을 수 있다. (가변항 함수를 표현할 때 사용)
// rest 문법
function sum(...theArgs) {
  // reduce(accumulator, currentValue)
  return theArgs.reduce((previous, current) => {
    return previous + current;
  });
}

sum(1,2,3) // 6
sum(1,2,3,4) // 10

// 배열 합치기
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1 = [...arr1, ...arr2];  // spread 문법은 기존 배열을 변경하지 않으므로(immutable), arr1의 값을 바꾸려면 새롭게 할당해야 함

// 배열 복사
let arr = [1, 2, 3];
let arr2 = [...arr]; // arr.slice()와 유사
arr2.push(4); // [1, 2, 3, 4]

// 객체 합치기
let obj1 = { foo: 'bar', x: 42 };
let obj2 = { foo: 'baz', y: 13 };

let clonedObj = { ...obj1 }; // {foo: 'bar', x: 42}
let mergedObj = { ...obj1, ...obj2 }; // {foo: 'baz', x: 42, y: 13}

// 함수에서 나머지 매개변수 받아오기
function myFun(a, b, ...manyMoreArgs) {
  console.log("a", a);
  console.log("b", b);
  console.log("manyMoreArgs", manyMoreArgs);
}

myFun("one", "two", "three", "four", "five", "six");

// 퀴즈 1번
let arr = [10, 30, 40, 20]
let value = Math.max(arr) // NaN (배열로 전달했기 때문)

let arr = [10, 30, 40, 20]
let value = Math.max(...arr) // 40 (요소들을 펼쳐서 실행)

// 퀴즈 2번
let arr = ['code', 'states']
let value = [
  ...arr,
  'pre',
  ...['course', 'student']
]
// ['code', 'states', 'pre', 'course', 'student']

🌱구조 분해 할당(Destructuring assignment)


  • 정의 : 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식
// 배열
const [a, b, ...rest] = [10, 20, 30, 40, 50]; 
// {0: 10, 1: 20, 2: 30, 3: 40, 4: 50}

// 객체
const {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40} 
// {a: 10, b: 20, c: 30, d: 40}

// 함수에서 객체 분해
function whois({displayName: displayName, fullName: {firstName: name}}){
  console.log(displayName + " is " + name);
}

let user = {
  id: 42,
  displayName: "jdoe",
  fullName: {
      firstName: "John",
      lastName: "Doe"
  }
};

whois(user) // jdoe is John

🌱호이스팅(Hoisting)


  • 정의 : 인터프리터가 변수와 함수의 메모리 공간을 선언하기 전에 미리 할당하는 것을 의미한다. (변수의 선언과 초기화를 분리한 후 선언만 코드의 최상단으로 옮기는 행위)

    • 호이스팅은 Side Effect이며, 이를 주의하여 코드를 작성해야 한다.
// 함수 선언식은 호이스팅에 영향을 받지만, 함수 표현식은 호이스팅에 영향을 받지 않음.
let funcExpressed = 'to be a function';

expect(typeof funcDeclared).to.equal('function'); // 함수 선언식은 호이스팅으로 인해 'function'으로 리턴
expect(typeof funcExpressed).to.equal('string'); // 함수 표현식은 영향을 받지 않아 'string'으로 리턴

function funcDeclared() { // 함수 선언식
      return 'this is a function declaration';
}

funcExpressed = function () { // 함수 표현식
      return 'this is a function expression';
};

const funcContainer = { func: funcExpressed }; // 함수 표현식을 객체형태로 담을 수 있다.
expect(funcContainer.func()).to.equal('this is a function expression');

funcContainer.func = funcDeclared; // const 재할당은 불가하지만, Object에 key-value 추가 및 변경은 가능 (바인딩 되는 값이 아니기 때문)
expect(funcContainer.func()).to.equal('this is a function declaration');

함수 표현식을 사용하는 것을 권장한다. 호이스팅이 일어나는 상황을 만들지 마라. - 더글라스 크락포드

🌱얕은 복사와 깊은 복사


  • 얕은 복사 (Shllow Copy) : 객체를 직접 대입하는 경우, 객체의 참조값(주소 값)을 복사하므로 원본 값이 변경된다. (같은 데이터 주소)
    • slice() 등이 대표적인 예시이다.
const obj1 = { a: 1, b: 2};
const obj2 = obj1;

console.log( obj1 === obj2 ); // true

obj2.a = 100;
console.log( obj1.a ); // 100
  • 깊은 복사 (Deep Copy) : 새로운 객체인 속성(property)만 복사해서 사용하여 원본 값이 변경되지 않는다. (객체의 실제 값을 복사)
    • spread, assign() 등으로는 depth1까지만 깊은 복사 수행 (완벽한 깊은 복사 불가능)
    • 재귀적으로 복사를 수행하거나, JSON.stringify() 등을 사용해야 완벽한 깊은 복사 수행 가능
const obj = {
  mastermind: 'Joker',
  henchwoman: 'Harley',
  relations: ['Anarky', 'Duela Dent', 'Lucy'],
  twins: {
    'Jared Leto': 'Suicide Squad',
    'Joaquin Phoenix': 'Joker',
    'Heath Ledger': 'The Dark Knight',
    'Jack Nicholson': 'Tim Burton Batman',
  },
};
  
function passedByReference(refObj) {
  refObj.henchwoman = 'Adam West';
}
passedByReference(obj); // 객체를 함수의 인수로 전달
obj.henchwoman; // 'Adam West'

const assignedObj = obj;
assignedObj['relations'] = [1, 2, 3];
obj['relations']; // [1, 2, 3]

const copiedObj = Object.assign({}, obj);
copiedObj.mastermind = 'James Wood';
obj.mastermind; // 'Joker'

obj.henchwoman = 'Harley';
copiedObj.henchwoman; // 'Adam West'

delete obj.twins['Jared Leto'];
expect('Jared Leto' in copiedObj.twins).to.equal(false);

🌱알게된 점 & 느낀 점


  • 페어분과 함께 네비게이터와 드라이버를 번갈아 수행하였는데, 빈칸에 들어갈 것으로 옳은 정답이 무엇인지 서로 토론하고 테스트를 통과하는 과정들이 마치 퀴즈 맞추는 게임을 하는 것 같아서 재미있었다! 또한, 디테일하게 알지 못했던 부분을 JavaScript Koans를 작성하는 것을 통해 알게 되어 뜻깊은 시간이었다.

  • 반복 학습이 매우 중요한데, 나는 지나간 것에 관심을 쏟기가 어려운 것 같다. 기본기 중요하니까 관심을 조금이라도 가지고 공부하자...젭알

🌱추가 학습

  • Execution context & lexical environment
  • 스코프 체이닝
  • reduce()
  • this
  • TDD
  • TDZ

| 참고자료 |

profile
FE developer

0개의 댓글