프로토타입과 생성자 함수
객체는 현실의 개념을 추상화하여 정의한 것으로 상태와 속성, 메서드를 가지고 있다. 자바스크립트는 프로토타입 기반객체지향 프로그래밍이다. 여기서 객체지향 프로그래밍이란, 객체라는 기본 단위로 나누고 이들의 상호작용을 서술하는 방식을 말한다.
프로토타입 객체
- 객체의 인스턴스를 만드는 부모 객체의 개념인 프로토타입
- 자바스크립트의 모든 객체는 부모 객체인 프로토타입 객체와 연결
- 부모객체의 속성과 메서드를 상속받아 사용
- 모든 객체는 [[prototype]] 인터널 슬롯을 가지고 있다 -> [[prototype]]은 상속을 위해 사용
- __proto__ 라는 접근자 속성으로 접근 가능 -> __proto__ 로 Object.prototype 객체에 접근
프로토타입 객체 - 생성하기
- 다른 객체에는 없지만, 함수에는 prototype 필드가 존재
- 함수에 부모객체 존재 -> 함수를 활용하여 부모객체의 인스턴스 생성가능
생성자 함수
- 인스턴스 객체 생성시 사용
- 다른 함수와 구분을 위해 대문자 함수명이 관습
- new 키워드와 함께 호출
- 인스턴스의 부모객체 == 생성자 함수.prototype
- constructor: 자기 자신을 생성한 객체 참조
추가 정리)
1. 자바스크립트에서 모든 함수는 객체이기 때문에 모든 함수 객체에는 prototype 속성이 존재하지만, 이 속성은 함수가 생성자로 사용될 때만 의미가 있다. 일반 함수로 사용되는 경우 빈 객체.
2. 생성자 함수가 new 키워드를 사용해서 새로운 객체를 만들면, 새로 생성된 객체의 [[prototype]] 은 생성자 함수의 prototype 속성을 참조하게 된다 -> 이 과정을 통해 새로 생성된 객체는 생성자 함수의 prototype 객체에 정의된 모든 속성과 메서드를 사용할 수 있다.
3. 생성자 함수의 prototype 은 생성자 함수가 정의되면, 자바스크립트 엔진은 해당 생성자 함수에 연결된 prototype 객체를 자동으로 생성한다.
4. prototype 객체는 기본적으로 constructor 속성을 가지며, 이 속성은 생성자 함수 자체를 가리킨다.
prototype 객체는 생성자 함수가 인스턴스를 만들 때 참조하는 객체로 constructor 필드에는 생성자 함수가 들어갈 수 밖에 없다.
5. prototype 객체에는 생성자 함수로부터 생성된 모든 인스턴스가 공통으로 가져야 할 메서드와 속성을 정의하는 장소이다.
6. prototype 객체에 따로 속성이나 메서드를 추가하지 않으면 해당 prototype 객체는 빈객체로 시작한다.
// 생성자 함수 정의됨 -> 정의됨과 동시에 프로토타입 객체 만들어짐
function Person(name, age) {
this.name = name;
this.age = age;
}
const person1 = new Person('Alice', 30);
console.log(person1.name); // Alice
console.log(person1.age); // 30
// 프로토타입 객체에 공통 속성 추가 -> 생성자 함수로 찍어낸 모든 인스턴스가 greet 메서드 사용 가능해짐
Person.prototype.greet = function() {
console.log('Hello, my name is ' + this.name);
};
person1.greet();
// person1.greet; // Hello, my name is Alice
// constructor 속성
console.log(person1.constructor === Person); // true
person1 객체는 직접적으로 constructor 속성을 가지고 있지 않지만, person1.__proto__ 가 Person.prototype 을 참조하기 때문에 person1.constructor 는 Person.prototype.constructor(Person) 와 같다.
프로토타입 chain 과 상속
객체의 속성 참조시, 속성이 없을 경우 프로토타입 체인이 동작하게 된다.
모든 객체의 부모는 Object.prototype 이므로, 거슬러 올라가다가 Object.prototype 객체를 만나면 prototype 체이닝이 마무리된다. 만약 마지막 부모객체(Object.prototype) 에서까지 속성을 찾지 못할 경우, undefined 가 반환된다.
(Object.prototype 객체의 부모는 없기 때문에 End of prototype chain 이라고도 부름)
프로토타입 객체의 확장과 상속
인스턴스 객체는 부모 객체의 메서드와 속성을 참조한다. (prototype 객체의 메서드와 속성 참조)
function Person() { // 생성자 함수
}
Person.prototype.age = 20; // 프로토타입 객체에 속성 추가
const joy = new Person(); // 인스턴스 만듦
console.log(joy); // undefined
console.log(joy.age); // 28
joy 라는 객체 내에 age 필드가 없음에도, 부모 객체의 속성을 상속받았기 때문에 age 속성을 사용 가능한 것을 알 수 있다.
이렇게 객체를 참조할 때 객체 내의 속성부터 프로토타입 체인을 통해 부모객체의 속성까지 참조가 가능하다.
만약 동일한 속성명이나 메서드명이 있는 경우, 객체의 속성과 메서드가 먼저 참조된다.
- property shadowing: 프로토타입 속성이 가려짐
- method overriding: 프로토타입 메서드가 가려짐
Class 문법
ES6 부터 class 문법이 추가됐다. 기존 프로토타입 기반 상속 메커니즘의 추상화로, 기존 프로토타입 패턴의 syntax sugar 라고 생각하면 된다.
class 의 형태
- class 클래스명 { ... }
- 클래스 내에는 메서드만 작성 가능
- 인스턴스 생성할 때는 new 클래스명();
class 문법
- constructor
인스턴스를 생성하고, 클래스 필드를 초기화하기 위한 약속된 특수 메서드로 인스턴스가 생성될 때, 호출되는 메서드이다.
인스턴스 생성시 전달한 인자를 constructor 메서드의 인자로 받을 수 있다. 그리고 constructor 는 생략이 가능하다. (생략이 되면 인스턴스 생성시 호출될 메서드가 없을 뿐)
class Person {
constructor(name) { console.log(name);}
}
const person1 = new Person('joy'); // 인스턴스 생성시 joy 출력됨
- 인스턴스 method
인스턴스 객체가 생성된 후에 사용 가능한 메서드이다. 인스턴스 메서드는 객체가 생성된 후에만 존재하며, 객체가 생성되기 전에는 호출할 수 없다. 클래스 내부에 정의된 메서드로, 메서드 내에서 클래스를 this 로 접근 가능하고, 인스턴스 속성에도 접근이 가능하다.
- 정적 메서드
클래스의 인스턴스를 생성하지 않아도 호출할 수 있는 메서드를 정의할 때 사용한다. 메서드 이름 앞에 static을 붙여야하고, util 용의 함수를 정의할 때 사용한다.
- 인스턴스 속성
클래스 내부에 캡슐화된 변수로 멤버 변수라고도 한다. 인스턴스 속성은 this 에 바인딩이 필요하다. 또 속성은 인스턴스 생성 이후 바로 참조가능해야하므로, 인스턴스 생성시 호출되는 constructor 메서드에서 인스턴스 속성 초기화를 진행한다.
- getter
- 특정 인스턴스 속성을 조회하며 조작하는 메서드
- 메서드 이름 앞에 get 키워드 사용하며, getter 메서드는 무조건 값을 반환함
- 일반 메서드처럼 호출하지 않고, 속성처럼 참조하는 형식으로 사용
class Person {
constructor(name) {
this.name = name;
}
get uppercaseName() { // getter
return this.name.toLocaleUpperCase();
}
}
const Joy = new Person('joy');
// 참조형태
console.log(Joy.upperCaseName); // JOY
- setter
- 특정 인스턴스 속성에 할당하며 조작하는 메서드
- 메서드 이름 앞에 set 키워드 사용
- 일반 메서드처럼 호출하지 않고, 속성처럼 할당하는 형식으로 사용
class Person {
constructor(age) {
this.age = age;
}
set plusYears(age) {
this.age += age;
}
}
const Joy = new Person(28);
Joy.plusYears = 3;
console.log(Joy.age); // 31
- 상속 (extends)
코드 재사용 관점에서 상속이 필요하다. (부모 - 자식) extends 와 super 키워드를 통해 class 에서 상속을 구현한다.
extends
- 부모 클래스를 상속받는, 자식 클래스를 정의할 때 사용함
- class 부모 { ... }
- class 자식 extends 부모 { ... }
super()
- 부모클래스의 생성자를 호출하게 됨
- 부모클래스의 인스턴스 속성을 바인딩함
- 자식클래스의 생성자가 this 에 접근하고 수정 가능
- 자식클래스의 constructor 사용시 constructor 반환문 전에 사용되어야 함
- super.메서드() -> super 키워드를 통해 부모 클래스에 접근 가능, 부모클래스 메서드 접근시 suepr.메서드이름으로 접근하여 호출
- super 는 생성자 함수 내에서 this 를 사용하기 전에 호출해야 함 -> 자식 클래스의 생성자가 부모 클래스의 생성자를 통해 초기화되기 때문
class Animal {
constructor(name) {
this.name = name;
}
}
class Dog extends Animal {
constructor(name) {
super(name);
}
sleep() {
return this.name + ' zzz...';
}
}
const dog = new Dog('pepe');
console.log(dog.name); // pepe
console.log(dog.sleep()); // pepe zzz...
추가 정리)
super 는 쓰는 경우도 있고 안 쓰는 경우도 있다.
1. super 을 무조건 써야하는 경우: 자식 클래스에서 생성자를 정의하는 경우
- super() 를 반드시 호출해야 함. 이는 부모 클래스의 생성자를 호출하여 부모 클래스에서 정의된 인스턴스 속성을 초기화 함.
- super() 를 호출한 후에 자식 클래스의 생성자에서 this 를 사용할 수 있음 -> super 을 호출하지 않으면 this 사용 불가, 에러남
2. 자식 클래스에서 생성자를 정의하지 않은 경우
- 자식 클래스에서 생성자가 명시적으로 정의되지 않았다면, 자바스크립트 부모 클래스의 생성자를 자동으로 호출하여 인스턴스를 초기화함. 이 경우 super() 를 직접 호출할 필요가 없음.
3. 자식 클래스의 메서드에서 super 를 호출하는 경우
- 부모 클래스의 메서드나 속성을 사용하고 싶을 때 super 를 사용 -> 자식 클래스의 메서드에서 부모 클래서의 메서드 호출 가능
- 부모 클래스의 기능을 확장하거나 재사용 가능
1 번에서 this 바인딩 과정
1. 객체 생성
- 자식 클래스의 생성자가 호출되면, 자바스크립트는 먼저 빈 객체를 생성 -> 이 객체는 this 에 바인딩 됨
2. 부모 클래스 생성자 호출 super()
- 자식 클래스 생성자 내에서 super() 호출됨
- super() 는 부모 클래스의 생성자를 호출하며, 이 때 부모 클래스의 생성자 내에서 this 는 방금 생성된 빈 객체에 바인딩 됨
- 부모 클래스 생성자 호출이 완료되면, 부모 클래스가 정의한 속성들이 this 에 바인딩 됨
3. 자식 클래스에서의 this 초기화
- super() 호출이 끝난 후 자식 클래스의 생성자는 this 에 접근할 수 있으며, 자식 클래스에서 필요한 추가적인 속성이나 메서드를 정의할 수 있음 (여기서 this 는 여전히 같은 빈 객체를 가리킴, 현재는 빈 객체가 아니지만)
4. 인스턴스 반환
- 자식 클래스의 생성자 작업이 모두 완료되면, this 는 최종적으로 초기화된 객체로 인스턴스가 됨
this 와 화살표 함수
this 란 컨텍스트 참조가 가능한 키워드 (전역 Context, 함수 Context) -> 객체를 참조하는 역할, 어떤 객체인지 동적으로 결정됨
(Context 를 참조한다는 것은 특정 시점에 어떤 상태를 담은 객체를 참조하는 것을 생각하면 됨)
함수 Context
- 함수가 호출될 때, 매개변수로 this 가 암묵적으로 전달
- 함수 호출방식에 따라 this 에 바인딩되는 객체가 상이
- ES6 화살표 함수는 this 바인딩을 제공하지 않음
- 함수호출 방식: 함수호출, 객체의 메서드 호출, 생성자 함수 호출, apply, call, bind 로 호출
함수 호출 방식
1. 전역 컨텍스트에서 this
- 브라우저 환경: 전역 스코프에서 this 는 window 객체를 가리킨다
console.log(this); // 윈도우 객체 출력
- 서버 측 환경 (Node.js 같은..): 서버 측에서는 전역 객체가 global 이다. 따라서 전역 스코프에서 this 는 global 을 가리킨다.
console.log(this); // global 객체를 출력
2. 함수 호출 방식에서의 this
- 일반 함수 호출
일반 함수 내부에서 this 는 전역 객체를 참조한다. 즉, 브라우저 환경에서는 window, Node.js 에서는 global 이다.
function showThis() {
console.log(this);
}
showThis(); // 브라우저에서는 window, Node.js에서는 global을 출력
- 내부(중첩) 함수에서의 this
객체의 메서드 내에 내부 함수가 있을 경우, 함수 호출방식으로 취급되어 전역객체를 바라본다.
이를 해소하기 위해 1. this 를 다른 변수에 저장하여 사용하거나, 2. 내부함수를 해당 객체 bind 하는 방식으로 해소할 수 있다.
1. this 를 다른 변수에 저장하여 사용
객체의 this 를 메서드 내에서 참조 가능하기 때문에, 객체의 this 를 메서드 내 지역변수에 담아두고 내부 함수에서 지역변수를 참조하면 객체의 this 를 사용할 수 있다.
const foo = {
name: 'joy',
age: 28,
getName() {
const that = this;
console.log(this.name);
function getAge() { // 내부 함수
console.log(that.age);
}
getAge();
},
};
foo.getName();
2. 내부함수를 해당 객체 bind 하는 방식
내부 함수에 해당 객체를 bind 시키면 this 를 사용할 수 있다.
const foo = {
name: 'joy',
age: 28,
getName() {
console.log(this.name);
function getAge() { // 내부 함수
console.log(this.age);
}
getAge.bind(foo)(); // 내부 함수에 해당 객체를 바인딩
},
};
foo.getName();
추가 설명)
1. 바깥 함수가 객체의 메서드인 경우
- 객체의 메서드 내에 정의된 중첩 함수는 일반 함수로 간주
- 중첩 함수가 호출될 때 this 는 전역 객체를 참조 -> 중첩 함수가 메서드로 호출되지 않고 단순히 함수로 호출되기 때문
- 따라서, 중첩 함수 내에서 this 는 바깥 함수(메서드) 의 this(객체) 를 상속받지 않음
2. 일반 함수 내 중첩 함수
- 일반 함수 내에 정의된 중첩 함수도 마찬가지로 일반함수로 간주
- 중첩 함수가 호출될 때 역시 this 는 전역 객체를 참조
-> 결국 바깥 함수가 메서드이든 일반함수이든, 중첩 함수의 this 는 전역 객체에 바인딩됨
3. 객체 메서드 호출에서 this
메서드는 객체의 속성으로 정의된 함수이다. 메서드를 호출하면 그 메서드 내에서 this 는 메서드를 호출한 객체를 참조한다(바인딩됨). 즉, 메서드가 호출된 객체 자체가 this 에 바인딩된다.
프로토타입 객체의 메서드도 동일하게 해당 메서드를 호출한 객체에 바인딩된다.
인스턴스가 메서드를 호출할 때, 그 메서드는 인스턴스 객체의 프로토타입에서 찾는다. this 는 호출한 인스턴스를 참조한다.
- 객체의 메서드
bar 객체에 getName 이라는 메서드를 동적으로 생성하고, foo.getName 을 참조해두었다.
foo, bar 에 같은 메서드를 참조해두었지만, 출력 값이 다르다 -> this 는 호출한 객체에 바인딩 되기 때문에 결과가 상이한 것
const foo = {
name: 'joy',
getName() {
console.log(this.name);
},
};
const bar = {
name: 'kei',
};
bar.getName = foo.getName;
foo.getName(); // joy
bar.getName(); // kei
- 프로토타입 객체의 메서드
객체의 메서드와 동일한 원리로 바인딩된다. foo 에서 프로토타입 메서드인 getName 을 호출했을 때, 프로토타입의 this 는 호출한 객체인 foo 에 바인딩 되어서 기존 Person 이 가지고 있던 속성 joy 가 아닌 kei 를 출력하게 된다.
function Person() {
this.name = 'joy',
}
Person.prototype.getName = function () {
console.log(this.name);
};
const foo = new Person();
foo.name = 'kei';
foo.getName();
추가 설명)
function Car(make, model) {
this.make = make;
this.model = model;
}
Car.prototype.getInfo = function() {
return `Car make: ${this.make}, model: ${this.model}`;
};
const myCar = new Car('Toyota', 'Corolla');
console.log(myCar.getInfo()); // 출력: Car make: Toyota, model: Corolla
1. myCar 객체는 Car 의 인스턴스이며, Car.prototype 을 프로토타입으로 가진다.
2. myCar 객체에는 make 와 model 속성이 직접 정의되어 있으며, getInfo 메서드는 프로토타입 체인을 통해 접근할 수 있다.
3. myCar.getInfo() 호출 시, this 는 myCar 를 참조하여 myCar 의 make 와 model 속성에 접근한다.
4. make 와 model 과 같이 인스턴스마다 다를 수 있는 인스턴스 속성은 생성자 함수에서 정의되고, 생성된 인스턴스가 해당 속성을 직접 가지고 있는다. 종류: 차와 같은 공통 속성은 Car.prototype 에 정의해 놓는다.
4. 생성자 함수 호출에서 this
생성자 함수 내에 this 가 있는 경우 함수 호출 단계
-> 생성자 함수로 인스턴스를 만드는 과정
1. 빈 객체 생성, this 바인딩
-> 생성자 함수가 new 키워드와 함게 호출될 때, 자바스크립트 엔진은 자동으로 빈 객체를 생성한다. 빈 객체는 생성자 함수 내에서 this 에 바인딩된다. 즉, 생성자 함수 내에서 this 는 자동으로 새로운 객체를 참조하게 된다.
{}
2. this 를 사용하여 동적으로 속성과 메서드 생성
-> 생성자 함수 내에서 this 를 사용하여 새로 생성된 객체에 속성과 메서드를 동적으로 추가한다.
{
this.name = name,
this.age = age,
}
3. 생성된 인스턴스 객체 반환
-> 생성자 함수에는 명시적인 반환문이 없어도, 생성자 함수가 끝나면 this 가 가리키는 객체가 자동으로 반환된다. (인스턴스 만들어짐)
5. apply, call, bind 호출 방식
자바스크립트 엔진이 진행하는 this 바인딩 방식을 따르지 않고, 명시적으로 따로 바인딩하는 방법이다.
함수 호출시에 바인딩되는 것을 제어하는 것이기 때문에 function build-in 객체 내에 존재하는 메서드이다.
- apply, call
둘 다 즉시 함수를 호출하면서 this 를 지정할 수 있다. 차이점은 함수에 인자를 전달하는 방법이다.
- 함수명.apply(바인딩할 객체, [함수 호출시 넘기는 인자 배열]) -> 함수 호출시 넘기는 인자들을 배열로 받음
- 함수명.call(바인딩할 객체, 인자1, 인자2, ...) -> 함수 호출시 넘기는 인자들을 개별로 받음
- 함수 내의 this 에 바인딩할 객체를 첫 번째 인자로 받음
- 함수에 인자들이 전달됨
- 함수 호출되며, this 는 지정된 객체로 설정됨
- 바인딩과 동시에 함수를 호출
- bind
bind 는 this 를 바인딩하고, 새로운 함수를 반환한다. 새로운 함수는 호출될 때 이미 설정된 this 를 사용할 수 있다.
바인딩과 호출이 분리되어 있다.
- 함수명.bind(바인딩할 객체)
- this 를 바인딩할 객체를 인자로 받음
- 선택적으로 미리 정의된 인자들을 나열할 수 있음
- 바인딩된 함수 반환되고, 호출될 때 바인딩된 this 를 사용 (함수 호출을 나중으로 미룰 수 있음)
- apply 와 차이점: 바인딩과 함수 호출이 분리되어 있음
6. 화살표 함수 -> this 의 동적 바인딩에 영향이 없음!
화살표 함수 문법은 ES6 부터 사용할 수 있다. 항상 상위 스코프 this 를 가리키기 때문에 지양해야 하는 사용 형태가 있다.
화살표 함수의 특징
1. 정적 this 바인딩 (Lexical this)
- 정적 바인딩: 화살표 함수는 this 를 자신이 정의된 위치의 상의 스코프에 있는 this 로 바인딩한다. 즉, 함수가 호출될 때의 this 가 아니라, 함수가 작성될 때의 this 를 사용한다.
- Lexical this: 이는 화살표 함수가 함수가 작성된 문맥에서 this 를 가져온다는 뜻이다. 그래서 화살표 함수의 this 는 언제나 같은 객체를 참조한다.
객체의 메서드로 화살표 함수가 쓰일 경우,
const foo = {
name: 'joy',
getName: () => {
console.log(this.name); // undefined -> this 가 foo 가 아닌 window 를 가리킴
},
};
console.log(foo.getName());
지양해야 하는 사용 형태
1. 객체의 메서드로서의 화살표 함수
객체의 메서드에서 this 는 해당 객체를 참조해야 하지만, 화살표 함수는 상위 스코프의 this 를 참조한다.
객체의 메서드에는 function 키워드로 작성된 함수를 할당하는 것을 권장한다.
const foo = {
name: 'joy',
getName: () => {
console.log(this.name); // undefined -> this 는 foo 가 아닌 window 가리킴
},
};
console.log(foo.getName());
2. 생성자 함수로서의 화살표 함수 사용 불가
화살표 함수로 생성한 함수 내에는 애초에 prototype 이 존재하지 않기 때문에 생성자 함수로서의 사용이 불가능하다.
화살표 함수는 내부적으로 this 를 고정하기 때문에 new 를 사용한 객체 생성이 불가능하다.
화살표 함수는 생성자 함수로 사용할 수 없으며, new 키워드로 호출하면 에러가 발생한다.
const PersonArrow = () => {
this.age = 30;
};
// TypeError: PersonArrow is not a constructor
const personArrow = new PersonArrow();
스코프
스코프
- 스코프 == 변수 영역
- 변수가 유효성을 갖는 영역
스코프의 규칙 -> 변수 영역을 지정하는 규칙
- 정적 영역 규칙: lexical scoping rule
- 동적 영역 규칙: dynamic scoping rule
lexical scoping rule
정적 영역 규칙으로 어디서 호출하였는지가 아니라, 어디서 선언하였는지에 따라 스코프 결정한다. (소스코드 구조 기준)
자바스크립트는 lexical scoping rule 을 따른다.
var foo = 'global';
function bar() {
var foo = 'local';
baz();
}
function baz() { // baz 함수의 상위 스코프는 전역
console.log(foo);
}
bar(); // global
baz(); // global
dynamic scoping rule
동적 영역 규칙으로 어디서 호출하였는지에 따라 스코프를 결정한다. 런타임 때 결정이 된다.
(자바스크립트에서는 해당 규칙이 적용되지는 않는다)
var foo = 'global';
function bar() {
var foo = 'local';
baz();
}
function baz() { // baz 함수의 상위 스코프는 baz 함수가 호출되고 있는 bar 함수와 전역까지의 범위
console.log(foo);
}
bar(); // local
baz(); // global
스코프 종류
1. 전역 스코프
- 소스 코드 상의 모든 곳에서 사용할 수 있는 전역 변수
- 자바스크립트: (브라우저 기준) window 객체
2. file 스코프
- 해당 파일 전체에서 접근 가능, 다른 파일에서 접근 불가능
- 원시적 형태의 모듈 영역
- ES6 + 자바스크립트: <script type="module" ... > 로 조화되는 파일
3. 모듈 스코프
- 모듈을 지원하는 프로그래밍 언어에서 모듈 단위 변수 선언 가능
4. 함수 레벨 스코프
- 지역변수: 함수 내에서 유효한 변수로 함수가 반환된 이후 사용불가, 함수 외부에서 유효하지 않음
- 자바스크립트는 기본적으로 함수 레벨 스코프
5. 블록 레벨 스코프
- 지역변수: 블록 코드 내에서 유효한 변수
- ES6 + 자바스크립트: let, const 키워드는 블록 레벨 스코프
실행 컨텍스트
자바스크립트 파일을 실행시킬 때 자바스크립트 엔진은 몇 가지 정보를 알아야 함.
-> 변수, 함수 선언, 변수의 유효범위(스코프), this
자바스크립트는 실행에 필요한 정보를 추상화하고 구분하기 위해 물리적인 객체의 형태로 관리하는데, 이것을 실행 컨텍스트라고 한다.
실행 컨텍스트는 코드가 실행되는 범위에 대한 개념이다.
자바스크립트 엔진은 코드를 읽고 평가하고, 평가된 코드를 실행한다. 여기서 평가와 실행은 실행 컨텍스트에 의해 관리된다.
- 코드평가단계: Creation Phase
- 코드실행단계: Execution Phase
실행 컨텍스트의 생성 단계 - Creation Phase
코드 평가 단계: Lexical Environment 생성
1. 함수와 변수를 기록(Environment Record) 을 참조
- 선언 정의, 객체 정의
- this 바인딩: 함수 호출여부에 따라 달라짐
2. 외부 환경 참조 (Outer Environment Reference)
- 스코프 체인이 형성됨
실행 컨텍스트의 실행 단계 - Excution Phase
코드 실행 단계: 위 -> 아래로 코드가 실행
- 변수는 값이 할당
- 함수 실행 코드가 있을 경우, 함수 실행 -> 함수의 실행 컨텍스트가 새로 생성되고, 생성된 실행 컨텍스트가 콜스택에 쌓이게 되고 새로운 컨텍스트의 생성단계과 실행단계를 거치게 됨
실행 컨텍스트의 3가지 종류
1. global 컨텍스트 == 전역 컨텍스트
- 함수 내에서 실행되는 코드가 아니라면, 전역 컨텍스트에서 실행
- 브라우저 기준: Global object = window
2. functional 컨텍스트
- 함수가 호출될 때마다, 함수에 대한 실행 컨텍스트 생성
- 각 함수들은 자신만의 실행 컨텍스트 존재
3. eval 컨텍스트
- eval 함수만의 실행 컨텍스트 존재
자바스크립트의 Call Stack
자바스크립트 엔진이 호출된 함수와 순서를 추적하는 방법이다.
어떤 함수가 동작하고 있는지, 다음에 어떤 함수가 호출되어야하는지 등을 제어한다. 또 함수가 반환된 후 실행이 올바른 지점에서 다시 선택되도록 한다. ( == 함수 호출을 수행할 수 있게 해주는 Record 보관 구조)
콜스택은 stack 자료구조를 사용하여 LIFO 형태를 띈다. 함수 호출시 만들어지는 실행 컨텍스트가 스택에 쌓인다. 자바스크립트는 하나의 stack 에서 실행 컨텍스트를 하나씩 처리 가능한 구조이다.
정리하자면,
1. 자바스크립트 엔진은 코드를 평가하여 실행 컨텍스트를 만든다
2. 콜스택에 하나씩 쌓고, 콜스택에서 실행 컨텍스트를 실행한다
3. 스택에서 하나씩 없앤다
실행 컨텍스트와 콜스택 마무리 정리
실행 컨텍스트란 자바스크립트를 실행시키기 위해 필요한 정보를 객체 형태로 추상화 한 것으로 1. 생성단계 2. 실행단계가 있다.
생성 단계에서는
Lexical Environment 을 정의한 후,
Environment Records 에서 변수/함수을 선언을 진행, 외부 환경 참조를 진행해 스코프 체인이 가능하도록 만듦.
실행 단계에서는 코드를 실행한다.
실행 컨텍스트에는 3가지 종류가 있는데, Global Context / Functional Context / eval Context 가 있다.
자바스크립트의 콜스택은 호출된 함수와 순서를 추적할 수 있는 구조를 가지고 있다. 스택 자료구조를 사용하기 때문에 LIFO 형태이다.
클로저
함수 안에 함수가 있는 내부 함수의 경우, Outer Environment Reference 는 외부 함수는 global 실행 context 를, 내부 함수는 외부 함수의 실행 context 를 참조하게 된다.
외부 함수 참조는 해당 실행 context 가 종료되면 참조가 해제되게 되는데, 함수가 종료되었는데 아직 참조가 남아있는 경우가 있다.
자바스크립트는 일급 객체이기 때문에 함수가 함수를 반환할 수 있다. 때문에 함수가 반환한 내부함수를 함수 외부에서 호출하는 특별한 상황이 생기기도 한다.
const foo = '1';
function bar() {
const apple = 0;
return function () {
return apple;
}
}
const callback = bar();
callback();
발생 조건
- 외부 함수 실행 컨텍스트 환경의 변수를 참조하고 있는 내부 함수 실행 컨텍스트
상황
- 내부 함수내에서 아직 외부 함수의 변수를 참조하고 있음
- 외부 함수가 종료되었지만, 외부 함수의 참조가 유지되어, 외부 함수 환경에 접근할 수 있는 상황
정리하자면, 클로저란
반환된 내부함수가 자신이 선언되었을 때의 환경이 스코프를 기억하여,
자신이 선언됐을 때의 환경 밖에서 호출되어도 그 환경(스코프)에 접근할 수 있는 함수
클로저의 특징
- 함수가 종료되어도, 스코프를 기억
- 특정 스코프에 접근할 수 있는 함수
-> 1. 상태 유지, 2. 은닉화에 사용
'웹 > JavaScript' 카테고리의 다른 글
[JavaScript] Array.sort() (0) | 2024.08.07 |
---|---|
[JavaScript] 기초 - 데이터 처리3 (0) | 2024.08.01 |
[JavaScript] 기초- 데이터처리2 (0) | 2024.07.30 |
[JavaScript] 기초 - 데이터 처리1 (0) | 2024.07.29 |
[JavaScript] 기초 - 데이터와 형태 (0) | 2024.07.29 |