자바스크립트 - 함수

이정인·2022년 3월 3일
0

기초부터 완성까지, 프런트엔드를 읽고 정리한 내용입니다.

자바스크립트 함수

함수란?

함수란 객체의 특별한 형태이며 statement로 구성된 몸체를 가진 하나의 실행단위이다.

또한 자바스크립트의 함수는 일급 함수로서 다른 함수의 매개변수나 반환 값으로도 사용할 수 있다.

일급함수란 아래의 조건을 만족하는 함수를 말한다.

  • 변수에 함수를 할당할 수 있음
  • 함수를 인자로 전달할 수 있음
  • 함수를 반환 값으로 사용할 수 있음
function doSomething() {
	console.log('Hello javascript');
}

function finishSomething(callback) {
	callback();
	console.log('finish');
}
finishSomething(doSomething);

doSomething()finishSomething()의 인자로 넘어간다. 이렇게 다른 함수의 인자로 넘어가는 함수를 콜백함수라고 한다. 가장 대표적인 예로는 DOM에 이벤트를 추가하는 addEventListener() 함수가 있다.

button.addEventListener('click', callback);

함수의 정의 방법

함수는 function 키워드로 정의되며 함수의 이름(식별자), 매개변수, 함수의 몸체를 가진다. 함수를 생성하는 방법에는 함수 선언문, 함수 표현식 그리고 Function 생성자 함수를 사용하는 방법이 있다. Function 생성자 함수는 보안 및 성능 문제가 있어 권장되지 않는다.

함수 선언문

함수 선언문은 함수의 이름이 반드시 정의되어야 한다. 함수 선언문이 실행되면 함수의 이름과 동일한 변수를 정의하여 함수 객체를 변수에 할당한다.

console.log(multiply(2, 3)); // 6

function multiply(a, b) {
 	return a * b;
}

함수 선언문에는 독특한 특징이 있다. 바로 호이스팅(hoisting)으로 인해 함수가 선언된 위치에서 코드의 최상단으로 끌어올려진다. 따라서 이 함수는 선언된 위치보다 상단에서 호출될 수 있다.

함수 표현식

함수 이름이 선택 사항이며 변수에 함수를 직접 할당한다.

const multiply = function(a, b) {
	return a * b;
}

여기서 multiply는 함수의 이름이 아니라 변수이다. 이렇게 이름이 없는 함수를 익명 함수 표현식이라고 부른다. 익명 함수의 호출은 함수를 할당한 변수를 사용하여 호출한다.

함수의 이름을 포함한 기명 함수 표현식도 있다. 이 때 함수의 이름은 함수의 몸체에서만 사용할 수 있다. 주로 재귀적으로 호출할 때 사용한다.

const factorial = function doSomething(n) {
	return n <= 1 ? 1 : n * doSomething(n - 1);
}
console.log(factorial(5)); // 120

함수 표현식은 함수 선언문과 달리 호이스팅이 되지 않기 때문에 변수에 함수를 할당하기 전에 호출할 수 없다.

함수의 호출

함수 호출은 표현식이기 때문에 값으로 평가되며, 함수 몸체에 return 문을 사용하여 결과 값을 반환할 수 있다. 만약 return 문이 명시되지 않는다면 undefined가 결과 값이 된다.

매개변수

함수를 정의할 때 매개변수를 명시하여 정보를 전달할 수 있다. 매개변수는 함수 몸체에서 지역 변수처럼 사용된다. 자바스크립트는 느슨한 타입을 가진 언어이기 때문에 타입을 명시하지 않으며 함수 호출 시 전달하는 인자 값도 검사하지 않고 인자의 개수도 검사하지 않는다. 정의된 매개변수보다 적은 수로 인자를 전달한다면 나머지 매개변수의 값은 undefined로 설정되고 더 많으면 무시된다.

인자 : 함수 호출 시 전달되는 값 argument

매개변수: 함수에서 전달된 인자를 받아들이는 변수 parameter

해체 할당과 매개 변수 기본값

function getUserInfo({name = 'Lee', age, country}) {
	return `name: ${name}, age: ${age}, country: ${country}`;
}

const userInfo = {name: 'Lee', age: 20};
console.log(getUserInfo(userInfo));

arguments

화살표 함수를 제외한 모든 함수에서는 arguments라는 객체를 사용할 수 있다. 이 객체를 사용해 함수에 실제로 전달된 인자들을 참조할 수 있으며 유사 배열 객체이기 때문에 인덱스로 프로퍼티에 접근할 수 있고, length 프로퍼티를 가지고 있다.

ES2015에서 등장한 나머지 매개변수로 대체할 수 있다. 나머지 매개 변수는 유사 배열 객체가 아닌 진짜 배열이기 때문에 인자들을 배열로 다루고 싶은 경우 유용하게 사용할 수 있다.

