-
[TIL] 스파르타) JS문법 종합반 2-4주차 (4-8까지) 수강TIL-sparta 2024. 4. 23. 20:38
--
학습 키워드: ECMAScript 6 (ES6), spread operator, callback function, scope chaining, call stack, this, arrow function
1. ES6의 Spread Operator (...)
- 본격적으로 강의를 수강하면서 예전 개인 프로젝트를 하며 독학하던 내용 중 애매하게 알던 부분들을 좀 더 명확하게 알 수 있었다. 아래는 ... operator (aka. spread operator)를 이용한 spread와 rest로, 기능 자체는 직관적이다.
// spread in arrays let arr1 = ['a', 'b', 'c']; // ['a', 'b', 'c'] let arr2 = [...arr1, 'd']; // ['a', 'b', 'c', 'd'] // declaration using spread operator (rest) const [first, ...arr3] = arr1; // first --> a // arr3 --> ['b', 'c'] // rest as parameter function sumDividedByFirst (divisor, ...arr4) { // parameter 내 ...arr4는 나머지(rest)이므로 항상 맨 뒤에 위치 let ret = arr4.reduce((acc, curr) => { return acc + curr; }, 0); return (ret + divisor) / divisor; } console.log(sumDividedByFirst(1,2,4,9)); // 16 / 2 = 8 // rest applied to key value pair let profile = { firstname:"don", lastname:"kim", locale:"ko-kr" } const {locale, ...fullname} = profile; // locale --> ko-kr // fullname --> {firstname:"don", lastname:"kim"}
- 한 가지 유의할 점은, key value pair의 객체 생성에 spread operator를 사용할 경우 내용물을 입력하는 순서가 중요하다는 것이다.
var user = { name: "john", age: 20, } var getAged = function (user, passedTime) { // 여기를 작성해 주세요! return {...user, age:user.age + passedTime}; // {age:user.age + passedTime, ...user} // 이렇게 작성하면 앞의 age가 user의 age로 덮어쓰여진다. }
2. this 의 binding과 scope chaining
- 프로그래밍을 처음 배우고부터 수도 없이 많이 사용해온 this라서 사용 방법은 잘 알지만, 개념 정리를 확실하게 하기 위해서 자바스크립트에서는 어떻게 동작하는지 적어보았다.
- JS에서 this는 this가 선언된 위치가 function이냐 method냐에 따라 갈린다고 한다. 일반적으로 어떤 객체에 종속되는 함수인 method의 경우 this가 해당 객체를 가리키고, 그 외 자체적으로 선언되는 function의 경우 대체로 전역 this, 즉, 브라우저의 window 객체 혹은 node 서버의 global 객체를 가리킨다.
- Method 속의 Callback Function 이라면?: 기본적으로 callback fn의 this 또한 모두 전역 객체를 바라보게 되어있으나, 경우에 따라 callback을 호출한 method가 가리키는 객체를 this로 삼을 때도 있다(예시: addEventListener 의 callback에서 this는 addEventListener의 주체, 즉, 해당 method를 호출한 객체가 button element라면 button element를 대상으로 한다). Method가 어떤 식으로 짜여져 있느냐에 따라 다를 수 있다고 하니 사용할 때 this가 내가 원하는 대상을 맞게 가리키고 있는지 주의깊게 확인하기로 한다.
- Arrow Function 일 때: 일반적인 function () {} 의 형태로 함수를 작성하면 해당 함수의 this는 전역 객체를 바라보도록 설정된다. 하지만 arrow function을 사용하여 작성하는 경우 this의 binding을 무시하여 this가 이전 scope의 this를 바라보게 된다고 한다. 이게 나에게 익숙한 this의 방식인 것 같은데 이 부분은 확실하게 하기 위해 따로 테스트가 필요할 것 같다.
3. call, apply, bind
- 2번과 이어지는 부분인데, 어딘가 낯익은 bind() 가 나타났다. 예전에 MERN (각각 MongoDB, Express, React, Node로 앞선 TIL들에서 언급하던 '옛날 프로젝트'와 동일한 프로젝트) Stack 을 사용한 프로젝트의 서버쪽을 개발하면서 공부도 할 겸 callback이 들어가는 함수를 여러개 작성하느라 며칠 고전했던 기억이 있는데, 그 때 사용해 본 것이 분명하다.
- 일단 셋 모두 첫 번째 parameter를 통해 함수 내 this의 context를 임의로 지정해 줄 수 있는 기능을 갖추고있다.
- call(): 앞서 말했듯이 첫 번째 argument에 해당 함수에서 this로 지정될 객체를 지정하고 그 뒤로 기존 그 함수의 파라미터에 맞는 argument들을 넘겨주면 된다.
let bookcase = []; function addBook (title, publishedDate) { this.push({"title":title, "publishedDate":publishedDate}); } addBook.call(bookcase, "some title", "some-da-te"); addBook.call(bookcase, "some title2", "some-d@-te"); // addBook("s0me title3", "s0me-d@-te"); // Uncaught TypeError: this.push is not a function // addBook의 this가 전역 객체를 바라보고 있으며 // 브라우저의 전역 객체인 window 혹은 Node의 global 에는 push 라는 method가 없기 때문 console.log(...bookcase); // { "title": "some title", "publishedDate": "some-da-te" } // { "title": "some title2", "publishedDate": "some-d@-te" }
- apply(): call과 동일하지만 첫 번쨰 파라미터 뒤의 argument 값을 array로 전달할 때 사용된다.
- bind(): 앞선 둘과는 다른 방식인데, call과 apply가 호출 즉시 함수를 실행시킨다면, bind는 함수에 미리 적용 시켜두고 나중에 호출할 수 있게 하는 방식이다.
function addBoundNum(num) { return this + num; // 'this' is global object by default } // bound function const addFive = addBoundNum.bind(5, 3); // won't run the function here; console.log(addFive()); // bound function 실행
- 실제로 위처럼 단순한 구조에 사용할 일은 없겠지만, 보다시피 bind와 call/apply의 주요 차이점인 실행 타이밍이 확연하게 나타난다.
4. Callback Function
- JS를 독학으로 공부하던 때 callback function을 이해하는데 시간이 좀 걸렸었다. 작성한 함수 안에 인자로 함수가 들어가는 구조인데, 이 callback의 구현을 나중에 내 function을 호출할 때 callback의 내용을 구현하는 방식으로 이루어진다.
Array.prototype.map123 = function (cb, args) { let mappedArr = {}; for (let i = 0; i < this.length; i++) { let mappedValue = cb.call(args || this, this[i]); mappedArr[i] = mappedValue; } return mappedArr; } let newArr = [1, 2, 3].map123(function (num) { console.log(this); // this = [5, 6, 7] return num * 2; }, [5, 6, 7]); console.log(newArr);
- 사실 알고보면 어려울게 하나도 없다. 그저 인자가 함수일 뿐이고 콜백이라는 함수의 구현이 호출 당시에 결정된다는 점이 다를 뿐이다. 위의 map123은 Array의 map처럼 기능하도록 구현한 함수인데, 말로 표현하면 "map123() 이 callback 함수를 인자로 받고, Array의 매 index마다 cb(function 값을 argument로 받게 될 parameter)에서 어떤 연산을 한다고 가정하고, 그 연산의 return 값인 mappedValue를 mappedArr에 넣어주고 최종적으로 mappedArr를 반환하겠다"는 정도의 뜻이 된다.
- 아래쪽의 newArr에서 이 callback 함수의 구현이 이루어졌는데, 여기서 주목할 점은 콜백 함수 argument 뒤에 [5, 6, 7] 이라는 argument를 추가해주면 이 인자가 map123()의 args에 해당하는 부분이 되고, cb.call(args 어쩌고) 부분으로 인해 콜백의 this 가 [5, 6, 7] 로 바뀐다는 점이다. 보여주기 식으로 넣어둔 값이고, callback 뒤에 아무런 argument를 추가하지 않으면 cb.call 의 args || this 의 논리에 따라 map123의 this인 array(여기서는 [1, 2, 3])가 callback의 this로 지정된다.
let obj = { vals: [1, 2, 3], logValues: function (v, i) { if (this === globalThis) { console.log("this is globalThis"); } else { console.log("this is not globalThis"); } }, }; [4, 5, 6].forEach(obj.logValues); // this is globalThis // [4, 5, 6].forEach(obj.logValues(1,2)); // Uncaught TypeError: undefined is not a function
- 다른 헷갈릴만한 점은 위처럼 forEach 등의 callback 함수로서 작용하도록 logValues를 사용할 때 인데, 이 때는 아래처럼 obj.logValues에 괄호를 넣지 않는다. forEach의 첫 번째 인자는 callback function이기 때문에 logValues의 결과 값이 되는 logValues() 가 아닌 logValues라는 함수 자체가 필요하기 때문이다.
728x90'TIL-sparta' 카테고리의 다른 글
[TIL] 스파르타) SQL (SELECT) 강의 2~5주차 수강 (2) 2024.04.25 [TIL] 스파르타) JS/SQL 강의 수강, TMDB 프로젝트 수정 (0) 2024.04.24 [TIL] 스파르타) Javascript 문법 종합반 1주차 수강, TMDB 개인 과제 완료 (0) 2024.04.22 [TIL] 스파르타) 웹개발 종합반 완강, MySQL Query SELECT 연습 01 (0) 2024.04.21 [TIL] Javascript의 Map과 WeakMap (0) 2024.04.20