일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 코딩테스트
- border radius
- html 끝
- 노마드 코더
- 백준 정리
- git
- button:focus cursor: pointer; outline: none;
- 나도코딩 파이썬
- nav태그
- li 태그
- :root
- 나도코딩
- 크롬웹
- calc()
- 드림코딩
- 할만한데?
- 생활코딩 WEB2-JavaScript
- margin 0 auto
- box-sizing: border-box
- git 버전관리
- Pull
- 생활코딩
- 백준
- 단계별로 풀어보기
- error: ENOENT: no such file or directory
- max-width
- 백준 자바스크립트
- WEB2-JavaScript
- 라매개발자
- HTML
- Today
- Total
코딩응애의 개발블로그
JavaScript 객체 지향 프로그밍 - 생활코딩 본문
3.2 객체와 반복문
console.group() & console.groupEnd()
웹 콘솔 로그에 새로운 인라인 그룹을 만듭니다. 이는 console.groupEnd()가 호출될 때까지 모든 다음 출력을 추가 수준으로
들여씁니다. 그냥 이런게 있구나 알아만 두기
4.2 객체 만들어 보기
객체란 서로 연관된 변수와 함수를 그룹핑 하고 이름을 붙인것이고 정리정돈을 할 수 있다.
5. this
프로그래밍에서 자기 자신을 가리키는 표현이 있는데 그게 바로 this 이다.
let kim = {
name:'kim',
first:10,
second:20,
sum:function(f,s){
return f+s;
}
}
console.log("kim.sum(kim.first, kim.second)", kim.sum(kim.first,kim.second));
// ------ 1차 개선 ------------
let kim = {
name:'kim',
first:10,
second:20,
sum:function(){
return kim.first+kim.second;
}
}
//console.log("kim.sum(kim.first, kim.second)", kim.sum(kim.first, kim.second));
console.log("kim.sum(kim.first, kim.second)", kim.sum());
// 이것도 아쉬움 왜냐면 kim을 k로 바꾼다면 동작을 안하기 때문에 그러면 일일히 kim을 k로 바꿔줘야한다
// 그리고 sum 이라는 함수가 속해있는 객체가 어떤 이름을 가지게 될지 예상하는건 논리적이지 않다
이러한 경우로 인해 객체 지향을 만든 사람들은 메소드가 있으면 그 메소드가 자신이 속해있는 객체를 가리키는 특수한 키워드를 만들었는데 그것이 바로 this다.
let kim = {
name:'kim',
first:10,
second:20,
sum:function(){
return this.first+this.second;
}
}
//console.log("kim.sum(kim.first, kim.second)", kim.sum(kim.first, kim.second));
console.log("kim.sum(kim.first, kim.second)", kim.sum());
// 이렇게 해주면 kim을 k든 다른 단어로 바꿔도 잘 동작하는것을 볼 수 있다.
6.1 constructor의 필요성
let kim = {
name:'kim',
first:10,
second:20,
third:30,
sum:function(){
return this.first+this.second+this.third;
}
}
let lee = {
name:'lee',
first:10,
second:10,
third:10,
sum:function(){
return this.first+this.second+this.third;
}
}
console.log("kim.sum()", kim.sum());
console.log("lee.sum()", lee.sum());
객체의 기본적인 동작방법이 바뀌면 같은 취지에 객체는 다 바꿔줘야 하는데 갯수가 적다면 일일히 바꿔도 큰 문제는 없겠지만 저런 코드가 만약 1억개가 있다면... 이럴때는 저런 형식의 객체를 찍어내는 공장을 만들어야 한다
6.2 constructor의 사례
객체를 찍어내는 공장의 사례 중 하나를 들자면
let d1 = new Date('2019-4-10'); // 내부적으로 2019-4-10 데이터를 가지고 있는 새로운 객체가 만들어져서 d1이 된다
console.log('d1.getFullYear()', d1.getFullYear()); // 2019 출력
저 Date라는 무언가는 앞에 new를 붙였을때 객체를 만들어서 우리한테 리턴해 주고 있다는걸 알 수 있다.
그렇다면 let kim 객체도 저런식으로 공장을 만든다면 코드도 훨씬 깨끗해 질것이다...
6.3 constructor 만들기
우선 let kim 객체에 내용을 가져와서 새로운 함수 Person()을 만들어보면
function Person() {
this.name = 'kim';
this.first = 10;
this.second = 20;
this.third = 30;
this.sum = function () {
return this.first + this.second + this.third;
}
}
console.log(Person()); // undefined 출력
// Person 앞에다가 new를 붙히면 Person이라는 객체가 만들어진다
console.log(new Person()); //Person { name: 'kim', first: 10, second: 20, third: 30, sum: [Function (anonymous)]} 출력
함수를 그냥 호출하면 그냥 함수인데 앞에다가 new를 붙이면 객체를 생성하는 생성자(constructor)가 된다
앞에 new가 붙어있다면 생성자 함수라고 함
이제 생성자를 만들어보면 이렇게 만들 수 있다
function Person(name, first, second){
this.name=name;
this.first=first;
this.second=second;
this.sum = function(){
return this.first+this.second;
}
}
let kim = new Person('kim', 10, 20);
let lee = new Person('lee', 10, 10);
console.log("kim.sum()", kim.sum()); // kim.sum() 60
console.log("lee.sum()", lee.sum()); // lee.sum() 30
이렇게 'new' 연산자와 생성자 함수를 사용하면 유사한 객체 여러 개를 쉽게 만들 수 있습니다.
함수 이름의 첫 글자는 대문자로 시작하고 반드시 'new' 연산자를 붙여 실행한다.
생성자함수를 사용하면 객체생성용 함수의 내부코드를 반복적으로 노출시킬 필요도 없고(은닉성/깔끔함),
new 생성자명() 식으로 간단히 객체를 생성할 수 있으며(편리),
모든 객체들의 원본인 생성자함수의 내부 코드만 수정하면 해당 생성자함수로 파생된 모든 객체들에도
내용이 적용되는 폭발적인 유지보수의 편리함이 있다.
7.1 prototype
JS를 프로토타입기반언어 라고 부르기도 한다.
Person이라고 하는 생성자를 사용하는 모든 객체에 sum 함수를 바꾸고 싶다면 그 개수에 따라 작업을 해야 한다
1억개라면 1억번...
생성자 함수 안에서 메소드를 정의할 경우 새로운 객체를 생산할 때마다 메소드를 정의하는 코드가 실행되어야 하고
같은 메소드가 중복 실행되면서 메모리를 낭비하게된다.
이것이 생성자 안에서 메소드를 만드는것에 단점이다. 즉 생산성이 많이 떨어진다..
7.2
function Person(name, first, second){
this.name=name;
this.first=first;
this.second=second;
}
Person.prototype.sum = function(){ // 생성자 함수에 공통적으로 사용할 sum 메소드 만들기
return 'prototype : '+(this.first+this.second);
}
var kim = new Person('kim', 10, 20);
kim.sum = function(){
return 'this : '+(this.first+this.second);
}
var lee = new Person('lee', 10, 10);
console.log("kim.sum()", kim.sum()); // kim.sum() this : 30
console.log("lee.sum()", lee.sum()); // lee.sum() prototype : 20
저러한 반복 작업과 메모리 낭비로 인해 나온것이 prototype 이다.
class constructor
어떤 객체가 생성될때 그 객체에 초기 상태를 지정하기 위한, 또는 객체가 생성되기전에 실행되도록 약속되어 있는 함수
바로 constructor() 함수이다. 약속된 이름이라 우리가 함부로 다르게 지정할 수 없다.
그리고 JS는 객체를 생성할때 constructor() 함수를 자동으로 호출해준다.
class Person{ // => 위에 function Person() 생성자 함수를 class를 이용해서 생성자 함수로 바꿈
constructor(name, first, second){
this.name = name;
this.first = first;
this.second = second;
}
}
var kim = new Person('kim', 10, 20);
console.log('kim', kim); // Person { name: 'kim', first: 10, second: 20 } 출력
class에서 객체의 메소드 구현하기
prototype을 class에서 구현을 할 수 있는데 어떻게 하냐면 일단 첫째 그냥 그대로 쓰는 방법
class Person{ // => 위에 function Person() 생성자 함수를 class를 이용해서 생성자 함수로 바꿈
constructor(name, first, second){
this.name = name;
this.first = first;
this.second = second;
}
}
Person.prototype.sum = function(){ // 생성자 함수에 공통적으로 사용할 sum 메소드 만들기
return 'prototype : '+(this.first+this.second);
}
var kim = new Person('kim', 10, 20);
console.log('kim', kim); // Person { name: 'kim', first: 10, second: 20 } 출력
console.log(kim.sum()); // prototype : 30 출력
이거 말고 클래스 안에다가 넣는 방법도 있다.
class Person{
constructor(name, first, second){
this.name = name;
this.first = first;
this.second = second;
}
sum(){ // 프로토타입과 같은 기능을 한다
return 'prototype : '+(this.first+this.second);
}
}
let kim = new Person('kim', 10, 20);
kim.sum = function(){
return 'this : '+(this.first+this.second);
}
let lee = new Person('lee', 10, 10);
console.log("kim.sum()", kim.sum()); // kim.sum() this : 30
console.log("lee.sum()", lee.sum()); // lee.sum() prototype : 20
위에 코드 처럼 클래스 내부에 sum 이라는 함수를 선언하면 그 함수는 메소드가 되는건 알고 있지?
암튼 이렇게 메소드를 만들면 자동으로 prototype으로 등록이 된다고 한다 와우...
class 상속
상속이 없다면 부모 클래스를 기반으로 자식 클래스를 만들때 기반이 되는 클래스의 기능을 모두 옮겨 적어야 되는
불편함과 코드의 중복이 발생하고 부모 클래스를 수정을 하면 부모의 자식 클래스 또한 다 수정을 해야하는 엄청난
중복이 발생한다. 예시 코드를 보자면
class Person{
constructor(name, first, second){
this.name = name;
this.first = first;
this.second = second;
}
sum(){
return this.first+this.second;
}
}
class PersonPlus {
constructor(name, first, second){
this.name = name;
this.first = first;
this.second = second;
}
sum(){
return this.first+this.second;
}
avg(){
return (this.first+this.second)/2;
}
}
var kim = new PersonPlus('kim', 10, 20);
console.log("kim.sum()", kim.sum()); // kim.sum() 30 출력
console.log("kim.avg()", kim.avg()); // kim.avg() 15 출력
이렇게 해도 되지만 중복이라는 아쉬움이 발생하게 된다. 중복을 제거해야 하는데 이때 사용하는게 상속이다.
extends 라는 키워드를 이용해서 상속을 한다.
class Person{
constructor(name, first, second){
this.name = name;
this.first = first;
this.second = second;
}
sum(){
return this.first+this.second;
}
}
class PersonPlus extends Person{
avg(){
return (this.first+this.second)/2;
}
}
var kim = new PersonPlus('kim', 10, 20);
console.log("kim.sum()", kim.sum()); // kim.sum() 30 출력
console.log("kim.avg()", kim.avg()); // kim.avg() 15 출력
똑같은 결과가 나오는 것을 볼 수 있다.
super
부모클래스가 갖고 있는 기능을 실행할 수 있다.
객체간의 상속
자바같은 유동적인 주류 객체 지향 언어에서는 부모 클래스로 부터 상속을 받은 자식 클래스를 통해 객체를 생성을 하는데
자바스크립트 에서는 객체가 직접 다른 객체에 상속을 받을 수 있고 얼마든지 상속 관계를 바꿀 수 있다.
이 때 상속을 하는 객체를 prototype ojbect라고 하며 상속을 받는 객체와 prototype link로 연결되어 있다.
let superObj = {superVal:'super'}
let subObj = {subVal:'sub'}
subObj.__proto__ = superObj; // 이렇게 하면 subObj는 superObj 객체의 자식이 됨
console.log('subObj.subVal =>', subObj.subVal); // subObj.subVal => sub 출력
console.log('subObj.superVal =>', subObj.superVal); // subObj.superVal => super 출력
subObj.superVal = 'sub';
console.log('superObj.superVal =>', superObj.superVal); // superObj.superVal => super 출력
console.log('subObj.superVal =>', subObj.superVal); // subObj.superVal => super 출력 하는 이유
__proto__ 라는 프로토타입 링크를 통해서 subObj가 superObj의 자식이다 라고 링크를 걸어주니까
제일 먼저 subObj에서 superVal이라는 속성이 있는지를 찾는데 없으니까 그러면 __proto__ 속성이 담고 있는 객체에서
superVal라는 속성이 있는지 찾고 있으니 그 값을 쓰는것임.
그렇다면 subObj.superVal = 'sub'; 이렇게 바꿔주면 값이 바뀔까? 정답은 아니다
왜냐면 subObj에 객체 값을 바꾼것이지 __proto__가 가리키는 superObj를 바꾼게 아니다
__proto__를 대신할 수 있는 방법이 있는데 Object.create를 이용하는 것이다.
let superObj = {superVal:'super'}
// let subObj = {subVal:'sub'}
// subObj.__proto__ = superObj;
let subObj = Object.create(superObj);
subObj.subVal = 'sub';
debugger;
console.log('subObj.subVal =>', subObj.subVal);
console.log('subObj.superVal =>', subObj.superVal);
subObj.superVal = 'sub';
console.log('superObj.superVal =>', superObj.superVal); // 바로 위에 코드 결과와 같은 결과가 나옴
이제 위에 개념들을 바탕으로 실습에서 활용할 예제들을 한번 살펴 보자면
let kim = {
name:'kim',
first:10, second:20,
sum:function(){return this.first+this.second}
}
// let lee = {
// name:'lee',
// first:10, second:10,
// avg:function(){
// return (this.first+this.second)/2;
// } 부모 객체에는 없는 메소드를 새로 만들어 줄 수도 있다
// }
// lee.__proto__ = kim;
let lee = Object.create(kim);
lee.name = 'lee';
lee.first = 10;
lee.second = 10;
lee.avg = function(){
return (this.first+this.second)/2;
}
console.log('lee.sum() : ', lee.sum()); // lee에 sum이 없어도 kim객체를 상속하기 때문에 결과는 20이 나온다
console.log('lee.avg() : ', lee.avg());
객체와 함수
call()
let kim = {name:'kim', first:10, second:20}
let lee = {name:'lee', first:10, second:10}
function sum(prefix){
return prefix+(this.first+this.second);
}
// sum();
console.log("sum.call(kim)", sum.call(kim, '=> ')); // sum.call(kim) => 30 출력
console.log("lee.call(lee)", sum.call(lee, ': ')); // lee.call(kim) : 20 출력
위에 적힌 코드를 보면 sum()은 어떤 객체에도 속해있지 않음. 그렇다면 객체 안에있는 first,second 는 어떻게 더할 수 있을까
이때 sum.call();을 이용하는데 이건 sum(); 과 같은 의미이다. 말그대로 함수를 실행해준다는 의미
이 call 메소드를 호출할때 인자로 kim을 준다면 저 this는 kim이 되는것임 그래서 함수가 객체에 속해 있지 않아도
kim의 메소드가 되는 효과를 얻을 수 있다.
call은 인자를 몇개를 받을 수 있는데 첫번째 인자로는 this를 대체하는 인자가 오고 두번째 인자로는 우리가 호출할려고 하는
함수에 파라미터(매개변수)에 들어갈 인자값들이 들어오게 된다.
console.log("sum.call(kim)", sum.call(kim, '=> '));
그래서 '=> '이 인자가 prefix가 있는 값으로 들어가게 되는것이다
bind()
call()은 호출될때마다 this를 바꾸는데 비해 bind()는 실행되는 함수의 this값을 원하는 객체로 고정시키는 새로운 함수를 만들어낸다. call 처럼 bind도 그 함수가 호출될때마다 사용될 인자를 지정할 수 있다.
let kim = {name:'kim', first:10, second:20}
let lee = {name:'lee', first:10, second:10}
function sum(prefix){
return prefix+(this.first+this.second);
}
// sum();
let kimSum = sum.bind(kim, '-> ');
console.log('kimSum()', kimSum()); // kimSum() -> 30 출력
즉 정리하자면 call은 실행되는 함수의 this값을 원하는 객체로 바꿔서 실행할 수 있게 해주고
bind는 실행되는 함수의 this값을 원하는 객체로 고정시키는 새로운 함수를 만들어낸다.
prototype vs __proto__
함수는 자바스크립트에서는 객체다. 객체이기 때문에 property(속성)을 가질 수 있다.
예를 들어서 이러한 함수를 정의를 했다고 가정을 해보자
function Person(name,first,second) {
this.name = name;
this.first = first;
this.second =second;
}
Person 이라는 새로운 객체가 생성이 되고 근데 객체가 하나 더생긴다 Person의 prototype 객체가 생긴다.
Person 뿐만이 아니라 모든 객체들이 메소드와 속성들을 상속 받기 위한 템플릿으로써
프로토타입 객체(prototype object)를 가진다.
이 두개의 객체는 서로 연관되고 관련되어 있기 때문에 Person이라는 객체에는 내부적으로 prototype 이라는 속성이
생기고 이 속성은 Person의 prototype 객체를 가리킨다
한마디로 Person.prototype 을 입력하면 Person의 prototype 객체를 의미한다는 것이다.
Person에 prototype 객체도 자신이 Person에 소속이라는 것을 표시하기 위해 constructor라는 속성을 만들고
이 속성은 Person을 가리키게 된다.
이제 인스턴스 하나를 생성을 해주면
function Person(name,first,second) {
this.name = name;
this.first = first;
this.second =second;
}
Person.prototype.sum = function(){}
let kim = new Person('kim',10,20)
이때 kim이라는 객체에 __proto__ 가 생성이 된다. 이 속성이 누구를 가리키냐면 kim이라는
객체를 생성한 Person의 prototype을 가리키게 된다.
그러면 Person.prototype 을 입력해도 kim.__proto__ 을 입력해도 Person의 prototype을 가리키게 되는것이다.
그리고 코드를 몇줄 추가해 보자면
function Person(name,first,second) {
this.name = name;
this.first = first;
this.second =second;
}
Person.prototype.sum = function(){}
let kim = new Person('kim',10,20)
console.log(kim.name)
kim.sum()
console.log(kim.name)
kim.name 이라는 것을 콘솔에 출력을 하려고 하면 일단 kim이라는 객체에 name 속성이 있는지 찾아보고 있다면 출력
만약 없다면 kim.__proto__가 가리키는 객체에 name이 있는지 다시 찾아봄
마지막줄 처럼 코딩을 하면 kim에 sum이 없으니 __proto__ 를 통해서 kim.__proto__가 가리키는 객체에
sum이 있는지를 찾는다
'JavaScript' 카테고리의 다른 글
코플릿 문제 풀면서 알게된 것들 - (join,split) (0) | 2022.07.27 |
---|---|
노마드 코더 - 바닐라 JS로 크롬 앱 만들기(#6.0 ~ #6.2) (0) | 2022.03.18 |
노마드 코더 - 바닐라 JS로 크롬 앱 만들기(#5.0 ~ #5.3) (0) | 2022.03.17 |
노마드 코더 - 바닐라 JS로 크롬 앱 만들기(#3.0 ~ #3.5) (0) | 2022.03.11 |
노마드 코더 - 바닐라 JS로 크롬 앱 만들기(#2.11 ~ #2.16) (0) | 2022.02.22 |