Search

JavaScript2

17. 생성자 함수에 의한 객체 생성

생성자 함수에 의한 객체 생성 방식은 마치 객체를 생성하기 위한 템플릿처럼 생성자 함수를 사용하여 프로퍼티 구조가 동일한 객체 여러 개를 간편하게 생성할 수 있다.
//생성자 함수 function Circle(radius){ //이 때 this는 생성자 함수가 this.radius = radius; this.getDiameter = function() { return 2* this.radius; }; } const circle1 = new Circle(5); const circle2 = new Circle(10); console.log(circle1.getDiameter()); console.log(circle2.getDiameter());
JavaScript
복사
일반 함수와 동일한 방법으로 생성자 함수를 정의하고 new 연산자와 함께 호출하면 해당 함수는 생성자 함수로 동작한다.
//new 연산자와 함께 호출하지 않으면 생성자 함수로 동작하지 않음. const circle3 = Circle(15); //일반 함수로 호출되었을 때, return이 없으므로 undefined를 반환 console.log(circle3); //일반 함수로 호출되면 this는 전역 객체에 바인딩된다. console.log(radius);
JavaScript
복사

생성자 함수의 인스턴스 생성 과정

1.
인스턴스를 생성하는 것
2.
생성된 인스턴스를 초기화하는 것
→ 암묵적으로 인스턴스를 생성하고 반환한다.
1.
인스턴스 생성과 this바인딩
암묵적으로 빈 객체가 생성됨.
이 인스턴스가 this에 바인딩된다.
런타임 이전의 과정
2.
인스턴스 초기화 → 개발자의 코드
3.
인스턴스 반환
완성된 인스턴스가 바인딩된 this가 암묵적으로 반환됨.
만약 return을 명시하면 그게 반환됨.

내부 메서드 [[Call]]과 [[Construct]]

