[모던 자바스크립트] 3. 원시 값과 객체 비교 및 함수
모던 자바스크립트 Deep Dive를 읽고 정리한 글입니다.
원시값과 객체
원시 타입 (숫자, 문자열, boolean, null, undefined, symobol)과 객체 타입의 구분점
- 원시 타입의 값은 변경불가능한 값(immutable value) 반면체 객체 타입의 값은 변경가능한 값(mutable value)
- 변수에 할당하였을 때 원시 타입은 값이 저장되고, 객체 타입은 참조 값이 저장된다.
- 다른 변수에 해당 변수를 할당하였을 때 원시 값은 값이 복사되어 전달되고, 객체 타입은 참조 값이 복사되어 전달된다.
값에 의한 전달
let score = 80;
let copy = score;
console.log(score === copy); // true
score = 100;
console.log(score, copy); // 80, 100
console.log(score === copy); // false
참조에 의한 전달
전달에 의해 2개의 식별자(변수)가 하나의 객체를 공유한다.
let person = { name: "Lee" };
let copy = person;
copy.name = "Kim";
copy.address = "Seoul";
console.log(person); // {name: 'Kim', address: 'Seoul'}
console.log(copy); // {name: 'Kim', address: 'Seoul'}
-
얕은 복사(shallow copy)와 깊은 복사(deep copy)
const obj = { x: { y: 1 } }; const shallowCopy = { ...obj }; console.log(obj === shallowCopy); // false console.log(obj.x === shallowCopy.x); // true const _ = require("lodash"); const deepCopy1 = _.cloneDeep(obj); const deepCopy2 = JSON.parse(JSON.stringify(obj)); console.log(obj.x === deepCopy1.x); // false
얕은 복사의 경우 객체가 중첩되어 있는 경우 중첩된 객체는 참조 값을 복사한다. 반면에 깊은 복사의 경우 중첩되어 있는 객체까지 모두 복사해서 값에 의한 복사가 가능하다.
함수
스코프, 실행 컨텍스트, 클로저, 생성자 함수에 의한 객체 생성, 메서드, this, 프로토타입, 모듈화 등 모두 함수와 깊이 관련된 만큼 자바스크립트에서 중요한 개념이다. 자바스크립트의 함수는 값의 성질을 갖는 객체 (변수, 프로퍼티, 배열 등의 값이 될 수 있음) 즉, 일급 객체이다. 함수명의 유무에 상관 없이 function 리터럴로 변수에 할당할 수 있다.
함수 리터럴
리터럴이란 사람이 이해할 수 있는 문자 또는 약속된 기호를 사용해 값을 생성하는 표기 방식이다. 함수 리터럴 구성요소는 함수 이름, 매개변수 목록, 함수 몸체이며 리터럴에 의해 생성된 값은 객체이다.
(function bar() {
console.log("bar");
});
bar(); // ReferenceError: bar is not defined
함수 리터럴에서 함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자이다. 위의 코드를 보면 그룹연산자 () 내에 있는 함수 리터럴 bar는 피연산자로 해석되어 함수 리터럴 표현식이 된다. 함수 리터럴의 함수명 bar는 내부에서만 참조 가능하므로 참조 에러가 발생한다. (단, 함수 선언문에서는 암묵적으로 식별자를 생성)
const func1 = function func2() {
console.log("function!");
};
func1(); // function!
func2(); // ReferenceError: func2 is not defined
마찬가지로 변수에 할당하거나 피연산자로 사용할 경우 함수 리터럴이 선언문이 아닌 값으로 평가되고 func1에 할당된다. 하지만 func2는 함수 몸체 내부에서만 참조 가능한 식별자이므로 참조 에러가 발생한다.
함수 정의
위의 함수 구성요소와 리턴할 값들을 기반으로 자바스크립트 엔진에 의해 함수가 정의되고 정의하는 방법은 4가지가 있다.
-
함수 선언문
function 리터럴과 동일한 형태지만 함수명을 생략할 수 없다. 표현식이 아닌 문이므로 변수에 할당할 수 없다. 자바스크립트 엔진에 의해 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고 함수 객체를 할당한다.
-
함수 표현식
// 함수 선언문, 표현식 비교 console.log(func1); // f func1(str) console.log(func2); // undefined console.log(func3); // ReferenceError console.log(func1("hello")); // hello console.log(func2("hello")); // TypeError console.log(func3("hello")); // ReferenceError // 함수 선언문 function func1(str) { return str; } // var에 할당된 함수 표현문 var func2 = function (str) { return str; }; // const에 할당된 함수 표현문 const func3 = function (str) { return str; };
함수 선언문은 표현식이 아닌 문이였지만 함수 표현식이 경우 표현식인 문이다.
함수 선언문의 경우 런타임 이전에 자바스크립트 엔진에 의해 평가되어 이미 함수 이름과 동일한 식별자에 할당되어 선언문 이전에 호출이 가능하다. 이를 함수 호이스팅 (Function Hoisting) 이라 한다.
반면에 함수 표현식의 경우 변수가 런타임 이전에 선언이 되지만 undefined로 초기화되어 있다. 런타임에 할당문이 실행될시 함수 표현식이 평가되고 할당된다.
var로 선언시 선언, 초기화가 동시에 진행되어 undefined가 되지만 let, const는 변수 호이스팅이 var와 동일하게 발생하여도 초기화가 동시에 진행되지 않기 때문에 uninitialized 상태가 되고 참조할 경우 ReferenceError가 발생한다.
-
Function 생성자 함수
Function 빌트인 객체를 이용한 함수 생성 받법이다. 위의 방식들과 달리 클로저를 생성하지 않는 등 다르게 동작하므로 바람직한 방식은 아니다.
-
화살표 함수
함수 표현식을 간소화한 방식이지만 완전히 대체하는 것은 아니다.
- this 바인딩 방식 다름
- prototype 프로퍼티 없음
- arguments 객체 생성하지 않음