Modern JS Deep Dive 17-21

MM·2022년 10월 26일

JSDeepDive

목록 보기
2/6
post-thumbnail

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

🔹 Object 생성자 함수

딱히 유용하진 않음.

const object=new Object();

🔹 생성자 함수

🔸 객체 리터럴에 의한 객체 생성 방식의 단점

인스턴스간 동일한 함수를 계속 중복 생성해야 한다!!

const circle={
	radius:5,
	getDiameter(){ return 2* this.radius; }
}

🔸 생성자 함수에 의한 객체 생성 방식의 장점

객체를 위한 클래스처럼 사용 가능!

function Circle(radius){
	this.radius=raidus;
  	this.getDiameter=function(){return 2*this.radius';};
}
  
const circle1 = new Circle(1); //이건 생성자.. js엔진이 암묵적으로 인스턴스를 생성+초기화+반환해줌
const circle2 = Circle(1);	   //이건 일반함수.. return이 없으니까 undefined
console.log(radius);		   //여기에서의 this는 전역객체이므로 windows.radius = 15

👇 this 바인딩

  • 일반 함수의 this = 전역 객체 (windows)
  • 메서드의 this = 매세드 호출 객체
  • 생성자 함수의 this = 생성자 함수가 미래 생성할 인스턴스

🚥 생성자 함수로 객체가 생성되는 순서

  1. 빈 인스턴스 객체 생성
  2. 빈 인스턴스 객체를 this에 바인딩
    ----런타임-----
  3. 생성자 함수의 내용 실행 = 초기화
  4. this 반환
    👉 명시적 객체 return이 있다면 그쪽이 우선된다.
    👉 명시적 원시값 return은 무시되고 this가 반환된다.

🔸 함수 선언문 or 함수 표현식으로 정의한 함수

생성자 함수로써 호출할 수 있다! (=객체 생성 가능)

function foo(){}
foo.prop=10; //일반 객체처럼 속성 추가 가능 = 일반객체의 내부 슬롯과 내부 메서드 가짐
foo(); //일반 객체와 달리 호출 가능 = 함수 객체의 내부슬롯과 내부 메서드 가짐

함수 객체의 내부 메서드

  • [[Call]] : 함수를 호출했을 때 실행되는 메서드. 모든 함수 객체는 반드시 [[Call]]을 가진다.
  • [[Construct]] : 함수를 생성자로 사용했을 때 실행되는 메서드

🔸 constructor와 non-constructor 구분하기

  • constructor: [[Construct]] 메소드를 가짐 = 함수 선언문, 함수 표현식, 클래스
  • non-constructor : [[Construct]] 메소드가 없음 = 메서드, 화살표 함수

🤗 쉽게 말하자면..

new를 붙일 수 있으면 constructor고 아니면 non-constructor다!

🔸 new연산자

function add(x,y){return x+y;} //일반 함수

let inst=new add(); //생성자 함수로 동작
console.log(inst);  //원시값은 반환 무시, {} 빈 객체 반환

function createUser(name, role){return {name, role} } //생성자 함수

inst=new createUser("name", "role"); //생성자 함수로 동작
console.log(inst); //객체 반환 유효, {"name", "role"} 반환

😲 일반 함수와 생성자 함수를 어떻게 구별하나요?

사실..기본적으로 형식적인 차이는 없다! 따라서 생성자 함수는 파스칼 케이스(첫글자 대문자)로 명명해서 일반 함수와 구분될 수 있게 하자.

function thisisjustFunction(a,b) { return a+b; } //일반 함수
function ThisisConstructor(a,b ) {return {a, b}}  //생성자 함수

🔸 new.target

함수가 생성자로 사용되었는지 일반 함수로 사용되었는지 알려주는 함수

  • 생성자로 사용 👉 new.target = 함수 자신
  • 일반함수로 사용 👉 new.target = undefined

🌍 메타 프로퍼티

constructor인 모든 함수 내부에서 암묵적인 지역변수로 사용되는 변수.
ex)this, new.target ..

🔸 스코프 세이프 생성자 패턴

IE와 같이 new.target을 사용하지 못하는 경우 해당 패턴을 이용하여 무조건 생성자 함수로 사용하도록 하는 패턴

