js 엔진은 js 코드를 실행(평가)하기위해서 피연산자와 연산자의 의미를 알고 있어야 한다.
ex) 10 + 20 // 피연산자: 10, 20 연산자: 20
컴퓨터는 CPU를 이용해 연산을하고 메모리를 이용해 데이터를 기억한다.
메모리란 데이터를 저장할 수 있는 메모리 셀의 집합이다.
셀 하나당 크기는 1바이트(8비트)이며, 컴퓨터는 메모리 셀 크기 단위로 데이터를 저장하거나 읽는다.
각 셀은 고유한 메모리 주소를 가진다. 메모리 주소는 0~ 메모리 크기만큼 정수로 표현한다.
이때 메모리 주소는 16진수로 표현되며, 메모리에 저장되는 데이터는 2진수로 표현된다.
그림은 편의상 10진수로 표현한 것이다.
자바스크립트는 개발자의 직접적인 메모리 제어를 허용하지 않는다.
메모리 주소를 통해 값에 직접 접근하면 치명적인 오류를 발생시킬 수 있기 때문이다.
만약 직접적인 접근을 허용해도 문제가 있다. 값이 저장된 메모리 주소는 코드를 실행할때마다 값이 저장된 메모리 주소는 계속해서 변경된다.
따라서 코드가 실행되기 전까지는 메모리의 주소값을 알 수 없다.
그러나 우리는 메모리에 저장된 값들을 재사용 하고 싶을 때가 있다.
이를위해 프로그래밍 언어는 변수라는 메커니즘을 제공한다.
즉, 우리는 변수를 이용해 값을 저장하고 재사용할 수 있는 것이다.
따라서 개발자는 메모리 주소를 통하는 것이 아닌 변수를 통해 안전하게 값에 접근할 수 있다.
식별자는 어떤 값을 구별해 식별할 수 있는 고유한 이름을 말한다.
식별자는 값이 저장되어 있는 메모리 주소를 기억해야한다.
그래야 해당 메모리 주소를 통해 메모리 공간에 저장된 값에 접근할 수 있기 때문이다.
즉, 식별자는 메모리 주소에 붙인 이름이라고 할 수 있다.
ReferenceError는 js엔진이 등록된 식별자를 찾을 수 없을때 발생하는 에러다.
변수 선언은 소스코드가 한줄씩 차례대로 실행되는 시점, 즉 런타임이 아니라 그 이전 단계에서 먼저 실행된다.
왜냐하면
js엔진은 먼저 소스코드의 평가 과정을 거치면서 소스코드 실행을 위한 준비를 한다.
이 소스코드 평가과정에서 모든 선언문(변수,함수..)을 소스코드에서 먼저 찾아내 실행하기 때문이다.
따라서 변수 선언이 코드 어디에 위치하든지 변수를 참조 할 수(읽을 수)있다.
단 let,const의 경우 변수의 초기화를 수행하기 전에 읽는 코드가 먼저 나타나면 에러가 발생한다.
왜냐하면 var는 선언과 동시에 초기화까지 시켜주지만 let,const는 그렇지 않기 때문이다.
이처럼 변수 선언문이 코드의 선두에 올려진 것처럼 동작하는 js의 특징을 변수 호이스팅이라고 한다.
변수 선언은 런타임 이전에 실행되지만 값의 할당은 소스코드가 순차적으로 실행되는 시점인 런타임에 실행된다.
값을 할당하거나 재할당 할때는 이전 값이 저장되어 있는 메모리 공간을 지우고 새로운 메모리 공간을 확보해 그안에 값을 저장한다.
더이상 불필요한 값들은 가비지 콜렉터에 의해 메모리에서 자동 해제된다.
(가비지 콜렉터란 애플리케이션이 할당한 메모리 공간을 주기적으로 검사해 더이상 사용되지 않는 메모리를 해제하는 기능을 말한다.)
사람이 이해할 수 있는 문자 또는 약속된 기호를 사용해 값을 생성하는 표기법
쉽게말해 우리가 알고있는 숫자,문자,기호들을 말한다.
값으로 평가될 수 있는 문을 말한다.
표현식이 계산(평가)되면 새로운 값을 생성하거나 기존 값을 참조한다(읽는다).
리터럴,식별자,연산자,함수/메서드등 값으로 표현될 수 있는 문은 전부 다 표현식이라고 할 수 있다.
ex) 10 , 10 + 20 ,arr[1], person.getName(), hello()...
변수 선언문은 표현식이라고 할 수 없다. 표현식은 값으로 평가될 수 있는 문이기 때문이다.
ex) var x;
프로그램을 구성하는 기본단위이자 최소 실행단위
문은 토큰으로 구성된다.
토큰은 문법적인 의미를 가지며 문법적으로 더이상 나눌 수 없는 코드의 기본 요소를 말한다.
문은 다른말로 명령문이라고 부를 수 있다. 즉 컴퓨터에게 내리는 명령이다.
표현식인 문은 값으로 평가될 수 있으면 표현식인 문이고,
값으로 평가될 수 없으면 표현식이 아닌 문인 것이다.
var x;
x = 1 + 2;
선언문은 표현식이 아닌 문일 것이고 , 할당문은 표현식인 문이 될 것이다.
또한 할당문은 그 자체로 표현식이될 수 있다
ex) var x =10;
ECMAScript 사양에 따르면 숫자 타입의 값은 배정밀도 64비트 부동 소수점 형식을 따른다. 배정밀도 64비트 부동소수점 형식은 8바이트로 숫자를 표현한다.
즉 모든 숫자를 실수로 처리하며 정수를 표현하기 위한 데이터 타입이 존재하지 않는다.
따라서 정수끼리 나눈다 해도 실수의 값이 나올 수 있다.
정수,실수,2진수,8진수,16진수 리터럴은 메모리에 64비트 부동소수점 형식의 2진수로 저장된다.
js는 2,8,16진수를 표현하기위한 별도의 데이터 타입이 없기 때문에 참조시 10진수로 해석된다.
일반 문자열 내에서는 줄바꿈이 허용되지 않는다.
따라서 줄바꿈, 공백등을 표현하려면 백슬래쉬(\)로 시작하는 이스케이프 시퀀스를 사용해야 한다!
일반 문자열과 달리 템플릿 리터럴(백틱)은 이스케이프 시퀀스를 사용하지 않고도 줄바꿈이 허용되고 공백도 있는 그대로 적용되는 장점이 있다.
js엔진 데이터 타입에 따라 정해진 크기의 메모리 공간을 확보한다.
예를들어 숫자형 타입을 가진 값을 참조할 경우, 숫자타입이 저장된 메모리 공간의
선두 메모리 셀의 주소를 찾아간다.
모든 값은 데이터 타입을 가지며 메모리에 2진수로 나열된다.
이렇게 메모리에 저장된 값은 데이터 타입에 따라 다르게 해석될 수 있다.
예를들어 0100 0001 2진수 값의 데이터 타입이 문자형이라면 'A', 숫자형이라면 65로 해석된다는 것이다.
결론적으로 데이터 타입이 필요한 이유에는
1. 값을 저장시 확보할 메모리 공간 크기를 결정하기 위해
2. 값을 참조시 읽어야할 메모리 공간 크기를 결정하기 위해
3. 메모리에서 읽어들인 2진수를 어떻게 해석할지 결정하기 위해
프로그래밍 언어에는 정적 타입 언어와 동적 타입 언어가 있다.
정적 타입 언어는 데이터 타입을 사전에 선언하는 언어이며
대표적으로 자바, c, c++, 코틀린등이 있다.
변수의 타입을 변경할 수 없다.
동적 타입 언어는 변수를 선언할때 데이터 타입을 선언하지 않는 언어이다.
다만 var,let,const 키워드로 변수를 선언할 뿐이다
대표적으로 JS가 있다.
JS의 변수에는 어떤 데이터 타입의 값이라도 자유롭게 할당할 수 있다.
즉 정적 타입 언어는 변수를 선언한 시점에 데이터 타입이 지정되고,
동적 타입 언어는 변수에 값을 할당할 시점에 데이터 타입이 동적으로 지정되는 것이다.
JS 변수는 선언이 아닌 할당에 의해 타입이 결정된다. 그리고 재할당에 의해 타입은 언제든지 바뀔 수 있다. 이러한 특징을 동적 타이핑이라고 한다.
그러나 이러한 동적 타입 언어도 취약점이 존재한다.
JS는 JS엔진에 의해 암묵적으로 타입이 자동 변환되기도 하기 때문에 개발자의 잘못된 타입 예측으로 오류를 유발할 수 있다.
JS언어는 유연성은 높지만 신뢰성은 낮다고 할 수 있다.
단항 산술 연산자 중 +는 숫자 타입이 아닌 피연산자앞에 붙이면 피연산자를 숫자타입으로 변환시켜준다.
또한 +는 두 피연산자의 타입을 동일하게 만들어주는 역할을 한다. 즉 어떤 피연산자의 타입을 또다른 피연산자의 타입으로 암묵적 타입 변환시켜준다.
단, undefined와 NaN는 숫자로 타입변환되지 않는다.
ex) console.log(1 + null); // 1
ex) console.log(1 + "1"); // '11'
단항 산술 연산자 중 -는 숫자 타입이 아닌 피연산자앞에 붙이면 피연산자를 숫자타입으로 변환시켜준다.
또한 -는 숫자타입인 피연산자의 부호를 반전한 값을 반환한다.
ex) console.log(-true); // -1
동등/비동등 비교 연산자는 비교시 타입이 암묵적 타입 변환된다.
일치 비교 연산자에서 주의할 것은 NaN이다!
NaN는 자기 자신과도 일치하지 않는 유일한 값이다.
또한 숫자 0은 -를 붙이든 안붙이든 모두 같은 값으로 취급된다.
논리 부정 연산자(!)는 언제나 불리언 값을 반환한다. 이때 피연산자가 반드시 불리언 값일 필요는 없다.
console.log(!18); // false
console.log(!""); // true
console.log(!NaN);
js엔진은 '',0,null,undefined,false,NaN 데이터 타입을 가진 변수들을 의도적으로 불리언 값으로 반환시 false로 변환한다.
제어문은 특정 조건에 따라 코드 블록을 실행하거나 코드블록을 반복해서 실행할때 사용한다.
제어문을 사용하면 코드의 실행 흐름을 인위적으로 제어할 수 있다.
그러나 제어문은 코드의 흐름을 어지럽게 만들어 가독성을 해치는 단점이 있기에
forEach,filter,map,reduce같은 고차함수를 사용한 함수형 프로그래밍 기법에서 복잡성을 해결 할 수도 있다.
0개 이상의 문을 중괄호로 묶은 것이며 (코드)블록이라고도 한다.
원래 문의 끝에는 항상 세미콜론(;)을 붙여야하지만
블록문은 문의 종료를 의미하는 자체 종결성을 가지고 있기 때문에 세미콜론을 붙여서는 안된다!
반복문에는 for, while, do-while, for...of , for...in이 있다.
break문은 반복문, switch문의 코드블록을 탈출한다.
이외에의 블록안에 break문을 사용하면 SyntaxError가 발생한다.
중첩된 for문에서 외부 for문을 탈출하고 싶을때는 레이블 문을 사용한다.
외부 for문 앞에 label을 붙여주는 것이다.
그러나 레이블은 일반적으로 권장하지 않는다.
프로그램의 흐름이 복잡해져 가독성이 좋지않고 오류를 발생시킬 확률이 높기 때문이다.
타입 변환에는 명시적 타입 변환과 암시적 타입 변환이 있다.
개발자가 의도적으로 값으 타입을 변환하는 것을 말하며, 타입 캐스팅이라고도 한다.
대표적으로 toString()은 숫자를 문자열로 변환할때 사용되는 메소드이다.
개발자의 의도와 상관없이 표현식을 계산하는 도중에 js 엔진에 의해 암묵적으로 타입이 자동 변환되는 것이다. 타입 강제 변환이라고도 한다.
그러나 타입 변환은 기존의 원시 값을 직접 변경하는 것은 아니다!
타입 변환은 기존 원시 값을 이용해 다른 타입의 새로운 원시 값을 생성하는 것이다!
const x = 20;
const y = x.toString();
const z = x + "";
console.log(typeof x, typeof y, typeof z); // number string string
단축 평가를 사용하면 if문을 대체할 수 있다.
어떤 조건이 Truthy 값일때 무언가를 해야한다면
if문 대신 && 연산자 표현식으로 대체할 수 있다.
const done=true;
let message='';
// if(done) message='완료';
message = done && '완료';
반대로 어떤 조건이 Falshy 값일 때 무언가를 해야한다면 || 연산자 표현식으로 if문을 대체할 수 있다.
const done = false;
let message = "";
// if(!done) message='완료';
message = done || "완료";
ES11에 도입된 연산자로 ?.로 표기하며 좌항의 피연산자가 null or undfined인 경우 undefined를 반환하고 그렇지 않으면 우항의 프로퍼티 참조를 이어간다.
옵셔널 체이닝 연산자 도입 이전에는 논리연산자&&를 사용한 단축평가를 통해 변수가 null인지 undefined인지 확인했다.
ex)elem?.value= elem && elem.value
ES11에 도입된 연산자로 ??로 표기하며 좌항의 피연산자가 null or undfined인 경우 우항의 피연산자를 반환하고 그렇지 않으면 좌항의 피연산자를 반환한다.
null 병합 연산자 도입 이전에는 논리연산자||를 사용한 단축평가를 통해 null이거나 undefined일 경우에 변수에 기본값을 설정했다.
객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임을 말한다.
c++, java 같은 언어는 클래스 기반의 객체 지향 언어이다. 클래스를 사전에 정의하고 필요한 시점에 new 연산자와 함께 생성자를 호출해 인스턴스를 생성한다.
인스턴스란 클래스에 의해 생성되어 메모리에 저장된 실체를 말한다.
Javascript는 프로토타입 기반 객체 지향 언어로 다양한 객체 생성 방법을 지원한다.
객체 리터럴, Object 생성자 함수, 생성자 함수, Object.create 메서드, 클래스(ES6)
객체의 프로퍼티 키는 식별자 네이밍 규칙을 따르는 것이 좋지만 꼭 따라야하는 것은 아니다.
그러나 중요한 점은 식별자 네이밍 규칙을 따르지 않는 이름에는 반드시 따옴표를 사용해야한다.
프로퍼티 키에 문자열, 심볼 값 외에 다른 값을 사용하면 따옴표는 붙지 않지만 암묵적으로 문자열로 변환된다.
대괄호 접근 연산자를 사용해서 프로퍼티에 접근할 경우
프로퍼티 키는 반드시 따옴표로 감싼 문자열이어야 한다.
만약 감싸지 않으면 js엔진은 식별자로 해석하게 된다.
const person = {
name: "Kim",
};
console.log(person[name]); //ReferenceError
ES6에서는 프로퍼티 값으로 변수를 사용하는 경우 변수이름과 프로퍼티 키가 동일한 경우
프로퍼티 키를 생략할 수 있다.
const x=1;
const y=2;
const obj={
x: x,
y: y,
};
const obj={ x, y};
ES6에서는 메서드 정의시 function 키워드를 생략한 축약표현을 사용할 수 있다.
const obj={
name:'Kim",
sayHi: function(){
console.log('Hi!' + this.name);
}
};
obj.sayHi();
const obj={
name:'Kim",
sayHi(){
console.log('Hi!' + this.name);
}
};
obj.sayHi();
원시 값을 변수에 할당하면 변수에는 실제 값이 저장된다.
원시 값을 갖는 변수를 다른 변수에 할당하면 원시 값이 복사되어 전달된다.
이를 값에 의한 전달이라고 한다.
원시 값이 한번 지정되면 변경할 수 없다는 것은 변수가 아니라 값에 대한 진술이다.
변수는 언제든 재할당을 통해 변경될 수 있다.
변수가 재할당되면 재할당 이전의 원시 값을 변경하는 게 아니라 새로운 메모리 공간을 확보해 재할당한 원시 값을 저장하는 이유도 기존의 원시값이 변경 불가능하기 때문이다.
ECMAScript사양에 문자열타입(2Bytes)과 숫자 타입(8bytes) 이외의 원시타입은 크기를 명확하게 규정하고 있지 않다.
1개의 문자는 2바이트의 메모리 공간에 저장된다.
만약 10개의 문자로 이뤄진 문자열이라면 20바이트의 메모리 공간이 필요하다.
문자열 타입 또한 원시타입이므로 변경이 불가능하다.
마치 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있고 length 프로퍼티를 갖는 객체를 말한다.
문자열은 유사배열 객체이므로 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있고 length 프로퍼티를 갖는다.
그러나 문자열 자체는 원시값이기 때문에 이미 생성된 문자열의 일부문자를 변경하려해도 반영되지 않는다.
그렇지만 변수에 새로운 문자열을 재할당하는 것은 물론 가능하다.
어떤 원시 값을 갖는 변수를 다른 변수에 할당할때 우리는 값에 의한 전달이라고 한다.
그러나 엄격하게 표현하자면 값이 전달되는것이 아니라 메모리 주소가 전달되는 것이기 때문이다.
이는 변수는 값이 아니라 값이 들어있는 메모리 주소를 가리키고 있기 때문이다.
단, 전달된 메모리 주소를 통해 메모리 공간에 접근하면 값을 참조할 수 있다.
객체를 변수에 할당하면 변수에는 참조 값이 저장된다.(메모리의 주소)
객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달된다.
이를 참조에 의한 전달이라고 한다.
대부분의 JS 엔진은 프로퍼티 키를 인덱스로 사용하는 해시테이블과 유사하지만 높은 성능을 위해 일반적인 해시 테이블보다 나은 방법으로 객체를 구현한다.
자바나 C++같은 언어는 클래스 기반 객체지향 언어로 사전에 정의된 클래스를 기반으로 객체를 생성한다. 다시 말해서 객체 생성 이전에 프로퍼티와 메서드가 이미 정의되어 있어 그대로 객체를 생성한다.
객체가 생성된 후에는 프로퍼티와 메서드를 삭제,추가할 수 없다.
이는 JS와 상반되는 개념이다.
따라서 JS는 사용하기 편리하지만 성능면에서 이론상 클래스 기반 언어의 객체보다 프로퍼티에 접근시에 비용이 더 많이드는 비효율적인 방식이다.
따라서 이를 해결하기 위해 V8 JS 엔진에서는 프로퍼티에 접근하기 위해 기존의 방식인 동적탐색 대신 히든클래스 라는 방식을 사용해서 C++ 객체의 프로퍼티에 접근하는 정도의 성능을 보장한다.
동적탐색: 런타임시 타입이 정해지는 동적타이핑 언어가 프로퍼티에 접근할때 사용하는 방식이다.
동적 타이핑 언어는 런타임 과정에서 프로퍼티를 찾아내야한다. 왜냐하면 런타임 과정에서 프로퍼티와 메서드가 선언되기 때문이다.
때문에 정적 타이핑 언어보다 런타임 과정에서 해야할 일이 많고 시간이 더 필요하다.
따라서 프로퍼티에 접근하는데 비용이 더 발생하는 것이다.
V8: 히든 클래스 방식:새 객체를 생성할 때 V8 엔진은 이에 대한 새로운 히든 클래스를 생성한다.

