본문 바로가기
JS

JS 5주차 개념 정리

by 썸맨 2023. 5. 25.
  • DOM : Document Object Model로 JS가 Document(HTML문서)를 알아 볼 수 있는 Object 형태로 Modeling(파싱, 해석) 한 결과물, 브라우저에 내장된 API 중 하나(이 API를 통해 DOM요소에 접근해 제어하면 브라우저의 많은 것을 컨트롤이 가능함), DOM은 계층 구조로 구성 되어 있어 부모자식 관계로 구성(호출 가능), DOM은 무조건!!! 브라우저 환경에서만 돌아간다(브라우저에 내장된 API), DOM은 node를 갖고 각각 node들은 속성과 메소드를 가짐
  • API : 주문 시 메뉴판과 같은 역할로 다른 시스템에서 데이터나 서비스를 요청 할 때 ★★★다른 시스템에서 제공하는 기능을 사용할 수 있도록 ★★★시스템과 사용자 간의 인터페이스 역할을 함(중간자 역할)
  • 브라우저는 DOM API를 제공함으로 DOM 객체에 접근 할 수 있도록 도와주는 역할을 함.  => DOM을 통해 HTML내용을 ★★★접근 할 수 있고 제어 할 수 있다★★★ ex) 현재 위치의 날씨를 확인 하려면 날씨 앱에서 제공하는 날씨 서비스 API를 호출 해 정보를 받아옴
  • DOM 관련 예) documennt.getElementById("id_name").innerHTML = 'Hello' , 여기서 나오는 동사는 메소드라 생각하고 명사는 속성이라 생각하기!(getElementById가 메소드 / innerHTML가 속성) / li태그들을 가져오고 싶을 때에는 getElementsByTagName('li')[으로 s가 꼭 붙어야 오류가 나질 않음(예제 li는 DOM 객체에 상에 여러 개 있으므로) , document.querySelector('footer').setAttribute('style', 'background-color : red;'); => footer부분의 스타일을 바꿔라~ , document.getElementsByTageName('h1')[0].innerText = 'abcdef' => h1태그의 제일 상단에 있는 것을 바꿔라
  • etc ) 선택자 : id=" "와 같이 특정 태그를 선택하기 위한 요소를 선택자라고 함. / 특정 선택자를 가져올 때는 querySelector를 제일 많이 사용 : document.querySelector('id/class/tag') - #id_name / .class_name / tag(앞에 아무것도 쓰지 않으면 태그 이름을 의미하는 것!), .children - 자식 노드 접근 / .parentElement(=parentNode) - 부모 노드 접근
  • 클라이언트 = 브라우저 = 사용자 (즉 우리가 크롬을 통해 클라이언트 역할)
  • 웹 페이지가 보여지는 과정 : 1. Client가 Server에게 요청(Request) -> 서버에서 응답을 준다(response) -> Client가 이응답을 수신하는데 응답의 형태가 HTML문서임(JS가 이해할 수 없음) -> 브라우저 내부의 렌더링 엔진을 통해 이 HTML문서를 해석함(JS가 알아들을 수 있도록!!)
  • 2. 렌더링을 마치면 JS가 해석한 내용을 토대로 트리형태의 DOM Tree를 구성함! -> CSS도 CSSON Tree가 만들어지는데 이 두개를 묶어 Render Tree를 구성한다.
  • 3. 화면에 그림을 그리기 위한 레이아웃을 계산 한 뒤 페인팅 과정으로 Client에게 제공
  • 클래스(class) : 클래스 문법은 ES6에서야 도입되었음(다른 언어들을 사용하던 개발자들이 JS로 넘오면서 클래스 처럼 사용하게 되면서 개발이 됨), 설계도(ex) 책상을 만들기 위한 설계도)로 이 설계도를 통해 특징(인스턴스)이 무엇인지 알 수 있음
  • 인스턴스(instance) : 클래스를 기반으로 만들어진 실제 사물(ex) 책상), 설계도에서 그려진 속성과 메소드를 가짐
class Car {
    constructor(modelName, modelYear, type, price) {
        this.modelName = modelName;
        this.modelYear = modelYear;
        this.type = type;
        this.price = price;                                          //property
    }

    makeNoise() {
        console.log(`${this.modelName}의 클락션 : 빵빵`) //해당 instance의 modelName을 가져와야 하므로 this를 꼭 붙임
    }

    printModelYear() {
        console.log(`${this.modelName}${this.modelYear}년도 모델 입니다.`)
    }
}

const car1 = new Car('K3', '2022', 'g', 2000);
const car2 = new Car('benz', '2021', 'd', 5000 );
const car3 = new Car('그랜져','2023', 'e', 6000 );

