Search

JavaScript1

학과
CS
phase
개월수
계획 월
2021/12/20
우선순위
4
유형
언어공부

변수

변수 선언의 실행 시점과 변수 호이스팅

변수 선언문이 코드의 선두로 끌어올려진 것처럼 동작하는 자바스크립트 고유의 특징을 변수 호이스팅이라고 한다.
변수 선언은 런타임 이전에 먼저 실행되지만, 값의 할당은 순차적으로 실행되는 시점인 런타임에 실행된다.

데이터 타입

원시 타입
숫자 타입(정수, 실수 구분이 없다.)
문자열
불리언
undefined - var 키워드로 선언된 변수에 암묵적으로 할당되는 값
null - 값이 없다는 것을 의도적으로 명시할 때 사용하는 값.
심벌 타입
객체 타입
객체, 함수, 배열 등

숫자 타입

1.
Infinity, -Infinity, NaN(Not a Number)
Not a Number → 산술 연산 불가

문자열 타입

문자열은 원시타입이며 변경 불가능한 값.
+연산자는 피연산자 중 하나 이상이 문자열인 경우 문자열 연결 연산자로 동작. 그 외의 경우는 덧셈 연산자로 동작
만약에 백틱을 쓰면 ${ }를 이용해서 값을 쓸 수 있는데, 문자열이 아니더라도 타입이 강제로 변환되어 삽입된다.

Undefined

자바스크립트 엔진에서 변수를 선언할 때, 쓰레기 값이 안들어가도록 집어넣는 값

null

null은 변수에 값이 없다는 것을 의도적으로 명시할 때 사용함.
함수가 유효한 값을 반환할 수 없는 경우 명시적으로 null을 반환하기도 함.
변수는 타입이 없다. 자바스크립트의 변수는 선언이 아닌 할당에 의해 타입이 결정된다. 재할당에 의해 변수의 타입은 언제든지 동적으로 변할 수 있다.
값에만 타입이 존재함. typeof는 변수의 데이터 타입을 반환하는 것이 아니라 변수에 할당된 값의 데이터 타입을 반환한 것.

변수를 위한 지침

변수는 꼭 필요한 경우에 한해 제한적으로 사용한다. 변수 값은 재할당에 의해 언제든지 변경될 수 있음
변수의 유효범위는 최대한 좁게 만들어 변수의 부작용을 억제해야함. 변수의 유효범위가 넓으면 넓을수록 변수로 인해 오류가 발생할 확률이 높아짐
전역 변수는 최대한 사용하지 말자. 어디서든지 참조/변경이 가능하므로 의도치않게 값이 변경될 가능성이 높고, 다른 코드에 영향을 줄 가능성도 높다. 오류가 발생할 경우, 오류의 원인을 특정하기 어렵게 만든다.
변수보다 상수를 사용해 값의 변경을 억제하자.
네이밍을 잘 하자. 변수 이름뿐 아니라 모든 식별자는 존재이유를 파악할 수 있는 적절한 이름으로 지어야 한다.

7. 연산자

이항 산술 연산자

+, - , *, /, %
→ 부수 효과가 없다. 산술 연산을 해도 피연산자의 값이 바뀌는 경우는 없고 언제나 새로운 값을 만들 뿐이다.

단항 연산자

++ : 증가, 부수효과로 암묵적으로 피연산자의 값을 변경
-- : 감소, 부수효과로 암묵적으로 피연산자의 값을 변경
+: 어떠한 효과도 없다. 숫자 타입이 아닌 피연산자에 +를 사용하면 피연산자를 숫자 타입으로 변환하여 반환함. 피연산자를 변경하는 건 아니고, 숫자 타입으로 변환한 값을 생성해서 반환
-: 피연산자의 부호를 반전한 값을 반환, 숫자 타입이 아닌 피연산자에 사용하면, 피연산자를 숫자 타입으로 변환하여 반환. 피연산자를 변경하는 것은 아니고, 부호를 반전한 값을 생성해 반환
-(-10); // 10 -'10'; // -10 -true; // -1 -'Hello' // NaN
Java
복사

문자열 연결 연산자

+연산자
피연산자 중 하나 이상이 문자열인 경우 문자열 연결 연산자로 동작함.
이때 true는 1로 false는 0으로, null은 0으로 암묵적으로 타입 캐스팅이 된다.

비교 연산자

==: 동등 비교, x와 y의 값이 같다.
===: 일치 비교 x와 y의 값과 타입이 같다.
≠ : x와 y의 값이 다르다.
≠=: x와 y의 값과 타입이 다르다.
동등 비교는 좌항과 우항의 피연산자를 비교할 때 암묵적 타입 변환을 통해 타입을 일치시킨 후 같은 값인지 비교한다.

