자식 -> 부모 : 자식에서 customEvent로 detail 속성 주고 넘겨주기

부모 -> 자식 : 부모에서 props나 메서드를 활용해 필요한 정보 넘겨주기

(문제)

button 컴포넌트에서 이벤트를 받아서 확인하려는데 

event.target으로 주니

내부에 있던 span 태그와 다른 아이콘 이미지 위에다가 마우스로 클릭했을 때 undefined가 떴었다..

currentTarget으로 하니 잘 동작하는 거다..

 

(원인)

event.target은 이벤트가 발생한 요소 그 자체

event.currentTarget은 handler가 연결된(위에서는 button 태그)요소

 

위 상황에서 버튼 이벤트를 잘 받아오기 위해서는 currentTarget을 썼어야 했다.

this 키워드

- 메서드가 자신이 속한 객체의 프로퍼티를 참조하려면 먼저 자신이 속한 객체를 가리키는 식별자를 참조해야 함.
- 생성자 함수 내부에서는 프로퍼티 또는 메서드를 추가하기 위해 자신이 생성할 인스턴스를 참조해야 함.

 

this (자기 참조 변수)
- 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 특수한 식별자가 필요했다.

- 자바나 c++ 같은 클래스 기반 언어 : this는 언제나 클래스가 생성하는 인스턴스를 가리킴.

- 자바스크립트 : this는 함수가 호출되는 방식에 따라 this에 바인딩될 값이 결정됨.

   - 전역에서 this는              전역 객체 window를 가리킴

   - 일반 함수 내부에서는      전역 객체 window를 가리킴  (strict mode가 적용되면 undefined가 바인딩 됨.)

   - 메서드 내부에서는          메서드를 호출할 객체를 가리킴

   - 생성자 함수 내부에서는  생성자 함수가 생성할 인스턴스를 가리킴

 

함수 호출 방식과 this 바인딩

- 동일한 함수도 다양한 방식으로 호출할 수 있음

 

const foo = function() {
	console.dir(this);
};

//1. 일반 함수 호출
foo(); //window

//2. 메서드 호출
const obj = {foo};
obj.foo(); //obj

//3. 생성자 함수 호출
new foo(); // foo{}

//4. Function.prototype.apply/call/bind 메서드에 의한 간접 호출
foo.call(bar);
foo.apply(bar);
foo.bind(bar)();

1.  일반 함수 호출

일반 함수로 호출된 모든 함수(중첩 함수, 콜백 함수 포함) 내부의 this는 전역 객체가 바인딩된다.

 

var value = 1;

const obj = {
	value: 100,
    foo() {
    	console.log("foo's this: ", this);  {value:100, foo:f}
        
        //콜백 함수 내부의 this에는 전역 객체가 바인딩됨.
        setTimeout(function () {
        	console.log("callback's this :: ", this); //window
            console.log("callback's this.value :: ", this.value); //1
        }, 100);
    }
};

obj.foo();

 

- 메서드 내부의 중첩 함수나 콜백 함수의 this 바인딩을 메서드의 this 바인딩과 일치시키기 위한 방법

   ->1)  Function.prototype.apply, call, bind 메서드 활용

   ->2) 화살표 함수 활용

 

var value = 1;

const obj = {
	value: 100,
    foo() {
    
    	//bind 함수
    	console.log("foo's this: ", this);  {value:100, foo:f}
        setTimeout(function () {
           console.log(this.value); //100
        }.bind(this), 100);
        
        //화살표 함수 내부의 this는 상위 스코프의 this를 가리킴
        setTimeout(() => console.log(this.value), 100);
    }
};

obj.foo();

 

2. apply, call, bind 메서드에 의한 간접 호출

- apply, call의 대표적인 용도 : arguments 객체와 같은 유사 배열 객체에 배열 메서드를 사용하는 경우

   - arguments는 배열이 아니기 때문에 Array.prototype.slice와 같은 배열의 메서드를 사용하지 못하나 apply 나 call 메서드로 가능

function convertArgsToArray() {
	console.log(arguments);
    
    //arguments 객체를 배열로 변환
    const arr = Array.prototype.slice.call(arguments);
    console.log(arr);
    
    return arr;
}

convertArgsToArray(1, 2, 3); // [1,2,3]

 

- bind 메서드는 apply와 bind와 달리 함수를 호출하지 않고,

첫 번째 인수로 전달한 값으로 this 바인딩이 교체된 함수를 새롭게 생성해 반환

 -> 메서드의 this와 메서드 내부의 중첩 함수 또는 콜백 함수의 this가 불일치하는 문제를 해결하기 위해 유용하게 사용됨.

function getThisBinding() {
	return this;
}

const thisArg = { a:1 };

// bind 메서드는 첫번째 인수로 전달된 thisArg로 this 바인딩이 교체된 getThisBinding 함수를 새로 생성해 반환
console.log(getThisBinding.bind(thisArg)); // getThisBinding
// bind 메서드는 함수를 호출하지 않으므로 명시적으로 호출해줘야 함.
console.log(getThisBinding.bind(thisArg)()); // { a:1 }
자바스크립트 객체는 다음과 같이 크게 3개의 객체로 분류할 수 있다.

