티스토리 뷰

🔖TAG 💡Argument&Parameter, 💡arrowFunc, 💡CallbackFunc, 💡closure, 💡defaultvalue, 💡PureFunc, 💡RestParameters, 💡void&return, 💡함수&메서드&생성자 📕

출처😌

클린코드 자바스크립트 | Udemy

자료🙄

Git: pocojang/clean-code-js (github.com)


목차

1. 과정 소개 
2. 변수 다루기 
3. 타입 다루기
4. 경계 다루기
5. 분기 다루기
6. 배열 다루기
7. 객체 다루기

8. 함수 다루기 🚩

    8-1. 함수,메서드,생성자

    8-2. argument & parameter

    8-3. 복잡한 인자 관리하기

    8-4. Default Value

    8-5. Rest Parameters

    8-6. void & return

    8-7. 화살표 함수

    8-8. Callback Function

    8-9. 순수 함수

    8-10. Closure


8. 함수 다루기

 

8-1. 함수,메서드,생성자

이 3가지의 구분을 할줄 알아야 합니다.

/**
 * 함수, 메서드, 생성자
 */
// 함수
function func() {
	return this;
}

// 객체의 메서드
const obj = {
	method() {
		return this;
	},
	conciseMethod() {
		return this;
	},
};

// 생성자 함수 (Class)
function Func() {
	return this;
}

/**
 * 함수
 * 1급 객체
 * - 변수나, 데이터에 담길 수 있다
 * - 매개변수로 전달 가능 (콜백 함수)
 * - 함수가 함수를 반환 (고차 함수)
 */
func(); // Window

// 메서드 => 객체에 의존성이 있는 함수, OOP 행동을 의미
obj.method(); // {method: ƒ, conciseMethod: ƒ}

// 생성자 함수 => 인스턴스를 생성하는 역할 => Class
const instance = new Func();
instance // Func {}

참고: this - JavaScript | MDN (mozilla.org)

 

8-2. argument & parameter

가볍게 이해하고 넘어가면 좋습니다.

/**
 * Argument & Parameter
 *
 * 매개변수, 인자, 인수....
 */

/**
 * Parameter (Formal Parameter)
 *
 * 형식을 갖춘, 매개변수
 */
function axios(url) {
	// some code
}

/**
 * Argument (Actual Parameter)
 *
 * 실제로 사용되는, 인자 or 인수
 */
axios('https://github.com');

 

8-3. 복잡한 인자 관리하기

/**
 * 복잡한 인자 관리하기
 */
function toggleDisplay(isToggle) {
	// ...some code
}

function sum(sum1, sum2) {
	// ...some code
}

function genRandomNumber(min, max) {
	// ...some code
}

function timer(start, stop, end) {
	// ...some code
}

function genSquare(top, right, bottom, left) {
	// ...some code
}

 

/**
 * 복잡한 인자 관리하기
 */
function createCar(name, brand, color, type) {
	return {
		name,
		brand,
		color,
		type,
	};
}

 

/**
 * 복잡한 인자 관리하기
 * 특정 인자가 없을 땐 명시적인 에러 발생
 */
function createCar({ name, brand, color, type }) {
	if (!name) {
		throw new Error('name is a required');
	}

	if (!brand) {
		throw new Error('brand is a required');
	}
}

 

8-4. Default Value

/**
 * default value
 */
function createCarousel(options) {
	options = options || {};	// <- 방어코드
	var margin = options.margin || 0;
	var center = options.center || false;
	var navElement = options.navElement || 'div';

	// ..some code
	return {
		margin,
		center,
		navElement,
	};
}

createCarousel(); // {margin: 0, center: false, navElement: 'div'}

아래 코드는 위 코드와 동작은 똑같습니다.

다른건 parameter부분에 default값들을 미리 설정하는 것 뿐입니다.

{ margin~~, center~~, navEle~~~ } = { } 부분은