typeof 연산자

string, number, boolean, undefined, symbol, object, function 중 하나를 반환한다.
null을 반환하지는 않고, 함수의 경우 function을 반환한다.
아직 선언한적 없는 식별자를 typeof연산을 하면 undefined를 반환하고,
null 값을 typeof연산을 하면 object를 반환한ㄷ.

9. 타입 변환과 단축 평가

암묵적 타입 변환은 기존 변수 값을 재할당하여 변경하는 것이 아니다.
표현식을 에러 없이 평가하기 위해 피연산자의 값을 암묵적 타입 벼노한해 새로운 타입의 값을 만들어 한 번 사용하고 버림.

문자열 타입으로 변환

문자열 연결 연산자가 올 때, 문자열로 모두 변환된다.
심벌 타입은 문자열 타입으로 안 바뀐다. //객체 타입 ({}) + '' // "[object Object]" Math + '' // "[object Math]" [] + '' // "" [10,20] + '' //"10,20" (function(){}) + '' // "function(){}" Array + '' // "function Array(){[native code]}"
JavaScript
복사

숫자 타입으로 변환

산술 연산자가 있을 때, 전부 숫자로 바뀐다.
1 - '1' // 0 1 * '10' // 10 1 / 'one' // NaN
JavaScript
복사
산술 연산자 말고도 비교 연산자일 때도, 모든 피연산자는 숫자여야한다.
'1' > 0 // true //문자열 타입 +'' // 0 +'0' // 0 +'1' // 1 +'String' // NaN //불리언 타입 +true // 1 +false //0 //null 타입 +null // 0 //undefined 타입 +undefined // NaN //심벌타입 +Symbol() // typeError //객체 타입 +{} // NaN +[] // 0 +[10,20] // NaN +(function(){}) // NaN
JavaScript
복사

불리언 타입으로 변환

if나 for문과 같은 제어문 또는 삼항 조건 연산자의 조건식은 불리언으로 암묵적으로 변환됨.
if(’’) → falsy
if(true) → truthy
if(0) → falsy
if(’str’) → truthy
if(null) → falsy

10. 객체 리터럴

원시 값을 제외한 나머지 값은 모두 객체임
객체 타입은 다양한 타입의 값을 하나의 단위로 구성한 복합적인 자료구조.
원시 값은 변경 불가능한 값이지만, 객체 타입의 값은 변경 가능한 값이다.
var person = { name : 'Lee', age: 20 };
JavaScript
복사
객체는 0개 이상의 프로퍼티로 구성된 집합이며, 프로퍼티는 키와 값으로 구성된다.
사용할 수 있는 모든 값은 프로퍼티 값이 될 수 있다.
자바스크립트의 함수는 일급객체이므로 값으로 취급할 수 있다. 함수도 프로퍼티 값으로 사용가능.
프로퍼티 값이 함수일 경우, 일반 함수와 구분하기 위해 메서드라 부른다.
var counter = { num: 0, increase: function(){ this.num ++; } }
JavaScript
복사
다양한 객체 생성 방법을 지원한다.
객체 리터럴
var person = { name : 'Lee'. sayHello : function(){ console.log(`Hello! myname is #{this.name}.`); } }
JavaScript
복사
Object 생성자 함수
생성자 함수
Object.create 메서드
클래스(ES6)
나머지는 함수를 사용한 객체 생성임

프로퍼티

프로퍼티 키 : 빈 문자열을 포함하는 모든 문자열 또는 심벌 값
프로퍼티 값 : 자바스크립트에서 사용할 수 있는 모든 값.
식별자 네이밍 규칙을 따르지 않는 프로퍼티 키를 사용하면 번거로워진다.
만약에 할거면 따옴표를 써라.
동적으로 추가할 수도 있음
다만 프로퍼티키로 사용할 표현식을 대괄호로 묶어야함.
var obj = {}; var key = 'hello'; obj[key] = 'world'; console.log(obj); // {hello: "world"}
JavaScript
복사

프로퍼티 접근

프로퍼티에 접근하는 방법은 접근 연산자(.)를 이용할 수도
대괄호 프로퍼티 접근 연산자([...])를 사용할 수도 있다. → 이 경우 따옴표로 감싸야한다.

11. 원시 값과 객체의 비교

