원시 자료형 vs 참조 자료형
원시 자료형 : number, string, boolean, undefined, null, symbol
참조 자료형 : 배열, 객체, 함수
원시 자료형 primitive type | 참조 자료형 reference type |
변수에 할당하면 메모리 공간에 값 자체를 저장 | 변수에 할당하면 메모리 공간에 주소 값을 저장 |
다른 변수에 할당하면 원시 값 자체가 복사되어 전달 | 다른 변수에 할당하면 주소 값이 복사되어 전달 |
변경 불가능한 값 immutable value, 읽기 전용 read only | 변경 가능한 값 mutable value |
* 참조 자료형은 힙 heap에 저장한다 (JavaScript)
* 힙 : 가비지 컬렉터가 관리하지 않는 영역이기 때문에 한정된 힙 공간을 효율적으로 사용하기 위해 주소를 활용
* 새로운 배열을 할당하면 새로운 힙 주소를 참조하게 됨
* 문자열도 배열처럼 인덱스로 접근은 가능하지만 원시 자료형이기 때문에 하나의 문자를 변경할 수 없음
→ 문자열 중 한 글자를 바꾸고 싶다면 변경된 문자열을 재할당해야 함
얕은 복사 vs 깊은 복사
** 원본 데이터 보관 중요 **
원시자료형의 복사 : 원본과 복사본 중 하나를 변경해도 다른 하나에 영향을 미치지 않음
참조 자료형의 복사 : 복사한 변수에 요소를 추가하면 같은 주소를 참조하고 있는 원본에도 영향을 미침
얕은 복사
배열 복사하기
1. slice()
slice 함수는 잘라낸 배열을 return함
let cArr = arr.slice(); // arr 처음부터 끝까지 복사한다
cArr 라는 새로운 배열(arr과 같은 요소를 가진) 생성
2. spread 문법
ES6 문법
let arr = [0, 1, 2, 3];
let copiedArr = [...arr];
console.log(copiedArr); // [0, 1, 2, 3]
console.log(arr === copiedArr); // false
copiedArr.push(4);
console.log(copiedArr); // [0, 1, 2, 3, 4]
console.log(arr); // [0, 1, 2, 3]
객체 복사하기
1. Object.assign({}, obj)
let obj = { firstName: "coding", lastName: "kim" };
let copiedObj = Object.assign({}, obj);
console.log(copiedObj) // { firstName: "coding", lastName: "kim" }
console.log(obj === copiedObj) // false
2. spread 문법
** 단, 참조 자료형 내부에 참조 자료형이 중첩되어 있는 경우,
slice(), Object.assign(), spread syntax를 사용해도 참조 자료형 내부에 참조 자료형이 중첩된 구조는 복사 X
내부 참조 자료형은 주소값만 복사됨. slice 등으로 다시 복사해야 함
깊은 복사
깊은 복사 deep copy : 참조 자료형 내부에 중첩되어 있는 모든 참조 자료형을 복사
1. JSON.stringify()와 JSON.parse()
JSON.stringify()는 참조 자료형을 문자열 형태로 변환하여 반환하고, JSON.parse()는 문자열의 형태를 객체로 변환하여 반환합니다. 먼저 중첩된 참조 자료형을 JSON.stringify()를 사용하여 문자열의 형태로 변환하고, 반환된 값에 다시 JSON.parse()를 사용하면, 깊은 복사와 같은 결과물을 반환합니다.
* 단, 중첩된 참조 자료형 중에 함수가 포함되어 있을 경우 함수 null이 되므로 완전한 깊은 복사 방법이라고 보기 어려움
// JSON
const arr = [1, 2, [3, 4]];
const copiedArr = JSON.parse(JSON.stringify(arr));
console.log(arr); // [1, 2, [3, 4]]
console.log(copiedArr); // [1, 2, [3, 4]]
console.log(arr === copiedArr) // false
console.log(arr[2] === copiedArr[2]) // false
2. 외부 라이브러리 사용
완전한 깊은 복사를 반드시 해야할 경우 외부 라이브러리를 사용
node.js 환경에서 외부 라이브러리인 lodash, 또는 ramda를 설치하여 사용할 수 있음
다음은 lodash의 cloneDeep을 사용한 깊은 복사의 예시
// lodash 라이브러리
const lodash = require('lodash');
const arr = [1, 2, [3, 4]];
const copiedArr = lodash.cloneDeep(arr);
console.log(arr); // [1, 2, [3, 4]]
console.log(copiedArr); // [1, 2, [3, 4]]
console.log(arr === copiedArr) // false
console.log(arr[2] === copiedArr[2]) // false
스코프
스코프 : 변수 접근 규칙에 따른 유효 범위
* 변수를 선언하는 위치를 기준으로 스코프 결정
작은 스코프에서 선언된 변수를 더 큰 스코프에서 참조할 수 없음
= 바깥쪽 스코프에서 안쪽 스코프로 접근 X
바깥 스코프에서 선언된 변수는 안쪽 스코프에서 사용할 수 있음
= 안쪽 스코프에서 바깥쪽 스코프로 접근 O
블록 스코프 : 중괄호 { } 기준으로 작은 스코프(=안쪽 스코프)와 더 큰 스코프(=바깥쪽 스코프)로 나뉨
scope1 에서 사용할 수 있는 변수 : a → 전역 스코프 Global scope
scope2 에서 사용할 수 있는 변수 : a, b
scope3 에서 사용할 수 있는 변수 : a, b, c
scope4 에서 사용할 수 있는 변수 : a, b, c, d
전역 스코프 Global scope ↔ 지역 스코프 Local scope
* 우선순위 : 지역 스코프 > 전역 스코프
let name = 'kimcoding';
function showName() {
// 지역변수로 전역과 동일한 이름을 사용할 수 있음 (다른 변수)
let name = '박해커'; // 이 선언이 없었다면 kimcoding 출력
console.log('1 : ' + name);
}
console.log('2 : ' + name);
showName();
console.log('3 : '+ name);
// 2 : kimcoding
// 1 : 박해커
// 3 : kimcoding
function showName() {
name = '박해커';
console.log('1 : ' + name); // 박해커
}
console.log('2 : ' + name); // kimcoding
showName();
console.log('3 : '+ name); // 박해커
let | const | var | |
유효 범위 | 블록 스코프 및 함수 스코프 |
블록 스코프 및 함수 스코프 |
함수 스코프 |
값 재할당 | 가능 | 불가능 | 가능 |
재선언 | 불가능 | 불가능 | 가능 |
* let 권장
* var는 예측하기 어려우므로 사용하지 않는 것을 권장
* var 사용하려면 호이스팅, this에 대한 이해가 필요
클로저
MDN 정의
함수와 함수가 선언된 어휘적(lexical) 환경의 조합을 말한다.
이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다.
좀 더 쉽게 표현하면
스코프를 고려할 때 외부 함수는 내부 함수의 변수를 접근할 수 없음
함수 자체를 반환함으로써 내부 함수의 변수를 외부 함수도 사용할 수 있게 해주는 방식
클로저를 응용하면 데이터 바인딩, 커링, 모듈 패턴 등에 사용할 수 있음
// 클로저 사용 패턴 1
function outerFn() {
const outerFnVar = 'outer 함수 내의 변수';
const innerFn = function() {
return 'innerFn은 ' + outerFnVar + '에 접근 가능';
}
return innerFn; // 함수 자체를 반환!
}
const innterFnOnGlobal = outerFn();
const message = innterFnOnGlobal();
message; // 실행
ES6
spread/rest 문법
// spread 문법
function sum(x, y, z) {
return x + y + z;
}
// const numbers = [1, 2, 3, 4]; 배열 개수가 몇 개든 x + y + z라 요소 3개만 sum 연산
const numbers = [1, 2, 3];
sum(...numbers) // 질문: 어떤 값을 리턴하나요?
// sum(numbers) x에 [1,2,3] -> 배열 + undefined + undefined
// sum(numbers[0], numbers[1], numbers[2])
// rest 문법
function sum(...theArgs) {
return theArgs.reduce((previous, current) => {
return previous + current;
});
}
function sum(...theArgs) {
let result = 0;
for(let idx = 0; idx < theArgs.length; idx++) {
result += theArgs[idx];
}
return result;
}
화살표 함수
함수표현식으로 함수를 정의할 때 function 키워드 대신 화살표 => 사용
* 매개변수가 한 개일 때 소괄호 생략 가능
* 매개변수가 없을 경우 소괄호 생략 불가
* 기능 코드(body)가 한 줄만 있는 경우 중괄호 생략 가능
* 기능 코드(body)가 한 줄만 있고 return문일 경우 중괄호 + return 둘다 생략 가능 (return 만 생략할 수 없음)
// 화살표 함수
const multiply = (x, y) => {
return x * y;
}
const square = x => x * x
// 위 코드와 동일하게 동작합니다.
const square = x => { return x * x }
// 위 코드와 동일하게 동작합니다.
const square = function (x) {
return x * x
}
+궁금한 점 개인학습
자바스크립트 객체 key(property)
속성 'a'가 객체에 존재하는 속성인지 알고 싶을 때 어떤 방법이 효율적인지 어떻게 알 수 있을까?
기존에 알던 includes, indexOf 는
내부를 뜯어보면 반복문(iterate)과 같은 작동방식이라 효율에 차이가 없음
따로 검색해서 알게 된 메서드 Object.hasOwn(), Object.prototype.hasOwnProperty
Object.hasOwn() 은 Object.prototype.hasOwnProperty + null-prototype.objects에도 적용 가능
상속된 속성은 false → 해당 객체만 가지고 있는 속성을 사용해야 할 때 쓸 수 있음
cf. 'in' operator : direct or inherited properties 모두 true
// The `in` operator will return true for direct or inherited properties:
"prop" in example; // true
"toString" in example; // true
"hasOwnProperty" in example; // true
객체의 프로토타입 체인
MDN 문서를 참고하다가 처음 본 개념.
the specified property in the object's prototype chain
자바스크립트의 각 객체에는 프로토타입이라는 다른 객체에 대한 링크를 보유하는 비공개 속성이 있음
프로토타입으로 null을 가진 객체에 도달할 때까지 이 연결은 계속됨
객체의 프로토타입, 프로토타입의 프로토타입 등 프로토타입 체인의 종단에 이를 때까지 그 속성을 탐색
추후에 자바스크립트의 프토토타입, 클래스 그리고 자바와의 차이점에 대해 학습할 것