함수도 객체이기 때문에 내부 슬롯과 내부 메서드를 모두 가지고 있다.
다만 일반 객체는 호출할 수 없지만, 함수는 호출할 수 있다.
따라서 함수로서 동작하기 위해 가지고 있는, 내부 슬롯과 내부 메소드가 있다.
내부 슬롯 → [[Environment]], [[FormalParameters]]
내부 메소드 → [[Call], [[Construct]]
일반 함수로서 호출되면 함수 객체의 내부 메서드 call이 호출되고, new 연산자와 함께 생성자 함수로서 호출되면 construct가 호출된다.
이때 함수 객체는 constructor 일수도 있고 non-constructor일 수도 있다.

constructor, non-constructor 구분 방법

constructor: 함수 선언문, 함수 표현식, 클래스
non-constructor: 메서드(ES6 메서드 축약 표현), 화살표 함수

new.target

생성자 함수가 new 연산자 없이 호출되는 것을 방지하기 위해 파스칼 케이스 컨벤션을 사용함
그래도 위험 → new.target이다.
constructor인 모든 함수 내에서 암묵적인 지역변수로 사용됨.
new.target을 쓰면 new연산자와 함께 생성자 함수로 호출됬는지 알 수 있다.
생성자 함수로 호출되면 new.target은 함수 자신을 가리킴. 안 그러면 undefined임
//생성자로 안 불리는 것을 방지함. function Circle(radius){ if(!new.target){ return new Circle(radius); } this.radius = radius; this.getDiameter = function(){ return 2 * this.radius; }; } //new.target 못쓰면 스코프 세이프 생성자 패턴을 쓰셈
JavaScript
복사

18.함수와 일급 객체

일급 객체

1.
무명의 리터럴로 생성할 수 있다(런타임에 생성이 가능하다)
2.
변수나 자료구조 등에 저장할 수 있다.
3.
함수의 매개변수에 전달할 수 있다.
4.
함수의 반환값으로 사용할 수 있다.

함수 객체의 프로퍼티

function square(number){ return number * number; } console.log(Object.getOwnPropertyDescriptors(square)); // length, name, arguments, caller, prototype console.log(Object.getOwnPropertyDescriptor(square, '__proto__'));// undefined // Object.prototype 객체의 접근자 프로퍼티이다. console.log(Object.getOwnPropertyDescriptor(Object.prototype, '__proto__'));
JavaScript
복사
arguments, caller, length, name, prototype 프로퍼티는 모두 함수 객체의 데이터 프로퍼티
일반 객체에는 없는 함수 객체 고유의 프로퍼티
__proto__ 는 접근자 프로퍼티이며, 함수 객체 고유의 프로퍼티가 아니라 Object.prototype 객체의 프로퍼티를 상속 받은 것을 알 수 있다. 모든 객체가 사용할 수 있다.

arguments 프로퍼티

순회 가능한 유사 배열
전달된 인수들의 정보를 담고 있다.
함수 내부에서 지역변수로 사용되며, 함수 외부에서는 참조할 수 없다.
자바스크립트는 함수의 매개변수와 인수의 개수가 일치하지 않는지 확인하지 않는다. 따라서 에러 발생하지 않음.

length 프로퍼티

매개변수의 갯수를 나타냄

name 프로퍼티

함수의 이름을 나타낸다.
익명 함수 표현식의 경우 ES5에서는 빈 문자열이었으나, ES6에서는 식별자를 값으로 갖는다.

__proto__ 접근자 프로퍼티

모든 객체는 [[Prototype]]이라는 내부 슬롯을 갖는다.
상속을 구현하는 프로토타입 객체를 가리킨다.
__proto__ 프로퍼티는 [[Prototype]] 내부 슬롯이 가리키는 프로토타입 객체에 접근하기 위해 사용하는 접근자 프로퍼티이다. 내부 슬롯에는 직접 접근이 안되고 간접적인 접근 방법을 제공

prototype 프로퍼티

생성자 함수로 호출할 수 있는 함수만이 소유하는 프로퍼티
함수가 객체를 생성하는 생성자 함수로 호출될 때 생성자 함수가 생성할 인스턴스의 프로토타입 객체를 가리킨다.

19. 프로토타입

요약하자면
1.
자바스크립트는 프로토타입 이론을 바탕으로 만들어졌다.
a.
대상을 분류할 때 속성으로 분류하는게 아닌, 가족 유사성으로 분류한다.
b.
가장 많은 존재와 가족 유사성을 가지는 것이 원형, prototype이다.
2.
이럴 때, 같은 단어라 하더라도 어떤 상황에서 접했나에 따라 의미가 달라진다.
a.
아이 입장에서 ‘참새’는 명확하게 새해 속하지만, ‘펭귄’은 해당 범주에 속하지 못할 수 있다.
b.
조류학자가 생각할 때는 ‘참새’와 ‘펭귄’ 모두 새의 범주에 속할 수 있다.
3.
따라서 프로토타입 기반 OOP는 다음과 같은 특징을 갖는다.
a.
개별 객체 수준에서 메소드와 변수를 추가(어떤 컨텍스트에서 보냐에 따라서, 개별 객체 수준에서 유사성이 달라지기 때문에)
b.
객체 생성은 일반적으로 복사를 통해 이루어짐(개별 객체를 각각 다뤄야하기 때문에)
c.
확장은 클래스가 아니라 위임(프로토타입에서 떨어진 인스턴스들에게 발화가 일어나면 프로토타입으로 나아가야하기 때문)
d.
개별 객체 수준에서 객체를 수정하고 발전시키는 능력은 선험적 분류의 필요성을 줄이고 반복적인 프로그래밍 및 디자인 스타일을 장려
e.
프로토타입 프로그래밍은 일반적으로 분류하지 않고 유사성을 활용하도록 선택
f.
결과적으로 설계는 맥락에 의해 평가
function 참새(){ this.날개갯수 = 2; this.날수있나 = true; } const 참새1 = new 참새(); console.log("참새의 날개 갯수 : ", 참새1.날개갯수); // 2 function (){ this.벼슬 = true; } .prototype = 참새1; // reference(오른쪽이 인스턴스인 점 주목) const1 = new (); console.log("닭1 날개 : ",1.날개갯수, ", 날수있나? ",1.날수있나); // 2, true1.날수있나 = false; console.log("다시 물어본다. 닭1은 날 수 있나? :",1.날수있나); // false // 아래는 고전적인 방식의 프로토타입 연결 function 펭귄(){ 참새.call(this); // copy properties } 펭귄.prototype = Object.create(참새.prototype); // 프로토타입 연결 const 펭귄1 = new 펭귄(); console.log("펭귄1 날개 : ", 펭귄1.날개갯수, ", 날수있나? ", 펭귄1.날수있나); // 2, true 펭귄1.날수있나 = false; console.log("다시 물어본다. 펭귄1은 날 수 있나? :", 펭귄1.날수있나); // false
JavaScript
복사
참새 생성자 함수
참새 프로토타입
참새1
닭 생성자 함수
닭 프로토타입 = 참새1
닭1.proto = 참새1
참새1.prototype = 참새 프로토타입
참새 프로토타입.prototype = Object.prototype
펭귄 생성자 함수 → 참새 생성자함수를 호출하지만, 이 때 this는 앞으로 생성될 펭귄 인스턴스를 얘기한다.
펭귄 프로토타입 = 참새 프로토타입을 프로토타입으로 가지는 어떤 객체
펭귄 1 = 펭귄 인스턴스
펭귄1.proto = 참새 프로토타입을 프로토타입으로 하는 빈 객체

상속과 프로토타입

function Circle(radius){ this.radius = radius; this.getArea = function(){ return Math.PI * this.radius ** 2; }; } const circle1 = new Circle(1); const circle2 = new Circle(2); console.log(circle1.getArea === circle.getArea);
JavaScript
복사
이렇게 되면 모든 객체는 radius 프로퍼티와 getArea 메서드를 갖는다.
getArea 메서드를 중복 생성하고 모든 인스턴스가 중복 소유함
function Circle(radius){ this.radius = radius; } //프로토타입은 Circle 생성자 함수의 prototype 프로퍼티에 바인딩되어있다. Circle.prototype.getArea = function(){ return Math.PI * this.radius ** 2; }; const circle1 = new Circle(1); const circle2 = new Circle(2); console.log(circle1.getArea === circle2.getArea);
JavaScript
복사

프로토타입 객체

모든 객체는 [[Prototype]]이라는 내부 슬롯을 가지며, 값은 프로토타입의 참조이다.
객체와 프로토타입과 생성자 함수는 서로 연결되어있다.
이 때 모든 객체는 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입, 즉 [[Prototype]]내부 슬롯에 간접적으로 접근할 수 있다.
__proto__는 접근자 프로퍼티이다. 따라서 간접적으로 접근할 수 있다.
const obj = {}; const parent = {x : 1}; obj.__proto__; obj.__proto__ = parent; console.log(obj.x); // 1
JavaScript
복사
또한 __proto__는 Object.prototype의 프로퍼티이다. 프로토타입 체이닝을 통해 거슬러 올라가서 사용하는 거다.
접근자 프로퍼티를 사용하는 이유는 순환 참조를 방지하기 위함이다.
const parent = {}; const child = {}; child.__proto__ = parent; parent.__proto__ = child; // cyclid proto
JavaScript
복사
하지만 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장하지 않는다.
왜냐하면 Object.create()를 쓰면 Object.prototype을 상속받지 않는 객체를 생성할 수 있기 때문
→ 대신 Object.getPrototypeOf 또는 Object.setPrototypeOf 메서드를 사용할 것은 권장

함수 객체의 prototype 프로퍼티

함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킴
결국 모든 객체가 가지고 있는 __proto__ 접근자 프로퍼티와 함수 객체만이 가지고 있는 prototype 프로퍼티는 결국 동일한 프로토타입이다.
하지만 사용하는 주체가 다르다.
전자는 모든 객체가 사용하고, 후자는 오직 생성자 함수만이 사용한다.

리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입

그럼 리터럴로 생성된 애들은 어떻게 하냐?
리터럴 표기법으로 생성한 객체도 생성자 함수로 생성한 객체와 본질적인 면에서 큰 차이는 없다.
따라서 프로토타입의 constructor 프로퍼티를 통해 연결되어있는 생성자 함수를 리터럴 표기법으로 생성한 객체를 생성한 생성자 함수로 생각해도 된다
→ 프로토타입 constructor 프로퍼티에 있는 생성자 함수 == 리터럴 표기법으로 생성한 객체의 생성자 함수
리터럴 표기법
생성자 함수
프로토타입
객체 리터럴
Object
Object.prototype
함수 리터럴
Function
Function.prototype
배열 리터럴
Array
Array.prototype
정규 표현식 리터럴
RegExp
RegExp.prototype

프로토타입 생성 시점

리터럴 표기법에 의해 생성된 객체도 생성자 함수와 연결된다. 객체는 리터럴 표기법 또는 생성자 함수에 의해 생성되므로 결국 모든 객체는 생성자 함수와 연결되어있다.
프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성된다.
사용자 정의 생성자 함수와 빌트인 생성자 함수로 구분해 볼 수 있다.
1.
사용자 정의 생성자 함수
a.
함수 선언문은 런타임 이전에 실행된다. 따라서 어떤 코드보다 먼저 평가되고, 프로토타입도 생성됨.
b.
생성된 프로토타입의 프로토타입은 Object.prototype이다.
2.
빌트인 생성자 함수
a.
빌트인 생성자 함수는 전역 객체가 생성되는 시점에 생성됨.
이후 생성자 함수 또는 리터럴 표기법으로 객체를 생성하면 생성된 객체의 [[Prototype]]내부 슬롯에 프로토타입이 할당된다.

객체 생성 방식과 프로토타입의 결정

1.
객체 리터럴
2.
Object 생성자 함수
3.
생성자 함수
4.
Object.create 메서드
5.
클래스
객체 리터럴에 의해 생성 & Object 생성자 함수
→ Object.prototype이 프로토타입이다.
생성자 함수에 의해 생성
→ 생성자 함수의 prototype을 프로토타입으로 가짐.
function Person(name){ this.name = name; } Person.prototype.sayHello = function(){ console.log(`Hi! My name is ${this.name}`); }; const me = new Person('Lee'); const you = new Person('Kim'); me.sayHello(); you.sayHello();
JavaScript
복사

프로토타입 체인

프로토타입의 프로토타입은 언제나 Object.prototype이다.
function Person(name){ this.name = name; } Person.prototype.sayHello = function(){ console.log(`Hi! My name is ${this.name}`); }; const me = new Person('Lee'); const you = new Person('Kim');
JavaScript
복사
스코프 체인과 프로토타입 체인은 서로 연관없이 별도로 동작하는 것이 아니라 서로 협력하여 식별자와 프로퍼티를 검색하는데 사용된다.

오버라이딩과 프로퍼티 섀도잉

const Person = (function(){ function Person(name){ this.name = name; } //프로토타입 메서드 Person.prototype.sayHello = function(){ console.log(`Hi! My name is ${this.name}`); }; //생성자 함수 반환 return Person; }()); const me = new Person('Lee'); //인스턴스 메서드 me.sayHello = function(){ console.log(`Hey! My name is ${this.name}`); }; me.sayHello(); // Hey! My name is Lee
JavaScript
복사
인스턴스 메서드 sayHello는 프로토타입 메서드 sayHello를 오버라이딩했고, 프로토타입 메서드 sayHello는 가려진다.→ 프로퍼티 섀도잉
이 때 하위 객체를 통해 프로토타입의 프로퍼티를 변경 또는 삭제하는 것은 불가능하다.

프로토타입의 교체

1.
생성자 함수에 의한 프로토타입의 교체
const Person = (function(){ function Person(name){ this.name = name; } //프로토타입 메서드 Person.prototype.sayHello = function(){ console.log(`Hi! My name is ${this.name}`); }; //생성자 함수 반환 return Person; }()); const me = new Person('Lee');
JavaScript
복사
생성자 함수가 생성할 객체의 프로토타입을 객체 리터럴로 교체한 것이다.
constructor 프로퍼티가 없다. 생성자 함수와 constructor 프로퍼티 간의 연결이 파괴된다.
const Person = (function(){ function Person(name){ this.name = name; } //프로토타입 메서드 Person.prototype = { constructor: Person, sayHello(){ console.log(`Hi! My name is ${this.name}`); } }; return Person; }()); const me = new Person('Lee');
JavaScript
복사
2.
인스턴스에 의한 교체
function Person(name){ this.name = name; } const parent = { sayHello(){ console.log(`Hi! My name is ${this.name}`); } }; Object.setPrototypeOf(me,parent); me.sayHello();
JavaScript
복사
프로토타입 교체를 통해 객체 간의 상속 관계를 동적으로 변경하는 것은 꽤 번거롭다. 따라서 직접 교체하지 않는 것이 좋다. ‘직접 상속’을 써라.
클래스를 사용하면 간편하고 직관적으로 상속 관계를 구현할 수 있다.

instanceof 연산자

instanceof 연산자는 좌변에 객체를 가리키는 식별자, 우변에 생성자 함수
→ 우변의 생성자 함수의 prototype에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면 true로 평가
→ 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수를 찾는 것이 아니라, 생성자 함수의 prototype에 바인딩된 객체가 체인 상에 존재하는지 확인함.

직접 상속

obj = Object.create(Person.prototype); obj.name = 'Lee'; console.log(obj.name); console.log(Object.getPrototypeOf(obj) === Person.prototype);
JavaScript
복사
Object.create에 의한 직접 상속
첫 번째 매개변수 : 객체의 프로토타입으로 지정할 객체
두 번째 매개변수 : 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이뤄진 객체
그러나 Object.prototype의 빌트인 메서드를 객체가 직접 호출하는 것을 권장하지 않음.
Object.create를 하면 체인 상 종점에 있을 수도 있기 때문.
따라서 간접적으로 호출하자!
const obj = Object.create(null); obj.a = 1; console.log(Object.prototype.hasOwnProperty.call(obj,'a'));
JavaScript
복사

정적 프로퍼티,메서드

생성자 함수로 인스턴스를 생성하지 않아도 참조,호출할 수 있는 프로퍼티,메서드
function Person(name){ this.name = name; } Person.prototype.sayHello = function(){ console.log(`Hi! My name is ${this.name}`); }; Person.staticProp = 'static prop'; Person.staticMethod = function(){ console.log('staticMethod'); }; const me = new Person('Lee'); Person.staticMethod(); me.staticMethod();// error
JavaScript
복사

프로퍼티 존재 확인

a.
in 연산자
i.
key in object
ii.
const person ={ name : 'Lee', address: 'Seoul' }; console.log('name' in person);
JavaScript
복사
b.
Object.prototype.hasOwnProperty 메서드
i.
객체에 특정 프로퍼티가 존재하는지

프로퍼티 열거

a.
for...in 문
i.
상속받은 프로토타입의 프로퍼티까지 열거하지만, Object.prototype의 프로퍼티는 열거 되지 않는다.
ii.
어트리뷰트가 Enumerable이 true인 것들만 열거한다.

20. strict mode

그냥 자바스크립트 문법을 좀 더 엄격하게 해주는 것
나오는 에러
암묵적 전역
변수,함수,매개변수의 삭제
매개변수 이름의 중복
with 문의 사용
strict mode 적용에 의한 변화
일반 함수의 this
arguments 객체

21. 빌트인 객체

자바스크립트 객체의 분류

표준 빌트인 객체
전역 객체의 프로퍼티로 제공된다.
호스트 객체
DOM, BOM, XMLHttpRequest 등 추가로 제공하는 객체
사용자 정의 객체
사용자가 직접 정의한 것.

표준 빌트인 객체

Object, String, Number, Boolean, Symbol, Date, Math, Array, Map/Set, Function, Promise 등등
Math, Reflect, JSON을 제외한 빌트인은 모두 인스턴스를 생성할 수 있는 생성자 함수 객체이다.

전역 객체

어떤 객체보다도 먼저 생성되는 특수한 객체
전역 객체
→ 표준 빌트인 객체와 환경에 따른 호스트 객체, 그리고 var 키워드의 전역 변수와 전역 함수를 프로퍼티로 가짐.
→ let이나 const 키워드로 선언한 전역변수는 전역 객체의 프로퍼티가 아니다. 보이지 않는 개념적인 블록 내에 존재한다.
1.
빌트인 전역 프로퍼티
a.
전역 객체의 프로퍼티
i.
Infinity
ii.
NaN
iii.
undefined
2.
빌트인 전역 함수
a.
eval
b.
isFinite
c.
isNaN
d.
parseFloat
e.
paresInt
f.
encodeURI → 아스키 문자 셋으로 변환한다.
g.
decodeURI
3.
암묵적 전역
a.
변수가 아니므로 변수 호이스팅이 발생하지 않는다.

22.this

자신이 속한 객체를 가리키는 식별자를 참조할 수 있어야한다..
this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수이다.
함수를 호출하면 arguments 객체와 this 가 암묵적으로 함수 내부에 전달된다.
함수 내부에서 arguments 객체를 지역 변수처럼 사용할 수 있는 것처럼, this도 지역변수처럼 사용할 수 있다.
this 바인딩은 함수 호출 방식에 의해 동적으로 결정
var value = 1; var value2 = 2; const obj = { value: 100, foo(){ let value2 = 22; console.log("foo's this:", this); // {value: 100, foo: f} console.log("foo's this.value: ", this.value); // 100 console.log("foo's value2: ", value2); // 22 function bar(){ console.log("bar's this : ", this); // window console.log("bar's this.value: ", this.value); // 1 } bar(); } }; obj.foo();
JavaScript
복사
1.
전역 실행 컨텍스트가 생성될 때 value, obj가 들어간다.
2.
컨텍스트 내 코드가 실행되면서 obj.foo라는 프로퍼티를 발화시킨다.
3.
실행 컨텍스트에 foo() 함수 실행 컨텍스트가 stack에 push된다.
4.
foo 실행 컨텍스트가 코드가 평가되는데, 이 때 함수 내부에서 선언된 value2와 bar를 밀어넣는다. 그리고 this에다가 foo를 발화시킨 객체를 바인딩한다.
5.
실행 컨텍스트 내의 코드가 실행된다.
6.
실행되면 하나씩 실행하는데 이 때, this.value라고 하면 프로토타입 체인을 검색한다.
7.
그냥 value2를 출력하라고 하면 렉시컬 스코프를 검색한다. 이 때 foo의 렉시컬 스코프는 본인이 선언된 스코프인 전역 렉시컬 스코프를 얘기한다.
8.
렉시컬 스코프는 말 그대로 어디서 정의됬냐를 기준으로 뒤지는 것
var value = 1; // let value = 1 var value1 = 11; var value2 = 2; const parent = { value: 10, value1: 200 } const obj = { value: 100, foo(){ let value2 = 22; console.log("foo's this: ", this); console.log("foo's value in lexical scope", value); // 1 console.log("foo's value1 in lexical scope",value1); // 11 console.log("foo's value2 in lexical scope", value2); // 22 console.log("foo's value in prototype chain", this.value); // 100 console.log("foo's value1 in prototype chain", this.value1); // 200 console.log("foo's value2 in porototype chain", this.value2); // undefined function bar(){ console.log("bar's this: ", this); console.log("bar's this.value: ", this.value); //1 , let 선언이면 undefined임 console.log("bar's value2 in lexical scope", value2); // 22 console.log("bar's value in lexical scope:", value); // 1 } bar(); } }; Object.setPrototypeOf(obj,parent); obj.foo();
JavaScript
복사
이 때 전역 객체의 value와 렉시컬 스코프 상의 value는 같다.
var로 선언한다면 그런 것.

함수 호출 방식과 this 바인딩

함수의 상위 스코프를 결정하는 방식인 렉시컬 스코프는 함수 정의가 평가되어 함수 객체가 생성되는 시점에 상위 스코프를 결정함. 하지만 this 바인딩은 함수 호출 시점에 결정된다.
1.
일반 함수 호출
→ 기본적으로 this에는 전역 객체가 바인딩된다.
전역 함수, 중첩 함수를 일반 함수로 호출하면 함수 내부의 this에는 전역 객체가 바인딩된다.
메서드 내에서 정의한 중첩함수도 일반 함수로 호출되면 중첩 함수의 내부의 this에는 전역 객체가 바인딩된다.
콜백 함수가 일반함수로 호출된다면, 콜백 함수 내부의 this에도 전역 객체가 바인딩된다.
그래서 메서드 내부의 중첩 함수나 콜백 함수의 this 바인딩을 메서드의 this 바인딩과 일치시키기 위한 방법
var value = 1; const obj = { value: 100, foo(){ const that = this; setTimeout(function(){ console.log(that.value);; },100); } }; obj.foo();
JavaScript
복사
var value = 1; const obj = { value: 100, foo(){ setTimeout(function(){ console.log(this.value);; }.bind(this),100); } }; obj.foo();
JavaScript
복사
또는 화살표 함수를 이용해서 this 바인딩 시킨다.
var value = 1; const obj = { value: 100, foo(){ setTimeout(()=> console.log(this.value),100); } }; obj.foo();
JavaScript
복사
화살표 함수 내부의 this는 상위 스코프의 this를 가리킨다.

메서드 호출

const anotherPerson = { name: 'Kim' }; anotherPerson.getName = person.getName; console.log(anotherPerson.getName()); //Kim const getName = person.getName; console.log(getName());
JavaScript
복사

생성자 함수 호출

만들 인스턴스를 가리킴

23.실행 컨텍스트

소스코드의 타입

전역 소스코드: 전역에 존재하는 코드, 함수나 클래스 등의 내부 코드는 포함되지 않음.
함수 코드: 함수 내부에 존재하는 코드, 함수 내부에 중첩된 함수, 클래스 등의 내부 코드는 포함되지 않음
eval 코드
모듈 코드: 독립적인 모듈 스코프

소스코드의 평가와 실행

소스코드의 평가: 선언문
소스코드의 실행: 선언문 이외의 실행

실행 컨텍스트 스택

전역 코드의 평가와 실행 → foo 함수 코드의 평가와 실행 → bar 함수 코드의 평가와 실행 → foo 함수 코드로 복귀 → 전역 코드로 복귀

렉시컬 환경

식별자와 식별자에 바인딩된 값, 그리고 상위 스코프에 대한 참조를 기록하는 자료구조로서 실행 컨텍스트를 구성하는 컴포넌트
실행 컨텍스트 스택이 코드의 실행 순서를 관리한다면, 렉시컬 환경은 스코프와 식별자를 관리한다.
렉시컬 환경은 두 가지로 구성된다.
1.
환경 레코드 → 스코프에 포함된 식별자를 등록하고 등록된 식별자에 바인딩된 값을 관리하는 저장소.
2.
외부 렉시컬 환경에 대한 참조 → 상위 스코프를 가리킴.

실행 컨텍스트의 생성과 식별자 검색 과정

var x = 1; const y = 2; function foo(a){ var x = 3; const y = 4; function bar(b){ const z = 5; console.log(a+b+x+y+z); } bar(10); } foo(20);
JavaScript
복사
전역 코드의 평가
1.
전역 실행 컨텍스트 생성
2.
전역 렉시컬 환경 생성
a.
전역 환경 레코드 생성
i.
객체 환경 레코드 생성
ii.
선언적 환경 레코드 생성
b.
this 바인딩
c.
외부 렉시컬 환경에 대한 참조 결정
객체 환경 레코드
→ var 키워드로 선언한 전역 변수와 함수 선언문으로 정의된 전역 함수는 전역 객체의 프로퍼티와 메서드가 된다.
선언적 환경 레코드 생성
→ var 키워드로 선언한 변수와 함수 선언문 이외에 let,const 키워드는 선언적 환경 레코드에 등록되고 관리됨.
this 바인딩
→ 실행 컨텍스트 상의 this
외부 렉시컬 환경
→ 실행 컨텍스트 상의 상위 렉시컬 스코프를 매칭 시킴
함수 코드 평가
1.
함수 실행 컨텍스트 생성
2.
함수 렉시컬 환경 생성
a.
함수 환경 레코드 생성
b.
this 바인딩
c.
외부 렉시컬 환경에 대한 참조

클로저

외부 함수보다 중첩 함수가 더 오래 유지되는 경우, 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있다. 이러한 중첩 함수를 클로저라고 부른다.
function foo(){ const x = 1; const y = 2; function bar(){ console.log(x); } return bar; } const bar = foo(); bar();
JavaScript
복사
outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 제거되지만, outer 함수의 렉시컬 환경까지 소멸하는 것은 아니다.
outer 함수의 렉시컬 환경은 inner 함수의 [[Environment]] 내부 슬롯에 의해 참조되고 있고, inner 함수는 전역 변수 innerFunc에 의해 참조되고 있으므로, 가비지 컬렉션의 대상이 되지 않기 때문이다.
const x = 1; function outer(){ const x = 10; const inner = function(){ console.log(x);}; return inner; } const innerFunc = outer(); innerFunc();
JavaScript
복사
outer 함수를 호출하면 outer 함수의 렉시컬 환경이 생성되고, 중첩 함수 inner가 평가된다.
함수 표현식으로 정의했기 때문에, 런타임에 평가된다. 이 때 inner는 자신의 [[Environment]] 내부 슬롯에 현재 실행 중인 실행 컨텍스트의 렉시컬 환경, 즉 outer 함수의 렉시컬 환경을 상위 스코프로서 저장한다.
outer함수의 실행이 종료하면, inner함수를 반환하면서 outer함수의 생명주기가 종료됨.
outer 함수의 실행 컨텍스트가 스택에서 제거되지만, outer 함수의 렉시컬 환경까지 소멸하는 것은 아니다.

클로저의 활용

상태를 안전하게 변경하고 유지하기 위해 사용한다.
상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해 사용함.
let num = 0; const increase = function(){ return ++ num; }; console.log(increase()); // 1 console.log(increase()); // 2 console.log(increase()); // 3
JavaScript
복사
→ 카운트 상태는 increase 함수가 호출되기 전까지 변경되지 않고 유지되어야한다.
→ 이를 위해 카운트 상태는 increase 함수만이 변경할 수 있어야한다.
const increase = function (){ let num = 0; return ++ num; }; console.log(increase()); // 1 console.log(increase()); // 1 console.log(increase()); // 1
JavaScript
복사
—> 이전 상태를 유지할 수 있도록 클로저를 사용해보자.
const inscrease = (function(){ let num = 0; return function(){ return ++num; }; }()); console.log(increase()); // 1 console.log(increase()); // 2 console.log(increase()); // 3
JavaScript
복사
위 코드가 실행되면, 즉시 실행 함수가 호출되고, 실행 함수가 반환한 함수가 increase 변수에 할당된다.
increase 변수에 할당된 함수는 자신이 정의된 위치에 의해 결정된 상위 스코프인 즉시 실행 함수의 렉시컬 환경을 기억하는 클로저
즉시 실행 함수는 소멸되지만, 즉시 실행 함수가 반환한 클로저는 increase 변수에 할당되어 호출된다.
즉시 실행 함수는 한 번만 실행되므로 increase가 호출될 때마다 num 변수가 재차 초기화될 일은 없을 것이다. num 변수는 외부에서 직접 접근할 수 없는 은닉된 private변수 이므로, 의도되지 않는 변경을 걱정할 필요가 없다.
다른 예
const counter = (function(){ let num = 0; return { increase(){ return ++ num; }, decrease(){ return -- num; } }; }()); console.log(counter.increase()); // 1 console.log(counter.increase()); // 2 console.log(counter.decrease()); // 1 console.log(counter.decrease()); // 0
JavaScript
복사
생성자 함수로 표현해보자
const Counter = (function(){ let num = 0; function Counter(){ } Counter.prototype.increase = function(){ return ++ num; }; Counter.prototype.decrease = function(){ return num > 0 ? --num : 0; }; return Counter; }()); const counter = new Counter(); console.log(counter.increase()); // 1 console.log(counter.increase()); // 2 console.log(counter.decrease()); // 1 console.log(counter.decrease()); // 0
JavaScript
복사
num은 즉시 실행 함수 내에서 선언된 변수.
생성자 함수 Counter는 프로토타입을 통해 increase,decrease 메서드를 상속받는 인스턴스를 생성한다.
increase, decrease 메서드는 모두 자신의 함수 정의가 평가되어 함수 객체가 될 때 실행 중인 실행 컨텍스트인 즉시 실행 함수 실행 컨텍스트의 렉시컬 환경을 기억하는 클로저.
프로토타입을 통해 상속되는 프로토타입 메서드일지라도, 즉시 실행 함수의 자유 변수 num을 참조할 수 있다.
외부 상태 변경이나 가변 데이터를 피하고 불변성을 지향하는 함수형 프로그래밍에서 부수 효과를 최대한 억제하여 오류를 피하고 프로그램의 안정성을 높이기 위해 클로저는 적극적으로 사용됨.
//함수를 인수로 받고 함수를 반환하는 고차함수 function makeCounter(predicate){ let counter = 0; return function(){ counter = predicate(counter); return counter; }; } function increase(n){ return ++n; } function decrease(n){ return --n; } const increaser = makeCounter(increase); console.log(increaser()); //1 console.log(increaser()); //2 const decreaser = makeCounter(decrease); console.log(decreaser()); // -1 console.log(decreaser()); // -2
JavaScript
복사
공유가능하게 만들라면?
//함수를 인수로 받고 함수를 반환하는 고차함수 const counter = (function (){ let counter = 0; return function(predicate){ counter = predicate(counter); return counter; }; }()); function increase(n){ return ++n; } function decrease(n){ return --n; } console.log(counter(increase)); //1 console.log(counter(increase)); //2 console.log(counter(decrease)); // 1 console.log(counter(decrease)); // 0
JavaScript
복사

캡슐화와 정보 은닉

캡슐화는 객체의 상태를 나타내는 프로퍼티와 프로퍼티를 참조하고 조작할 수 있는 동작인 메서드를 하나로 묶는 것
외부에 공개할 필요가 없는 구현의 일부를 외부에 공개되지 않도록 감춘다.
function Person(name,age){ this.name = name; let _age = age; } Person.prototype.sayHi = function(){ console.log(`Hi! My name is ${this.name}, I am ${_age}.`); }; // 불가능
JavaScript
복사
const Person = (function(){ let _age = 0; function Person(name,age){ this.name = name; _age = age; } Person.prototype.sayHi = function(){ console.log(`Hi! My name is ${this.name}. I am ${_age}.`); }; return Person; }()); const me = new Person('Lee',20); me.sayHi(); // Hi My name is Lee. I am 20 console.log(me.name); // Lee console.log(me._age); // undefined const you = new Person('Lee',30); you.sayHi(); // Hi! My name is Kim. I am 30 console.log(you.name); // Kim console.log(you._age); // undefined
JavaScript
복사
→ _age 변수의 상태가 유지되지 않는다.
Person.prototype.sayHi 메서드가 단 한번 생성되는 클로저이기 때문에 발생하는 현상.
즉시 실행 함수가 호출될 대 생성됨. 즉시 실행 함수의 실행 컨텍스트의 렉시컬 환경참조를 기억한다.
어떤 인스턴스로 호출하더라도 동일한 상위 스코프를 사용하게 된다.