function Circle(radius){
	if(!(this instanceof Circle) //생성자 함수로 사용하지 않으면 this = 전역객체인 점을 이용!
       return new Circle(radius); // 무조건 생성자 함수로 인스턴스를 생성한 뒤 리턴하게 한다
}

👉 Object, Function 생성자 함수는 이와 같이 new를 사용하지 않아도 생성자 함수로 쓸 수 있다.




📃 18장. 함수와 일급 객체

🔹 일급 객체

JS의 함수는 일급 객체!

🔸 일급 객체의 조건

  • 무명의 리터럴 생성이 가능 (런타임 생성)
  • 변수나 자료구조에 저장 가능
  • 함수의 매개변수에 전달 가능
  • 함수 변환값으로 사용 가능

🔹 함수 객체의 프로퍼티

🔸 arguments 프로퍼티

함수 호출시 전달되니 인수들을 저장하는 유사배열객체.
함수가 호출되면 암묵적으로 매개변수가 선언되고 undefined로 초기화된 후 인수를 할당받는다.

  • 주어진 매개변수 수 < 필요한 인수 수 👉 인수가 전달되지 않아 undefined로 초기화된 상태
  • 주어진 매개변수 수 > 필요한 인수 수 👉 넘치면 무시되지만, 일단 arguments 프로퍼티 안에 저장됨

😴 undefined 연산

undefined간 연산을 수행하면 NaN이 반환된다!

🔸 length 프로퍼티

선언한 매개변수 개수

🔸 name 프로퍼티

함수의 이름.
익명함수의 경우 함수 객체를 가리키는 식별자를 갑승로 가진다

🔸 _ _proto__ 접근자 프로퍼티

Object.prototype에게 상속받은 접근자 프로퍼티.
모든 객체는 [[Prototype]]이라는 내부 슬롯을 가지고 자신의 프로토타입 객체를 값으로 가진다.
_ _proto__ 접근자를 이용해서만 [[Prototype]]의 값인 자신의 프로토타입 객체에 접근이 가능하다.

🥴 접근자 프로퍼티를 통해 프로토타입에 접근하는 이유

상호 참조를 통해 서로가 자신의 프로토타입이 되는 비정상적인 순환 프로토타입 체인이 만들어지면, 프로퍼티 검색을 위해 프로토타입 체인을 타고 상위로 올라갈 때 무한 루프가 발생한다!
👉 프로토타입 체인은 오직 단방향 링크드리스트로 구현되어야 한다!

🔸 prototype 프로퍼티

constructor 함수 객체만이 가지고 있는 프로퍼티.
생성자 함수가 생성하는 인스턴스의 프로토타입 객체를 가리킨다.




📃 19장. 프로토타입

🔹 상속과 프로토타입

🔹 프로토타입 객체

Circle.prototype = circle1.__proto__
//생성자 함수로써 가지는 프로토타입 = 자신의 원본 형태를 찾아가는 [[Prototype]] 내부슬롯

모든 프로토타입은 constructor 프로퍼티를 가진다.
👉 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 쌍으로 존재한다.

🔹 프로토타입 생성 시점

🔸 사용자 정의 생성자 함수와 프로토타입 생성 시점

프로토타입과 생성자 함수는 언제나 쌍으로 존해자므로,
프로토타입은 생성자 함수가 constructor로 평가되어 함수 객체를 생성하는 시점에서 생성된다.
👉 이때 생성되는 프로토타입의 프로토타입은 항상 Object.prototype!

🔸 빌트인 생성자 함수와 프로토타입 생성 시점

Object, String, Number...

전역 객체가 생성되는 시점에 빌트인 생성자 함수가 생성되고,
빌트인 생성자 함수가 생성되는 시점에 빌트인 프로토타입이 생성도니다.

🌏 전역 객체

코드가 실행되기 이전 단계에 JS 엔진에 의해 생성되는 특수한 객체 (window, global..)


🔹 프로토타입 체인

🔸 end of prototype chain

Object.prototype은 언제나 프로토타입 체인의 종점.
여기에서도 프로퍼티를 못 찾으면 undefined를 반환한다.

🔗 프로토타입 체인과 스코프 체인

  • 프로토타입 체인: 상속과 프로퍼티 검색을 위한 메커니즘
  • 스코프 체인: 식별자 검색을 위한 메커니즘

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

🔸 프로퍼티 섀도잉

하위 프로퍼티의 오버라이딩에 의해 상위 프로퍼티 값이 가려지는 현상

🔸 오버라이딩

상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의해 사용하는 방식

😎 하위 객체는 프로토타입의 프로퍼티를 조작할 수 없다!

프로토타입 프로퍼티를 조작하려면 하위 객체를 통해 프로토타입 체인으로 접근해서는 안 되고, 프로토타입에 직접 접근해야 한다!


🔹 프로토타입의 교체

부모 객체인 프로토타입을 동적으로 변경하여 상속 관계를 변경하는 것이 가능하다.

🔸 생성자 함수에 의한 프로토타입의 교체

prototype 프로퍼티를 이용한 프로토타입 교체

const Person=(
  function(){ //익명함수를 넣어서 즉시실행되게 하는 생성자 함수
    function Person(name){this.name=name;}
  
    Person.prototype={ //프로토타입을 sayHello로 교체합니다
      constructor: Person,     	//교체될 함수에는 constructor 프로퍼티가 없으므로 명시적 추가
      sayHello(){console.log(this.name);}
    }; //생성자함수 Person의 prototype 프로퍼티를 교체.

    
    return Person();
}());

👉 Person.prototype을 교체한 것으로 Person 생성자 함수와의 연결은 여전히 유지됨!

🔸 인스턴스에 의한 프로토타입의 교체

_ _proto__를 이용한 프로토타입 교체

const Person(name){this.name=name;}

const me = new Person("홍길동");

const parent={
  constructor: Person, //교체될 함수에는 constructor 프로퍼티가 없으므로 명시적 추가
  sayHello(){console.log(this.name);}};

Person.prototype=parent; //Person.prototype을 parent로 교체하여 Person 생성자 함수와 연결
Object.setPrototypeOf(me, parent);
//인스턴스의 __proto__에 접근하여 아까 만든 parent로 해당 인스턴스의 프로토타입 교체

me.sayHellog(); //홍길동

👉인스턴스의 [[Prototype]]을 교체한 것으로 Person 생성자 함수는 교체된 parent와 아무런 접점이 없음!
👉Person 생성자 함수의 prototype에는 여전히 인스턴스와 연결이 끊어진 Person.prototype이 연결되어 있다. 이것을 parent로 교체해 주어야 함.


🔹 instanceof 연산자

객체 instanceof 생성자함수

우변의 생성자 함수 프로토타입이 좌변 객체 프로토타입 체인 상에 존재하면 true!
프로토타입 체인만 확인하고 생성자 함수와의 연결에는 영향x.

me.constructor===Person; //false
me instanceof Person;    //true
me instanceof Object;    //true

😧 Person을 parent로 교체했는데 왜 여전히 me의 프로토타입 체인에 Person이 존재하나요?

코드를 잘 읽어 보자! 우리가 한 건 Person와 parent를 스왑한 것이 아니라 Person.prototype에 parent를 넣은 것!

Person.prototype=parent;

즉, Person은 여전히 존재하고


🔹 직접 상속

🔸 Object.create로 직접 상속하기

명시적으로 프로토타입을 지정하여 직접적으로 상속받는 새로운 객체를 생성하는 메서드
해당 객체의 프로토타입에 매개변수의 프로토타입을 넣어 사용한다.

let obj=Object.create(null); //Object.prototype도 상속받지 않는다!
//프로토타입 체인의 종점에 위치하는 객체가 된다.

obj = Object.create(Object.prototype); //Object.prototype은 상속받는다
obj = Object.create(Object.prototype, 
                    {x:
                     {value: 1, 
                      wirtable: true, 
                      enumerable: true, 
                      configurable: true}}); // ={x:1}

let proto={x:10};
obj = Object.create(proto); //proto 상속 (obj->proto->Object->null)

function Person(name){this.name=name;}
obj = Object.create(Person.prototype); //Person 상속 (obj->Person.prototype->Object.prototype)

🔸 객체 리터럴 내부에서 _ _ proto__로 직접 상속하기

const proto={x:10};
const obj={y:20, __proto__: proto}; //obj = {y:20, x:10}

🔹 정적(static) 프로퍼티/메서드

생성자 함수에서만 참조/호출할 수 있는 프로퍼티/메서드.
해당 생성자 함수로 만든 인스턴스에서는 사용할 수 없다.

function Person(name){this.name=name;}
Person.prototype.sayHello=function(){console.log(this.name)};

//정적 프로퍼티
Person.staticProp = "static prop";

//정적 메서드
Person.StaticMethod=function(){console.log("static Method")};

const me = new Person("홍길동");

Person.staticMethod(); //static Method
me.staticMethod(); //에러!

😑 왜 인스턴스에서는 생성자 함수의 정적 메서드를 사용할 수 없나요?

인스턴스에서 상속으로 사용할 수 있으려면, 해당 메서드가 인스턴스의 프로토타입 체인 내에 존재해야 상속받을 수 있다.
생성자 함수의 정적 프로퍼티/메서드는 프로토타입 체인이 아닌 Person 생성자 함수가 소유하고 있으므로 인스턴스가 사용할 수 없다.


🔹 프로퍼티 존재 확인

🔸 in 연산자

객체 내에 특정 프로퍼티가 존재하는지 여부를 확인
해당 객체 내에 없을시 프로토타입 체인을 타고 올라가며 해당 프로퍼티를 찾는다

const person={name:"홍길동"};

console.log("name" in person);  //true
console.log(Reflect.has(person, name)); //true
console.log(person.hasOwnProperty("name")); //true. 상위 프로토타입 검색x



📃 20장. strict mode

🔹 strict mode란?

strict mode는 언어의 문법을 좀 더 엄격히 적용하고
오류 발생 여지가 있는 코드에 대해 명시적인 에러를 발생시킨다.

🔸 전역에 strict mode 사용은 피하자

strict mode는 스크립트(< script >) 단위로 적용된다

<script>
  'use strict'
  //엄격 모드 적용
</script>
<script>
  //엄격모드 적용x
</script>

🔹 Strict mode가 발생시키는 에러

🔸 암묵적 전역(implicit global)

function foo(){x=10;} 
foo();

console.log(x);
  1. JS는 함수를 호이스팅한다.
    👉 foo함수의 스코프에서 x변수가 선언된 곳을 찾는다.
  2. foo함수의 스코프에 x가 존재하지 않는다!
    👉 foo함수 컨텍스트의 상위 스코프인 전역 스코프에서 x가 선언된 곳을 찾는다.
  3. 전역 스코프에도 x가 존재하지 않는다!
    👉 JS 엔진은 암묵적으로 전역 객체에 x 프로퍼티를 동적 생성한다!
    👉 전역 객체에 x 프로퍼티가 생성되어 마치 전역변수처럼 사용된다!
    👉 이러한 현상을 암묵적 전역이라고 한다.

    개발자의 의도와 상관없이 발생한 암묵적 전역은 오류를 발생시킬 위험이 크다!

🔸 변수, 함수, 매개변수 삭제 및 이름 중복

delete 연산자로 변수, 함수, 매개변수를 삭제하면 오류가 발생한다.


🔹 strict mode 적용에 의한 변화

🔸 일반 함수의 this

strict mode를 적용하면 일반 함수에서의 this에 undefined가 바인딩된다.
strict mode를 적용하지 않으면 일반 함수에서의 this에 자신이 바인딩된다.

🔸 arguments 객체

매개변수로부터 전달된 인수의 재할당이 반영되지 않는다.




📃 21장. 빌트인 객체

🔹 JS 객체 분류

🔸 표준 빌트인 객체

ECMAScript 사양에 정의된 객체로 애플리케이션 전역의 공통 기능을 제공한다.
전역 객체 프로퍼티로써 제공되어 별도의 선언 없이 전역 변수처럼 사용이 가능하다.

🔸 호스트 객체

ECMAScript 사양에는 정의되어 있지 않지만 JS 실행환경에서 추가로 제공하는 객체.
DOM. BOM, fetch, SVG, Web storage 등이 여기에 속한다.

🔸 사용자 정의 객체


🔹 래퍼 객체

원시값 중 string, number, boolean, symbol 값에 대해 객체처럼 접근하면 생성되는 임시 객체를 말한다.

const str="string";
console.log(str.length); // 이때 래퍼 객체(String 생성자 함수의 인스턴스)가 생성된다
//래퍼 객체의 프로퍼티나 메서드를 사용한 후 다시 원시값으로 되돌려진다.
//이후 래퍼 객체는 가비지 컬렉션에 의해 정리된다.


🔹 전역 객체

window, global..

코드가 실행되기 이전 단계에 JS 엔진에 의해 어떤 객체보다도 먼저 생성되는 객체.
어떤 객체에도 속하지 않는 최상위 객체이다.

🔸 전역 객체의 특징

  • 개발자가 의도적으로 생성할 수 없다. (생성자 함수 x)
  • 브라우저 환경의 모든 JS코드는 하나의 전역 객체를 공유한다.
  • 전역 객체의 프로퍼티를 참조할 때 객체명을 생략할 수 있다.
    👉 암묵적 전역에 의해 자동으로 전역 객체에서 프로퍼티를 찾기 때문!

🚨 키워드 별 전역 객체 특징

var로 선언한 전역 변수와 전역 함수는 전역객체의 프로퍼티가 된다.
let, const로 선언한 전역 변수는 전역객체의 프로퍼티가 아니며, 전역 렉시컬 환경의 선언적 환경 레코드 내에 존재하게 된다. (뭔 소리고..)

🔸 빌트인 전역 프로퍼티

Infinity, NaN, undefined..

전역 객체의 프로퍼티.

🔸 빌트인 전역 함수

eval, isFinite, isNaN..




📃 22장. this

🔹 this키워드

🔸

profile
중요한 건 꺾여도 그냥 하는 마음

0개의 댓글