지난주에 이어 프로그래밍 방식에 대해 계속 학습해오고 있다. 함수형, 선언형, 명령형, 객체지향형 .. 종류는 많지만, 특정 방식이 더 우월하기 보다는 상황에 따라 적절한 방식이 있기 마련이다. 지난주엔 함수형 프로그래밍 방식하나로 골머리를 앓았지만 그래도 공부해보면서 상황에 따라 무엇이 더 적절한지에 대한 감각은 좀더 생긴것 같다. 대학에서 프로그래밍을 배우기 시작한 이후로 그냥 "이렇게 생긴게 있다"에서 무엇이 더 적절한지에 대해 고민하는 수준에 도달한 것 같다. 한층 더 높은 고민을 할 기회를 제공해준 '프로그래머스' 분들께 감사를...
선언형 프로그래밍은 '어떤 방법으로' 보다는 '무엇'을 나타내야 하는지를 묘사
'어떤 방법으로'는 명령형 프로그래밍에 가까운 방식이고, 선언적 프로그래밍은 '무엇'을 나타내야 할지를 묘사한다. 둘이 무슨 차이냐 싶지만 간단한 코드를 통해서도 둘의 차이를 알수 있다.
//명령형
function double(arr){
let results=[];
for(let i=0;i<arr.length;i++){
if(typeof arr[i] === 'number'){
results.push(arr[i]*2)
}
}
return results;
}
Selector('body').innterHTML=double([1,2,3,'a']);
//선언형
function double(arr){
return arr
.filter(param => typeof param === 'number')
.map(number=>number *2);
}
두 함수는 같은 일을 처리하는 함수이다. 명령형이 결과값을 만들기 위해 여러개의 방법(for
, if
, *2
)을 여러 방법을 조합하는 방식이라 한다면, 선언형은 순차적으로 표현할 대상을 순차적으로 표현(filter
, map
)하는 방법이다.
js
는 배열을 활용하는데 있어 선언형 프로그래밍 방식을 사용하길 권장한다. 선언형 프로그래밍 방식을 통해 가독성
, 간결성
, 확장성
이라는 장점을 얻을 수 있기 때문이다.
함수를 추상화하는 것은 레고
에 비유할 수 있다. 특정 상황에만 맞는 것이 아닌 다양한 경우에 대처할 수 있고, 특정 부분이 다르다면 해당 부분만 갈아낄 수 있는 레고
와 같다고 볼 수 있다.
const button1=document.createElement('button');
button1.textContext='Button1';
button1.addEventListener('click',()=>{
if($button1.style.textDecoration==='text-through'){
$button1.style.textDecoration='none';
}else{
$button1.style.textDecoration='text-through';
}
}
const button2=document.createElement('button');
button2.textContext='Button2';
button2.addEventListener('click',()=>{
if($button2.style.textDecoration==='text-through'){
$button2.style.textDecoration='none';
}else{
$button2.style.textDecoration='text-through';
}
}
const button3=document.createElement('button');
button3.textContext='Button3';
button3.addEventListener('click',()=>{
if($button3.style.textDecoration==='text-through'){
$button3.style.textDecoration='none';
}else{
$button3.style.textDecoration='text-through';
}
}
다음 코드는 버튼 3개를 만들고, 각 버튼별로 '취소선/없음'이 토글이 되도록 하는 코드이다. 각 버튼별로 하는일이 같은데 각 버튼별로 같은 기능을 수행하기 위해 같은 구조의 코드를 반복한다. 불필요한 반복, 가독성 저하 라는 단점들을 가지고 있다. 이러한 특징은 함수의 추상화
를 통해 해결 가능하다.
function ToggleButton({$target,text}){
const $button=document.createElement('button');
$target.appendChild($button);
$button.addEventListener('click',()=>{
if($button.style.textDecoration==='text-through'){
$button.style.textDecoration='none';
}else{
$button.style.textDecoration='text-through';
}
});
this.render=()=>{
$button.textContent=text;
}
this.render();
}
const $app=document.querySelector('#app');
new ToggleButton({$target:$app, text:'button1');
new ToggleButton({$target:$app, text:'button2');
new ToggleButton({$target:$app, text:'button3');
ToggleButton()
이라는 함수로 반복되었던 일들을 추상화하여 달라지는 부분들만 인자로 넣어지면 완전히 같은 기능을 하는 코드가 된다. 가독성은 높이면서, 불필요한 반복과 실수를 줄일수 있게 된 것이다.
ToggleButton()
에 들어가는 인자를 추가하여 단순히 토글만 되는 함수가 아닌 다른 기능을 하는 함수를 만드는 것도 가능하다.
function MakeButton({$target,text,onClick}){
const $button=document.createElement('button');
$target.appendChild($button);
$button.addEventListener('click',onClick);
this.render=()=>{
$button.textContent=text;
}
this.render();
}
const $app=document.querySelector('#app');
const toggle=()=>{
if($button.style.textDecoration==='text-through'){
$button.style.textDecoration='none';
}else{
$button.style.textDecoration='text-through';
}
}
const timer=()=>{
setTimeout(() => {
alert("timer is ringed");
}, 3000);
}
new MakeButton({$target:$app, text:'button1', onClick:toggle);
new MakeButton({$target:$app, text:'button2', onClick:timer);
new MakeButton({$target:$app, text:'button3', onClick:toggle);
기존의 ToggleButton()
에서 toggle
기능을 구현한 callback
함수를 onClick
로 추상화하여 인자로 받음으로써, toggle
, timer
기능을 할 수 있는 버튼을 만들 수 있는 함수 MakeButton()
으로 추상화 할 수 있다.
이렇게 함수의 세부적인 부분들을 추상화 함으로써 코드를 효율적으로 관리할 수 있다.
이 내용은 오늘 수업때 배운 내용은 아니지만 수업 내용을 따라하면서 생긴 문제를 해결하면서 알게 된 것이다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./mkToggle.js"></script>
<div id="app"></div>
</body>
</html>
mkToggle
에는 #app
안에 추가할 요소들에 대한 정보들이 들어 있다. 그런데 위와 같이 script
가 #app
위에 선언될 경우 해당 코드는 에러가 발생한다. 정확히는 mkToggle
에서 구현한 요소들이 웹 페이지에 랜더링이 되지 않는다.
그 이유는 랜더링의 순서 때문이다. 일반적으로 브라우저는 html
를 파싱하여 dom tree
를 만들어 랜더링한 뒤, css
를 파싱하여 cssom
을 만든 다음, 마지막으로 script
파일을 실행한다. 그런데 script
가 #app
보다 위에 있는 경우, 스크립트가 실행될 때 #app
보다 위에 있기 때문에 #app를 참조할 수 없다. 참조가 불가능하기 때문에 script
입장에서는 #app
의 위치를 파악할 수 없고, 그로 인해 랜더링을 할 수 없다.
이러한 상황에서 방법은 2가지가 있다. 하나는 script
를 #app
보다 밑에 선언하거나, script
안에 defer
를 선언하는 것이다. defer
는 보류하다라는 뜻이다. 즉, script
의 실행을 보류했다가 html
랜더링이 끝난뒤에 실행하겠다는 의미이다. dom tree
가 만들어진 뒤에 실행하기 때문에 script
는 #app
의 위치를 파악할 수 있고, 랜더링이 정상적으로 이루어 진다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./mkToggle.js" defer></script>
<div id="app"></div>
</body>
</html>
지난 한주동안 함수형 프로그래밍을 배우면서 힘들다고 노래를 불렀었다. 근데 확실히 한번 고비를 겪고 나니 생각보다 함수형 프로그래밍 방식이 유용하게 쓰인다. 각 기능을 추상화 함으로써 가독성과 효율성을 모두 챙기는 일석이조의 효과를 톡톡히 누릴 수 있게 되었다. 지난 한주간의 고생이 헛되지 않았던 것이었다.
오늘은 vanilaJS 첫날이라 그런지 상대적으로 덜 부담스러웠다. 그러나 아직 배울 내용이 많고, 높은 난이도의 내용이 즐비할 것이다. 그럼에도 지난주의 고생이 좋은 밑거름이 될 것 같아, 좀더 산듯한 출발이 가능할 것 같다.