원시 값을 변수에 할당하면, 변수에는 실제 값이 저장된다.
객체를 변수에 할당하면 변수에는 참조 값이 저장된다.
원시 값을 갖는 변수를 다른 변수에 할당하면 원본의 원시 값이 복사되어 전달됨.
객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달됨.
하지만 ‘값에 의한 전달’이라는 용어는 정확하게는 자바스크립트를 위한 용어가 아니다.
변수에는 값이 전달되는 것이 아니라 메모리 주소가 전달된다.
그렇다면 원시 값과 객체는 할당할 때 어떻게 되느냐
원시 값의 경우 새로운 원시 값이 메모리에 올라가고 그 메모리 주소가 변수에 들어간다.
객체의 경우 기존 변수에 있는 주소 값이 그대로 복사되어 들어간다.
따라서 객체의 경우, 한 객체를 두 개의 변수가 공유하는 것이고, 원시 값의 경우 값이 같은 서로 다른 두 메모리 주소를 각각 들고 있는 것.

12. 함수

자바스크립트의 함수는 객체 타입의 값이다.
객체를 객체 리터럴로 생성하는 것처럼 함수도 함수 리터럴로 생성할 수 있다.
var f = function add(x,y) { return x+y; };
JavaScript
복사
구성 요소
함수 이름 : 식별자이며, 생략할 수 있다. 함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자.
매개변수 목록 : 함수 몸체 내에서 변수와 동일하게 취급됨.
함수 몸체 : 함수가 호출되었을 때 일괄적으로 시행될 문들을 하나의 실행 단위로 정의한 코드 블럭, 함수 호출에 의해 실행된다.
함수는 객체다.

함수 정의

함수 선언문
function add(x,y){ return x+y; }
JavaScript
복사
함수 표현식
var add = function(x,y){ return x + y; };
JavaScript
복사
Function 생성자 함수
var add = new Function('x','y','return x+y');
JavaScript
복사
arrow function(ES6)
var add = (x,y) => x+y;
JavaScript
복사

함수 정의

1.
함수 선언문
function add(x,y){ return x+y; }
JavaScript
복사
함수 선언문은 함수 이름을 생략할 수 없다.
함수 선언문은 표현식이 아닌 문이다. 표현식이라면 표현식 값이 평가되어 생성된 함수가 출력되야하지만, undefined가 나온다.
엔진이 코드의 문맥에 따라 동일한 함수 리터럴을 표현식이 아닌 문인 함수 선언문으로 해석하거나, 표현식인 문인 함수 리터럴 표현식으로 해석할 수 있다.
function foo(){console.log('foo');} foo(); // foo (function bar(){ console.log('bar');}); bar(); // bar is not defined
JavaScript
복사
foo는 함수 선언문으로 해석됨.
하지만 그룹 연산자 내에 있는 함수 리터럴은 함수 선언문으로 해석되지 않고 함수 리터럴 표현식으로 해석됨. 그룹 연산자의 피연산자는 값으로 평가될 수 있어야하기 때문.
함수 리터럴에서 함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자임.
따라서 함수 몸체 외부에서는 함수 이름을 함수로 참조할 수 없으므로 함수 몸체 외부에서는 함수 이름으로 함수를 호출할 수가 없다. 함수를 가리키는 식별자가 없다.
하지만 함수 선언문은 가능한데, 그 이유는 엔진이 함수 이름으로 암묵적으로 식별자를 생성하기 때문이다.
함수는 함수 이름으로 호출하는 것이 아니라 함수 객체를 가리키는 식별자로 호출한다.
2.
함수 표현식
값의 성질을 갖는 객체를 일급 객체라 하는데, 자바스크립트에서 함수는 일급 객체임.
함수 리터럴로 생성한 함수 객체를 변수에 할당할 수 있다. 이런 정의 방식을 함수 표현식이라 한다.
var add = function foo(x,y){ return x+y; }; console.log(add(2,5)); // 얘는 호출된다. console.log(foo(2,5)); // foo is not defined
JavaScript
복사

함수 생성 시점과 함수 호이스팅

console.dir(add);// f add(x,y) console.dir(sub); // undefined //함수 호출 console.log(add(2,5)); //7 console.log(sub(2,5)); // sub is not a function //함수 선언문 function add(x,y){ return x+y; } //함수 표현식 var sub = function (x,y) { return x-y; };
JavaScript
복사
함수 선언문으로 정의한 함수는 선언문 이전에 호출할 수 있다.
표현식으로 정의한 함수는 함수 표현식 이전에 호출할 수 없다.
→ 함수 선언문으로 정의한 함수와 함수 표현식으로 정의한 함수의 생성 시점이 다르기 때문이다.
런타임에는 이미 함수 객체가 생성되어 있고 함수 이름과 동일한 식별자에 할당까지 완료된 상태다.
함수 선언문이 코드의 선두로 끌어 올려진것처럼 동작하는 자바스크립트 고유 특징 → 함수 호이스팅
var 키워드로 선언된 변수는 undefined 로 초기화되고, 함수 선언문을 통해 생성된 식별자는 함수 객체로 초기화됨.
함수 표현식은 변수에 할당되는 값이 함수 리터럴인 문이다.
함수 표현식은 변수 선언문과 변수 할당문을 한 번에 기술한 축약 표현과 동일하게 동작한다.
변수 할당문의 값은 할당문이 실행되는 시점, 런타임에 평가되므로 함수 표현식의 함수 리터럴도 할당문이 실행되는 시점에 평가되어 함수 객체가 된다.
→ 함수 표현식으로 함수를 정의하면 함수 호이스팅이 아니라 변수 호이스팅이 발생함.
함수 호이스팅은 함수를 호출하기 전에 함수를 선언해야한다는 당연한 규칙을 무시한다. 이 같은 문제 때문에 함수 선언문 < 함수 표현식

