23. Chapter 1 프로그래밍 기초 1 (항해 3일차)

코이그·2023년 4월 5일

항해99

목록 보기
22/54

강의 요약

데이터 타입

  • 기본형과 참조형 데이터 타입을 나누는 기준: 값의 저장 방식, 불변성 여부.

  • 변수 영역: 변수와 상수를 구분하는 기준 (변수에 새로운 값을 할당할 수 있는가)

  • 데이터 영역: 불변성의 여부를 구분하는 기준 (데이터 영역의 값을 수정할 수 있는가)

메모리, 데이터

bit: 0과 1을 가지고 있는 메모리를 구성하기 위한 작은 단위.
byte: 8개의 비트.

메모리: byte 단위로 구성
데이터: 메모리 주소값으로 구분이 됨.

변수 영역과 데이터 영역은 따로 관리됨. (값을 바로 변수에 대입하는 것이 아닌, 변수의 데이터에 값의 주소를 저장)

  1. 자유로운 데이터 변환
    • 변수와 값을 같은 영역에 저장했을 때: 변수의 값을 현재 메모리 영역 범위보다 더 큰 값으로 바꾸려고 하면 다음 메모리 영역까지 침범하게 되고 침범된 영역에 이미 변수/값이 존재한다면 이후 모든 영역들을 다음 영역으로 밀어야됨.
  1. 효율적인 메모리 관리
    • 변수와 값을 같은 영역에 저장했을 때: 1만개의 변수를 생성해 모든 변수에 숫자 1을 저장하려고 하면 80000byte(숫자형 크기 x 10000개)만큼의 공간이 필요함. 하지만 숫자 1이라는 값을 다른 영역에 따로 관리를 하게 된다면 모든 변수들이 이 숫자 1의 주소값만 가지고 있으면 되고 결국 필요한 메모리 공간이 훨씬 줄어듬. (변수 영역은 데이터 영역보다 보통 작기 때문에)

기본형 (Primitive)

  1. Number
  2. String
  3. Boolean
  4. null
  5. undefined
  6. Symbol
  • 값이 담긴 주소값을 바로 복제
  • 불변성 O (데이터 영역 메모리를 변경할 수 없음)

참조형 (Reference)

Object

  1. Array
  2. Function
  3. Date
  4. RegExp
  5. Map, WeakMap
  6. Set, WeakSet
  • 값이 담긴 주소값들로 이루어진 묶음을 가리키는 주소값을 복제
  • 불변성 X

메모리

참조형 데이터는 객체의 변수(프로퍼티) 영역이 별도로 존재함.

  1. 변수 영역 (obj)
  2. 데이터 영역 (프로퍼티의 값)
  3. 별도 영역 (obj의 프로퍼티)

불변성이 없다는 것은 위의 3번인 별도 영역에 프로퍼티의 값(데이터의 주소)이 바뀔 수 있다는 것을 뜻함.

중첩객체(객체의 프로퍼티가 객체)에 대해서도 별도 영역이 새로 생김.
예)

obj = {
	x: 3;
    arr: [3, 4, 5];
}
  1. 변수 영역 (obj)
  2. 데이터 영역 (3, 4, 5) (3은 이미 x에 할당할 때 생겼기 때문에 arr[0]은 굳이 새로운 3을 만들 필요 x)
  3. 별도 영역 (x, arr)
  4. 별도 영역2 ([0], [1], [2])
// changeName 함수 내에서 newUser 객체에 user 객체를 복사(단순 할당).
// 단순 할당은 가변이기 때문에 newUser.name에 새로운 값을 할당하면 기존의 객체도 영향을 받음.
var user = {
  name: "wonjang",
  gender: "male",
};

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

var user2 = changeName(user, "twojang");

if (user !== user2) console.log("유저 정보가 변경되었습니다.");

console.log(user.name, user2.name);
console.log(user === user2);

// twojang twojang
// true

//////////////////////////////////////////////////

// changeName 함수에서 user 객체의 단순 할당이 아닌 
// 새로운 객체를 만들어서 name은 새로 할당하고 
// gender는 user 객체의 gender를 할당하고 반환 (새로운 주소).
var user = {
  name: "wonjang",
  gender: "male",
};

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

var user2 = changeName(user, "twojang");

if (user !== user2) console.log("유저 정보가 변경되었습니다.");

console.log(user.name, user2.name);
console.log(user === user2);

// wonjang twojang
// false

위에는 객체의 프로퍼티가 2개 밖에 없어서 괜찮지만 만약 프로퍼티가 많아지면 새로운 객체를 생성하여 반환하는 과정이 엄청 길어지기 때문에 비효율적인 방법임.

해결방법) 얕은 복사

var copyObject = function (target) {
  var result = {};

  for (var prop in target) {
    result[prop] = target[prop];
  }
  return result;
};

