배꼽파지 않도록 잘 개발해요

[엘리스sw] 3주차 3일 - 자바스크립트 동작원리 본문

교육/엘리스 SW 학습 내용

[엘리스sw] 3주차 3일 - 자바스크립트 동작원리

꼽파 2024. 1. 8. 16:05


◆  자바스크립트 함수가 실행되는 과정
◆  실행 컨텍스트
◆  this가 가리키는 것
◆  화살표 함수와 일반 함수의 this
◆  자바스크립트 Closure
◆  ES6 Rest, Spread Operator


자바스크립트 함수가 실행되는 과정

자바스크립트 코드의 실행1

// 어떤 코드도 없는 경우
  • this : window 객체 (브라우저의 최상위단 스코프)
  • Variable Object(변수들을 담는 객체) : {}
  • Scope chain : []

· 자바스크립트 엔진은 코드가 없어도 실행 환경(실행 컨텍스트)을 초기화한다.
· 스코프(scope)는 코드가 현재 실행되는 환경, 맥락(context)을 의미한다.
- this 포인터, 스코프에 저장된 변수들, 스코프 체인 등이 환경에 포함된다.
- this 포인터(레퍼런스 변수)의 경우, 글로벌 스코프에서는 window를 가리킨다.


자바스크립트 코드의 실행2

function myFunc() {
	let a = 10;
	let b= 20;
	function add(first, second) {
		return first + second
	}
	return add(a, b)
}

myFunc()

myFunc() 실행

  • this : undefined(strict mode) (자바스크립트의 strict mode로 실행했을 떄 this는 undefined가 됨)
  • Variable Object(변수들을 담는 객체) : {
    a: 10
    b: 20
    add: function {...}
    }
  • Scope chain : [global]

add 함수 실행

  • this : undefined
  • Variable Object(변수들을 담는 객체) : {
    first: 10
    second: 20

    }
  • Scope chain :  [ myFunc, global ]

·  함수가 실행되면 함수 스코프에 따라 환경이 만들어진다.

·  this, 함수 스코프의 변수들, 그리고 스코프 체인이 형성된다.

·  스코프 체인을 따라 글로벌 환경에 도달한다.


자바스크립트 코드의 실행 3

let o = {
	name: 'Daniel',
	method: function(number) {
		return this.name.repeat(number)
	}
}

function myFunc() {
	let n = 10
	return o.method(n)
}

myFunc()

 

처음 실행

  • this : window
  • Variable Object(변수들을 담는 객체) : {
    o: {...}
    myFunc: function() {...} 
    }
  • Scope chain :  [ ]

myFunc 함수 실행 컨텍스트

  • this : undefined
  • Variable Object(변수들을 담는 객체) : {
    n: 10
    }
  • Scope chain :  [ global ]

o.method 호출

  • this : o
  • Variable Object(변수들을 담는 객체) : {
    number: 10
    }
  • Scope chain :  [ global ]

myFunc 함수 실행 종료 후

  • Variable Object(변수들을 담는 객체) : { n: 10 }
  • Scope chain :  [ global ]

전역 컨텍스트

  • Variable Object(변수들을 담는 객체) : { 
    o: {...},
    myFunc: function() {...}
    }

·  객체의 메서드의 경우, 메서드 환경에서의 this는 해당 객체를 가리키게 된다.

·  하지만 this가 가리키는 것은 환경에 따라 변할 수 있다.


실행 컨텍스트

실행 컨텍스트(Execution context)
· 실행 컨텍스트 혹은 실행 맥락은 자바스크립트 코드가 실행되는 환경
· 코드에서 참조하는 변수, 객체(함수 포함), this 등에 대한 레퍼런스가 있음.
· 실행 컨텍스트는 전역에서 시작해, 함수가 호출될 때 스택에 쌓이게 됨.

 

전역 실행 컨텍스트와 함수 실행 컨텍스트

전역 실행 컨텍스트(Global Execution Context) 함수 실행 컨텍스트(Function Execution Context)
- 자바스크립트가 실행될 때 만들어진다.
- 함수나 클래스 내부의 코드를 무시하고 컨텍스트를 가진다.
- 함수가 실행될 때 만들어진다.
- 함수에 존재하는 코드는 함수 내부에서만 컨텍스트를 가진다.

·  전역 실행 컨텍스트 → 함수 실행 컨텍스트
·  호출되는 순서에 따라 스택에 쌓이고, 나중에 쌓인 함수부터 실행됨.

 

