본문 바로가기
모던 자바스크립트 튜토리얼

[SOPT] JS 스터디 2주차

by 치우치지않는 2023. 4. 22.

객체

객체에 대한 개략적인 설명

자바스크립트 -> 총 8개의 자료형. 7개 = 하나의 데이터만 담을 수 있어, 원시형 

객체 = 다양한 데이터를 담을 수 o (따라서, 객체는 원시형이 아니다!)

키(key, 문자형만(혹은 심볼형. 만약 문자형도 아니고 심볼형도 아닌 값이 오면 문자형으로 자동 형변환된다.))와 값(value, 모든 값)의 쌍 = 프로퍼티(property)

키를 이용해 프로퍼티를 찾고, 수정, 삭제

빈 객체를 만드는 두 가지 방법

let user = new Object(); // '객체 생성자' 문법

let user = {}; // '객체 리터럴' 문법 (주로 사용하는 방법 -> 왜?)

간편성, 가독성, 속도

더보기

가독성

function Person(name, age) { this.name = name; this.age = age; } var John = new Person("John Doe", 30);

 

객체 이름과 생성자 함수의 이름이 다르다. 생성자 함수 이름  = Person, 객체 이름 = John

function Person(name, age, address, phone) { this.name = name; this.age = age; this.address = address; this.phone = phone; this.getInfo = function() { return this.name + " is " + this.age + " years old and lives at " + this.address + ". Phone: " + this.phone; }; } var John = new Person("John Doe", 30, "123 Main St, Anytown, USA", "555-1234"); console.log(John.getInfo());

 

여러 개의 프로퍼티를 가진 복잡한 객체일수록 생성자 문법을 사용하면 복잡하다. 

위 코드를 객체 리터럴로 작성할 경우 아래와 같이 쓰일 수 있다. 

var John = { name: "John Doe", age: 30, address: "123 Main St, Anytown, USA", phone: "555-1234", getInfo: function() { return this.name + " is " + this.age + " years old and lives at " + this.address + ". Phone: " + this.phone; } };

 

속도

뿐만 아니라, 리터럴 방식으로 작성할 경우 오버헤드가 작아져 객체를 더 빨리 생성할 수 있다.

 

키가 복수의 단어로 이루어진 경우

키를 ""로 묶어야 하고, 값에 접근할 때는 . 을 사용하는 대신, 객체명["복수의 단어로 이루어진 키"] 처럼 배열과 같은 방식으로 접근. 이를 대괄호 표기법이라 하는데, 대괄호 표기법을 이용하면 키 문자열 대신 문자열 변수가 올수도 있음. ex. let John = "나는 존 입니다" Person[John]

계산된 프로퍼티

프로퍼티 이름을 동적으로 할당하고 싶다면, 객체 내에 프로퍼티를 작성할 때, [변수명] 를 이용할 수 있다. 

ex) 

let book = permission ? "comic" : "study";

let boughtBook = {
	[book]: true,
}

console.log(boughtBook.comic)

단축 프로퍼티

프로퍼티의 값이 변수이고, 키와 값의 변수 이름이 서로 같다면, ex) name: name 두 번 적을 필요 없이 한 번만 적으면 된다. ex) name,

undefined 

자바스크립트의 중요한 특징: 존재하지 않는 프로퍼티에 접근하려 해도 에러가 발생하지 않고 undefined 를 반환한다.

in 연산자를 이용해서, 프로퍼티의 존재 여부 확인 가능!

let person = {
	hair: "brown",
    eye: "blue",
}

console.log("hair" in person); //true
console.log("foot" in person); //false

let hairstyle = "hair";
console.log(hairstyle in person); //true

'for...in' 반복문 

for(let body in person){
	console.log(body); // hair, eye 가 출력됨
    console.log(person[body]) // brown, blue 가 출력됨
}

한 가지 주의할 점은, 자동 객체 정렬을 제공한다는 것!

let Person = {
	"37": "John",
    "22": "Mina",
}

위 Person 을 for in 돌려 보면, 37 이 먼저, 22가 나중에 나와야할 것 같지만, 22가 먼저, 37이 다음으로 나온다. 자동 객체 정렬이 되었기 때문! (키 타입이 숫자이면 그렇게 나온다..!) 이를 방지하기 위해서는 + 를 붙여주고, 출력시에도 + 를 붙여주면 된다. 

let Person = {
	"+37": "John",
    "+22": "Mina",
}

for(let age in Person){
	console.log(+age); // 37 22
}

참조에 의한 객체 복사

객체와 원시형의 가장 큰 차이점: 객체는 참조에 의해 저장되고, 복사된다!

원시형의 값 할당과 복사
객체의 값 할당과 복사

객체는 메모리 내 어딘가에 저장되고, 변수 user 엔 객체를 참조할 수 있는 값! 메시지 모양 이모티콘이 저장된다. c 언어로 생각하면, 주소가 저장되는 것! 따라서 객체가 할당된 변수를 복사할 땐, 객체의 참조값이 복사되고 객체는 복사되지 않는다.

복사된 객체. 같은 주소가 저장된다.

객체를 서랍장에 비유하면 변수는 서랍장을 열 수 있는 열쇠라고 할 수 있습니다. 서랍장은 하나, 서랍장을 열 수 있는 열쇠는 두 개인데, 그중 하나(admin)를 사용해 서랍장을 열어 정돈한 후, 또 다른 열쇠로 서랍장을 열면 정돈된 내용을 볼 수 있습니다.
let fruit = {
	berry: "blueberry",
}

