
자바스크립트에선 객체의 프로퍼티에 함수를 할당해 객체에게 행동할 수 있는 능력을 부여해준다.
메서드 : 객체 프로퍼티에 할당된 함수
ex) 객체 user에게 인사할 수 있는 능력 부여
let user = {
name: "John",
age: 30
};
user.sayHi = function() {
alert("안녕하세요!");
};
user.sayHi(); // 안녕하세요!
-> sayHi는 user에 할당된 메서드!
객체 리터럴 안에 메서드를 선언할 때
// 아래 두 객체는 동일하게 동작합니다.
user = {
sayHi: function() {
alert("Hello");
}
};
// 단축 구문을 사용
user = {
sayHi() { // "sayHi: function()"과 동일합니다.
alert("Hello");
}
};
메서드에서 외부 변수를 참조해 객체에 접근해보자
let user = {
name: "John",
age: 30,
sayHi() {
alert(user.name); // 외부 변수 : user
}
};
잘 작동한다. 그러나,
❗ 이렇게 외부 변수를 사용해 객체를 참조하면 예상치 못한 에러가 발생할 수 있다!
user를 복사해서 다른 변수(admin)에 할당하고, user는 전혀 다른 값으로 덮어썼다고 가정하면, sayHi()는 원치 않는 값(null)를 참조할 것이다.
let user = {
name: "John",
age: 30,
sayHi() {
alert( user.name );
}
};
let admin = user;
user = null; // user를 null로 덮어씁니다. --> user의 name은 사라짐!
admin.sayHi(); // // Error: Cannot read property 'name' of null
🔽 그렇다면!!
메서드 내부에서 this 키워드를 사용하면 객체에 접근할 수 있다.
👉 this : 메서드를 호출할 때 사용된 객체를 나타냄
let user = {
name: "John",
age: 30,
sayHi() {
// 'this'는 '현재 객체'를 나타냅니다.
alert(this.name);
}
};
user.sayHi(); // John
this가 가리키는 것은 런타임에 결정된다.
동일한 함수더라도 다른 객체에서 호출하면 this가 참조하는 값이 달라진다.
let user = { name: "John" };
let admin = { name: "Admin" };
function sayHi() {
alert( this.name );
}
// 별개의 객체에서 동일한 함수를 사용함
user.f = sayHi;
admin.f = sayHi;
user.f(); // John (this == user)
admin.f(); // Admin (this == admin)
admin['f'](); // 참고 : Admin (점과 대괄호는 동일하게 동작함)
객체가 없어도 호출할 수 있다.
🔽 "use strict" 엄격 모드에서
function sayHi() {
alert(this);
}
sayHi(); // undefined
그러나 엄격모드가 아니라면?
this가 전역 객체를 참조한다. 따라서 브라우저 환경이면 this는 window라는 객체를 참조한다.
👉 주의하고 개발하기!
this가 없는 화살표 함수화살표 함수는 '고유한' this를 가지지 않는다.
화살표 함수에서 this를 참조하면, 화살표 함수가 아닌 '평범한' 외부 함수에서 this 값을 가져온다.
let user = {
firstName: "보라",
sayHi() {
let arrow = () => alert(this.firstName);
arrow();
}
};
user.sayHi(); // 보라
👉 함수 arrow() 의 this는 arrow()가 아닌 user.sayHi()를 가져온다.
위와 같이 별개의 this가 만들어지는 건 원하지 않고, 외부 컨텍스트에 있는 this를 이용하고 싶을 때 화살표 함수를 이용하면 된다!
유사한 객체를 여러 개 만들어야 할 때 유용한 생성자 함수!
👉 재사용할 수 있는 객체 생성 코드를 구현
일반 함수와의 기술적 차이는 없다!
관례
1. 험수 이름 첫 글자는 대문자로 시작
2. 반드시'new'연산자를 붙여 실행
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("보라");
alert(user.name); // 보라
alert(user.isAdmin); // false
new User(...)를 써서 함수를 실행하면 어떤 알고리즘이 동작할까?
1. 빈 객체를 만들어 this에 할당
2. 함수 본 문 실행. this에 새로운 프로퍼티를 추가해 this를 수정
3. this를 반환 ( => user에 할당되는 것! )
🔽그 과정을 보자면🔽
function User(name) {
// this = {}; (빈 객체가 암시적으로 만들어짐)
// 새로운 프로퍼티를 this에 추가함
this.name = name;
this.isAdmin = false;
// return this; (this가 암시적으로 반환됨)
}
➕ 재사용할 필요가 없는 복잡한 객체를 만들어야 한다면?
▶ 익명 함수 이용let user = new function() { this.name = "John"; this.isAdmin = false; // 사용자 객체를 만들기 위한 여러 코드. // 지역 변수, 복잡한 로직, 구문 등의 // 다양한 코드가 여기에 들어갑니다. };👉 재사용은 막으면서 코드를 캡슐화
생성자 함수엔 보통 return 문이 없다.
👉 반환해야 할 것은 모두 this에 저장되고, this는 자동으로 반환되기 때문
❓ 만약 return문이 있다면?
this 대신 객체가 반환return문 무시function BigUser() {
this.name = "원숭이";
return { name: "고릴라" }; // <-- this가 아닌 새로운 객체를 반환함
}
alert( new BigUser().name ); // 고릴라
function SmallUser() {
this.name = "원숭이";
return; // <-- this를 반환함
}
alert( new SmallUser().name ); // 원숭이
생성자 함수로 객체 내부에 메서드도 추가해줄 수 있다.
function User(name) {
this.name = name;
this.sayHi = function() {
alert( "제 이름은 " + this.name + "입니다." );
};
}
let bora = new User("이보라");
bora.sayHi(); // 제 이름은 이보라입니다.
/*
bora = {
name: "이보라",
sayHi: function() { ... }
}
*/
프로퍼티가 없는 중첩 객체를 에러 없이 안전하게 접근할 수 있다.
ex) 사용자가 여러 명 있는데 그중 몇 명은 주소 정보를 가지고 있지 않다고 가정해보자.
이럴 때 user.address.street을 사용해 주소 정보에 접근하면 에러가 발생할 수 있다!
let user = {}; // 주소 정보가 없는 사용자
alert(user.address.street); // TypeError: Cannot read property 'street' of undefined
👉 address는 undefined로 들어오지만, 그 이후 street까지 접근 불가
ex2) 브라우저에서 동작하는 코드를 개발할 때 페이지에 존재하지 않는 요소에 접근한다면 ㅜㄴ제 발생
// querySelector(...) 호출 결과가 null인 경우 에러 발생
let html = document.querySelector('.my-element').innerHTML;
옵셔널 체이닝 ?.은 앞의 평가 대상이 undefined나 null이면 평가를 멈추고 undefined를 반환한다.
ex1) 이전 ex1)에 적용해보자
let user = {}; // 주소 정보가 없는 사용자
alert( user?.address?.street ); // undefined, 에러가 발생하지 않습니다.
❗ 옵셔널 체이닝을 남용해서는 안된다.
위 예시에서 논리상 user은 반드시 있어야 하는데 address는 필수값이 아니다.
따라서 user.address?.street을 사용하는 것이 바람직하다.
❗ ?. 앞 변수는 꼭 선언되어 있어야 한다.
변수 user가 선언되어있지 않으면 user?.anything 평가시 에러가 발생한다.
단락 평가 : ?.는 왼쪽 평가대상에 값이 없으면 즉시 평가를 멈춘다.
let user = null;
let x = 0;
user?.sayHi(x++); // 아무 일도 일어나지 않습니다.
alert(x); // 0, x는 증가하지 않습니다.
?.()와 ?.[]?.은 연산자가 아니고 함수나 대괄호와 함께 동작하는 특별한 문법 구조체이다.
?.()let user1 = {
admin() {
alert("관리자 계정입니다.");
}
}
let user2 = {};
user1.admin?.(); // 관리자 계정입니다.
user2.admin?.(); // ?.에서 단락평가에 의해 멈추고 함수를 실행하지 않음.
?.[]let user1 = {
firstName: "Violet"
};
let user2 = null; // user2는 권한이 없는 사용자라고 가정해봅시다.
let key = "firstName";
alert( user1?.[key] ); // Violet
alert( user2?.[key] ); // undefined
alert( user1?.[key]?.something?.not?.existing); // undefined
➕
?.는delete와 조합해 사용도 가능하다delete user?.name; // user가 존재하면 user.name을 삭제
❗ ?. 읽기나 삭제는 가능하지만, 쓰기에는 사용할 수 없다.
// user가 존재할 경우 user.name에 값을 쓰려는 의도
user?.name = "Violet"; // SyntaxError: Invalid left-hand side in assignment
👉 에러가 발생하는 이유 : undefined = "Violet"이 되기 때문.
타입스크립트를 쓰면서 옵셔널 체이닝을 많이 사용했었는데 그냥 에러를 없애기 위해 남용하는 게 아니라 말씀해주신 주의사항을 잘 고려하면서 사용해야겠다는 생각이 들었어요!! 좋은 아티클 감사합니다 ㅎㅎ