JS 함수(2024-11-06)

짝은별·2024년 11월 6일

JS

목록 보기
6/23

CSS 스타일 지정

JS로 스타일을 불러오는 방법엔 두 가지가 존재한다

${tagName}.style.${property}

이를 활용하면 JS만으로 특정 태그나 class에 CSS 속성을 지정할 수 있다

우선 콘솔창으로 확인해보자

$0브라우저에서 가리키는 태그이다 그에 대한 'backgroundColor''red'로 줬다
그리고 $0.style.backgroundColor라고 하니 'red'를 반환하는 모습이다

이를 이용해 CSS를 설정하는 방법은 다음과 같다

${tagName}.style.${property} = '${value}';

그러면 실험으로 위에서 선택한 태그에 backgroundColorblue로 변환시켜보겠다

처럼 변하는 모습을 확인할 수 있다

하지만 이러한 방식을 사용하면 요소 값을 불러오는 것부적절하다
이유는 오직 인라인 방식으로 설정된 값인식하기 때문이다.

backgroundColorred로 설정했으니 나오지만 fontSize같은 경우엔 나오지 않는다

getComputedStyle(${tagName}).${property}

위와 같은 문제를 해결하고자 이런 함수를 사용한다
작동하는 방식은 똑같지만 값을 불러오는 점에선 차이가 있다

이제 inline으로 설정하지 않더라도 정상적으로 값을 불러올 수 있다

일급 객체와 일급 함수

일급 객체

일급 객체란 다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체를 가리킨다

함수매개변수로 전달,수정,변수에 대입과 같은 연산을 지원할때 일급 객체라 한다

일급 함수

일급 함수함수일급 객체로 취급하는 것을 의미한다

JS의 경우 모든 것을 객체로 볼 수 있다->함수 역시 객체이다->따라서 일급 객체라고도 볼 수 있다

함수일급 객체 혹은 일급 함수라 부를 수 있다

함수

함수 표현식

function sayHi() {
  console.log( "Hello" );
}

let func = sayHi;

func(); // Hello
sayHi(); // Hello
//함수를 선언하고 복사하는 것도 가능하다

다음과 같이 작성된 함수를 함수 표현식이라고 부른다
이때 함수객체로 활동이 가능하다고 했기에 복사도 역시 가능하다
그리고 함수는 func()으로 사용하면 실행을 그렇지 않으면 실행이 되지 않는다
따라서 let func = sayHisayHi라는 함수func에 복사하겠다는 의미가 된다
만약 let func = sayHi()였다면 func에는 함수가 아닌 undefined가 할당된다
이유는 함수 내부return이 없어 값을 반환하지 못하기 때문이다

함수 표현식

const sayHi = function() {
  console.log("Hello");
}

위와 같이 작성된 함수를 함수 표현식이라고 부른다
변수를 먼저 설정하고 그 뒤에 함수를 할당해주는 형식이다

그렇다면 둘의 차이는 무엇일까?
가장 극명하게 차이나는 부분은 hoisting에 있다

우선 함수 선언식hoisting이 일어날 때 함수 본문 전체가 hoisting이 되어 어디서든 접근이 가능하도록 할 수 있다

하지만 함수 표현식의 경우 const로 변수를 생성하고 그 위에 함수를 할당하기 때문에 hoisting이 일어날때 consthoisting이 된다
이때 const이므로 TDZ에 들어가게 된다
따라서 initialization이 일어나야 접근이 가능해진다

참고로 strict mode에서는 함수 선언문코드 블록 내에 위치하면 해당 함수는 블록 내 어디서든 접근할 수 있지만 블록 밖에서는 접근하지 못한다

let age = prompt("나이를 알려주세요.", 18);

// 조건에 따라 함수를 선언함
if (age < 18) {

  function welcome() {
    alert("안녕!");
  }

} else {

  function welcome() {
    alert("안녕하세요!");
  }

}
// 함수를 나중에 호출합니다.
welcome(); // Error: welcome is not defined