위 코드의 방어코드 부분과 똑같습니다. 이 부분이 없으면 에러가 뜹니다.

/**
 * default value / default parameter
 */
function createCarousel({
	margin = 0,
	center = false,
	navElement = 'div',
} = {}) {
	// ..some code
	return {
		margin,
		center,
		navElement,
	};
}

createCarousel(); // {margin: 0, center: false, navElement: 'div'}

 

/**
 * default value
 */
const required = (argName) => {
	throw new Error('required is ' + argName);
};

function createCarousel({
	items = required('items'),  // <- required 함수는 해당 parameter가 안들어가면 에러를 냄 (안전 & 명시적 코드!)
	margin = 0,
	center = false,
	navElement = 'div',
} = {}) {
	// ... some code

	return {
		margin,
		center,
		navElement,
	};
}

console.log(
	createCarousel({
		margin: 10,
		center: true,
		navElement: 'span',
	}),
); // Uncaught Error: required is items

 

8-5. Rest Parameters

가변인자가 들어올 때 arguments객체(유사배열객체)를 사용합니다.

하지만 이 또한 문제가 있습니다.

sumTotal 함수가 추가적인 인자를 받고 싶을 때는 문제가 발생할 수 있습니다.

/**
 * Rest Parameters
 */
function sumTotal() {
	Array.isArray(arguments); // false
	Array.from(arguments) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    
	return Array.from(arguments).reduce(
		(acc, curr) => acc + curr,
	);
}

// sumTotal(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // 55

//------------------------------------------

function sumTotal2(...args) {
    Array.isArray(args); // true

    return args.reduce((acc, curr) => acc + curr);
}