var user = {
  name: "wonjang",
  gender: "male",
};

var user2 = copyObject(user);
user2.name = "twojang";

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

console.log(user.name, user2.name);
console.log(user === user2);

for~in 반복문을 돌 때 프로퍼티로 객체가 있을 수 있기 때문에(중첩객체의 단순 할당 / 주소의 복사) 이 방법도 완벽한 복사는 못함.

해결방법) 깊은 복사

var copyObjectDeep = function (target) {
  var result = {};
  // target의 타입이 object이면서 null이 아닐 때
  // 현재 함수 재귀적으로 호출
  if (typeof target === "object" && target !== null) {
    for (var prop in target) {
      result[prop] = copyObjectDeep(target[prop]);
    }
  } else {	// target의 타입이 object가 아닐 때 바로 할당
    result = target;
  }
  return result;
};

var obj = {
  a: 1,
  b: {
    c: null,
    d: [1, 2],
  },
};

var obj2 = copyObjectDeep(obj);

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

console.log(obj);
console.log(obj2);

null, undefined

둘 다 '없음'을 의미하는 값.
undefined: js가 할당.
null: 개발자가 없는 값이라고 명시.

undefined

// 1. 변수의 값이 지정되지 않은 경우 (데이터 영역에 메모리 주소가 없는 경우)
var a;
console.log(a);				// 값을 대입하지 않은 변수 접근

// 2. 객체나 배열에 접근할 때 해당 데이터가 존재하지 않는 경우
var obj = { a: 1 };
console.log(obj.b);			// 존재하지 않는 property 접근

// 3. 함수가 반환문이 없는 경우
var func = function() {};
console.log(func());		// 반환 값이 없는 함수

null

!주의! null의 타입은 object (typeof null === object)

console.log(null == undefined);		// true
console.log(null === undefined);	// false

실행 컨텍스트

실행할 코드에 제공할 환경 정보들을 모아놓은 객체.

역할:

  1. 호이스팅. (선언된 변수를 위로 끌어올림)
  2. 외부 환경 정보 구성.
  3. this값 설정.

콜 스택

우리가 작성한 코드는 동일한 환경에 묶인 것 끼리 모여서 객체를 이루고 그 객체들은 콜 스택에 쌓임.
실행된 객체들은 콜 스택에서 하나씩 없어짐.

  • 코드의 환경 및 순서를 보장함.
// ---- 1번
var a = 1;
function outer() {
  function inner() {
    console.log(a); //undefined
    var a = 3;
  }
  inner(); // ---- 2번
  console.log(a);
}
outer(); // ---- 3번
console.log(a);

위 코드의 실행 순서: 코드실행 → 전역(in) → 전역(중단) + outer(in) → outer(중단) + inner(in) → inner(out) + outer(재개) → outer(out) + 전역(재개) → 전역(out) → 코드종료

VariableEnvironment

  1. 현재 컨텍스트 내의 식별자 정보(=record)를 갖고있음.
    • var a = 3 (var a -> 식별자 정보)
  2. 외부 환경 정보(=outer)를 갖고있음.
  3. 선언 시점 (LexicalEnvironment의 snapshot)

LexicalEnvironment

VariableEnvironment와 동일하지만 변경사항을 실시간으로 반영함.

VE vs. LE

담기는 항목은 완벽하게 동일하지만 스냅샷 유지여부가 다름.
VE: 스냅샷 유지.
LE: 스냅샷 유지 x. (실시간으로 변경사항을 계속해서 반영함)

  • 결국 실행 컨텍스트를 생성할 때, VE에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LE를 만들고, 이후에는 주로 LE가 활용됨.

record

호이스팅

변수 정보(record) 수집 과정을 이해하기 쉽게 설명한 '가상 개념'.

호이스팅 규칙:
법칙 1: 매개변수 및 변수는 선언부를 호이스팅함.

// 원래 코드
function a (x) {
  var x = 1;
  console.log(x);
  var x;
  console.log(x);
  var x = 2;
  console.log(x);
}
a(1);

// 매개변수 적용
function a () {
  var x = 1;
  console.log(x);
  var x;
  console.log(x);
  var x = 2;
  console.log(x);
}
a(1);

// 호이스팅 적용
function a () {
  var x;
  var x;
  var x;
  
  x = 1;
  console.log(x);
  console.log(x);
  x = 2;
  console.log(x);
}
a(1);

// 예상					실제
// 1					1
// undefined			1
// 2					2

법칙 2: 함수 선언은 전체를 호이스팅함.

// 원래 코드
function a () {
  console.log(b);
  var b = 'bbb';
  console.log(b);
  function b() {}
  console.log(b);
}
a();