Function 생성자 함수

기본 제공 빌트인 함수이다.
매개변수 목록과 함수 몸체를 문자열로 전달하면서 new 연산자와 함께 호출하면 함수 객체를 생성해서 반환한다.
var add = new Function('x','y','return x+y'); console.log(add(2,5));
JavaScript
복사
일반적이지 않으며 바람직하지도 않다.
→ 클로저를 생성하지 않는 등, 함수 선언문이나 함수 표현식으로 생성한 함수와 다르게 동작함.

화살표 함수

arrow function
function 키워드 대신 화살표를 사용해 좀 더 간략한 방법으로 함수를 선언한다.
화살표 함수는 항상 익명 함수로 정의한다.
기존의 함수보다 표현만 간략한게 아니라, 내부 동작 또한 간략화되있다.
생성자 함수로 사용할 수 없으며, 기존 함수와 this 바인딩 방식이 다르고, prototype 프로퍼티가 없으며 arguments 객체를 생성하지 않는다.
const add = (x,y) => x+y; console.log(add(2,5)); // 7
JavaScript
복사

함수 호출

매개변수를 통해 인수를 전달한다.
이 때 인수는 값으로 평가될 수 있는 표현식이어야한다.
매개변수는 함수를 정의할 때 선언하며, 함수 몸체 내부에서 변수와 동일하게 취급된다.
즉, 함수가 호출되면 함수 몸체 내에서 암묵적으로 매개변수가 생성되고, 일반적인 변수처럼 undefined로 초기화된 이후, 인수가 순서대로 할당됨.
매개변수의 스코프는 함수 내부다.
함수는 매개변수의 개수와 인수의 개수가 같은지 체크하지 않는다.
부족하다면 undefined이다. 초과된 인수는 무시됨.(암묵적으로 arguments 프로퍼티에 저장된다.)
함수 호출은 표현식이다. 함수 호출 표현식은 return 키워드가 반환한 표현식의 평가 결과, 즉 반환값으로 평가된다. 반환문은 함수의 실행을 중단하고 함수 몸체를 빠져나간다. 그리고 return 키워드 뒤에 오는 표현식을 평가해 반환한다. return 키워드 뒤에 반환값으로 사용할 표현식을 명시적으로 지정하지 않으면 undefined가 반환된다.
function foo(){ return; } console.log(foo()); //undefined function foo(){ } console.log(foo()); // undefined
JavaScript
복사

참조에 의한 전달과 외부 상태의 변경

원시 타입 인수는 값 자체가 복사되어 매개변수에 전달되기 때문에 함수에서 그 값을 변경해도 원본은 훼손되지 않는다.
객체 타입 인수는 참조 값이 복사되어 전달되기 때문에, 함수에서 그 값을 변경하면 원본이 훼손된다.
객체의 변경을 추적하려면 옵저버 패턴 등을 통해 참조를 공유하는 모든 이들에게 변경 사실을 통지하고 대처하는 추가 대응이 필요
문제 해결 방법 중 하나는 불변 객체로 만들어 사용.
복사하는 비용은 들지만, 원시 객체처럼 변경 불가능하게 만듬. 복제할 때는 깊은 복사를 통해 생성하고 재할당함.
외부 상태를 변경하지 않고 외부 상태에 의존하지도 않는 함수를 순수 함수. → 함수형 프로그래밍이다.

다양한 함수의 형태