실행 컨텍스트 스택 동작

function outer() {
    console.log("Outer function start");
    inner();
    console.log("Outer function end");
}

function inner() {
    console.log("Inner function");
}

outer();
실행 현재 스택
1. outer 함수가 호출 outer
2. outer 함수 내의 inner 함수 호출 outer → inner
3. inner 함수 실행 완료 → inner 함수 스택에서 제거 outer 
4. outer 함수 실행 완료 → outer 함수 스택에서 제거 (비어있음)

 

렉시컬 환경
식별자와 식별자에 연결된 값을 저장하는 자료구조

function outer() {
    let outerVar = "I'm in the outer function";

    function inner() {
        let innerVar = "I'm in the inner function";
        console.log(outerVar); // outerVar는 외부 함수의 렉시컬 환경에 접근 가능
        console.log(innerVar); // innerVar는 현재 함수의 렉시컬 환경에 접근 가능
    }

    inner();

    console.log(outerVar); // outerVar는 여전히 외부 함수의 렉시컬 환경에 접근 가능
    // console.log(innerVar); // 오류: innerVar는 inner 함수의 렉시컬 환경에서만 접근 가능
}

outer();

 

outer 함수 렉시컬 환경은 outerVar을 포함하고 있음.
inner 함수 렉시컬 환경은 innerVar을 포함하고 있음.

inner 함수는 외부 함수인 outer 함수의 렉시컬 환경에 접근할 수 있음.
inner 함수의 실행이 완료되면 해당 실행 컨텍스트가 호출 스택에서 제거됨.

outer 함수는 종료되지 않고 실행되는데, 
이때 outer 함수의 변수 outerVar에는 접근 가능하지만,
inner 함수의 렉시컬 환경에는 접근이 불가능함.

outer 함수의 실행이 완료되면 해당 실행 컨텍스트가 호출 스택에서 제거됨.


this가 가리키는 것

함수가 호출되는 상황

함수 호출

함수를 직접 호출

function simple() {
    console.log("simple");
}

simple();  // simple

 

메서드 호출

객체의 메서드를 호출

const myObject = {
    name: function() {
        console.log('Gildong');
    }
};

myObject.name();  // Gildong


생성자 호출

생성자 함수를 호출

new 키워드를 사용하여 생성자 함수를 호출하여 객체의 새 인스턴스가 생성되고, 일반적으로 속성을 초기화하고 작업을 진행함.

function myClass() {
    this.name = "Gildong";
}

const myInstance = new myClass();
console.log(myInstance.name);  // Gildong

 

간접 호출

call, apply 등으로 함수를 간접 호출

function loveHate(arg1, arg2) {
    console.log(`${arg1} hates ${arg2}`);
}

const object = { key: "value" };

loveHate.call(object, "Gildong", "Dooly");  // Gildong hates Dooly
loveHate.apply(object, ["Gildong", "Dooly"]);  // Gildong hates Dooly

 

콜백함수

콜백함수는 특정 동작 이후 불려지는 함수로, 보통 다른 함수의 인자로 보내지는 함수를 의미함.
ex. setTimeout(function{}), addEventListener

function abc(callback) {
    console.log("안녕하세요.");
    setTimeout(() => {
        callback();
    }, 5000)  // 5초 뒤에 실행
}

function def() {
    console.log("5초 뒤에 또보네요!");
}

abc(def);

 

dynamic binding
· 함수는 다양한 상황에서 호출될 수 있다.
· 함수의 호출 환경에 따라 this는 동적으로 세팅된다.
· 이렇게 this가 환경에 따라 바뀌는 것을 동적 바인딩(dynamic binding)이라 한다.
· bind, apply, call 등으로 this가 가리키는 것을 조작할 수 있다.

let o = {
    name: "Gildong",
    f1: () => {  // 화살표 함수
        console.log("[f1] this: ", this);
    },

    f2: function () {  // 일반 함수
        console.log("[f2] this: ", this);
    },
};

o.f1();  // global
o.f2();  // o

setTimeout(o.f1, 10);  // global
setTimeout(o.f2, 20);  // global

·  f1 : 화살표 함수, 호출 시 this는 함수가 생성된 환경을 가리키도록 고정됨.

·  f2 : 일반 함수, this는 함수를 호출된 환경을 가리키며, this는 동적으로 바뀔 수 있음.

·  f2 : 객체의 메서드, 호출될 때 객체가 this로 할당됨.

 