callback함수

callback함수

다른 함수가 실행을 마친 뒤 실행이 되는 함수이다
예제를 보며 이해해보자

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

function showOk() {
  console.log( "동의하셨습니다." );
}

function showCancel() {
  console.log( "취소 버튼을 누르셨습니다." );
}

ask('동의', showOk, showCancel);

이때 매개변수로는 question,yes,no가 있다
questionconfirm 선택시 전달받을 값이다

yesno는 각각 question에 여부에 따라 실행될 함수이다
이때 confirm에서 확인을 누른다면 '동의하셨습니다'취소를 눌렀다면 '취소 버튼을 누르셨습니다'출력되게 된다

이 코드를 이해하는건 크게 어렵지 않다
매개변수함수를 받을 수 있고, 함수arguments에 담아 넘겨줄 수 있구나 정도로 생각할 수 있으니

그렇다면 이를 인지하고 새로운 코드를 작성하면 다음과 같아진다.

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

ask(
  '동의',
  function () {
    console.log('동의하셨습니다.');
  },
  function () {
    console.log('취소 버튼을 누르셨습니다.');
  }
);

이렇게 하면 미리 함수를 작성하지 않더라도 넘겨줄 수 있다.

하지만 여기서 의문점은 왜 이게 callback인가? 라는 생각이 든다
이는 모든 구간에서 실행 시간이 존재하지 않기 때문에 체감이 안되는 것이다

사실 흐름은 이렇다
1. ask()를 실행한다
2. confirm을 통해 question에 true or false를 담는다
3. true인지 false인지 판단하고 yes() or no()를 호출한다
4. yes() or no()가 실행된다
이렇게 보니 이해가 된다

아 기존에 작성했던 function먼저 실행되지 않고 ask()실행된 후 마지막에 실행되는거구나 하고

하지만 왜 굳이 callback을 써야하는가 라고 질문한다면
비동기에서 매우 유리한 이점을 가지고 있다

비동기

비동기란 간단하게 말해 코드의 흐름순서대로 계산하지 않고 완료되는 순서대로 계산하는 것을 의미한다

만약 1초가 걸리는 일3초가 걸리는 일이 있다
이때 3초가 걸리는 일을 먼저 하고 1초가 걸리는 일을 한다면 이는 총 4초의 시간을 사용한다
하지만 1초가 걸리는 일3초가 걸리는 일동시에 시행한다면 총 3초의 시간만 사용하게 된다

이것이 바로 비동기의 기본 개념이다

그렇다면 이제 비동기callback상당 부분 닮아있다는 것이 느껴질 것이다

이번엔 실행 시간이 존재하는 코드로 callback의 예제를 살펴보자

function getGeolocation(success) {
  navigator.geolocation.getCurrentPosition((so) => {
    const data = so.coords.latitude;
    success(data);
  });
}

getGeolocation((data) => {
  console.log(data);
});

이 코드는 현재 위치의 위도를 불러오는 함수이다
이를 실행하면 현재의 위치 정보에 대한 위도가 정상적으로 출력이 된다

하지만 코드를 살짝 변형해보면

function getGeolocation(success) {
  navigator.geolocation.getCurrentPosition((so) => {
    const data = so.coords.latitude;
    console.log(data);
  });
}

이는 undefined가 출력된다

분명 선언하는 형식도 같고 다른건 오직 함수를 외부에서 가져온다는 점밖에 없다
하지만 결과값에 대한 것은 천지차이이다

이유는 바로 callback의 성격을 살펴보면 알 수 있다
callback은 앞서 말했든 함수가 실행되고 마지막으로 실행되는 함수이다

따라서 첫 번째 코드에선 data에 위도를 저장하고 저장이 완료되면 data를 출력하는 함수를 불러오는 것이다

