부트캠프/자바스크립트 완벽 가이드

4장. 표현식과 연산자 - 단축 평가, 비트 NOT(~), 2진수 음수 변환

하이고니 2022. 12. 29. 16:42
  • 표현식은 자바스크립트 프로그램의 구절(phrase)과 같다.
  • 표현식은 모두 자바스크립트의 '값'으로 평가될 수 있다.
  • 표현식에는 값으로 평가되는 것 외에도 변수 할당 같은 부수 효과가 있을 수 있다.
  • 리터럴, 변수 참조, 프로퍼티 접근 같은 단순한 표현식을 연산자로 묶어서 더 큰 표현식을 만들 수도 있다.
  • 연산자는 크게 산술 연산, 비교, 불, 논리, 할당, 비트 조작 연산자로 나눌 수 있고, 그 외 조건 연산자를 비롯한 기타 연산자가 더 있다.
  • + 연산자는 숫자를 더할 때나 문자열을 병합할 때 사용할 수 있다.
  • 논리 연산자 &&||에는 때에 따라 피연산자 중 하나만 평가하는 특별한 '단축 평가' 방식이 있다. 

단축 평가

getLastElementOfProperty

문제

객체와 키를 입력받아 키에 해당하는 값이 배열인 경우, 마지막 요소를 리턴해야 합니다.

입력

인자 1 : obj

  • 임의의 속성을 갖는 객체

인자 2 : property

  • string 타입의 키

출력

  • 배열의 요소를 리턴해야 합니다.

주의 사항

  • 주어진 키에 해당하는 값이 배열이고, 빈 배열이 아닌 경우에만 배열의 요소를 리턴해야 합니다.
  • 그 외의 경우, undefined를 리턴해야 합니다.

입출력 예시

const obj = {
  abc: [1, 2, 5],
};

let output = getLastElementOfProperty(obj, 'abc');
console.log(output); // --> 5

 

 

 

 

정리하면, getLastElementOfProperty는 '객체'와 그 객체에 포함된 '키의 이름'을 입력값으로 받는 함수다.

그리고 객체 안에 그 '키의 이름'을 갖는 key가 있고, 그 key의 value가 배열이라면 그 배열의 마지막 요소를 리턴한다.

 

 

나의 코드

function getLastElementOfProperty(obj, property) {

    let prop = obj[property];

    if (prop.length === 0 || Array.isArray(prop) === false) {
      return undefined;
    } 
    return prop[prop.length - 1];
}

 

1. obj라는 객체 안에, property라는 이름을 갖는 키의 value를 prop이라고 선언한다. 

2. prop이 빈 배열이거나, 아예 배열이 아닐 경우에 함수는 undefined를 반환한다.

3. 위 두가지 예외가 아닐 경우, 함수는 prop이라는 배열의 가장 마지막 element 를 반환한다.

 

 

이런 구조라고 생각했다.

 

꽤나 잘 짰다고 생각했지만 prop.length 를 검사하는 순간 에러가 발생했다.

 

 

 

 

let prop = obj[property] 에서 obj에 키가 'arr'인 프로퍼티가 없으므로
propundefined가 할당되는데, undefindedlength라는 프로퍼티가 없어서 검사하려 하면 에러가 뜬다.

 

 

 

(p. 73 '프로퍼티를 가질 수 없는 값')

