Today I Learned 2023.02.21. [코어 자바스크립트 1]

Dongchan Alex Kim·2023년 2월 21일
0

Today I Learned

목록 보기
19/31
post-thumbnail

데이터 타입의 종류

  • 기본형(Primitive) VS 참조형(Reference)

    기본형은 값이 담긴 주솟값을 바로 복제하는 반면에,
    참조형은 값이 담긴 주솟값들로 이루어진 묶음을 가르키는 주솟값을 복제한다는 점이 다르다.

  • 메모리용량이 과거보다 월등히 커진 상황에서 등장한 자바스크립트는 상대적으로 메모리 관리에 대한 압박이 자유로워짐.

    숫자의 경우 정수형인지 부동소수형인지를 구분하지 않고 64비트, 즉 8바이트를 확보한다.
    → 형변환에 대한 걱정이 없어짐.

  • 모든 데이터는 바이트 단위의 식별자, 더 정확하게는 메모리 주솟값을 통해 서로 구분하고 연결한다.
    - 변수 : 변할 수 있는 무언가를 뜻한다. (무언가 → 데이터를 뜻함)
    - 식별자 : 어떤 데이터를 식별할 수 있는 이름 (변수명)
  • 데이터 할당
let a; // 변수 a 선언

a = 'Dongchan'; // 변수 a에 데이터 할당

let a = 'Dongchan'; // 변수 선언과 데이터 할당을 한 문장으로 표현

사용자가 a에 접근하자면, 컴퓨터는 메모리에서 a라는 이름을 가진 주소를 검색해 해당 공간에 담긴 데이터를 반환함.

미리 확보된 공간 내에서 데이터 변환 → 변환된 데이터를 다시 저장하기 위해 데이터 크그에 맞게 늘리는 작업 필요.
뒤에 저장된 데이터들을 전부 뒤로 옮기고, 이동시킨 주소를 각 식별자에 다시 연결
→ 매우매우 비효율적임

문자열 데이터의 변환을 처리하려면 → 변수와 데이터를 별도의 공간에 나누어 저장하는 것이 가장 효율적임.
기존 문자열에 어떤 변환을 가하든 상관없이, 무조건! 새로 만들어 별도의 공간에 저장한다.

  • 불변값

    변수 VS 상수
    -> 변수 영역 메모리의 변경가능성이 관건임
    불변성 여부
    -> 데이터 영역 메모리의 변경 가능성이 관건임

  • 가변값

    참조형 데이터는 무조건 가변값? NO!
    설정에 따라 변경 불가능한 경우도 있고, 아예 불변값으로 활용하는 방안도 있음.
    결국, 기본형 데이터와의 차이는 '객체의 변수(프로퍼티) 영역'이 별도로 존재한다.

let obj1 = {
	a : 1,
  	b : 'bbb'
};
obj1.a = 2;

변수 obj1이 바라보고 있는 주소는 바뀌지 않는다는 점을 알아야 한다.
변수 obj1은 특정 객체가 들어가 있는 주소값을 바라보고 있고,
객체 안에서 바라보고 있는 특정 데이터 주소가 변한 것이기 때문이다.

  • 중첩 객체 (nested object)
let obj = {
	x : 3,
  	arr : [3,4,5]
};
  • 참조 카운트
    재할당 명령으로 주소를 참조하는 변수가 하나도 없게 된다면?
    -> 참조카운트 0
    -> 참조 카운트가 0인 메모리 주소는 가비지 컬렉터의 수거 대상이 됨.
    -> 특정 시점이나 메모리 사용량이 포화상태에 임박할 때마다 자동으로 수거 대상들을 수거한다.
    -> 수거된 메모리는 다시 새로운 값을 할당할 수 있는 빈 공간이 된다.

  • 변수 복사
    참조형 데이터를 복사한 변수 obj2의 프로퍼티의 값을 바꾸게되어도, obj1과 obj2는 여전히 같는 객체를 바라보고 있는 상태이다.
    obj2에도 새로운 객체를 할당함으로서 값을 직접 변경하면,
    -> 새로운 공간에 새 객체가 저장되고,
    -> 그 주소를 변수 영역의 obj2위치에 저장한 것이다.
    -> 이때는 객체에 대한 변경임에도 값이 달라진다.

자바스크립트의 모든 데이터 타입은 참조형 데이터일 수 밖에 없다. 다만 기본형은 주솟값을 복사하는 과정이 한 번만 이뤄지고, 참조형은 한 단계를 더 거치게 된다는 차이가 있다.

가변이라는 것은 참조형 데이터 자체를 변경할 경우가 아니라, 그 내부의 프로퍼티를 변경할 때 성립한다.

  • 불변 객체
    : 값을 전달 받은 객체데 변경을 가하더라도 , 원본 객체는 변하지 않아야 하는 경우
var user = {
	name : 'Dongchan',
  	gender : 'male'
};

