[JS] 메서드와 this
📌 참고 사이트
메서드와 this
ko.javascript.info
📋 객체의 프로퍼티에 함수 할당하기
📍 메서드 만들기
💡 객체에서의 메서드 : 객체 프로퍼티에 할당된 함수
다음은 아래와 같은 user 객체에 sayHi 함수를 할당해 주어 user 객체에 할당된 함수를 호출(점 표기법과 ()를 사용하여)하면 user가 인사 멘트를 날리는 코드이다.
let user = {
name: 'kanu',
age: 6;
}
// 함수 표현식으로 함수 만든 후 객체 프로퍼티 user.sayHi에 함수를 할당해 줌.
user.sayHi = function () {
console.log('안녕하세요!');
}
user.sayHi(); // 안녕하세요!
아래과 같은 방식으로 이미 정의된 함수를 이용해 만들 수도 있다.
// 함수를 먼저 선언
function sayHi() {
console.log('안녕하세요!');
}
// 먼저 선언된 함수를 메서드로 등록
// (메서드 등록 시에는 함수를 호출하여 실행시키는 것이 아니기 때문에 ()를 적지 않는다.)
user.sayHi = sayHi;
❓ 객체 지향 프로그래밍
객체 지향 프로그래밍(Object-Oriented Programming, OOP)은 컴퓨터 프로그래밍의 패러다임 중 하나로, 컴퓨터 프로그램을 명령어로 보는 시각에서 벗어나 여러 개의 독립된 단위인 '객체' 들의 모임으로 파악하고자 하는 것이다.
각각의 객체는 메시지를 주고받고, 데이터를 처리하는 것이 가능하다.
객체 지향 프로그래밍은 프로그램을 유연하고 변경이 쉽게 만들기 때문에 대규모의 소프트웨어 개발에 많이 사용된다.
💡 객체 지향 프로그래밍의 기본 구성 요소
➡️ 클래스 (class)
같은 종류의 집단에 속하는 속성(attribute)과 행위(behavior)를 정의한 것으로, 객체 지향 프로그램의 기본적인 사용자 정의 데이터형이라고 할 수 있다.
클래스는 다른 클래스 또는 외부 요소와 독립적으로 디자인해야 한다.
➡️ 객체 (object)
클래스의 인스턴스 (실제로 메모리 상에 할당된 것) 이다.
클래스는 객체의 특징과 동작을 정의하는데 사용되며, 객체는 클래스의 구현을 바탕으로 생성되어 실제로 사용되는 것이다.
객체는 자신의 고유한 속성 (attribute) 을 가지며 클래스에서 정의한 행위를 수행할 수 있다.
객체의 행위는 클래스에 정의된 행위에 대한 정의를 공유함으로써 메모리를 경제적으로 사용한다.
➡️ 메서드 (method), 메시지 (message)
클래스로부터 생성된 객체를 사용하는 방법으로, 객체에 명령을 내리는 메시지라고 할 수 있다.
메시지는 객체의 속성을 조하는 데 사용된다.
또, 객체 간의 통신은 메시지를 통해 이루어진다.
💡 객체 지향 프로그래밍의 장점
하나의 문제 해결을 위한 데이터를 클래스에 모아 놓은 데이터형을 사용함으로써 응집력을 강화하고, 클래스간에 독립적인 디자인을 함으로써 결합력을 약하게 한다.
객체 지향 프로그래밍 - 위키백과, 우리 모두의 백과사전 (wikipedia.org)
객체 지향 프로그래밍 - 위키백과, 우리 모두의 백과사전
위키백과, 우리 모두의 백과사전. 객체 지향 프로그래밍(영어: Object-Oriented Programming, OOP)은 컴퓨터 프로그래밍의 패러다임 중 하나이다. 객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목
ko.wikipedia.org
📍 메서드 단축 구문
객체 리터럴 (중괄호를 이용해 생성된 객체) 안에 메서드를 선언할 때 사용할 수 있는 단축 문법은 다음과 같다.
아래 예시의 두 객체는 동일하게 작동한다.
user = {
sayHi: function() {
console.log("Hello");
}
};
// 단축 구문을 사용한 예시 (function 생략)
user = {
sayHi() { // "sayHi: function()"과 동일하다.
console.log("Hello");
}
};
객체 상속에서 조금 차이가 있지만 위처럼 function과 :를 생략하고 key명() {...} 이러한 형식으로 작성해도 메서드 정의가 가능하다.
📋 메서드와 this
➡️ 대부분의 메서드는 객체 프로퍼티의 값을 포함하는데, 이 경우 메서드 내부에서 this 키워드를 사용하면 해당 객체 자기 자신에 접근이 가능하다.
(아래에서 보겠지만, JS에서 this는 항상 메서드가 정의된 객체를 참조하는 것은 아니다.)
정확하게는 this는 메서드를 호출할 때 사용된 객체를 나타낸다.
let user = {
name: "John",
age: 30,
sayHi() {
// this는 현재 객체 (user 객체)를 나타낸다.
console.log(this.name);
}
};
user.sayHi(); // John
➡️ 위와 같이 this를 사용하지 않고 객체명을 직접 작성하여 객체에 접근하는 것도 가능하다.
but 위와 같은 방식을 사용하면 다음과 같이 예상치 못한 에러가 발생이 가능하다.
let user = {
name: "John",
age: 30,
sayHi() {
console.log(user.name);
}
}
let admin = user; // admin 변수에 user 객체 복사
user = null; // user에 할당된 값인 객체를 null로 덮어쓰기
admin.sayHi(); // Uncaught TypeError: Cannot read properties of null (reading 'name')
admin에 user의 값을 복사해 줌으로써 admin에도 초기에 user에 할당된 객체인 {name: "John", age: 30,
sayHi() {console.log(user.name);}} 이 그대로 할당되게 된다.
그 다음 user의 값 자체를 새로운 값인 null로 덮어쓰면 user의 값은 null로 바뀌지만 admin의 값은 여전히 위의 객체이다.
이 상태에서 admin의 sayHi 메서드를 실행시키면 console.log(user.name)의 코드를 수행하는데, 이때 user가 null이므로 null의 name 프로퍼티를 참조하는 말이 안 되는 일이 일어나면서 오류가 발생하게 된다.
💡 위와 같은 에러를 방지하기 위해 this를 사용해야 한다.
user 대신에 this를 사용해 보자.
let user = {
name: "John",
age: 30,
sayHi() {
console.log(this.name);
}
};
let admin = user;
user = null;
admin.sayHi(); // John
admin에는 {name: "John", age: 30, sayHi() {console.log(this.name);}}; 객체가 복사되게 된다.
user의 값 자체를 null로 덮어써도 admin.sayHi()가 제대로 동작하는 이유는 console.log(this.name)에서 this는 admin 객체를 나타내기 때문이다. 따라서 admin 객체의 name 프로퍼티를 참조한다.
📋 자유로운 this
➡️ 자바스크립트의 this는 다른 프로그래밍 언어의 this와 다르게 모든 함수에 this를 사용할 수 있다.
➡️ 자바스크립트에서의 this는 프로그래밍이 실행 중인 런타임에 결정되기 때문에 문맥에 따라 달라진다.
let user = {name: "Ann"};
let admin = {name: "Admin"};
// 이렇게 this를 사용해도 에러 발생 X
function sayHi() {
console.log(this.name);
}
// 별개의 객체에서 동일한 함수 사용하기
// user 객체와 admin 객체에 sayHi 함수의 내용을 담은 f 메서드가 추가된다.
user.f = sayHi;
admin.f = sayHi;
// this는 점 앞의 객체를 참조하기 때문에 (ex) user.f에서 . 앞의 user 참조) this 값이 달라짐.
user.f(); // John (this는 user 객체를 의미)
admin.f(); // Admin (this는 admin 객체를 의미)
// 대괄호를 사용하여 호출할 수도 있다.
admin['f']();
위 예시에서 obj.f()를 호출했다면 this는 f를 호출하는 동안의 obj를 나타내게 된다.
➡️ 객체가 없어도 this를 사용한 함수를 호출 가능하다.
// 엄격 모드에서 실행 시
"use strict"
function learnThis() {
console.log(this);
}
learnThis(); // undefined
// 일반 모드에서 실행 시
function learnThis() {
console.log(this);
}
learnThis();
// Window {window: Window, self: Window, document: document, name: '', location: Location, …}
// window 객체가 출력됨.
1️⃣ 엄격 모드에서 실행 시
엄격 모드에서 위 코드를 실행하면 this에는 undefined가 할당된다.
따라서 this.name처럼 객체 프로퍼티 접근을 하려 하면 에러가 발생하게 된다.
2️⃣ 엄격 모드가 아닐 때 실행 시
엄격 모드가 아닐 때에는 this가 전역 객체를 참조한다.
브라우저 환경에서는 window라는 전역 객체를 참조하게 된다.
Chrome 같은 브라우저의 개발자 도구 console 창에서 실행시켜 보면 window 객체가 출력되는 것을 볼 수 있다.
위의 예시와 같이 적은 경우는 실수로 작성된 경우가 많으므로 함수 본문에 this가 사용되었다면 객체 컨텍스트 내에서 함수를 호출할 것이라고 예상하는 것이 좋다.
📍 JS에서의 자유로운 this
다른 언어를 사용하는 사람들은 JS에서도 this는 항상 메서드가 정의된 객체를 참조할 것이라고 혼동하기 쉬운데, JS에서 this는 런타임에 결정되므로 메서드가 어디서 정의되었는지와 상관없이 this는 점 앞의 객체가 무엇인가에 따라 자유롭게 결정된다.
➡️ 장점 : 함수를 하나만 만들어 여러 객체에서 재사용 가능
➡️ 단점 : 위와 같이 의도하지 않았는데 this가 undefined를 가리키거나 window 객체를 가리키게 하는 실수를 할 수 있다.
📋 this가 없는 화살표 함수
화살표 함수는 일반 함수에서와 달리 고유한 this를 가지지 않는다.
화살표 함수를 사용하면 화살표 함수가 아닌 화살표 함수를 감싸고 있는 외부 함수에서 this 값을 가져온다.
let user = {
firstName: "보라",
sayHi() {
// arrow(화살표 함수)에서의 this는 외부 함수인 sayHi 함수에서 가져온 this값 (user 객체)이다.
let arrow = () => console.log(this.firstName);
arrow();
}
}
user.sayHi(); // 보라
// 외부 함수를 작성하지 않고 화살표 함수만 작성한 경우
let user = {
firstName: "보라",
sayHi: () => {console.log(this.firstName);}
}
user.sayHi(); // undefined
// (화살표 함수는 일반 함수와 달리 고유한 this를 가지지 않음. this를 출력해 보면 전역 객체 window가 나옴.)
// 일반 함수의 형태로 작성한 경우
let user = {
firstName: "보라",
sayHi() {console.log(this.firstName);} // 여기서 this는 user 객체를 가리킨다.
}
user.sayHi(); // 보라
별개의 this가 만들어지는 것은 원하지 않고, 외부 컨텍스트에 있는 this를 이용하고 싶은 경우 화살표 함수를 사용하는 것이 유용하다.
✏️ 과제
📍 객체 리터럴에서 'this' 사용하기
함수 makeUser는 객체를 반환한다.
이 객체의 ref에 접근하면 어떤 결과가 발생하고, 그 이유는 무엇일까?
function makeUser() {
return {
name: "John",
ref: this
};
};
let user = makeUser();
console.log(user.ref.name);
🅰️
TypeError: Cannot read property 'name' of undefined의 에러가 발생한다.
위 makeUser 함수는 객체의 메서드로써 호출된 게 아니라 함수로써 호출되었기 때문에 makeUser 함수 내 this는 undefined가 된다.
(브라우저에서 console.log(user.ref)를 찍어 보면 this는 전역 객체인 window인 것으로 나온다.)
따라서 undefined의 name 프로퍼티를 참조하려는 이상한 일이 일어나므로 오류가 발생한다.
오류가 발생하지 않도록 하려면 다음과 같이 작성해야 한다.
function makeUser() {
return {
name: "John",
ref() {
return this;
}
};
};
let user = makeUser();
console.log(user.ref().name); // John
위에서 user.ref()를 하면 객체 안의 ref() 함수가 실행되어 this, 즉 user 객체 자기 자신의 내용을 반환하게 된다.
따라서 this.name이 되고, user.name을 하는 것과 같으므로 에러가 나지 않고 John이 정상적으로 출력된다.
📍 계산기 만들기
calculator라는 객체를 만들고, 새 메서드를 구현해 보자.
➡️ read()에서는 프롬프트 창을 띄우고 더할 값 2개를 입력받는다. 입력받은 값은 객체의 프로퍼티에 저장한다.
➡️ sum()은 저장된 두 값의 합을 반환한다.
➡️ mul()은 저장된 두 값의 곱을 반환한다.
🅰️
let calculator = {
read() {
this.a = +prompt('첫 번째 값을 입력해 주세요.', 1);
this.b = +prompt('두 번째 값을 입력해 주세요.', 1);
},
sum() {
return this.a + this.b;
},
mul() {
return this.a * this.b;
},
}
calculator.read(); // 1과 1을 입력하면
console.log(calculator.sum()); // 2
console.log(calculator.mul()); // 1
위와 같이 calcultor 객체를 정의하고 calculator.read()를 실행시켜 보자.
read 메서드에서 this는 calculator 객체를 의미하고, calculator는 입력받은 값에 따라 a, b 프로퍼티가 새로 생성되게 된다.
그리고 calculator.sum()과 calculator.mul()을 실행시키면 calculator 객체(this가 가리키는 것)의 a 프로퍼티와 calculaor 객체의 b 프로퍼티의 각각의 연산 결과가 반환되게 된다.
❗ this를 사용하는 것을 잊지 말자!
📍 체이닝
다음과 같이 올라가기(up)와 내려가기(down) 메서드를 제공하는 객체 ladder가 있다.
let ladder = {
step: 0,
up() {
this.step++;
},
down() {
this.step++
},
showStep: function() { // 사다리에서 몇 번째 단에 올라와 있는지를 보여줌.
alert(this.step);
}
};
메서드를 연이어 호출하고자 하면 다음과 같이 코드 작성이 가능하다.
ladder.up(); // 1 증가
ladder.up(); // 1 증가
ladder.down(); // 1 감소
ladder.showStep(); // 1
up, down, showStep을 수정해 아래처럼 메서드 호출 체이닝이 가능하도록 해보자.
ladder.up().up().down().showStep(); // 1
🅰️
let ladder = {
step: 0,
up() {
this.step++;
return this;
},
down() {
this.step--;
return this;
},
showStep: function() {
alert(this.step);
return this;
}
};
위와 같이 메서드 호출 때마다 객체 자기 자신을 반환하게 하면 된다.
이렇게 하면 처음에 ladder.up()에서 반환된 값인 this (ladder 객체를 나타냄)가 이어져 그 다음에 this.up() 이런 식으로 이어져서 실행될 수 있다.