인덱스를 사용하는 이유에 대해서 생각해보자 왜 사용할까?

  • 조건을 만족하는 듀플(들)을 빠르게 조회하기 위해서
  • 빠르게 정렬하거나 그룹핑 하기 위해서

사용한다고 볼 수 있다. 한마디로 정의하자면 

특정 조건을 만족하는 데이터를 빠르게 찾기 위해 인덱스를 사용한다고 보면 된다.

 

MySql 기준으로 인덱스를 생성하려면

 

CREATE INDEX 인덱스명 ON 테이블명;

CREATE INDEX 인덱스명 ON 테이블명(컬럼명);

 

중복을 허용하지 않는 UNIQUE INDEX

 

CREATE UNIQUE INDEX  인덱스명 ON 테이블명(컬럼명1, 컬럼명2);

 , 를 사용하여 여러 필드에 인덱스를 설정해 줄 수 있다. 

 

인덱스는 대부분의 DBMS에서 프라이머리 키로 설정하면 자동으로 생성해준다.

 

인덱스를 확인하려면

 

SHOW INDEX FROM 테이블명; 

쿼리를 사요하면 된다.

 

SELECT 쿼리에서 내가 원하는 인덱스를 사용하고 싶으면 

SELECT * FROM 테이블명 INDEX (인덱스명) WHERE 조건절;

 

인덱스를 사용하면 조회시간을 단축할 수 있다. 그러면 최대한 많으면 많을수록 좋은게 아닐까?

그럴것 같지만 아니다.

 

인덱스를 생성할때마다 인덱스를 저장하는 부가적인 데이터가 생성되고, 테이블에 write (수정,삽입,삭제) 가 빈번하다면 인덱스도 동일하게 write 해줘야 하기 때문에 부하가 발생할 수 있고 저장공간의 낭비가 생길 수 있다.

그렇기 때문에 불필요한 인덱스는 만들지 말아야한다.

 

또 Full Scan 방식이 더 좋은 경우도 있다.

  • table에 데이터가 별로 없는 경우 (수십~수백건)
  • 조회하려는 데이터가 테이블의 상당 부분을 차지할때

 

 

 

 

'Back-End > DB' 카테고리의 다른 글

SQL 파싱과 최적화  (3) 2024.07.25
Lock 과 트랜잭션 동시성 제어 (Oracle)  (3) 2024.07.23
인덱스의 기본 (2)  (0) 2023.10.12
인덱스의 기본  (3) 2023.10.11
DB 설계를 잘못하면 생기는 일  (1) 2023.09.13

데이터베이스 스키마 설계를 잘못하면 어떤 일이 발생할까?

empt_id empi_name birth_date position salary dept_id dept_name
1 D 1995 null 10000 1001  DEV

위 와 같은 테이블이 있다고 가정을 해보자 임직원의 정보와 해당 임직원의 부서 정보를 모두 같이 가지고 있는 테이블 구조이다.

그렇다면 위와 같이 테이블을 설계했다면 발생하게 되는  일을 생각해보자

 

직원이 한명 입사하여 새로운 임직원의 정보를 등록해보자

empt_id empi_name birth_date position salary dept_id dept_name
1 D 1995 null 10000 1001  DEV
2 Simon 1999 null 3000 1001 DEV

부서 아이디와 부서 명이 중복된 데이터가 생기게 된다. 이렇게 되면 저장 공간의 낭비가 발생하게되고, 실수로 인한 데이터 불일치 가능성이 존재하게된다. (부서명을 실수로 DEB로 입력한다면 같은 부서이지만 부서명이 다르게 된다.)

 

이번에는 부서의 배치를 받지 못한 임직원이 입사를 했다고 생각해보자

empt_id empi_name birth_date position salary dept_id dept_name
1 D 1995 null 10000 1001  DEV
2 Simon 1999 null 3000 1001 DEV
3 Jen 2000 null null null null

Jen 의 부서정보는 모두 null 데이터가 들어가게 된다. 할 수 있다면 null 값은 최대한 적에 사용하는것이 좋다.

 

이번에는 새로운 부서가 창설되었다고 생각해보자

empt_id empi_name birth_date position salary dept_id dept_name
1 D 1995 null 10000 1001  DEV
2 Simon 1999 null 3000 1001 DEV
3 null null null null 1002 QA