1. 표준 빌트인 객체
- ECMAScript 사양에 정의된 객체를 말하며 애플리케이션 전역의 공통 기능을 제공한다.
2. 호스트 객체
- ECMAScript 사양에 정의되어있지 않지만, 자바스크립트 실행 환경에서 추가로 제공하는 객체를 말한다.
3. 사용자 정의 객체
- 표준 빌트인 객체와 호스트 객체처럼 기본 제공되는 객체가 아닌 사용자가 직접 정의된 객체를 말한다.

표준 빌트인 객체

- Object, Number, Boolean, Symbol, Data, Math, RegExp, Array, Map/Set, WeakMap/WeakSet, Function, Promise, Reflect, Proxy, JSON, Error 등 40여 개의 표준 빌트인 객체를 제공

- Math, Reflect, JSON(정적 메서드만 제공)을 제외한 표준 빌트인 객체는 모두 인스턴스를 생성할 수 있는 생성자 함수 객체(프로토타입 메서드와 정적 메서드를 제공)다.

const strObj = new String('Lee');

//string 생성자 함수를 통해 생성한 strObj 객체의 프로토타입은 String.prototype이다.
console.log(Object.getPrototypeOf(strObj) === String.prototype); //true

- 표준 빌트인 객체의 prototype 프로퍼티에 바인딩 된 객체(ex. String.prototype)는 다양한 기능의 빌트인 프로토타입 메서드를 제공한다.

- 그리고 표준 빌트인 객체는 인스턴스 없이도 호출 가능한 빌트인 정적 메서드를 제공한다.

 

const numObj = new Number(1.5);

//Number.prototype의 프로토타입 메서드
console.log(numObj.toFixed()); //2

//Number의 정적 메서드
console.log(Number.isInteger(0.5)); //false

 

 원시값과 래퍼 객체

- 문자열, 숫자, 불리언 값에 대해 객체처럼 접근하면 생성되는 임시 객체를 래퍼 객체라 함.

const str = 'hi';

//원시 타입의 문자열이 래퍼 객체인 String 인스턴스로 변환됨.
console.log(str.length); //2
console.log(str.toUpperCase()); //HI

//래퍼 객체로 프로퍼티에 접근하거나 메서드 호출한 후, 다시 원시값으로 되돌림
console.log(typeof str); //string

- 래퍼 객체의 처리가 종료되면, 래퍼 객체의 [[StringData]] 내부 슬롯에 할당된 원시 값으로 원시의 상태 즉, 식별자가 원시값을 갖도록 되돌리고 래퍼 객체는 가비지 컬랙션의 대상이 된다.

- String, Number, Boolean 생성자 함수를 new 연산자와 함께 호출하여 문자열, 숫자, 불리언 인스턴스를 생성할 필요가 없고 권장하지도 않음.

 

전역 객체

- 전역 객체는 코드가 실행되기 이전 단계에 js엔진에 의해 어떤 객체보다도 먼저 생성되는 특수한 객체이며, 어떤 객체에도 속하지 않은 최상위 객체다.

- 브라우저 환경 : window(또는 self, this, frames)

- Node.js 환경 : global

 

globalThis : ES11에서 도입된 모든 환경에서 사용 가능한 통일된 식별자

globalThis === this //true
globalThis === global //true

 

전역 객체
표준 빌트인 객체와
환경에 따른 호스트 객체,
그리고 var 키워드로 선언한 전역 변수와 전역 함수를
프로퍼티로 갖는다.

- 전역 객체가 최상위 객체라는 것은 프로토타입 상속 관계상에서 최상위 객체라는 의미가 아니라,
  전역 객체 자신은 어떤 객체의 프로퍼티도 아니며, 객체의 계층적 구조상 표준 빌트인 객체와 호스트 객체를 프로퍼티로 소유한다는 것을 말함.

- 전역객체의 특징

    1) 전역객체는 개발자가 의도적으로 생성할 수 없음. 즉, 전역객체를 생성할 수 있는 생성자 함수가 제공되지 않음.

    2) 전역객체의 프로퍼티를 참조할 때 window를 생략할 수 있음.

window.pareseInt('F', 16); //15
pareseInt('F', 16); //15

window.parseInt === parseInt; //true

   

    3) 전역 객체는 Object, String, Number, Boolean, Function, Array, RegExp, Date, Math, Promise 같은 모든 표준 빌트인 객체를 프로퍼티로 가지고 있다.

 

    4) 자바스크립트 실행환경에 따라 추가적으로 프로퍼티와 메서드를 갖는다. 브라우저 환경에서는 DOM, BOM, Canvas, XMLHttpRequest, fetch, requestAnimationFrame, SVG, Web Storage, Web Component, Web Worker와 같은 클라이언트 사이드 Web API를 호스트 객체로 제공하고, Node.js 환경에서는 Node.js 고유의 API를 호스트 객체로 제공한다.

 

   5) var 키워드로 선언한 전역 변수와 선언하지 않은 변수에 값을 할당한 암묵적 전역, 그리고 전역 함수는 전역 객체의 프로퍼티가 된다.

