객체를 가리키는 참조 변수이다. 파이썬의 클래스에서 쓰이는 self
와 비슷한 느낌을 받았다. 이러한 this
는 호출되는 위치에 따라 다른 값을 출력한다.
function a(){ console.log(this) }
a();
// Window {window: Window, self: Window, document: document, name: '', location: Location, …}
위의 예제에서 this
는 window라는 브라우저의 전역공간을 가리킨다. 아래의 예제에서는 myObj 객체를 가리킨다.
let myObj = {
val1: 100,
func1: function () {
console.log(this);
}
}
myObj.func1(); // {val1: 100, func1: ƒ}
어떠한 객체의 메서드가 아닌 단독 호출되는 함수의 this
는 전역공간(window)를 참조함을 알 수 있다. 아래의 코드도
let myObj = {
val1: 100,
func1: function () {
console.log(this);
}
}
let test = myObj.func1;
test()
// Window {window: Window, self: Window, document: document, name: '', location: Location, …}
myobj
객체를 호출하는 것이 아닌 test
라는 객체를 호출했기에 window 전역공간을 참조한다.
/ * this */
function sayName(){
console.log(this.name);
}
var name = 'Hero';
// 전역으로 선언한 name 변수의 앞에는 window 가 생략되어 있습니다.
// 때문에 window.name === "Hero" 가 성립합니다.
let peter = {
name : 'Peter Parker',
sayName : sayName
}
let bruce = {
name : 'Bruce Wayne',
sayName : sayName
}
sayName(); // Hero
peter.sayName(); // Peter Parker
bruce.sayName(); // Bruce Wayne
/* sayName() 함수를 실행했을 때와
peter, bruce 객체의 sayName 함수를 호출했을 때의 결과를 비교해 보세요 */
위의 예제에서는 각 객체의 이름을 출력함을 알 수 있다.
this
는 함수가 만들어질 때가 아닌 실행될 때 그 값이 결정된다!
function sayName(){
console.log(this.name);
}
var name = 'Hero';
let peter = {
name : 'Peter Parker',
sayName : sayName
};
let bruce = {
name : 'Bruce Wayne',
sayName : peter.sayName
};
bruce.sayName(); // Bruce Wayne
아무리 bruce
함수의 sayName
의 값이 peter
함수의 sayName
이라고 되어있어도 bruce
에 대해 sayName
을 실행했으므로 bruce
의 이름을 출력한다.
자바스크립트의 객체는 키, 값 쌍으로 이루어진 데이터의 묶음이고, 객체 지향의 객체는 표현하고자 하는 구체적인 사물을 추상적으로 표현한 것이라 보면 된다.
위의 그림과 같이 필요한 최소한의 정보로 대상을 표현하는 것을 추상화라고 한다.
const me = {
name : '나',
address : '나의 집',
phoneNum : '010-0000-0000',
sleeping : function(){
console.log('잠을 잔다.');
sleep.fatigue_minus();
console.log('현재 피로도는', sleep.fatigue);
}
}
const sleep = {
fatigue: 10, // 피로도
fatigue_minus : function(){
this.fatigue--;
},
}
console.log('현재 피로도는', sleep.fatigue);
위와 같이 객체를 만들어 객체와 객체 간 서로 메소드를 통해 상호작용하게 하는 것을 객체지향 프로그래밍이라고 한다.
여기서 문제가 있는데 이러한 객체는 재사용이 불가능하다. 따라서 좀 더 효율적인 방법으로 객체를 만들게 된다.
객체를 만들 때 new 연산자와 함께 사용하는 함수이다. 이러한 생성자를 통해 생성된 객체는 같은 프로퍼티와 메서드를 공유할 수 있다.
let myArr = new Array(1,2,3);
let myArr2 = new Array(4,5,6);
myArr.forEach(item=>{
console.log(item);
})
myArr2.forEach(item => {
console.log(item);
})
function NewFactory(name){
this.name = name;
this.sayYourName = function(){
console.log(`삐리비리. 제 이름은 ${this.name}입니다. 주인님.`);
}
}
NewFactory라는 이름의 생성자 함수를 만들었다. 이후 robot1이라는 인스턴스(객체)를 생성해 주었다.
let robot1 = new NewFactory('브랜든');
원래 함수 안에서 this는 함수를 호출한 객체를 참조한다. 그렇지만 생성자 함수 앞에 new 연산자가 사용되면 함수 안의 this는 생성자가 만들어낸 객체(인스턴스)를 참조한다.
다 좋은데 위의 방법도 문제가 있다. 객체의 매서드를 등록 할 때마다 새로운 함수를 생성하고 있다. 100개의 객체를 생성하면 100개의 함수를 새로 생성한다. 이는 메모리의 낭비인데 이것을 해결하기 위해 프로토타입이 등장하였다.
프로토타입은 특정 객체에 대한 참조이다... 생성자 함수가 인스턴스를 생성하게 되면 그 안에는 숨겨진 [[Prototype]]
이 존재하게 된다. 코드상으로 __proto__
라고 하는데 이 __proto__
프로퍼티는 자신을 만든 생성자 함수의 프로토타입을 참조하는 역할을 한다. new 키워드를 통해 생성자 함수의 프로토타입과 인스턴스의 __proto__
가 연결된다.
function Test(){};
const obj = new Test();
obj.__proto__ === Test.prototype // 식으로 나타낸 것
인스턴스는 __proto__
를 통해 생성자 함수의 프로토타입에 접근하여 필요한 여러가지 값과 매서드를 사용할 수 있다.
function Test(){};
const obj = new Test();
console.log(obj.prototype); // undefined
console.log(obj.__proto__ === Test.prototype); // true
모든 자바스크립트의 타입들은 Object 타입을 상속받는 다는 것을 알아두자.
console.log(Array.prototype.__proto__ === Object.prototype);
console.log(Number.prototype.__proto__ === Object.prototype);
console.log(String.prototype.__proto__ === Object.prototype);
console.log(Math.__proto__ === Object.prototype);
// 부모 역할의 생성자 함수 생성
function Parent() {
this.name = '재현';
}
Parent.prototype.rename = function (name) {
this.name = name;
}
Parent.prototype.sayName = function () {
console.log(this.name);
}
// 자식 역할의 생성자 함수 생성
function Child() {
Parent.call(this);
}
Child.prototype = Object.create(Parent.prototype); // 지정된 프로토타입 객체를 갖는 새 객체를 만듭니다.
Child.prototype.canWalk = function () {
console.log('now i can walk!!');
}
call
키워드는 Child 함수의 this가 Parent 생성자 함수의 this를 바라보게 만든다. 그 다음 Object.create 함수로 주어진 인자를 Child.prototype에 연결한다. 이러한 과정을 통해 Child 객체는 parent 객체의 모든 것을 상속받게 된다.
하지만 이러한 프로토타입은 코드의 모습이 지저분하고 생성자 함수에서만 의미를 가지는 문제가 있다. 다른 언어의 프로그래머들도 이해하기 어려운 부분도 있다. 이를 해결하기 위해 클래스를 도입했다.
파이썬의 클래스와 거의 유사하다.
// function Robot(name) {
// this.name = name;
// }
// Robot.prototype.sayYourName = function () {
// console.log(`삐리비리. 제 이름은 ${this.name}입니다. 주인님.`);
// }
class Robot {
// 클래스의 생성자 함수입니다. 하나의 클래스는 하나의 생성자만 정의할 수 있습니다.
// 그리고 생성자 함수는 new 키워드가 호출될때 자동으로 실행됩니다.
constructor(name) {
this.name = name;
}
// 메소드를 정의합니다. 메소드는 클래스가 생성한 인스턴스를 통해 사용할 수 있습니다.
sayYourName() {
console.log(`삐리비리. 제 이름은 ${this.name}입니다. 주인님.`);
}
}
robot1 = new Robot('로봇1');
여기서 this는 파이썬의 self라고 생각해도 좋다. 위의 코드에서 객체지향의 중요한 개념인 인캡슐레이션을 만날 수 있다. 이는 데이터와 해당 데이터를 조작하는 메서드들을 하나의 단위로 묶은 것을 뜻한다.
class BabyRobot extends Robot {
constructor(name) {
super(name);
this.ownName = '아이크';
}
sayBabyName() {
// 또한 상속을 받게되면 부모 클래스의 메소드를 사용할 수 있게 됩니다. 때문에 this로 접근 할 수 있습니다.
this.sayYourName();
console.log('Suceeding you, Father!');
}
}
클래스의 상속은 extends 키워드를 사용한다. 상속을 받는 클래스를 파생 클래스 라고 하며, 부모 클래스의 프로퍼티를 상속받기 위해 super 함수를 사용한다. super는 부모 생성자를 참조한다.
위의 코드는 Robot(부모 클래스)를 extends(상속) 받고 있다.
파이썬이랑 뭔가 비슷한 것 같으면서 다르다. 특히 프로토타입 부분이 아리까리 한데 실습을 해보면서 복습해 봐야겠다. 확실히 눈으로 보는거는 금방 날아가고 글로 쓰면 조금 기억에 남고 실습하거나 설명하면 확실하게 기억되는 것 같다.