클래스, 생성자, 메서드
- 클래스 기반의 언어는 클래스로 객체의 기본적인 형태와 기능을 정의하고, 생성자로 인스턴스를 만들어서 사용할 수 있다. 모든 인스턴스가 클래스에 정의된 대로 같은 구조이고 보통 런타임에 바꿀 수 없다.
- 프로토타입 기반의 언어는 객체의 자료구조, 메서드 등을 동적으로 바꿀 수 있다. -> 자바스크립트는 프로토타입 기반의 언어
- 자바스크립트는 거의 모든 것이 객체이고, 특히 함수 객체로 많은 것을 구현해낸다. 클래스, 생성자, 메서드도 모두 함수로 구현이 가능하다.
<script>
function Person(arg) {
this.name = arg;
this.getName = function () {
return this.name;
}
this.setName = function (value) {
this.name = value;
}
}
var me = new Person("LEE");
console.log(me.getName());
me.setName("KIM");
console.log(me.getName());
</script>
1) 자바스크립트에서 클래스 및 생성자의 역할을 하는 함수가 있고, 사용자는 new 키워드로 인스턴스를 생성하여 사용할 수 있다.
2) me는 Person의 인스턴스로서 name 변수가 있고, getName()과 setName() 함수가 있다.
3) 위 에제의 경우 객체를 여러개 생성할 경우 공통적으로 사용할 수 있는 getName(), setName() 함수를 따로 생성하여 불필요하게 중복되는 영역을 메모리에 올려놓고 사용하여 자원의 낭비가 발생한다.
<script>
function Person(arg) {
this.name = arg;
}
Person.prototype.getName = function () {
return this.name;
}
Person.prototype.setName = function (value) {
this.name = value;
}
var me = new Person("me");
var you = new Person("you");
console.log(me.getName());
console.log(you.getName());
</script>
1) prototype 프로퍼티에 getName(), setName() 함수를 정의
2) 각 객체는 각자 따로 함수 객체를 생성할 필요 없이 getName(), setName() 함수를 프로토타입 체인으로 접근할 수 있다.
<script>
Function.prototype.method = function (name, func) {
this.prototype[name] = func;
}
function Person(arg) {
this.name = arg;
}
Person.method("setName", function (value) {
this.name = value;
});
Person.method("getName", function () {
return this.name;
});
var me = new Person("me");
var you = new Person("you");
console.log(me.getName());
console.log(you.getName());
</script>
- 더글라스 크락포드는 함수를 생성자로 사용하여 프로그래밍하는 것을 추천하지 않는다. 그 기유는 생성된 함수는 new로 호출될 수 있을 뿐만 아니라, 직접 호출도 가능하기 때문이다. 하지만 new로 호출될 때와 직접 호출될 때의 this에 바인딩되는 객체가 달라지기 때문에 생성자로 사용되는 함수는 첫 글자를 대문자로 표기할 것을 권고한다.
상속
- 자바스크립트는 클래스를 기반으로 하는 전통적인 상속을 지원하지 않는다. 하지만 자바스크립트 특성 중 객체 프로토타입 체인을 이용하여 상속을 구현해낼 수 있다.
프로토타입을 이용한 상속
<script>
function create_object(o) {
function F() {}
F.prototype = o;
return new F();
}
</script>
1) create_object() 함수는 인자로 들어온 객체를 부모로 하는 자식 객체를 생성하여 반환한다.
2) 새로운 빈 함수 객체 F를 만들고, F.prototype 프로퍼티에 인자로 들어온 객체를 참조한다.
3) 함수 객체 F를 생성자로 하는 새로운 객체를 만들어 반환한다.
<script>
var person = {
name : "LEE",
getName : function () {
return this.name;
},
setName : function (arg) {
this.name = arg;
}
};
function create_object(o) {
function F() {}
F.prototype = o;
return new F();
}
var student = create_object(person);
student.setName("KIM");
console.log(student.getName());
</script>
1) person 객체를 상속하여 student 객체를 생성
2) 클래스에 해당하는 생성자 함수를 만들지도 않았고, 그 클래스의 인스턴스를 따로 생성하지도 않았다.
3) 단지 부모 객체에 해당하는 person 객체와 이 객체를 프로토타입 체인으로 참조할 수 있는 자식 객체 student를 생성
<script>
var person = {
name : "LEE",
getName : function () {
return this.name;
},
setName : function (arg) {
this.name = arg;
}
};
function create_object(o) {
function F() {};
F.prototype = o;
return new F();
}
function extend(obj, prop) {
if(!prop) { prop = obj; obj = this; }
for(var i in prop) obj[i] = prop[i];
return obj;
};
var student = create_object(person);
var added = {
setAge : function (age) {
this.age = age;
},
getAge : function () {
return this.age;
}
};
extend(student, added);
student.setAge(25);
console.log(student.getAge());
</script>
1) 얕은 복사를 사용하는 extend() 함수를 사용하여 student 객체를 확장
2) extend() 함수는 사용자에게 유연하게 기능 확장을 할 수 있게 하는 주요 함수일 뿐만 아니라, 상속에서도 자식 클래스를 확장할 때 유용하게 사용된다.
클래스 기반의 상속
<script>
function Person(arg) {
this.name = arg;
}
Function.prototype.method = function (name, func) {
this.prototype[name] = func;
}
Person.method("setName", function (value) {
this.name = value;
});
Person.method("getName", function () {
return this.name;
});
function Student(arg) {
}
function F() {};
F.prototype = Person.prototype;
Student.prototype = new F();
Student.prototype.constructor = Student;
Student.super = Person.prototype;
var me = new Student();
me.setName("LEE");
console.log(me.getName());
</script>
<script>
var inherit = function (Parent, Child) {
var F = function () {};
return function (Parent, Child) {
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.super = Parent.prototype;
};
}();
</script>
캡슐화
- 관련된 여러 가지 정보를 하나의 틀 안에 담는 것
<script>
var Person = function (arg) {
var name = arg ? arg : "LEE";
this.getName = function () {
return name;
}
this.setName = function (arg) {
name = arg;
}
};
var me = new Person();
console.log(me.getName());
me.setName("KIM");
console.log(me.getName());
console.log(me.name);
</script>
1) private 멤버로 name 선언, public 메서드로 getName(), setName() 선언
2) this 객체의 프로퍼티로 선언하면 외부에서 new 키워드로 생성한 객체로 접근이 가능했다.
3) 하지만 var로 선언된 멤버들은 외부에서는 접근이 불가능하다.
4) public 메서드가 클로저 역할을 하면서 private 멤버인 name에 접근할 수 있다.
5) 자바스크립트의 기본적인 정보 은닉 방법
<script>
var Person = function (arg) {
var name = arg ? arg : "LEE";
return {
getName : function () {
return name;
},
setName : function (arg) {
name = arg;
}
};
}
var me = Person();
console.log(me.getName());
</script>
1) Person 함수를 호출하여 객체를 반환받는다. 이 객체에 Person 함수의 private 멤버에 접근할 수 있는 메서드들이 담겨있다.
2) 사용자는 반환받는 객체로 메서드를 호출할 수 있고, private 멤버에 접근할 수 있다.
<script>
var ArrCreate = function (arg) {
var arr = [1, 2, 3];
return {
getArr : function () {
return arr;
}
};
}
var obj = ArrCreate();
var arr = obj.getArr();
arr.push(5);
console.log(obj.getArr());
</script>
1) 주의할 점이 있다. 접근하는 private 멤버가 객체나 배열이면 얕은 복사로 참조만을 반환하므로 사용자가 이후 이를 쉽게 변경할 수 있다.
<script>
var Person = function (arg) {
var name = arg ? arg : "LEE";
var Func = function () {}
Func.prototype = {
getName : function () {
return name;
},
setName : function (arg) {
name = arg;
}
};
return Func;
}();
var me = new Person();
console.log(me.getName());
</script>
1) 꼭 객체가 반환되어야 하는 경우에는 깊은 복사로 복사본을 만들어서 반환하는 방법을 사용하는 것이 좋다.
2) 객체를 반환하는 것이 아닌 함수를 반환하여 사용자가 반환받은 객체가 Person 함수 객체의 프로토타입에 접근할 수 있도록하고, Person을 부모로 하는 프로토타입을 이용한 상속을 구현한다.
3) 클로저를 활용하여 name에 접근할 수 없게 했다. 즉시 실행 함수에 반환되는 Func가 클로저가 되고 이 함수가 참조하는 name 프로퍼티가 자유 변수가 된다. 따라서 사용자는 name에 대한 접근이 불가능하다.