//var 키워드로 선언한 전역 변수
var foo = 1;
console.log(window.foo); // 1

//선언하지 않은 변수에 값을 암묵적 전역, bar은 전역 변수가 아니라 전역 객체의 프로퍼티
bar = 2;
console.log(window.bar); //2

//전역 함수
function baz() { return 3; } 
console.log(window.baz()); //3

 

    6) let이나 const로 선언한 전역 변수는 전역 객체의 프로퍼티가 아니다. let이나 const 키워드로 선언한 전역 변수는 보이지 않는 개념적인 블록(전역 렉시컬 환경의 선언적 환경 레코드) 내에 존재하게 된다.

    7) 분리되어 있는 자바스크립트 코드는 하나의 전역을 공유한다

 

빌트인 전역 프로퍼티

- 전역 객체의 프로퍼티를 의미함.

 

Infinity

- 무한대를 나타내는 숫자 값

console.log(window.Infinity === Infinity); // true

console.log(3/0); // Infinity
console.log(-3/0); // -Infinity

console.log(typeof Infinity); // number

NaN

- Not a Number

- console.log(window.NaN);

undefined

- console.log(window.undefined);

 

빌트인 전역 함수

- 전역 객체의 메서드

 

eval (이 함수의 사용은 금지해야 한다.)

- eval함수를 통해 사용자로부터 입력받은 콘텐츠를 실행하는 것은 보안에 매우 취약하다.

- 그리고 eval함수를 통해 실행되는 코드는 js엔진에 의해 최적화가 수행되지 않아서 일반적인 코드 실행에 비해 처리 속도가 느림

 

isFinite

isNaN

parseFloat

parseInt

encodeURI / decodeURI

- 쿼리 스트링 구분자로 사용되는 =, ?, &는 인코딩하지 않음

encodeURIComponent / decodeURIComponent

- 쿼리 스트링 구분자로 사용되는 =, ?, &는 인코딩함.

 

암묵적 전역

var x = 10; //전역 변수

function foo() {
	//선언하지 않은 식별자에 값을 할당
	y = 20; //window.y = 20;
}

//선언하지 않은 식별자 y를 전역에서 참조가능
console.log(x+y); //30

- 선언하지 않은 식별자 y는 마치 선언된 전역 변수처럼 동작한다.

   - 이는 선언하지 않은 식별자에 값을 할당하면 전역 객체의 프로퍼티가 되기 때문임. (y는 전역객체에 프로퍼티를 동적 생성해서 마치 전   역 변수처럼 동작함)

   - y는 변수 선언 없이 단지 전역 객체의 프로퍼티로 추가되었을 뿐이므로 y는 변수가 아니다. (변수 호이스팅이 발생하지 않는다.)

//전역 변수 x는 호이스팅이 발생함
console.log(x); //undefined
//전역 변수가 아니라 단지 전역 객체의 프로퍼티인 y는 호이스팅이 발생하지 않는다.
console.log(y); //ReferenceError: y is not defined

var x = 10; //전역 변수

function foo() {
	//선언하지 않은 식별자에 값 할당
	y = 20; // window.y = 20;
}
foo();

console.log(x+y); //30
ES5부터 strict mode가 추가되었다.
- strict mode는 자바스크립트 언어의 문법을 좀 더 엄격히 적용하여 오류를 발생시킬 가능성을 높임.
- 자바스크립트 엔진의 최적화 작업에 문제를 일으킬 수 있는 코드에 대한 명시적인 에러를 발생시킴.
- ESLint 같은 린트 도구를 사용해 유사한 효과를 얻을 수 있다.

*ESLint :
- 정적 분석 기능을 통해 소스코드를 실행하기 전에 소스코드를 스캔하여 문법적 오류만이 아니라 잠재적 오류까지 찾아내고 오류의 원인을 리포팅해주는 도구

 

 

Strict mode의 적용

- 전역의 선두나 함수 몸체의 선두에 추가한다.

'use strict';

function foo() {
	x = 10; //ReferenceError: x is not defined
}
foo();

전역에 strict mode를 적용하는 것은 피하자

- 전역에 적용한 strict mode는 스크립트 단위로 적용함. (해당 스크립트에 한정되어 적용)

- strict mode 스크립트와 non-strict mode 스크립트를 혼용하는 것은 오류를 발생시킬 수 있다.

- 특히, 외부 서드파티 라이브러리를 사용하는 경우 라이브러리가 non-strict mode인 경우도 있어서 전역에 strict mode를 적용하는 것은 바람직하지 않다.

    - 이러한 경우, 즉시 실행 함수로 스크립트 전체를 감싸서 스코프를 구분하고, 즉시 실행 함수의 선두에 strict mode를 적용한다.

(function () {
	//즉시 실행 함수 선두에 적용
	'use strict';
    //Do something..
}());

 

함수 단위로 strict mode를 적용하는 것도 피하자

