- 아래 질문에 대한 대답 정리하기
- 각 질문에 대한 느낀 점도 포함하기
- 어디에 쓸 수 있을지
- 어떤 특징이 이러이러한 것 같다 등
- bit란 binary digit로 컴퓨터에서 데이터의 가장 작은 단위이다. 0과 1을 사용하여 2진법으로 표현한다.
- 비트 연산자는 Bitwise operators라고 하며 비트를 조작하는 연산자를 지칭한다.
- 부호가 있는 정수는 가장 왼쪽 첫 번째에 있는 최상위 비트로 음수를 표현한다. 0은 양수, 1은 음수를 나타낸다. 부호를 표현하는 비트를 제외한 나머지 비트로 정수의 값을 표현한다.
- 구하려는 음수의 절대값의 2진 표현을 구한 후 이 숫자에 대해 1의 보수를 구한다. 1의 보수는 1은 0으로, 0은 1로 바꾸면 된다.
- 위의 결과에 1을 더한다.
- 예를 들어 6비트로 -18을 표현해보자.
- -18의 2진 표현을 구하기 위해 절대값 18의 2진 표현인 010010의 1의 보수를 구한다.
- 010010의 1의 보수는 101101이다.
- 101101에 1을 더한 101110이 -18의 6비트 정수 표현이다.
~a와 같이 틸데(~)를 사용하며, 피연산자의 1의 보수를 반환한다. 즉, 비트를 뒤집는다.~010010 => 101101
a & b처럼 앰퍼샌드(&)를 사용하여 두 개의 피연산자를 취한다.- 두 피연산자의 대응되는 비트가 모두 1이면 1을 반환하고, 아니면 0을 반환한다.
1010 & 1001 => 1000
a | b와 같이 파이프(|)을 사용하여 두 개의 피연산자를 취한다.- 두 연산자의 대응되는 비트 중 하나라도 1이면 1을 반환하고, 아니면 0을 반환한다.
1010 | 1001 => 1011
a ^ b와 같이 캐럿(^)을 사용하여 두 개의 피연산자를 취한다.- 두 연산자의 대응되는 비트가 서로 다를 때 1을 반환, 그 외에는 0을 반환한다.
1010 ^ 1001 => 0011
a << b와 같이 <<를 사용하여 두 개의 피연산자를 취한다.- 좌항의 모든 비트를 우항의 숫자만큼 왼쪽으로 움직이고, 오른쪽은 0으로 채운다. 부호는 유지한다.
1010 << 2 => 101000
a >> b와 같이 >>를 사용하여 두 개의 피연산자를 취한다.- 좌항의 모든 비트를 우항의 숫자만큼 오른쪽으로 움직이고, 부호는 유지한다. 왼쪽은 부호 비트로 채우고 오른쪽 남는 비트는 버린다.
11010 >> 2 => 11110
a >>> b와 같이 >>>를 사용하여 두 개의 피연산자를 취한다.- 좌항의 모든 비트를 우항의 숫자만큼 오른쪽으로 움직이고, 왼쪽은 0으로 채운다. 오른쪽 남는 비트는 버린다.
11010 >>> 2 => 00110
- 어디에 어떤 용도로 사용할 수 있는지는 잘 모르겠지만, 컴퓨터 내부에서 이루어지는 정수 연산 방법을 이해하게 된 것 같다.
- 10을 2로 나눠 몫과 나머지를 구한다.
- 1에서 구한 나머지를 결과 배열에 unshift 한다.
- 1에서 구한 몫으로 1, 2번을 반복한다.
- 몫이 2보다 작을 때까지 반복한다.
- 2보다 작아진 마지막 몫의 값을 결과 배열에 마지막으로 unshift 한다.
- 결과 배열을 순서대로 출력한다.
10진 표현을 2진 표현으로 변경하는 방법을 알게 됐고 코드로 구현하는 방법도 명확하게 알게 되었다. 알고리즘 문제로 나오면 금방 풀 수 있을 것 같다.
- 자바스크립트에서 hoisting이란 변수와 함수의 선언이 그들이 포함된 scope의 최상단으로 끌어올려지는 것을 의미한다.
- scope는 전역(global) 또는 지역(local) 범위가 있으며 자바스크립트에서 local은 대부분의 경우 함수 범위를 말한다.
- 변수는 식별자의 선언만 호이스팅되기 때문에 값 할당 구문 이전에 변수에 접근하게 되면 var로 선언된 변수의 경우 undefined 값을 갖고 있다.
- let 또는 const로 선언된 변수 또한 호이스팅 되나, var와는 다르게 값 선언 구문 이전에 변수에 접근하게 되면 ReferenceError를, 선언 후 할당 전에 접근하게 되면 undefined 값을 출력한다.
- 함수 선언식의 경우 함수 전체가 호이스팅되며, 함수 표현식은 변수 호이스팅과 마찬가지로 함수를 할당하려는 변수의 식별자만 호이스팅 된다.
- 위와 같은 이유로 함수 표현식으로 선언된 함수(특히 var)를 함수 할당 이전에 호출하게 되면 함수가 아니라는 TypeError가 출력된다.
자바스크립트에서 변수와 함수가 호이스팅 되는 특징은 변수와 함수가 어디서 선언되든 상관없이 접근하고 사용할 수 있다는 장점도 있지만, 코드의 실행 결과를 예측하기 어렵게 하고 잠재적 오류의 가능성을 높인다는 단점도 있다. 그래서 함수 표현식을 포함한 변수는 최대한 선언과 초기화 후에 접근 및 사용하는 것이 좋다.
자바스크립트에서의 변수와 함수의 내부 동작 방식을 어느 정도 이해할 수 있게 되었고 왜 변수를 선언, 초기화 한 후 사용해야 하는지, 함수 선언식과 표현식의 차이가 무엇인지에 대해서도 알 수 있게 되었다. 이를 통해 잠재적 오류를 좀 더 쉽게 잡아낼 수 있을 것 같다.
!!(double exclamation)은 논리연산자 NOT(!)을 두 번 쓴 것으로, boolean type이 아닌 값을 boolean type으로 바꿔주는 효과가 있다.- Boolean data type이란 true 또는 false의 두 가지 값만 갖는 데이터 타입이다.
- 어떤 값에 논리연산자 NOT을 적용하면 그 값의 반대되는 boolean 값을 얻을 수 있지만,
!를 두 번 적용하게 되면 한 번 더 NOT이 되므로 원래 값의 boolean 값을 얻을 수 있는 것이다.!5 === false!!5 === true
- 즉,
!!value는Boolean(value)와 같은 효과가 있다.!!5 === Boolean(5)
!은 자주 썼었지만 !!은 사실 처음 봤는데 어떤 값의 boolean 값을 얻고 싶을 때 편리하게 사용할 수 있을 것 같다.
삼항 연산자는 conditional operator 또는 ternary operator라고 하는데, 자바스크립트의 연산자 중 유일하게 3개의 피연산자를 갖는다.
다음과 같은 3개 이상의 case를 갖는 switch 문이 있다고 하자.
const value = 4;
switch (value) {
case 1:
console.log('one');
break;
case 2:
console.log('two');
break;
case 3:
console.log('three');
break;
default:
console.log('nothing');
}value 값에 따라 다른 결과를 출력하는 간단한 예제인데, 이 코드를 삼항 연산자로 바꾸어 똑같이 동작하도록 만들 수 있다. 삼항 연산자로 바꾼 코드는 아래와 같다.
const value = 4;
value === 1 ? console.log('one')
: value === 2 ? console.log('two')
: value === 3 ? console.log('three')
: console.log('nothing');3가지 case일 때 뿐만 아니라 그 이상의 case가 있어도 위와 같이 삼항 연산자를 이용하여 코드를 작성할 수 있다.
2개의 케이스가 있을 때만 삼항 연산자를 사용했었는데 3개 이상의 케이스에서도 삼항 연산자를 사용할 수 있다는 걸 새로 알게 됐다. 또, if-else 문 뿐만 아니라 switch 문도 삼항 연산자로 바꿔 쓸 수 있음을 알 수 있었다. 코드 작성 중 적합한 경우에 삼항 연산자를 잘 이용하면 더욱 간결한 코드를 작성할 수 있을 것 같다.
==는 Double equals로 느슨한 같음(abstract comparison)을 비교한다.==는 비교하려는 두 값이 type이 다르면 비교 가능한 같은 type으로 강제 형 변환을 한 후 엄격한 비교를 한다.===는 Triple equals로 엄격한 같음(strict comparison)을 비교한다.===는 비교하려는 두 값의 type과 value가 모두 같을 때 true를 반환한다.
- 0과 ''(빈 문자열)은 false와 같다.
0 == false'' == false
- null과 undefined는 서로 같지만 다른 것들과는 같지 않다.
null == undefined
- NaN은 자기 자신인 NaN을 포함하여 그 어떤 것과도 같지 않다.
- Infinity는 true처럼 사용되지만 true나 false와 비교할 수 없다.
- 빈 배열 []은 true처럼 사용되지만 0, '', false와 비교하면 같다.
[] == 0[] == ''[] == false
- NaN은 자기 자신인 NaN과도 비교할 수 없다.
==이 값을 비교하기 전에 강제로 형 변환을 한다는 사실을 새로 알게 되었고 ==에 예측하기 어려운 예외상황이 많다는 것도 알게 되었다. 웬만하면 ===을 쓰라는 말만 들었지 왜 그런지 정확히 이해하지 못했는데 이번에 알아보면서 이유를 체감하게 된 것 같다. 앞으로 최대한 ===만 쓸 수 있도록 하고, 어쩔 수 없이 ==을 사용해야 하는 상황이 오면 결과를 확실히 확인한 후 사용해야겠다.
||은 Logical OR라고 하며 논리연산자 중 하나로, 비교하는 두 개의 피연산자 중 하나만 true여도 true를 반환하고 모두 false이면 false를 반환한다.const value = a || b;는 위와 같은 원리를 이용하여 변수 초기값을 할당하는 방법이다. a와 b가 boolean 타입의 변수라면 원래 작동 방식대로 true나 false를 반환하겠지만, boolean이 아닌 평가할 수 있는 다른 타입의 값이라면 다음과 같이 작동한다.- true로 평가할 수 있는 값이 있다면 true로 평가할 수 있는 첫 번째 값을 반환한다.
undefined || 1은 1을 반환true || 2는 true을 반환
- 모든 값이 false로 평가된다면 가장 마지막 값을 반환한다.
null || 0은 0을 반환
- true로 평가할 수 있는 값이 있다면 true로 평가할 수 있는 첫 번째 값을 반환한다.
- a와 b의 자리에는 변수뿐만 아니라 함수나 수식과 같은 표현도 쓸 수 있고, 반환 값을 통해 같은 방식으로 평가된다. 또한 객체는 항상 true로 평가된다.
(x = 0) || (x = 1)는x = 1의 결과인 1을 반환
- 논리연산자는 결과를 확정 지을 수 있을 때 평가를 멈추고 결과를 바로 반환하는 '단축 평가(Short-circuit evaluation)'를 수행한다.
||은 피연산자 중 하나만 true여도 평가 결과가 true이기 때문에, 왼쪽부터 평가를 시작해서 true로 평가되는 피연산자를 만나면 더이상 진행하지 않고 true로 평가를 종료한다.true || (x = 1)는 첫 번째 인자가 true이기 때문에 다음 인자인x = 1을 수행하지 않고 바로 true를 반환한다. 따라서 변수 x에는 1이 할당되지 않는다.
논리연산자 ||은 종종 사용하던 것이었는데 좀 더 명확하게 작동 방식을 알게 되었고, 단축 평가를 수행하여 생각보다 효율적으로 동작하고 있다는걸 알게 되었다. 사용하려는 어떤 값의 단순한 유효성 검사와 할당에 유용할 것 같고 간단한 if 문을 대신해서 더욱 간결하게 사용 할 수 있을 것 같다.
- eval()은 문자열로 된 코드를 평가하고 수행한 후 결과값을 반환하는 함수이다.
eval('2 + 2') === 4
eval 함수는 동적으로 코드를 생성하고 실행하는 편리하고 쉬운 방법이지만 아래와 같은 여러 단점들이 있기 때문에 최대한 사용을 피하는 게 좋다.
- eval 함수는 eval이 접근할 수 있는 scope내의 변수들을 너무 쉽게 변경시킬 수 있기 때문에 코드의 예측을 어렵게 한다.
- 디버깅이 어렵다. (eval함수로 사용되는 코드는 줄번호가 없다.)
- eval 함수는 최신 자바스크립트 엔진에 최적화되어있지 않기 때문에 매우 느리다.
- eval 함수 사용이 보안을 취약하게 만들 가능성이 있다. (사용자 입력에 관한 보안 이슈가 있다.)
-
동적으로 객체 속성에 접근할 때 속성 접근자를 사용하기
eval( 'var result = obj.' + propName );대신var result = obj[ propName ]; -
짧은 코드를 평가하고 수행할 때 함수 생성자 또는 함수를 사용하기
-
eval 대신 함수 생성자 사용
function looseJsonParse(obj){ return eval(obj); } console.log(looseJsonParse( "{a:(4-1), b:function(){}, c:new Date()}" ))
function looseJsonParse(obj){ return Function('"use strict";return (' + obj + ')')(); } console.log(looseJsonParse( "{a:(4-1), b:function(){}, c:new Date()}" ))
-
문자열 대신 함수 사용
setTimeout(" ... ", 1000);대신setTimeout(function() { ... }, 1000);
-
-
JSON 데이터를 다룰 때 JSON.parse를 이용하기
eval 함수는 이번에 처음 알게 됐는데 어떤 상황에 어떤 방식으로 편리하게 쓸 수 있는지는 잘 모르겠지만, 단점이 많은 함수인 것 같아서 혹시 사용할 일이 있어도 최대한 다른 방식으로 문제를 해결해야겠다는 생각이 들었다. 하지만 eval 함수가 과거의 흔적(?)으로 생각보다 많은 곳에 사용돼있다고 하니, 이번 공부가 레거시 코드를 해석하거나 수정해야 할 일이 생겼을 때 유용할 것 같다.
null 은 자바스크립트에서 일반적으로 어떤 객체 값의 '의도적인 없음'을 나타내는 값이다. 따라서 다음과 같은 경우를 가진 변수에 접근했을 때 null 이 출력된다.
-
고의로 변수의 값이 '없음'을 표현하기 위해 변수에 직접
null을 할당했을 때let value = null; // value is null
-
특정 조건에 해당하는 객체의 값을 찾지 못했을 때 반환
let value = 'abc'.match(/[0-9]/); // value is null let saveButton = document.getElementById("save"); // saveButton is null
undefined 는 일반적으로 값이 할당되지 않은 변수의 기본값으로 사용된다. 자바스크립트에서는 많은 경우에 undefined 가 출력되는데 대표적으로 다음과 같은 경우에 undefined 가 출력된다.
-
선언만 되고 아직 초기값이 할당되지 않은 변수에 접근했을 때
let value; console.log(value); // value is undefined
-
함수에 정의된 매개변수에 인자가 들어오지 않는 경우
function sum(val1, val2) { return val1 + val2; // val2 is undefined } sum(5);
-
함수에서 어떤 값도 반환하지 않는 경우
function sum(val1, val2) { let result = val1 + val2; } let result = sum(5, 5); // result is undefined
"x" is not defined 는 ReferenceError 타입의 하나로 전체 에러 메세지는 ReferenceError: "x" is not defined 이다. 존재하지 않는 변수를 어딘가에서 참조했을 때 나타나는 에러 메세지이다. 대표적으로 다음과 같은 경우에 이 에러 메세지를 출력한다.
-
변수가 선언되지 않았을 때
console.log(value); // ReferenceError: value is not defined let value;
-
잘못된 scope에서 접근했을 때
function numbers() { let num = 1; return num; } console.log(num); // ReferenceError: num is not defined.
null 과 undefined 에 대해서 명확히 알게 되었고, 어떨 때 is not defined 라는 에러를 마주치는지도 알게 되어서 프로그램을 구현하며 생기는 문제를 좀 더 쉽게 해결할 수 있을 것 같다. 또, 변수를 선언한 후 최대한 초기값을 할당하고 사용하는 게 문제를 발생시킬 확률을 낮춰줄 수 있을 것 같다고 생각했다.
-
add 함수는 '다음 인자를 받는 함수'를 반환하는 함수이다.
-
add 함수에서 반환되는 함수는 입력받은 2개의 인자를 계산하고 반환한다.
-
위의 내용을 코드로 구현하면 다음과 같다.
function add(num1) { return function (num2) { return num1 + num2; } } const result = add(10)(2); // result is 12
이처럼 add 함수에서 반환된 익명 함수에서 지역 scope를 벗어난 변수 num1에 접근하고 사용할 수 있는 이유는 closure 개념 때문이다. closure란, 어떤 함수가 생성되는 시점에 존재하는 execution context와 lexical environment를 포함하는 접근할 수 있는 모든 환경을 뜻한다.
-
위의 코드를 arrow function으로 다음과 같이 간결하게 바꿀 수 있다.
const add = num1 => num2 => num1 + num2; const result = add(10)(2); // result is 12
이번 질문은 이미 예전에 pipe 함수를 통해 함수를 반환하는 함수를 연습해 본 적이 있어서 쉽게 해결할 수 있었다. 하지만 closure는 여러 번 본 개념임에도 불구하고 아는 것 같았지만 짧고 명확하게 설명하기가 어렵다고 느꼈다. closure와 관련 있는 개념들에 대해 기회가 될 때 좀 더 자세히 공부해보고 싶다.
- 함수에 선언된 파라미터만큼만 인자를 받고 나머지는 무시된다.
- 'arguments' 라는 객체를 사용하면 입력된 모든 인자에 접근할 수 있다.
- 'arguments' 는 배열 같은 구조이지만 배열은 아니므로 배열 메소드를 사용할 수 없다.
- Rest parameter는 배열이므로 'arguments' 대신 Rest parameter를 사용하면 배열 메소드를 사용하여 데이터를 편리하게 다룰 수 있다.
- 함수에서 입력받은 인자와 대응되지 못한 파라미터는 값이 undefined로 할당된다.
- 대응되지 못할 가능성이 있는 파라미터는 기본값을 설정해주는 게 좋다.
원래 알고 있던 내용이지만 확실하게 정리할 수 있어서 좋았고, 가능한 함수의 파라미터는 받으려는 인자 개수와 맞춰서 선언하고 사용하는 게 좋을 것 같다고 생각했다. 또한, 받으려는 인자 개수의 변경 가능성이 있는 경우에는 arguments 객체보다는 ES6에서 추가된 Rest parameter를 사용하면 유용할 것 같다.
자바스크립트에서 함수는 항상 값을 반환한다. 보통 return 문을 이용하여 값을 반환하는데, return 문에는 다음과 같은 특징이 있다.
- 함수 내부에서 return 문을 만나면 함수는 더 이상 실행되지 않고 종료된다.
- 특정 값과 함께 return 문이 사용됐다면 함수는 그 값을 반환하며 종료한다.
- return 문에 어떤 값도 주어지지 않고 사용됐다면 함수는 undefined를 반환하며 종료한다.
함수 안에 return 문이 사용되지 않은 경우에도 함수 내 모든 구문이 실행된 후 undefined를 반환하며 종료된다.
함수의 반환 값이 없을 때 undefined를 반환하고, 특별한 값 없이 return만 사용해도 undefined를 반환하기 때문에 어떤 함수에서 특별히 반환할 값이 없다면 굳이 return 문을 사용하지 않아도 되겠다고 생각했다. 또, 자바스크립트에서 undefined는 보통 false로 처리되기 때문에 함수에서 아무런 반환 값이 없을 때 초기화되지 않은 변수와 마찬가지로 알아서 undefined를 반환해주는 게 편리한 것 같다고 생각했다.
익명 함수란 영어로는 Anonymous functions라고 하며, '이름이 없는 함수'들을 말한다.
-
함수 표현식 (Function expression)
const add = function (param1, param2) { return param1 + param2; }
-
화살표 함수 (Arrow functions)
const add = (param1, param2) => param1 + param2;
-
즉시 실행 함수 (IIFE, Immediately Invoked Function Expression)
(function () { console.log('IIFE'); })();
- 익명 함수는 이름이 없기 때문에 짧고 간결하여 편리하게 사용할 수 있다.
- 익명 함수는 재사용 가능성이 없는 일회성 함수를 표현할 때 유용하다.
- 익명 함수는 보통 아주 짧고 간단한 함수나, 콜백 함수, 즉시 실행시킬 함수에 적당하다.
- 익명 함수는 편리하지만 이름이 없기 때문에 디버깅이 어려울 수 있다.
익명 함수를 어떤 경우에 쓰면 유용하게 사용할 수 있을지 알게 되었다. 특히 즉시 실행 함수는 주로 한 번만 실행될 외부 코드(라이브러리나 프레임워크와 같은)를 가져다 쓸 때 전역 네임스페이스를 오염시키지 않고 사용할 수 있기 때문에 유용하다는 것을 알 수 있었다. 그러나 익명 함수를 이유 없이 무분별하게 사용하게 될 경우 문제 발생 시 디버깅이 어려워질 수 있기 때문에 용도에 맞게 적절히 사용하는 게 좋을 것 같다.