·  최상단 스코프의 실행 컨텍스트는 전역이다.

·  setTimeout으로 함수의 실행 환경을 바꾼다.

 

this를 조작하는 경우

·   bind, call, apply 등의 함수로 this를 조작한다.
·   setTimeout은 함수 호출과는 다른 콜백호출이다.

let o = {
  name: "Gildong",
  printName: function() {
    console.log("내 이름은", this.name);
  },
};

o.printName();  // 내 이름은 Gildong
setTimeout(o.printName, 10); // 내 이름은 undefined
setTimeout(o.printName.bind(o), 20);  // 내 이름은 Gildong
  • o.printName(); : printName 메서드는 o 객체 내에서 호출되었기 때문에 this는 o를 참조함.
  • setTimeout(o.printName, 10); : printName 함수가 setTimeout에 전달되었을 때 this의 값이 o가 아닌 전역 객체(브라우저 환경에서는 window)를 참조함. this가 전역 객체를 가리키므로, this의 name속성이 지정되어 있지 않아 undefined를 반환함.
  • setTimeout(o.printName.bind(o), 20); : bind 메서드를 사용하여 o 객체를 printName 함수에 명시적으로 바인딩함. 따라서 this는 항상 o를 참조하여 this.name은 Gildong이 됨.

화살표 함수와 일반 함수의 this

화살표 함수 vs 일반 함수

· 화살표함수의 this : 호출된 함수를 둘러싼 실행 컨텍스트를 가리킨다.
· 일반 함수의 this : 새롭게 생성된 실행 컨텍스트를 가리킨다.

const o = {
    method() {
        console.log("context : ", this)  // o
        let f1 = function () {  // 일반 함수
            console.log("[f1] this : ", this)
        }
        let f2 = () =>  // 화살표 함수
            console.log("[f2] this : ", this)
        f1()  // global
        f2()  // o
    },
};

o.method()
  • f1()은 실행 될 때 새로운 컨텍스트를 생성한다. 이때 f1에 바인딩된 컨텍스트가 없으므로 this는 global를 가리킨다.
  • f2()는 함수 컨텍스트를 생성하며 this 변수는 부모의 컨텍스트를 가리킨다. 따라서 this는 o가 된다.

· 화살표 함수의 this는 정해지면 바꿀 수 없다.
· call, bind, apply를 사용해도 바뀌지 않는다.
· setTimeout 등 this가 바뀌는 상황에서 유용하다.

 

화살표 함수 활용 예시

window.name = "Gildong";
let o = { name: "Go" };

let arrowFunction = (relationship) => console.log(this.name + relationship)

arrowFunction('hates Dooly');  // Output: Gildonghates Dooly
arrowFunction.bind(o)('hates Dooly');  // Output: Gildonghates Dooly
arrowFunction.call(o)('hates Dooly');  // Output: Gildongundefined
arrowFunction.apply(o)('hates Dooly');  // Error: Uncaught TypeError: arrowFunction.apply(...) is not a function

자바스크립트 Closure

·  자바스크립트에서 함수는 일급 객체(first-class object) 이다.

·  일급 객체(first-class object)일급 객체란, 다른 변수처럼 대상을 다룰 수 있는 것을 말한다.
·  즉, 자바스크립트에서 함수는 변수처럼 다룰 수 있다.

 

클로저(closure)
·  자바스크립트 클로저는, 함수의 일급 객체 성질을 이용한다.
·  함수가 생성될 때, 함수 내부에서 사용되는 변수들이 외부에 존재하는 경우 그 변수들은 함수의 스코프에 저장된다.
·  함수와 함수가 사용하는 변수들을 저장한 공간을 클로저라고 한다.

function memberInfo() {
	// 외부 함수의 변수
    let name = "";
    let birthdate = "";

	// 내부 함수들
    function changeName(text) {
        name = text;
    }

    function changeBirthdate(text) {
        birthdate = text;
    }

    function print() {
        console.log("Name: ", name);
        console.log("Birthdate: ", birthdate);
    }
    
    // 클로저를 통해 외부 함수의 변수에 접근 가능한 함수들을 반환
    return { changeName, changeBirthdate, print };
}


// person1 객체 생성
const person1 = memberInfo();

person1.changeName('고길동');
person1.changeBirthdate('1940-04-04');
person1.print();


// person2 객체 생성
const person2 = memberInfo();

person2.changeName('둘리');
person2.changeBirthdate('1004-04-04');
person2.print();