어떤 스타일의 프로퍼티 접근 표현식을 쓰든 .이나 [ 앞에 있는 표현식을 첫 번째로 평가한다. 그 값이 null이나 undefined이면 이 둘은 프로퍼티를 가질 수 없는 값이므로 표현식은 TypeError를 일으킨다. 

 

 

 

처음에는 에러를 해석할 생각을 못하고 막 헤매다가 혹시나 하는 마음에 if 문 안에 있는 두가지 조건의 순서를 바꿔보았는데,

오류없이 테스트가 통과됐다.

 

 

나는 도무지 이해할 수 없었다. OR 연산자에 순서가 있는 것인가??????

 

순서가 있었다.

 

논리 표현식 &&||은 왼쪽 것부터 연산한다. 순서 상 오류가 존재하는 값부터 평가했기 때문에 오류가 난 것이다.

 

 

(p. 98 'OR 연산자의 단축 평가')

OR 의 왼쪽 부분이 falsy한 값이면  OR 연산자는 오른쪽 부분을 평가하고 그 값을 표현식의 값으로 반환한다.
하지만
  OR 왼쪽 부분이 truthy한 값이면 단축 평가가 일어나서 오른쪽에 있는 표현식은 평가하지 않고 바로 truthy한 값을 반환한다.

 

function getLastElementOfProperty(obj, property) {

    let prop = obj[property];

    if (Array.isArray(prop) === false || prop.length === 0) {
      return undefined;
    } 
    return prop[prop.length - 1];

}

 

prop.length 부터 평가할 경우

 

여기서는 prop.length부터 평가하고, 에러가 발생한다.

 

배열인지부터 평가할 경우

 

여기서는 배열인지부터 평가하고, truthy한 값이 나왔기 때문에 단축평가가 일어나서 바로 undefined를 반환한다.

오류가 발생하는 부분인 prop.length는 아예 평가하지 않는다.

 

 

사실 위 문제에서 prop.length === 0는 테스트할 필요가 없었다. 아래의 코드를 보자.

 

function getLastElementOfProperty(obj, property) {
    let prop = obj[property]
    if(Array.inArray(prop)) {
    	return prop[prop.length - 1];
    }
}

 

함수는 리턴 명시된 것이 없으면 undefined를 리턴한다.

 

 

(p. 75)

호출 표현식을 평가할 때는 첫 번째로 함수 표현식을 평가하고, 그 다음으로 함수 인자 표현식을 평가해 인자 값 리스트를 만든다. 함수 표현식의 값이 함수가 아니라면 TypeError가 일어난다. 그리고 함수를 정의할 때 지정된 매개변수(parameter)에 인자 값을 순서대로 할당한 다음, 함수 바디를 실행한다. 함수가 return 문을 사용해 값을 반환한다면 그 값이 호출 표현식의 값이다. 그렇지 않다면 호출 표현식의 값은 undefined 이다.

 

 

배열이 아닐 경우에는 리턴되는 것이 없어서 undefined,

배열의 길이가 0일 경우에는 prop의 인덱스 prop(prop.length - 1)이 존재하지 않아서 undefined 가 뜬다.

 

 

이 부분은 이번 챕터와는 큰 관련이 없지만

예외 사항에 대해 항상 따로 조건문을 만들어서 해결하는 게 아니라,
위의 코드처럼 한 방에 해결될 수 있는 것인지 체크하는 습관을 기르면 좋을 것 같아서 적는다.


비트 연산자

(p. 87)

 

왜 0과 1을 다뤄야 하는지, 또 이게 실무에서 자주 쓰이는지는 잘 모르겠다.

블로그 몇 개 뒤져본 바로는, 여러개의 불값을 계속 비교해야 할 때 유용하게 쓰일 것 같다.

 

아래의 내용은 모두 2진수를 기준으로 한다. 다른 진법의 수일 때는 0x1234 to binary 같은 방식으로 검색해서 2진수로 생각.

 

비트 AND(&): 모두 1일 때만 1, 나머지는 0

비트 OR(|): 하나라도 1이면 1, 둘다 0이면 0

비트 XOR(^): 둘이 다를 때는 1, 둘이 같을 때는 0

왼쪽 시프트(<<): 모든 비트를 왼쪽으로 한 칸 이동. 가장 왼쪽 비트는 버리고 새로 생긴 마지막 자리에는 0을 쓴다.

 

ex) 00001010 << 2 => 00101000

한 자리 이동하면 2를 곱하는 효과. 두 자리 이동하면 4를 곱하는 효과.

 

오른쪽 시프트(>>): 모든 비트를 오른쪽으로 한 칸 이동. 밀려난 비트는 그냥 버린다. 비는 자리들은 원래의 부호로 채운다.

 

ex)

00001100 >> 2 => 00000011

11111111 >> 2 => 11111111

 

2진수에서 맨 왼쪽 자릿수는 부호를 나타낸다. 0이면 +, 1이면 -

 

ex)

1000010 은 '-2'

00001010 은 '+10' 

 