하지만 두번째 코드의 경우 data를 저장하고 저장이 완료되지 않아도 출력을 해버린다

차이가 느껴지는가?
이런 차이가 발생하는 이유는 바로 data에 위도를 저장하는데에 있어 실행시간이 존재하기 때문이다
(이는 navigator.geolocation.getCurrentPosition에 대한 내용이다)

사실 처음엔 이해가 잘 되지 않아 그림으로 그려보며 이해를 했었다


마지막으로 callback에 대한 간단한 이점과 코드 가독성에 대해 언급해보겠다

마지막에 봤던 예제를 살펴보면 외부 함수에서 data에 접근이 가능한 것처럼 보인다
이것이 바로 callback의 이점이다
함수 내부에서 사용되는 변수밖에서도 접근이 가능하도록 하는 것도 callback함수의 역할 중 하나인 것이다

만약 callback함수의 가독성을 증가시키고 싶으면 화살표 함수를 사용하면 된다

이는 forEach,reduce,map에서 살펴보겠다

forEach, reduce, map

callback함수를 이용한 배열의 여러 가지 기능들이 존재한다
optional로 적힌 곳은 자주 사용하진 않는 곳이다 적지 않아도 문제는 없다

forEach

forEach는 배열을 순환할때 주로 사용한다

	const arr = [10,20,30,40];
  
  arr.forEach(function (price) {
    sum += price;
  });
  
  console.log(sum); //100

forEach${arrayName}.forEach(function(value,index,array) {...},thisArgs)로 사용한다
각각의 속성을 살펴보자

  • ${arrayName} : 배열의 이름
  • value : 배열의 값을 받아줄 매개변수 이름
  • index : 배열의 index를 받아줄 매개변수 이름
  • array(optional) : 배열 그 자체를 받아줄 매개변수 이름
  • thisArgs(optional) : forEach에서 사용할 this의 값

이를 통해 배열을 순회하며 원하는 실행이 가능하다
참고로 forEach는 반환하는 값이 없어 그저 순환만 한다

reduce

reduce는 배열의 값을 계산할 때 주로 사용한다

  
  sum = arr.reduce(function (acc, cur) {
    return acc + cur;
  }, 0);
  
  console.log(sum); //100

reduce${arrayName}.reduce(function(acc,cur,index,arr){...},initialValue)로 사용한다
각각의 속성을 살펴보자

  • ${arrayName} : 배열의 이름
  • acc : 누적된 값을 저장하는 매개변수 이름
  • cur : 현재 값을 저장하는 매개변수 이름
  • index(optional) : 배열의 index를 저장하는 매개변수 이름
  • arr(optional) : 배열 그 자체를 저장하는 매개변수 이름
  • initialValue(optional,하지만 권장) : 초기값을 설정

이때 reduce는 반환값이 존재하여 return이 반드시 작성되어야 한다
만약 initalValue를 작성하지 않는다자동으로 arr[0]가 acc에 할당되며 시작한다
reduce의 반환값으로는 어떤 값이든 가능하다

map

const friends = ['a', 'b', 'c', 'd'];
// alphabet을 배열 내의 모든 elements에게 prefix로 주고 싶다
const f = friends.map(function (value, index) {
  return `<li>이름 : ${value}</li>`;
});

mapmap(function(cur,index,arr) {} ,thisArg)로 사용한다
각각의 속성을 살펴보자

  • cur : 현재의 값을 저장하는 매개변수 이름
  • index : 배열의 index를 저장하는 매개변수 이름
  • arr(optional) : 배열 그 자체를 저장하는 매개변수 이름
  • thisArg : map에서 사용할 this의 값

이때 map도 반환값이 존재하여 return이 반드시 작성되어야 한다

arguments와 ...rest

function sum() {
  ...
}
sum(1,2,3,4,5,6,7);