클로저는 함수 memberInfo 내에 정의된 내부 함수들이 외부 함수의 변수에 접근할 수 있는 기능을 나타낸다. 

클로저를 통해 내부 함수들은 name 및 birthdate와 같은 외부 함수의 변수에 접근하고 수정할 수 있다. 

이를 통해 person1 및 person2 객체가 서로 독립적으로 상태를 유지할 수 있다.

 

return { changeName, changeBirthdate, print };

memberInfo 함수가 실행될 때 내부 함수들이 반환되면서 외부 함수의 변수에 접근할 수 있는 클로저가 생성된다. 

따라서 person1, person2는 서로 다른 클로저를 가지며, 독립된 상태를 유지할 수 있다.
이 객체를 반환하지 않을 경우, 외부에서 내부 함수를 통해 name, birthdate에 접근할 방법이 없어지게 된다.


ES6 Rest, Spread Operator

Rest Operator
· 함수의 인자, 배열, 객체 중 나머지 값을 묶어 사용하도록 한다.
· 함수의 인자 중 나머지를 가리킨다.
· 배열의 나머지 인자를 가리킨다.
· 객체의 나머지 필드를 가리킨다.

함수 인자 Rest Operator

·  함수 인자 rest operator는 인자들을 배열로 묶는다.

function findMin(...rest) {
	return rest.reduce((a, b) => 
		a < b ? a : b)
}

findMind(7, 3, 5, 2, 4, 1) // 1

 

·  rest에는 숫자들이 배열로 담긴다.
·  reduce 함수로 min값을 리턴한다.

 

객체 Rest Operator

·  객체의  rest operator는 지정된 필드 이외의 나머지 필드를 객체로 묶는다.

const o = {
	name: "Daniel",
	age: 23,
	address: "Street",
	job: "Software Engineer",
};

const { age, name, ...rest } = o;
findSamePerson(age, name);

·  age, name을 제외한 나머지 필드는 rest 변수로 할당된다.

 

배열의 rest Operator

·  배열의 rest operator는 나머지 인자를 다시 배열로 묶는다.
·  sumArray의 tail 변수는 첫 번쨰 원소 head를 제외한 나머지 값들을 다시 배열로 묶는다.
·  tail은 하나씩 줄어들게 되며, 길이가 0이 되면 합을 반환한다.

function sumArray(sum, arr) {
	if (arr.length === 0) return sum;
 	const [head, ...tail] = arr;
	return sumArray(sum + head, tail);
}

sumArray(0, [1, 2, 3, 4, 5]);

 

spread Operator
·   묶인 배열 혹은 객체를 각각의 필드로 변환한다.
·   객체는 또 다른 객체로의 spread를 지원한다.
·   배열은 또 다른 배열의 인자, 함수의 인자로의 spread를 지원한다.

·   spread operator로 객체를 복사하면, 객체나 배열 필드의 경우 reference만 복사된다.

let o = {
	name: "Daniel",
	age: 23,
	address: "Street",
	job: "Software Engineer",
}

let o2 = { ...o, name: "Tom", age: 24 }

let o3 = { name: "Tom", age: 24, ...o }

o2.job  // Software Engineer
o3.name  // Daniel

· spread operator의 등장 순서에 따라 객체의 필드가 덮어씌워 질 수 있다.
· ...o가 뒤에 등장하면, 기존의 name 필드가 나중에 등장하여 앞의 {name: "Tom"}을 덮어 씌운다.

 

배열 Spread operator

// mergeObjects 함수 정의
function mergeObjects(...objects) {  // { a: 1, b: 3, c: 7 }
    return Object.assign({}, ...objects);
}

// findMinInObject 함수 정의
function findMinInObject(o) {
	return Math.min(...Object.values(o));
}

let o1 = { a: 1 }
let o2 = { b: 3 }
let o3 = { c: 7 }

console.log(findMinInObject(mergeObjects(o1, o2, o3)));  // 1

·  mergeObjects는 주어진 객체들의 필드를 합친다.
·  findMinInObject에서는 객체의 필드들 중 최솟값을 반환한다.
·  Object.values는 객체 값들의 배열을 반환한다.
·  배열 spread operator로, Math.min의 인자를 넘긴다.

 

	return Math.min(...Object.values(o));

·   Object.values(o) : 객체 o의 값들을 배열로 변환 → [1, 3, 7]
·   Math.min(...[1, 3, 7]) : 배열을 인자로 전달 → Math.min(1, 3, 7)

728x90