0으로 채우는 오른쪽 시프트(>>>): 위와 같지만 비는 자리들을 무조건 0으로 채운다.부호 붙은 32비트 값을 부호 없는 정수처럼 취급할 때 유용하다.

 

11111111 >>> 2 => 00111111

 

 

비트 NOT(~): 1은 0으로, 0은 1로 바꾼다.

근데 이게 뭐 피연산자의 부호를 바꾸고 1을 뺀 것과 동일하다는데 왜 그런지 설명이 없다.

 

ex) 15 => ~0x0F => ~ 0x0000000F => 0xFFFFFFF0 => -16

 

NOT 연산을 하면 피연산자의 부호가 바뀌고 거기에 1을 뺀 것과 같다고 한다.

 

이건 책에서 나온 예시인데 뭔 말인지를 모르겠다.

 

32비트(4 X 8)라서 8칸인가? 뭐 그런 것도 헷갈리지만,

0xFFFFFFF0 가 10진수 -16 이 되는 과정이.. 아예 이해가 안 됐다.

 

일단 0x0F 는 2진수로 00001111 이고 NOT 연산을 하면 11110000 이 돼서 0xF0 이 되어야할 것 같지만,

맨 앞 자리수는 부호의 역할을 해야하니 앞에 1이 하나 더 와야하고, 16진수의 규칙을 맞추다보면

결국 1111 1111 1111 1111 1111 1111 1111 0000 까지 가게 된다. 

 

이것저것 찾아보다가 이거 지금 당장 완벽하게 이해하려고 하다가는 개발 때려칠 것 같아서 대충 봤다.

그 과정에서 2진수 양수를 음수로 바꾸는 과정을 알게 됐고, 그 과정은 아래와 같았다.

 

1. 모든 자릿수의 1과 0을 다 바꾼다. (반전)

2. 반전된 값에 1을 더한다.

3. 맨 앞에 그냥 1을 갖다 붙인다. (부호)

 

만들어진 2진수 음수를 다시 10진수로 바꾸는 과정은 위의 세 과정을 거꾸로 하면 된다.

 

1. 맨 앞에 있는 1을 따로 뗀다. (부호)

2. 1을 뺀다.

3. 모든 자릿수의 1과 0을 다 바꾼다.

 

16진수 음수인 0xFFFFFFF0 을 10진수로 바꿔보자.

 

16진수 음수 0xFFFFFFF0 을 2진수로 바꾸면 아래와 같다. (F는 15이므로 2진수로 1111)

0b / 1111 / 1111 / 1111 / 1111 / 1111 / 1111 / 1111 0000 

 

(부호로 읽히는 맨 앞 자리와, 맨 뒤 8자리만 신경써도 됨)

 

1. 맨 앞에 있는 1은 이 수가 음수임을 나타내니 따로 뗀다. 

0b '1' / 111 / 1111 / 1111 / 1111 / 1111 / 1111 / 1111 0000

 

2. 위 숫자에서 1을 빼면

0b '1' / 111 / 1111 / 1111 / 1111 / 1111 / 1111 / 1110 1111

 

3. 부호 제외 모든 값을 반전시키면

0b '1' / 000 / 0000 / 0000 / 0000 / 0000 / 0000 / 0001 0000

 

앞에 0 다 떼고 보면 2진수 10000은 10진수로 16이고 맨 앞자리가 1이므로 음수, 즉 -16이 된다.

 

 

 

아래 자료들을 참고했습니다.

 

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

 

[컴퓨터구조] 이진수의 음수 표현

않이... 0과 1로 '-' 를 어떻게 표현해요..? MSB(Most Significant Bit) 가장 중요한 비트 2진수 예시를 하나 들어보겠습니다 10진수 33 33 = 32 + 1 = 2^5 + 2^0 = 2^5 * 1 + 2^4 * 0 + 2^3 * 0 + 2^2 * 0 + 2^1 * 0 + 2^0 * 1 밑줄

kevinkim95-dev.tistory.com

 

자바스크립트(JavaScript): 비트 연산자 (Bit Operator) - BGSMM