- 위와 같은 이유로 strict mode는 즉시 실행 함수로 감싼 스크립트 단위로 적용하는 것이 바람직함.

strict mode가 발생시키는 에러 예시

- 암묵적 전역 

- 변수, 함수, 매개변수의 삭제

- 매개변수 이름의 중복

- with 문의 사용

 

strict mode 적용에 의한 변화

일반함수의 this

- strict mode에서 일반 함수로서 호출하면 this에 undefined가 바인딩된다.

   - 생성자 함수가 아닌 일반 함수 내부에서는 this를 사용할 필요가 없기 때문이다. (에러발생x)

(function() {
	'use strict';
    
    function foo() {
    	console.log(this); //undefined
    }
    foo();
    
    function Foo() {
    	console.log(this); //Foo
    }
    new Foo();
}());

 

arguments 객체

- strict mode에서는 매개변수에 전달된 인수를 재할당하여 변경해도 arguments객체에 반영되지 않음

프로토타입 체인

- Person 생성자 함수에 의해 생성된 me 객체는 Object.prototype 메서드인 hasOwnProperty로 호출할 수 있는데, 이것은 me 객체가 Person.prototype뿐만 아니라 Object.prototype도 상속받았다는 것을 의미함.

 

프로토타입 체인
- JS가 객체지향 프로그래밍의 상속을 구현하는 메커니즘
- 자바스크립트는 객체의 프로퍼티(메서드 포함)에 접근하려고 할 때,
해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라
자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다.

- Object.prototype을 프로토타입 체인의 종점이라 함.!

프로토타입 체인 상속과 프로퍼티 검색을 위한 메커니즘
vs.
스코프 체인 식별자 검색을 위한 메커니즘 (자바스크립트 엔진은 함수의 중첩 관계로 이루어진 스코프의 계층적 구조에서 식별자를 검색함.)

me.hasOwnProperty('name');​

 

1. 스코프 체인에서 me 식별자를 검색한다.
2. me 식별자는 전역에서 선언되었기 때문에 전역 스코프에서 검색된다.
3. me 식별자를 검색한 다음, me 객체의 프로토타입 체인에서 hasOwnProperty 메서드를 검색한다.
=> 서로 협력하여 식별자와 프로퍼티를 검색하는 데 사용함

 

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

- 프로토타입이 소유한 프로퍼티를 프로토타입 프로퍼티, 인스턴스가 소유한 프로퍼티를 인스턴스 프로퍼티라고 함.

- 인스턴스 메서드가 프로토타입 메서드를 오버라이딩하여,,

- 상속 관계에 의해 프로퍼티가 가려지는 현상을 프로퍼티 섀도잉이라고 한다.

   - 오버라이딩 : 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식

- 하위 객체를 통해 프로토타입의 프로퍼티를 변경/삭제하는 것은 불가능한다. (get 허용, set 불가)

   - 프로토타입에 직접 접근하면 가능.

 

프로토타입의 교체

- 프로토타입은 임의의 다른 객체로 변경할 수 있다.

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

const Person = (function () {
	function Person(name) {
    	this.name = name;
    }
    
    //1) 생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체하기
    Person.prototype = {
    	sayHello() {
        	console.log(`Hi My name is ${this.name}`);
        }
    };
    
    return Person;
}());

const me = new Person('Lee');

//프로토타입을 교체하면 constructor프로퍼티와 생성자 함수 간 연결이 파괴됨.
console.log(me.constructor === Person); //false
//프로퍼티 체인에 따라 Object.prototype의 constructor 프로퍼티가 검색된다.
console.log(me.constructor === Object); //true

- 프로토타입으로 교체한 객체 리터럴에는 constructor 프로퍼티가 없다.

   - constructor 프로퍼티는 js엔진이 프로토타입을 생성할 때 암묵적으로 추가한 프로퍼티다.

- 따라서, me 객체의 생성자 함수를 검색하면 Person이 아닌 Object가 나옴.

 

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

- 프로토타입은 생성자의 prototype 프로퍼티 뿐만 아니라 인스턴스의 __proto__ 접근자 프로퍼티를 통해 접근할 수 있다.

   - 따라서 인스턴스의 접근자 프로퍼티나 Object.setPrototypeOf 메서드를 통해 프로토타입을 교체할 수 있다.

생성자 함수의 prototype 프로퍼티에 다른 임의의 객체를 바인딩하는 것은
미래에 생성할 인스턴스의 프로토타입을 교체하는 것이고, 
__prototype__ 접근자 프로퍼티를 통해 프로토타입을 교체하는 것은 
이미 생성된 객체의 프로토타입을 교체하는 것이다.

 

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

const me = new Person('Lee');

//프로토타입으로 교체할 객체
const parent = {
	sayHello() {
    	console.log(`Hi! My name is ${this.name}`);
    }
};

//1) me 객체의 프로토타입을 parent 객체로 교체하기
Object.setPrototypeOf(me, parent);
//== me.__proto__ = parent;
me.sayHello();

