딥브레인AI 풀스택 과정 코딩테스트 완료~ 만점이다!
통계치 나오는 기능까지 만들었다! 이제 가장 기본적인 기능은 끝!!!
추가하고 싶은 기능 목록!
1. .insertAfter()
?
myParentNode.insertBefore(newChild, oldChild)
는 myParentNode
가 가지고 있는 기존 자식노드 oldChild
(기준) 앞에 새로운 자식노드 newChild
를 추가하는 함수이다.
하지만 그 반대로 기준 자식노드 뒤에 추가하는 함수는 따로 정의되어 있지 않아서 보통 다음과 같은 스니펫을 사용한다.
Object.prototype.insertAfter = function (newNode) {
if (!!this.nextSibling) {
this.parentNode.insertBefore(newNode, this.nextSibling);
} else {
this.parentNode.appendChild(newNode);
}
};
2. Node
vs. Element
나의 경우에는 위의 스니펫에서 parentNode
대신 parentElement
를 사용했다. 둘의 차이점은 노드와 요소(element)의 차이일텐데, 정확히 무엇이 다른걸까?
설명에 따르면 노드 ⊃ 요소 라고 보면 된다. 노드는 docuement
, document.body
, <p>
등 DOM 요소, html 태그 등등을 포함하는 개념이다. 반면 요소는 특정 유형의 노드로 html 태그를 이용해 지정 가능한 것을 말한다. 그러면 어떻게 노드와 요소를 구분해서 호출할 수 있을까?
이 블로그의 맨 아래 파트 예시를 보면 요소는 태그 안에 들어가 있는 것을, 노드는 전체를 다 가져온다고 이해하면 될 것 같다.
this
가 함수를 불러온 요소 그 자체를 가리킨다고 착각해서;; this.previousElement
를 시도하는 등의 헛발질을 하다가... (this
는 파이썬의 self
랑 유사한 것 같음)e
를 받아와서 간단히 해결했다! 바뀐 코드는 이렇다↓// 전: 맨 마지막 블록의 시간을 가져옴
const latestTime = allBlocks[allBlocks.length-1].querySelector(".endTime").value;
// 후: 클릭 이벤트가 일어난 블록의 시간을 가져옴
const latestTime = e.target.previousSibling.previousSibling.value;
.insertAfter
프로토타입 함수를 만든 후 집계 버튼을 누르면 "insertAfter NaN:NaN"라는 예상하지 못한 게 나온다. 정작 html을 확인해보면 저런 요소도 존재하지도 않는데 왜 나오는지 아직 잘 모르겠다. 그냥 함수랑 프로토타입 함수의 차이 때문에 그런 것 같은데... 🙄 내일 검색 좀 해봐야겠다파이썬 클래스와 유사한 개념이라고 이해했다! MDN 설명에서 아래와 같은 생성자 함수(≃클래스 생성자 함수)를 선언하고 그걸 이용해 인스턴스를 만들었다.
function Person(first, last, age, gender, interests) {
// 속성과 메소드 정의
this.first = first;
this.last = last;
//...
}
var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);
그러면 person1
인스턴스에는 first
, last
,...와 같이 생성자 함수에서 정의한 속성도 들어있다. 이 때 Person()
은 person1
의 프로토타입 객체(≃상위 클래스)이다. 그리고 person1
에는 Person()
의 속성 뿐 아니라 Person()
의 프로토타입 객체인 Object
의 속성인 vaueOf
와 같은 속성도 들어있다.
파이썬에서 인스턴스에서 메소드를 호출하면 상위 클래스의 메소드를 확인하고, 없으면 상위 클래스의 상위 클래스의 메소드를 확인하는 것과 똑같은 방식이다.
출처: MDN
하지만 person1
이 Object
의 모든 속성을 상속받은 것은 아니다. 프로토타입 객체가 인스턴스에게 상속해줄 속성은 따로 지정할 수 있는데, 그런 속성을 프로토타입 속성이라고 부른다. 즉 Object.prototype.valueOf()
로 메소드가 정의되었기 때문에 Person()
과 person1
이 상속받을 수 있는 것이다.
"보통
this
가 현재 객체의 프로토타입 객체를 가리킬 것이라 오해하지만 그렇지 않죠. (프로토타입 객체는__proto__
속성으로 접근 가능한 내장 객체인 것 기억 하시나요?). 대신에prototype
속성은 상속 시키려는 멤버들이 정의된 객체를 가리킵니다."
뭔가 이해했다고 생각했는데 이 글을 보니 다시 혼란에 빠졌다... OOP는... 참 묘하다... 시간을 좀 더 들여야겠다 🤨 아직 Python OOP도 완벽히 이해를 못한 것 같다
디버그를 돌려봐도 마지막에 writeTrack(trackObj)
함수에 전달하는 객체에는 insertAfter
가 없는데... 왜 저 함수가 실행되고 나면 없던 게 생기는 건지 나 참 ㅠㅠ
지금은 도저히 알 수 없어서 그냥 마지막 <ul>
을 제거하는 식으로 작업했다. 좀 더 연습하다보면 이유를 알게 되겠지...?
저놈의 프로토타입... 해결책이 안 보여서 JS 객체지향 인강을 좀 들어보기로 🙃 (코드잇과 생활코딩 섞어서 들음)
Prototype-based Programming
자바스크립트는 다른 언어와는 다른 방법으로 객체 지향에 접근한다. 자바스크립트에서 객체 지향 프로그래밍을 논의할 때 Prototype-based Programming이라고 부른다.
this
는 생성된 객체(정확히는 인스턴스인듯?) 그 자체를 가리킨다.function createUser (name, age) {
const user = {
//name: name,
//age: age,
name,
age, // 프로퍼티와 파라미터가 동일한 경우에는 이렇게 생략해서 써도 된다(모던 JS).
helloTo(others) {
console.log(`${this.name} says hello to ${others.name}`);
},
};
return user;
}
const user1 = createUser('Soo',27);
new
를 붙이면 해당 함수는 새로운 객체를 초기화하한다. 이 함수를 생성자 함수라고 부른다.new
와 결합하여 객체를 생성한다.return
값을 지정하지 않는다.function User (name, age) {
this.name = name;
this.age = age;
this.helloTo = function (others) {
console.log(${this.name} says hello to ${others.name}`);
};
}
const user1 = new User('Soo', 27);
Prototype-based Programming
class
를 사용할 수 있다. 이전에 배운 파이썬 OOP와 유사해 보인다.class User {
constructor (name, age) { // 파이썬으로 치면 __init()__ 인 듯?
this.name = name;
this.age = age;
}
helloTo(others) {
console.log(`${this.name} says hello to ${others.name}`);
}
}
const user1 = new User('Soo', 27);
myFunc()
함수를 정의한 후에 myFunc()
을 호출해도 되지만 window.myFunc()
을 호출해도 결과는 동일하다. 이는 myFunc()
이 window
객체의 메소드라는 의미이다. 즉 우리는 function myFunc()
과 같이 함수를 정의할 때 window
라는 전역 객체의 내부에 메소드로 정의한 것이다. window
는 편의를 위해 생략된 것이다.window
)의 프로퍼티라고 하는 것이다.참고
전역 객체의 명칭은 호스트 환경에 따라 달라질 수 있다. node.js에서 전역 객체는window
가 아니라global
이다.
Prototype-based Programming
this
1) 함수에서 this
는 전역 객체window
를 의미한다.
// 출처: 생활코딩
function func(){
if(window === this){
document.write("window === this");
}
}
func(); // 실행 결과는 "window === this"
2) 메소드에서 this
는 메소드를 가지고 있는 객체를 가리킨다. 이것을 통해 1)의 결과도 이해할 수 있다. func()
은 전역 객체 window
의 메소드이기 때문이다.
var o = {
func : function(){
if(o === this){
document.write("o === this");}
}
}
Prototype-based Programming
function sum(x, y) { return x+y; } // 함수 리터럴
var sum2 = new Function('x', 'y', 'return x+y;');
sum2
와 같이 생성자 함수를 통해 함수를 정의할 수도 있고, sum
과 같이 함수를 정의할 수도 있다. sum
과 같이 정의하는 것을 '함수 리터럴'이라고 부른다. 이는 const a = { ... };
를 객체 리터럴 이라고 부르는 것과 마찬가지이다.
즉 sum
, sum2
함수는 둘 다 객체이다.
apply
window
의 메소드이지만, 다르게 호출할 경우 다른 객체의 메소드가 될 수도 있다. 그 예가 apply
이다.// 출처: 생활코딩
var o = {}
function func(){
switch(this){
case o:
document.write('o<br />');
break;
case window:
document.write('window<br />');
break;
}
}
func(); // window 의 객체이므로 this === window
func.apply(o); // o 의 객체로 호출되었으므로 this === o
(강의에서 apply
자체를 자세히 다루지 않아서 좀 더 찾아보고 있는데 뭔가... 왜 굳이 이렇게 하나 싶게 생긴 예제가 나온다; 함수를 호출할 때 인자를 그냥 주는 거랑 무슨 차이인거지?)
Prototype-based Programming 상속(하는 법)
// 출처: 생활코딩
function Person(name){
this.name = name;
}
// 사람 객체의 프로퍼티 선언
Person.prototype.name=null; // 굳이 안 해줘도 되지만 name 프로퍼티가 있음을 보여주기 위함
Person.prototype.introduce = function(){
return 'My name is '+this.name;
}
function Programmer(name){
this.name = name;
}
// 사람 객체의 프로퍼티를 프로그래머 객체에 상속시킴
Programmer.prototype = new Person(); // 작동 방식은 다음 장에 설명
Programmer.prototype.coding = function(){
return "hello world";
}
var p1 = new Programmer('Soo');
document.write(p1.introduce()+"<br />");
document.write(p1.coding()+"<br />");
Prototype-based Programming 프로토타입!
왜 필요할까?
prototype 말 그대로 그 객체의 '원형'이다. 즉 생성될 때부터 기본적으로 가지고 있어야 하는 것을 의미한다.
만약 '원형'이 필요가 없다면 굳이 힘들게 const obj = new myFunc();
과 같이 생성자 함수를 이용해 객체 obj
를 만들 필요가 없다. 그냥 const obj = {}
이렇게 객체 리터럴로 선언해도 된다.
하지만 obj
안에 어떤 프로퍼티가 처음부터 담겨있기를 원한다면, 즉 특정 '원형'을 원한다면 prototype를 이용해 그것을 줄 수 있다.
어떻게 상속하나?
prototype은 객체 안의 객체이다. myFunc.prototype
에 프로퍼티를 주어 사용할 수 있다. myFunc.prototype.name = 'Soo';
와 같이 부여하면 const obj = new myFunc();
으로 obj
객체를 만들었을 때 그 객체 안에 {name : 'Soo'}
가 들어간다. 전달하는 과정은 아래와 같다.
function Person(){}
Person.prototype.hi = 'hi';
function Programmer(){}
Programmer.prototpye = new Person();
const soo = new Programmer();
console.log(soo.hi); // 'hi'
네 번째 라인에서 new Person();
은 {hi : 'hi'}
를 Programmer.prototype
에 전달해준다. 그래서 Programmer.prototype
은 Person.prototpye
과 같은 프로퍼티를 가지는 것이다. 이런 식으로 prototype을 통해 객체 간의 프로퍼티를 전달하는 것을 prototype chain이라고 부른다.
someone
을 만들고 그 프로퍼티 {hi : 'hello world'}
를 전달하는 것도 가능하다. function Person(){}
Person.prototype.hi = 'hi';
const someone = new Person();
someone.hi = 'hello world';
function Programmer(){}
Programmer.prototpye = someone;
const soo = new Programmer();
console.log(soo.hi); // 'hello world'
왜
Programmer.prototpye = Person.prototype
이라고 하지 않나?
이렇게 해도 결과는 동일하게 나온다. 하지만Person.prototype
을 전달하면 문자 그대로Person
의 prototpye 그 자체가 전달되어 버린다. 그래서Programmer
에 프로퍼티를 추가할 때Person
에도 똑같이 추가가 된다.
이렇게 부모 객체에 영향을 주는 것을 막기 위해서Person
의 복사본을 만들어 prototype에 주는 것이다.
Prototype-based Programming Object 확장
insertAfter
가 출력되는 이유를 찾았다!!!neddle
을 찾는 함수 contain
을 프로토타입에 추가해 확장했다고 가정해보자.//코드 출처: 생활코딩
Object.prototype.contain = function(neddle) {
for(const name in this){
if(this[name] === neddle){
return true;
}
}
return false;
}
이렇게 확장 한 뒤에 임의의 객체 내부의 프로퍼티를 출력하면 우리가 추가한 메소드까지 출력된다.
const o = {'name':'Soo', 'hello':'world'}
for(const name in o){
console.log(name);
}
// 출력 결과
// name
// hello
// contain <-- 우리가 추가한 메소드까지 출력된다! 모든 객체에 contain이 상속되었기 때문에 이렇게 나온다.
따라서 넓은 범위에 영향을 주는 Object와 같은 객체를 확장하는 것은 위험하다.
hasOwnProperty
for(const name in o){
if(o.hasOwnProperty(name)) // o 안의 프로퍼티 name이 o 만의 것인지 체크한다. 상속받은 contain은 false이기 때문에 출력되지 않는다.
console.log(name);
}
와아악 해결했다 이제 안나온다 🤗
깃 브랜치 잘못 합쳐서 날아갔다... ^^ 이젠 깃 공부다
Python
여러 값을 반환하는 함수를 apply
(혹은 map
)해서 한 번에 여러 열을 채우려고 했다. 예를 들어 'a'열의 값을 map
해서 df의 'c', 'd'열을 한 번에 채우는 상황이다.
def my_function(x):
a = x + 1
b = x + 2
return a, b
df = pd.DataFrame(np.array([[1, 2], [11, 12]]), columns=['a', 'b'])
하지만 df['a'].map(lambda x: my_function(x))
의 반환값은 [2, 3], [12, 13]
의 object이기 때문에 이걸 단번에 'c', 'd'열로 넘길 수는 없다. 이걸 object->array->pd로 바꾸어서 넣어야 하나 고민하고 있는데 이 글을 보고 쉽게 해결했다.
def my function(s):
s['c'] = s['a'] + 1
s['d'] = s['a'] + 2
return s
df = df.apply(my_function, axis = 1)
함수에 단일 값 x
대신 pd.Series를 넘겨주고 함수가 pd.DataFrame을 반환하는 방식이다. 이번에는 apply
로 끝냈지만 map
으로 하는 방법도 있지 않을까 궁금하다.
아!!!! 드디어!!!! Object 프로토타입 함수 문제 해결했다!!!!! 오래걸렸다 정말 ㅠㅠㅠ 이제 꼭 구현해야 하는 'Start!' 버튼 hidden이랑 삭제 만들어보자! 그리고 빈 칸은 읽어오지 않는 것까지 구현해야겠다
시작 hidden & 삭제 버튼 & 모든 block 삭제되었을 때 시작 다시 나타나는 것까지 구현 완료!! 이제 더 이상 근무시간은 기록하지 않아도 된다고 해서... 살짝 당황스럽지만... 혼자서라도 쓰면 되니까~ 😄 (아까운데 어디 뿌릴 곳 없을까)
1년 동안 CS(알고리즘, 자료구조)기초, SQL, HTML&CSS, JS, OOP 기초를 공부했다. 데이터 분석 직무로 가고 싶다→근데 분석만 하면 안 되고 데이터 추출도 알아야 한다고?→데이터 추출하려면 웹을 알아야 한다고? 하는 사고를 거쳐 어쩌다보니 JS를 제일 많이 공부했다. 원래 목표는 백엔드였는데 JS에 빠져들어서 미니 웹 어플리케이션을 만드는 것으로 한 해를 마무리했다 🤗
JS를 예상 외로 오래 공부했지만 실제로 동작하는 무언가를 만드는 것은 정말 재미있는 경험이었다! 내년에는 개발과 분석 사이의 줄타기를 좀 더 잘 하며 공부해야겠다.