car1.makeNoise();
car1.printModelYear();

car2.makeNoise();
car2.printModelYear();

car3.makeNoise();
car3.printModelYear();

=>class에는 constructor(생성자 - 매개변수 값에 해당 클래스가 가져야 할 필수 값(property)을 넣어줘야 함)가 필수 & 메소드를 넣을 수 있음 (클래스는 객체를 정확하고 빠르게 많이 만들 수 있기에 사용)

  • Getters / Setters : 모든 객체 지향 프로그래밍 언어가 가진 기본적 개념, 생성자에서 property(필수 입력 값)를 정의 하는데 이 property에 접근하고 세팅하기 위해 지원함,
  • 1) get은 외부에서 해당 객체 property를 요구할 때 내부 값을 반환해서 출력해주는 역할(retrun이 필요하다!) ex) console.log(car1.modelName) - car1의 modelName을 get해서 출력해주세요.
  • 2) set은 value를 생성자의 property에 set해주는 것이므로 value라는 매개변수가 들어옴 -> 해당 property를 내부적으로 검증 후 세팅할 수 있도록 제약 조건을 만드는 것!
  • Getters / Setters 사용시 주의!! : 객체의 property값과 변수이름을 같게 하면 생성자의 검증 메소드인 set이 무한루프가 일어남(콜스택에 실행컨텍스트가 계속 쌓여 사이즈가 커져 오류 : Maxinun call stack size exceeded) 따라서 get/set이 나오는 생성자는 모든 this에 접근하는 property이름을 _(underscore)를 사용해 선언
//getters, setters 약속 : this 앞에는 _(underscore : private한 값을 가짐, 은밀하고 감춰야 할 떄)다붙이기!!!!!
class Rectangle {
    constructor(height, width) {
        this._height = height;              //_를 붙임으로 요 인스턴스 내에서만 쓰이기 위한 변수로 분리해놓겠다.
        this._width = width;
    }

    get width() {    //width를 위한 getter
        return this._width;
    }
    set width(value) { //width를 위한 setter, 외부로부터 값이 들어왔을 때 내부적으로 검증 후 세팅을 할 수 있음
        if(value <= 0) {
            console.log('[오류] 가로길이는 0보다 커야 합니다!')
        } else if(typeof(value) !== 'number') {
            console.log('[오류] 가로길이 입력 값이 숫자 타입이 아닙니다.')
        }
        this._width = value;
    }
    get height() {
        return this._height;
    }
    set height(value) {         //Maximum call stack size exceeded : 콜스택에 쌓이는 실행컨텍스트의 사이즈가 너무 커졌다(무한루프)
        if(value <= 0) {
            console.log('[오류] 세로길이는 0보다 커야 합니다!')
        } else if(typeof(value) !== 'number') {
            console.log('[오류] 세로길이 입력 값이 숫자 타입이 아닙니다.')
        }
        this._height = value;     //무한반복 돌기 때문에 _를 써서 이 인스턴스 내에서만 쓰기 위해서만 사용하겠다 라고 정한 것임
    }

    getArea() {
        const a = this._width * this._height
        console.log(`넓이는 => ${a}입니다.`)
    }
}

const rect1 = new Rectangle(10, 20);
const rect2 = new Rectangle(20, 20);
const rect3 = new Rectangle(15, 25);
rect1.getArea();

=>은밀하고 감춰야 할때(이 인스턴스 내부에서만 사용) _(underscore)를 붙이기!(getters / setters 사용 약속!)

 

