Translated from:
https://ultimatecourses.com/blog/everything-you-wanted-to-know-about-javascript-scope
var이 신경쓰인다면? var과 let 차이점 참고:
https://www.freecodecamp.org/news/var-let-and-const-whats-the-difference/
// Scope A
var myFunction = function () {
// Scope B
var myOtherFunction = function () {
// Scope C
};
};
함수 안에 다른 함수가 존재할 때, 그 내부 함수는 바깥 함수에 접근 권한을 가지는데 이것을 Lexical Scope, 또는 Closure, Static Scope이라 한다.
// Scope A
var myFunction = function () {
// Scope B
var name = 'Todd'; // defined in Scope B
var myOtherFunction = function () {
// Scope C: `name` is accessible here!
};
};
myOtherFunction
은 호출되지 않고 단순하게 정의만 되어있다. 이것의 순서 또한 각 영역의 변수들에게 영향이 있는데, 아래 새 함수를 정의하고 호출해보자.
var myFunction = function () {
var name = 'Todd';
var myOtherFunction = function () {
console.log('My name is ' + name);
};
console.log(name);
myOtherFunction(); // call function
};
// Will then log out:
// `Todd`
// `My name is Todd`
Lexical scope은 편리하다. 그 어떠한 변수/object/함수라도 그것의 부모 scope안에 정의되어있다면 scope chain에서 활용 가능하다.
var name = 'Todd';
var scope1 = function () {
// name is available here
var scope2 = function () {
// name is available here too
var scope3 = function () {
// name is also available here!
};
};
};
기억해야 할 것은 Lexical scope은 역방향으로는 통하지 않는다는 것이다. 예를 들어보자.
// name = undefined
var scope1 = function () {
// name = undefined
var scope2 = function () {
// name = undefined
var scope3 = function () {
var name = 'Todd'; // locally scoped
};
};
};
이 경우에서 name
은 참조를 return할 수 있지만 변수 자체를 return하는 것은 불가능하다.
Scope chain이란 주어진 함수의 scope를 결정하는 것이다. 우리가 알다시피 각 함수는 자신만의 영역이 있고, 한 함수 내부에 정의된 함수는 바깥 함수와 연결된 local scope를 가지는데 이 연결을 바로 chain이라고 부른다. scope을 결정하는 것은 언제나 위치 이다. 변수를 resolve할 때 자바스크립트는 항상 가장 안쪽의 scope에서부터 시작해서 바깥쪽으로 뻗어나가며 목표하는 변수/object/function을 찾는다.
Closures는 Lexical scope와 밀접한 연관성이 있다. closure의 원리가 어떻게 풀리는지의 좋은 예는 바로 function reference를 return할 때 나타난다. parent scope에서 활용 가능할 수 있도록 scope내에서 return 기능을 사용하는 것은 바람직하다.
var sayHello = function (name) {
var text = 'Hello, ' + name;
return function () {
console.log(text);
};
};
여기서는 closure
의 개념을 이용하여 sayHello
내의 scope을 public space에서 접근하지 못하도록 했다. 함수를 호출하는것은 return 하는것과 같은 결과를 불러오지 못한다.
sayHello('Todd'); // nothing happens, no errors, just silence...
이 함수는 함수를 return한다. 따라서 assignment가 먼저 이루어져야하고, 그 다음에 호출을 해야한다.
var helloTodd = sayHello('Todd');
helloTodd(); // will call the closure and log 'Hello, Todd'
사실 다음처럼 호출을 먼저 할 수 있기도 하지만 closure를 호출하게 될 것이다. (뭔솔?)
sayHello('Bob')(); // calls the returned function without assignment
this
의 디폴트 값은 가장 바깥영역의 global object인 window
다. this
는 정의되는 함수에 따라 다양한 방식으로 바인딩 될 수 있다.
var myFunction = function () {
console.log(this); // this = global, [object Window]
};
myFunction();
var myObject = {};
myObject.myMethod = function () {
console.log(this); // this = Object { myObject }
};
var nav = document.querySelector('.nav'); // <nav class="nav">
var toggleNav = function () {
console.log(this); // this = <nav> element
};
nav.addEventListener('click', toggleNav, false);
this
를 다루다 보면 다음과 같은 문제를 맞닥뜨리게 되는데, 이렇게 되면 같은 함수 안에 있다고 해도 scope가 변경될 수도 있으며 this
의 값도 바뀌게 된다.
var nav = document.querySelector('.nav'); // <nav class="nav">
var toggleNav = function () {
console.log(this); // <nav> element
setTimeout(function () {
console.log(this); // [object Window]
}, 1000);
};
nav.addEventListener('click', toggleNav, false);
여기서 우리는 이벤트 핸들러와 관련이 없는 새로운 scope를 생성했으며 따라서 이것의 디폴트 값은 window
로 설정되어 있다.
만약 우리가 새로운 scope 생성에 영향을 받지 않는 this
값에 접근하고자 한다면 다음과 같은 방법을 써야 한다. 그것은 this
값에 대한 참조를 that
변수를 사용해 은닉하고 lexical binding을 적용하는 것이다.
var nav = document.querySelector('.nav'); // <nav class="nav">
var toggleNav = function () {
var that = this;
console.log(that); // <nav> element
setTimeout(function () {
console.log(that); // <nav> element
}, 1000);
};
nav.addEventListener('click', toggleNav, false);
이 메소드들은 scope을 함수에 전달하여 매치되는 this
값을 바인드한다.
var links = document.querySelectorAll('nav li');
for (var i = 0; i < links.length; i++) {
(function () {
console.log(this);
}).call(links[i]);
}
위의 코드에서는 link[i]
가 루프의 변수가 되어 함수의 scope를 바꿔주며 this
값에 변화를 주고 있다. scope를 바꿔주고 싶으면 .call()
, .apply()
둘 다 쓸 수 있다. 이 둘의 차이점은 .call(scope, arg1, arg2, arg3)
은 ,
로 구분된 독립적인 argument를 요한다는 것이고, .apply(scope, [arg1, arg2])
는 배열을 argument로 요구한다는 것이다.
.call()
, .apply()
를 사용하면 함수를 실제로 호출하게 된다는 것을 기억해야 한다.
myFunction(); // invoke myFunction
이렇게 하기 보다는, .call()
에게 주도권을 넘겨주고 메소드를 chain하는 것이 좋다.
myFunction.call(scope); // invoke myFunction using .call()
.bind()
는 함수를 실행한다기 보다는 함수 실행 전에 값을 바인딩하는 역할을 한다.
// works
nav.addEventListener('click', toggleNav, false);
// will invoke the function immediately
nav.addEventListener('click', toggleNav(arg1, arg2), false);
function reference에는 변수를 넣을 수 없다. 이 문제는 다음과 같이 새로운 함수를 생성하여 해결할 수 있다.
nav.addEventListener('click', function () {
toggleNav(arg1, arg2);
}, false);
그러나 이렇게 함으로써 scope는 변경되고 우리는 또 다시 쓸모없는 함수를 생성한 것이 되고 만다. 이 때 .bind()
를 쓰면 쓸모없는 함수를 생성하지 않고도 인수를 넣을 수 있게 된다.
nav.addEventListener('click', toggleNav.bind(scope, arg1, arg2), false);
자바스크립트에는 다른 언어들 처럼 public
이나 private
scope에 대한 개념이 없다. 대신 closure
를 이용해야 한다.
Module
패턴을 이용하면 public
이나 private
scope을 만들 수 있다. 함수를 함수로 감싸게 되면 손쉽게 private
scope을 만들어낼 수 있는데, 위에서 언급했듯이 함수를 만들면 scope이 생성되고, 해당 영역은 global scope으로부터 보호받게 되기 때문이다.
(function () {
// private scope inside here
})();
이제 이 함수에 기능을 조금 추가해보자.
(function () {
var myFunction = function () {
// do some stuff here
};
})();
그러나 이후 이 함수를 호출하면 scope의 문제가 발생한다.
(function () {
var myFunction = function () {
// do some stuff here
};
})();
myFunction(); // Uncaught ReferenceError: myFunction is not defined
성공이다! 우리는 성공적으로 private scope을 만들어 냈다. 그러나 만약 내가 이 함수가 public하길 원한다면 어떻게 해야 할까?
Module
은 private & publice scope, Object
를 이용하여 함수의 scope을 적절하게 사용할 수 있게 한다.
// define module
var Module = (function () {
return {
myMethod: function () {
console.log('myMethod has been called.');
}
};
})();
// call module + methods
Module.myMethod();
public
메소드는 return
구문을 통해 리턴되어 전역에서 접근이 가능하게 되었으나 동시에 namespace
되었다. 이 모듈은 원하는 만큼 확장이 가능하다.
// define module
var Module = (function () {
return {
myMethod: function () {
},
someOtherMethod: function () {
}
};
})();
// call module + methods
Module.myMethod();
Module.someOtherMethod();
그렇다면 private 메소드는 어떻게 만들까? 많은 개발자들은 그들의 함수를 global scope에 쏟아내며 오염시킨다. 전역에서 접근이 가능해야 하는 API를 호출을 제외하면, 우리의 코드를 움직이게 하는 함수들은 전역에 있을 필요가 없다. 우리는 다음과 같이 함수들을 return하지 않음으로써 private scope을 생성할 수 있다.
var Module = (function () {
var privateMethod = function () {
};
return {
publicMethod: function () {
}
};
})();
publicMethod
는 호출이 가능하지만 privateMethod
는 private scope내에 생성이 되었기 때문에 호출할 수 없다. 이들은 helpers, addClass, removeClass, Ajax/XHR calls, Arrays, Objects와 같은 것들이다.
여기서 재밌는 사실은 같은 scope 내에 있으면 함수가 return이 된 후라도 서로 얼마든지 접근이 가능하다는 것이다. 이 말은 public
메소드가 private
메소드에 접근할 수 있다는 뜻이며, 서로 interact할 수 있다는 것이다.
var Module = (function () {
var privateMethod = function () {
};
return {
publicMethod: function () {
// has access to `privateMethod`, we can call it:
// privateMethod();
}
};
})();
이는 강력한 상호작용을 가능하게 하면서 동시에 코드의 보안성도 잡아준다. 자바스크립트의 가장 중요한 부분은 보안이며, 이것은 우리가 함수를 전역 공간에 마음껏 풀어놓지 못하는 이유이기도 하다. 각종 공격에 취약해질 수 있기 때문이다.
var Module = (function () {
var myModule = {};
var privateMethod = function () {
};
myModule.publicMethod = function () {
};
myModule.anotherPublicMethod = function () {
};
return myModule; // returns the Object with public methods
})();
// usage
Module.publicMethod();
private
메소드를 정의할 때에는 '_'를 이름에 넣어 구분 가능하도록 해주는 것이 좋다.
var Module = (function () {
var _privateMethod = function () {
};
var publicMethod = function () {
};
return {
publicMethod: publicMethod,
anotherPublicMethod: anotherPublicMethod
}
})();