카카오 클라우드 스쿨 1기 개발자 과정 4주차 - 2

EUNLEE·2022년 7월 5일
0

2022.07.05 화요일

🐠 Data Type(지난 시간에 이어서)

  1. Object type
    JavaScript는 객체 지향 언어(객체 기반 언어) → JavaScript를 구성하는 대부분은 객체!
    객체는 mutable 특성(변할 수 있는)을 갖는다.
    객체: 0개 이상(property가 없어도 객체)의 property의 집합이고 각 property는 key/value의 쌍으로 구성된다.
    /* 객체 literal로 객체 생성하기 */
    var person = { // person은 객체
    		name: '홍길동', // property
    		age: 20 // name과 age는 property key, '홍길동'과 20은 property value
    }

💡 property 작성 시 key: name 으로 쓰는 것이 권장된다.(key에 바로 콜론 : 붙이기. 띄어쓰기x)
key: name (o) , key : name (x)
크게 상관은 없지만 전자가 권장된다는 것을 알아두자.

💡 JSON과 자바스크립트 객체는 전혀 상관없다. JSON은 일반적으로 사용하는 데이터 형태의 표준.

객체를 생성하는 방법

  1. 객체 literal
    “가장 간단한 방법”
         var obj = {
             'name': '홍길동',
             'printName': function myPrint(){
                 console.log(`내 이름은 ${this.name}`); // this는 현재 사용하는 객체를 지칭하는 레퍼런스
             }
         };
         
         console.log(typeof obj); //object
     
  2. Object 생성자 함수
  3. 생성자 함수 (user defined)
  4. Object.create() method
  5. ’ES6’ → class
    → 어떻게 만드느냐에 따라 약간씩 객체가 달라짐.(사용법 등)

Property의 key

  • 문자열
    - 따옴표 생략 가능(Naming Rule에 부합하는 경우)
            var obj = {
                name: '홍길동', // key가 식별자 명명 규칙에 맞다면 quotation으로 감싸지 않아도 된다. 'name'(o), name(o)
                printName: function myPrint(){ // 함수가 property의 value가 될 수 있다.
                    console.log(`내 이름은 ${this.name}`); // this는 현재 사용하는 객체를 지칭하는 레퍼런스
                },
                "!myPhone": '010-1234-5678', // key가 식별자 명명 규칙에 맞지않다면 quotation으로 감싸주어야한다.
                10: 300 // key를 숫자(10)으로 써도 자동 casting해서 string('10')으로 인식한다.
            };
  • symbol

Property의 value

  • JavaScript에서 value로 인식되는 모든 것

    💡 JavaScript에서는 함수도 객체이므로 함수를 값으로 가질 수 있다.
    → 함수의 method
    (다른 언어와 다르게 자바스크립트에서는 method도 property로 취급된다. 통상적으로 method라고 부를뿐이다.)

Property의 동적 추가, 삭제

  • . (dot rotation)
    - 일반적인 notation을 따를 경우
  • [ ] (bracket notation)
    • 삭제는 delete notation
    /* property의 동적 추가, 삭제 */
    var obj = {
        myName: '홍길동'
    }
    
    obj.myAge = 20;
    obj['myAge'] = 30;
    obj[10] = 300;
    //obj."!myPhone" = '010-1234-5678'; // 불가능
    obj["!myPhone"] = '010-1234-5678';
    
    console.log(obj); //{ '10': 300, myName: '홍길동', myAge: 30, '!myPhone': '010-1234-5678' }
    
    delete obj.myAge;
    console.log(obj); //{ '10': 300, myName: '홍길동', '!myPhone': '010-1234-5678' }

기타 다른 형태의 property

  • code로 알아봅시다.
        var obj = {
            10: '문자열 10으로 인식됩니다.',
            let: 'let도 keyword로 사용가능하지만 혼동되므로 권장되지 않아요.',
            myName: '홍길동',
            '!myName': 'naming rule에 맞지않는 경우는 따옴표로 감싸주어야 합니다.',
            myName: 'key를 중복해서 쓰면 마지막에 나온 것을 씁니다.'
        }
        
        console.log(obj);
        // 출력 결과
        /* 
        {
            '10': '문자열 10으로 인식됩니다.',
            let: 'let도 keyword로 사용가능하지만 혼동되므로 권장되지 않아요.',
            myName: 'key를 중복해서 쓰면 마지막에 나온 것을 씁니다.',
            '!myName': 'naming rule에 맞지않는 경우는 따옴표로 감싸주어야 합니다.'
        }
         */
        
        console.log(obj.myAddress); //정의되지 않은 key를 쓰면 error가 아닌 undefined가 출력된다.