* 위에 만든 Car Class에 요구사항 붙이기(각 인스턴스를 가져오고 세팅하는 메서드 & 이걸로 set해서 get하는 로직까지)

    get modelName() {
        return this._modelName;
    }
    //입력값의 검증까지 가능하기에 중요하다!
    set modelName(value) {
        //유효성 검사 실시!(잘못된 데이터가 들어오지 않도록)
        if(value.length <= 0) {
            console.log('[오류] 모델이름이 입력되지 않았습니다.')
            return; //오류 발생하면 바깥으로 빠져나오게 함 아래로 흐르지 않게
        } else if (typeof(value) !== "string") {
            console.log('[오류] 모델이름 오류 입니다.')
            return;
        }
        this._modelName = value;
    }

    get modelYear() {
        return this._modelYear;
    }
   
    set modelYear(value) {
        //유효성 검사 실시!(잘못된 데이터가 들어오지 않도록)
        if (typeof(value) !== "string") {
            console.log('[오류] 입력된년도 오류 입니다.')
            return;
        }else if (value.length !== 4) {
            //연도에 대한 유효성 검증 로직은 구글링하면 엄청~~많이 나옴
            console.log('[오류] 입력된 년도가 4자리가 아닙니다.')
            return;
        }
        this._modelYear = value;
    }

    get type() {
        return this._type;
    }
    set type(value) {
        //g, d, e가 아닌경우 오류
        if(value.length <= 0) {
            console.log('[오류] 타입이 입력되지 않았습니다.')
            return; //오류 발생하면 바깥으로 빠져나오게 함 아래로 흐르지 않게
        }else if(value !== 'g' && value !== 'd' && value !== 'e') { //셋 중 하나라도 포함이 안될 때 이므로 &&를 사용
            console.log('[오류] 타입 오류 입니다.')
            return;
        }
        this._type = value;
    }

    get price() {
        return this._price;
    }
    set price(value) {
        if(typeof(value) !== 'number') {
            console.log('[오류] 입력된 가격이 숫자가 아닙니다.')
            return;
        } else if(value < '100') {
            console.log('[오류] 가격이 100만원보다 작을 수 없습니다. 확인해주세요.')
            return; //오류 발생하면 바깥으로 빠져나오게 함 아래로 흐르지 않게
        }
        this._price = value;
    }

=> 위의 유효성 검사는 예시임 value.length는 문자열만 가능하기에 거기에 다른 숫자가 들어오게 된다면 undefined가 들어오게 됨 따라서 modelYear 검증 부분에 if(value.length !== 4)부분이 먼저 나오면 무조건 이 조건을 만족함(undefined.length는 4가 아니기 때문에) 그래서 string 타입인지를 먼저 비교 하고 나서 4자리인지 검사해야 함(조건에 따라 다르게 설정하기) => String(value).length로 비교 or value.toString().length로 명시적 형 변환으로 비교도 가능하긴 함.

※ 주의 : 숫자는 length를 쓸 수 없다! 사용하기 위해선 String으로 명시적 형변환을 통해 문자열로 바꾼 후 사용이 가능함!! (사용 시 undefined로 출력)

  • 상속(Inheritance) : 클래스를 유산으로 내려주는 주요 기능(부모가 가진 형과 질을 내려주는 것!) 부모 <-> 자식, 상속받는 자식 클래스는 기본적으로 생성자를 생성 하지 않아도 됨. 상속받을 자식 클래스에게 꼭! extends 부모클래스명을 추가!
//동물 전체에 대한 클래스
class Animal {
    constructor (name) {
        this.name = name;
    }

    speak() {
        console.log(`${this.name} says!`)
    }
}

class Dog extends Animal {
    //부모로부터 상속받은 메소드를 재정의 하는 행위 OverRiding(오버라이딩)
    speak() {
        console.log(`${this.name} barks!`)
    }
}

const cuttyPuppy1 = new Dog('봉구');
cuttyPuppy1.speak();

=> 메소드를 재정의 행위는 오버라이딩임. (부모로 부터 내려받은 메소드를 재정의 해서 braks!가 출력이 됨)

 

* 상속 예시 2)

class Car {
    constructor(modelName, modelYear, type, price) {
        this.modelName = modelName;
        this.modelYear = modelYear;
        this.type = type;
        this.price = price;
    }

    makeNoise() {
        console.log(`${this.modelName}의 클락션 : 빵빵`)
    }
    printModelYear() {
        console.log(`${this.modelName}${this.modelYear}년도 모델 입니다.`)
    }
}

class ElectronicCar extends Car {
    //만약 생성자의 재정의가 필요하다면 재정의 꼭 해주기(생성자의 내용이 부모클래스랑 다르면 꼭 써줘야 함)
    constructor(modelName, modelYear, price, chargeTime) { //전기차는 타입이 고정이 되어 있기 때문에 type필요x
        //부모클래스에게도 자식클래스의 생성자가 만들어진걸 알려주기
        super(modelName, modelYear, 'e', price); //super는 부모의 constructor임 -
        this._chargeTime = chargeTime;
    }

    set chargeTime(value) {       //검증 부분
        this._chargeTime = value;
    }
    get chargeTime() {
        return this._chargeTime;
    }
}

const eleCar1 = new ElectronicCar('테슬라', '2018', 9000, 60);
eleCar1.makeNoise();
eleCar1.printModelYear();

console.log(eleCar1._chargeTime); //getters, 60 출력
eleCar1.chargeTime = 20;
console.log(eleCar1._chargeTime); //20 출력

