JS - ES6 필기노트

Asher Kim·2021년 11월 29일
0

일급 시민 / 객체 / 함수

  • 일급 시민
  1. 변수에 담을 수 있다.
  2. 함수(혹은 메소드)의 인자(매개변수, Parameter)로 전달할 수 있다.
  3. 함수(혹은 메소드)의 반환값(return)으로 전달할 수 있다.
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

*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'
  • 은닉화 외부에서 내부의 데이터에 대한 접근을 금지하며, getter, setter 로서 간접적으로 변수의 접근을 제어하는것을 의미 private 과는 다른 의미를 가진다(캡슐화에 해당)
  • 흔히하는 실수
// 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;
	}
}

즉 메소드는 **객체의 속성값으로 담겨진 함수**를 부르는 명칭이다.

JS 배열 내장함수

  • forEach

기본적으로 for문과 동일하다.

하지만 for문은 break를 통해 빠져나올 수 있는 특성을 가졌지만 forEach는 배열의 사이즈만큼 모두 순회하는 기능만 가지고 있다.

[1,2,3,4,5].forEach((item) => {
	console.log(item); // 출력: 1,2,3,4,5
})
  • Map

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

Filter도 위의 반복문과 같은 반복문이지만 조건에 맞는 모든 요소들을 보아 새로운 배열을 반환 해주는 메소드이다.

const a = [1,2,3,4,5];
const b = a.filter((item) => {
	return item%2 === 0;
});

console.log(b); // 출력: [2,4]
  • reduce

이역시 같은 반복문 이지만 데이터를 순회하면서 값을 연산하거나 축약하고 새로운 데이터를 반환 해주는 메소드이다.

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
  • splice

해당 구간 인덱스의 요소를 다른 요소로 바꾸거나 삭제하고 새로운 배열을 반환한다.

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의 사용이라면 좋은 코드지만 의도하지 않았다면 큰 문제를 발생시킬 수 있는 메서드가 될 수 있다고 생각한다.
  • slice

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 pop unshift push
* 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]
  • indexOf

메서드의 인자값으로 넘어오는 데이터 값의 해당 index를 알려준다

const a = ['a', 'b', 'c', 'd'];
console.log(a.indexOf('c')); // 3
  • findeIndex

배열에서 조건에 맞는 값의 index를 알려준다. findIndex의 인자의 조건은 콜백함수여야 한다.

const a = [
  { name : '호랑이' },
  { name : '사자' },
  { name : '고양이' },
  { name : '멍멍이' }
]
console.log(a.findIndex(ary => ary.name === '고양이')); // 2
  • find

배열에서 조건에 맞는 값의 데이터를 알려준다. findIndex와는 index, 데이터 각자 리턴하는 데이터가 다르다는 차이가 있다.

const a = [
  { name : '호랑이' },
  { name : '사자' },
  { name : '고양이' },
  { name : '멍멍이' }
]
console.log(a.find(ary => ary.name === '고양이')); // {name: '고양이'}
  • join

배열을 문자열로 리턴하는데 메서드의 인자로 넘겨준 값으로 각 요소 사이에 구분을 둘 수 있다.

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"

번외

  • es6 기초 문법 공부 파일

https://github.com/asherkuu/javascript-es6

profile
공부 기록 일지 작성하기 프로젝트 🤪

0개의 댓글