QA 부서 저장 용 row를 생성해야 하는데 위 예시와 같이 매끄럽지도 않고 null 데이터도 많이 들어가게 된다. (empt_id 는 primary key 이기 때문에 임시 값이 들어갔다.)

 

이렇게 만들어진 부서에 임직원이 최초로 들어왔다고 생각해보자

empt_id empi_name birth_date position salary dept_id dept_name
1 D 1995 null 10000 1001  DEV
2 Simon 1999 null 3000 1001 DEV
3 null null null null 1002 QA
4 Ujon 2001 null 1500 1002 QA

그렇다면 위에 empt_id = 3 에 해당하는 정보는 더 이상 필요 없게 된다.

 

 

위 테이블을 정상적으로 구분하여 만들어보자

empt_id empt_name birth_date position salary
1 D 1995 null 10000
2 Simon 1999 null 3000
3 Ujon 2001 null 1500
dept_id dept_name
1001 DEV
1002 QA

위와 같이 2개의 테이블로 나눠 데이터를 저장하게 되면 해결될 것이다.

'Back-End > DB' 카테고리의 다른 글

SQL 파싱과 최적화  (3) 2024.07.25
Lock 과 트랜잭션 동시성 제어 (Oracle)  (3) 2024.07.23
인덱스의 기본 (2)  (0) 2023.10.12
인덱스의 기본  (3) 2023.10.11
인덱스를 사용하는 이유  (0) 2023.09.14

List 

  • 같은 종류의 아이템을 저장
  • 순서를 보장
  • 중복을 허용

 

Set

  • 같은 종류의 아이템을 저장
  • 순서를 보장하지 않음
  • 중복을 허용하지 않음

 

만약 월드컵에서 한번이라도 골을 넣은 순서를 저장한다고 하면 무엇을 사용해야 할까?

중복을 허용하면 안되고, 순서가 필요없기 때문에 set을 사용하면 될 것이다.

 

'자료구조&알고리즘' 카테고리의 다른 글

시간복잡도  (0) 2023.09.19
Array에서 Index는 왜 0부터 시작할까?  (2) 2023.09.15
Array List 와 Linked List  (1) 2023.09.08
Array 와 List 의 차이  (1) 2023.09.08
Queue(큐)  (0) 2023.09.08

내부 메서드 [[Call]] 과 [[Construct]]

함수 선언문 또는 함수 표현식으로 정의한 함수는 일반적인 함수로서 호출할 수 있는 것은 물론 생성자 함수로서 호출할 수 있다.

 

// 함수는 객체다
function foo() {}

// 함수는 객체이므로 프로퍼티를 소요할 수 있다.
foo.prop = 10;

// 함수는 객체이므로 메서드를 소유할 수 있다.
foo.method = function() {
	console.log(this.prop);
};


foo.method(); // 10

 

함수는 객체이지만 일반 객체와는 다르다. 일반 객체는 호출할 수 없지만 함수는 호출할 수 있다.

 

함수가 일반 함수로서 호출되면 함수 객체의 내무 메서드 [[Call]]이 호출되고 new 연산자와 함께 생성자 함수로서 호출되면 내부 메서드 [[Construct]]가 호출된다. 

 

function foo () {}

// 일반적인 함수로서 호출: [[Call]]이 호출된다.
foo();

// 생성자 함수로서 호출: [[Construct]]가 호출된다.
new foo();

내부 메서드 [[Call]]을 갖는 함수 객체를 callable이라 하며, 내부 메서드 [[Construct]]를 갖는 함수 객체를 constructor, 갖지 않는 함수를 non-construct라고 부른다. callable은 호출할 수 있는 객체, 즉 함수를 말하며, constructor 는 생성자 함수로서 호출할 수 있는 함수, non-constructor는 객체를 생성자 함수로서 호출할 수 없는 함수를 의미한다.

 

constructor 와  non-constructor의 구분

자바스크립트 엔진은 어떻게 constructor와 non-constructor를 구분할까?

  • constructor : 함수 선언문, 함수 표현식, 클래스(클래스도 함수다)
  • non-constructor : 메서드(ES6 메서드 축약 표현), 화살표 함수
// 일반 함수 정의 : 함수 선언문, 함수 표현식
function foo() {}
const bar = function() {};
// 프로퍼티 x의 값으로 할당된 것은 일반 함수로 정의된 함수다. 이는 메서드로 인정하지 않는다.
const baz = {
  x : fun
};