자바스크립트(JavaScript): 비트 연산자 (Bit Operator) 비트 연산자란 2진수(binary)를 연산할 때 사용하는 연산자입니다. 예를 들어 십진법으로 표기한 정수 70을 이진법 표기로 변환하면 1000110B 가 되는

yoonbumtae.com

 

비트 연산의 개념 


조건부 호출

 

(p. 76)

ES2020에서는 () 대신 ?.()를 통해 함수를 호출할 수 있다. 일반적으로 함수를 호출할 때 괄호 왼쪽의 표현이 null이나 undefined, 기타 함수가 아닌 것으로 평가될 때는 TypeError가 일어난다. ?.() 호출 문법을 사용하면 ?. 왼쪽에 있는 표현식이 null이나 undefined로 평가될 때 호출 표현식 전체를 undefined로 평가하며 예외는 일어나지 않는다.

조건부 프로퍼티 접근과 비슷한 것으로 생각했는데, 예시가 잘 이해되지 않는다.

function square(x, log) {	// 두 번째 인자는 선택 사항인 함수
	if (log) {		// 선택 사항인 함수 인자를 받았음
    	log(x);			// 호출
    }
   	return x * x;		// 인자의 제곱을 반환
}
function square(x, log) {
	log?.(x);
   	return x * x;
}

위 코드와 아래 코드는 'log'가 함수면 호출하라는 코드이다. 위는 if 문을 사용했고 아래는 조건부 호출을 사용했다.

정확하게 log 가 square 라는 외부함수 안에서 어떻게 작동하는지 간단한 예시를 만들어보고 싶은데 잘 떠오르지 않는다.


연산자 우선 순위

(p. 81)

typeof my.functions[x](y)

// my라는 객체에는 functions라는 프로퍼티가 있고
// 그 값은 함수의 배열이다. 번호가 x인 함수를 호출하면서 인자 y를 전달하고,
// 반환되는 값의 타입을 구한다.
const array = [
  function func1(num) {
    return num + 1;
  },
  function func2(num) {
	return num + 2;
  }
];

const my = { name: ha jong seung, functions: array }

my.functions[0](3)	// Output: 4
my.functions[1](3)	// Output: 5

typeof my.functions[0](3) // 'number'

함수 호출을 대괄호로 시작하는 것처럼 보여서 처음에 이해가 좀 어려웠다.

객체의 프로퍼티가 배열이고, 그 배열 안에 함수들이 배치되어 있는 것을 상상하니 이해가 되었다.


연산자 결합성

(p. 82 오른쪽에서부터 결합되는 것들)

y = a ** b ** c			// y = {a ** (b ** c)}
x = ~ - y			// x = ~ (- y)
w = x = y = z			// w = {x = (y = z)}
q = a ? b : c ? d : e ? f : g	// q = a ? b : (c ? d : (e ? f : g))

평가 순서

(p. 83)

 

연산의 순서와 관계 없이, 자바스크립트는 표현식을 왼쪽에서 오른쪽으로 평가한다.

평가 순서는 한 표현식이 다른 표현식에 영향을 미치는 부수 효과가 있을 때에만 의미가 있다.

아래에 그 예시를 들어보겠다.

z++

w를 먼저 평가하고

x를 평가하는데, 이 때 z++ 이므로 x에는 1이 할당된 후 기존 z 값에 1을 더한다. (x === 1, z === 2)

이후 평가 순서에 따라 y는 2, z는 2이므로 계산된 값은 1 + 2 * 2 = 5 이다.

++z

w를 먼저 평가하고

x를 평가하는데, 이 때 ++z 이므로 x에는 기존 z 값에 1을 더한 2가 할당된다. (x === 2, z === 2)

이후 평가 순서에 따라 y는 숫자 2, z는 2이므로 계산된 값은 2 + 2 * 2 = 6 이다.

'부트캠프 > 자바스크립트 완벽 가이드' 카테고리의 다른 글

7장 배열  (0) 2023.01.26
6장. 객체  (0) 2023.01.15
5장. 문 (단순 내용 나열)  (0) 2023.01.07
4장 표현식과 연산자 - 스터디 정리 및 회고  (0) 2023.01.03
3장 타입, 값, 변수  (0) 2022.12.27