항해/TIL

TIL(2/10) / DOM, 클래스, 클로저

yeeendy 2024. 2. 10. 22:24

DOM(Document Object Modeling)

  • HTML 문서의 계층적 구조와 정보를 표현하며 이를 제어할 수 있는 API, 즉 프로퍼티와 메서드를 제공하는 트리 자료구조
    브라우저 환경에서 확인 할 수 있다. Node환경에서는 확인 불가능
  • 브라우저에 내장되어 있기 때문에 HTML 내용을 javascript로 접근, 제어 가능
  • 모든 요소와 요소의 어트리뷰트, 텍스트를 각각의 객체로 만들고 이들 객체를 부자 관계를 표현할 수 있는 트리 구조
  • 모든 DOM의 node들은 '속성'과 '메서드'를 갖고있음
    • Node 객체의 속성은 값 
      => 해당 객체의 특성을 나타내는 값을 가져오거나 설정
    • 메서드는 동작을 수행
      => 해당 객체가 수행하는 작업을 나타내는 함수
//Finding

// 해당 id명을 가진 요소 하나를 반환합니다.
document.getElementById("id명")

// 해당 선택자를 만족하는 요소 하나를 반환합니다.
document.querySelector("선택자")

// 해당 class명을 가진 요소들을 배열에 담아 인덱스에 맞는 요소를 반환합니다.
document.getElementsByClassName("class명")[인덱스]

// 해당 태그명을 가진 요소들을 배열에 담아 인덱스에 맞는 요소를 반환합니다.
document.getElementsByTagName("태그명")[인덱스]

// 해당 선택자를 만족하는 모든 요소들을 배열에 인덱스에 맞는 요소를 반환합니다.
document.querySelectorAll("선택자명")[인덱스]

// 새로운 노드를 생성합니다.
const div = document.createElement('div');
document.body.append(div);
document.body.append(div);


// Changing


// 이 둘은 차이가 있어요!
element.innerHTML = new html content
element.innerText = new text

// style을 바꿔요.
element.style.property = new style

//method를 통해 클래스를 추가해봐요.
element.setAttribute(attribute, value)

// 어랏? 그럼 이런것도 가능
element.setAttribute("style", "background-color:red;");
element.style.backgroundColor = "red";

클래스

클래스

 

  • 값처럼 사용할 수 있는 일급 객체
  • 특징
    ✔︎ 무명의 리터럴로 생성할 수 있다. 즉, 런타임에 생성이 가능하다
    ✔︎ 변수나 자료구조(객체, 배열 등)에 저장할 수 있다.
    ✔︎ 함수의 매개변수에게 전달할 수 있다.
    ✔︎ 함수의 반환값으로 사용할 수 있다.
  • 클래스 몸체에서 정의할 수 있는 메서드는 constructor(생성자), 프로토타입 메서드, 정적 메서드의 세 가지가 있다.
  • 클래스는 생성자 함수와 유사하게 동작하지만 몇 가지 차이가 있다
    1. 클래스를 new 연산자 없이 호출하면 에러가 발생한다.
      하지만 생성자 함수를 new 연산자 없이 호출하면 일반 함수로서 호출된다.
    2. 클래스는 상속을 지원하는 extends와 super 키워드를 제공한다.
      하지만 생성자 함수는 extends와 super 키워드를 지원하지 않는다.
    3. 클래스는 호이스팅이 발생하지 않는 것처럼 동작한다.
      하지만 함수 선언문으로 정의된 생성자 함수는 함수 호이스팅이, 함수 표현식으로 정의한 생성자 함수는 변수 호이스팅이 발생한다.
    4. 클래스 내의 모든 코드에는 암묵적으로 strict mode가 지정되어 실행되며 strict mode를 해제할 수 없다.
      하지만 생성자 함수는 암묵적으로 strict mode가 지정되지 않는다.
    5. 클래스의 constructor, 프로토타입 메서드, 정적 메서드는 모두 프로퍼티 어트리뷰트 [[Enumrable]]의 값이 false다.
      다시 말해, 열거되지 않는다.
  • 인스턴스
//클래스 선언문
class Person{
    //생성자
    constructor(name){
        this.name = name;  // name 프로퍼티는 public하다
    }

	//프로토타입 메서드
	sayHi(){
		console.log(`Hi! My name is ${this.name}`);
	}

	//정적 메서드
	static sayHello(){
		console.log('Hello!');
	}
}

//인스턴스 생성
const me = new Person('Lee');

//인스턴스의 프로퍼티 참조
console.log(me.name);  // Lee
//프로토타입 메서드 호출
me.sayHi(); // Hi! My name is Lee
//정적 메서드 호출
Person.sayHello();  // Hello!

 

인스턴스 생성
  • 클래스는 생성자 함수이며 new 연산자와 함께 호출되어 인스턴스를 생성
class Person {}
const me = new Person();
console.log(me);  // Person {}
  • 함수는 new 연산자의 사용 여부에 따라 일반 함수로 호출되거나
    인스턴스 생성을 위한 생성자 함수로 호출되지만
    클래스는 인스턴스를 생성하는 것이 유일한 존재 이유이므로 반드시 new 연산자와 함께 호출
    -> 클래스를 new 연산자 없이 호출하면 타입 에러 발생
  • 클래스 표현식으로 정의된 클래스의 경우,
    클래스를 가리키는 식별자(Person)를 사용해 인스턴스를 생성하지 않고 기명 클래스 표현식의 클래스 이름(MyClass)을 사용해 인스턴스를 생성하면 에러가 발생