// 일반 함수로 정의된 함수만이 constructor다. 
new foo(); // -> foo {}
new bar(); // -> bar {}
new baz.x(); // -> x {}


// 화살표 함수 정의
const arrow = () => {};

new arrow(); // TypeError : arrow is not a constructor

// 메서드 정의 : ES6의 메서드 축약 표현만 메서드로 인정한다.
const obj = {
	x() {}
};

new obj.x(); // TypeError: obj.x is not a constructor

 

new 연산자

일반 삼수와 생성자 함수에 특별한 형식적 차이는 없다. new 연산자와 함께 함수를 호출하면 해당 함수는 생성자 함수로 동작한다. new 연산자와 함께 함수를 호출하면 해당 함수는 생정자 함수로 동작한다. 다시 말해, 함수 객체의 내부 메서드 [[Call]]이 호출되는 것이 아니라 [[Constructor]] 가 호출된다. 단 new 연산자와 함께 호출하는 함수는 non-constructor가 아닌 constructor이어야 한다.

 

// 생성자 함수로서 정의하지 않은 일반 함수
function add(x, y) {
	return x + y;
}

// 생성자 함수로서 정의하지 않은 일반 함수를 new 연산자와 함께 호출
let inst = new add();

// 함수가 객체를 반환하지 않있으므로 반환문이 무시된다. 따라서 빈 객체가 생성되어 반환된다.
console.log(inst); // {}


// 객체를 반환하는 일반 함수
function createUser(name, role) {
	return { name, role };
}

// 일반 함수를 new 연산자와 함께 호출
inst = new createUser('Lee', 'admin');
// 함수가 생성한 객체를 반환한다.
console.log(inst); // {name: 'Lee', role: 'admin'}

함수 내부에서 new.target을 사용하면 new 연산자와 함께 생성자 함수로서 호출되었는지 확인할 수 있다. new 연산자와 함께 생성자 함수로서 호출되면 함수 내부의 new.target은 함수 자신을 가리킨다. new 연산자 없이 일반 함수로서 호출된 함수 내부의 new.target은 undefined 다.

 

// 생성자 함수
function circle(radius) {
	// 이 함수가 new 연산자와 함께 호출되지 않았다면 new.target은 undefined다.
    if(!new.target) {
    	// new 연산자와 함께 생성자 함수를 재귀 호출하여 생성된 인스턴스를 반환한다.
        return new circle(radius);
    }
    
    this.radius = radiusl
    this.getDiameter = function() {
    	return 2 * this.radius;
    };
}


// new 연산자 없이 생성자 함수를 호출하여도 new.target을 통해 생성자 함수로서 호출된다.
const circle = Circle(5);
console.log(circle.getDiameter());

Object 생성자 함수

new 연산자와 함께 Object 생성자 함수를 호출하면 빈 객체를 생성하여 반환한다. 빈 객체를 생성한 이후 프로퍼티 또는 메서드를 추가하여 객체를 완성할 수 있다.

 

생성자 함수(constructor)란 new 연산자와 함께 호출하여 객체(인스턴스)를 생성하는 함수를 말한다. 생성자 함수에 의해 생성된 객체를 인스턴스(instance)라 한다.

// 빈 객체의 생성
const person = new Object();

// 프로퍼티 추가
person.name = 'Lee';
person.sayHello = function() {
	console.log('Hi' + this.name);
};

console.log(person); // {name: 'Lee', sayHello: f}
person.sayHello(); // Hi Lee

 

 

생성자 함수

객체 리터럴에 의한 객체 생성 방식은 직관적이고 간편하다. 하지만 객체 리터럴에 의한 객체 생성 방식은 단 하나의 객체만 생성한다. 따라서 동일한 프로퍼티를 갖는 객체를 여러 개 생성해야 하는 경우 매번 같은 프로퍼티를 기술해야 하기 때문에 비효율적이다. 

 

예제를 보자

const circle1 = {
	radius : 5,
    getDiameter() {
    	return 2 * this.radius;
    }
};



console.log(circle1.getDiameter()); // 10


const circle2 = {
	radius : 10,
    getDiameter() {
    	return 2 * this.radius;
    }
};