1.
즉시 실행 함수
2.
재귀 함수
3.
중첩 함수
4.
콜백 함수
5.
순수 함수와 비순수 함수
즉시 실행 함수
함수 이름이 없는 익명 함수를 사용하는 것이 일반적이다.
//익명 즉시 실행 함수 (function(){ var a = 3; var b = 5; return a*b; }()); //기명 즉시 실행 함수 (function foo(){ var a = 3; var b = 5; return a*b; }()); foo(); // referenceError: foo is not defined
JavaScript
복사
그룹 연산자 내의 기명 함수는 함수 선언문이 아니라 함수 리터럴로 평가되고, 함수 이름은 몸체에서만 참조할 수 있으므로 즉시 실행 함수를 다시 호출할 수 없다.
즉시 실행 함수는 반드시 그룹 연산자(...) 로 감싸야한다. 아니면 에러
function(){ }(); // syntaxError: 함수 정의가 함수 선언문 형식에 맞지 않기 때문 function foo(){ }();// syntaxError: unexpected token, 선언문이 끝나는 위치에 ;를 암묵적으로 추가
JavaScript
복사
그룹 연산자의 피연산자는 값으로 평가되므로 기명 또는 무명함수를 그룹 연산자로 감싸면 함수 리터럴로 평가되어 함수 객체가 된다.
즉, 그룹 연산자로 함수를 묶은 이유는 함수 리터럴을 평가해서 함수 객체를 생성하기 위함.
먼저 함수 리터럴을 평가해서 함수 객체를 생성할 수 있으면, 어떤 연산자를 써도 좋다.
즉시 실행 함수도 일반 함수처럼 값을 반환할 수 있고 인수를 전달할 수도 있다.
var res = (function(){ var a = 3; var b = 5; return a*b; }()); console.log(res); res = (function(a,b){ return a*b; }(3,5)); console.log(res);
JavaScript
복사
재귀 함수
함수 내부에서 자기 자신을 호출할 때 사용한 식별자 foo는 함수 이름이다. 함수 이름은 함수 몸체 내부에서만 유효하다. 함수 내부에서는 함수 이름을 사용해 자기 자신을 호출할 수 있다.
뿐만 아니라 함수를 가리키는 식별자로도 자기 자신을 재귀호출 할 수 있다.
다만 외부에서 호출하면 함수를 가리키는 식별자로 해야함
var factorial = function foo(n){ if(n<=1) return 1; return n*factorial(n-1); //console.log(factorial == foo); //return n* foo(n-1); }; console.log(factorial(5)); // 120
JavaScript
복사
중첩 함수
함수 내부에서 정의된 함수를 중첩함수 또는 내부 함수
중첩 함수를 포함하는 함수는 외부 함수
중첩 함수는 외부 함수 내부에서만 호출할 수 있다. 외부함수를 돕는 헬퍼 함수의 역할을 함.
function outer(){ var x = 1; function inner(){ var y = 2; console.log(x+y); } inner(); } outer();
JavaScript
복사
콜백 함수
function repeat(n,f){ for(var i=0; i<n; i++){ f(i); } } var logAll = function(i) { console.log(i) }; repeat(5,logAll); // 0 1 2 3 4 var logOdds = function(i){ if(i%2) console.log(i); }; repeat(5,logOdds); // 1 3
JavaScript
복사
변경되는 일을 함수 f로 추상화했고, 이를 외부에서 전달받음.
함수는 일급 객체이므로 함수의 매개변수를 통해 함수를 전달할 수 있다.
함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백함수라고 하며,
매개변수를 통해 함수의 외부에서 콜백함수를 전달받은 함수를 고차함수라고한다.
고차함수는 매개변수를 통해 전달받은 콜백 함수의 호출 시점을 결정해서 호출한다.
콜백함수는 고차함수에 의해 호출되며, 이 때 고차함수는 필요에 따라 콜백 함수에 인수를 전달할 수 있다.
따라서 고차함수를 전달할 때는 함수 자체를 전달해야한다.
순수 함수와 비순수 함수
부수효과가 없는 함수를 순수 함수
부수 효과가 있는 함수를 비순수 함수
순수 함수는 어떤 외부 상태에도 의존하지 않고 오직 매개변수를 통해 함수 내부로 전달된 인수에게만 의존해 반환값을 만든다. 또한 함수의 외부 상태를 변경하지 않는다.
함수형 프로그래밍은 순수함수와 보조 함수의 조합을 통해 외부 상태를 변경하는 부수 효과를 최소화해서 불변성을 지향하는 프로그래밍 패러다임.
로직 내에 존재하는 조건문과 반복문을 제거해서 복잡성을 해결하며, 변수 사용을 억제하거나 생명주기를 최소화해서 상태 변경을 피해 오류를 최소화하는 것을 목표로 한다.

13. 스코프

모든 식별자(변수 이름, 함수 이름, 클래스 이름 등)은 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할 수 있는 유효 범위가 결정된다. 이를 스코프라고 함.
자바스크립트 엔진은 코드를 실행할 때 코드의 문맥을 고려한다. 코드가 어디서 실행되며 주변에 어떤 코드가 있는지에 따라 동일한 코드도 다른 결과를 만들어낸다.
→ 코드가 어디서 실행되며 주변에 어떤 코드가 있는지를 렉시컬 환경이라 한다.
→ 렉시컬 환경을 구현한 것이 실행 컨텍스트, 모든 코드는 실행 컨텍스트에서 평가되고 실행된다.
스코프 내에서 식별자는 유일해야하지만 다른 스코프에는 같은 이름의 식별자를 사용할 수 있다.
*var는 스코프 내에서 중복 선언이 허용된다. 이는 의도치않게 변수값이 재할당되어 변경되는 부작용을 발생시킨다. let이나 const는 스코프 내에서 중복 선언을 허용하지 않음

스코프의 종류

전역 스코프→ 전역 → 코드의 가장 바깥 영역
지역 스코프 → 지역 → 함수 몸체 내부
지역 스코프는 자신의 지역 스코프와 하위 지역 스코프에서 유효하다.

스코프 체인

함수는 중첩 될 수 있으므로 함수의 지역 스코프도 중첩될 수 있다.
스코프가 함수의 중첩에 의해 계층적 구조를 갖는다는 것을 의미
중첩 함수의 지역 스코프는 중첩 함수를 포함하는 외부 함수의 지역 스코프와 계층적 구조를 갖는다. 외부 함수의 지역 스코프를 중첩 함수의 상위 스코프라 한다.
변수를 참조할 때 자바스크립트 엔진은 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작하여 상위 스코프 방향으로 이동하며 선언된 변수를 검색한다.
이는 상위 스코프에서 유효한 변수는 하위 스코프에서 자유롭게 참조할 수 있지만 하위 스코프에서 유효한 변수를 상위 스코프에서 참조할 수 없다는 것을 의미한다.
스코프는 근데 사실 그냥 함수든 변수든 상관이 없기 때문에 식별자를 찾는 규칙이라 보면 된다.

함수 레벨 스코프

지역은 함수 몸체 내부를 말하고, 지역은 지역 스코프를 만든다.
코드 블록이 아닌 함수에 의해서만 지역스코프가 생성된다.
대부분의 프로그래밍 언어는 모든 코드 블록(if, for, while, try,catch)이 지역 스코프를 만든다. → 블록 레벨 스코프
하지만 var 키워드로 선언된 변수는 오로지 함수의 코드블록 만들 지역 스코프로 인정한다. → 함수 레벨 스코프
하지만 let,const 키워드는 블록 레벨 스코프를 지원한다.

렉시컬 스코프

var x = 1; function foo(){ var x = 10; bar(); } function bar(){ console.log(x); } foo(); bar();
JavaScript
복사
bar 함수의 상위 스코프가 무엇인지에 따라 결정된다.
두 가지 패턴을 예측할 수 있는데
1.
함수를 어디서 호출했는지에 따라 함수의 상위 스코프를 결정
2.
함수를 어디서 정의했는지에 따라 함수의 상위 스코프를 결정
첫번째 방식으로 하면 상위 스코프는 foo함수의 지역 스코프와 전역 스코프이다.
두번째 방식으로 하면 상위 스코프는 전역 스코프이다.
첫번째 방식을 동적 스코프 → 함수를 정의하는 시점에는 함수가 어디서 호출될지 알수 없다. 따라서 함수가 호출되는 시점에 동적으로 상위 스코프를 결정하기 때문에 동적 스코프
두번째 방식을 렉시컬 스코프, 정적 스코프 → 상위 스코프가 동적으로 바뀌지 않고, 함수 정의가 평가되는 시점에 상위 스코프가 정적으로 결정되기 때문에 정적 스코프라고 부른다.
자바스크립트는 렉시컬 스코프를 따르므로, 함수를 어디서 정의했는지에 따라 상위 스코프를 정의.
따라서 위 예제에서 bar함수는 항상 1을 출력한다.

전역 변수의 문제점

변수의 생명 주기

function foo(){ var x = 'local'; console.log(x); return x; } foo(); console.log(x);
JavaScript
복사
지역 변수 x는 foo함수가 호출되기 이전까지는 생성되지 않는다.
foo 함수를 호출하지 않으면 함수 내부의 변수 선언문이 실행되지 않기 때문이다.
함수 내부에서 선언한 변수는 함수가 호출된 직후에 함수 몸체의 코드가 한줄씩 순차적으로 실행되기 이전에 엔진에 의해 먼저 실행된다.
지역 변수의 생명 주기는 함수의 생명 주기와 일치한다.
다만 지역변수가 함수보다 오래 생존하는 경우도 있다.
함수 내부에서 선언된 지역 변수는 함수가 생성한 스코프에 등록된다. 함수가 생성한 스코프는 렉시컬 환경이라 부르는 물리적 실체가 있다. 변수는 자신이 등록된 스코프가 소멸될 때까지 유효하다.
할당된 메모리 공간은 더이상 그 누구도 참조하지 않을 때 가비지 콜렉터에 의해 해제되어 가용 메모리 풀에 반환된다. 누군가가 메모리 공간을 참조하면 확보된 상태로 남아있다.
스코프도 마찬가지다. 누가 스코프를 참조하고 있으면 스코프는 소멸하지 않고 생존하게 된다.
일반적으로 함수가 종료하면 함수가 생성한 스코프도 소멸한다. 하지만 누군가가 스코프를 참조하고 있으면 스코프는 해제되지 않고 생존하게 된다.
var x = 'global'; functioni foo(){ console.log(x); // undefined var x = 'local'; } foo(); console.log(x); // global
JavaScript
복사
위에서 undefined 값을 가진다.
호이스팅은 스코프를 단위로 동작한다.
전역 변수는 전역 객체의 프로퍼티가 된다. 전역 변수의 생명 주기가 전역 객체의 생명 주기와 일치한다는 것을 말함.
전역 객체는 코드가 실행되기 이전 단계에 자바스크립트 엔진에 의해 어떤 객체보다도 먼저 생성되는 특수한 객체
클라이언트 사이드 환경은 window, node.js에서는 global 객체를 의미함.
표준 빌트인 객체와 환경에 따른 호스트 객체 그리고 var 키워드로 전역변수와 전역 함수를 프로퍼티로 갖는다.

전역 변수의 사용을 억제하는 방법

1.
즉시 실행 함수
a.
모든 코드를 즉시 실행 함수로 감싸면 모든 변수는 즉시 실행 함수의 지역 변수가 된다.
(function(){ var foo = 10; //.. }()); console.log(foo); // referenceError: foo is not defined
JavaScript
복사
2.
네임스페이스 객체
a.
전역에 네임스페이스 역할을 담당할 객체를 생성하고 전역 변수 처럼 사용하고 싶은 변수를 프로퍼티로 추가
var MYAPP = {}; MYAPP.name = 'Lee'; console.log(MYAPP.name);
JavaScript
복사
그다지 유용하지 않다.
3.
모듈 패턴
a.
클래스를 모방해서 관련이 있는 변수와 함수를 모아 즉시 실행 함수로 감싸 하나의 모듈을 만든다.
클로저를 기반으로 동작한다. 전역 변수의 억제는 물론 캡슐화까지 구현할 수 있다.
var Counter = (function(){ //private 변수 var num = 0; //외부로 공개할 데이터나 메서드를 프로퍼티로 추가한 객체를 반환 return { increase(){ return ++num; }, decrease(){ return --num; } }; }()); //private 변수는 외부로 노출되지 않는다. console.log(Counter.num); // undefined console.log(Counter.increase()); // 1 console.log(Counter.increase()); // 2 console.log(Counter.decrease()); // 1 console.log(Counter.decrease()); // 0
JavaScript
복사
4.
ES6 모듈
더는 전역 변수를 사용할 수 없다.
파일 자체의 독자적인 모듈 스코프를 제공함.
<script type = "module" src = "lib.mjs"></script>
JavaScript
복사

15. let, const 키워드와 블록 레벨 스코프

var 키워드로 선언한 변수의 문제점

a.
변수의 중복 선언 허용
var x = 1; var y = 1; //초기화문이 있는 변수 선언문은 var 키워드가 없는 것처럼 동작 var x = 100; //초기화문이 없는 변수 선언문은 무시됨. var y; console.log(x); console.log(y);
JavaScript
복사
b.
함수 레벨 스코프
i.
오로지 함수 코드 블록만을 지역 스코프로 인정한다.
ii.
함수 외부에서 var 키워드로 선언한 변수는 코드 블록 내에서 선언해도 모두 전역변수가 된다.
var x = 1; if(true){ var x = 10; } console.log(x); // 10 var i = 10; for(var i = 0; i < 5; i++){ console.log(i); } console.log(i); // 5
JavaScript
복사
c.
변수 호이스팅
i.
변수 선언문이 스코프의 선두로 끌어올려진 것처럼 동작함.
ii.
변수 호이스팅에 의해 면수 선언문 이전에 참조할 수 있다. 이러면 언제나 undefined를 반환함

let 키워드

a.
변수 중복 선언 금지
b.
블록 레벨 스코프
i.
함수, if, for, while 문 등을 지역 스코프로 인정
c.
변수 호이스팅
i.
변수 호이스팅이 발생하지 않는 것처럼 동작한다.
ii.
var 키워드는 선언 단계와 초기화 단계가 한번에 런타임 이전에 진행된다.
iii.
let 키워드는 선언 단계와 초기화 단계가 분리되어 진행된다. 런타임 이전에 선언 단계가 실행되지만, 초기화 단계는 변수 선언문에 도달했을 때 실행
iv.
초기화 이전에 참조하려고 하면 참조 에러를 뱉음
d.
전역 객체와 let
i.
let 키워드로 선언한 전역 변수는 전역 객체의 프로퍼티가 아니다.
ii.
보이지 않는 개념적인 블록(렉시컬 환경의 선언적 환경 레코드) 내에 존재하게 된다.

const 키워드

a.
선언과 초기화
i.
반드시 선언과 동시에 초기화해야한다.
b.
재할당이 금지된다.
c.
상수
d.
객체
i.
원시 값을 할당했을 때는 변경이 안되지만, 객체를 할당했을 때는 값을 변경할 수 있다.
ii.
재할당을 금지할 뿐, ‘불변’을 의미하지는 않는다.

var vs. let vs. const

a.
ES6를 사용한다면 var 키워드는 사용하지 않는다.
b.
재할당이 필요한 경우에 한정해 let 키워드, 변수의 스코프는 최대한 좁게
c.
변경이 발생하지 않고 읽기 전용으로 사용하는 원시 값과 객체에는 const 키워드 사용

16.프로퍼티 어트리뷰트

내부 슬롯과 내부 메서드

내부 슬롯과 내부 메서드는 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 의사 프로퍼티와 의사 메서드이다.
모든 객체는 [[Prototype]]이라는 내부 슬롯을 가지는데, .__proto__를 통해 간접적으로 접근 가능

프로퍼티 어트리뷰트와 프로퍼티 디스크립터 객체

프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의한다.
프로퍼티 상태
a.
프로퍼티 값
b.
값의 갱신 가능 여부
c.
열거 가능 여부
d.
재정의 가능 여부
프로퍼티 어트리뷰트는 엔진이 관리하는 내부 상태 값인 내부 슬롯 [[Value]], [[Writable]], [[Enumerable]], [[Configurable]]이다. 직접 접근할 수 없지만 메서드를 사용하여 확인 가능
Object.getOwnPropertyDescriptor(객체 참조, “프로퍼티 이름”)
→ 프로퍼티 디스크립터 객체를 반환한다.

데이터 프로퍼티와 접근자 프로퍼티

프로퍼티는 데이터 프로퍼티와 접근자 프로퍼티로 구분한다.
데이터 프로퍼티 : 키와 값으로 구성된 일반적 프로퍼티
접근자 프로퍼티: 값이 없고, getter랑 setter를 통해 저장되고 호출되는 프로퍼티
데이터 프로퍼티
[[Value]] : 프로퍼티 값
[[Writable]] : 프로퍼티 값의 변경 가능 여부
[[Enumerable]]: 프로퍼티의 열거 가능 여부
[[Configurable]]: 프로퍼티 재정의 가능 여부
접근자 프로퍼티
get
set
eumerable
configuralble
const person = { firstName: 'Ungmo', lastName : 'Lee', get fullName(){ return '${this.firstName} ${this.lastName}'; }, set fullName(name){ [this.firstName, this.lastName] = name.split(' '); } }; person.fullName = 'Heegun Lee'; person.fullName; descriptor = Object.getOwnPropertyDescriptor(person.'fullName');
JavaScript
복사
내부적 동작
프로퍼티 키가 유효한지 확인한다.
프로토타입 체인에서 프로퍼티를 검색한다.
검색된 프로퍼티가 데이터 프로퍼티인지, 접근자 프로퍼티인지 확인
접근자 프로퍼티라면 getter를 호출한다.

프로퍼티 정의

const person = {}; Object.defineProperty(person, 'firstName', { value: 'Ungmo', writable: true, enumerable: true, configurable: true }); Object.defineProperty(person,'lastName', { value: 'Lee' }); //값 누락 시 대부분 false
JavaScript
복사

객체 변경 방지

a.
객체확장 금지 (Object.preventExtensions)
i.
프로퍼티 추가 X
b.
객체밀봉(Object.seal)
i.
프로퍼티 추가와 삭제, 어트리뷰트 재정의 금지
c.
객체 동결(Object.freeze)
i.
값 읽기만 가능
이때 불변 객체로 만들려면, 모든 프로퍼티에 대해 재귀적으로 Object.freeze 메서드를 호출해야함.
중첩 객체는 영향을 못주기 때문이다.