ES6에서 추가된 객체 literal 확장표현방식

  • code로 알아봅시다.
        let x = 1;
        let y = 2;
        
        // 식별자를 이용해서 property 정의하기
        const obj = {x, y} // 변수의 이름을 key로 변수의 값을 value로 갖는다.
        console.log(obj); //{ x: 1, y: 2 }
        /* property의 value로 작성된 함수를 method로 나타내기 */
        // 위 아래 
        // let myObj = {
        //     name: "홍길동",
        //     printName: function(){ // 얘는 원래 자바스크립트에서 말하는 method가 아님.
        //         console.log(this.name);
        //     }
        // }
        
        let myObj = {
            name: "홍길동",
            printName(){ // 자바스크립트라는 언어에서 얘기하는 method(축약표현으로 되어있는 형태)
                console.log(this.name);
            } 
        }
        
        myObj.printName(); //홍길동

🐠 Primative value(원시값) vs 객체

원시값 - immutalble(불변함)

객체 - mutable(가변함)

var obj = {name: ‘홍길동'}


유사 배열 객체(Array-like object)

Primative value를 객체처럼 쓰는 순간 내부슬롯에 값이 만들어지고 배열처럼 사용할 수 있도록 각각의 primative들이 만들어지고 여러 method가 자동적으로 상속받아서 많이 생긴다.

let myStr = ‘홍길동’;

myStr[0];

/* Primative value(원시값) vs 객체 */
let myStr = 'Hello'; // primative value

// primative value를 마치 객체(배열)처럼 사용할 수 있다.
console.log(myStr[0]); //H
console.log(myStr.length); //5

myStr[0] = 'h'; // 그림의 key 0의 값이 H에서 h로 바뀐다고 생각하면 된다.(0: H -> 0:h)
// primative value 자체의 값은 변하지 않는다.
console.log(myStr); //Hello // 그림으로 따지면 맨 상단 내부 슬롯의 Hello
console.log(myStr[0]); //H // 그림으로 따지면 맨 상단 내부 슬롯의 Hello의 H

같은 객체를 참조할 때의 내부 구조


🐠 Function(함수)

function: 일련의 과정을 수행하기 위한 statement를 { } (중괄호)를 이용해서 하나의 실행단위로 만들어 놓은 것

함수를 사용하는 이유

반복적인 code → 함수화

  • 유지보수성이 올라간다.
  • 오류가 발생 확률이 낮아진다. → 코드의 신뢰도가 높아진다.

함수 정의(definition) & 호출(call, invoke)

변수는 선언한다는 표현을 하지만 함수는 정의한다고 표현한다.

function add(x, y) { // add는 함수이름,  x, y는 parameter(매개변수)
	return x + y;  // x + y는 리턴값
}

add(3, 5); // 3, 5는 argument(인수)

// parameter와 argument 구분할 것

함수 literal

literal은 “값” 이다.

var func = function add(x, y){
    return x + y;
}
// function부터 } 닫는 괄호까지가 "함수 literal"이다.
  • 함수 이름은 (다른 이름과 구별하기 위한) 식별자(identifier)이다.
  • 함수 이름은 함수 내부에서만 사용할 수 있다.(외부로 노출되지 않는다.) 함수 이름 자체를 사용할 수 없고 식별자를 이용해서 함수를 사용할 수 있다. (그래서 보통 익명함수로 쓴다.)
    var myFunc = function add(x, y){
        return x + y;
    }
    
    console.log(add(3, 5)); // ReferenceError: add is not defined
    console.log(myFunc(3, 5)); //8
  • named function(기명 함수): 이름이 있는 함수
  • anonymous function(익명 함수): 이름이 없는 함수
    • literal로 변수에 저장할 때
  • 함수는 객체이다.

함수를 정의(definition)하는 방법

  1. 함수 선언문

    함수 literal과 똑같이 생겼지만 변수에 저장할 수 없다!!

    function add(x,y){
    	return x + y;
    }
    /* 함수 선언문 */
    function foo(){ // foo라는 식별자. 내부적으로는 foo라는 이름의 변수가 생기고 함수 객체가 생성됨.
        console.log('foo 함수');
    }
    
    // 객체 하나 덜렁 있는 것.. 그냥 숫자 3 하나 딱 있는거랑 똑같다.
    // (1 + 2) * 3
    (function bar() {
        console.log('bar 함수');
    })
    
    foo();
    bar(); // 사용불가능
  2. 함수 표현식

    함수 이름 자체를 사용할 수 없고 식별자를 이용해서 함수를 사용할 수 있다. (그래서 보통 익명함수로 쓴다.)

    var myFunc = function(x, y){
    	return x + y;
    }
  3. Function 생성자 함수

    사용하기 굉장히 불편하므로 권장되지 않는다.

    var add = new Function('x', 'y','return x+y');
  4. ‘ES6’ → 화살표함수(Arrow function)

    화살표 함수에서는 this를 사용할 수 없다.

    var add = (x, y) => x + y;

함수 선언문 vs 함수 표현식 hoisting

자바스크립트 엔진에 의해서 (런타임 하기 전에) 묵시적으로 함수 이름과 똑같은 변수가 만들어진다.


/* 함수 선언문 vs 함수 표현식 hoisting */

foo(); // 호출된다.
add(); // TypeError: add is not a function

// 함수 선언문
function foo(){
    console.log('foo 함수');
}

// 함수 표현식
var add = function bar(){
    console.log('bar 함수');
}

함수 선언문은 hoisting의 위험이 있고 상식적으로 프로그래밍을 하는 입장에서 함수 표현식을 사용하는 것이 맞다.

💡 모든 식별자는 hoisting이 일어난다.

함수 호출(call, invoke)

함수는 함수 이름으로 호출하지 않는다. 함수에 대한 identifier(식별자)를 이용하여 호출한다.

  • JavaScript의 함수는 Overloading이 발생하지 않는다. → 인자의 개수가 달라도 호출 가능하다. “arguments라는 내부객체를 이용”하기 때문이다.

    Overloading: 같은 함수의 이름을 가지고 있지만 파라미터(매개변수) 및 리턴 타입 등의 특징이 다른 여러개의 함수를 만드는 것.

    // 함수 선언문
    function add(x, y){
        return x + y;
    }
    console.log(add(2, 5));
    // 인자를 순서대로 mapping하므로 x는 2로 초기화되고, y는 초기화되지 않아 undefined가 된다.
    console.log(add(2)); //NaN (연산 불가)
    // 인자의 개수가 달라도 함수 호출은 가능하다.
    // 위에 선언한 함수 add는 내부적으로 arguments를 다 저장해둔다.
    // arguments에 대해 알아보자.
    function add() {
        // add(2, 3, 4)를 호출하면 arguments에 [2, 3, 4]가 저장된다.
        let sum = 0;
        // arguments는 유사배열객체(Array-like Object)이다.
        // 유사배열객체는 배열과 비슷하게 생겼지만 배열은 아니다.(배열의 특수한 method를 사용할 수 없음.)
        // 모든 유사배열객체는 length라는 property를 가지고 배열처럼 index를 이용해서 access 가능. 당연히 순환 가능.
        for(let i=0; i<arguments.length; i++){
            sum += arguments[i];
        }
        return sum;
    }
    
    console.log(add(2, 3, 4)); //9
  • 함수에 return 구문이 없으면 undefined를 반환한다.
    /* 함수에 return 구문이 없으면 undefined를 반환한다. */
    function add() {
        let sum = 0;
        for(let i=0; i<arguments.length; i++){
            sum += arguments[i];
        }
        //return sum;
    }
    
    console.log(add(2, 3, 4)); //undefined

즉시 실행 함수(IIFE; Immediately Invoked Function Expression)

함수를 선언함과 동시에 call(invoke)

  • 함수의 재사용이 불가능하다. (다시 호출할 수 없다.)
    /* IIFE(즉시 실행 함수) */
    (function add(){
        let x = 10;
        let y = 20;
        console.log(x + y);
    }());
    
    // 노란색 형광펜 부분을 객체(함수 literal) 하나라고 생각한다.
    // 빨간생 형광펜 부분이 함수 호출부
    // ( )로 감싸주지 않으면 자바스크립트 엔진이 문법 오류라고 생각하기 때문에 ( )로 감싸준다.
  • 한번 쓰고 버리기 때문에 굳이 함수에 이름을 붙일 필요가 없어서 익명함수로 쓴다.
  • 언제 사용할까? → 가장 대표적인 예로 전역변수를 지역변수화 할 수 있다. (예시) 다음과 같이 html 파일에서 여러 JavaScript 파일을 땡겨올 때
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
        <script src="index1.js"></script>
        <script src="index2.js"></script>
        <script src="index3.js"></script>
        <script src="index4.js"></script>
        <script src="index5.js"></script>
    </head>
    <body>
        소리없는 아우성!!
    </body>
    </html>
    여기서 자바스크립트 파일들은 전역변수 공간을 공유하고 있다. 그런데 이 때 index1.js에서 정의한 변수를 index2.js에서 같은 이름으로 사용하는 동일한 변수명으로 인한 문제 등으로 충돌이 발생할 수 있다. 따라서 이런 경우에 ( )로 묶어서 즉시 실행 함수로 작성한다면 이 파일들은 전역변수 공간이 아닌 지역변수 공간을 쓰게 되고 충돌이 발생하지 않는다.

중첩 함수(nested function)(또는 내부 함수(inner function))

중첩 함수(내부 함수)를 가지고 있는 바깥쪽 함수는 외부 함수(outer function)라고 한다.

/* 중첩 함수(nested function)(또는 내부 함수(inner function)) */

var x = 100; // global scope(전역변수)
var y = 200; // global scope(전역변수)
function outer(){ // outer function
    let x = 0; // function level scope(지역변수)

    function inner(){ // inner function
        let x = 10; // function level scope(지역변수), scope가 다르기 때문에 위에 있는 x와는 다른 x이다.
        console.log(y); //200
        **// 자바스크립트 엔진은 처음에는 자신의 지역 scope에서 변수를 찾다가 찾는 변수가 없으면 상위 scope에서 찾는다.
				// 하위 scope -> 상위 scope로 계속해서 찾아 올라간다.
        // 전역변수는 가장 상위 scope이기 때문에 찾는데 오래걸린다. 그래서 전역변수를 많이 쓰면 좋지 않다는 것!
        // scope chain: 모든 scope는 chain으로 연결되어 있다.**
    }
}

일급 객체(first-class citizen 또는 first-class object)

💡 일급 객체가 되기위한 4가지 조건
1. 익명의 literal로 생성 가능 → 동적으로 생성이 가능
2. 객체가 변수나 자료구조에 저장 가능
3. 객체를 다른 함수의 인자로 전달 가능
4. 함수의 리턴값으로 객체를 사용

→ JavaScript의 함수는 위 조건들을 만족한다. 즉 JavaScript 함수는 “일급 객체”라고 할 수 있다.

/* 일급 객체 */

// 잘 만든 함수가 존재한다. (아래 예시의 repeat 함수)
// 그런데 이 함수의 기능을 변경(추가)하고 싶다.. -> 그 방법은?
// 1. 원래 있던 함수를 수정하자! -> 오류 발생 risk가 너무 크다.
// 2. 함수를 새로 추가해서 만들자! -> 이것도 이상해
// 3. 함수를 추상화시켜서 인자로 받아서 사용하자! -> 채택!!

function repeat(n, f){
    for(var i=0; i<n; i++){
        f(i);
    }
}

let logAll = function(k){
    console.log(k);
}

let logOdd = function(i){
    if(i%2){
        console.log(i);
    }
}

repeat(5, logAll);
//repeat(5, logAll()); // 이거는 안된다!! ()는 함수를 호출하라는 뜻! logAll은 식별자임.

// logAll과 logOdd와 같은 함수들을 뭐라고 부를까??
// **콜백함수(Callback Function)**: 다른 함수의 인자로써 넘겨진 후 특정 이벤트에 의해 호출되는 함수.
// 그럼 콜백함수를 받아서 하나의 융합 함수를 만드는 repeat함수를 뭐라고 부를까??
// **고차함수(Higher-Ordered Function)**: 함수를 인자로 전달받거나 함수를 결과로 반환하는 함수.

// Callback function은 Higher-ordered function과 결합한다.

🐠 Property

Scope

scope: “식별자가 유효한 범위”

scope chain: JavaScript Engine이 identifier를 찾을 때(identifier resolution) 사용하는 메커니즘

💡 JavaScript Engine은 코드를 실행할 때 문맥(context)을 고려해서 실행한다.
→ 현재 실행중인 code가 어디에 존재하는 code인지와 code 주변 정보를 파악해서 실행
lexical Environment를 고려해서 실행한다. → (를 구현한 것이 바로) “execution context”

함수가 호출되었을 때

  • scope를 함수가 호출된 곳을 기준으로 설정 → dynamic scope(동적 scope)
  • scope를 함수가 정의된 곳을 기준으로 설정 → static scope(lexical scope)(정적 scope)
    • 거의 대부분의 언어(JavaScript 포함)가 lexical scope 사용
/* scope */
var x = 1; // 전역 scope의 전역변수

function foo(){
    var x = 10; // 지역 scope의 지역변수
    console.log(x); //10
}
function bar() {
    console.log(x); //1
}

foo();
bar();

💡 전역변수를 사용하면
1. 가독성이 나빠진다.(오류의 여지가 많다.)
2. 메모리 resource를 소모한다.
3. 변수를 늦게 찾는다.(효율이 안좋다)
4. 다른 file과 변수 충돌이 생길 수 있다.
→ 꼭 필요하면 경우가 아니라면 사용을 줄이자.

내부 slot과 내부 method

객체는 Property의 집합이며 각각의 Property는 property attribute를 가진다.

JavaScript 객체는 내부 slot내부 method를 가진다.

  • 내부 slot과 내부 method 둘다 [[…]] 대괄호 2개로 감싸진 형태로 나타난다.
  • 내부 slot과 내부 method 또한 객체가 갖는 property이다.
  • 개발자가 직접적으로 사용할 수 없다.(JavaScript Engine에 의해 사용된다.)
    • 상속과 관련된 [[Prototype]]obj.__Proto__를 사용하면 내부 slot에 접근할 수 있는 방법을 제공해준다.
    • [[Prototype]] == __Proto__ 이라고 생각하면 된다. [[Prototype]] 도 결국 객체 타입의 property다.
    • 모든 내부 slot에 이렇게 접근할 수 있는 것은 아니다.

💡 브라우저에서 직접 눈으로 확인해보자

var obj = {
    name: '홍길동'
};
console.dir(obj) // dir로 객체의 세부 내용을 확인할 수 있다. -> 브라우저에서 확인해야 한다.


Object type의 [[Prototype]]이라는 내부슬롯을 가지고 있음을 확인할 수 있다.

Property attribute

property를 생성할 때 해당 property의 상세를 나타내는 값(기본값으로 정의된다.)

💡 Property의 상세(Property attribute)
1. [[Value]] property의 값
2. [[Writable]] property의 값을 수정할 수 있는지 여부(key에 대한 value를 고정시킬지 말지)
3. [[Enumerable]] 해당 property가 열거될 수 있는지 여부
4. [[Configurable]] property attribute를 재정의 할 수 있는지 여부
위 4가지가 모두 내부슬롯[[…]]으로 되어있다.

/* Property Attribute를 확인해보자. */
const person = {
    name: 'Lee',
    age: 20
};

// property attribute를 직접적으로 접근할 수 없기 때문에
// getOwnPropertyDescriptor 또는 getOwnPropertyDescriptors로 확인한다.
// property attribute를 descriptor 객체로 만들어서 가져올 수 있다.
// person이라는 객체의 name이라는 property에 대한 내용을 들고와!
console.log(Object.getOwnPropertyDescriptor(person, 'name')); // 하나의 property 가져오기
//{ **value**: 'Lee', **writable**: true, **enumerable**: true, **configurable**: true }

console.log(Object.getOwnPropertyDescriptors(person)); // 모든 property(name, age)에 대해 가져오기
// {
//     name: {
//       value: 'Lee',
//       writable: true,
//       enumerable: true,
//       configurable: true
//     },
//     age: { value: 20, writable: true, enumerable: true, configurable: true }
//   }

Property define(프로퍼티 정의하기)

/* property 정의하기 */
const person = {
    age: 20
};

// person.name = '홍길동';
Object.defineProperty(person, 'name', {
    value: '홍길동',
    writable: false,
    enumerable: false,
    configurable: true
});

console.log(person); // 브라우저에서 확인해야한다.(node로 확인불가능)
console.log(Object.getOwnPropertyDescriptor(person, 'name'));

person.name = '아이유'; // writable: false 이므로 바뀌지 않는다.
console.log(person); //홍길동

// Object.keys(): property의 key만 뽑아서 유사배열객체로 보여줌.
console.log(Object.keys(person)); // name의 enumerable: false 이므로 age만 나온다.

for(let idx in person){
    console.log(idx); // property key (person의 key값들이 출력된다.)
    console.log(person[idx]); // property value (person의 value들이 출력된다.)
}

📌 JavaScript의 for in과 for of 참고
자바스크립트 for in vs for of 반복문 정리

0개의 댓글