const Person = class MyClass {};
const me = new Person();
console.log(MyClass);  // ReferenceError
const you = new MyClass();  // ReferenceError
  • 기명 함수 표현식과 마찬가지로 클래스 표현식에서 사용한 클래스 이름은 외부 코드에서 접근 불가능
생성자 함수
  • return 값을 만들지 않는다
  • 인스턴스가 만들어질 때 실행되는 코드
  • 생성자 함수의 함수 몸체에서 수행해야 하는 것
    ✔︎ 인스턴스를 생성
    ✔︎ 생성된 인스턴스를 초기화(인스턴스 프로퍼티 추가 및 초기값 할당)
ES5 클래스 작성 문법
  • 클래스를 작성하는데 function 키워드를 사용
  • 클래스 이름은 함수 이름 규칙을 따른다
  • 일반적으로 클래스 이름의 첫 글자는 대문자
function MyClass() {
    // constructor(생성자) 함수
}
MyClass.prototype.methodName = function() {
	// 메서드 내용
}
  • 위와 같이 클래스 생성자를 정의하고, prototype 객체에 메서드를 추가
  • 이후 해당 클래스의 인스턴스를 생성할 때 new 키워드를 사용
let myInstance = new MyClass();
//ES5 작성 예시
function Car(brand, name, color) {
    this.brand = brand;
    this.name = name;
    this.color = color;
}
Car.prototype.drive = function() {
    console.log(this.name + '가 운전을 시작합니다.');
}
let avante = new Car('hyundai', 'avante', 'black');
avante.color;  // black
avante.drive();  // 'avante'가 운전을 시작합니다.'
ES6 클래스 작성 문법
//ES5 예제를 ES6로 만들어보기
class Car {
    constructor(brand, name, color) {
        this.brand = brand;
        this.name = name;
        this.color = color;
    }
    drive() {
        console.log(${this.name}가 운전을 시작합니다.);
    }
}

let avante = new Car('hyundai', 'avante', 'black');
avante.color; // black
avante.drive(); // 'avante'가 운전을 시작합니다.'

 

Getter, Setter
  • class에서는 getter와 setter를 사용하여 class 속성에 접근할 수 있다.
  • getter : 속성 값을 반환하는 메소드
  • setter : 속성 값을 설정하는 메소드
class Rectangle {
  constructor(height, width) {
    // underscore : private, _ 안 쓰면 에러남
    this._height = height;
    this._width = width;
  }

  get width() {
    return this._width;
  }
  //외부에서 들어왔을 때 내부에서 검증하고 세팅할 수 있음
  set width(value) {
    if (value <= 0) {
      console.log("[오류] 가로길이는 0보다 커야 합니다");
      return;
    } else if (typeof value !== "number") {
      console.log("[오류] 세로길이로 입력된 값이 숫자타입이 아닙니다!");
      return;
    }
    this._width = value;
  }
  get height() {
    return this._height;
  }
  set height(value) {
    if (value <= 0) {
      console.log("[오류] 세로길이는 0보다 커야 합니다");
      return;
    } else if (typeof value !== "number") {
      console.log("[오류] 세로길이로 입력된 값이 숫자타입이 아닙니다!");
      return;
    }
    this._height = value;
  }
  getArea() {
    const a = this._height * this._width;
    console.log(`넓이는 ${a}입니다`);
  }
}

const rectangle1 = new Rectangle(10, 20);
rectangle1.getArea(); // 넓이는 200입니다

 

상속(Inheritance)
  • class는 상속을 통해 다른 class의 기능을 물려받을 수 있다
    • 상속을 받는 class : subclass / derived class
    • 상속을 하는 class : superclass / base class
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 me = new Dog("dd");
me.speak();

클로저

  • 클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합
  • 렉시컬 스코프 : 스코프에 대한 참조는 함수 정의가평가되는 시점에 함수가 정의된 환경(위치)에 의해 결정
  • outer : 정의된 환경에 대한 정보를 저장하는 곳
  • 외부 함수보다 중첩 함수 더 오래 유지되는 경우, 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 여전히 참조할 수 있다.
  • 클로저를 사용하는 이유
    • 상태를 안전하게 변경하고 유지하기 위해 사용
    • 은닉하다(특정 함수에게만 상태 변경을 허용한다)
    • 캡슐화 : 프로퍼티와 메서드를 하나로 묶는 것

예시)

// 함수가 호출될 때마다 호출된 횟수를 누적하여 출력하는 카운터를 구현해보자
let num = 0;

const increase = function () {
    return ++num;
};

console.log(increase());
// num = 100; // 치명적인 단점이 있어요.
console.log(increase());
console.log(increase());

// 보완점
// num은 increase 함수가 호출되기 전까지변경되지 않고 유지돼야 함
// num은 increase 함수만이 변경할 수 있음
// 전역변수인 num이 문제다 -> 지역변수로 바꿔보자
// 클로저를 활용해 리팩토링

const increase = (function () {
  let num = 0;
  // 클로저
  return function () {
    return ++num;
  };
})();

// 이전 상태값을 유지
console.log(increase()); //1
console.log(increase()); //2
console.log(increase()); //3
// 기능을 확장해보자

const increase = (function () {
  let num = 0;
  // 클로저인 메서드 (increase, decrease)를 갖는 객체를 반환
  // property는 public -> 은닉되지 않는다. 
  return {
    increase() {
      return ++num;
    },
    decrease() {
      return num > 0 ? --num : 0;
    },
  };
})();

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

console.log(counter.decrease()); // 1
console.log(counter.decrease()); // 0