1. 프로토타입이란?
어떤 제품을 만드는 과정에 시험용으로 미리 만들어본 물건이다
자바스크립트는 프로토타입을 기반으로 상속을 구현한 언어이다.
빈 {}를 콘솔창에 찍어보면 어떤 Prototype을 상속받았는지 볼수있다
빈 []을 콘솔창에 찍어보자.
엔진에서 자동적으로 new Array()를 쳐준것이다. 밑쪽에 보면은 밑에 [[Prototype]] : Object가 하나 더있다.
또 펼쳐 보면은, 객체가 또 부모로 있는데, Object가 최상위 객체임을 알수있다.
모든 객체의 상속이 결국에는 올라가는 구조로 되어있다.(이것을 다중상속, 즉 프로토타입 체인이라고 한다)
오토박싱 : 원시타입 객체를 객체타입으로 바꿔주는것.
name이라는 변수를 선언했다. name을 쳤더니 그냥 'hello'만 나온다.
그렇다면, name. 을하면은 아무것도 안나오는게 정상이다.
근데 실제로는 . 찍으면 기능들이 많이 나타난다.
예를들어 name.concate("Lu") ==> 이때만 잠깐 원시 -> 객체타입으로 바꿔준다.
. 점 찍는순간!
String이라고하는 생성자함수를 name이 상속받고있다. 그렇기때문에 charAt같은 내부연산자를 쓸 수 있는 것이다.
근데 name = null; 을 대입하면 아예 주소값을 없애버린다.
2. prototype. __proto__
함수객체는 일반객체와 달리 prototype프로퍼티가 추가로 있다.
모든 일반 함수는 prototype을 가지고 있어서 모든 함수는 생성자함수를 통해 객체를 생성할수있다.
예)
function machine(){
this.bolt = "strike";
this.nut = "snowball";
}
var nunu = new machine()
nunu라는 오브젝트가 만들어진다. machine은 오브젝트 복사 기계인 것이다.
이런식으로 자식에게 물려줄수도 있지만 또 다른 방법으로 프로토타입이라는것을 써도 자식 object에게 데이터를 물려줄수있다
machine이라는 함수가 만들어지는데 machine.prototype이라는 비밀공간도 같이 생긴다.
프로토타입은? 그냥 유전자라고 이해를 하자.
machine.prototype.name = "morae"라고 선언을 했다.
그냥 유전자에 name이라는 공간에 morae를 저장한거다.
자식도 전부다 가져다 쓸수있다.
nunu를 쳐봐도 name:"kim"이 나오지는 않는다.
자식에게 변수를 넘겨주는 2가지 방법을 보았는데, 이 두가지 방법의 차이점은
첫번째로, 직접 변수를 넘겨주는 경우
자식이 객체를 직접 가지고있다.
하지만 prototype으로 넘겨준다면
부모만 가지고있게 된다. 자식이 그냥 끌어다 쓴다. (출처 : 코딩애플 유튜브)
즉, 이 prototype을 통해서 constructor를 만들고 생성자가 객체를 만들고 new를 통해서 생성자객체를 생성을 해서 인스턴스는 생성자객체의 상속을 받는다.
즉, 생성자함수를 객체로 만든것을 프로토타입이라고 한다.
그 객체 안에 constructor라는 생성자가 있고 new를 통하면은 생성자함수로 호출할수가 있다.
그 이유가 constructor가 내재되어있기때문에 new에서 작동을 한거다.
constructor는 생성자함수로 만든 객체를 바인딩한것이다.
obj3는 Person으로 객체를 만든 인스턴스. __proto__를 통해서 부모객체에 접근할 수 있다.
다시, __proto__와 prototype의 차이를 보면
__proto__ : Object가 가지고있다. 모든 객체들이 상속받았기 때문에 모두가 가지고있다
get, set접근자 프로퍼티로 만들어져있다
Object내부에서 만들어졌기 때문에 get, set을 통해 상위객체로 간편하게 객체를 꺼내오고 넣을 수 있다.
Object로 만든 생성자함수의 부모를 가져온다
예제로 보자.
function Person(name){
this.name = name;
}
let foo = new Person('Lee')
1) 그냥 Person(= foo.constructor)
2) Person.prototype(= foo.__proto__)
3) Person.prototype.constructor(= foo.__proto__.constructor)
4) Person.constructor(= Function)
3. 생성자 함수에만 prototype가 있다.
console.log(function () {}.hasOwnProperty('prototype')); //true(생성자함수에만 prototype가 있다)
function arrowtwo(a,b){
return a+b;
}
console.log(arrowtwo.hasOwnProperty('prototype')); //true
4. 일반객체는 prototype가 없다
console.log({}.hasOwnProperty('prototype')); //false(일반객체는 prototype가 없다)
5. arrow함수에는 prototype가 없다
let arrow = (a, b) => a + b;
console.log(arrow.hasOwnProperty('prototype')); //false(arrow함수는 생성자함수가 아니기때문에 prototype을 갖고있지않다)
6. 일반함수메서드에만 prototype이 있다
class Animal {
constructor(name, sound) { //이부분은 prototype에있는 constructor를 쓰는것과 똑같다.
this.name = name;
this.sound = sound;
this.sayHello = function () { //ES5버전 일반함수 메서드
console.log(`안녕`);
};
}
makeSounds() { //ES6버전 메서드 축약방식(prototype을 가지지 않는다)
console.log(`${this.sound} 소리를 내다`);
}
}
const cat = new Animal('나비', '미아옹');
console.log(cat.hasOwnProperty('prototype')); // false
console.log(cat.makeSounds.hasOwnProperty('prototype'));// false (메서드로는 생성자함수 쓸 수 없다)
console.log(cat.sayHello.hasOwnProperty('prototype')); // true(일반함수메서드에만 prototype이 있다)
console.log(cat.__proto__) // 결과로 {}가 나오는데, 우리가 object를 상속받았다는 의미이다
7. 객체에 __proto__가 있는게 아니라 객체가 Object를 상속받았기 때문에 __proto__가 있는 것이다.
const obj = {};
console.log(obj.hasOwnProperty('__proto__')); //false가 나온다. obj가 __proto__를 갖고있는게 아니라 부모객체인 Object에 접근해서 __proto__가져왔던것.
console.log(obj.__proto__.hasOwnProperty('__proto__')); //true가 나온다. __proto__를 통해 Object객체로 접근했으니까.
8.
function Person(name) {
this.name = name;
}
const me = new Person('박연미');
console.log(me.constructor === Person); //true
console.log(me.__proto__); // {constructor: ƒ}
그림으로 보면,
내가 그린기린그림
요약 :
자바스크립트는 특정한 객체의 프로퍼티나 메서드에 접근할려고 할 때 해당 객체의 프로퍼티 메서드가 없으면 프로토타입이라고하는 애가 있어서 그거를 링크로 가서 부모를 참조를 해서 거기에 있는 프로퍼티나 메소드를 찾아서 검색을 한다. 이거를 프로토타입체인이라고 한다.
{}의 부모는 Object이다. 그러므로 아래와 같다.
let lee = {
name: 'Lee',
score: 90,
};
console.log(lee);
console.log(lee.hasOwnProperty('name')); // name값이 있으니까 true
console.log(lee.__proto__ === Object.prototype); //
console.log(Object.prototype.hasOwnProperty('hasOwnProperty')); //lee한테 없으니까 상위객체로 가서 hasOwnProperty를 찾은것이다
9. Object안에 내부 함수 중에 keys, values, entries 객체를 가져올 수 있는 메소드가 있다
//static메서드라서 인스턴스를 만들지 않고 클래스 이름으로 접근할수 있는 요소들.
const dog = { name: '바둑이', age: 3 };
console.log(Object.keys(dog));
console.log(Object.values(dog));
console.log(Object.entries(dog));
10. 프로퍼티가 있는지 없는지 알아보는 방법
//프로퍼티가 있니 없니?
const dog = { name: '바둑이', age: 3 };
console.log('name' in dog); //true 있다
console.log(dog.hasOwnProperty('name'));//true 있다
11. getOwnPropertyDescriptors를 써서 Object안에의 상태정보를 descriptors라는 객체로 받아올수있다
(끝에 s가 붙으면 모든 프로퍼티에 대한 정보를 보는 옵션)
const dog = { name: '바둑이', age: 3 };
const descriptors = Object.getOwnPropertyDescriptors(dog);//Object안에의 상태정보를 descriptors라는 객체로 받아올수있다
console.log(descriptors);
결과
**프로퍼티 어트리뷰트(데이터 프로퍼티)
value : 넣어준 value값
writable : value를 수정할 수 있는지 없는지
enumerable : for in이라던가 Object키를 통해 값들을 열거할 수 있는지 없는지 false면은 속성을 보여주지 않음
configurable : 앞의 설정값들을 수정할 수 있는지 없는지.
11.2. getOwnPropertyDescriptor를 통해 어트리뷰터 하나의 속성값만 보기
const dog = { name: '바둑이', age: 3 };
const desc = Object.getOwnPropertyDescriptor(dog, 'name');
console.log(desc);
답
12. defineProperty를 통해 Object의 상태정보를 바꿔보고 적용해보자.
const dog = { name: '바둑이', age: 3 };
const descriptors = Object.getOwnPropertyDescriptors(dog);//Object안에의 상태정보를 descriptors라는 객체로 받아올수있다
console.log(descriptors);
const desc = Object.getOwnPropertyDescriptor(dog, 'name');
console.log(desc);
Object.defineProperty(dog, 'name', {
value: '멍멍',
writable: false,
enumerable: false,
configurable: false,
});
dog.name = '왈왈'; //writable을 false로 바꿨기때문에 왈왈로 변경이 되지 않는다.
console.log(dog.name); //멍멍
console.log(Object.keys(dog)); //enumerable를 false로 바꿨기때문에 name은 보이지 않고 age만 보인다
delete dog.name; //configurable를 false로 했기때문에 삭제도 되지 않는다.
console.log(dog.name);
13. defineProperties을 사용하여 객체를 안에있는 속성값을 추가, 조작해보자
const teacher = {};
Object.defineProperties(teacher, { //defineProperties로 객체를 안에있는 속성값을 추가해서 만든다.
firstName: {
value: '연미',
writable: true,
enumerable: true,
configurable: true,
},
lastName: {
value: '박',
writable: true,
enumerable: true,
configurable: true,
},
fullName: { //은닉화.
get() {
return `${this.lastName} ${this.firstName}`;
},
set(name) {
this.lastName = name[0];
this.firstName = name.slice(1);
},
configurable: true,
// enumerable: true, //이거를 주석처리하면 fullName함수가 보이지 않는다.
},
});
console.log('--------------');
console.log(teacher);
console.log(teacher.fullName); //fullName의 get함수를 호출한것
teacher.fullName = '오은영'; //fullName의 set이 실행이 되고 안에있는 lastName, firstName을 수정한다.
console.log(teacher); //그 후에 teacher을 실행했더니 오 은영으로 바뀐다.
14. 이제 현대방법으로 바꿔보자.
writable : false대신 freeze를 사용
//속성값 알아보기
const yeonmi = { name: '박연미' };
const dog = { name: '바둑이', age: 5, owner: yeonmi };
Object.freeze(dog); //freeze는 사실 writable을 false로 만드는 것이다.
dog.name = '누렁이';
console.log(dog); //누렁이로 바뀌지 않고 여전히 바둑이!
freeze를 하면 수정도 되지 않고 삭제도 되지 않는다. 그러나 yeonmi라는 객체는 변경이 가능하다. 주소값을 저장해둔것 뿐이니까. 객체는 얕은 복사가 된다.
//속성값 알아보기
const yeonmi = { name: '박연미' };
const dog = { name: '바둑이', age: 5, owner: yeonmi };
Object.freeze(dog); //freeze는 사실 writable을 false로 만드는 것이다.
dog.play = function () {
console.log('뛰어놀기 ');
}; //추가도 되지 않음
console.log(dog);
delete dog.name; //삭제도 되지 않음
console.log(dog);
yeonmi.name = '아이유'; //owner부분의 yeonmi는 yeonmi의 주소값을 갖고있는것이다. 얕은복사가 된다!
console.log(dog);
15. Object를 복제한 다음 복제한 객체의 수정은 가능하다.
//속성값 알아보기
const yeonmi = { name: '박연미' };
const dog = { name: '바둑이', age: 5, owner: yeonmi };
Object.freeze(dog); //freeze는 사실 writable을 false로 만드는 것이다.
const cat = { ...dog }; //객체의 깊은복사. 스프레드 연산자를 이용해 cat이라는 새로운 방을 만들었다(복제함!)
//Object.assign(cat, dog); //위와 같은 의미.
cat.owner = '유재석';
console.log(dog);
16. seal을 이용한 밀봉 후 데이터수정, 삭제, 추가, 속성재정의
seal의 의미 :
writable : true, //수정만 가능
enumerable : true,
configurable : false //삭제, 추가, 속성재정의는 불가능
const yeonmi = { name: '박연미' };
const dog = { name: '바둑이', age: 5, owner: yeonmi };
Object.freeze(dog); //freeze는 사실 writable을 false로 만드는 것이다.
const cat = { ...dog };
Object.seal(cat); //seal:밀봉.
cat.name = '나비'; //변경은 가능
console.log(cat);
delete cat.owner; //삭제는 불가능
console.log(cat);
17. preventExtensions로 확장을 막는다.
const rabbit = { name: '토순이' };
Object.preventExtensions(rabbit); //확장을 막는다. 이름을 바꿀수는 있지만 새로운 요소를 추가할수는 없다.
rabbit.name = '토깽이';
console.log(rabbit);
rabbit.age = 1; //age를 넣을 수 없다.
console.log(rabbit);
delete rabbit.name; //삭제는 가능함
console.log(rabbit);
18. isFrozen, isSealed, isExtensible를 통해 현재 상태를 볼수도 있다.
const dog = { name: '바둑이', age: 5};
Object.freeze(dog); //freeze는 사실 writable을 false로 만드는 것이다.
const cat = { ...dog };
Object.seal(cat); //seal:밀봉.
const rabbit = { name: '토순이' };
Object.preventExtensions(rabbit); //확장을 막는다. 이름을 바꿀수는 있지만 새로운 요소를 추가할수는 없다.
console.log(Object.isFrozen(dog)); //true
console.log(Object.isSealed(cat)); //true
console.log(Object.isExtensible(rabbit)); //false
19. 생성자함수의 상속과 활용
// 프로토타입을 베이스로한 객체지향 프로그래밍
function Person(name, age) { //생성자함수
this.name = name;
this.age = age;
}
Person.prototype.eat = function () { //Person이라는 객체에 eat이라는 메서드를 추가.
console.log(`${this.name}은 밥을 먹는다 `);
};
function Teacher(name, age, teacherNo) {
// super(name, age)
Person.call(this, name, age); //name하고 age를 외부에서 받는게 아니라 Person을 부를꺼야!
//Person생성자함수에 있는 this를 가져온다 즉, name이랑 age는 Person으로 넘겨준다.
this.teacherNo = teacherNo;
}
Teacher.prototype = Object.create(Person.prototype);
//원래 object.prototype을 Person.prototype으로 변경하겠다.
//Teacher생성자함수를 Person생성자함수에 상속해준다.
//class Teacher extends Person과 같음.
console.log(Teacher);
//자식 클래스의 메서드 추가
Teacher.prototype.teach = () => {
console.log('가르친다');
};
const yeonmi = new Teacher('박연미', 30, 12341234);
yeonmi.teach(); //가르친다
yeonmi.eat(); //박연미은 밥을 먹는다
function Student(name, age) {
Person.call(this, name, age); //Student에 값을 넘겨줬지만 실은 Person에서 실행이 되는것.
}
//Student클래스를 Person에 상속
Student.prototype = Object.create(Person.prototype);
Student.prototype.study = () => { //메서드 추가.
console.log(`공부하자!! `);
};
Teacher에는 eat이 없고 Person에만 eat이 있는데, Teacher.prototype = Object.create(Person.prototype); 에서
Person.prototype으로 Teacher.prototype을 변경했기때문에 Person에 접근할수있다
19-2. 두번째 예
function Person(name, age) { //생성자함수
this.name = name;
this.age = age;
}
Person.prototype.eat = function () { //Person이라는 객체에 eat이라는 메서드를 추가.
console.log(`${this.name}은 밥을 먹는다 `);
};
function Teacher(name, age, teacherNo) {
// super(name, age)
Person.call(this, name, age); //name하고 age를 외부에서 받는게 아니라 Person을 부를꺼야!
//Person생성자함수에 있는 this를 가져온다 즉, name이랑 age는 Person으로 넘겨준다.
this.teacherNo = teacherNo;
}
Teacher.prototype = Object.create(Person.prototype);
//원래 object.prototype을 Person.prototype으로 변경하겠다.
//Teacher생성자함수를 Person생성자함수에 상속해준다.
//class Teacher extends Person과 같음.
console.log(Teacher);
//자식 클래스의 메서드 추가
Teacher.prototype.teach = () => {
console.log('가르친다');
};
function Student(name, age) {
Person.call(this, name, age); //Student에 값을 넘겨줬지만 실은 Person에서 실행이 되는것.
}
//Student클래스를 Person에 상속
Student.prototype = Object.create(Person.prototype);
Student.prototype.study = () => { //메서드 추가.
console.log(`공부하자!! `);
};
const hgd = new Student('홍길동', 20);
hgd.eat(); //이것도 마찬가지로 Student.prototype = Object.create(Person.prototype);을 했으니 가능!
hgd.study();
이것도 마찬가지로 Student.prototype = Object.create(Person.prototype);을 했으니 상속했다!
19-3. 세번째
function Person(name, age) { //생성자함수
this.name = name;
this.age = age;
}
Person.prototype.eat = function () { //Person이라는 객체에 eat이라는 메서드를 추가.
console.log(`${this.name}은 밥을 먹는다 `);
};
function Teacher(name, age, teacherNo) {
// super(name, age)
Person.call(this, name, age); //name하고 age를 외부에서 받는게 아니라 Person을 부를꺼야!
//Person생성자함수에 있는 this를 가져온다 즉, name이랑 age는 Person으로 넘겨준다.
this.teacherNo = teacherNo;
}
Teacher.prototype = Object.create(Person.prototype);
//원래 object.prototype을 Person.prototype으로 변경하겠다.
//Teacher생성자함수를 Person생성자함수에 상속해준다.
//class Teacher extends Person과 같음.
console.log(Teacher);
//자식 클래스의 메서드 추가
Teacher.prototype.teach = () => {
console.log('가르친다');
};
function Student(name, age) {
Person.call(this, name, age); //Student에 값을 넘겨줬지만 실은 Person에서 실행이 되는것.
}
//Student클래스를 Person에 상속
Student.prototype = Object.create(Person.prototype);
Student.prototype.study = () => { //메서드 추가.
console.log(`공부하자!! `);
};
console.log(yeonmi instanceof Teacher); //true
console.log(yeonmi instanceof Person); //true
console.log(yeonmi instanceof Student); //false
console.log('-------------');
console.log(hgd instanceof Teacher); //false
console.log(hgd instanceof Person); //true
console.log(hgd instanceof Student); //true
그림으로 보면,
'Javascript' 카테고리의 다른 글
오답노트) 3,2,1,끝이 1초에 한번 출력 (2) | 2022.10.04 |
---|---|
이벤트루프, 동기,비동기,Promise (0) | 2022.10.04 |
여러개의 element를 공통된 TagName으로 가져온 후 addEventListener처리해보기 (0) | 2022.10.02 |
export로 내보내고, import로 가져오기 (0) | 2022.10.02 |
함수선언문과 함수표현식의 차이, 호이스팅 (0) | 2022.10.02 |