이와 같은 코드가 있다고 가정해보자
우리는 배열의 모든 값을 계산하고 싶다 하지만 매개변수로 일일히 받기엔 매개변수가 너무 많아진다

그렇다면 방법이 없을까? 그렇지 않다.
바로 arguments를 이용하면 된다

function sum() {
  console.log(arguments);
}
sum(1,2,3,4,5,6,7);


다음과 같은 결과가 출력된다
그렇다면 iterator가 있으니 배열이니까 forEach 혹은 reduce를 사용해도 되겠다!
아쉽게도 이것은 객체이다(arrayLike이다)

그러면 for...of문을 사용할 수 있겠다!
하지만 배열이면 더 편하게 할 수 있으니 배열로 변환해보자

네 가지 방법이 있다

  • spread syntax
  • Array.from()
  • call로 배열의 특성 빌려오기
  • prototype 변경
const arr = [...arguments]; // spread syntax
const arr = Array.from(arguments); 
const arr = Array.prototype.slice.call(arguments); //만약 한 번에 한해서만 사용하고 싶으면 arr에 저장하지 않고 사용해도 된다

spread syntax는 많이 봤으니 넘어가고
Array.from 부터 차근차근 보자

Array.from

Array.from(${arrayLike})를 사용하게 되면 배열로 바꿔주는 기능이다

배열의 특성 빌리기

우선 argumentsobject이므로 call을 이용해 Array의 성질을 빌려오는 것이다
Array.prototype.slice.call(arguments);를 하게 되면 배열로 변환되고 이걸 새로운 배열에 담아 사용하면 된다

prototype 변경

현재 이는 지원이 중단되어 더 이상은 권장되진 않는다
방법을 살펴보면 '던더(__${name}__)'라고 불리는 녀석을 사용해 변환한다

arguments._\_proto__ = Array.prototype을 이용하게 되면 argumentsprototypearray로 변경된다

이는 arguments를 영원히 바꿔버리므로 주의하는 것이 좋다

IIFE

IIFE(Immediately Invoked Function Expression)

이는 선언과 동시에 실행이 되는 함수를 가리킨다

(function () {
  console.log(1);
})();

바로 1을 출력하는 코드가 된다
따라서 실행부가 따로 존재하지 않는다

그렇다면 이것의 장점은 무엇일까
바로 var가둬놓기 적합하다
앞서 var는 전역을 오염시키는 성질을 가지고 있다고 했다
그래서 var만 쓰던 과거엔 상당히 민감한 문제였다
따라서 고안한 방식이 함수 스코프에 var를 가둬서 쓰되 함수를 일일히 호출하는 것이 아닌 선언과 동시에 실행하자 라는 의미였다

또한 encapsulization(캡슐화)에도 뛰어나다
캡슐화는 연관이 있는 함수를 한 곳에 묶고 모든 것을 포함한 함수를 return하는 방법을 말한다
요즘에는 많이 사용되지 않는 추세이지만 이후 나올 closure에 주로 사용된다

최근엔 캡슐화도 모듈 프로그래밍(import, export)이 자리를 대신하고 있다

IIFE를 이용한 캡슐화는 다음과 같이 정리할 수 있다

화살표 함수

이번 시간에 계속해서 언급했던 화살표 함수는 간단하게만 살펴보자

화살표 함수는 본문이 짧으면 {}를 생략할 수 있다
하지만 생략하는 순간 자동으로 return이 선언된 것과 마찬가지이다
이를 주의해야한다

또한 매개변수'_' 로 설정이 되었다면 그것은 암묵적으로 사용하지 않겠다는 것으로 소통된다

화살표 함수에서 arguments를 담당하는 녀석은 ...rest이다
하지만 arguments와는 다르게 ...rest는 처음부터 배열로 태어난다

arrow function에 대한 이야기는 추후 다른 게시글에서 더 언급해보겠다

profile
FE(철 아님) 개발자 꿈꾸다

0개의 댓글