//프로토타입을 교체하면 constructor프로퍼티와 생성자 함수 간 연결이 파괴됨.
console.log(me.constructor === Person); //false
//프로퍼티 체인에 따라 Object.prototype의 constructor 프로퍼티가 검색된다.
console.log(me.constructor === Object); //true

- 인스턴스에 의한 프로토타입 교체 : Person 생성자 함수의 prototype 프로퍼티가 교체된 프로토타입을 가리키지 않는다.

   - (근데 생성자 함수에 의한 프로토타입 교체는  ok)

 

- 프로토타입으로 교체한 객체 리터럴에 constructor 프로퍼티를 추가하고, 생성자 함수의 prototype 프로퍼티를 재설정해서 파괴된 생성자 함수와 프로토타입 간의 연결을 되살려볼 수 있다.

   -> (프로토타입 교체를 통해 객체 간의 상속 관계를 동적으로 변경하는 것은 꽤나 번거로움 ;;)

   - 따라서 프로토타입은 직접 교체하지 않는 것이 좋다~~~~~ 상속관계를 인위적으로 설정할 때는 이후 나오는 직접상속이 더 안전하다.

 

Instanceof 연산자

- 이항 연산자로서 좌변에는 객체를 가리키는 식별자, 우변에는 생성자 함수를 가리키는 식별자 (함수가 아니면 TypeError 발생)

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

const me = new Person('Lee');

//Person.prototype이 me 객체의 프로토타입 체인 상에 존재하므로 true로 평가
console.log(me instanceof Person); //true 
//Object.prototype이 me 객체의 프로토타입 체인 상에 존재하므로 true로 평가
console.log(me instanceof Object); //true

 

- 프로토타입 교체를 통해 instanceof 연산자의 동작 과정 알아보기

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

const me = new Person('Lee');

const parent = {};

Object.setPrototypeOf(me, parent);

//Person의 생성자 함수와 parent 객체는 연결되어있지 않다.
console.log(Person.prototype === parent); // false
console.log(parent.constructor === Person); // false (Person 말고 Object가 ok일듯)

//Person.prototype이 me 객체의 프로토타입 체인 상에 존재하지 않아서 false로 평가되는거임.
console.log(me instanceof Person); //false
console.log(me instanceof Object); //true

- parent 객체를 Person 생성자 함수의 prototype 프로퍼티에 바인딩하면, me instanceof Person은 true..

Person.prototype = parent;

console.log(me instanceof Person); //true
console.log(me instanceof Object); //true

 

instanceof 연산자는 생성자 함수의 prototype에 바인딩된 객체가 프로토타입 체인 상에 존재하는지 확인한다. (프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수를 찾는 것이 아님 !!!)

- 따라서, 생성자 함수에 의해 프로토타입이 교체될 때는 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴되어도,

  생성자 함수의 prototype 프로퍼티와 프로토타입 간의 연결은 파괴되지 않으므로, instanceof는 아무런 영향을 받지 않음.

 

 

직접 상속

Object.create에 의한 직접 상속

- 명시적으로 프로토타입을 지정하여 새로운 객체를 생성 (다른 객체 생성 방식과 마찬가지로 추상 연산 OrdinaryObjectCreate 호출)

 

/**
 @param {Object} prototype - 생성할 객체의 프로토타입으로 지정할 객체
 @param {Object} [propertiesObject] - 생성할 객체의 프로퍼티를 갖는 객체
 @returns {Object} 지정한 프로토타입 및 프로퍼티를 갖는 새로운 객체
*/
Object.create(prototype[, propertiesObject])

 

정적  프로퍼티/메서드

- 생성자 함수를 인스턴스로 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메서드를 말함

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

//프로토타입 메서드
Person.prototype.sayHello = function () {
	console.log('');
};


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

//정적 메서드
Person.staticMethod = function() {
	console.log('static');
};

Person.staticMethod(); //static

- 앞에서 살펴본 Object.create 메서드는 Object 생성자 함수의 정적 메서드고, (인스턴스로 호출불가)

 Object.prototype.hasOwnProperty 메서드는 Object.prototype의 메서드다. (Object.prototype은 모든 객체의 프로토타입 체인의 종점이라 모든 객체를 호출할 수 있음)

- MDN문서에서 정적 프로퍼티/메서드와 프로토타입 프로퍼티/메서드를 구별할 수 있다!

   - 참고로, 프로토타입 프로퍼티/메서드를 표기할 때 prototype을 #으로 표기하는 경우도 있음.

   - Object.prototype.isPrototypef === Object#isPrototypeOf 으로 표기

 

프로퍼티 존재 확인

in 연산자

- 객체 내 특정 프로퍼티가 존재하는지 여부를 확인함.

- in연산자 === ES6에서 도입된 Reflect.has메서드

const person = {
	name: 'Lee',
    address: 'Seoul',
};

console.log('name' in person); //true
console.log('address' in person); //true
console.log('age' in person); //false

//프로토타입 체인 상에 존재하는 모든 프로토타입에서 toString 프로퍼티를 검색함.
console.log('toString' in person); // true -> Object.prototype메서드

