필자의 주관이 들어가 있다.
큰 문제는 일어나지 않겠지만...
parameter의 scope는 다음과 같은 경우 약간 달라질 수 있다.
non-simple parameter forms(parameters with default values, rest parameters (using ...), and destructured parameters)
//1.
// outer/global scope: RED(1)
function getStudentName(/*BLUE(2)*/ studentID = 0) {
// function scope: GREEN(3)
// ..
}
//with function expression and CLOSURE
function whatsTheDealHere(id,defaultID = () => id) {
id = 5;
//case2: var id = 5; // shadow id!
console.log( defaultID() );
}
whatsTheDealHere(3);
// 5
//case2: 3
//Huh
function whatsTheDealHere(id,defaultID = () => id) {
var id;
console.log(`local variable 'id': ${ id }`);
console.log(
`parameter 'id' (closure): ${ defaultID() }`
);
console.log("reassigning 'id' to 5");
id = 5;
console.log(`local variable 'id': ${ id }`);
console.log(
`parameter 'id' (closure): ${ defaultID() }`
);
}
whatsTheDealHere(3);
// local variable 'id': 3 <--- Huh!? Weird!
//id에 undefined 아닌 3
// parameter 'id' (closure): 3
// reassigning 'id' to 5
// local variable 'id': 5
// parameter 'id' (closure): 3
위의 이상한 상황을 방지하기 위해 지역변수로 shadow하지 말고, parameter로 클로즈 오버하는 defulat parameter function을 지양하자.
var askQuestion = function ofTheTeacher(){
// why is this not a duplicate declaration error?
let ofTheTeacher = "Confused, yet?";
};
ofTheTeacher 식별자가 함수 내부로 간다고 했지만 사실은 바깥 스코프와 main 함수 스코프 중간의 implied scope에 있다.
함수 이름과 관련하여
inferred names 추론된 이름도 stack traces에서 보여지긴 하나..
var notNamed = function(){
// ..
};
var config = {
cb: function(){
// ..
}
};
notNamed.name;
// notNamed
config.cb.name;
// cb
function ajax(url,cb) {
console.log(cb.name);
}
ajax("some.url",function(){
// ..
});
// ""
//?.. 익명함수로 넣어줘서 그런거 아님?....... 일단.. keep goin..
//다음과 같이 할당을 할 경우에는 추론 실패
var config = {};
config.cb = function(){
// ..
};
config.cb.name;
// ""
var [ noName ] = [ function(){} ];
noName.name
// ""
또한 function 선언식 처럼 lexical name identifier가 없으면 자기 자신을 가리킬 수 없다.
(recursion, event handling)
// broken
runOperation(function(num){
if (num <= 1) return 1;
return num * oopsNoNameToCall(num - 1);
});
// also broken
btn.addEventListener("click",function(){
console.log("should only respond to one click!");
btn.removeEventListener("click",oopsNoNameHere);
});
이름에 목적을 잘 드러내자. 심지어 한 줄 짜리어도!(In map, then)
lookupTheRecords(someData)
.then(function extractSalesRecords(resp){
return resp.allSales;
})
.then(storeRecords);
arrow function의 목적은 lexical this 동작이다. 즉, 화살표 함수에서의 this는 다른 변수 참조와 비슷하게 작동한다(없을 시 바깥으로 가는 것을 말하는 듯). var self = this
나 .bind(this)
를 이용해 바깥 함수에서의 this를 계승하기를 원한다면 화살표 함수를 써라. 유의해야할 점은 화살표 함수는 기본적으로 inferred name을 갖는다.
여기서도 이름을 써주자. 그리고 IIFE의 첫번째 괄호는 그저 JS parser에 function 키워드를 함수 선언으로 인식하지 않게 하기 위함이라 이것을 대체할 방법들이 있다. 함수 앞에 !, +, ~
등 단항 연산자를 붙여서 만들 수 있고, void를 앞에 붙여서 standalone IIFE를 정의할 수 있다.
void를 쓰면 값이 반환되지 않을 것이라고 명백히 할 수 있다.
호이스팅의 이점을 살펴보자(주관적).
// dependencies
var aModuleINeed = require("very-helpful");
var anotherModule = require("kinda-helpful");
// public API
var publicAPI = Object.assign(module.exports,{
getStudents,
addStudents,
// ..
});
// ********************************
// private implementation below cache, otherData
var cache = { };
var otherData = [ ];
function getStudents() {
// ..
}
function addStudents() {
// ..
}
간혹 exported public API 전에 변수의 값을 둬야할 필요가 있다.
cache = {}; // used here, but declared below
// public API
var publicAPI = Object.assign(module.exports,{
getStudents,
addStudents,
refreshData: refreshData.bind(null,cache)
});
// ********************************
// private implementation
var cache /* = {}*/;
const는 primitive에만 쓰자.
var는 함수 최상위 레벨 영역(시작부 등 무관) 혹은 전역.
let은 블록 단위, 지역적으로 쓰자.
또 var를 활용.... do...while,
function getStudents() {
try {
// not really a block scope
var records = fromCache("students");
}
catch (err) {
// oops, fall back to a default
var records = [];
}
// ..
}
function getStudents() {
var data = [];
// do something with data
// .. 50 more lines of code ..
// purely an annotation to remind us
var data;
// use data again
// ..
}
(I)콜백 비동기 동기 잘 모름.
비동기 콜백은 나중에 호출되는 함수 참조로, 여기서의 콜백은 완료, 일시정지, 자체적으로 연기되었다가 나중에 호출 시 돌아가 재개한다는 의미다.
setTimeout(function waitForASecond(){
// this is where JS should call back into
// the program when the timer has elapsed
},1000);
// this is where the current program finishes
// or suspends
calling back이 다양한 의미로 쓰였지만 우선 특정 위치로 돌아간다는 의미로 콜백은 비동기다.
function getLabels(studentIDs) {
return studentIDs.map(
function formatIDLabel(id){
return `Student ID: ${
String(id).padStart(6)
}`;
}
);
}
getLabels([ 14, 73, 112, 6 ]);
// [
// "Student ID: 000014",
// "Student ID: 000073",
// "Student ID: 000112",
// "Student ID: 000006"
// ]
프로그램이 일시정지되거나 종료된적이 없기 때문에 callback이 없다.
Dependency Injection (DI) or Inversion of Control (IoC)이라고 생각할 수 있겠다.
여기서 DI는 일을 완성하기 위해서 필수적인 부분을 다른 곳으로 옮기는 것(물려준다고 봐도 무방할 듯). map => formatIDLabel
IoC는 control 관점으로 본 것. formatDLabel의 호출 control을 map에 준 것.
이를 framework와 library의 차이점을 설명하기 위해 적용할 수 있는데.
라이브러리의 함수는 내가 호출하는 것. framework는 이것이 나의 함수를 호출.
필자의 제안. 동기 콜백을 inter-invoked functions (IIFs)라고 하자. 그럼 비동기 콜백은 비동기식으로 호출되는 IIF.
function printLabels(labels) {
var list = document.getElementByID("labelsList");
labels.forEach(
function renderLabel(label){
var li = document.createELement("li");
li.innerText = label;
list.appendChild(li);
}
);
}
function printLabels(labels) {
var list = document.getElementByID("labelsList");
for (let label of labels) {
// just a normal function call in its own
// scope, right? That's not really closure!
renderLabel(label);
}
// **************
function renderLabel(label) {
var li = document.createELement("li");
li.innerText = label;
list.appendChild(li);
}
}
당연히 아님.
function printLabels(labels) {
var list = document.getElementByID("labelsList");
var renderLabel = renderTo(list);//클로저 맞지.
// definitely closure this time!
labels.forEach( renderLabel );
// **************
function renderTo(list) {
return function createLabel(label){
var li = document.createELement("li");
li.innerText = label;
list.appendChild(li);
};
}
}
var StudentList = (function defineModule(Student){
var elems = [];
var publicAPI = {
renderList() {
// ..
}
};
return publicAPI;
})(Student);//계승으로 다른 모듈.
대부분 클래식 모듈은 원래 publicAPI를 사용하지 않고 객체를 직접 반환.
AMD는 클래식 모듈의 다른 형태
RequireJS.
define([ "./Student" ],function StudentList(Student){
var elems = [];
return {
renderList() {
// ..
}
};
});
덜 구체적이고 정확한 format. 유사한 format들의 모음. 브라우저(AMD식 로더)나 노드에서 빌드 도구 변환없이 모듈에 대해 더 나은 interop을 만들 수 있도록 설계되어 있다. 그냥 IIFE.
(function UMD(name,context,definition){
// loaded by an AMD-style loader?
if (
typeof define === "function" &&
define.amd
) {
define(definition);
}
// in Node?
else if (
typeof module !== "undefined" &&
module.exports
) {
module.exports = definition(name,context);
}
// assume standalone browser script
else {
context[name] = definition(name,context);
}
})("StudentList",this,function DEF(name,context){
var elems = [];
return {
renderList() {
// ..
}
};
});
if..else를 통해 세가지 환경을 구분한다.