※ 책 [혼자 공부하는 자바스크립트]를 공부하며 기록, 정리해 놓은 노트이다.
목차
객체의 기본
요소, 속성, 메소드, this, 동적 속성 추가/제거
요약:
delete
키워드를 써서 하면 된다.
객체
타입은 object
.
// 객체 정의:
const product = {
제품명: '7D 건조 망고',
유형: '당절임',
'무게(g)': 600,
성분: ['망고', '설탕', '메타중아황산나트륨', '치자황색소',],
HACCP: true,
고객문의: function () { },
}
중괄호로 생성하고 키: 값
형태를 콤마로 연결해 속성과 메소드를 입력한다.
⇒ 키는 식별자와 문자열을 모두 사용 가능하다. 대부분 식별자를 사용하고 식별자로 쓸 수 없는 단어(~@+…이런 것들)를 키로 사용할 때 문자열을 써줌.
요소에 접근할 땐 객체[키]
혹은 객체.키
로 써준다.
⇒ 키로 식별자를 썼을 때: 객체['키']
혹은 객체.키
모두 가능
⇒ 키로 문자열을 썼을 때: 객체['키']
만 가능.
// 객체 요소에 접근 - 키가 식별자:
product['제품명'] => 꼭 따옴표로 감쌀 것
product[제품명] => Uncaught ReferenceError: 제품명 is not defined
product.제품명
// 객체 요소에 접근 - 키가 문자열:
product['무게(g)']
product.'무게(g)' => Uncaught SyntaxError: Unexpected string
// 객체 요소에 접근 - 메소드일 땐:
product.고객문의()
product['고객문의']()
속성과 메소드
속성: 객체 내부에 있는 값
메소드: 속성 중에 함수 자료형인 것
this 키워드: 객체 내부의 메소드에서 객체(인스턴스) 자신을 나타내는 키워드.
// 위의 객체 정의에서...
const product = {
제품명: '7D 건조 망고',
유형: '당절임',
'무게(g)': 600,
성분: ['망고', '설탕', '메타중아황산나트륨', '치자황색소',],
HACCP: true,
고객문의: function () {
console.log(`${this.제품명} 은/는 식품안전관리인증기준(HACCP)을 ${this.HACCP ? '통과' : '통과하지 못'}했습니다.`)
},
}
// 메소드 호출
product.고객문의()
=> 결과:
7D 건조 망고 은/는 식품안전관리인증기준(HACCP)을 통과했습니다.
동적으로 객체 속성 추가/제거
: 객체 생성 이후에 속성을 추가하거나 제거하는 것.
객체이름.없던_키 = 없던_값
혹은 객체이름['없던_키'] = 없던_값
⇒ 배열과 똑같다.
delete 객체이름.지울_속성_키
혹은 delete 객체이름['지울_속성_키']
메소드 간단 선언 구문
메소드를 선언할 때, 익명함수의 선언을 풀버전으로 하지 않고 더 간단하게 할 수 있다:
const product = { 고객문의: function () { console.log(`this.제품명`) } }
⇒
const product = { 고객문의 () { console.log(`this.제품명`) } }
⇒ 만약 메소드로 화살표 함수를 써도 에러가 뜨진 않는다. this가 window 객체를 가리키게 될 뿐이다:
const product = { 고객문의: () ⇒ { console.log(this.제품명) } }
객체를 예쁘게 출력하고 싶을 때:
console.log(JSON.stringify(객체이름, null, 4))
(+ 추가)
객체 구조 분해 할당
분해 할당이란: ES6
에서 새롭게 도입한 문법인데 객체(Object) 나 배열(List)을 변수로 ‘분해'할 수 있도록 한 것이다.
// 객체 분해 할당 예시
const obj = { name: "이용우", age: 28, tech: "Node.js" };
const { name, age, tech, hair } = obj;
console.log(name); // 이용우
console.log(age); // 28
console.log(tech); // Node.js
console.log(hair); // undefined: obj에는 "hair" 프로퍼티가 존재하지 않습니다.
한 마디로 일단 객체를 하나 만들어 놓고, ‘obj.name’이라고 호출하지 않고 따로 그냥 ‘name’이라고만 부르고 사용하고 싶을 때, 저렇게 변수를 분해해서 할당하여 사용한다.
객체를 분해할 때에는 반드시 변수명과 객체의 프로퍼티 이름이 일치해야 한다.
프로퍼티의 이름이 유효한 식별자인 프로퍼티만 분해 후 할당된다. (애초부터 없던 속성은 undefined로 정해진다는 얘기)
자유로운 this
(너무나 자유로워 결론이 나지 않은…)
다음과 같은 코드가 있다고 할 때,
let user = {
firstName: "보라",
sayHi1() {
alert(this.firstName);
},
sayHi2() {
let arrow = () => alert(this.firstName);
arrow();
},
sayHi3() {
let brief = function() {
alert(this.firstName)
};
brief();
},
sayHi4: () => { alert(this.firstName) },
};
user.sayHi1(); // 보라
user.sayHi2(); // 보라
user.sayHi3(); // undefined
user.sayHi4(); // undefined
대원칙: 메서드 내부에서 this는 ‘현재 객체’를 나타낸다.
자바스크립트에서는 특이하게도 ‘현재 객체’가 메서드가 정의된 시점의 객체가 아니고 런타임 시의 객체로 정해진다. (그 때 그 함수를 부르고 있는 객체가 바로 this가 가리키는 대상이 되는 것)
그 밖의 자잘한 규칙은:
- 객체가 없는데 this가 포함된 함수가 호출되면: 엄격 모드에선 “이 함수를 부르고 있는 객체는 이 시점엔 없다”고 해서 this == undefined가 되고, 엄격 모드가 아니면 “이 함수를 부르고 있는 객체는 암묵적으로 이 바깥의 더 큰 객체일 테니” this == 전역 객체가 된다. (브라우저 환경에서 전역 객체 == window)
function sayHi() {
alert(this);
}
sayHi(); // undefined
⇒ 한 마디로, 엄격 모드가 아닐 때 뚜렷한 객체가 없이 this가 포함된 함수가 호출될 땐 더 상위 객체를 찾아 this로 가리킨다(…).
⇒ 더 쉽게 이해하자면 나(함수)를 호출한 객체가 반드시 있을 테니까 위로위로 거슬러 올라가는 것이고, 엄격 모드에선 그걸 막는 것.
- 화살표 함수는 자신만의 ‘this’를 가지지 않는다. 화살표 함수에게는 ‘this’라는 변수가 만들어지지 않는다.
- 규칙 1번과 2번을 결합하면, 그래서 화살표 함수 안에서 this가 쓰였을 때 가장 가까운 바깥의 ‘평범한’ 외부 함수에서 this값을 가져오는(= 더 상위 객체를 만날 때까지 거슬러 올라가 this로 가리키는) 것이다.
이제 sayHi들을 이해해보자.
- sayHi1 : sayHi1을 호출한 객체는 user. 따라서 this == user이고 this.firstName == ‘보라’
- sayHi2 : this가 담긴 arrow()가 호출될 때 이 arrow는 화살표 함수이므로 ‘this’를 가지지 않는다. 그럼에도 불구하고 this를 써야할 때, 가장 가까운 바깥 함수에서 this를 가져온다. 가장 가까운 바깥 함수 sayHi2의 this가 가리키는 대상은 user. 따라서/ 호출한 대상은 객체가 아닌 sayHi2()라는 메서드이다. user.sayHi2.arrow()라는 식으로는 (실험 결과)참조가 안 되므로 어쨌든 가장 가까운 조상 객체인 user를 this로 삼는다. 그래서 this == user이고 this.firstName == ‘보라’
- sayHi3 : brief()가 호출될 때 얘는 평범한 익명 함수이므로 ‘this’를 가진다. 따라서 this를 찾기 위해 외부 함수에 의존하지 않고 자신의 this가 가리키는 window를 참조한다. user.sayHi3()를 호출한 건 user라는 객체지만, 그 안에서 brief()를 호출할 땐 user.sayHi3.brief()가 아니라 (window).brief()가 되는 것.
this == window이고 this.firstName == undefined
- 근데 함수 자체도 객체라서 속성을 가질 수 있다고 했는데, 왜 함수.속성() 식으로 참조가 안되는 거지? sayHi3.brief()도 안되고 sayHi3().brief()도 당연히 안 된다.
⇒ 함수 { }안에 써지는 ‘바디’와 리턴값은, 객체 { }안에 써지는 키:값, 값들과 완전히 구분된다. 즉, function이라고 선언하고 시작하는 { }블록은 객체 속성 형식(키:값)을 전혀 인식하지 못한다. 함수 내부에 변수를 정의해 놓는 것은, 함수를 만들어 놓고 동적으로 속성을 넣어주는 것과는 완전히 별개의 얘기인 것이다. 그러니까 user.sayHi3.OOO이 안되는 거지. sayHi3가 함수라고 땅땅 못박고 정의해놨는데.
⇒ 그러니까! “.함수”에 도달했으면 그 이후로는 .연결을 못한다고 보면 (일단) 되겠다.
- 근데 함수 자체도 객체라서 속성을 가질 수 있다고 했는데, 왜 함수.속성() 식으로 참조가 안되는 거지? sayHi3.brief()도 안되고 sayHi3().brief()도 당연히 안 된다.
- 화살표 함수 sayHi4()가 윗대의 함수를 참조하려고 보니 없네. 그래서 바깥으로 나가 window를 만나 성씨 삼게 됐다.(…) this == window이고 this.firstName == undefined
⇒ 충격적인 결말: “함수”가 호출될 때, “.”의 앞부분을 자기 성씨라고 한다면 자기 성씨 객체를 찾기 위해 상위 블록을 검색해나간다. “객체.함수()”처럼 이름이 완성될 수 있다면 그 객체가 this. 그런데 “상위함수.함수()”처럼 되어야 하게 생겼다면 이 형식은 (상위함수에게 함수()를 직접 동적 추가해준 게 아니라면)불가능하므로, 그냥 “최상위객체(=전역객체).함수()”가 풀네임이 되게 된다. 즉, 함수 내에서 불리어 실행되는 함수들은 다 “window.함수내함수()” 풀네임으로 불리는 것이다. “함수.함수()”가 아니라. 근데 함수()가 불리는 곳마다 성씨를 갈아치울 수 있다는 게 함정. 특별히, 호출되는 대상 함수가 화살표 함수일때는 풀네임이 “상위함수.함수()”이게 생겼든지 “객체.함수()”이게 생겼든지에 상관 없이, 무조건 상위”함수”가 가리키는 this를 성씨로 삼는다. 그래서 아까 sayHi4()에서는 상위”함수”를 찾아가느라 window를 만난 거고, 다음과 같은 코드에서는 상위 함수가 가리키는 this가 객체 group이 되어 결과를 도출하게 되는 것이다:
let group = {
title: "1모둠",
students: ["보라", "호진", "지민"],
showList() {
this.students.forEach(
student => alert(this.title + ': ' + student)
);
}
innerTest() { // showList()와 같은 깊이. showList() > 내부함수 > 화살표함수 호출.
(function() {
console.log("바깥 함수");
(() => console.log("내부 화살표 함수"))();
console.log("다시 바깥함수");)()
}
};
group.showList();
=> 결과:
1모둠: 보라
1모둠: 호진
1모둠: 지민
⇒ 화살표 함수 내부의 this: “forEach
에서 화살표 함수를 사용했기 때문에 화살표 함수 본문에 있는 this.title
은 화살표 함수 바깥에 있는 메서드인 showList
가 가리키는 대상과 동일해진다.” 따라서 this.title == group.title
- 근데 엄밀히 말하면 여기 화살표 함수의 외부 함수는 forEach() 자체 아냐? showList()는 그 바깥 함수일텐데. forEach의 this는 …성씨가 .students니까 students 배열이 되는 건가? students배열이 가진 메소드 forEach는 this로 students배열을 가리켜야지, 왜 group을 가리키고 있대?
⇒ 실험 결과, 객체.속성배열.의메소드()를 호출하면 내부에서 그냥 익명이든 화살표 함수든 this==객체.속성배열 이고 ⇒ 객체.속성배열의메소드를직접연결한함수()를 호출하면 내부에서 익명과 화살표 모두 this==객체 를 가리킨다.
⇒ 두 결과 모두 배열이라는 객체가 중간에 껴 있느냐 아니냐로 this의 종착지가 중간의 배열이 되느냐 제일 바깥의 ‘객체’가 되느냐로 나뉘는 일관된 형식을 따른다고 이해할 수 있다.
근데 위의 코드에선 group.students배열.의메소드()를 호출했는데 내부에서 사용된 화살표 함수가 this==students배열을 가리키지 않고 스킵하여 this==group이 되어버림.
group.showList()와 같은 레벨과 깊이의 함수체인을 만들고 group.innerTest()로 호출해봤는데(이번엔 배열의 메소드가 화살표함수를 호출하는 형태가 아니라 그냥 함수가 화살표함수를 호출하도록 바꿔서), 이번엔 최내부의 this==window로 확인된다.
둘의 공통점: 둘 다 한 단계(객체)를 스킵하고 그 상위의 객체를 성씨(this)로 삼음.
group.배열.메소드의this ⇒ group을 this로, group.메소드.함수의this ⇒ window를 this로.
둘의 차이점: (배열의)”메소드가” this가 담긴 내부함수를 호출하느냐, (메소드 내의)그냥 함수가 this가 담긴 내부함수를 호출하느냐의 차이가 있었다….
결론이 안 나므로 패스..!!
참고:
[책] 혼자 공부하는 자바스크립트 - 윤인성
모던 JavaScript: <메서드와 this>, <화살표 함수 다시 살펴보기>
Uploaded by N2T