이번 시간에는 Javascript에 꽃이라고 불릴 정도로 핵식점인 기능을 담당하고 있는 Functions에 대해 알아보자.
여러분들이 매일 사용하고 있는 각각의 프로그램마다 제공하능 고유한 기능들이 있다.
이렇게 프로그램 안에서도 각각 저마다의 기능을 수행하는 함수들이 있는데
이렇게 절차적인 언어(procedural language)같은 경우는 함수가 프로그램에서 굉장히 중요한 기능을 담당한다.
그런데 Javascript에는 class가 추가되어 object oriented programing(OOP)이 아닌가?
이 추가된 class도 Java 언어처럼 Pure한 Object-oreiented가 아니고 Prototype을 베이스로한 각자의
Object-oreiented이다. 그래서 이 function이 굉장히 중요한 기능을 담당하고 있기 때문에
때로는 우리가 sub-program이라고도 부른다. 프로그램안에서 또 각각의 작은 단위의 기능들을 수행하는 것이 바로
function이다.
어떤 언어를 배우든 간 이 function은 대체적으로 Input파라미터를 받아서 이것들을 잘 처리한 다음 output으로
return을 하는 것이 바로 function이다. 그래서 우리는 언어 자체의 존재하는 function 자체를 쓸때.
또는 API(Application Programing Interface)를 쓸때 function(함수)의 이름을 보고 이 함수가 어떤 일을 할지를
생각할 수 있고 또 전달해야 할 파라미터 값들이 무엇인지 어떤 값이 return되기를 기대할 수 있는지
이런 Interface를 보고서 이런 일들을 수행하구나 라고 이해할 수 있다.
그래서 함수에는 Input(들어오고)과 Output(나가고) 이 중요하고 Function(함수)의 이름을 정하는 것이 중요하다.
// Functions
// - fundamental building block in ther program;
// - subprogram can be used multiple times
// - performs a task or calculates a value
앞에서 Functions 은 프로그램을 구성하는 굉장히 기본적인 building Block이라고 설명했는데, subprogram이라고도
불리면서 여러번 재사용이가 가능한 장점이 있다.
그리고 대체적으로 function은 한 가지의 테스크나 아니면 어떤 값을 계산하기 위해 쓰이고 있다.
// 1.Function declaration
// function name (param1, param2) { body... return; }
// one function === one thing
// naming: doSomething, command, verb
// e.g. createCardAndPoint -> createCard, createPoint
// function is object in JS
Javscript에서 function을 정의하는 방법은 function이라는 키워드를 입력하고 함수의 name을 지정한 다음에
params(파라미터) 를 나열한다음 body함수안에 기본적인 비즈니스 로직을 작성한 다음 return을 해주면 된다.
여기서 정말 중요한 포인트는 하나의 function(함수)는 한가지의 일만 하도록 만들어야 한다.
그리고 함수의 이름을 작성할때 지난시간에 변수의 이름을 정할때는 명사로 지정했는데
하지만 함수는 무언가를 동작하는 것이라 doSomething, command, verb 처럼 동사의 형태로 이름을 지정해야 한다.
그리고 함수의 이름을 정하기가 어렵다면, 혹시 내가 함수안에 너무 많은 것을 하는지 생각할 필요가 있다.
그리고 Javascript의 함수는 object다. 그렇기 떄문에 object로 간주되어 변수에도 할당할 수 있고 파라미터에 전달되고
함수를 return할 수 있는 것이다.
function printHello() {
console.log('Hello');
}
printHello();
그래서 이렇게 함수가 정의된 걸 보면 function이라는 키워드를 쓰고 함수의 이름은 printHello고 파라미터는 () 하나도 받지않고 console.log를 출력하고 있다.
이렇게 함수를 정의하면 printHello(); 로 함수를 호출할 수 있다.
그런데 사실 이 함수는 조금 쓸모가 없다. 계속 hello밖에 출력이 안됨
function log(message) {
console.log(message);
}
log('sdsd');
그래서 우리가 조금 더 유용한 함수를 만들려면 이렇게 파라미터로 message를 전달하며 전달된 메세지를 화면에
출력하도록 만드는 것이 좋다. 그래서 log라는 함수를 호출해 우리가 원하는 메세지를 출력할 수 있다.
그리고 Javscript에서 Type이 없다고 누누이 설명했는데 그렇기 때문에 아쉽게도 이 함수 자체의 인터페이스만
보았을때 이 message가 string을 전달해야할지 숫자도 전달해야되는지 명확하지 않다.
function log(message) {
console.log(message);
}
log('sdsd');
log(12123)
그래서 사용하는 사람이 숫자를 전달할수 있다.
다행이 숫자가 문자열로 변환되 log가 출력되 상관없지만 다른 함수에서 type이 중요할 경우 Javascript에서는
조금 난해할 수 있다.
지금 Javascript로만 코딩할것은 아니고 나중에 현업을 가면 Typescript도 쓰고 다양한 언어도 공부할텐데 조금 맛보기로
알아보자.
그래서 사실 규묘있는 프로젝트를 하거나 현업에서 다양한 개발자들과 일하거나 나중에 라이브러리형태로 api를
이용해야할때 Typescript를 쓰는게 조금 더 명확하고 개발을 더 쉽게 만들어 준다.
이유는
이렇게 함수의 이름만 봐도 정확하게 무엇을 하는 아이인지 이렇게 함수의 이름과 전달되어야 하는 파라미터와
리턴되는 타입과 어떤값이 return되는지 확실하게 확인하고 쓸 수 있기 때문이다.
반대로 Javscript에서는 Interface가 정확하게 명시되지 않아 프로그램밍을 만들때 많은 문제를 발생한다.
// 2. Parameters
// premitive parameters: passed by value
// object parameters: passed by reference
function changeName(obj) {
obj.name = 'coder';
}
const ellie = {name: 'ellie'};
changeName(ellie);
console.log(ellie);
두번째로 function에 전달되는 Parametes들은 Primitive같은 타입의 경우
메모리에 value가 그대로 저장되있어 value가 전달되고
Object경우 우리가 메모리에 reference(래퍼런스)가 저장 되어 진다고 말했는데
그래서 refernece가 전달되어 진다.
조금 더 자세히 이해하려면 위에 예제를 보면 changeName이라는 function은 전달된 obj의 이름을 'coder'로 무조건 변경하는 함수이다. 그래서 ellie라는 const를 정의한 다음 ellie라는 obj를 만들어 할당하면
메모리에는 object 반들어지 ref가 메모리에 들어가고 이 refrence는 바로 이 ellie라는 obj를 메모리 어딘가에 가리키고 있다. 그래서 changeName(ellie)를 전달하게 되면 이 전달된 {name: ellie} 가 가리키고 있는 곳에 이름을 coder로
변경하게 되는 것이다. console.log로 출력된 결과를 보면 ellie에서 coder로 변경된 것을 볼수 있다.
그래서 object는 래퍼런스로 전달되기 때문에 함수안에서 oject에 값을 변경하게 되면 그 변경된 사항이 그대로
메모리에 적용이 되기 때문에 나중에 추후에 변경된 사항을 확인 가능 하다.
// 3. Default parameters (added in ES6)
function showMessage(message, from) {
console.log(`${message} by ${from}`);
}
showMessage('Hi!');
세번째로는 Default paramters에 대해 알아볼건데 이거는 es6버전 부터 추가되어진 친구다.
여기 showMessage를 보면 message(메세지), from(에서) 두가지 파라미터들을 받아온다.
그래서 이 message가 누구로 부터 온지 출력하는데, 여기서 보면 showMessage를 호출할때
showMessage함수로 하나만 출력했는데 그래서 여기에 message는 출력이 되지만 from이 정의되지 않아 undefined로
나온다. 그래서 예전에는 이렇게 파라미터의 값이 전달되지 않을 경우를 대비해서
// 3. Default parameters (added in ES6)
function showMessage(message, from) {
// 파라미터의값이 전달 되지 않을 경우를 대비
if (from === undefined) {
from = 'unknown';
}
console.log(`${message} by ${from}`);
}
showMessage('Hi!');
from이 undefined일때 우리가 원하는 가장 기본정은 unknown이라는 string을 추가하자라고 해서 unkown을 추가했다.
하지만 이렇게 귀찮게 작성할 필요 없이
// 3. Default parameters (added in ES6)
function showMessage(message, from = 'unknown') {
// // 파라미터의값이 전달 되지 않을 경우를 대비
// if (from === undefined) {
// from = 'unknown';
// }
console.log(`${message} by ${from}`);
}
showMessage('Hi!');
이렇게 파라미터 옆에다가 원하는 default값을 지정하면 사용자가 파라미터를 전달하지 않을때 값이 대체되어 사용된다.
// 4. Rest paramets (added in ES6)
function printAll(...args) {
for (let i = 0; i < args.length; i++) {
console.log(args[i]);
}
}
printAll('dream', 'coding', 'ellie');
네번째로는 Rest Parametes인데 이것도 es6에 추가되었다. 이렇게 ...args라고 작성하면 rest Parameters라고 불리는데
이것은 바로 배열 형태로 전달하게 된다. 그래서 printAll이라는 함수를 호출할때 인자를 총 3개를 전달했는데
그래서 지금 args는 3개의 값의 담겨저 있는 배열이다.
이런식으로 배열 형태로 전달이 되는 것이다. 그래서 우리가 for & loop를 이용해 args의 개수망큼 돌다가
처음 하나를 출력 그리고 둘 세번째 출력을 하는 것이다.
console을 보면 순서대로 출력이 되었다. for & loop로 배열을 출력할때는
// 간단하게 이런식으로 for & of를 이용해서 간단하게 출력할 수도 있다.
for (const arg of args) {
console.log(arg)
}
arg에 있는 모든 값들이 차례대로 하나씩 지정되 console을 출력하는 방식
// 더 간단하게 하는 방법
// 배열의 forEach를 사용해 출력해도 된다.
args.forEach((arg) => console.log(arg));
또는 forEach를 사용해 출력해도 된다. 다음으로 넘어가기전 function은 Object의 일종이라고 말했는데
그 말이 무엇이냐면 우리가 아까 printAll을 선언했는데
이렇게 printAll에 속성값들을 확인 할 수 있다.
// 5. Local Scope
let globalMessage = 'global'; // global variable
function printMessage() {
let message = 'hello';
console.log(message); // local variable
console.log(globalMessage);
}
printMessage();
5번쨰로는 우리가 Scope에 대해서 한번 더 정리해보는 시간을 가져보자. 지난 data시간에 block scope 과 global scope에 대해 설명했는데 나중에 Javascript 언어의 심화 공부떄 closer나 rexcoinbinment에 대해 들어보는 시간을 가져볼 것이다. 그런 아이들은 이런 원칙을 상세하게 설명하는 아이다.
사실을 딱 하나의 개념에서 파생된 아이들이다. 그딱 하나의 원칙만 이해하면 나머지는 쉽다 위 한문장만 이해하면 된다.
// 5. Local Scope
let globalMessage = 'global'; // global variable
function printMessage() {
// 지역 변수 = 지역적이라 안에서만 접근 가능
let message = 'hello';
// 밖에서는 접근이 불가하다.
console.log(message); // local variable
// 하지만 안에서는 globalMessage를 볼 수 있다.
console.log(globalMessage);
}
printMessage();
// console.log(message); // 불가
이렇게 {} 블럭안에 함수를 선언하게 되면 이것은 지역변수 이다. 지역 변수는 지역적이라 안에서만 접근이 가능하고
만약 밖에서 메세지를 선언하면 에러가 발생한다. 하지만 안에서는 globalMeesage를 볼수 있고 출력이 가능하다.
이게 바로 Scope Message이다. 그리고 이 원칙은 어느 곳에서나 적용된다.
let globalMessage = 'global'; // global variable
function printMessage() {
// 지역 변수 = 지역적이라 안에서만 접근 가능
let message = 'hello';
// 밖에서는 접근이 불가하다.
console.log(message); // local variable
// 하지만 안에서는 globalMessage를 볼 수 있다.
console.log(globalMessage);
// 원직은 어는 곳에서나 적용이가능하다.
// 이렇게 함수안에 함수를 선언할 수 있다.
function printAnother() {
console.log(message);
let childMessage = 'hello'
}
// 자식안에 정의된 childMessage를 바로 부모 상위 위에서 보려고 한다면
// 에러가 발생한다.
console.log(childMessage);
}
printMessage();
이렇게 printMessage안에 printAnother이라는 함수가 두개 들어있는데 똑같은 원칙에 입각해 자식은 부모에게
정의된 메세지를 확인 할 수 있는데 자식안에 정의된 childMessage를 바로 부모 상위 위에서 보려고 한다면
에러가 발생한다.
그래서 한가지만 기억하면 Javscript에 Scope란 유리창에 틴트가 처리된 것과 똑같다.
안에서는 밖이 보이지만 밖에서는 안을 볼 수없고 접근이 불가하다.
let globalMessage = 'global'; // global variable
function printMessage() {
// 지역 변수 = 지역적이라 안에서만 접근 가능
let message = 'hello';
// 밖에서는 접근이 불가하다.
console.log(message); // local variable
// 하지만 안에서는 globalMessage를 볼 수 있다.
console.log(globalMessage);
// 원직은 어는 곳에서나 적용이가능하다.
// 이렇게 함수안에 함수를 선언할 수 있다.
function printAnother() {
console.log(message);
let childMessage = 'hello'
}
// 자식안에 정의된 childMessage를 바로 부모 상위 위에서 보려고 한다면
// 에러가 발생한다.
console.log(childMessage);
}
printMessage();
그리고 이렇게 중첩된 함수에서 자식의 함수가 부모 함수에 정의된 변수들에 접근이 가능한 것들이 바로 Closer이다.
// 6. Return a value
function sum(a,b) {
return a + b;
}
const result = sum(1,2); // 3
console.log(`sum: ${sum(1,2)}`);
다음은 함수에서는 paramters로 값들을 전달받아 계산된 값을 return 할 수 있다. sum이라는 함수가 만들어저서
1과 2를 더해 3이 나오는것을 볼수있는데
// 5. Local Scope
let globalMessage = 'global'; // global variable
function printMessage() {
// 지역 변수 = 지역적이라 안에서만 접근 가능
let message = 'hello';
// 밖에서는 접근이 불가하다.
console.log(message); // local variable
// 하지만 안에서는 globalMessage를 볼 수 있다.
console.log(globalMessage);
// 원직은 어는 곳에서나 적용이가능하다.
// 이렇게 함수안에 함수를 선언할 수 있다.
function printAnother() {
console.log(message);
let childMessage = 'hello'
}
// 자식안에 정의된 childMessage를 바로 부모 상위 위에서 보려고 한다면
// 에러가 발생한다.
// console.log(childMessage); // error
// return 타입이 없는 경우
return undefined;
}
이렇게 return 타입이 없는 함수들은 return undefined이 들어있는 거랑 똑같다. 그래서 모든 함수에는
return undefined이거나
// 6. Return a value
function sum(a,b) {
return a + b;
}
const result = sum(1,2); // 3
console.log(`sum: ${sum(1,2)}`);
이렇게 우리가 값을 return 할 수 있다.
// 7. Early Return, early exit
// bad
function upgradeUser(user) {
if (user.point > 10) {
// long upgrade logic...
}
}
그리고 현업에서 사용하는 팁을 알려주자면 Early Return을 하라, Early exit 하라는 코드지적질을 받을 수 있는데
이렇게 upgradeUser라는 함수안에 user에 포인터가 10 이상일 경우 업그레이드하는 로직이 있다면
이렇게 > 10 일때 작동할다고 작성하면 블록 안에 logic을 많이 작성하면 가독성이 떨어진다.
그래서 이런경우 if 와 else를 번갈아 적용하는 것 보다.
// good
function upgradeUser(user) {
if (user.point <= 10) {
return;
}
// long upgrade logic...
}
이런 식으로 조건이 맞지 않을때 빨리 return 해서 빨리 함수를 종료하고 그 다음에 와서 필요한 logic들을 실행하는게
더 좋다. 나중에 코드 작성시 조건이 맞지 않는 경우 조건이 맞지 않는 경우 undefined한 경우 값이 -1인 경우
빨리 return하고 필요한 logic은 뒤에서 작성하는게 좋다.
// First-class function
// functions are treated like any other variable
// can be assigned as a value to variable
// can be passed as an argument to other functions.
// can be returned by another function
// 1. Function expression
// a function declaration can be called earlier then it is defined. (hoisted)
// a function expression is created when the execution reaches it.
const print = function () {
console.log('print');
};
print();
const printAgain = print;
printAgain();
const sumAgain = sum;
console.log(sumAgain(1,3));
자 이렇게 우리가 function을 어떻게 선언 할 수 있는지 알아보았는데 이제는 function expression에 대해 알아보자
우리가 First-class function이라는 것에 대해 지난 시간에 다뤄보았는데 그 말이 function은 다른 변수와 마찬가지로
변수에 할당되고 function에 파라미터에 접근되고 return 값으로도 return이 된다는 말인데 그것이 가능한게
function expression이다.
const print = function () {
console.log('print'); // anonymous function == 함수의 이름이 없는것
};
print();
const printAgain = print;
printAgain();
const sumAgain = sum;
console.log(sumAgain(1,3));
이곳을 보면 함수를 선언함과 동시에 바로 print 라는 변수에 할당하는 것을 볼 수 있는데 이렇게 function에
아무이름이 없고 키워드를 이용해서 파라미터와 이렇게 블럭을 이용한 것을 볼 수 있는데
이렇게 함수에 이름이 없는 것을 annonymous function이라고도 불린다. 이렇게 이름없이 그냥 필요한
부분만 작성해 변수에 할당할 수 있고,
아니면 우리가 원하면 함수의 이름을 작성할 수 있다.
const print = function() { // anonymous function == 함수의 이름이 없는것
console.log('print');
};
// print라는 변수에 함수를 호출하듯이 호출하면 바로 print호출
print();
// 그리고 다른 변수에 할당하면 결국 printAgaing은 print을 가리키디
const printAgain = print;
// 다시 함수를 호출하면 print
printAgain();
// sum이라는 함수를 만들었는데 이것도 sumAgain에 호출하면 된다.
const sumAgain = sum;
console.log(sumAgain(1,3));
이렇게 함수를print에 할당하면 우리가 print변수에 함수를 호출하듯이 print() 호출하게 되면
이렇게 호출된다. 이렇게 function declaration 과 function expression의 차이점은
const print = function() { // anonymous function == 함수의 이름이 없는것
console.log('print');
};
// print라는 변수에 함수를 호출하듯이 호출하면 바로 print호출
print();
// 그리고 다른 변수에 할당하면 결국 printAgaing은 print을 가리키디
const printAgain = print;
// 다시 함수를 호출하면 print
printAgain();
// sum이라는 함수를 만들었는데 이것도 sumAgain에 호출하면 된다.
const sumAgain = sum;
console.log(sumAgain(1,3));
function expression은 할당된 다음부터 호출이 가능 한 반면 function declartion이 된다.
우리가 var 호이스팅에 알아봤듯이 function decleration도 호이스팅이 된다.
그 말은 함수가 선언되기 이전에 호출할 수 있다는 뜻으로
// 정의하기도 전에 호출 가능
sum(2,3);
// 6. Return a value
function sum(a,b) {
return a + b;
}
이렇게 함수를 정의하기도 전에 호출이 가능하다는 뜻. 이것은 자바스크립트 엔진이 선언된 것을 제일 위로 올려주기
때문이다. 그래서 이런 차이점도 있고 다른 차이점도 있다.
// 2.Callback function using function expression
function randomQuiz(answer, printYes, printNo) {
if (answer === 'love you') {
printYes();
} else {
printNo();
}
}
우리가 이제는 callback function 선언에 대해 알아보자. 이 randomQuiz라는 function을 보면 정답과 정답이 맞을때
호출할 함수와 틀릴때 호출할 함수 둘을 전달하는데 이렇게 함수를 전달해 상황에 맞으면 전달된 함수를 부르라고
전달하는 것을 callback function이라 한다. 즉 printYes & No의 callback functio 수가 파라미터로 전달되
이 함수의 구현 사항을 보면 정답이 'love you'일때 정답이면 printYes() 콜백 함수를 아니면 printNo() 콜백함수를 호출
하게 된다. 이 함수를 호출하려면 정답과 print Yes & No expression 두가지를 전달해야한다.
const printYes = function() {
console.log('yes!');
};
const printNo = function print() {
console.log('no!');
};
randomQuiz('wrong', printYes, printNo);
randomQuiz('love you', printYes, printNo);
여기를 보면 printYes라는 변수에 yes를 출력하는 함수를 할당하고 No에도 동일하게 할당
그래서 randomQuiz를 호출할때 anwser과 printYes & printNo 콜백함수들을 각각 전달하게 된것
그래서 정답이면 yes 아니면 no를 호출
// annoymous fnction = 이름이 없는
const printYes = function() {
console.log('yes!');
};
여기 보면 printYes에는 이름이 없는 annoymous function이 쓰이고
// named function
// better debugging in debugger's stack traces
// recursions
const printNo = function print() {
console.log('no!');
};
printNo에는 이렇게 함수 이름이 print라고 지정되어있다 이것을 named function이라고 하는데 이렇게
expression에서 이름을 쓰는 경우는 우리가 디버깅을 할때 디버깅에 stack에 함수의 이름이 나오기 위해서 쓰고
이것은 나중에 디버깅시 알아보고
또는 함수안에서 자신 스스로 또다른 함수를 호출할때 쓰는데
// recursions
const printNo = function print() {
console.log('no!');
// 또는 함수 안에서 자신 스스로 함수를 호출할때 사용한다.
print();
};
이렇게 개수가 지금 늘어나는 것을 볼수 있는데 이렇게 함수 안에서 함수 자신 스스로를 부르는 것을
recursions라고 한다. 이렇게 여러개로 실행하면 프로그램이이 죽으니 정말 필요할때 필요수만 계산하던지
반복되는 평균 값을 계산한다 던지 쓰임새가 다양한데
이 부분도 나중에 따로 정리해서 공부해보자
stack이 꽉찬 상태로 에러가 발생하는데 call stack에 대해서도 다음에 다뤄보겠다. 오늘은 입문편에 맞게
프로젝트를 할때 무리없이 할정도로 알아보았다.
// Arrow functions
// always annoymous
const simplePrint = function () {
console.log('simplePrint!');
}
우리가 expression을 알아보니 Arrow Function을 잊으면 안된다.
이 Arrow Functions은 함수를 간결하게 만들어주는 좋은 아이인데, Arrow Function은 항상 이름이없는 annoymous functions 이다.
이렇게 expression을 이용하면 function도 사용해야 하고 block도 써야하는 번거로움이 있는데
const simplePrint = () => console.log('simplePrint!!');
이렇게 간단하게 함수를 만들 수 있다.
// 이것도 간결하게
const add = (a, b) => a + b;
이렇게 간결하게 쓸수 있고 나중에 함수형 프로그래밍에 배열이나 리스트를 볼때 이것이 더욱더 좋은 효과를 볼 수 잇다.
// 함수안에서 조금더 다양한 일들을 해야할때
const simpleMultiply = (a, b) => {
// do something more
return a + b;
}
함수안에서 조금 더 다양한 일들을 해야해 블럭이 필요하면 위처럼 블럭 처리가 가능 대신 블럭을 쓰면 return을
써서 값을 return 해줘야 한다.
마지막 하나만 알고 끝내자
// IIFE : Immediately Invoked Function Expression
function hello() {
console.log('IIFE');
}
바로 IIFE이다. 바로 함수를 이렇게 선언하게 되면 나중에 따로 hello()라고 호출하는데 하지만 선언 함과 동시에 호출 하려면
// IIFE : Immediately Invoked Function Expression
(function hello() {
console.log('IIFE');
})();
하지만 함수를 선언함과 동시에 호출하려면 위와 같이 함수의 선언을 ()로 묶은 다음에 함수를 호출하듯이 해주면
바로 함수 호출 가능 요즘에 쓰이지 않지만 JS에서 함수를 바로 호출할때 사용할 수 있다.
'프론트 엔드 > Javascript' 카테고리의 다른 글
[Javascript] 모듈 export, import (0) | 2022.01.16 |
---|---|
[javascript] 드림코딩 자바스크립트 3.코딩의 기본 operator,if,for loop 코드리뷰 팁 (0) | 2021.12.23 |
[javascript] 드림코딩 자바스크립트 2.데이터 타입, data types, let vs var, hoisting (0) | 2021.12.23 |