기본적으로 변수를 자바스크립트의 어느 부분에 정의하느냐가
접근(액세스) 지점을 결정한다.
"변수와 함수를 열린 공간에 넣겠습니까? 아니면 함수 안의 중첩 함수에 넣겠습니까?"
이 선택이 나중에 어디서 변수를 쓰느냐에 큰 영향을 미친다.
function collectEggs() {
let totalEggs = 6;
console.log(totalEggs);
}
계란 수집 함수를 만들었다!
function collectEggs() {
let totalEggs = 6;
}
// 함수 바깥
console.log(totalEggs);
함수 밖에서 totalEggs라는 변수를 쓰면 정의가 되어있지 않다는 에러가 뜬다!
왜냐하면 함수 안에서 정의한 변수는 그 함수로 내에서만 유효하다!!
function collectEggs() {
let totalEggs = 6;
}
// 함수 바깥
collectEggs(); // 함수 호출 후
console.log(totalEggs); // 변수를 사용해도 마찬가지
let totalEggs = 0; // 함수 바깥 전역변수로 지정
function collectEggs() {
totalEggs = 6;
}
// 함수 바깥
console.log(totalEggs); // 여긴 아직 totalEggs = 0
collectEggs(); // 함수 호출 후
console.log(totalEggs); // totalEggs = 6 으로 변경된다
이렇게 전역 변수를 이용하면 함수 내부에서 정의한 것이 외부에도 적용이된다.
(하지만 이런 방법이 흔한 일은 아니다 ^^)
이렇듯 함수는 일종의 독립된 기계처럼 입력 값을 취하고 출력 값을 내놓는다.
그것들은 실제 캡처할 수 있는 출력 값을 내놓을 때 외에는(return)
주변 세계에 그다지 영향을 끼치지 않는다!
(1)번 예시
let bird = '금강앵무';
function birdWatch() {
let bird = '왜가리';
}
birdWatch();
console.log(bird); // 금강앵무
당연히 '금강앵무'가 출력이 된다.
(2)번 예시
let bird = '금강앵무';
function birdWatch() {
let bird = '왜가리';
console.log(bird);
}
birdWatch(); // 왜가리
console.log(bird); // 금강앵무
안에서 bird를 출력하면 내부변수인 '왜가리'를 출력한다!
(3)번 예시
let bird = '금강앵무';
function birdWatch() {
// let bird = '왜가리'; // 코메트 아웃 한다면?
console.log(bird);
}
birdWatch(); // 금강앵무
console.log(bird); // 금강앵무
내부에 정의된 변수가 없기 때문에 외부에 정의 되어있는 bird와 접점이 생겨
'금강앵무'를 출력한다!
즉, 함수 내부에서 외부와 같은 이름으로 정의된 변수가 있을 경우
가까운 곳에 있는 내부의 변수를 먼저 참조한다.
기본적으로 블록은 함수를 제외하고는 중괄호가 있는 모든 곳을 가리킨다.
(조건문, 반복문 등)
var 키워드를 쓰면 함수에서는 변수의 범위가 제한되지만, block에선 제한되지 않는다.
for (let i = 0; i < 5; i++) {
var msg = 'ABCDEF';
console.log(msg);
}
console.log(msg);
이 코드를 실행하면 msg 변수가 외부에서도 엑세스 된다.
결과적으로 콘솔창에 (x5) ABCDEF, ABCDEF 모두 출력된다.
심지어..
for (var i = 0; i < 5; i++) { // i를 var로 선언하면..
var msg = 'ABCDEF';
console.log(msg);
}
console.log(i);
외부에서 i를 출력하면 5가 출력된다........
var는 조심해야하고, 그래서 요즘은 let, const를 쓴다.
기본적으로 함수 내부 함수의 실행방법은 아래와 같다.
function 은행강도(){
const 영웅들 = ['스파이더맨','세균맨','호빵맨','원더우먼'];
function 도와줘요히어로() {
for(let 영웅 of 영웅들){ // 부모함수에서 영웅들을 참조했다.
console.log(₩도와주세요, ${영웅}!₩)
} // 내부 for of 문
} // 도와줘요히어로 function
도와줘요히어로(); // 내부함수를 실행해줘야한다!!
} // 은행강도 function
// 함수 외부
은행강도(); // 실행!
실행하면 도와줘요히어로() 함수가 실행돼서
console창에 '도와주세요, ${영웅}!'이 순차적으로 반복해서 출력된다!
도와줘요히어로 함수에서 부모의 변수 '영웅들'을 참조했는데 문제가 없이 잘 실행된다.
이것이 렉시컬 범위의 기본 범위다.
부모 함수 안에 중첩된 내부 함수는 /
해당 외부 함수(부모 함수)의 범위에나 또는 범위 내에서 정의된 변수에 액세스 할 수 있다.
심지어...
function 은행강도(){
const 영웅들 = ['스파이더맨','세균맨','호빵맨','원더우먼'];
function 도와줘요히어로(){
function 안에또함수(){
for(let 영웅 of 영웅들){ // 부모함수에서 영웅들을 참조했다.
console.log(₩도와주세요, ${영웅}!₩)
} // 내부 for of 문
} // 안에또함수 function
안에또함수(); // 내부함수의 내부함수도 실행해줘야겠지?
} // 도와줘요히어로 function
도와줘요히어로(); // 내부함수를 실행해줘야한다!!
} // 은행강도 function
// 함수 외부
은행강도(); // 실행!
위의 코드와 같이 함수 내부함수의 내부함수에서도
조부모함수의 변수를 액세스하여 참조해서 실행이 된다!!
이게 렉시컬 범위 내용의 전부다,
중첩된 함수나 내부 함수는 상위 몇 레벨 위에 있든 상관 없이,
부모 함수나 조부모 함수 등이 액세스하는 동일한 항목에 액세스 할 수 있다.
하지만 분명히하자면 그 반대로는 불가능하다.
위의 코드로 예시를 들자면 '도와줘요히어로 function'에 변수를 정의하면
'안에또함수 function'에서는 사용가능하지만,
부모함수인 '은행강도 function'에서는 사용할 수 없다.
기본적인 함수 선언문(식) -- 엄밀히 말하면 독자적인 구문이다.
function add(x,y){
return x + y;
}
함수 표현식 -- 이름없는 함수를 변수에 저장한다.
const add = function (x,y){
return x + y;
}
add(2,3); // 5
함수 호출도 기존과 다르지 않다.
const PI = 3.14;
const add = function (x, y) {
return x + y;
}
이 예시는 변수에 이름없는 숫자를 지정하는 것 처럼
변수에 이름없는 함수를 지정하는 것이라는 것과.
JavaScrip에서 함수는 값이라는 점을 증명한다
마치 숫자,배열,객체를 저장하고 전달하는 것 처럼..
그래서 함수를 인수로 전달 할 수도 있고..
숫자를 반환하듯이 함수도 반환 값으로 반환할 수도 있다..
다른 함수와 함께 작동하거나 또는 다른 함수에서 작동하는 함수를 고급스럽게 표현한 것
고차함수란 결국
1.다른 함수를 인수로 받아서 그 인수로 어떤 작업을 하는 함수
2.함수를 반환할 수 있는 함수
function callTwice(func) { // 두 번 반복하는 함수
func();
func();
}
function callTenTimes(f) {
for(let i = 0; i < 10; i++){
f();
}
}
function rollDie() { // 주사위를 던져 1~6 중 하나를 출력하는 함수
const roll = Math.floor(Math.random() * 6) + 1;
console.log(roll);
}
callTwice(rollDie); // 함수 호출하면 안되는거 유의하자
callTenTimes(rollDie);
위와 같이 rollDie 함수를 argument(인수)로, 각 함수의 매개변수(인자)로 전달했다.
함수 내에서 함수를 값으로 반환하는 것. 다시 한번 중요한 것은
함수는 값으로 취급돼서 반환도 되고 인수로도 전달된다는 점!!
function 미스테리함수() {
const rand = Math.random();
if(rand > 0.5) {
return function() {
console.log("축하해, 난 좋은 함수야!");
console.log("1억에 당첨됐어!");
}
} else {
return function() {
alert("너는 바이러스에 걸렸어.");
alert("윈도우창을 닫지마!!");
alert("윈도우창을 닫지마!!");
alert("윈도우창을 닫지마!!");
alert("윈도우창을 닫지마!!");
alert("윈도우창을 닫지마!!");
}
}
}
미스테리함수();
// return 함수를 호풀하고있는 것이 아니다.
// 그저 함수가 반환값일 뿐이라 콘솔에 입력될 뿐이다
위 처럼 function을 리턴하는 함수를 만들었다
밖에서 호출했지만 사실 안에 있는 return에 있는 익명함수를 실행한 것은 아니다.
다른 반환 값처럼 저장해서 사용하려면 캡처를 해야한다.
const 미스테리 = 미스테리함수();
미스테리(); // 함수호출 성공!
미스테리함수()의 반환값인 익명함수를 미스테리라는 변수에 저장을 했기때문에
미스테리를 미스테리();로, 안쪽에 있던 익명함수를 호출할 수 있다!!
이런 기능을 사용할건데 이건 하드코딩 버전이다.
function isBetween(num) {
return num >= 50 && num <= 100;
}
function isBetween2(num) {
return num >= 1 && num <= 10;
}
아래와 같이 만들 수 있다.
makeBetweenFunc라는 팩토리 함수를 만든다.
function makeBetweenFunc(min, max) {
return function(num){
return num >= min && num <= max;
}
}
makeBetween(50, 100);
/ 을 하면 아래와 같은 '값'이 반환된다. 그렇기 때문에 그것을 변수에 넣어 호출하면된다.
function(num){
return num >= 50 && num <= 100;
}
/ 이렇게 말이다!!!!
const isChild = makeBetweenFunc(0, 18);
isChild(4); // true
isChild(40); // flase
const isAdult = makeBetweenFunc(19,64);
isAdult(23); // true
isAdult(6); // flase
const isSenior = makeBetweenFunc(65, 120);
isSenior(45); // false
isSenior(89); // true
메서드는 함수인데,
객체에 종속된 특성으로 함수(function)에 포함되는 개념이다.
메서드 이름 앞에 점을 찍어서 사용한다. 특정자료.indexOf 같이.
객체의 프로퍼티(특성)으로 추가한 메서드(함수)의 예시.
const myMath = {
square: function(num) {
return num * num;
},
cube: function(num) {
return num ** 3;
},
}
myMath.square(4); // 16 , 보통은 이렇게 한다.
myMath["square"](4) // 16 , 이렇게도 가능하다.
myMath 라는 '객체 리터럴'을 만들었다.
또 아래 코드와 같은 속기법이 있다. 콜론(:)과 function을 생략할 수 있다.
const myMath = {
square(num) {
return num * num;
},
cube(num) {
return num ** 3;
},
}
myMath.square(4); // 16 , 보통은 이렇게 한다.
myMath["square"](4) // 16 , 이렇게도 가능하다.
가장 흔하게는 메서드에 있는 객체를 가리킬 때 this 키워드를 사용한다.
예시) this.프로퍼티(특성)
const cat = {
name: 'Blue Steele',
color: 'grey',
breed: 'scottish fold',
meow() {
console.log(`${this.name} says MEOWWW`)
}, // 여기서 name에 this.를 쓰지않으면 undefined.
}
하지만 항상 this가 메서드를 정의한 객체를 가리키는 것은 아니다!
this 값이 바뀔 수 있기 때문이다.
const cat = {
name: 'Blue Steele',
color: 'grey',
breed: 'scottish fold',
meow() {
console.log(`${this.name} says MEOWWW`)
}, // 여기서 name에 this.를 쓰지않으면 undefined.
}
const meow2 = cat.meow; // 코드 내용은 똑같다.
meow2(); // says MEOWWWW 가 출력된다.
위 코드에서는 this 키워드가 cat 객체를 가르키지 않는다.
meow2에서 this는 Window 객체를 가리킨다!! (자바스크립트 내장객체)
window는 최상위 객체다.
function을 정의한다면 'window.함수명' 으로 실행 가능하다.
즉, 함수를 정의하면 따로 생긴 것 같지만 실제론 window에 속한다.
Try와 Catch는 오류를 잡아내어 코드 실행이 정지되거나 중단되지 않도록 하는 역할을 한다.
try {
hello.toUpperCase();
} catch {
console.log("ERROR!!!!");
}
console.log("AFTER"); // 에러가 있어도 잘 실행된다.
오류를 발생시키려 하거나 오류가 날 것으로 예상되면 그걸 try 블록으로 감싼다.
catch는 try안에 오류가 있을 때 실행하게 되는 코드이다.
function yell(msg){
try {
console.log(msg.toUpperCase().repeat(3));
} catch (e) {
console.log("please pass a string next time!");
}
}
위 코드에서 문자를 입력하면 문제가없지만
숫자나 배열과 같은다른 타입을 입력하면 Type에러가 뜬다!
그래서 위 처럼 트라이/캐치 문으로 감싸주는 예시다.
비동기 함수를 다룰 때, express , node를 다룰 때 사용될 것이다.
이상으로 JavaScript 함수에서 조금 난이도가 있는 개념들 정리를 마치겠습니다.
기본적인 부분들이지만 차근차근 공부하다 보니 생각보다 재밌다.
실전에서 쓰려면 아직 어렵겠지만 기본을 익혔다는 든든함을 가지고 더 나아가야겠다.