let food = fruit;

fruit.berry = "strawberry";

console.log(food.berry); // strawberry

객체를 독립된 객체로 복제하고 싶다면 어떻게 해야 할까?

방법 1) 빈 객체를 만들고, for in 을 사용해 모든 프로퍼티 복사

let food2 = {};
for (let prop in food) {
	food2[prop] = food[prop];
}

방법2) Object.assign 이용하기

Object.assign(dest, [src1, src2, src3...])

src1, src2, src3 는 모두 객체이며, 이 객체의 모든 프로퍼티들이 dest 객체로 복사된다. 그리고 마지막으로 dest 를 반환한다. 만약, 동일한 키가 dest 에 있는 경우라면 src 의 키로 대체된다. 

let clone = Object.assign({}, user);

를 이용하면 for in 을 사용하지 않고도 객체를 복제할 수 있다.

중첩 객체 복사

만약, 프로퍼티 중에 원시값이 아닌, 다른 객체를 참조하는 값이 있다면 어떻게 해야 할까? Object.assign({}, user) 를 사용할 경우, 복제를 원했지만, 복사만 될 수 있다. 왜? 객체는 참조에 의해 복사되니까!!! 새로운 객체를 만들어서 값을 복사한다 한들, 복제되지 못함. 이 경우, 프로퍼티의 값을 모두 검사하면서 값이 객체이면 해당 객체의 구조를 복사해주는 깊은 복사를 해야 함. 라이브러리 lodash의 메서드인 _.cloneDeep(obj)을 사용하면 깊은 복사를 할 수 있음!

메서드와 this

객체 = 엔티티. 객체에게 동사(행동)을 부여해 주는 것이 메서드. 

let person = {
	hair: "brown",
    eyes: "blue",
}

person.eat = function () {
	console.log("eat");
}

person.eat(); // eat

function sleep() {
	console.log("sleep");
}

person.sleep = sleep;
person.sleep(); //sleep

person = {
	watch() {
    	console.log("watch");
    }
}

person.watch();
person.hair; // 에러 남. 이제 watch 밖에 없는 객체가 되어서.

메서드와 this 

많은 메서드가 자신이 속한 객체의 프로퍼티를 사용하게 되는데 이때 필요한 것이 this. 

let Person = {
	hair: "brown",
	eye: "blue",
    
    sleep() {
    	console.log(`close your ${this.eye}`)
    }
    
}

여기서 this 대신에 Person.eye 를 사용할 수도 있지만, 혹시 모를 에러가 발생할 수 있으니, this 사용을 권장.

자바스크립트에서 this 는 어느 함수에나 올 수 있고, this 의 값은 런타임에 결정되기 때문에 컨텍스트에 따라 달라진다. 따라서 동일 함수여도 다른 객체에서 쓰였다면 this 의 값은 서로 다르게 나올 수 있다. 

화살표 함수는 this 가 없다. 

화살표 함수에서 this 를 참조할 수는 있지만, 이 경우 this 는 화살표 함수를 가리키는 것이 아니라 외부에 있는 일반 함수의 this 가 된다. 

let person = {
	hair: "brown",
    eye: "blue", 
    sleep() {
    	let wash = () => {
        	console.log(`wash your ${this.hair}`);
        }
    }
}

여기서 this 는 wash 의 this 가 아니라 sleep 의 this 가 된다.

new 연산자와 생성자 함수

비슷한 객체를 여러 개 생성해야 할 경우 생성자 함수를 사용하면 편리하게 만들 수 있다! 생성자 함수에서는 지켜야 할 약속이 두 가지 있는데,

1. 함수의 첫 글자는 반드시 대문자

2. 실행할 때는 new 를 붙여서 실행한다. 

function Person(name) {
	this.name = name;
    this.eye = "blue",
    this.hair = "hair",
    this.sleep = function sleep() {
    	console.log(`close your ${eye}`);
    }
}

let person1 = new Person('현수');
let person2 = new Person('지헌');

여기서 생성자 함수가 실행되는 메커니즘은 

1. 빈 객체를 만들고 this 를 할당한다. 

2. this 에 프로퍼티들을 추가한다.

3. this 를 반환한다.

이다. 

생성자 함수와 return 문

생성자 함수는 자동으로 this 를 리턴하므로, 일반적으로는 return 이 없다. 만약 return 문이 있다면,

1. 원시형을 반환하는 경우 -> 무시

2. 객체를 반환하는 경우 -> this 대신 객체를 반환

하게 된다. 그러나 return 을 사용하는 생성자 함수는 거의 없다. 

옵셔널 체이닝 '?.'

자바스크립트는 존재하지 않는 프로퍼티에 접근해도, 에러를 내지 않고 undefined 를 반환한다. 그런데 undefined 아래에 있는 프로퍼티에 접근하면 에러가 난다. 예를 들어서 Person.eye.color 는 undefined 를 반환하겠지만, Person.eye.color.eyecolor 에 접근하면, 에러가 난다. 

이를 막기 위해 옵셔널 체이닝을 이용한다. 

Person.eye?.clolor?.eyecolor 를 이용하면, ?. 앞에 있는 프로퍼티의 값이 null 혹은 undefined 일 때 평가를 멈추고 undefined 를 반환한다.

?. 는 연산자가 아닌 문법이기 때문에 ?.() 를 이용해 객체 내 함수에 접근할 수도 있고, ?.[] 를 이용해 객체 내 키에 접근할 수도 있다.

댓글