console.log(circle2.getDiameter()); // 20

위 처럼 객체 리터럴에 의해 객체를 생성하는 경우 프로퍼티 구조가 동일함에도 불고하고 매번 같은 프로퍼와 메서드를 기술해야 한다. 그렇기 때문에 만약 수십 개의 객체를 생성해야 한다면 문제가 크다.

 

 

생성자 함수에 의한 객체 생성 방식의 장점

생성자 함수에 의한 객체 생성 방식은 마치 객체(인스턴스)를 생성하기 위한 템플릿(클래스)처럼 생성자 함수를 사용하여 프로퍼티 구조가 동일한 객체 여러 개를 간편하게 생성할 수 있다.

// 생성자 함수
function Circle(radius) {
	// 생성자 함수 내부의 this는 생성자 함수가 생성할 인스턴스를 가리킨다.
    this.radius = radius;
    this.getDiameter = function() {
    	return 2 * this.radius;
	};
};


// 인스턴스 생성
const circle1 = new Circle(5); // 반지름이 5인 Circle 객체를 생성

console.log(circle1.getDiameter()); // 10
더보기

this는 객체 자신의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수다. this가 가리키는 값, 즉 this바인딩은 함수 호출 방식에 따라 동적으로 결정된다.

함수 호출 방식 this가 가리키는 값(this 바인딩)
일반 함수로서 호출 전역 객체
메서드로서 호출 메서드를 호출한 객체(마침표 앞의 객체)
생성자 함수로서 호출 생성자 함수가(미래에) 생성할 인스턴스

생성자 함수는 이름 그대로 객체(인스턴스)를 생성하는 함수다. 하지만 자바와 같은 클래스 기반 객체지향 언어의 생성자와는 다르게 그 형식이 정해져 있는 것이 아니라 일반 함수와 동일한 방법으로 생성자 함수를 정의하고 new 연산자와 함께 호출하면 해당 함수는 생성자 함수로 동작한다. 만약 new 연산자와 함께 생성자 함수를  호출하지 않으면 생성자 함수가 아니라 일반 함수로 동작한다.

 

생성자 함수의 인스턴스 생성 과정

1. 인스턴스 생성과 this 바인딩

암묵적으로 빈 객체가 생성된다. 이 빈 객체가 생성자 함수가 생성한 인스턴스다. 그리고 암묵적으로 생성된 빈 객체, 즉 인스턴스는 this에 바인딩된다. 생성자 함수 내부의 this가 생성자 함수가 생성할 인스턴스를 가리키는 이유가 바로 이것이다. 이 처리는 런타임 이전에 실행된다.

 

function Circle(radius) {
	// 1. 암묵적으로 인스턴스가 생성되고 this에 바인딩된다.
    console.log(this); // Circle{}
    
    
    this.radius = radius;
    this.getDiameter = function() {
    	return 2 * this.radius;
    };
}

 

2. 인스턴스 초기화

생성자 함수에 기술되어 있는 코드가 한 줄씩 실행되어 this에 바인딩되어 있는 인스턴스를 초기화한다. 즉, this에 바인딩되어 있는 인스턴스에 프로퍼티나 메서드를 추가하고 생성자 함수가 인수로 전달받은 초기값을 인스턴스 프로퍼티에 할당하여 초기화하거나 고정값을 할당한다. 

 

3. 인스턴스 반환

생성자 함수 내부의 모든 처리가 끝나면 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다.

만약 this가 아닌 다른 객체를 명시적으로 반환하면 this가 반환되지 못하고 return 문에 명시간 객체가 반환된다.

function Circle(radius) {
	// 1. 암묵적으로 빈 객체가 생성되고 this에 바인딩된다.
    
    // 2. this에 바인딩되어 있는 인스턴스를 초기화한다.
    this.radius = radius;
    this.getDiameter = function() {
    	return 2 * radius;
    };
    
    return {};
}

// 인스턴스 생성. Circle 생성자 함수는 명시적으로 반환한 객체를 반환한다.
const circle = new Circle(1);

console.log(circle); // {}

따라서 생성자 함수 내부에서 명시적으로 this가 아닌 다른 값을 반환하는 것은 생성자 함수의 기본  동작을 훼손한다. return 문을 반드시 생략해야 한다.

우선 List는 무엇인가?

