뭐든지 처음 학습할 때는 많은 양의 정보가 방해가되고, 흡수도 되지 않음을 느낀다.
그래서 보통 getting start 가 서두에 있고, 이후에 상세한 명세서가 나오는게 아닐 까싶다.
최근에 라이브러리, 프레임워크를 공부하다보니 기초가 또 부족한느낌을 받았다.
이제는 많은 배경지식이 쌓여서, javascript 기초를 다시보면, 이전에는 보이지 않았던 것들이 보일거같아서
모던 자바스크립트를 정주행 해봤다.
아리송했던 개념들을 다시잡을 수 있었고, 초기 설계 실수로 해결하지 못하고 잔재하는 오류사항들이 몇몇있어서, javascript가 쉬우면서도 어려운 언어가 아닐가 싶다. 그래도 ECMAscript가 계속해서 발전하면서 새로운 문법이 추가된 것을 생각하면 javascript의 문제들을 해결하는 역사를 보고있는거 같아서 흥미롭기도하다. 이 게시글에서 간단하게 내가 해깔렸던 것과 새로알게된 점들을 정리해보고자 한다.
자바스크립트란?
자바스크립트가 왜 나왔는지는 다시한번 상기하면 좋을거같았다.
"웹 페이지에 생동감을 불어넣기 위해" 만들어진 언어이다.
또 이름에대한 재밌는 비화가 있는데, 처음엔 LiveScript 였다가, 당시 자바의 인기가 아주 높아, 홍보차원에서 Javascript라고 자바의 동생격인 언어로 변경하였다고 한다.
이름만은 자바에서 차용해왔지만, 꾸준히 발전을 거듭하면서 ECMAScript라는 고유 명세를 갖춘 독립적인 언어가 되었다.
자바스크립트는 어떻게 동작하는가?
웹 브라우저에서 동작하는 것이 기본이지만,
정확히 Javascript Engine이 있는 환경이라면 어떤 디바이스에서도 동작한다.
그 엔진의 종류는 다양한데 대표적으로 Chrome과 Opera, Node 에서 사용하는 V8 엔진이있다.
또다른 엔진은 사파리에서 SquirrelFish, Firefox에서는 SpiderMonkey, IE와 Edge에서는 Trident, Chakra 가 내장되어 있다.
엔진의 동작은 3단계를 걸친다.
1. 엔진이 자바스크립트를 읽는다 (파싱)
2. 파싱한 데이터를 기계어로 전환한다 (컴파일)
3. 기계어가 실행된다.
추가적으로 컴파일이 끝난 코드를 감시하면서, 계속 분석하며 이미 기계어로 전환된 코드를 다시 최적화 하기도한다.
브라우저에서 할 수 있는일
브라우저단에서 보안상의 이유로 메모리나, CPU, 파일시스템에 접근을 제한하는 제약사항이 있습니다.
반면에 운영체제에서 동작하는 node 환경에서는 같은 javascript언어임에도 파일시스템에 접근하거나, 네트워크 요청을 수행할 수 있다.
또한 웹개발자가 반드시 한번 쯤은 마주하는 CORS(Cross-Origin Resource Sharing) 정책도, Same Origin Policy 정책에 따라서 발생한다.
서로 다른 페이지에서 다른 페이지의 정보에 접근해 중요한 정보드를 훔치는것을 막기위함이다.
javascript를 이용하면 서버와 쉽게 정보를 주고 받을 수 있지만, 같은 origin 이 아닐때는 서버에서 명확히 승인을 해주어야한다.
이는 http 헤더등을이용해 승인할 수 있는데, 이것이 CORS를 허용하는 행위이다.
이런 제약사항은 모두 보안을 위해 만들어졌다.
HTML5 의 변화
브라우저에 js를 삽입하는 방법은 script 태그를 이용하는 것이다.
html 태그들에는 각각 속성값을 부여할 수 있는데, HTML4 까지는 script태그에 type을 명시해서, 자바스크립트임을 명시해줘야 했는데, HTML5로 넘어오면서 생략가능하게 변경되었다.
이점은 그렇게 중요한 요소는 아니게 느껴질 지라도, HTML이면 HTML이지 왜 끝에 5 라는 버전이 또 붙었는가를 생각해 보게 했고, HTML도 5버전이 되면서 시맨틱 태그들이 생기고, 무분별한 div의 사용을 자제하고, 의미있는 태그를 사용함으로써 접근성을 높이고, 웹표준을 지키는 것도 중요한 요소이기 때문에, HTML의 역사도 한번쯤 생각해보면 좋을 거같아서 짚고 넘어가본다.
또 웹개발 초기에 많이 실수했던 부분인데, sciprt태그의 src는 3가지로 생각해볼 수있다.
1. 상대경로
2. 절대경로
3. 외부 URL
이 경로를 잘 생각하지 않고 작업하다가, 어떤 환경에 배포했을때 찾지 못하는 문제를 종종 경험했었다. 특히나 github pages를 통해 뭔가를 배포해본 경험이있다면 알텐데, 가끔 경로가 로컬개발환경에선 잘되다가 안되는 경우가 있다.
Script 태그의 기능
단순히 가독성을 위해 분리할 수 도있지만
script태그를 별도의 파일로 작성하면 브라우저에서 스크립트를 다운받아 cache에 저장하는 이점이있다.
또 src 속성과 태그 내부의 코드는 동시에 존재할 수 없다. 만약 src 속성을 주었다면 태그 내부의 코드는 무시된다.
세미콜론은 선택?
자바스크립트나 파이썬을 해보면 세미콜론을 안붙여서 되서 처음엔 좋았지만, 그 행위가 코드를 읽기 힘들게 만들기도한다.
파이썬은 모르겠지만, javascript에서는 세미콜론이 자동으로 삽입되는 효과를 가지는데 이를 autometic semicolon insertion 이라고한다.
이렇게 자동으로 삽입되는 조건이 있는데, 대표적으로 return 구문이있다.
그런데 [] 앞에는 세미콜론이 있다고 가정하지 않는다. 그래서 만약 []를 사용하는데 이전에 세미콜론이 없다면, [] 구문이 앞의 문장과 구별되지 않는 문제가 발생해서 에러가 발생한다.
그러면 결국 이때는 명시적으로 세미콜론을 붙여줘야하게 되고, 전체적인 코드는 세미콜론이 있었다, 없었다 작성하게된다.
이는 좋지 못한 방법임으로, 모두 명시적으로 세미콜론을 붙이는 것이좋고, 프리티어나, eslint와 같은 도구들이 웹 개발자들이 협업하기 위해서는 거의 필수적으로 가져가기 때문에, 이때 자동으로 설정을 해주곤 한다.
use strict;
말그대로 엄격모드이다.
javascript 는 ECMAScript의 명세서가 변동됨에 따라 새로운 기능이 추가되기도하고, 기존의 기능이 변경되기도한다.
항상 이전버전의 하위호환을 지키기 때문에, 기존의 코드가 돌아갈 수 있도록 한다.
하지만 javascript의 언어차원의 버그를 줄이기 위한 노력으로 기존의 기능이 변경된 점이 있는데 이를 명시적으로 적용할 수 있도록 해주는 것이 use strict 구문이다.
대표적으로 선언되지 않은 변수가 전역변수로 설정되는 점과, this가 null 이나 undefined 일경우 window, global을 가르키도록 하는 점들이, 에러를 반환하도록 그리고 undefined를 명시하도록 한다.
또 이 구문은 전역적으로 반영할 수 도있지만, 함수단위로만 적용할 수 도있다. 그리고 한번 적용되면 돌이킬 방법은 없다.
그런데 클래스와 모듈을 사용하게 된다면 자동으로 use strict가 적용된다.
이미 코드에서 이 둘을 사용한다면 명시적으로 붙이지 않아도 적용된다.
자료형
이번에 등한시하고 있던 자료형이 BigInt와 Symbol 형이다.
BigInt는 단순히 숫자 뒤에 n을 붙여주면, 기존 number 보다 더 큰 범위를 표현할 수 있다.
symbol은 아직 사용할 필요성을 못느껴서 추후에 공부해보려고한다.
자바스크립트에서 null은 언어적 오류를 가지고있다.
null은 객체가 아니지만 typeof null 은 object를 반환한다.
이는 하위 호환성을 유지하기 위해서 수정하지 못하고있는 점이다.
형 변환
함수와 연산자에 전달되는 값은 대부분 적절한 자료형으로 자동변환이 된다.
문자형, 숫자형, 불린형 형변환이 있는데, 명시적으로 변환해주면 예상치못한 사이드 이펙트를 방지할 수 있지만,
자동 형변환을 잘 이해하고 있어야 좀더 깔끔한 코드가 나왔다.
문자열 형변환은 null은 "null" 이런식으로 예측가능한 방식으로 일어난다.
숫자형은 조금 주의해야한다.
undefined는 NaN이되고, null과 false, '' 는 0
true, 비어있지 않은 문자열은 1 이된다.
하지만 문자열은 또 trim이적용되고서 숫자가 아닌 값들이 있으면 NaN이된다.
불린형은 직관적으로 비어있다고 느껴지는 값들은 false이고 나머지는 true이다.
숫자0, '', null, undefined, NaN은 false이고 이외에는 다 true이다.
주의해야 할 점은 "0" 과 " " 는 true이다.
위에서 말한 false가 되는 규칙에 해당하지 않기 때문에
연산자
연산자에서 쓰이는 용어는 3가지가있다.
단항, 이항, 피연산자
피연산자를 하나만 받으면 단항(unary)
피연산자를 두개 받으면 이항(binary)
이항 연산중 + 는 둘중 하나라도 문자열이 있으면 문자열로 형변환을 하고 그 결과를 반환한다.
반면에 나머지 연산자들은 모두 숫자로 형변환해서 결과값을 반환한다.
대표적으로 "1" + 2 = "12" 가 나오는게 예이다.
그런데 또 주의할 점이있다. 이게 아니었으면 단항,이항이라는 개념을 대수롭지 않게 여겼을 텐데,
이항이 아닌 단항에서의 숫자가 아닌 형과 + 연산은 number 형태로 형변환을 한다.
+"" = 0
+true = 1
+"2" = 2
이 형태는 nestJS를 사용할 때 요청 param, query, body 값들이 모두 string형으로 오는데, 파이프를 연결해주지 않으면 형변환이 일어나지 않기 때문에, 간단히 +를 붙임으로서, 숫자값으로 변경하는게 보일러 플레이트로 들어가있다.
즉, Number() 와 +단항연산은 같은 효과를 볼 수 있다.
연산자 우선순위
이것도 단순히 수학연산과 동일하지만 특별히 생각해줘야할 것들이 있다.
= 연산이 가장 낮은 우선순위로 마지막에 할당되는 역할을 하는데, 반환은 계산된 값이 반환된다는 점을 알고있어야, 혹시나 다른사람 코드에서 사용된 괴상한 문법이 이해가 된다.
let a = 1;
let b = 2;
let c = 3 - (a = b + 1);
alert( a ); // 3
alert( c ); // 0
예를 들어서 위와 같은 상황이라면 a에 값을 할당하고, 괄호의 결과는 b+1을 반환한다.
또 복합할당 연산자 += , -= , *=, /= , %= 은 = 연산자의 우선순위와 같아진다. 즉, 거의 최 하위이다.
또 ++, -- 와같은 증가,감소 연산자는 변수에 만할당할 수있다. 5++ 이런건 안된다. 이때 반환값을 사용하지 않는 경우라면 전위형과 후위형엔 차이가 없다. ( ++count, count++ )
하지만 아래처럼 반환값을 바로 사용하는 경우에는 주의해야한다.
let counter = 1;
alert( 2 * counter++ ); // 2
쉼표 연산자
쉼표도 연산자였나? 하는 생각이 들었다.
쉼표는 = 연산자보다 우선순위가 낮다. 그렇기 때문에 = 과 함께 사용하려면 () 를 같이 써줘야 예상하는 결과를 얻을 수 있고,
마지막 표현식의 결과만 반환된다.
let a = (1 + 2, 3 + 4);
alert( a ); // 7 (3 + 4의 결과)
도데체 이 연산자는 어디에 사용될까 싶었는데, 바로 가까이 사용되고 있었다.
// 한 줄에서 세 개의 연산이 수행됨
for (a = 1, b = 3, c = a * b; a < 10; a++) {
...
}
이렇게 for문에서 begin 구문에 여러 변수를 선언할 때 사용할 수 있었다.
쉼표가 = 보다 우선순위가 낮기 때문에 각각의 변수에 선언이 먼저되고, 반환값은 어차피 사용되지 않으니, 한줄에서 여러 변수를 선언할 수 있는 효과를 낼 수 있다.
비교 연산자
비교연산자는 불린형을 반환한다.
다른건 몰라도 null 과 undefined, ===(일치 연산자) 이 세가지를 유의해야한다
먼저 문자열은 유니코드로 비교하고(사전순) 길이가 다르면 길이가 큰것이 크다고 판단한다.
유니코드이기 때문에 대소문자도 구분 소문자가 더 크다.
비교연산자는 비교하려는 값의 자료형이 다르면 모두 숫자형으로 자동 형변환을 한다.
숫자와 문자를 비교할 때는 문자가 숫자로 바뀌면서 연산이 된다.
불린의경우 true = 1 , false = 0 으로 변환된 후 비교를 한다.
재밌는 상황
let a = 0;
alert( Boolean(a) ); // false
let b = "0";
alert( Boolean(b) ); // true
alert(a == b); // true!
숫자0 과 문자 0은 각각 불린값으로 형변환하면 false와 true가 된다.
그런데 비교연산을 할때는 둘다 숫자로 변환되기 때문에, true가 나오는 아이러니한 상황이 발생한다.
이와 비슷하게 0과 '' 를 구분하지 못한다. 둘다 false와 같은값인데, 사실은 다른 값이다.
이 현상때문에 === 이라는 일치연산자를 쓰라고 eslint에서 항상 노라줄이 뜬다.
비교를 할때는 의도적이든 그렇지 않든 일치연산자를 쓰는게 안전하다고 생각한다.
일치연산자는 형을 자동변환하지 않고 검사하기 때문에, 형이다르면 다르다고 판별한다.
null 과 undefined 비교, 일치 연산
console.log( null === undefined ); // false
console.log( null == undefined ); // true
console.log( null >= undefined ); // false
결론부터 보자면 참 난감하다.
일치연산은 다른 자료형이기 때문에 false가 맞지만
분명 비교연산중 타입이 다르면 숫자형으로 변형한다고했는데,
null = 0, undefined = NaN 이됨으로 다르다.
이는 동등연산자 == 일때, 양 값이 null 이나 undefined일때는 형변환을 하지 않는다.
하지만 다른 비교연산자일때는 형변환이 이루어진다.
논리연산자
흔히 값을 비교할 때도 논리연산을 사용하지만, falsy한 값일때의 분기처리를 간단히 하기위해서 자주 사용된다.
주의할 점은 이 연산이 반환하는 값인데, 연산할 때는 불린형으로 변형하지만 반환할때는 원상태를 반환한다.
|| 연산자
목적은 최초의 truthy 한 조건을 찾는것이고, 왼쪽부터 연산이 이루어지면서, 앞에서 조건이 만족되면 연산을 멈추고 그 원형을 반환한다.
만약 truthy한 값이 하나도 없다면, 마지막 피연산자를 반환한다.
let firstName = "";
let lastName = "";
let nickName = "바이올렛";
alert( firstName || lastName || nickName || "익명"); // 바이올렛
&& 연산자
반대로 최초의 falsy 한 조건을 찾고, 찾으면 그 원형을 반환한다.
그렇지 않다면 마지막 피연산자를 반환한다.
그래서 가끔 리액트에서는 {렌더링을 원하는 값의 truthy조건 && 렌더링을 원하는 값} 의 형태로 조건부 렌더링을하는데,
둘다 falsy라면, 숫자 0이 렌더링되는 경우가 있었다.
! 연산자
피연산자를 불린형으로 변경하고 이를 활용해서 !! 두번쓰면, truthy, falsy한 값을 불린형으로 그대로 변경할 수 있다.
nullish 병합 연산자 ??
이 문법은 스펙에 추가된지 얼마 안되서 구식 브라우저에서는 폴리필이 필요합니다.
피연산자중 값이 확정되어있는 변수를 찾아 반환한다.
let firstName = null;
let lastName = null;
let nickName = "바이올렛";
// null이나 undefined가 아닌 첫 번째 피연산자
alert(firstName ?? lastName ?? nickName ?? "익명의 사용자"); // 바이올렛
위 코드처럼 앞서서 null, undefined가 아니면 계속 다음 피연산자로 넘어간다.
|| 와 마찬가지로 truthy한값을 찾지못하면 마지막 피연산자를 반환하는데
그럼 어떤 차이가있을까?
?? 연산자는 반드시 null, undefined일때만 false로 취급한다.
실질적으로 차이나는 점은 숫자 0을 구분한다.
?? 연산자는 && 와 || 와 함께 사용할 수 없으며 함께 사용하기 위해서는 () 를 이용해야한다.
보통 변수에 기본값을 할당할 때 사용된다.
// height가 null이나 undefined인 경우, 100을 할당
height = height ?? 100;
반복문
break 나 continue 구문을 삼항연산자에서 사용할 수 없다.
이점이 일반 if문을 ? 연산자로 의미없이 대체해서 사용해선 안되는 이유 중 하나이다.
(i > 5) ? alert(i) : continue; // 에러
이번에 공부하면서 엄청 유용한 사실을 하나 알게됬는데
반복문앞에 레이블(label) 식별자를 지정할 수 있어서
2중, 3중 반복문에서 특정한, 최상위 반복문을 break , continue로 제어할 수 있다.
outer: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
let input = prompt(`(${i},${j})의 값`, '');
// 사용자가 아무것도 입력하지 않거나 Cancel 버튼을 누르면 두 반복문 모두를 빠져나옵니다.
if (!input) break outer; // (*)
// 입력받은 값을 가지고 무언가를 함
}
}
유용하게 쓸수 있을듯 하다.
Switch 문
자료형과 값 모두 같아야 동작한다.
함수
함수의 파라미터는 언제나 복사한 값을 사용한다.
그래서, 원시타입을 넘겨서 조작할때는 불변성을 고려할 필요가 없지만,
객체의 경우엔 값이 복사되기 때문에, 불변성을 고려해야한다.
매개변수에 값을 전달하지 않으면 undefined가 된다.
지역변수를 선언한것과 같기 때문에, 선언만하고 할당하지 않은 상태이기 때문이다.
return 구문이 생략되있거나 reuturn; 만 한다면 undefined를 반환한다.
자바스크립트는 return 구문을 만나면 자동으로 ; 세미콜론을 넣기 때문에, 개행을 포함하려면 ()를 써야한다.
함수표현식
자바스크립트에서 함수는 특별한 종류의 값으로 취급한다.
다른언어에서는 특별한 동작을 하는 구조로 취급하기 때문에 조금 다르다.
그래서 자바스크립트에선는 독특하게 함수를 변수에 담을 수 있고, callback 이라는 개념이 있다.
표현식을 사용할 수 있음으로서 익명함수(anonymous function) 이 존재한다.
두가지의 중요한 차이중 하나는 언제 함수를 생성하느냐 인데,
함수 선언식은 프로그램이 동작할때 가장먼저 생성하고, 표현식은 일반 변수와 동일하게 순차적으로 코드를 읽어들였을때 생성된다.
* var 도 선언문을 모두 취상위로 올린다. 이를 호이스팅이라한다.
function의 장점은 선언되기 이전에, 어디서든지 사용할 수 있다는 장점이있다.
이는 장점이자 단점으로 작용할 수 도 있다. 다른사람이 코드를 읽을때 선언된적이 없는 함수를 찾아 이동을 해야하기 때문에, 흐름을 파악하는데 어려울 수 있다.
airbnb의 리액트 린트설정을 보면 선언문을 항상 먼저 위치하도록 강제한다.
이렇게 적고나니 반성하게되는거같다.
이제 까지 밥먹듯 쓰던 문법들을 정확히 알지 못하고 쓴, 야속한 세월들..
처음 프론트엔드 개발을 시작할 때 기술이 끊임없이 변한다는 것을 잘 이해못했는데,
내가 처음 개발을 시작할때와 지금을 사이에도 리액트도 한버전이 업데이트되고, react-router-dom 도 버전이 올라가고, 시간라이브러리는 moment를 많이 쓰다가 지금은 dayjs가 트렌드이고..
이젠 진짜 체감하고있다
절때 마음에서 버려야할 마인드가 하만 쭉 판다는 생각인것 같다.
다양하게 배우고, 받아들이면서 필요한걸 그때그때 내것으로 만들수 있는 능력이 필요하다고 느낀다.
그래서 더욱이 javascript를 이해하고있어야하고, 기초가 중요한거 같다.
'Archive' 카테고리의 다른 글
객체 (0) | 2022.08.29 |
---|---|
다시보는 babel 과 polyfill (0) | 2022.08.28 |
3시간동안의 과제 테스트를 끝내고 (0) | 2022.08.27 |
EC2 Ubuntu timezone 설정하기 (0) | 2022.08.17 |
github actions SSH + EC2 CICD 파이프라인 구축(삽질 10시간이상) (0) | 2022.08.05 |