funciton sum(...args) {
  //args는 배열이기 때문에 배열 내장 메서드를 사용할 수 있다.
	args.forEach(function (arg) { 
		// ...
	});
}

sum(1,2);

일반 매개변수와도 사용할 수 있는데 마지막 매개변수만 나머지 매개변수가 될 수 있다.

function sum(type, ...args) {
	args.forEach(function (arg) { 
		// ...
	});
}
sum('money', 1, 2);

화살표 함수 (Arrow function)

화살표 함수는 ES2015에 등장한 문법으로 기존 함수 표현식에 비해 간결하게 함수를 정의할 수 있으며 항상 익명 함수이다.

  • function 키워드를 생략한다.
  • 매개변수가 하나인 경우 괄호를 생략할 수 있다.
  • 함수 몸체에서 문이 하나인 경우 중괄호나 return 키워드를 생략할 수 있다.
const greeting = name => 'hello ${name}';

특징

화살표함수는 arguments 객체와 this를 바인딩하지 않기 때문에 기존 함수와는 다르게 동작한다. 나머지 매개변수를 사용하여 arguments 객체를 대체할 수 있다.

바인딩은 변수와 변수에 관련된 프로퍼티를 연관시키고 값을 설정하는 것을 말한다.

const func = () => arguments[0];
func(1); //Uncaught ReferenceError: arguments is not defined

const func = (...args) => args[0]
func(1); // 1

this

자바스크립트 함수에서는 this라는 특별한 키워드를 사용할 수 있다. this는 읽기 전용 갑승로 런타임 시 설정할 수 없으며 함수를 호출한 방법에 의해 값이 달라진다.

전역 실행 컨텍스트는 자바스크립트 엔진이 코드를 실행할 때 처음으로 생성되는 컨텍스트이다. 자바스크립트 코드가 실행되는 최상위 환경이라고 할 수 있다. 전역 실행 컨텍스트에서 this는 항상 전역 객체를 참조한다. (브라우저의 경우 window, 노드의 경우 global)

일반 함수

function func() {
	console.log(this === window); // true (브라우저 기준)
}
func();

//-----------
function func() {
	'use strict';
	console.log(this === window); // false
	console.log(this === undefined);  //true
}
func();

windwo.func()이 아니라 func()을 호출 했기 때문에 함수의 컨텍스트가 어디에 속하는지 알 수 없으므로 this는 undefined가 되어야 한다. 하지만 this의 바인딩이 되지 않은 경우 기본 값으로 전역 객체를 참조하기 때문에 window가 나온다. 자바스크립트 엄격 모드를 사용할 경우 해결할 수 있다. 엄격모드는 엄격하게 오류를 검사하여 안전한 자바스크립트 작성을 도와준다.

생성자 함수

new 키워드를 사용하여 함수를 호출하면 생성자 함수로 동작한다.

function Venhicle(type) {
	this.type = type;
}
const car = new Vehicle('Car');

생성자 함수를 호출하여 객체가 생성될 때 아래와 같은 순서를 거친다.

  • 객체를 생성하여 this에 바인딩

    생성자 함수 내의 코드를 실행하기 전에 객체를 만들어 this에 바인딩 한다. 생성된 객체는 생성자 함수의 prototype 프로퍼티에 해당하는 개체를 프로토타입으로 설정한다. 이 객체는 이 후 단계에서 this를 통해 계속 참조된다.

  • 프로퍼티 생성

    this에 바인딩한 객체에 프로퍼티를 생성한다.

  • 객체 반환

    생성된 객체, this에 바인딩한 객체를 반환한다. 기본적으로는 this를 반환하지만 다른 반환 값을 명시적으로 지정하였다면 해당 값이 반환된다.

메서드

자바스크립트에서는 객체의 프로퍼티인 함수를 메서드라고 부르며 메서드를 호출하면 this는 해당 메서드를 소유하는 객체로 바인딩된다. 단 메서드를 어떻게 호출했느냐에 따라 this 바인딩이 달라진다.

const obj = {
	lang: 'javascript',
	greeting() {
		console.log(this);
		return `hello ${this.lang}`;
	}
}
console.log(obj.greeting()); // 'hello javscript'

const greeting = obj.greeting;
console.log(greeting()); //'hello undefined'

두번째 경우에는 함수의 컨텍스트가 정확하지 않으므로 this는 전역 변수를 참조하거나 엄격 모드인 경우 undefined로 남아 있게 된다.

call(), apply(), bind()

함수 내장 메서드인 call(), apply(), bind()를 이용하여 함수의 호출 방법에 상관 없이 this로 바인딩될 객체를 변경할 수 있다. 이를 명시적 바인딩이라고 한다.

call(), apply()

어떤 함수를 다른 객체의 메서드처럼 호출할 수 있게한다. this를 특정 객체에 바인딩하여 함수를 호출하는 역할을 한다.