// 호이스팅 적용
function a () {
  var b;
  function b() {}		// => var b = function b() {}
  
  console.log(b);
  b = 'bbb';
  console.log(b);
  console.log(b);
  
// 예상					실제
// undefined			function b
// bbb					bbb
// function b			bbb

함수 정의 2가지 방식:

  1. 함수 선언문 (함수명이 곧 변수명)
  2. 함수 표현식 (정의한 함수를 별도 변수에 할당)

함수 선언문: 호이스팅에 의해 함수 전체가 위로 올라감.
함수 표현식: 호이스팅에 의해 함수 선언부는 위로 올라감.

함수를 함수 선언문으로 구현하면 호이스팅에 의해 함수 전체가 맨 위로 올라가기 때문에 문제가 발생할 수 있음.
예)

// 함수 선언문으로 정의
function sum(x, y) {
  return x + y;
}

console.log(sum(1, 2));
console.log(sum("1", "2"));

function sum(x, y) {
  return `${x} + ${y} = ${x + y}`;
}

console.log(sum(1, 2));
console.log(sum("1", "2"));

// 호이스팅 적용
function sum(x, y) {
  return x + y;
}
function sum(x, y) {
  return `${x} + ${y} = ${x + y}`;
}

console.log(sum(1, 2));
console.log(sum("1", "2"));
console.log(sum(1, 2));
console.log(sum("1", "2"));

// 예상 출력				// 실제 출력
// 3					1 + 2 = 3
// 12					1 + 2 = 12
// 1 + 2 = 3			1 + 2 = 3
// 1 + 2 = 12			1 + 2 = 12

///////////////////////////////////////////////

// 함수 표현식으로 정의
var getSum = (x, y) => x + y;

console.log(getSum(1, 2));
console.log(getSum("1", "2"));

var getSum = (x, y) => `${x} + ${y} = ${x + y}`;

console.log(getSum(1, 2));
console.log(getSum("1", "2"));

// 호이스팅 적용
var getSum;
var getSum;

getSum = (x, y) => x + y;
console.log(getSum(1, 2));
console.log(getSum("1", "2"));

getSum = (x, y) => `${x} + ${y} = ${x + y}`;
console.log(getSum(1, 2));
console.log(getSum("1", "2"));

// 예상 출력				// 실제 출력
// 3					3
// 12					12
// 1 + 2 = 3			1 + 2 = 3
// 1 + 2 = 12			1 + 2 = 12

outer

outerEnvironmentReference
outer: 특정 실행 컨텍스트가 생성될 때의 환경 정보.

스코프: 식별자에 대한 유효범위.
스코프 체인:

// 전역 { A { B { C {} } } }
// A의 outer: 전역의 LE
// B의 outer: A의 LE
// C의 outer: B의 LE

var a = 1;

var outer = function () {
  var inner = function () {
    console.log(a);
    var a = 3;
  };
  inner();
  console.log(a);
};

outer();
console.log(a);

// inner()의 outer은 outer()의 LE.
// 하지만 inner()에서 사용된 a가 outer()에 존재하지 않기 때문에 undefined 출력.

// outer()의 outer은 전역의 LE.
// outer()에서 사용된 a는 전역에 존재하기 때문에 1 출력.

각각의 실행 컨텍스트는 LE 안에 record와 outer가 있고, outer 안에는 그 실행 컨텍스트가 선언될 당시의 LE정보가 다 들어있기 때문에 scope chain에 의해 상위 컨텍스트의 record를 읽어올 수 있다.

this

상황에 따라 다른 의미를 가짐.

this 값

  1. 전역 공간에서의 this:

    • global 객체 (노드)
    • window 객체 (브라우저)
  2. 메소드로서 호출할 때 그 메소드 내부에서의 this:

    • 함수 vs. 메소드 (함수: 독립적, 메소드: 종속적)
    • 메소드에서의 this: 메소드를 호출한 객체
    var func = function (x) {
    console.log(this, x);
    };
    // 호출의 주체가 없으므로 위 this는 global
    func(1);
    
    var obj = { method: func };
    // obj가 method의 주체이므로 this는 obj
    obj.method(2);
  3. 함수로서 호출할 때 그 함수 내부에서의 this:

    • 함수에서의 this: global 객체
    • 메소드 내부에 있는 함수도 예외 x
  4. 콜백 함수 호출할 때 그 함수 내부에서의 this:

    • 콜백 함수도 함수이기 때문에 무조건 전역 객체를 가리킴
    • 예외: 콜백 함수에 별도로 this를 지정한 경우
  5. 생성자 함수 내부에서의 this:

    • 구체적인 인스턴스로 만들기 위한 일종의 틀
    • this는 인스턴스를 가리킴

this를 우회하는 방법

  1. 변수 활용 (내부 스코프에 이미 존재하는 this를 별도의 변수에 할당)
