표현식의 평가 순서


let foo = { n: 1 };
let bar = foo; // foo 객체의 참조 주소가 bar 변수에 할당됨
foo.x = foo = { n: 2 }; // 값을 반환하는 할당 연산자

console.log(foo.x) // ?

위 문제를 얼핏보면 foo.x의 결과는 { n: 2 }일 것 같지만 아니다. foo.xundefined를 출력한다. 위 코드의 연산 과정을 하나하나 풀어보면 아래와 같다.

연산 과정을 도식화한 이미지

연산 과정을 도식화한 이미지

  1. 왼쪽 표현식 평가

    우측 표현식이 평가한 값이 할당될 곳을 결정하기 위해 foo.x가 현재 참조하는 객체 { n: 1 } 확인

  2. 오른쪽 표현식 평가

    1. 할당될 곳을 결정하기 위해 foo 변수가 현재 참조하는 객체 { n: 1 } 확인
    2. 새로운 객체 { n: 2 }foo 변수에 할당되고 해당 객체 반환
  3. 우측 표현식이 반환한 { n: 2 }{ n: 1 } 객체의 x 속성에 할당

  4. bar 변수는 기존 { n: 1 } 객체를 참조하므로 콘솔 출력 결과는 { n: 1, x: { n: 2 }}

  5. foo 변수엔 { n: 2 } 객체가 할당 됐으므로 foo.x 콘솔 출력 결과는undefined

⭐️ foo.x가 참조하는 foo 객체 { n: 1 }는 우측 표현식의 foo 변수가 변경되기 전에 결정되는 것에 주목하자. 할당 연산자는 우결합성(우측부터 계산)을 가지므로 3번째 줄은 아래와 동일하다.

foo.x = (foo = { n: 2 })

하지만 표현식의 평가는 항상 왼쪽부터 이뤄지므로 할당 연산이 이뤄지기 전, 왼쪽 표현식부터 할당될 변수들이 참조하고 있는 값을 평가하고, 우측부터 연산한 값이 할당되는 것.

(번외) 연산자의 결합성


<aside> 💡 결합성은 우선순위가 동일할 때만 고려된다. (...) 소괄호를 이용한 “그룹” 우선순위가 가장 높다.

</aside>

거듭제곱을 제외한 모든 산술 연산자는 좌(左)결합성(왼쪽 → 오른쪽으로 계산)을 가진다. (a 연산자1 b) 연산자2 c

1 + 8 + 88 // 연산자 우선순위가 모두 동일하므로 좌결합성 -> (1 + 8) + 88
4 + 5 + "px" // (4 + 5) + "px"

거듭제곱, 할당 연산자, 삼항 연산자는 우(右)결합성(오른쪽 → 왼쪽으로 계산)을 가진다. a 연산자1 (b 연산자2 c)

2 ** 5 ** 10 // 연산자 우선순위가 모두 동일하므로 우결합성 -> 2 ** (5 ** 10)
let a = b = 8 + 8 // let a = (b = 8 + 8)

연산자의 우선순위가 다르면 결합성과 상관없이 우선순위가 높은 연산자가 먼저 실행된다.

3 + 10 * 2 // 곱셈의 우선순위(15)가 더 높으므로 -> 3 + (10 * 2)

표현식의 평가는 결합성과 무관하게 항상 왼쪽에서 오른쪽으로 진행된다. ⭐️

const echo = (name, num) => {
	console.log(name + '항 평가');
	return num;
}

console.log(echo("1", 6) ** echo("2", 2) ** echo("3", 3));
// 콘솔 출력 결과 : '1항 평가' -> '2항 평가' -> '3항 평가'
// 표현식의 평가는 왼쪽부터 진행되므로 echo('1', 6)부터 평가한 것
// 연산은 우결합성이므로 오른쪽부터 계산 -> 6 ** (2 ** 3)

(번외) 이항 연산자


<aside> 💡 + - = 등 대부분의 자바스크립트 연산자는 값을 반환한다. x = value를 호출하면 valuex에 할당되고 value를 반환한다. 연산자 체이닝 등은 이런 특징을 활용한 표현식이다.

</aside>

// 단항 연산자(하나의 피연산자만 받음)
let a = 1;

// 이항 연산자 (두개의 피연산자를 받음)
let a = 1, y = 2; // let a = 1; let y = 2;

// 할당 연산자 체이닝
let a = b = c = 1; // let a = 1; let b = 1; let c = 3;

// 값을 반환하는 할당 연산자
let a = 1;
let b = 2;
let c = 3 - (a = b + 1); 
// ⑴ (우측 표현식) b + 1을 평가한 값 3을 a 변수에 할당하고 3 반환 
// ⑵ 우측 표현식에서 반환한 값 3을 c 변수에 할당
console.log(a); // 3
console.log(c); // 0