var changeName = function(user, newName){
	var newUser = user;
  	newUser.name = newName;
    return newUser;
}

var user2 = changeName(user, 'Jung');

if(user !== user2){
	console.log('유저의 정보가 변경되었습니다'); // 출력없이 통과함.
}

console.log(user.name, user2.name) // Jung Jung
console.log(user ===user2) // true

변경 전과 후에 서로 다른 객체를 바라보게 만들어야 한다.

var user = {
	name : 'Dongchan',
  	gender : 'male'
};

var changeName = function(user, newName){
	return{
    	name : newName,
      	gender : user.gender
    };
};

var user2 = changeName(user, 'Jung');

if(user !== user2){
	console.log('유저의 정보가 변경되었습니다'); // 유저의 정보가 변경되었습니다
}

changeName 함수가 새로운 객체를 반환하도록 만들어야 한다.
BUT 새로운 객체를 만들면서, 변경필요 헚는 기존 객체의 프로퍼티를 하드코딩함.
-> 이런 방식보다는 대상 객체의 프로퍼티 개수에 상관 없이 모든 프로퍼티를 복사하는 함수를 만들어야한다.

var copyObject = function(target){
	var result = {}; // 새로운 객체 생성
    for (var prop in target){
    	result[prop] = target[prop]; // 타겟 객체에서 프로퍼티 가져오기
    }
  	return result;
}

객체 내부 변경시 무조건 copyObject() 함수만 사용하기로 합의한다면, user객체가 곧 불변 객체이다.
( == copyObject함수가 똑같은 역할을 한다. )
프로퍼티 변경할 수 없게끔 시스템적으로 제약을 거는 편 -> immutable.js baobab.js 등의 라이브러리가 등장

얕은 복사와 깊은 복사

얕은 복사는 바로 아래 단계의 값만 복사하는 방법이고, 깊은 복사는 내부의 모든 값들을 하나하나 찾아서 전부 복사하는 방법이다.
중첩된 객체에서는 참조형 데이터가 저장된 프로퍼티를 복사할때, 그 주솟값만 복사한다.
-> 해당 프로퍼티에 대해 원본과 사본이 모두 동일한 참조형 데이터 주소를 가리킨다.

즉, user 객체에 직접 속한 프로퍼티에 대해서는 복사해서 완전히 새로운 데이터가 만들어진 반면,
한 단계 더 들어간 urls의 내부 프로퍼티들은 기존 데이터를 그대로 참조하는 것이다.
-> 이런 현상이 발생하지 않으려면, user.url을 불변객체로 만들어야 한다.

  • 기본형 데이터의 경우 그대로 복사하면 되지만, 참조형 데이터의 경우 다시 그 내부의 프로퍼티들을 복사해야한다. ( 참조가 있을 때마다 재귀 )
var copyObjectDeep = function(target){
	var result = {};
  if(typeof target === 'object' && target !== null){
  	for(var prop in target){
      result [prop] = copyObjectDeep(target[prop]);
    }
  }else{
  	result = target;
  }
  return result;
}

-> JSON 문법으로 표현된 문자열로 전환했다가, 다시 JSON 객체로 바꾸는 것이 하나의 방법이 될 수 있다.

var copyObjectViaJSON = function(target){
	return JSON.parse(JSON.stringify(target));
}

var obj = {
	a : 1,
  	b : {
    	c : null,
      	d : [1,2],
      	func1 : function () {console.log(2);}
    },
 	func2 : function(){console.log(4);}
}

obj2.a = 3;
obj2.b.c = 4;
obj.b.d[1] = 3;

console.log(obj) // { a : 1, b : { c : null, d : [1, 3], func1 : f()}, func2 : f()}
console.log(obj2) // { a : 3, b : { c : 4, d : [1,2]}}

undefined와 null 값

< undefined를 반환하는 경우 >

  1. 값을 대입하지 않은 변수
  2. 객체 내부의 존재하지 않는 프로퍼티에 접근하려할 때
  3. return 문이 없거나 호출되지 않는 함수의 실행결과

undefined가 비록 '비어있음'을 의미하긴하지만, 하나의 값으로 동작하기 때문에 순회의 대상이 될 수 있다!
값으로서 어딘가 할당된 undefined는 실존하는 데이터인 반면, 자바스크립트 엔진이 반환해주는 undefined는 문자 그대로 값이 없음을 나타내는 것이다.

  • null
    비어있음을 명시적으로 나타내고 싶을 때는, null을 쓰면 된다. -> undefined는 오직 '값을 대입하지 않은 변수에 접근하고자 할 때 자바스크립트 엔진이 반환해주는 값'으로서만 존재할 수 있다.

typeof null 은 object이다.
자바스크립트 내부 버그라고 한다.
-> 동등연산자(==)로 한다면, null과 undefined가 서로 같다고 판단한다.
-> 일치연산자(===)로 한다면 정확히 null값을 판별할 수 있다.