console.log(Reflect.has(person, 'name')); // true

 

Object.prototype.hasOwnProperty 메서드

- Object.prototype.hasOwnProperty 메서드를 사용해도 객체에 특정 프로퍼티가 존재하는지 확인가능.

- 다른 점은 상속받은 프로토타입의 프로퍼티 키인 경우 false를 반환함.

 

프로퍼티 열거

for ... in문

- 객체의 모든 프로퍼티를 순회하며 열거할 때 사용.

const person = {
	name: 'Lee',
    address: 'Seoul',
};

for(const key in person) {
	console.log(key + ': ' + person[key]);
}
//name: Lee
//address: Seoul

- Object.prototype.toString 프로퍼티의 프로퍼티 어트리뷰트가 [[Enumerable]]이 false이다. (열거 불가)

  - 출력 시, 나타나지 않는 이유다.

 

for ... in문은
객체의 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서
프로퍼티 어트리뷰트 [[Enumerable]]의 값이 true인 프로퍼티를
순회하며 열겨한다.

 

- 배열에는 for ... in 문을 사용하지 않고
   일반적인 for문이나 for ... of 문(키가 아닌 값 할당) 또는 Array.prototype.forEach 메서드 사용을 권장

   - 사실 배열도 객체이므로 프로퍼티와 상속받은 프로퍼티가 포함될 수 있음.

Object.keys/valules/entries 메서드

- for ... in문은 객체 자신의 고유 프로퍼티 뿐만 아니라 상속받은 프로퍼티도 열거하기 때문에 Object.prototype.hasOwnProperty 메서드로 객체 자신의 프로퍼티인지 확인하는 추가 절차가 필요하다.

-객체 자신의 프로퍼티만 열거하기 위해서는 Object.keys/valules/entries 메서드를 사용하는 것을 권장한다.

const person = {
	name: 'Lee',
    address: 'Seoul',
    __proto__: {age : 20}
};

//객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환
console.log(Object.keys(person)); ["name", "address"]

//ES8에서 도입된 Object.values 메서드
//객체 자신의 열거 가능한 프로퍼티 값을 배열로 반환
console.log(Object.values(person)); // ["Lee", "Seoul"]

//ES8에서 도입
//객체 자신의 열거 가능한 프로퍼티 키와 값의 쌍의 배열을 배열에 담아 반환
console.log(Object.entries(person)); // [ ["name", "Lee"], ["address", "Seoul"] ]

Object.entries(person).forEach(([key, value]) => console.log(key, value));
/*
name Lee
address Seoul
*/
자바스크립트는
명령형, 함수형, 프로토타입 기반 객체지향 프로그래밍을 지원하는
멀티 패러다임 프로그래밍 언어

 

객체지향 프로그래밍

- 자바스크립트는 객체 기반의 프로그래밍 언어이며 자바스크립트를 이루고 있는 거의 "모든 것"이 객체다.

- 객체는 상태 데이터와 동작을 하나의 논리적인 단위로 묶은 복합적인 자료구조라 할 수 있음.

    - 이때 객체 상태 데이터를 프로퍼티, 동작을 메서드라고 부름.

상속과 프로토타입

- 자바스크립트는 프로토타입을 기반으로 상속을 구현하여 불필요한 중복을 피한다.

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);

//getArea 메서드를 중복 생성하고 모든 인스턴스가 중복 소유한다.
//getArea 메서드는 하나만 생성해서 모든 인스턴스가 사용하는 것이 바람직함.
console.log(circle1.getArea === circle2.getArea); //false
console.log(circle1.getArea());
console.log(circle2.getArea());

- 위 생성자 함수는 문제가 있음.

   - Circle 생성자 함수는 인스턴스를 생성할 때마다 getArea 메서드를 중복 생성하고 모든 인스턴스가 중복 소유한다. 

   - 메모리를 불필요하게 낭비하고, 퍼포먼스에도 악영향을 준다.

 자바스크립트는 프로토타입을 기반으로 상속을 구현한다.
- 상속을 통해 불필요한 중복을 제거해보자!
//생성자 함수
function Circle(radius) {
	this.radius = radius;
}

// Circle 생성자 함수가 생성한 모든 인스턴스가 getArea 메서드를 
// 공유해서 사용할 수 있도록 프로토타입에 추가함.
// 프로토타입은 Circle 생성자 함수의 prototype 프로퍼티에 바인딩되어 있다.
Circle.prototype.getArea = function() {
	return Math.PI * this.radius ** 2;
};

const circle1 = new Circle(1);
const circle2 = new Circle(2);

//부모 객체의 역할을 하는 프로토타입 Circle.prototype으로부터 getArea 메서드를 상속받는다.
console.log(circle1.getArea === circle2.getArea); //true

- 생성자 함수가 생성할 모든 인스턴스가 공통적으로 사용할 프로퍼티나 메서드를 프로토타입에 미리 구현해두면

   생성자 함수가 생성할 모든 인스턴스는 별도의 구현 없이 상위(부모) 객체인 프로토타입의 자산을 공유하여 사용할 수 있다.

 