List란 어떤 순서가 있는 데이터의 집합이다. 

 

그렇다면 List의 종류는 어떤것이 있을까?

  1. Array List
  2. Linked List 

두 종류가 있다.

 

1. Array List 

Array List

위 그림과 같이 Array List 는 연속적인 공간에 순차적으로 데이터를 저장한다. Array List 의 장점은 Indexing이 가능 하다는 것이다. 단점은 추가/ 삭제가 어렵다는 것이다. 사이즈가 고정되어 있다. 

 

 

 

2. Linked List 

Linked List

위 그림과 같이 Linked List 는 비연속적인 공간에 순서대로 데이터를 저장한다. Linked List 의 장점은 추가/ 삭제가 쉽다는 것이다. 단점은 위치 탐색을 할때 오래 걸린다는 것이다. 같은 데이터가 들어있으면 Linked List 의 메모리가 좀 더 많다.

 

 

 

두 자료구조의 공통점은 둘 다 순서가 있다는 것이다. 

'자료구조&알고리즘' 카테고리의 다른 글

Array에서 Index는 왜 0부터 시작할까?  (2) 2023.09.15
List 와 Set  (1) 2023.09.12
Array 와 List 의 차이  (1) 2023.09.08
Queue(큐)  (0) 2023.09.08
Stack  (0) 2023.09.08

Array 와 List 의 차이는 무엇일까?

 

우선 Array

연속적인 메모리에서 같은 종류의 아이템들을 저장할 수 있는 자료구조다. [ ] 를 사용하여 인덱스를 활용할 수 있다. 

인덱스를 사용하면 사이즈가 얼마나 크던지 원하는 자료에 접근할때 걸리는 시간이 동일하다.

 

 

List 는 무엇일까?

순서를 가지며 추가, 삭제, 탐색이 가능한 ADT(Abstract Data Type) 이다. Array에 비해서 추상적이다 내부적으로 어떻게 동작하는지 알려주지 않는다. 

* ADT는 구조의 속성과 행위를 설명한다. 

 

따라서 

  • add
  • remove
  • get
  • contains

같은 operation을 사용 가능하다.

 

List는 어떻게 구현할 수 있을까?

  • Array (= ArrayList)
  • Linked node (= LinkedList) 

크게 두 가지를 사용하여 구현 가능하다. 

 

 

구현에 관점에서 List 는 ADT이고 안에 있는 ArrayList나 LinkedList 등이 자료구조라고 할 수 있다.

 

 

'자료구조&알고리즘' 카테고리의 다른 글

Array에서 Index는 왜 0부터 시작할까?  (2) 2023.09.15
List 와 Set  (1) 2023.09.12
Array List 와 Linked List  (1) 2023.09.08
Queue(큐)  (0) 2023.09.08
Stack  (0) 2023.09.08

큐는 

  • Last In Last Out : LILO
  • First In First Out : FIFO

구조를 가지고 있다.

큐는 순서를 보장한다. 그렇다면 어떤 곳에 사용할 수 있을까? 

 

은행 창구 번호표 처리, 메신저의 메신저 함, 티켓팅 시스템 등등 순서 보장이 필요한 곳에서 사용할 수 있을것 같다.

'자료구조&알고리즘' 카테고리의 다른 글

Array에서 Index는 왜 0부터 시작할까?  (2) 2023.09.15
List 와 Set  (1) 2023.09.12
Array List 와 Linked List  (1) 2023.09.08
Array 와 List 의 차이  (1) 2023.09.08
Stack  (0) 2023.09.08

스택은

  • Last In First Out : LIFO
  • First In Last Out : FILO

 특징을 가지고 있다.

 

가장 먼저 들어간 것이 마지막에 나오고 가장 나중에 들어간 것이 제일 처음에 나온다.

 

예들 들어 Ctrl + Z 같은 것들을 구현할 수 있을 것이다. 

 

Stack 에 집어 넣는 동작을 Push, 빼는 동작을 Pop 이라고 한다.

'자료구조&알고리즘' 카테고리의 다른 글

Array에서 Index는 왜 0부터 시작할까?  (2) 2023.09.15
List 와 Set  (1) 2023.09.12
Array List 와 Linked List  (1) 2023.09.08
Array 와 List 의 차이  (1) 2023.09.08
Queue(큐)  (0) 2023.09.08

