전역 스코프 (Global scope)
코드 어디에서든지 참조할 수 있다.
지역 스코프 (Local scope or Function-level scope): var
함수 코드 블록이 만든 스코프로 함수 자신과 하위 함수에서만 참조할 수 있다.
function foo() {
var x = "variable";
// x는 오직 foo 함수 내부에서만 사용 가능
console.log("Inside function");
console.log(x);
}
foo();
console.log(x); // 에러 발생
if 문, loop 문 등과 같은 다른 유형의 블록에서는 스코프로 간주되지 않는다.
var age = 32;
if (true) {
var korAge = age + 1;
console.log(`Your Korean Age is ${korAge}!`); // 'Your Korean Age is 33!'
}
console.log(korAge); // 33
블록 스코프(Block scope): let
, const
블록이란 여는 중괄호 '{'와 닫는 중괄호 '}'의 집합으로, 일반적인 c/c++ 에서 사용하는 규칙이다.
var age = 32;
if (true) {
let korAge = age + 1;
console.log(`Your Korean Age is ${korAge}!`); // 'Your Korean Age is 33!'
}
console.log(korAge); // Uncaught ReferenceError: korAge is not defined
렉시컬 스코프(Lexical Scope)
렉시컬 스코프란 중첩된 함수 그룹에서 내부 함수가 상위 범위의 변수 및 기타 리소스에 액세스 할 수 있음을 의미한다.
즉, 함수를 어디서 선언하였는지에 따라 상위 스코프를 결정한다는 뜻이며,
가장 중요한 점은 함수를 어디서 호출하는지가 아니라 어디에 선언하였는지에 따라 결정된다.
다른 말로, 정적 스코프(Static Scope)라 부르기도 한다.
var x = 1;
function foo() {
var x = 10;
bar();
}
function bar() {
console.log(x);
}
foo(); // 1
bar(); // 1
Javascript 는 렉시컬 스코프를 따르므로, 함수를 선언한 시점
에 상위 스코프가 결정된다.
함수를 어디서 호출하였는지는 스코프 결정에 아무런 영향을 주지 않는다.
위 예제의 bar
함수는 전역에 선언되었기에, bar
함수의 상위 스코프는 전역 스코프이고,
따라서 전역 변수 x의 값 1을 두 번 출력한다.
관련 키워드: Scope, Scope chain, Execution context...
function 함수명(){
함수 로직
}
// 예시
function SayHello(){
console.log("hello!");
}
SayHello(); // 출력: hello!
일반 함수는 자바스크립트가 처음 읽힐 당시에 해석되므로, 함수가 쓰여진 위치가 상관없다.
이를 호이스팅이라고 하는데, 쉽게 말해 자바스크립트가 처음 로드될때 모든 객체(변수, 함수)의 선언부를 맨 위로 끌어올리는 것이다.
일반 함수는 함수 전체(함수명, 함수바디)가 통째로 호이스팅 되어 맨 위로 올라가기 때문에 위치 상관없이 읽을 수 있는 것이다.
SayHello(); // "hello!" 가 정상적으로 출력됨.
function SayHello(){
console.log("hello!");
}
SayHello(); // "hello!" 가 정상적으로 출력됨.
// 호이스팅 된 모습
//
// function SayHello(){ <- 함수 선언이 먼저 일어나고,
// console.log("hello!");
// }
//
// SayHello(); <- 첫 번째 SayHello();
// SayHello(); <- 두 번재 SayHello();
함수의 이름이 존재하지 않음
function() {
console.log("hello!");
}
익명 함수는 재사용 하지 않는, 한번만 사용할 함수를 위한 개념으로, 따로 함수의 이름을 갖지 않는다. 리터럴(Literal) 방식으로 변수에 담겨 사용하는 함수이다. ("함수의 이름 != 변수")
// 리터럴 방식
let a = 10;
const b = 20;
익명함수를 호출하기 위해선 괄호로 감싸거나(즉시실행함수: Immediately Invoked Function Expression), 변수에 저장해야한다.
// 즉시 실행 함수
(function() {
console.log("hello!");
})(); // 출력: hello!
// 익명 함수
const sayHello = function() {
console.log("hello!");
}
sayHello(); // 출력: hello!
함수가 이름을 갖는 것과 변수에 저장되는 것은 다르다.
//익명 함수
sayHello(); // Uncaught ReferenceError: Cannot access 'sayHello' before initialization
let sayHello = function() {
console.log("hello!");
}
sayHello(); // 위에서 에러가 났으니 출력이 나오지 않음
// 이 자바스크립트를 읽을 때(호이스팅 된 모습)
//
// const sayHello;
//
// sayHello(); <- sayHello의 초기화가 진행되지 않았다.
//
// sayHello = function(){
// console.log("hello!");
// }
//
// sayHello(); <- 초기화는 진행된 후 불렸으니, 원래대로라면 출력 가능
결론: 익명함수는 메모리 관리에 효과적인 방안
일반 함수는 자바스크립트를 초기에 읽어올 때 모두 호이스팅된다고 하였다.
만약, 전체 자바스크립트 내에서 단 한번만 쓰이는 함수가 일반 함수로 구현되어 있다고 가정해보자.
이 함수는 자신이 사용될 단 한번을 기다리며 불필요하게 메모리를 차지하고 있어야 한다.
메모리 사용량이 성능에 중요한 영향을 미칠 수 있는 웹 애플리케이션에서, 이는 메모리 낭비라고 볼 수 있다.
따라서 단 한번만 사용되는(재사용이 필요없는) 함수의 경우,
불필요한 시간동안 메모리를 차지하지 않도록 익명함수로 구현한다면,
정확히 해당 함수가 필요한 위치에서만 해당 함수가 구현되고 사라지면서 메모리를 아낄 수 있게 된다.
function
이라는 키워드 없이 fat arrow
=>
를 이용해 함수를 생성하는 방법
const sum = function(x, y) {
return x + y;
}
sum(3, 4); //7
화살표함수는 return
명령어 없이도 함수 실행을 종료시키고 값을 반환한다.
const sum = (x, y) => x + y;
sum(3, 4); //7
화살표 함수 표현(arrow function expression)은 function 표현에 비해 구문이 짧고 자신의 this, arguments, super 또는 new.target을 바인딩 하지 않습니다.
화살표 함수는 항상 익명입니다. 이 함수 표현은 메소드 함수가 아닌 곳에 가장 적합합니다. 그래서 생성자로서 사용할 수 없습니다.-MDN
기본 문법
var/let/const 함수명 = (매개변수) => {실행문}
var foo = (a, b) => {
return (a + b) * 10;
};
var foo = a => {
return a * 10;
};
var foo = (a, b) => a + b;
기본적으로 화살표함수는 익명 함수로만 사용할 수 있기 때문에, 함수를 호출하기 위해서는 표현식으로 써야한다.
// literal
var addNumber = (a, b) => {
return a + b;
};
addNumber(1, 2); // 3
// IIFE
((a, b) => {
return a + b;
})(1, 2); // 3
// 잘못된 표현
function (a, b) => {
return a + b;
}; //Uncaught SyntaxError: Function statements require a function name
화살표함수를 사용할 수 없는 경우
어디서 선언했느냐가 아니라, 어디서 호출했는가에 따라 달라진다.
this는 "호출한 메서드를 소유하는 객체"
javascript 의 this는 함수의 실행방식에 따라 값이 다르다.
일반함수 실행방식
전역공간에서 this는 전역객체를 가리킨다
function foo() {
console.log(this);
}
foo(); // window
핵심은 함수가 실행되는 부분을 찾는 것이다.
function foo() {
console.log(this);
}
function bar() {
foo();
}
bar();
메소드 실행
메소드 실행에서의 this는 메소드를 소유하고 있는 객체를 가리킨다.
const obj = {
name: 'obj',
foo: function() {
console.log(this);
}
};
obj.foo(); // obj
const obj2 = {
name: 'obj2',
foo: obj.foo
}
obj2.foo(); // obj2
const obj3 = obj.foo;
obj3(); // ???
obj3: 일반함수 실행방식
생성자 실행
new
연산자를 붙여 실행합니다.function User(name) {
// this = {}; (빈 객체가 암시적으로 만들어짐)
// 새로운 프로퍼티를 this에 추가함
this.name = name;
this.isAdmin = false;
// return this; (this가 암시적으로 반환됨)
}
function Constructor(name) {
this.name = name;
}
console.log(new Constructor('test'));
const temp = Constructor('temp');
console.log(temp);
console.log(windows.name);
명시적 this 바인딩
명시적 this 바인딩은 함수가 myFuc.call() 이나 myFuc.apply() 등의 메소드 호출을 통해 실행되는 것을 뜻한다.
( function.prototype.call / function.prototype.bind / function.prototype.apply )
call(), apply(), bind() 호출에서 this는 첫번째 매개변수를 가리킨다.
const object = { name: 'test' };
function foo(a,b,c) {
console.log(this);
}
foo(11, 12, 13);
foo.call(object, 21, 22, 23);
foo.apply(object, [31, 32, 33]);
const bar = foo.bind(object);
bar(41, 42, 43);
Arrow function에서 this는 arrow function이 정의된 곳의 문맥을 그대로 따른다.
함수가 어떻게 호출되었는지에 따라 바인딩할 객체가 결정되는 일반함수와는 달리,
화살표함수의 this
는 화살표함수가 호출되는 시점과는 무관하게 선언되는 시점에 결정되며 언제나 상위 스코프의 this
를 가리킨다.
렉시컬 스코프(Lexical Scope)
: 함수를 어디서 선언하였는지에 따라 상위 스코프가 정해지는 방식. 자바스크립트를 비롯한 대부분의 프로그래밍 언어는 렉시컬
스코프를 따름. ↔ 동적 스코프(Dynamic Scope)
일반함수
var obj = {
myName: 'Barbie',
logName: function() {
console.log(this.myName);
}
};
obj.logName(); //"Barbie"
화살표함수
var obj = {
myName: 'Barbie',
logName: () => {
console.log(this.myName); // this는 obj의 상위 객체인 window 를 가리킴
}
};
obj.logName(); //undefined
const person = () =>{
this.age = 0;
console.log('age: ', age);
console.log(this)
setTimeout(()=>{
this.age += 1;
console.log('arrow age: ', this.age); // 상위 스코프의 this를 가리킨다.(정적으로 결정됨)
}, 1000);
}
person();
Method 로 사용
const obj = {
a: 'hi',
b: () => console.log(this.a, this),
c: function () {
console.log(this.a, this);
}
}
obj.b(); // prints undefined, Window 혹은 전역객체
obj.c(); // prints 'hi', Object {...}
Property 로 사용
const person = {
name: 'Lee',
};
Object.prototype.sayHi = () => console.log(`Hi ${this.name}`);
person.sayHi(); // Hi undefined
생성자
화살표 함수는
prototype
property 가 없다.
const Foo = () => {
console.log(this);
}
const foo = new Foo(); // TypeError: Foo is not a constructor
addEventListner 의 callback 함수
const box = document.getElementById('box');
box.addEventListener('click', () => {
console.log(this); //window
});
||
, &&
연산자 활용
var person = { age: 10 };
if(
person.name === undefined ||
person.name === false ||
person.name === '' ||
person.name === null ||
person.name === 0
) {
console.log('noname');
} else {
console.log(person.name);
}
//
console.log(person.name || 'noname');
if(person.age > 19) {
console.log('운전가능');
} else {
console.log(false);
}
//
console.log(person.age > 19 && '운전가능')
Optional operator .?
let me = {}
me.name // undefined
me.name.first // error
me.name?.first // undefined
me.friends[0] // error
me.friends?.[0] // undefined
me.where(); // error
me.where?.(); // undefined
값이 없을 때, 값을 할당하는 Null 병합 할당 연산자
let config = {min: 10};
if(config.min === undefined || config.min === null) {
config.min = 20;
}
if(config.max === undefined || config.max === null) {
config.max = 100;
}
console.log(config);
// -->
let config = {min: 10};
config.min ??= 20;
config.max ??= 100;
console.log(config)
...
연산자를 이용한 복제, 원본 데이터의 유지
const todos = [
{
title: '공부',
done: true
},
{
title: '게임',
done: false
}
];
todos[1].done = true;
todos.push({title: '운동', done: false});
console.log(todos);
// React 에서 자주 사용하는 복사
const copyTodos = [...todos]
copyTodos[1] = {...copyTodos[1]}
copyTodos[1].done = true;
copyTodos.push({title: '운동', done: false});
// immer
import produce from "immer";
const copyTodos = produce(todos, draft => { // draft 는 todos의 임시 복제본
draft[1].done = true;
draft.push({title: "운동", done: false});
});
setTodos(copyTodos);