const obj = { name : 'javascript' };

function greeting() {
	return 'Hello ${this.name}
}

function getUserInfo(age) {
  return `${this.name} age: ${age}`
}
console.log(greeting.call(obj)); // 'Hello javascript'
console.log(getUserInfo.call(obj, 20)) // 'javscript age : 20'

call() 메서드를 통해 호출하는 함수로 인자를 전달할 수도 있다.

apply()는 call()가 동일하지만 호출하는 함수에 전달할 인자를 배열 형태로 전달해야한다.

const obj = { name : 'lee' };

function getUserInfo(age, country) {
  return `${this.name} age: ${age} country: ${country}`
}
console.log(getUserInfo.apply(obj, [20, korea]))

bind()

함수 this 바인딩을 변경할 수 있지만 call(), apply()와 차이가 있다. 함수가 어디서 어떻게 호출되는지에 상관 없이 this 값을 고정하고 싶을 때 사용한다.

  • 함수의 this 바인딩을 영구적으로 변경한다. 한번 변경하면 call(), apply(), 다른 bind()를 사용해도 변경할 수 없다. (생성자 함수로 사용되는 경우는 예외)
  • this를 바인딩하여 함수를 호출하는 것이 아니라 새로운 함수를 반환한다.
const obj = { name : 'lee' };
const obj2 = { name : 'kim' };

function getUserInfo(age, country) {
  return `${this.name} age: ${age} ${country}`
}

const bound = getUserInfo.bind(obj1)
console.log(bound(20, 'Korea')); // lee age: 20 korea
console.log(getUserInfo.apply(obj2, [20, korea]))// lee age: 20 korea

전역 컨텍스트에서 호출하든 어쩌든 값이 고정된다. this 뿐만 아니라 함수에 전달할 인자도 고정할 수 있다.

const obj = { name : 'lee' };
const obj2 = { name : 'kim' };

function getUserInfo(age, country) {
  return `${this.name} age: ${age} ${country}`
}

const bound = getUserInfo.bind(obj1, 20)
console.log(bound('Korea')); // lee age: 20 korea

화살표 함수와 렉시컬 this

화살표 함수는 this를 바인딩 하지 않기 때문에 this 바인딩 규칙과 완전히 다르게 동작한다.

렉시컬 this

함수 호출에 따라 동적으로 this를 바인딩하는 것이 아니라 함수를 어디에 선언하는지에 따라 this의 값이 결정된다. 화살표 함수의 this는 화살표 함수를 둘러싸고 있는 렉시컬 스코프에서 this의 값을 받아 사용한다. 이러한 this를 렉시컬 this라고 하며 이 값은 변경되지 않는다.

렉시컬 스코프는 자바스크립트 엔진이 변수를 찾는 검색 방식에 대한 규칙이며 함수를 어디에 선언했는지에 따라 결정된다.

const obj = {
	lang : 'javascript',
	greeting: () => {
		return 'hello ${this.lang}';
	}
}
console.log(obj.greeting()); // hello undefined

함수의 컨텍스트를 obj로 지정했음에도 불구하고 this에 접근하지 못한다. 화살표 함수의 렉시컬 this가 obj가 아닌 obj를 둘러싸고 있는 전역 컨텍스트에서 값을 받아오기 때문이다.

화살표 함수의 특징

  • this를 따로 바인딩하지 않고 변경되지 않는 렉시컬 this를 갖는다. 따라서 call(), apply(), bind()를 사용해 변경할 수 없다.

  • 생성자 함수로 사용할 수 없다.

    생성자 함수는 객체를 생성하여 바인딩하지만 화살표 함수는 렉시컬 this의 특징 때문에 이러한 동작이 불가능하기 때문이다.

setTimeout()과 같은 전역 객체의 함수를 메서드에서 호출할 경우 화살표 함수를 사용하면 명확하고 간결하게 표현할 수 있다.

const obj = {
	name : 'javascript',
	greeting() {
		setTimeout((function timer() {
      console.log(this.name)
    }).bind(this), 100);
  }
};

const obj2 = {
	name : 'javascript',
	greeting() {
		setTimeout(() => {
			console.log(this.name);
		}, 1000);
	}
}
//화살표 함수는 자신을 둘러싸고 있는 렉시컬 스코프인 obj를 렉시컬 this로 참조하기 때문에 bind() 메서드를 사용하지 않아도 this를 obj 객체로 바인딩할 수 있다.

DOM에 이벤트를 추가하는 addEventListener() 함수의 경우 화살표 함수를 주의해서 사용해야한다.

block.addEventListener('click', function() {
	// this는 block 객체에 바인딩 된다. this = event.currentTarget
	console.log(this.id);
});

block.addEventListener('click', () => {
	// this는 전역 객체에 바인딩 된다. 요소에 접근할 수 없다.
	console.log(this.id);
})

0개의 댓글