=> 위처럼 자식 클래스의 생성자를 재정의(오버라이딩) 하려면 생성자를 꼭 다시 선언하고 내부에 super로 부모것을 가져오는 부분도 적어줘야 함! - 부모와 자식의 생성자가 다르기 때문에 sync를 맞추기 위해 적어주는 것임

  • Static Method(정적 메소드) : 내부 메소드가 static임, 굳이 인스턴스화 시킬 필요가 없는(객체를 만들 필요가 없을 경우) 경우에 주로 사용함.
class Calculator {      //생성자 만들어서 인스턴스화 시키지 않고 바로 접근하고 싶다!
    static add(a, b) { //static을 사용하면 Calculator.으로 바로 호출이 가능하다!
        console.log("[계산기] 더하기를 진행합니다.")
        return a+b;
    }  
    static substract(a, b) {
        console.log("[계산기] 빼기를 진행합니다.")
        return a-b;
    }
}

console.log(Calculator.add(3, 5));
console.log(Calculator.substract(10, 6));

=> 계산기 경우 new로 객체를 만들 필요가 없으니 바로 접근해서 호출하려 한다! 이때 정적 메소드가 사용됨.

  • Closure : 함수와 그 함수가 선언된 LE(Lexical Environment)와의 조합, 외부함수(outer())보다 중첩함수(innerFunc())가 더 오래유지 될 때! 중첩함수는 이미 생명주기가 종료된 외부 함수의 변수를 ★★★여전히★★★ 참조 할 수 있다.
const x = 1;
 
function outer() {
  const x = 10;
  const inner = function () {
    console.log(x);
  };
  return inner; //리턴을 해주고
}
 
const innerFunc = outer(); //outer함수를 실행해서 innerFunc에 담는다! 즉 return 부분을 innerFunc에 담는다
//-------------여기는 outer의 실행context popup되어서 종료됨
innerFunc(); //outer가 popup되어서 사라졌음에도 innerFunc의 x는 여전히 10을 출력할 수 있다. -> 클로저!!

=> outer()함수를 실행 해 innerFunc에 담는다(outer()의 return 부분을 innerFunc에 넣음), InnerFunc 선언시점 후로 outer()의 실행컨텍스트는 콜스택에서 popup되어서 종료(사라짐) but!!! 클로저로 인해서 outer()가 콜스택에서 사라졌음에도 innerFunc는 outer()의 x를 여전히 가져와 출력할 수 있다!! (여전히 참조하고 있기 때문에 Garbage Collector가 이 x를 수거해 가지 않았음)

  • Garbage Collector : 참조카운트 0인 값들을 수거! but 참조하고 있는 값들은 가져가지 않음(안쓰는 애들만 가져감)
const x = 1;

function outerFunc() {
  const x = 10;
 
  function innerFunc() {
    console.log(x); //10
  }

  innerFunc();
}

outerFunc();

=> 여기서의 innerFunc에서의 x는 내부에 없음 따라서 외부 환경(outer)으로 시선을 돌려 outerFunc에 있는 10을 가져옴(outer는 이 innerFunc가 실행 될 때의 당시 변수 정보인 LE이다 - 함수 선언!! 당시의 외부 변수등의 정보)

  • Lexical Scope : JS엔진은 함수를 ★★★어디서 호출★★★했는 지가 아닌 ★★★어디에 정의★★★ 했는 지에 따라서 스코프(상위 스코프)를 결정한다!!!
  • outer : 실행컨텍스트에 있는 outer는 외부 LE에 대한 참조값이다!, outer는 함수 선언(정의)되는 시점에 결정이 됨!!!
const x = 1;
 
function outerFunc() {
  const x = 10;
  innerFunc(); // 1
}

function innerFunc() {
  console.log(x); // 1
}
outerFunc();

=> 스코프 접근 불가 ex) outerFunc내부에 innerFunc가 호출되고 있음에도 불구하고 Lexical Scope를 따르는 프로그래밍 언어이기에 innerFunc에서는 outerFunc의 x에 접근 할수가 없다(★서로 별도의 스코프 가짐★) 따라서 x는 1이 출력

  • 클로저 복습(매우매우 중요!) : 상태를 안전하게 은닉하면서 변경하고 유지하기 위해 사용
let num = 0

// 카운트 상태 변경 함수
const increase = function () {
    // 카운트 상태를 1만큼 증가시킨다.
    return ++num;
};

=> 변수가 은닉 되지 않았기에 중간에 num = 100; 이렇게 넣어버리면 바뀌어버리는 치명적인 단점

 