프로토타입 객체

- 프로토타입 객체는 객체 간 상속을 구현하기 위해 사용한다.

    - 프로토토입을 상속받은 하위(자식) 객체는 상위 객체의 프로퍼티를 자신의 프로퍼티처럼 사용할 수 있다.

- 모든 객체는 [[Prototype]]이라는 내부 슬롯을 가지고, 이 내부 슬롯의 값은 프로토타입의 참조다.

   - 객체 생성 방식에 의해 [[Prototype]]에 저장되는 프로토타입이 결정된다.

       - 객체 리터럴에 의해 생성된 객체의 프로토타입 : Object.Prototype

       - 생성자 함수에 의해 생성된 객체의 프로토타입 : 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체

모든 객체는 하나의 프로토타입을 갖는다.
모든 프로토타입은 생성자 함수와 연결되어 있다.
- 즉, 객체와 프로토타입과 생성자 함수는 서로 연결되어 있다.

 

1) __proto__ 접근자 프로퍼티

- 모든 객체는 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입, 즉 [[Protoype]] 내부 슬롯에 간접적으로 접근할 수 있다.

- 접근자 프로퍼티는 자체적으로 값([[Value]] 프로퍼티 어트리뷰트)을 가지지 않고,
  접근자 함수인 ([[Get]], [[Set]] 프로퍼티 어트리뷰트로 구성된 프로퍼티다.) 

const obj = {};
const parent = { x:1 };

//getter 함수인 get ___proto__가 호출되어 obj 객체의 프로토타입을 취득
obj.__proto__;
//setter 함수인 set __proto__가 호출되어 obj 객체의 프로토타입을 교체
obj.__proto__ = parent;

console.log(obj.x); //1

2) __proto__ 접근자 프로퍼티는 상속을 통해 사용된다.

- __proto__ 접근자 프로퍼티는 Object.protoype의 프로퍼티다. (객체가 직접 소유하는 프로퍼티가 아님.)

-  Object.protoype는 프로토타입 체인의 최상위 객체다. 이 객체의 프로퍼티와 메서드는 모든 객체에 상속됨.

const person = {name:'Lee'};
console.log(person.hasOwnProperty('__proto__')); // false
console.log(Object.getOwnPropertyDescriptor(Object.prototype, '__proto__'));
// {get : f, set: f, enumerable: false, configurable: true}

//모든 객체는 Object.prototype의 접근자 프로퍼티 __proto__를 상속받아 사용할 수 있다.
console.log({}.__proto__ === Object.prototype); // true

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

- 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위해서이다.

 

4) __proto__ 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장되지 않는다.

함수 객체의 prototype 프로퍼티

- prototype 프로퍼티는 생성자 함수가 생성할 객체의 프로토타입을 가리킨다.

- non-constructor인 화살표 함수와 es6 메서드 축약 표현으로 정의한 메서드는 prototype 프로퍼티를 소유하지 않으며, 프로토타입도 생성하지 않는다.

 

프로토타입의 constructor 프로퍼티와 생성자 함수

- 모든 프로토타입은 constructor 프로퍼티를 갖는다.

- constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킨다.

 

객체 리터럴에 의해 생성된 객체의 프로토타입

const obj = { x: 1 };
console.log(obj.constructor === Object}; //true
console.log(obj.hasOwnProperty('x')); //true

- 리터럴 표기법에 의해 생성된 객체도 물론 프로토타입이 존재하는데, 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 단정할 수 없다.!

 

Object 생성자 함수에 의해 생성된 객체의 프로토타입

const obj = new Object();
obj.x = 1;

- Object 생성자 함수에 인수를 전달하지 않거나, undefined 또는 null을 인수로 전달하면서 호출하면 내부적으로 추상 연산 OrdinaryObjectCreate를 호출하여 Object.prototype을 프로토타입으로 갖는 빈 객체를 생성한다.

   - 객체 리터럴에 의해 생성된 객체와 동일한 구조를 갖는다.

   - 표준 빌트인 객체인 Object 생성자 함수와 더불어 생성된 프로토타입 Object.prototype은 다양한 빌트인 메서드를 가짐.

Object 생성자 함수에 의해 생성된 obj 객체는 Object.prototype을 프로토타입으로 갖게 되며, 이로써 Object.prototype을 상속받는다.

 

 

생성자 함수에 의해 생성된 객체의 프로토타입

- 다른 객체 생성 방식과 마찬가지로 추상 연산 OrdinaryObjectCreate가 호출된다.

- 생성자 함수에 의해 생성되는 객체의 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어있는 객체이다. 

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

const me = new Person('Lee');

- 사용자 정의 생성자 함수 Person과 더불어 생성된 프로토타입 Person.prototype의 프로퍼티는 constructor 뿐임. 

- 프로토타입 Person.prototype에 프로퍼티를 추가하여 하위 객체가 상속받을 수 있도록 구현해볼 수 있음.

   - 프로토타입도 객체이기 때문에 일반 객체와 같이 프로토타입에도 프로퍼티를 추가/삭제할 수 있다! 

 

 

