const a = 1 // 변수에 담을 수 있다.
function f1 (num) { // 매개변수로 전달할 수 있다.
const b = num + 1
return b // return으로 전달할 수 있다.
}
console.log(f1(a)) // result: 2
*** 대부분의 프로그래밍 언어에서 숫자형/문자형 등은 일급 십민의 조건을 충족한다**
일급 시민의 조건을 충족하는 객체를 의미한다.
const a = {msg: 'a는 1급 객체입니다.'} // 변수에 담을 수 있다.
function f1 (a) { // 매개변수로 전달할 수 있다.
const b = a;
b.msg2 = '이것은 2번째 메세지입니다.'
return b // return으로 전달할 수 있다.
}
console.log(f1(a))
// result : {msg: 'a는 1급 객체입니다.', b: '이것은 2번째 메세지입니다.'}
// 변수에 함수를 할당한다.
const a = function (num) {
return num * num;
}
// 매개변수로 함수를 전달한다.
function b (fun) {
const num = fun(10);
// return 값으로 함수를 사용할 수 있다.
return function (num2) {
const num2 = num2 || 2;
return num * num2;
}
}
// b에 a라는 함수 전달했으며
// b는 다시 함수를 반환한다.
// 결국 c도 함수로 사용할 수 있다.
const c = b(a);
console.log(c()); // result : 200
console.log(c(3)); // result : 300
*closure
는 JS를 이용한 고난이도의 테크닉을 구사하는데 필수적인 개념으로 활용된다*
사전적 의미에서 closure
는 내부함수가 외부함수의 맥락(context
)에 접근할 수 있는 것을 가르킨다.
라고 정의 되어있지만 이는 소스를 보면서 이해하도록 하자.
동시에 Closure는 일급함수에 해당한다
function outter() { // 외부함수
const variable = "variable"; // 외부함수의 전역변수
function inner() { // 내부 함수 (외부함수 안에 선언된 함수)
const title = "coding everybody";
console.log(title);
console.log(variable); // * inner에서 outter의 전역 변수에 접근이 가능하다
}
// outter 함수든 inner 함수든 함수표현식, arrow 함수 등으로도 표현이 가능하다
// 함수 표현식
const inner2 = function () {
const title2 = "coding everytime";
console.log(title2);
};
// arrow 함수
const inner3 = () => {
const title3 = "coding everywhere";
console.log(title3);
};
// inner 함수는 outter 함수 안에서만 쓰인다는 의미를 가지며 응집성이 좋고 보기에도 좋다
return inner();
}
outter();
function outter() {
var title = "coding everybody";
// return 을 했다 라는건 그 함수는 호출 후 소멸된다는 뜻이다
return function () {
console.log(2, title); // 외부함수의 변수에 접근
};
}
const inner2 = outter2(); // 외부함수 실행 후 소멸
inner2(); // 결과: 'coding everybody'
외부함수는 호출 후 소멸되었지만 여전이 외부함수의 title 변수에 접근이 가능하며
log가 잘 출력되는 것을 확인할 수 있었다.
*** 이러한 내부함수에서 외부함수의 전역변수에 접근할 수 있는 매커니즘이 바로 클로저의 중요한 특징이다.**
일반적인 객체지향프로그래밍은 Prototype을 통해 객체를 다루는 것을 말하는데,
이를 통해 객체를 생성할 경우 비밀변수 접근에 대한 권한 문제가 존재한다.
function Hello(name) {
this._name = name;
}
Hello.prototype.say = function() {
console.log('Hello, ' + this._name);
}
const hello1 = new Hello('aa');
const hello2 = new Hello('bb');
const hello3 = new Hello('cc');
hello() 로 생성된 객체들은 모두 _name 이라는 변수를 가지게 된다.
hello1.say(); // 'Hello, aa'
hello2.say(); // 'Hello, bb'
hello3.say(); // 'Hello, cc'
hello1._name = 'yosi';
hello1.say(); // 'Hello, yosi' < 데이터가 변함 === 객체의 변수에 쉽게 접근이 가능하다
* **비밀변수**의 중요성
sw가 커질수록 많은 사람이 코드를 작성할텐데
그럴경우에 많은 데이터가 sw에 존재하게 되는데 그 데이터가 누구나 수정할 수 있는 데이터가 된다는 것은
그 sw는 망가질 가능성이 커진다는 것을 의미하게 되기 때문이다.
**클로저**의 경우
전역변수에 접근하려면 객체의 메소드를 통해서 접근이 가능하기 때문에
title(ex)이라는 변수를 외부에서 아무리 변경을 해도 함수에 대한 사용에 영향을 전혀 주지 않는다.
** **클로저**는 **비밀변수**에 접근할 수 있게 해주는 좋은 매커니즘이다
function factory_movie(title) {
// title은 outter의 전역변수로서 사용된다
// 객체 안의 메소드들을 내부함수라고 생각하면된다
return {
get_title: function () { // 객체의 소속이면서 factory_movie의 내부함수 이다
return title; // 내부함수에서 title에 접근이 가능하다
},
set_title: function (_title) {
title = _title; // set_title에서 받아온 데이터를 outter의 전역변수에 저장도 가능하다
},
};
}
// outter 호출 하기
let ghost = factory_movie("Ghost in the shell");
let matrix = factory_movie("Matrix");
console.log(ghost);
// { get_title: [Function: get_title], set_title: [Function: set_title] }
console.log(matrix);
// { get_title: [Function: get_title], set_title: [Function: set_title] }
console.log(ghost === matrix); // false
언뜻 반환하는 데이터를 보면 같은 데이터인 것처럼 보이지만 참조하고 있는 **outter**의
전역변수 또는 매개변수의 데이터(**context**)가 다르기 때문에
두개의 데이터는 서로 다른 데이터임을 의미한다.
console.log(ghost.get_title()); // 결과: Ghost in the shell
console.log(matrix.get_title()); // 결과: Matrix
ghost.set_title("유후~");
matrix.title = "요시"; // ** 접근 불가 < 접근 차단을 해주었기 때문에 은닉화를 쉽게 해결할 수 있다.
* 위에서 set_title을 통해 outter의 전역변수인 title의 데이터 값을 바꾸어
outter 자체의 title 데이터를 바꾸고 있는것 처럼 보이지만
다른 변수에서 선안한 outter의 데이터에는 영향이 가지 않는다.
console.log(ghost.get_title()); // 결과: '유후~'
console.log(matrix.get_title()); // 결과: 'Matrix'
// Bad
var arr = [];
for (var i = 0; i < 5; i++) {
arr[i] = function () {
return i;
};
}
for (var index in arr) {
console.log(arr[index]());
// 위에 선언된 함수가 외부의 context(i)에 접근할 수 있을 것으로 예상했지만 결과는 아래와 같았다.
// 예상 0 1 2 3 4 출력
// 실제 5 5 5 5 5 출력
// 위의 함수는 함수에 감싸진 inner함수가 아닌 **for**문에 감싸진 일반 함수기 때문에
// i의 값은 inner함수의 외부 변수가 아니고 이미 반복문을 다 돈 후의 값을 return하게 된다.
}
// Good
var arr = [];
for (var i = 0; i < 5; i++) {
arr[i] = (function (id) { // 외부함수 선언
return function () { // 내부함수 선언
return id;
};
})(i); // 선언과 동시에 호출(IIFE: 즉시 실행 함수)
// for문이 반복될 때마다 외부함수가 실행되고 실행되는 시점에서
// i값을 넘겨주고 외부함수가 가지고 있었기 때문에 그 시점에 맞는 id 값을 반환할 수 있었던 것이다
}
for (var index in arr) {
console.log(arr[index]()); // 결과: 0 1 2 3 4
}
// Good
// arrow 함수와 IIFE 대신 따로 함수를 만들어서 실행한 예제
var arr = [];
const funcId = (id) => {
return () => {
return id;
};
};
for (var i = 0; i < 5; i++) {
arr[i] = funcId(i);
}
for (var index in arr) {
console.logrr[index]()); // 결과: 0 1 2 3 4
}
클로저는 가비지 컬렉터
의 수거 대상 이 되지 않는다. 생성 및 메모리에 할당된 후 사용되지 않을 때 제거되지 않고 계속 남아 있음으로서 메모리 누수
가 발생하게 된다.
이 때문에 클로저 사용을 지양하는 사람도 있지만 클로저의 **참조 카운트**
를 **0**
으로 만들어만 준다면 가비지 컬렉터의 대상이 되고 제거됨으로서 메모리 누수가 아니게 된다.
*메모리 누수
란 개발자의 의도와 달리 어떤 값의 참조 카운트가 0이 되지 않아 가비지 컬렉터의 수거 대상이 되지않아 사용하지 않는데 데이터가 메모리 속 남아있는 것을 의미한다.*
// 클로저를 가비지 컬랙터의 수거 대상으로 만들기 (참조 카운트 0 만들기)
let outter4 = () => {
let a = 1; // inner의 참조 대상이기 때문에 메모리에 계속 저장된 상태로 남아있게 된다.
return () => {
return ++a;
};
};
let cs1 = outter4();
// outter는 호출이 된 후 소멸되었지만 a라는 outter의 전역 변수는
// inner함수가 참조해야 함으로 메모리에 저장이 되어 진다.
console.log(cs1()); // 결과: 2
// 이후 a 변수에 대한 메모리는 사용이 되지 않더라도 메모리에 남아있는 상황
cs1 = null; // outer 식별자의 inner 함수 참조를 끊음
// console.log(cs1()); // not a function
console.log(cs1); // null < 참조 카운트가 0 인 상태가 되었다
함수 vs 메서드
function foo() {
return 100;
}
또한 위의 코드는 아래와도 같다.
const foo = function() {
return 100;
}
함수는 값이 될 수 있다는 특성을 가진다.
위의 함수 처럼 변수 foo에 함수가 담겨있다라고 볼 수 있다.
이를 **함수**라고 한다
함수는 객체의 값으로 포함될 수도 있다.
이렇게 객체의 속성값으로 담겨진 함수를 **메소드**라고 한다.
const bar = {
fbi: function() {
return 500;
}
}
즉 메소드는 **객체의 속성값으로 담겨진 함수**를 부르는 명칭이다.
기본적으로 for문과 동일하다.
하지만 for문은 break를 통해 빠져나올 수 있는 특성을 가졌지만 forEach는 배열의 사이즈만큼 모두 순회하는 기능만 가지고 있다.
[1,2,3,4,5].forEach((item) => {
console.log(item); // 출력: 1,2,3,4,5
})
Map또한 for, forEach와 같은 반복문과 같다. 다만 Map은 새로운 배열(특정 필드)을 반환을 해주는 메소드이다.
const a = [1,2,3,4,5];
const b = a.map((item) => {
return item + item;
})
console.log(b); // 출력: [2,4,6,8,10]
Filter도 위의 반복문과 같은 반복문이지만 조건에 맞는 모든 요소들을 보아 새로운 배열을 반환 해주는 메소드이다.
const a = [1,2,3,4,5];
const b = a.filter((item) => {
return item%2 === 0;
});
console.log(b); // 출력: [2,4]
이역시 같은 반복문 이지만 데이터를 순회하면서 값을 연산하거나 축약하고 새로운 데이터를 반환 해주는 메소드이다.
const a = [1,2,3,4,5];
const b = a.reduce(function(
// reduce의 정해진 4개의 매개변수
accumulator: a, // 누적 되는 매개 변수
currentValue: b, // 현재 순환하는 인덱스의 값
currentIndex: c, // 현재 순환하는 인덱스
array: d // 호출한 배열을 가르킴
) {
if(c === d.length - 1) {
return ( a + b ) / d.length;
} else {
return a + b;
}
})
console.log(b); // 출력: 3
해당 구간 인덱스의 요소를 다른 요소로 바꾸거나 삭제하고 새로운 배열을 반환한다.
const a = [1,2,3,4,5];
const b = a.splice(0,2); // a의 배열에서 0에서 부터 1번과 2번째 데이터를 삭제 및 할당
console.log(b); // [1,2]
console.log(a); // [3,4,5] >>>>>>>> b에 splice되어진 a의 값을 할당해주었으나 부모인 a에게도 영향이 가는것을 볼 수 있다.
// 의도된 splice의 사용이라면 좋은 코드지만 의도하지 않았다면 큰 문제를 발생시킬 수 있는 메서드가 될 수 있다고 생각한다.
splice와는 다르게 해당 구간 인덱스만을 가지는 새로운 배열을 반환한다.
splice는 해당 구간의 index를 삭제하지만 slice에선 그대로 유지가 된다.
const a = [1, 2, 3, 4, 5];
const b = a.slice(1, 3);
// splice와 다르게 해당 구간의 end 요소가 -1 부분까지만 가진다.
// a의 배열에서 1번째 index부터 3 - 1개의 데이터를 할당
console.log(b); // 출력: [2,3]
console.log(a); // 출력: [1,2,3,4,5]
* shift() 배열의 첫번째 요소 제거
[1,2,3,4,5].shift(); // 출력: [2,3,4,5]
* pop() 배열의 마지막 요소 제거
[1,2,3,4,5].shift(); // 출력: [1,2,3,4]
* unshift() 배열의 첫번째 요소에 데이터 추가
[1,2,3,4,5].unshift(6); // 출력: [6,1,2,3,4,5]
* push() 배열의 마지막 요소에 데이터 추가
[1,2,3,4,5].push(7); // 출력: [1,2,3,4,5,7]
메서드의 인자값으로 넘어오는 데이터 값의 해당 index를 알려준다
const a = ['a', 'b', 'c', 'd'];
console.log(a.indexOf('c')); // 3
배열에서 조건에 맞는 값의 index를 알려준다. findIndex의 인자의 조건은 콜백함수여야 한다.
const a = [
{ name : '호랑이' },
{ name : '사자' },
{ name : '고양이' },
{ name : '멍멍이' }
]
console.log(a.findIndex(ary => ary.name === '고양이')); // 2
배열에서 조건에 맞는 값의 데이터를 알려준다. findIndex와는 index, 데이터 각자 리턴하는 데이터가 다르다는 차이가 있다.
const a = [
{ name : '호랑이' },
{ name : '사자' },
{ name : '고양이' },
{ name : '멍멍이' }
]
console.log(a.find(ary => ary.name === '고양이')); // {name: '고양이'}
배열을 문자열로 리턴하는데 메서드의 인자로 넘겨준 값으로 각 요소 사이에 구분을 둘 수 있다.
const a = [1,2,3,4,5];
console.log(a.join()); // "1,2,3,4,5" >>>> 인자가 없으면 ','가 기준이 된다.
console.log(a.join('-')); // "1-2-3-4-5"