var obj1 = {
	outer: function() {
		console.log(this); // (1) outer

		// AS-IS
		var innerFunc1 = function() {
			console.log(this); // (2) 전역객체
		}
		innerFunc1();

		// TO-BE
		var self = this;
		var innerFunc2 = function() {
			console.log(self); // (3) outer
		};
		innerFunc2();
	}
};

// 메서드 호출 부분
obj1.outer();
  1. 화살표 함수 활용
    화살표 함수가 도입된 이유: 함수 내부에서 this가 유실되기 때문에

    일반 함수와 화살표 함수의 가장 큰 차이점: this binding 여부

var obj = {
	outer: function() {
		console.log(this); // (1) obj
		var innerFunc = () => {
			console.log(this); // (2) obj
		};
		innerFunc();
	}
}

obj.outer();

명시적 this binding

call

호출하고자 하는 함수 끝에 .call()을 호출하고 첫 번째 매개변수로 this로 binding하고자 하는 객체를 전달.

// 1
var func = function (a, b, c) {
  console.log(this, a, b, c);
};

// no binding
func(1, 2, 3);

// 명시적 binding
func.call({ x: 1 }, 4, 5, 6);

// 2
var obj = {
  a: 1,
  method: function (x, y) {
    console.log(this.a, x, y);
  },
};

obj.method(2, 3);

obj.method.call({ a: 4 }, 5, 6);

apply

call과 완전히 똑같은 기능인데 호출 함수의 매개변수들을 대괄호로 묶어줘야 함.

// 1
var func = function (a, b, c) {
  console.log(this, a, b, c);
};

// no binding
func(1, 2, 3);

// 명시적 binding
func.apply({ x: 1 }, [4, 5, 6]);

// 2
var obj = {
  a: 1,
  method: function (x, y) {
    console.log(this.a, x, y);
  },
};

obj.method(2, 3);

obj.method.apply({ a: 4 }, [5, 6]);

call, apply 활용

  1. 유사배열객체(array-like-object)
    • 배열과 유사한 객체:
      • 반드시 length가 필요함.
      • index가 0부터 1씩 증가해야 함.
//객체에는 배열 메서드를 직접 적용할 수 없어요.
//유사배열객체에는 call 또는 apply 메서드를 이용해 배열 메서드를 차용할 수 있어요.
var obj = {
  0: "a",
  1: "b",
  2: "c",
  length: 3,
};
Array.prototype.push.call(obj, "d");
console.log(obj); // { 0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4 }

var arr = Array.prototype.slice.call(obj);
console.log(arr); // [ 'a', 'b', 'c', 'd' ]
  1. 생성자 내부에서 다른 생성자를 호출(공통된 내용의 반복 제거)
function Person(name, gender) {
	this.name = name;
	this.gender = gender;
}
function Student(name, gender, school) {
	Person.call(this, name, gender); // 여기서 this는 student 인스턴스!
	this.school = school;
}
function Employee(name, gender, company) {
	Person.apply(this, [name, gender]); // 여기서 this는 employee 인스턴스!
	this.company = company;
}
var kd = new Student('길동', 'male', '서울대');
var ks = new Employee('길순', 'female', '삼성');
  1. 여러 인수를 묶어 하나의 배열로 전달할 때 사용
var numbers = [10, 20, 3, 26, 45];

var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);

console.log(max, min);

// 근데 위보다 더 쉬운 방법은 spread operator의 활용
var max = Math.max(...numbers);
var min = Math.min(...numbers);

bind

call, apply와 마찬가지로 this를 binding하는 함수.
차이점: 즉시 호출되지 않음.

목적:
1. 함수에 this를 미리 적용

// bind
var func = function (a, b, c, d) {
  console.log(this, a, b, c, d);
};

// func에 this를 미리 binding해서 새로운 함수에 할당
// 새로운 함수의 this는 고정
var bindFunc1 = func.bind({ x: 1 });

bindFunc1(2, 3, 4, 5);
  1. 부분 적용 함수
// 매개변수를 미리 지정 가능 (부분적으로)
var bindFunc2 = func.bind({ x: 1 }, 4, 5);

bindFunc2(6, 7);

모든 함수는 name 프로퍼티가 있는데 bind된 함수들은 name 프로퍼티가 bound라는 접두어가 붙음.
예)

console.log(func.name);			// func
console.log(bindFunc1.name);	// bound func
console.log(bindFunc2.name);	// bound func

bind를 애초에 함수를 구현할 때 붙일 수 있음.
예)

var obj = {
	outer: function() {
		console.log(this);
		var innerFunc = function () {
			console.log(this);
		}.bind(this); // innerFunc에 this를 결합한 새로운 함수를 할당
		innerFunc();
	}
};
obj.outer();
profile
COYG🔴⚪

0개의 댓글