추상 연산 OrdinaryObjectCreate에 의해 생성자 함수와 생성자 함수의 prototype 프로퍼티에 바인딩되어있는 객체 사이에 연결이 만들어진다.

일급 객체

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

- 함수가 일급객체라는 것은 함수를 객체와 동일하게 사용할 수 있다는 의미

   - 객체는 값이므로 함수는 값과 동일하게 취급할 수 있음.

- 따라서 함수는 값을 사용할 수 있는 곳이라면 리터럴로 정의할 수 있으며 런타임에 함수 객체로 평가된다.

- 함수형 프로그래밍을 가능케 하는 자바스크립트의 장점 중 하나다.

//자바 스크립트는 위의 조건을 모두 만족해서 일급 객체임.

//1.2. (변수에 저장)
const increase = function (num) {
	return ++num;
};

const decrease = function (num) {
	return --num;
};

//2. (객체에 저장)
const auxs = {increase, decrease};

- 함수는 객체이지만, 일반 객체와는 차이가 있다.

   - 일반 객체는 호출할 수 없지만, 함수 객체는 호출할 수 있다.

   - 그리고 함수 객체는 일반 객체에는 없는 함수 고유의 프로퍼티를 소유한다.

 

함수 객체의 프로퍼티

- 함수는 객체라서 프로퍼티를 가질 수 있음 -> console.dir 메서드로 함수 객체 내부를 볼 수 있다.

   - 모든 프로퍼티의 어트리뷰트는 -> Object.getOwnPropertyDescriptors 메서드로 확인할 수 있다.

   - arguments, caller, length, name, prototype 프로퍼티는 모두 함수 객체의 데이터 프로퍼티

   - __proto__는 접근자 프로퍼티 (함수 객체 고유의 프로퍼티가 아니라 Object.prototype객체의 프로퍼티를 상속받음.)

 

1) arguments 프로퍼티

- arguments 객체는 함수 호출 시 전달된 인수들의 정보를 담고 있는 iterable한 유사 배열 객체

- 함수 객체의 arguments는 함수 내부에서 지역 변수처럼 사용할 수 있는 arguments 객체를 참조함.

자바스크립트는 함수의 매개변수와 인수의 개수가 
일치하는 지 확인하지 않는다.
//arguments 객체의 Symbol 프로퍼티

function multiply(x, y) {
	const iterator = arguments[Symbol.iterator]();
    
    console.log(iterator.next()); // {value: 1, done:false}
    console.log(iterator.next()); // {value: 2, done:false}
    console.log(iterator.next()); // {value: 3, done:false}
    console.log(iterator.next()); // {value: undefined, done:true}
    
    return x * y;
}

multiply(1, 2, 3);

- 유사 배열 객체는 배열이 아니라서 배열 메서드를 사용할 경우 에러가 발생

   - Function.prototype.call, Function.prototype.apply를 사용해 간접 호출해야 함.

function sum() {
	// arguments 객체를 배열로 변환
	const array = Array.prototype.slice.call(arguments);
    return array.reduce(function(pre,cur) {
    	return pre + cur;
    }, 0);
}

console.log(sum(1,2));
console.log(sum(1,2,3,4,5));

- ES6 Rest 파라미터의 도입으로 이거 별로 중요하지 않게 됨.

function sum(... args) {
	return args.reduce((pre, cur) => pre+cur, 0);
}

2) caller 프로퍼티 

- ECMAScript 사양에 포함되지 않은 비표준 프로퍼티라고 함 (이후 표준화돌 예정도 없는 프로퍼티라 참고만)

function foo(func) { return func() };

function bar() {return 'caller: ' + bar.caller; }

console.log(foo(bar));  //caller : function foo(func) {...}

3) length 프로퍼티 

- 함수를 정의할 때, 선언한 매개변수의 개수를 가리킴.

4) name 프로퍼티

- 함수 이름을 나타냄. (ES6에서 정식 표준이 되었다.)

5) __proto__ 접근자 프로퍼티

- 모든 객체는 [[Prototype]]이라는 내부 슬롯을 갖는다.

   - [[Prototype]] 내부 슬롯은 객체지향 프로그래밍의 상속을 구현하는 프로토타입 객체를 가리킨다.

- __proto__ 프로퍼티는 프로토타입 객체에 접근하기 위해 사용하는 접근자 프로퍼티이다.

const obj = { a: 1 };
console.log(obj.__proto__ === Object.prototype); // true

console.log(obj.hasOwnProperty('a')); // true
console.log(obj.hasOwnProperty('__proto__')); // false 

//hasOwnProperty : 키가 객체 고유의 프로퍼티일 때만 true, 상속받은 프로토타입의 프로퍼티인 경우 false

 

6) prototype 프로퍼티

- 생성자 함수로 호출할 수 있는 함수 객체, 즉, constructor만이 소유하는 프로퍼티

- 일반 객체와 non-constructor에는 prototype 프로퍼티가 없다.

 

 

+ Recent posts