실행 컨텍스트

  • 스택, 큐 (stack, queue)의 개념

    스택은 출입구가 하나뿐인 우물과 같은 데이터 구조이다.
    a,b,c,d를 저장했다면 -> 꺼낼 때는 d,c,b,a 순서로 나온다.
    -> Lexical Environment + Variable Environment(나중가면 바뀌긴 하지만, 일단 초기 상태에서는 동일.)

  • 콜스택은 실행컨텍스트를 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.
    -> 전역 컨텍스트가 먼저 스택에 쌓이고, 중간에 있는 함수가 실행이 되면서 스택으로 또 쌓인다.
    -> 해당 함수가 종료되면, 해당 함수 컨텍스트가 콜스택에서 삭제된다.
    -> 그 후 콜스택에는 전역 컨텍스트만 남으므로 실행 중단되었던 전역 컨텍스트가 실행된다.

Lexical Environment

환경 정보들을 사전에서 접하는 느낌으로 모아놓은 것
-> 즉, 렉시컬 환경은 스코프를 구분하여 등록하고 관리하는 저장소 역할을 하는 렉시컬 스코프의 실체이다.
-> 함수를 어디서 호풀했는지가 아닌, 어디에 정의했는지에 따라 상위 스코프를 결정한다.

  1. 환경 기록 (Environment Record)
    스코프에 포함된 식별자를 등록하고 등록된 식별자에 바인딩된 값을 관리하는 저장소이다.
  2. 외부 렉시컬 환경에 대한 참조 (Outer Lexical Environment Reference)
    외부 렉시컬 환경에 대한 참조는 상위 스코프를 말함. -> 해당 실행 컨텍스트를 생성한 소스코드를 포함하는 상위 코드의 렉시컬 환경을 말한다.
  • 호이스팅( Hoisting )
    코드가 실행되기 전임에도 불구하고 자바스크립트 엔진은 이미 해당 환경에 속한 변수명들을 모두 알고 있게되는 셈
    이미 선언된 변수는, 비어있는 선언일 경우 무시할 수 있다.

  • !! 변수는 선언부만 끌어올려 호이스팅을 하고, 함수 선언은 전체를 끌어올려서 선언한다.

  • !! 변수의 할당부분은 원래 자리에 남겨둔다. (순서대로 실행됨)

  • 함수 선언문과 함수 표현식
  1. 함수선언문은 함수의 function 정의부만 존재하고, 별도의 할당 명령이 없는 것 -> 반드시 함수명이 정의되어야 한다.
  2. 함수표현식은 정의한 function을 별도의 변수에 할당하는 것
console.log(sum(1,2));
console.log(multiply(3,4));

function sum(a,b){
	return a + b
}

var multiply = function(a,b){
	return a * b
}
//호이스팅을 마친 상태 재현 
function sum(a,b){   //함수 선언문은 전체를 호이스팅함
	return a + b
};

var multiply; // 변수는 선언부만 끌어올립니다.




console.log(sum(1,2));
console.log(multiply(3,4));

multiply = function(a,b){ // 변수의 할당부는 원래 자리에 남겨둔다.
	return a * b
}

함수 선언문은 전체를 호이스팅 BUT 함수 표현식은 변수 선언부만 호이스팅했다
-> '할당'한 것이 곧 함수 표현식이다.

함수를 처음 선언한 100번째 줄보다 이전 줄에 sum함수를 호출하는 코드가 있었다면 그 줄에서 바로 에러가 검출되므로 더욱 빠른 타이밍에 손쉽게 디버깅할 수 있었을 것이다.

원할한 협업을 위해서는 전역공간에 함수를 선언하거나 동명의 함수를 중복으로 선언하는 경우는 없어야만 한다.

SCOPE & Outer Environment Reference

스코프란 식별자에 대한 유효범위이다.
식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것을 스코프 체인 Scope Chain 이라고 한다.

  • outer Environment Reference는 연결 리스트의 형태를 띕니다.
    또한 각각의 outer Environment Reference는 오직 자신이 선언된 시점의 Lexical Environment만 참조하고 있으므로 가장 가까운 요소부처 차례대로 접근한다. -> 다른 순서로 접근은 불가능하다.

무조건 스코프체인 상에서 가장 먼저 발견된 식별자에만 접근 가능하다.

  • inner 스코프의 Lexical Environment부터 검색할 수 밖에 없다.
    -> inner 스코프의 Lexical Environment에 식별자 a가 존재하므로,
    -> 스코프 체인 검색을 더이상 하지 않고, 즉시 inner Lexical Environment에서의 a를 반환하게 된다.
    !! 전역 공간에서 선언한 동일한 이름의 a변수에는 접근할 수 없다. -> 변수의 은닉화 variable shadowing

코드의 안정성

코드의 안정성을 위해 항상 가급적 전역변수 사용을 최소화하고자 노력하는 것이 중요하다.

profile
Disciplined, Be systemic

0개의 댓글