* 보완해보기 : 1. 카운트 상태(num 변수 값)는 increase 호출 전까지 변경되면 안된다(중간에 바껴버리면 치명적)  2. 카운트 상태는 오직 increase 함수만이 변경해야 한다!  3. 결국 전역변수 num이 문제

-> if) 지역변수로 넣는다면? 문제가 생김 : increase()가 호출될 때마다 num이 초기화 되어서 1만 출력이 됨.

-> 클로저 적용해보기!

const increase = (function () {
    let num = 0;

    return function() {
    return ++num;
    };                //increase는 요놈이다 -> 얘는 항상 외부 함수를 참조하고 있기 때문에!! 가비지 컬렉터가 가져가지 않음
})();

console.log(increase()); //1
console.log(increase()); //2
console.log(increase()); //3

=> 1. 코드 실행 시, '즉시 실행 함수'가 호출됨(increase의 function() 내부) -> 이 함수가 반환이 되어 increase에 할당!

2. increase 변수에 할당된 함수는 자신이 정의된 위치에 의해 결정된 상위 스코프인 즉시 실행 함수의 LE를 기억하는 클로저다 -> increase는 let num = 0; 을 기억함.

3. 즉시 실행 함수는 소멸(콜스택에서 popup됨) -> but num은 클로저로 인해 남아있음(증가가 실행이 됨)

※결론 : num은 초기화되지 않고 외부에서 접근할 수없는 은닉된 값 & increase에서만 num 변경 가능

 

숙제) 숫자맞추기

구현 1. 남아있는 숫자를 보여줄 수 있게 아래의 = 이후 작성

 $remainingCount.innerHTML = `${11 - numGuesses}`;

=> 총 10번의 기회이고 numGuesses의 초기값이 1이므로 11을 빼야 정상 작동(0이라면 10을 빼야함)

 

구현 2. 유저에게 메시지를 보여줄 수 있도록 아래의 영역 구현

 $guessingResult.innerHTML = `<h1>${message}</h1>`;

=> 사용자가 정답을 맞추기 위해 범위 내의 숫자를 입력하면 기회가 남아있는 경우에 compareGuess 함수로 정답의 수와 비교를 해서 거기에 맞는 displayMessage(출력할message)함수를 실행하므로 이것을 $guessingResult부분에 출력을 하면 됨(여기서 빽틱으로 h1태그를 감싸면 자동으로 css에서 h1태그를 대상으로 지정한 css를 적용해서 출력하게 됨.)

 

구현 3. 유저가 원의 크기로 정답을 유추하기 쉽게 showCircle함수 이용해 구현하기, 함수 작업이 끝나면 해당 div에 원의 이름 입력하기.

function makeAnswerCircle(correct, CIRCLE_NAME) {
  showCircle(correct, CIRCLE_NAME, $answerCircleArea).then(div => { //showCircle에서 Promise를 반환하기 때문에 .then으로 성공을 했을 경우를 처리 가능
    div.id = "answerCircle"; //이미 만들어놓은 css사용하기 위해서
    div.append(CIRCLE_NAME); //동그라미 안에 글자가 나오도록!!
  })
}

=>위에서 게임을 시작할 때, makeAnswerCircle(randomNumber, "answer")을 넣어줘서 실행 하므로 정답의 수의 크기에 원을 그려줌(그려주는 위치는 $answerCircleArea부분에) 그리고 난 후에 이 원안에 answer이름을 넣어줌.

function makeGuessCircle(guess) {
  const CIRCLE_NAME = "guess"
  showCircle(guess, CIRCLE_NAME, $guessCircleArea).then(div => { //showCircle에서 Promise를 반환하기 때문에 .then으로 성공을 했을 경우를 처리 가능
    div.id = "guessCircle"  //이미 만들어놓은 css사용하기 위해서
    div.append(CIRCLE_NAME); //동그라미 안에 글자가 나오도록!!
  }) 
}

=>이 원은 입력을 하게 되면 checkGuess함수가 실행되면서 도전 기회가 아직 남아있을 때 기존의 원을 지우고 추측값을 비교하는 compareGuess함수를 실행 해 해당 추측값의 원 크기를 그려줌 - makeGuess(guess, "guess") (함수안에 원 이름의 변수를 생성해서 넣어도 되고 위에 make처럼 인자를 받아 그걸로 변수 이름을 넣어도 된다.

'JS' 카테고리의 다른 글

JS 내용 정리  (1) 2023.06.03
JS 메소드  (4) 2023.05.30
JS 4주차 개념 정리  (0) 2023.05.25
JS 3주차 개념 정리  (0) 2023.05.23
개발일지(2023.05.22)  (0) 2023.05.22