데이터 프로퍼티와 접근자 프로퍼티

프로퍼티는 데이터 프로퍼티와 접근자 프로퍼티로 구분할 수 있다.

  • 데이터 프로퍼티 : 키와 값으로 구성된 일반적인 프로퍼티다
  • 접근자 프로퍼티 : 자체적으로는 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 함수로 구성된 프로퍼티다

데이터 프로퍼티

데이터 프로퍼티는 다음과 같은 프로퍼티 어트리뷰트를 갖는다. 프로퍼타 어트리뷰트는 자바스크립트 엔진이 프로퍼티를 생성할 때 기본값으로 자동 생성된다.

const person = {
	name : 'Lee'
};

// 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크럽터 객체를 취득한다.
console.log(Object.getOwnPropertyDescriptor(person, 'name'));
// { value : "Lee", writable : true, enumerable : true, configurable : ture }

Object.getOwnPropertyDescriptor 메서드가 반환한 프로퍼티 디스트립터 객체를 살펴보면 value 프로퍼티의 값은 'Lee'이다.

이것은 프로퍼티 어트리뷰트 [[Value]]의 값이 'Lee'인 것을 의미한다. 그리고 writable, enumerble, configurable 프로퍼티의 값은 모두 true 이다. 

 

 

접근자 프로퍼티

접근자 함수는 getter/setter 함수라고도 부른다.

const person = {
	// 데이터 프로퍼티
    firstHName : 'Ungmo',
    lastName : 'Lee',
    
    //fullName은 접근자 함수로 구성된 접근자 프로퍼티다.
    // getter 함수
    get fullName() {
    	return `${this.firstName} ${this.lastName}`;
    },
    set fullName(name) {
    	[this.firstName, this.lastName] = name.split(' ');
    }
};

console.log(person.firstName + ' ' + person.lastName); // Ungmo Lee

person.fullName = 'Hee Lee';
console.log(person); // {firstName: "Hee", lastName: "Lee"}

 

 

프로퍼티 정의

프로퍼티 정의란 Object.defineProperty 메서드를 사용하여 프로퍼티의 어트리뷰트를 정의하는 것이다.

const person = {};

// 데이터 프로퍼티 정의
Object.defineProperty(person, 'firstName', {
	value : 'Ungmo',
    writable : true,
    enumerable : true,
    configurable : true
});

Object.defineProperty(perosn, 'lastName', {
	value : 'Lee'
});

let descriptor = Object.getOwnPropertyDescriptor(pserson, 'firstName');
console.log('firstName', descriptor);
// firstName {value: 'Ungmo', writable: true, enumerable: true, configurable: true}

// 디스트립터 프로퍼티를 누락시키면 undefined, false가 기본값이다.
descriptor = Object.getOwnPropertyDescriptor(person, 'lastName');
console.log('lastName', descriptor);
// lastName {value : 'Lee', writable: false, enumerable: false, configurable: false}

//[[Enumerable]]의 값이 false인 경우
// 해당 프로퍼티는 for...in 문이나 Object.keys 등으로 열거할 수 없다.
// lastName 프로퍼티는 [[Enumerable]]의 값이 false이므로 열거되지 않는다.
console.log(Object.keys(person)); // ['firstName']

// [[Writable]]의 값이 false 인 경우 해당 프로퍼티의 [[Value]]의 값을 변경할 수 없다.
// lastName 프로퍼티는 [[Writable]]의 값이 false 이므로 값을 변경할 수 없다.
// 이때 값을 변경하면 에러는 발생하지 않고 무시된다.
person.lastName = 'Kim';

// [[Configurable]]의 값이 false인 경우 해당 프로퍼티를 삭제할 수 없다.
// lastName 프로퍼티는 [[Configurable]]의 값이 false이므로 삭제할 수 없다.
// 이때 프로퍼티를 삭제하면 에러는 발생하지 않고 무시된다.
delete person.lastName;

 

 

객체 변경 방지

객체는 변경 가능한 값이므로 재할당 없이 직접 변경할 수 있다. 즉, 프로퍼티를 추가하거나 삭제할 수 있고, 프로퍼티의 값을 갱신할 수 있으며, Object.defineProperty 또는 Object.defineProperties 메서드를 사용하여 프로퍼티 어트리뷰트를 재정의할 수도 있다.

 

 

