자바스크립트는 HTML, CSS 와 함께 웹을 구성하는 요소중 하나로 웹 브라우저에서 동작하는 유일한 프로그래밍 언어이다.
자바스크립트는 개발자가 별도의 컴파일 작업을 수행하지 않는 인터프리터 언어이다.
인터프리터 언어: 런타임에 한 줄씩 바이트코드로 변환한 후 실행한다. 코드가 실행될 때마다 인터프리트 과정이 반복 수행되며 실행단계와 인터프리트 과정이 분리되어 있지 않고 반복수행되어 코드 실행 속도가 비교적 느리다.
javascript 의 변수란 하나의 값을 저장하기 위해 확보한 메모리 공간 자체 또는 그 메모리 공간을 식별하기 위해 붙인 이름을 말한다.
키워드 : 자바스크립트 코드를 해석하고 실행하는 자바스크립트 엔진이 수행할 동작을 규정한 일종의 명령어
var score;
위의 변수 선언문은 변수 이름을 등록하고 저장할 메모리 공간을 확보한다. 메모리 공간에는 아직 값을 할당하기 전이나 undefined 라는 값이 암묵적으로 할당되어 초기화된다. 그래서 선언을 하고 할당하지 않은 변수를 출력하면 undefined 가 뜨는 것을 확인할 수 있다.
사실 변수 선언은 한줄씩 코드가 실행되는 런타임 이전 단계에서 먼저 실행된다. 이는 뒤에서 설명할 호이스팅과 관련이 있다.
좀더 자세히 알아볼까?
변수 중복 선언 허용
var x = 1;
var y = 1;
var x = 100; // 초기화문이 있는 변수 선언문은 var 키워드가 없는 것처럼 동작
var y; // 초기화문이 없는 변수 선언문은 무시됨
console.log(x) // 100
console.log(y) // 1
함수 레벨 스코프
=> 이것을 풀이하자면 var 키워드는 함수 레벨 스코프를 갖기에 var 선언변수중 함수 내에서 var 키워드로 선언한 변수만 지역 스코프를 갖는다는 의미이며 그 외의 var 선언시 전역 스코프를 갖는다는 것이다. 이는 의도치 않은 재할당의 문제를 야기할 수 있다.
변수 호이스팅
자바스크립트 엔진의 특징으로 모든 키워드는 변수 호이스팅이 작동한다. 이는 변수 선언문이 스코프의 선두로 끌어 올려진 것처럼 동작하는 것을 말한다.
ES6 에서 let, const 가 도입되었다.
변수 중복 선언 금지
블록 레벨 스코프
var 키워드로 선언한 변수는 오로지 함수의 코드블록만 지역 스코프로 인정하는 함수 레벨 스코프를 따른다.
let 키워드로 선언한 변수는 모든 코드 불록(함수, if 문, for 문, while문, try/catch 문 을 지역 스코프로 인정한다.
변수 호이스팅
let 키워드로 선언한 변수는 변수 호이스팅이 발생하지 않는 것처럼 동작한다.
let 키워드로 선언한 변수는 "선언 단계"와 "초기화 단계"가 분리되어 진행된다. 즉, 런타임 이전에 자바스크립트 엔진에 의해 암묵적으로 선언 단계가 먼저 실행되나 초기화 단계는 변수 선언문에 도달했을 때 실행된다.
단, 실제로는 let 도 호이스팅이 발생한다.
let foo = 1; // 전역 변수
{
console.log(foo);
let foo = 2; // 지역변수
}
let 키워드 사용시 변수 호이스팅이 발생하지 않으면 전역변수를 가져와서 실행이 되어야 하나 오류를 볼때 알 수 있듯 let 도 변수 호이스팅이 발생하고 있음을 알 수 있다.
자바스크립트는 ES6 에서 도입된 let, const 를 포함하여 모든 선언(var, let, const, function, function*, class 등)을 호이스팅한다.
단, EC6 에서 도입된 let, const, class 를 사용한 선언문은 호이스팅이 발생하지 않는 것처럼 동작한다.
전역 객체와 let
var 로 선언한 전역 변수, 암묵적 전역, 전역 함수 등은 전역 객체 window 의 프로퍼티가 된다.
var x = 1; // 전역 변수
y = 2; // 암묵적 전역
function foo() {} // 전역 함수
let 키워드로 선언한 전역 변수는 전역 객체의 프로퍼티가 아니라 window.foo 처럼 접근할 수 없다.
let x = 1;
console.log(window.x) // undefined
console.log(x) // 1
선언과 초기화
const 키워드로 선언한 변수는 반드시 선언과 동시에 초기화해줘야 한다.
블록 레벨 스코프, 호이스팅이 발생하지 않는 것처럼 동작한다.
재할당 금지
상수
원시 값 할당시 재할당이 불가능하다.
상수의 이름은 대문자로 선언해 상수임을 명확히 나타낸다. 여러 단어일때는 _ 로 구분해서 스네이크 케이스로 표현한다.
상수와 객체
const 키워드로 선언된 변수에 객체 할당시 값을 변경할 수 있다.
객체는 재할당 없이도 값을 바꿀 수 있다.
const 는 재할당을 금지할 뿐 불변을 의미하지는 않는다.
즉, 프로퍼티 동적 생성, 삭제, 프로퍼티 값의 변경을 통한 객체의 변경이 가능하다.
const 에 객체를 할당시 재할당이 안되는 것은 참조값이 변경되지 않음을 의미한다.
ES6 에서는 되도록 var 를 사용하지 않는다.
재할당이 필요한 경우 let 을 사용한다.
기본적으로 const 를 사용하는 것이 안전하며 추후 재할당이 필요할 경우 let으로 바꿔도 늦지 않다.
const user = Symbol('this is a user') // 유일한 값을 가진 변수 이름 생성
강한 타입 언어 : 타입 검사 통과하지 못할시 실행 자체가 안된다.
(Java, C, C++, C#, Golang, Typescript)
약한 타입 언어 : 런타임시 타입 오류를 만나더라도 실행을 막지 않는다.
(Javascript, Python)
3가지 출력 방법
let colors = ["red", "orange", "blue", "cyan"];
console.log("색상 :", colors); // 파라미터
console.log("색상 :" + colors); // 변수를 문자로 변환후 출력
console.log(`색상 : ${colors}`); // 템플릿 문자열
템플릿 문자열 : ES6 부터 지원되는 문자열 표기법. 백쿼트``를 사용해 표현. 멀티라인 문자열, 표현식 삽입, 태그드 템플릿 기능 지원
var template = `<ul>
<li>리스트
</ul>`;
console.log(template);
let name = "james";
console.log(`my name is ${name}`);
typeof
typeof null // object
typeof function // function
자바스크립트에서 함수는 객체인데 typeof 연산자에 함수를 사용시 object 가 아닌 function 이 리턴된다.
length : array 길이 반환
push : 마지막 요소 추가
pop : 마지막 요소 제거
unshift : 첫 번째 요소 추가
shift : 첫 번째 요소 제거
indexOf : index 값 반환
includes : 포함여부 boolean
누구나 객체를 출력해보면 Prototype 이라고 뜨는 부분에 대해 궁금해하지 않을수 없다. 아마도.. 그래서 prototype 에 대해 조사해보았다.
함수는 자바스크립트에서 객체다.
function Person() {}
var Person = new Function();
따라서 위의 함수 선언식을 하단과 같이 new 생성자로 나타낼 수 있다.
Java 를 알게되니 이제서야 이해가 되는데 this 개념을 통해 함수도 객체이니 다음과 같이 나타낼 수 있다.
function Person(name, first, second) {
this.name = name;
this.first = first;
this.second = second;
}
위와 같은 Person 이라는 함수 객체를 만들면 이에 따라 Person 의 프로토타입 객체가 생성된다. 이 두 객체는 서로 연관이 있다.
Person 이라는 함수는 내부적으로 prototype 이라는 property 를 갖게 되고 Person's prototype 객체는 내부적으로 constructor 라는 property 를 갖게 된다.
Person 의 prototype property 는 Person's prototype 을 가리키고 Person's prototype 의 constructor property 는 Person 을 가리키는 상호참조의 모습을 보인다.
개발자 모드를 참고해보자
Person.prototype 객체는 (Person's prototype 객체) constructor
와 __proto__
속성을 가지고 있다.
그리고 이 Person 의 프로토타입 객체의 constructor 는 Person 을 가리키고 있다.
자바스크립트의 특징중 하나로 프로퍼티의 동적 할당이 있다. 이는 property 에 없는 것도 동적으로 할당이 가능하다는 의미이다. Person.prototype 에 존재 하지 않았던 sum 이라는 property 를 추가해보자.
var kim = new Person('kim', 10, 20);
var Lee = new Person('Lee', 20, 30);
이때 Kim 의 property 중 __proto__
는 Kim 이라는 객체를 생성한 Person 의 prototype 를 가리킨다.
javascript 에서 함수를 실행할때 다음과 같은 순서를 갖는다.
먼저
kim.sum();
kim.hello();
가 있다고 하자.
Kim.sum()
의 경우 먼저 Kim 이라는 객체 내에 sum 이라는 함수가 있는지 찾는다. 이때 없다는게 확인되면 __proto__
를 통해 Person's prototype 을 찾아가서 sum 이 있는지 찾아본다.
다행이 이번에 sum 이 있었다. 먼저 정의한 sum 이라는 함수는 "sum 입니다~!" 를 출력하도록 해서 위와 같은 결과를 볼 수 있다.
만약에 Person's prototype 에도 sum 이 없다면 Person's prototype 의 __proto__
를 따라 모든 객체의 프로토타입 객체인 Object 를 찾아가서 sum 이라는 함수가 있는지 찾아본다.
이렇게 프로퍼티인 함수를 실행할때 객체 내부를 찾아보고 프로토타입 객체를 찾아보고 그 상위 Object 를 찾아가는 일련의 과정을 프로토타입 체이닝
이라고 부른다.
console.log() : HTML 과 유사한 트리에서 요소를 보여준다.
console.dir() : JSON 과 같은 트리에서 요소를 보여준다.
데이터 타입은 상황에 따라 변할 수 있다.
숫자 2 + 문자열 3은?
console.log(typeof (2 + "3")) // string
console.log(2 + "3") // 23
숫자 2 * 문자열 3은?
console.log(typeof (2 * "3")); // number
console.log(2 * "3"); // 6
위의 두 예시는 분명 문자로든 숫자로든 타입 변환을 명시하지 않았음에도 숫자로, 문자로 타입이 멋대로 바뀌는 모습을 볼 수 있다. 이를 암묵적 타입 변환이라 하는데 javascript 에서는 명시적 타입 변환과 암묵적 타입 변환으로 나누어 볼 수 있다.
그러기 앞서 형변환시 True 로 , False 로 평가되는 값들에 대해 알아야 한다.
False 로 평가되는 값
아래서 예시를 들 테지만 바로 이해가 되지 않을까봐 예시하나를 작성했다.
let name = '';
if (!name) {
console.log('이름이 입력되지 않았습니다. 다시 입력해주세요.');
}
이해하기 쉽지 않은가. 이름이 없으면 이름을 입력하라는 의미가 바로 이해가 되고 name 은 '' 인데 '' 는 false 로 평가되니 not (false) = true 로 if 문 내의 문이 실행된 것이다.
java 의 경우 if 의 조건문 내에 true, false 만 들어가야 하는데 javascript 는 boolean 타입이 아니더라도 다른 자료형이 boolean 형으로 평가될 수 있다.
재밌는 사례를 하나 더 추가했다.
console.log(3 > 2 && '안녕하세요');
console.log(3 > 2 || '');
console.log(3 > 4 || false );
이들은 어떻게 출력될까?
결과는 다음과 같다.
뭔가 이상하지 않나? 왜 갑자기 안녕하세요가 뜨지? true 도 아니고..
이 부분에 대해 단축 평가 파트에서 자세히 설명하려고 한다.
True 로 평가되는 값
'10' + 2 // '102', 문자열 타입으로 변환
5 * '10' // 50, 숫자 타입으로 변환
while (1) {} // 무한반복, 불리언 타입으로 변환
`1 + 1 = ${1 + 1}` // "1 + 1 = 2"
+ 연산자는 문자열 연결 연산자로 모든 피연산자는 코드의 문맥상 모두 문자열 타입이어야 한다.
템플릿 문자열에서는 표현식의 평가 결과를 문자열 타입으로 암묵적 타입 변환한다.
수업중 형변환에 대해 배웠던 부분은 바로 이 명시적 타입 변환에 대한 내용이었다.
String(1); // "1"
(1).toString(); // "1"
앞에서 프로토타입에 대해 설명해서 위의 내용이 잘 이해될 것이다. 객체에 toString 이라는 메서드가 없어도 프로토타입 객체에 이미 toString 이라는 메서드가 등록되어 있으니 사용할 수 있는 것이다.
1 + '10'; // 110
Number('0'); // 0, Number 생성자 함수를 new 연산자 없이 호출하는 방법
pareInt('0'); // 0, parseInt, parseFloat 함수를 사용하는 방법 (문자열만 변환 가능)
+'10.53' // 10.53, +단항 산술 연산자를 이용하는 방법
'10.53' * 1 // 10.53, *산술 연산자를 이용하는 방법
Boolean('x'); // true, 빈 문자열만 아니면 문자열은 모두 true 이다. (Truthy/ Falsy 개념 참고)
Boolean({});
Boolean([]); // true, 빈 객체, 빈 배열은 Truthy 값이다.
!!''; // ! 부정 논리 연산자를 두 번 사용하는 방법, Truthy, Falsy 에 따라 불리언 타입으로 변환된다.
논리합 또는 논리곱 연산자 표현식의 평가 결과는 불리언 값이 아닐 수도 있다.
논리합(||) 또는 논리곱(&&) 연산자 표현식은 언제나 2개의 피연산자 중 어느 한쪽으로 평가된다.
'Cat' && 'Dog' // "Dog"
NaN && "dog" // NaN
논리곱 연산자는 왼쪽 값이 truthy 값이면 오른쪽 값을 리턴하고 왼쪽 값이 falsy 값이면 왼쪽 값을 리턴한다.
'Cat' || 'Dog' // "Cat"
NaN || "dog" // dog
논리합 연산자는 왼쪽 값이 truthy 값이면 왼쪽 값을 리턴하고 왼쪽 값이 falsy 값이면 오른쪽 값을 리턴한다.
단축평가가 유용한 경우
var elem = null;
var value = elem.value; // TypeError: Cannot read property 'value' of null;
var value = elem && elem.value; // null, elem 이 falsy 값이고 논리곱이므로 왼쪽 값인 null 이 반환된다. 또한 에러가 발생하지 않는다.
function print(name) {
const message = name || '기본유저';
console.log(message);
}
print();
print('james');
이렇게 입력값이 주어지지 않았을 경우 default 값을 넣어주는 식으로 논리합 연산자를 조건식처럼 활용할 수 있다.
옵셔널 체이닝 연산자 ?. 는 좌항의 피연산자가 null 또는 undefined 일 경우 undefined 를 반환하고 그렇지 않으면 우항의 프로퍼티 참조를 이어간다.
var elem = null;
var value = elem?.value;
console.log(value); // undefined
// 혹은
var value = elem && elem.value;
console.log(value); // null
논리곱과 옵셔널 체이닝 연산자의 차이점으로 논리곱은 좌항 피연산자가 Falsy 값이면 좌항 피연산자를 그대로 반환하나 옵셔널 체이닝 연산자는 좌항 피연산자가 Falsy 값이라도 null 혹은 undefined 가 아니면 우항의 프로퍼티 참조를 이어간다. 중요한 차이점이다!
null 병합 연산자 ?? 는 좌항의 피연산자가 null 혹은 undefined 인 경우 우항의 피연산자를 반환하고 그렇지 않으면 좌항의 피연산자를 반환한다. 기본값 설정시 유용하다.
var foo = null ?? 'default string';
console.log(foo); // 'default string'
옵셔널 체이닝 연산자는 좌항 피연산자가 null 혹은 undefined 였을때 undefined 를 반환하고 그렇지 않으면 우항 피연산자를 반환하는데 null 병합 연산자는 좌항의 피연산자가 null 혹은 undefined 였을때 우항 피연산자를 반환하고 그렇지 않으면 좌항의 피연산자를 반환한다.
null 병합 연산자는 좌항 피연산자가 Falsy 값이라도 null, undefined 가 아니면 좌항의 피연산자를 그대로 반환한다.
세가지 방식의 차이점을 숙지해야 한다. 실제로 현업에서 위 세가지 방식을 자주 사용했던 기억이 난다.
prefix increment/decrement operator 전위 증감 연산자
: 먼저 피연산자의 값을 증가/감소시킨 후 다른 연산 수행postfix increment/decrement operator 후위 증감 연산자
: 먼저 다른 연산을 수행한 후, 피연산자의 값을 증가/감소시킨다.function 함수이름(파라미터) {
동작
return 리턴값
}
const 함수이름 = function(파라미터) {
동작
return 리턴값
}
const 함수이름 = (파라미터) => {
동작
return 리턴값
}
파라미터가 1개일때 파라미터를 감싸는 소괄호를 생략할 수 있고 파라미터가 2개 이상이거나 없을때는 반드시 소괄호를 써야 한다. 파라미터가 1개일 때도 소괄호를 사용하길 권장한다고 한다.
arrow function 은 함수의 중괄호가 return 문 하나로만 이루어져 있을시 중괄호와 return을 생략할 수 있다.
함수 선언과 동시에 즉시 실행되는 함수. 이름을 지어주더라도 외부에서 재사용할 수 없다. 단, 내부에서 재귀적으로 사용시에는 이름을 사용할 수 있다.
(function 함수이름(파라미터) {
동작
return 리턴값
})();
콜백함수 : 다른 함수에 파라미터로 전달된 함수. 함수는 값으로 취급될 수 있다. 이는 함수의 리턴값이 함수가 될 수도 있다는 것을 말한다.
고차함수 : 함수를 리턴하는 함수
함수 선언시 인자부분을 parameter 라고 하고 실제 함수를 호출시 파라미터로 전달하는 부분을 argument라고 한다.
function sayHello(name) { // name 은 인자를 전달하는 parameter
console.log(`${name} hello!`);
}
sayHello("james"); // james 는 실제로 전달되는 인자인 argument
rest parameter : 다수의 파라미터가 있을때 나머지 parameter 를 ... 로 나타낼 수 있다.
spread : rest parameter 가 여러 개의 값을 하나로 묶어준다면 Spread 는 하나로 묶인 값을 여러 개의 개별 값으로 나누는 역할을 한다.
javascript 에서 switch 문은 break 를 만나기 전까지 모든 구문을 실행하기 때문에 각 case 에 반드시 break 을 넣어줘야 한다.
3항 연산자 1줄로 나타낼 수 있다면 if else 문보다 3항 연산자를 사용하도록 해보자
if (score > 80) {
console.log("A");
} else {
console.log("B");
}
score > 80 ? console.log("A") : console.log("B");
const person = {
name: "John",
age: 30,
occupation: "Developer"
};
for (let key in person) {
console.log(key + ": " + person[key]);
}
진도가 정말 빠르게 나간다. 정리하고 체화하는 데만 한참 걸린 것 같다. 특히 이번 포스트를 정리하며 프로토타입과 프로토타입 체이닝에 대해 새로 알게 된게 기억에 남는다. Spread, rest parameter, 논리곱, 옵셔널 체이닝, null 병합연산자 등은 실무에서도 자주 사용했었는데 팀 프로젝트가 시작되면 이런 부분도 적극적으로 활용해봐야겠다. 어제 대표님의 이야기를 듣고 당일 배운 내용은 당일 정리하자는 작은 목표를 실현하고자 노력했다. 힘내보자.