sumTotal2(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // 55

추가적인 인자를 받고 싶을 때는

나머지 매개변수(rest parameters)를 사용합니다.

함수 선언부에서만 쓸 수  있습니다.

이것은 전개 연산자(spread Operator)와는 다릅니다.

/**
 * Rest parameters
 */
function sumTotal(
	initValue,
	bonusValue,
	...args
) {
	return args.reduce(
		(acc, curr) => acc + curr,
		initValue,
	);
}

sumTotal(100, 99, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // 155

 

8-6. void & return

void는 함수의 반환이 없다는 의미입니다.

setState() 와 alert()는 return 이 없는 void입니다.그래서 return setState()나 return alert()를 할 필요가 없습니다.또 아래의 코드들은 함수의 이름이 모호해서 리턴값이 있는지 알 수 없습니다.

/**
 * void & return
 */
function handleClick() {
	return setState(false);
}

function showAlert(message) {
	return alert(message);
}

function test(sum1, sum2) {
	const result = sum1 + sum2;
}

function testVoidFunc() {
	return test(1, 2);
}

testVoidFunc();

아래 처럼하는것이 더 명시적인 코드입니다.

function handleClick() {
	setState(false);
	return;
}

function showAlert(message) {
	alert(message);
	return;
}

내가 쓰는 API들이 void 인지 return이 있는지 확인하고 쓰는 습관을 가져야합니다.

한 예로, Array.push()도 반환값이 있습니다.

 

아래의 코드는

함수의 이름을 is나 get을 붙임으로써

반환값이 있다는 암시를 합니다.

그러면 함수를 값 자체로 활용할 수 있습니다.

/**
 * void & return
 */
function isAdult(age) {
	return age > 19;
}

function getUserName(name) {
	return '유저 ' + name;
}

const isFlag = isAdult(10) // false

 

결론

1. 함수를 만들 때 반환값이 있는지 없는지 명시적으로 네이밍을 해줍니다.

2. 반환값이 없는 API를 무의미하게 return하지 않도록 합니다.

3. 자신이 쓰는 API들이 어떤 반환값을 가지는지 확인하는 습관을 가집니다.

 

8-7. 화살표 함수

화살표 함수와 일반 함수가 this를 가리키는게 다릅니다.

 

화살표 함수는 Lexical Scope를 가집니다. 즉, 상위의 문맥을 따르는 경향이 있습니다.

그래서 메서드에서 화살표 함수를 사용할 때 this 조작접을 주의해야 합니다.

/**
 * Arrow Function
 */
const user = {
	name: 'Poco',
	getName: () => {
		return this.name;	//화살표 함수의 this는 상위 스포크의 window를 가리킵니다.
	},
};

user.getName(); // undefined

const user2 = {
    name: 'BOB',
    getName: function() {
        return this.name;
    }
}
user2.getName() // 'BOB'

 

화살표 함수에서는 arguments나 call, apply, bind 등 사용할 수 없습니다.

/**
 * Arrow Function
 */
const user = {
	name: 'Poco',
	getName: () => {
		return this.name;
	},
	newFriends: () => {
		const newFriendList = Array.from(arguments);

		return this.name + newFriendList;
	},
};

user.newFriends('Jang', '장') // Uncaught ReferenceError: arguments is not defined

그럼 화살표함수에서 arguments를 사용하고 싶으면 어떻게 해야할까요?

나머지 매개변수(Rest parameters)를 사용하면 됩니다.

const user = {
	name: 'Poco',
	getName: () => {
		return this.name;
	},
	newFriends: function (...rest) {

		return this.name + rest.join('');
	},
};

user.newFriends('Jang', '장') // 'PocoJang장'

 

다음 예시는 화살표함수 포함한 생성자함수를 만들어서

new 키워드를 이용해서 인스턴스를 만들고 있습니다.

아래는 오류가 납니다.

📌화살표 함수로 만든 함수는 생성자로 사용할 수 없습니다.

몰론 Class라는 Syntactic Sugar가 있기 때문에 이걸 만들 이유는 없습니다.

하지만 new 키워드와 조합하는 함수를 화살표함수로 만들 경우에는 큰 에러를 일으킬 수 있습니다.

const Person = (name, city) => {
	this.name = name;
	this.city = city;
};

const person = new Person('poco', 'korea'); //Uncaught TypeError: Person is not a constructor

 

다음 케이스는 부모 클래스와 자식 클래스가 있습니다.

부모 클래스는 일반 함수로 된 메서드와 화살표 함수로 된 메서드가 있습니다.

 

부모 클래스에서 화살표 함수로 만든 메서드를

자식 클래스에서 super로 받으면 오류가 발생합니다.

화살표함수로 클래스를 구현했을 때 생성자 함수 내부에서 바로 초기화가 되는 현상이 있습니다.

 

또 하나의 문제는

부모 클래스와 자식 클래스에서 동일한 이름의 메서드가 있을 때

부모 클래스의 메서드가 화살표 함수로 작성되었다면

자식 클래스에서 동일한 이름의 메서드를 출력할 때 부모 클래스의 메서드가 출력되는 문제가 발생합니다.

부모 클래스의 메서드를 일반 함수로 바꿔주면 문제는 해결됩니다.

class Parent {
	parentMethod() {
		console.log('parentMethod');
	}

	parentMethodArrow = () => {
		console.log('parentMethodArrow');
	};

	overrideMethod = () => {
		return 'Parent';
	};
}

class Child extends Parent {
	childMethod() {
		super.parentMethod();
		// super.parentMethodArrow(); // Uncaught TypeError: Child.childMethod is not a constructor
	}

	overrideMethod() {
		return 'Child';
	}
}

// new Child().childMethod(); // parentMethod
// new Child().overrideMethod(); // 'Parent'	<- ?!?! 부모의 메서드가 출력됨 (부모 메서드를 일반 함수로 바꾸면 해결됨)

 

결론

 

화살표 함수 사용은 아래와 같은 점들을 유의해야합니다.

1. this 동작방식 차이 이해

2. 클래스 입장에서의 오버라이드

3. 부모 메서드를 가지고 응용하는 경우

4. 화살표 함수는 new 연산자와  조합 불가

5. arguments객체나 call, apply, bind 사용 시 유의

 

화살표 함수를 자주 사용 하시되 위의 유의점을 잘 이해하고 있으면 좋습니다.

 

8-8. Callback Function

콜백 함수는 함수를 다른 함수에 넘겨서 제어권을 위임할 수 있습니다.

/**
 * Callback Function
 */
function register() {
	const isConfirm = confirm(
		'회원가입에 성공했습니다.',
	);

	if (isConfirm) {
		redirectUserInfoPage();
	}
}

function login() {
	const isConfirm = confirm(
		'로그인에 성공했습니다.',
	);

	if (isConfirm) {
		redirectIndexPage();
	}
}

 

/**
 * Callback Function
 */
function confirmModal(message, cbFunc) {
	const isConfirm = confirm(message);

	if (isConfirm && cbFunc) {
		cbFunc();
	}
}

function register() {
	confirmModal(
		'회원가입에 성공했습니다.',
		redirectUserInfoPage,
	);
}

function login() {
	confirmModal(
		'로그인에 성공했습니다.',
		redirectIndexPage,
	);
}

 

8-9. 순수 함수

비 순수 함수를 쓰면 외부의 값을 조작하면

동일한 비순수함수의 값들이 달라지는 문제가 생깁니다.

/**
 * Pure Function
 */
let num1 = 10;
let num2 = 20;

function impureSum1() {
	return num1 + num2;
}

function impureSum2(newNum) {
	return num1 + newNum;
}

function pureSum(num1, num2) {
	return num1 + num2;
}

pureSum(10, 20);
pureSum(10, 20);
pureSum(10, 20);
pureSum(30, 100);
pureSum(30, 100);

  

/**
 * Pure Function
 */

function changeValue(num) {
	num++;

	return num;
}

////////////////////////////////

const obj = { one: 1 };

// 객체, 배열 => 새롭게 만들어서 반환 해야 원본 값이 바뀌지 않음
function changeObj(targetObj) {
	targetObj.one = 100;

	return targetObj;
}

changeObj(obj); // {one: 100}

obj; //{one: 100} <-- ?!?! 원본 값도 바뀌어 버리는 문제

// 문제 해결
function changeObj2(targetObj) {
    return { ...targetObj, one: 100}
}
changeObj2(obj) // {one: 100}

obj; // {one: 1}

 

결론

 

1. 항상 동일한 input과 동일한 output이 나오도록 순수 함수를 의식적으로 작성해 나가시는게 좋습니다.

2. 순수함수에서 reference타입(객체, 배열)을 다룰 때에는 새롭게 만들어서 반환하는 것이 안전한 코드를 만들 수 있습니다.

 

8-10. Closure

클로저는 사용하지 않아도 되는 자리는 사용하지 않는 것이 가장 좋습니다.

 

아래는 간단한 클로저의 예시 입니다.

addOne 변수는 add함수에 인자 1을 넣은 값입니다.

지금 클로저를 사용한 함수는 괄호가 2개 입니다.

 

📌 add(1)을 호출하면 add함수는 외부함수만 실행된 것이고,

실행된 상태에서 내부 함수의 환경을 기억하고 있는 것 입니다.

 

외부 함수에 값(1)을 넣은 클로저 함수를 addOne 변수에 담은 상태에서,

이제 addOne 변수를 함수처럼 인자를 넣어 호출하면, 클로저 함수의 내부 함수 까지 실행됩니다.

 

똑같은 함수로 실행시킬 때마다 개별 환경 컨텍스트를 기억할 수 있는 것입니다.

function add(num1) {
	return function sum(num2) {
		return num1 + num2;
	};
}

const addOne = add(1);
const addTwo = add(2);

// addOne 
/*출력결과
 * ƒ sum(num2) {
 *   return num1 + num2;
 * }
*/

addOne(3) // 4 <-- ?!?!

 

아래는 클로저 확장 개념입니다.

addOne 은 5와 2를 add함수에 넣은 컨텍스트 상태를 캡쳐링 하고 있는 상태입니다.

다시 addOne에다 다른 값을 넣어 다른 값을 얻을 수 있습니다.

이것이 클로저의 장점입니다.

function add(num1) {
	return function (num2) {
		return function (calculateFn) {
			return calculateFn(num1, num2);
		};
	};
}

function sum(num1, num2) {
	return num1 + num2;
}

function multiple(num1, num2) {
	return num1 * num2;
}

const addOne = add(5)(2); // 5는 add 함수의 첫 번째 컨텍스트, 2는 add 함수의 두 번째 컨텍스트에 들어가 기억됨
const sumAdd = addOne(sum);
const sumMultiple = addOne(multiple);

addOne
/* 실행결과
 *ƒ (calculateFn) {
 *   return calculateFn(num1, num2);
* }
*/
sumAdd // 7
sumMultiple // 10

 

아래는 log 출력하는 함수입니다.

클로저로 각자의 매개변수를 활용할 수 있습니다.

/**
 * Closure
 */
function log(value) {
	return function (fn) {
		fn(value);
	};
}

const logFoo = log('foo');
logFoo
/* 출력결과
 * ƒ (fn) {
 *		fn(value);
 *	}
*/

logFoo((v) => console.log(v)); // foo
logFoo((v) => console.info(v)); // foo
logFoo((v) => console.error(v)); //에러: foo
logFoo((v) => console.warn(v)); // 경고: foo

 

아래 코드는 클로저가 적용되지 않은 상태입니다.

const arr = [1, 2, 3, 'A', 'B', 'C'];

const isNumber = (value) =>
    typeof value === 'number';
const isString = (value) =>
    typeof value === 'string';

arr.filter(isNumber) // (3) [1, 2, 3]

클로저를 적용하면 아래와 같습니다.

const arr = [1, 2, 3, 'A', 'B', 'C'];

function isTypeOf(type) {
	return function (value) {
		return typeof value === type;
	};
}

const isNumber = isTypeOf('number');
const isString = isTypeOf('string');

console.log(isNumber);
/* 출력결과
 *ƒ (value) {
 *		return typeof value === type;
 *	}
*/

console.log(isString);
/* 출력결과
 *ƒ (value) {
 *		return typeof value === type;
 *	}
*/
arr.filter(isNumber); // (3) [1, 2, 3]
arr.filter(isString); // (3) ['A', 'B', 'C']

 

다음 예제는 fetch가 들어간 예시 입니다.

function fetcher(endpoint) {
	return function (url, options) {
		return fetch(endpoint + url, options)
			.then((res) => {
				if (res.ok) {
					return res.json();
				} else {
					throw new Error(res.error);
				}
			})
			.catch((err) => console.error(err));
	};
}

const naverApi = fetcher('http://naver.com');
const daumApi = fetcher('http://daum.net');

getDaumApi('/webtoon').then((res) => res);
getNaverApi('/webtoon').then((res) => res);

 

throttle, debounce 예제 입니다.

/**
 * Closure
 */
someElement.addEventListener(
	'click',
	debounce(handleClick, 500),
);

someElement.addEventListener(
	'click',
	throttle(handleClick, 500),
);
 

의도적으로 클로저를 쓰는 코드를 만드는 리팩토링 과정을 거쳐보면 좋습니다.

'WEB > JavaScript' 카테고리의 다른 글

[week5] JS - 2  (0) 2022.08.01
[week4] JS  (0) 2022.07.25
[클린코드 For JS] 7. 객체 다루기  (0) 2022.06.30
[클린코드 For JS] 6. 배열  (0) 2022.06.29
[클린코드 For JS] 5. 분기  (0) 2022.06.28
댓글
다크모드
Document description javascript psychology
더보기 ,제목1 태그 호버