LIVID 프론트엔드 스터디를 통해 웹 성능 최적화를 한 달간 진행했고 이제 자바스크립트 코딩 패턴 교제로 한 달간 스터디를 진행하기로 했다. 이 책은 JS의 ES5 문법을 기준으로 쓰였는데 여러모로 JS의 개념을 제대로 이해하는 데 좋다고 느꼈다. 이번 자바스크립트 코딩 패턴을 통해 closure와 this 그리고 module 그리고 prototype에 대해 깊이 알고 싶다. 그리고 회사에 있는 레거시 코드를 보고 리팩토링할 기회로 이어진다면 너무 재밌을 것 같다.
JS에서 함수의 특징은 두 가지가 있다. 가장 중요!!
첫 번째, 일급 객체 라는 것.
우선 일급이 무엇인가? 일급 시민, 일급 함수?
일급이란 다른 요소와 차이가 없다는 것이고 값처럼 쓸 수 있는 것을 의미한다.
특징으론
함수가 일급 객체인 이유에 대해 스터디원과 논의해본 결과 한 문장으로 말하면
인자와 반환값으로 함수를 사용할 수 있기 때문이라는 결론이 낫다.
두 번째, 유효 범위(scope)를 제공한다.
자바스크립트 함수는 객체인데 호출해서 실행시킬 수 있는 특별한 기능이 있는 것으로 알면 될 것 같다.
핵심적인 차이는 이름과 호이스팅이다. 모든 변수는 함수 본문 최상위로 끌어올려지는 특징이 있는데...
function foo() {
return 'global foo'
}
function bar() {
return 'global bar'
}
function hoistMe() {
console.log(foo()) // 'local foo;
console.log(bar())
// var일 경우 TypeError: bar is not a function
// let일 경우 ReferenceError: Cannot access 'bar' before initialization
function foo() {
return 'local foo'
}
var bar = function () {
return 'local bar'
}
}
hoistMe()
hoistMe 함수 내부에 foo의 정의 부분이 호출되는 시점보다 늦어도 'local foo'가 잘 보이는데 bar은 호이스팅이 되지 않는 문제가 있다.
함수선언문 | 함수표현식 |
---|---|
- 변수에 할당할 수 없다. - 함수 이름을 생략할 수 없다. - 런타임 이전에 자바스크립트 엔진에 의해 함수 객체가 먼저 생성된다 - 런타임에서 함수 선언문이 실행되기 이전에 함수를 참조할 수 있으며 호출할 수도 있다. | - 변수에 할당할 수 있다. - 함수 이름을 생략할 수 있다. - 함수 표현식은 변수에 할당되는 값이 함수 리터럴인 문이다. - 함수 호이스팅이 발생하는 것이 아니라 변수 호이스팅이 발생한다. - 함수 표현식으로 정의한 함수는 함수 표현식 이후에 참조, 호출해야 한다. TDZ와 연관이 있다. |
이벤트가 발생했을떄 호출될 콜백 함수의 포인터를 전달하는 것이다.
document.addEventListener("click", console.log, false)
콜백 패턴의 대표적인 window 객체의 API는 setTimeout과 setInterval이다.
함수는 객체이기 떄문에 반환 값으로 사용될 수 있다. 리턴 값으로 데이터의 값이나 배열을 반환할 필요가 없다는 것. 함수를 리턴할 수도 있다고 입력 값에 따라 필요한 함수를 새로 만들 수도 있다.
var setup = function () {
console.log(1)
return function () {
console.log(2)
}
}
var my = setup()
my()
setup()은 반환된 함수를 감싸고 있어서 클로저를 생성한다. 클로저는 반환되는 함수에서는 접근할 수 있지만 코드 외부에서는 접근할 수 없어서 비공개 데이터 저장을 위해 사용할 수 있다.
아래 예제로 클로저를 정확히 이해할 수 있다.
var setup = function () {
var count = 0
return function () {
return(count += 1)
}
}
var next = setup()
next() // 1
next() // 2
next() // 3
주의!! 클로저를 만들기 위해 반환값을 함수로 해야만 만들 수 있는 것은 아니라는 것을 기억하자
Q. useState을 클로저 관점으로 생각해보면 아래와 같은 코드가 맞는 코드인가? 라는 궁금증이 생겼다.
const useState = (initValue) => {
let value = initValue;
const setValue = (newValue) => {
value = newValue
}
return [value, setValue]
}
위에서 정리한대로 반환값이 함수가 아니어도 해당 value를 수정하기 위해선 setValue라는 함수를 통해서만 value를 수정할 수 있다는 점에서 클로저를 만든 것이 맞다고 스터디원들과 결론을 맺었다.
예를 들어
let scareMe = function () {
console.log("once!");
scareMe = function () {
console.log("after once");
}
}
scareMe(); // once!
scareMe(); // after once!
scareMe(); // after once!
scareMe(); // after once!
함수가 선언되자마자 실행되도록 하는 문법이다.
(function () {
console.log('hey')
}()) //hey
(function () {
console.log('hey')
})()// hey
초기화 단계가 완료될 때 까지만 필요한 임시변수들을 담아놓기 위한 방법이 즉시 실행 함수 패턴이다!! 블록 스코프를 임의로 만드는 것.
var getResult = (function(){
var res = 2 + 2;
return function () {
return res
}
}())
getResult()
이 코드를 보고 알게 된 것은 res 값에 접근이 불가하다는 것. 왜 즉시 실행함수 패턴을 사용하는가 찾아보니 전역 스코프가 아닌 독자적인 스코프에 값을 저장하고 싶을 때 사용한다는 것을 알게 됐다.
Init 메소드를 사용한다.
({
maxWidth : 600,
maxHeight: 400,
gimmeMax : function (){
return this.maxWidth + "x" + this.maxHeight
},
init : function (){
console.log(this.gimmeMax())
}
}).init() // '600x400'
즉시 실행 함수 패턴과 마찬가지로 전역 네임스페이스를 보호하는 특징이 있다.
정리해보면 4.5와 4.6은 전역 스코프의 namespace를 침범하지 않으려는 방법임을 기억하자.
최적화 패턴의 일환으로 브라우저 탐지가 전형적인 예라고 한다. 음??! 아직 잘 모르겠다.
예시를 살펴보면
-function createCar(name, brand, color, type) {
+function createCar({ name, brand, color, type }) {
return {
name,
brand,
color,
type
}
}
-createCar('아이오닉5', '현대', 'red', '전기')
+createCar({
+ name : '아이오닉5',
+ brand : '현대',
+ color : 'red',
+ type : '전기'
+})
현재 구조 분해 할당의 개념과 동일하다는 것을 알 수 있었다.
var sayHi = function (who) {
return "Hello" + (who ?"," + who : '') + "!";
}
sayHi() // Hello!
sayHi('world') // Hello, world!
sayHi.apply(null,['hello']) //Hello,hello!
]
위에서 호출과 적용 모두 결과가 동일하다?! 신기하다..
두 개의 매개변수를 받고 첫 번째 인자는 함수 내에 this와 바인딩할 객체, 두 번째는 배열 또는 arguments가 필요하다.
var alien = {
sayHi : function (who) {
return "Hello" + (who ?"," + who : '') + "!";
}
}
alien.sayHi('world') // Hello,world
sayHi.apply(alien,["humans"]) // Hello, humans!
apply와 동일한데 arguments의 매개변수가 하나 일때 배열을 만들지 않아도 되도록 한다.
var alien = {
sayHi : function (who) {
return "Hello" + (who ?"," + who : '') + "!";
}
}
alien.sayHi('world') // Hello,world
sayHi.call(alien,"humans") // Hello, humans!
커링은 함수를 변형하는 과정이다.
function add(x,y) {
var oldx = x, oldy = y;
if(typeof oldy === 'undefined'){
return function (newy){
return oldx + newy
}
}
return x + y
}
typeof add(5)
add(3)(4)
add() 적용이 아닌, 호출할 때, add가 반환하는 내부 함수에 클로저를 만든다. 클로저 (반환 함수 기준 외부 환경)는 x,y 인자를 비공개 변수인 oldx ,oldy로 저장한다. 장황해서 더 간단한 예제로 책에서 소개해준다.
oldx와 oldy가 없고 암묵적으로 x를 클로저에 저장시킨 코드
function add(x,y) {
if(typeof y === 'undefined'){
return function (y){
return x + y
}
}
return x + y
}
typeof add(5)
add(3)(4)
커링을 활용하여 어떤 함수에도 적용되는 방식으로 구현한 로직
function currying (fn) {
var slice = Array.prototype.slice,
stored_args = slice.call(arguments,1)
return function () {
var new_args = slice.call(arguments),
args = stored_args.concat(new_args)
return fn.apply(null,args)
}
}
대부분의 매개 변수가 항상 비슷할 때 사용할 수 있다. 부분적으로 적용해놓고 바뀌는 값만 매개변수로 받을 수 있게 하려고 사용된다.
자바스크립트의 함수에 대해 정리해보는 시간을 가졌다. 자바스크립트에 함수는 일급 객체이기에 다른 데이터 타입과 마찬가지로 값으로 사용할 수 있다. 그 말인 즉 슨 함수의 인자 혹은 반환 값으로 사용할 수 있다는 것이다. 또 다른 특징은 스코프를 제공해준다는 것이다. 이 특징으로 말미암아 과거에는 함수를 선언하여 지역 스코프를 만들어 전역 네임 스페이스를 안전하게 관리하던 시절이 있다는 것을 알게 됐다.