객체 확장 금지

 

Object.preventExtensions 메서드는 객체의 확장을 금지한다. 객체 확장 금지란 프로퍼티 추가 금지를 의미한다.

즉, 확장이 금지된 객체는 프로퍼티 추가가 금지된다. 확장이 가능한 객체인지 여부는 Object.isExtensible 메서드로 확인할 수 있다.

const person = {name: 'Lee'};

// person 객체는 확장이 금지된 객체가 아니다.
console.log(Object.isExtensible(person)); // true

// person 객체의 확장을 금지하여 프로퍼티 추가를 금지한다.
Object.preventExtensions(person);

console.log(Object.isExtensible(person)); // false

// 프로퍼티 추가가 금지된다.
person.age = 20; // 무시. strict mode에서는 에러
console.log(person); // {name : 'Lee'}

// 프로퍼티 삭제는 불가능하지만 삭제는 가능하다.
delete person.name;
console.log(person); // {}

// 프로퍼티 정의에 의한 프로퍼티 추가도 금지된다.
Object.defineProperty(person, 'age', {value:20});
// TypeError: Cannot define property age, object is not extensible

 

 

객체 밀봉

Object.seal 메서드는 객체를 밀봉한다. 객체 밀봉이란 프로퍼티 추가 및 삭제와 프로퍼티 어트리뷰트 재정의 금지를 의미한다.

밀봉된 객체는 읽기와 쓰기만 가능하다. 밀봉된 객체인지 여부는 Object.isSealed 메서드로 확인할 수 있다.

const person = {name:'lee'};

// person 객체를 밀봉된 객체가 아니다.
console.log(Object.isSealed(person)); // false

// person 객체를 밀봉하여 프로퍼티  추가,삭제,재정의를 금지한다.
Object.seal(person);

// 밀봉된 객체는 configurable이 false 다
console.log(Object.getOwnPropertyDescriptors(person));


person.age = 20; // 프로퍼티 추가 금지 무시된다.
console.log(perosn); {name:'lee'}

delete person.name; // 프로퍼티 삭제도 불가능 무시된다.

// 프로퍼티 재정의가 금지된다.
Object.defineProperty(person, 'name', {configurable: true});

 

객체 동결

Object.freeze 메서드는 객체를 동결시킨다. 동결된 객체는 읽기만 가능하다. 동결된 객체인지 여부는 Object.isFrozen 메서드로 확인할 수 있다.

const person = {name:'lee'};

// person 객체를 동결하여 프로퍼티 추가, 삭제, 재정의, 쓰기를 금지한다.
Object.freeze(person);

console.log(Object.getOwnPropertyDescriptors(person));
// 

perosn.age = 20; // 추가 금지 무시된다.

delete person.name; // 삭제도 무시된다.

person.name = 'Kim'; // 값 갱신 무시된다.

// 프로퍼티 어트리뷰트 재정의도 금지된다.
Object.defineProperty(person, 'name', { configurable: true });

 

 

불변 객체

위에서 알아본 변경 방지 메서드들은 얕은 변경 방지로 직속 프로퍼티만 변경이 방지되고 중첩 객체까지는 영향을 주지는 못한다. 따라서 Object.freeze 메서드로  객체를 동결하여도 중첩 객체까지는 동결할 수 없다.

const person = {
	name : 'lee',
    address : {city : 'seoul'}
};

// 얕은 객체 동결
Object.freeze(person);

// 직속 프로퍼티만 동결한다.
console.log(Object.isFrozen(person)); // true
// 중첩 객체까지는 동결하지 못한다.
console.log(Object.isFrozen(person.address)); // false

따라서 중첩 객체까지 동결하여 변경이 불가능한 읽기 전용의 불변 객체를 구현하려면 객체를 값으로 갖는 모든 프로퍼티에 대해 재귀적으로 Object.freeze 메서드를 호출해야 한다.

function deepFreeze(target) {
	if(target && typeof target === 'object' && !Object.isFrozen(target)) {
    	Object.freeze(target);
    }
    
    Object.keys(target).forEach(key => deepFreeze(target[key]));
}

const person = {
	name : 'lee',
    address: {city: 'seoul'}
};

deepFreeze(person);

console.log(Object.isFrozen(person)); // true

console.log(Object.isFrozen(person.address)); // true

+ Recent posts