그런 다음 새 프로퍼티를 추가해 동일한 객체를 수정하면 V8 엔진에서 이전 클래스의 모든 프로퍼티가 포함된 새 히든 클래스를 만들고 새 프로퍼티를 포함한다.

이 객체에 다른 프로퍼티를 추가하면 동일한 과정이 수행된다. 또 다른 히든 클래스가 생성되고 이전 프로퍼티와 새 프로퍼티가 모두 오프셋으로 포함된다.

히든 클래스를 이용하면 사전형 탐색(dictionary lookups)을 우회할 수 있을 뿐만 아니라 유사한 객체가 생성되거나 수정될 때 이미 생성된 클래스를 재사용할 수 있다.
예를 들어 기사(const articleObject = { })라는 다른 빈 객체를 생성하면 V8 엔진은 새로운 히든 클래스를 생성하지 않는다. 대신 이미 생성된 C01 클래스를 가리킨다.
그러나 articleName이라는 새 프로퍼티를 추가해 articleObject를 수정하면 V8은 name이라는 프로퍼티만 있기 때문에 이전에 생성한 클래스(C02)를 사용할 수 없다.
출처: 자바스크립트 성능의 비밀
따라서, 자바스크립트 코드의 성능 향상을 최대화하려면 동적 프로퍼티 추가를 줄여야 할 수도 있다.
객체는 변경 가능한 값이다.
객체를 할당한 변수가 가리키는 메모리 주소를 통해 메모리 공간에 접근하면
생성된 객체가 저장된 메모리 공간의 주소에 접근할 수 있다.
객체는 변경 가능한 값이기 때문에 객체를 할당한 변수는 재할당 하지 않고도
객체를 직접 변경할 수 있다.
const person = {
name: "lee",
};
// 프로퍼티 값 수정
person.name = "kim";
// 프로퍼티 동적 생성
person.age = 25;
여러개의 식별자가 하나의 객체를 공유할 수 있다.
객체를 가리키는 변수를 다른 변수에 할당하면 객체의 주소값이 복사되어 전달된다.
이를 참조에 의한 전달이라고 한다.
두 변수에 저장된 메모리 주소는 다르지만 동일한 참조값을 가진다.(동일한 객체의 주소값을 가진다.)
이것은 두개의 식별자가 하나의 객체를 공유함을 의미한다.
두 식별자중 어느 한쪽에서 객체를 변경하면 서로 영향을 받는다고 할 수 있다.
값에 의한 전달이나 참조에 의한 전달이나 결국 식별자가 기억하는 메모리 공간에 저장되어 있는 값을 복사해서 전달한다는 면에서 동일하다!
그 값이 원시값(단순 데이터)이냐 참조값(객체의 주소)이냐의 차이만 있을 뿐이다.