JS DeepDive 정리 1장 - 20장
자바스크립트 딥다이브를 읽으며 중요하거나 모르는 내용 위주로 정리했습니다.
1. 프로그래밍이란?
- 프로그래밍이란 컴퓨터에게 실행을 요구하는 커뮤니케이션이다. 구문과 의미의 조합이다.
- 문제 해결을 위해 컴퓨팅 사고력을 요한다.
- 우리는 프로그래밍 언어를 작성하고, 컴파일러는 그것을 기계어로 번역한다.
- 문법적으로 틀리지 않아도, 요구사항에 부합하지 않으면 의미가 없다.
- 결국 프로그래밍은 요구사항을 분석하고, 함수의 집합으로 변환하고, 흐름을 제어하는 일이다.
2. 자바스크립트란?
- 넷스케이프에서 브라우저에서 동작하는 프로그래밍 언어를 도입하기로 결정 → 모카 → 라이브스크립트 →자바스크립트
- JScript 의 등장으로 위기를 맞았으나 표준화 기구 ECMA 인터내셔널에 JS의 표준화를 요청, ECMAScript로 명명되었다.
- 서버-브라우저가 비동기 통신을 할 수 있는 Ajax가 XMLHttpRequest라는 이름으로 등장했다. 부분적으로 렌더링이 가능했다.
- jQuery로 DOM을 쉽게 제어할 수 있게 됐다.
- V8 자바스크립트 엔진으로 JS의 발전이 촉발 되었다.
- Node.js는 V8엔진으로 빌드된 JS런타임 환경이다. 브라우저가 아니어도 독립적인 실행환경을 조성한다.
- Node.js는 비동기 I/O를 지원하며 단일스레드 이벤트 루프 기반으로 동작하기 때문에 처리 성능이 좋다.
- ECMAScript는 자바스크립트의 부분집합이다. ECMAScript가 뼈대를 이루고, Web API를 합친 것까지 자바스크립트이다.
- 웹 브라우저에서 동작하는 유일한 프로그래밍 언어이고, 인터프리터 언어이며, 복잡한 과정을 거치며 일부 코드를 컴파일하고 실행한다.
- 자바스크립트는 명령형, 함수형, 프로토타입기반 객체 지향 프로그래밍을 지원하는 멀티 패러다임 프로그래밍 언어이다.
3. 자바스크립트 개발 환경과 실행 방법
- Node.js에서는 Web API를 호출할 수 없다.
- 브라우저에서는 fs 가 지원되지 않는다.
- 브라우저는 Client-sied API를 지원하고, Node.js는 Host의 API를 지원한다.
- 나머지 내용은 기타 환결 설정
4. 변수
- 메모리 주소를 통해 값에 직접 접근하는 것은 치명적인 오류 발생을 야기한다. 값이 저장될 메모리의 주소는 코드가 실행되기 전에는 알 수가 없다. 기억하고 싶은 값을 메모리에 저장하기 위해 변수를 이용한다. 변수는 메모리 공간 자체 또는 메모리 공간을 식별하기 위해 붙인 이름을 의미한다. 즉 값이 아니라
값의 위치
이다.(메모리 주소의 nickname이라고 생각하자. result=30은 30이 저장된 메모리 주소를 result라고 부르겠다는 뜻이다) 변수는 컴파일러나 인터프리터에 의해 메모리 주소값으로 치환된다. - 변수에 값을 대입하는 것을 할당, 변수로부터 값을 읽는 것을 참조라고 한다.
- 식별자는 고유한 이름이다. 값을 구별해서 식별할 수 있는 고유한 이름이다. 변수 이름도 식별자이다. 변수, 함수, 클래스 등이 식별자이다.
- 실제로 메모리에 저장되는 값은 2진수이다.
- 변수 선언시 암묵적으로 초기값은
undefined로 초기화된다.
- 변수 선언은 변수 선언 단계와 undeined로의 초기화 단계로 나뉠 수 있으며, 변수 이름을 비롯한 모든 식별자는 실행컨텍스트에 등록된다.
- 실행컨텍스트는 자바스크립트 엔진이 소스코드를 평가하고 실행하기 위해 필요한 환경을 제공하고 실행결과를 관리하는 영역으로, 변수는 여기에 키와 값 형식의 객체로 저장된다.
- 변수 선언은 런타임 이전에 진행이 되기 때문에 호이스팅이 동작하는 것이다. 모든 선언문이 실행되면서 소스코드의 평가과정이 끝난 뒤 런타임이 실행된다. 이는 선언과 할당을 한 줄에 적더라도 마찬가지이다!!! 이 때 최초로 undefined로 선언될 때와 80이 될 때 메모리 주소에 변동이 생긴다.
- 변수 호이스팅: 자바스크립트는 라인 바이 라인으로 실행이 되지만 실행 전 소스코드의 평가과정을 거치면서 변수, 클래스 등의 모든 선언문을 먼저 실행한다. 따라서 변수가 어딘가에 선언이 되어있기만 하면 선언문보다 사용문을 먼저 쓸 수 있다. 변수 선언문이 끌어올려진 것처럼 동작하기에 이를 호이스팅이라고 한다. 선언과 할당이 동시에 이루어져있다고 해도, 실행 순서는 구분되어서 실행된다.
- 변수에서 값을 재할당하면 메모리 주소 째로 다시 부여된다. 가비지 콜렉터는 메모리 공간을 주기적으로 검사하여 사용되지 않는 메모리를 해제한다.
- 식별자 네이밍은 문자로 시작하거나, 언더스코어(_), 달러($)가 시작문자로 허용된다. 숫자는 안 된다!!
- 일반적으로 변수, 함수에는 카멜케이스, 클래스 이름에는 파스칼 케이스를 따른다.
- 값을 재할당 하면 메모리 주소도 바뀐다.
- DOM노드를 표현할 때는 $를 앞에 붙이는 것이 컨벤션이다.
5. 표현식과 문
문(최소 실행 단위)→표현식(값으로 평가될 수 있는 문)→ 값(표현식의 평가 결과) 순으로 정의
표현식인 문과 표현식이 아닌 문이 있음. 구분하기 위해서는 x=에 그 문을 넣을 수 있는지를 보자. 표현식인 경우 개발자도구에서 값을 리턴해준다.
할당문도 표현식인데, 선언문은 표현식이 아니다. var x=1을 콘솔에 입력해보면 undefined이지만 x=2를 한번 더 찍어보면 2를 리턴한다.
- 값은 식(표현식)이 평가되어 생성된 결과이고, 모든 값은 데이터 타입을 갖는다. 변수에는 식이 아닌 값이 할당된다.
- 리터럴은 사람이 이해할 수 있는 문자를 통해 값을 생성하는
표기법
이다. 숫자리터럴 3을 입력하면 JS엔진은 이를 평가해 메모리에 11을 저장하는 식. 사람이 리터럴을 코드에 기술하고, 엔진은 이를 평가해 값을 생성한다. 컴퓨터를 이해시키시 위한 표기법이다. - 표현식은 값으로 평가될 수 있는 statement이다. 리터럴도 그 자체로 표현식이며, 연산식, 변수도 표현식이다.
값이 생성되면 표현식이다.
덧붙이자면 x=으로 받아서 할당이 가능하면 표현식이다! - 문은 최소 실행 단위이다. 문은 토큰으로 이루어져있으며, 토큰은 형태소이다. 세미콜론은 문의 종료를 나타낸다.
- 표현식인 문과 표현식이 아닌 문:
그 자체로 값이 될 수 있는가?
를 통해 구분할 수 있다. - 표현식은 항상 값을 반환한다.
변수 선언문
은표현식이 아니다.
- 그러나 할당문은 표현식이다!!
6. 데이터타입
숫자(double64)문자불리언 널언디파인드 심벌 객체 총 7개
- 데이터타입은 크게 원시타입과 객체타입으로 분류된다.
- js에서는 모든 숫자 타입은 64비트 실수이다. 진법별 타입이 없다. 다른 진법의 리터럴로 값을 할당해도 모두 10진수로 출력된다.
- Infinity, -Infinity, NaN(Not-a-Number)가 존재한다.
- 문자열은
변경 불가능한 값(immutable value
이다. - 백틱을 이용한 템플릿 리터럴 내에서는 이스케이프 시퀀스 없이도 줄바꿈 허용. 일반 따옴표 문자열은 아예 불가능.
- ${} 은 표현식 삽입으로, 강제로 문자열로 변환되어 삽입된다. 반드시 템플릿 문자열 안에서 이루어져야한다.
- 심볼 타입은 이름이 충돌할 위험이 없는 객체의 유일한 property key를 만들기 위해 사용한다. 리터럴이 아니라 함수를 호출해서 생성된다. 다른 값과 절대 중복되지 않는다.
var key = Symbol('key')
var obj = {};
obj[key] = 'value'
console.log(obj[key])
- 자료 형은 아래와 같은 세 가지 기능을 한다.
- 값을 저장할 때 확보해야 하는 메모리 공간의 크기 결정
- 값을 참조할 때 한 번에 읽어야 하는 메모리 공간의 크기 결정
- 메모리에서 읽어들인 2진수를 어떻게 해석할건지 결정(숫자. 문자..)
- 🌟🌟🌟🌟🌟자바스크립트는 선언이 아니라 할당에 의해 타입이 결정되는 동적 타이핑 언어이다!!!!!!!!
- 변수선언의 최소화, 스코프의 범위 최소화, 전역 변수 선언의 최소화가 타입변환이 마구 일어나는 JS의 오류를 줄인다.
9. 타입 변환과 단축 평가
- 명시적 타입 변환에는 new 없이 표준 빌트인 생성자 함수(객체를 생성하기 위한 함수)를 호출하는 방법 (예를 들어 String(1), Number('1')) , 빌트인 메서드를 사용하는 방법이 있고(1.toString(), parseInt('1)) 암묵적으로 타입 변환을 이용하는 방법도 있다.
- 두 방법이 구분되는 기준은, 코드 문맥 내에서 타입 변환을 하겠다는 개발자의 의도가 명백히 드러나는가이다.
- 두 방법 모두 기존 원시 값을 직접 변경하지는 않는다. x===1일때, var str= x+ '' 인상황에서 str는 string이지만 x는 영향받지 않는다.
- 타입 변환은 기존 원시 값을 사용해 다른 타입의 새로운 원시 값을 생성하는 것이다.
- 이항 연산 시 +에서는 문자열타입으로 변환이 우선시 되지만, 문자열 (빼기, 곱하기,나누기) 숫자타입에서는 숫자타입으로 변환을 시도한다.
- 단항 연산자 +에서 피연산자가 숫자가 아니면 숫자로 바뀐다.
- 불리언 타입으로 변환되는 경우는 if() 안에 불리언 타입이 아닐 때 불리언으로 바꾸니다.
- 단축 평가 : 'Cat' && 'Dog'에서 Cat만으로는 표현식의 결과가 결정되지 않는다. 'Dog'가 논리 연산의 결과를 결정한다. 결과는 'Dog'를 그대로 반환한다. 'Cat'||'Dog' 는 'Cat'을 반환한다. 즉 읽다가 논리 값이 결정되면 결정된 순간의 마지막 토큰을 반환한다.
단축평가
인 이유는 반환시점에서 평가 결과가 결정되었으므로 더 이상 논리 연산을 진행하지 않기 때문이다. - 단축평가는 undefined 또는 null인지 확인하고 참조할 때 패턴으로 쓰이기도 한다.
const var = null;
const value = elem && elem.value
//but optional chaining is more useful!
- ⭐⭐⭐ null 병합 연산자 : 좌항이 null일 경우 우항 할당해준다.
||
를 통해 단축평가로 할당을 할 때와 다른 점은 '' 나 0 이 유효한 값이라면 예기치 못한 동작을 발생시킬 수 있는 반면, null병합연산자에서는 그럴 일이 없다는 것이다.
10. 객체 리터럴 : 객체는 데이터프로퍼티 + 접근자 프로퍼티 + 메소드로 구성
- 원시 타입의 값은 변경 불가능하지만 객체 타입의 값은 변경이 가능하다.
- 자바스크립트에서 사용할 수 있는 모든 값은 프로퍼티 값이 될 수 있다.
- 객체는 중괄호 안에 프로퍼티와 메서드로 구성되어 있으며, 자바스크립트에서 사용할 수 있는 모든 값은 프로퍼티 값이 될 수 있다. 값에 원시 타입의 값이 아닌 함수가 올 경우 메서드라고 한다.
- 객체지향 프로그래밍이란 객체의 집합으로 프로그램을 표현하려는 패러다임이다.
- 객체리터럴은 0개 이상의 프로퍼티로 구성이 되며, 표현식(값)이므로 세미콜론을 붙인다. 코드블록과 다르다.
- 객체의 프로퍼티 키는 기본적으로 문자열이며, 네이밍 규칙을 따르면 따옴표를 생략해도 된다. 그러나 네이밍 규칙에 어긋나는 경우 따옴표를 사용한다.
- 동적으로 프로퍼티 키를 생성할 수 있다. 파이썬의 딕셔너리와 방법이 비슷하다.
- 중복 키 선언 시 덮어써진다.
- 프로퍼티에 접근 하는 방법은 두 가지로, 마침표 접근법과 대괄호 접근법이 있다. 대괄호로 접근 시
따옴표를 꼭 붙여야한다.
프로퍼티 키가 식별자 네이밍 규칙을 준수하지 않는 경우, 대괄호 표기법을 이용해서 접근해야한다.
var person ={
'last-name':'Lee',
1:10
}
person.last-name;
// 브라우저 환경 : NaN. (global 객체 window에 name property가 있다.)
// Node.js 환경 : ReferenceError : name is not defined
- 객체에 존재하지 않는 프로퍼티에 접근하면 undefined를 반환한다.name이 정의되어 있어도 person[name]은 ReferrenceError이다(name이 프로퍼티 키가 아닌 식별자로 인식되기 때문)
- 존재하지 않는 프로퍼티에 값 할당 시 동적 생성이 된다.
- 삭제는
delete
연산자를 이용하면 된다. - ES6에서 추가된 기능으로, 프로퍼티 값을 변수 값으로 줄 때 키를 변수명과 동일하게 할 경우 변수명 명시를 생략할 수 있다.
- 프로퍼티 키 이름을 정할 때, 대괄호를 이용해서 계산된 프로퍼티 키 이름을 쓸 수 있다.
- 🌟🌟🌟🌟 ES6에서는 메서드에서 function()기호를 생략할 수 있다. ES6메서드라 함은 객체 내부에 function이 생략된 함수(축약된)만을 의미한다. 이는 프로퍼티에 할당한 함수와 다르게 동작한다!!
11. 원시 값과 객체의 비교
- 메모리 측면에서, 원시 값은 실제 값이 저장되지만, 객체는 변수에 참조 값이 저장된다.
- 변수에 변수를 할당할 때, 원시 값은 pass by value(원시 값이 복사되어 전달)로, 객체는 pass by reference(참조 값이 복사되어 전달)로 전달이 된다.
- 변수가 불가능한 것이 아니라 원시 값이 불가능하다. 변수는 교체가 가능하다. 원시값을 재할당 하는 것은 새로운 메모리 공간을 필요로 한다.
- 숫자 자료형은 숫자크기와 상관없이 8바이트이지만 문장열은 한글자당 2바이트이다.
- 문자열은 유사배열 객체여서 순회가 가능하다.
- 문자열 또한 원시값으로 인덱스를 이용해 일부 변경하는 것이 불가능하다. 그것은 재할당하는 것이기 때문이다.
- 변수에 변수를 할당하면 값이 복사되어 전달되지만, 두 변수는 다른 메모리에 위치한다.
- 엄밀히 말하자면 변수에는 값이 아니라 메모리 주소가 저장되어있다. 식별자는 메모리 주소에 붙인 이름이라고 할 수 있다.
- 즉 엄밀히 말하면 pass by value는 틀린 말이다. 변수에는 값이 아니라 메모리 주소가 전달되기 때문이다. 단지 변수에 변수 할당 시 메모리에 저장된 값이 원시값이면, 새로운 메모리 주소에 그 값을 담고 그 메모리가 다시 변수에 할당된다. 두 메모리가 별개로 관리된다는 사실이 중요하다.
- 자바 C++은 개체 생성 전 메서드와 프로퍼티가 정해져있어 객체 생성 이후 삭제/추가가 불가능하지만 자바스크립트는 가능하다. V8엔진에서는 히든 클래스를 이용해서 c++객체에 근접하는 성능을 보장한다.
- 원시값의 경우, 변수는 값을 갖는다고 표현하지만 객체의 경우 변수는 객체를 참조하고 있다는 표현이 더 적절하다.
- 객체는 변경이 가능한데, 참조값은 그대로이지만 실제 객체의 프로퍼티는 변경이 된다.
- 객체가 수정가능한 방식으로 관리되는 이유는 그것이 더 효율적이기 때문이다.
- 복사는 기본적으로 원본과 다른 객체를 만드는 것이고, 스프레드 문법과 객체리터럴로 새로운 객체를 만들면 전체 객체는 참조값이 다르지만 안의 프로퍼티는 실질적으로 같다. 깊은 복사를 이용하려면 lodash의 cloneDeep을 이용하여야 한다.
복사
란 원본과 독립적으로 움직이는 객체이다. - 변수에다가 객체를 복사하면 얕은복사가 되는데, 가리키는 객체가 같은 객체(동일주소)가 된다.
- 자바스크립트에는 값에의한 전달, 참조에의한 전달이라는 개념을 구분하는 게 무의미하다. 그냥 식별자가 기억하는 메모리에 담긴 값이 복사가 되는 것이다. 단지 변수에 원시 값이 할당되면 그 변수에 해당하는 메모리에 원시 값이 있고, 객체가 할당되어있으면 객체의 주소가 담겨있을 뿐이다. 변수 복사는 그냥 변수에 담긴 내용을 전달한다.
12. 함수
- 함수는 객체이며, 일반 객체와 다르게
호출
할 수 있다 - 함수 리터럴은 함수 이름 생략이 가능하지만, 함수 선언문은 불가능하다.
리터럴은 변수에 담기 전, 우변에 있는 상태로 이해해보자!또는 괄호, 세미콜론과 같이 엮여있으면 문 대신 리터럴로 해석이 된다.
- new Function ('x','y','return x+y')처럼 Function 생성자 함수로 만들 수도 있다.
- 같은 함수 리터럴이더라도 단독으로 사용되면 선언문으로 평가되지만, 그룹 연산자 () 안에 넣는다던지, 대입연산자의 피연산자로 활용한다던지 하면 함수 리터럴로 평가된다.
- 🌟🌟🌟🌟🌟원칙적으로 함수 이름은 함수 몸체 안에서만 참조할 수 있는 식별자이지만,
함수 선언문에서는 암묵적으로 자바스크립트 엔진이 함수 이름으로 식별자를 생성한다.
(호출하기 위해)따라서 함수 리터럴로 함수가 만들어진 경우 이름을 통한 호출이 불가능하지만 선언문을 통해 함수가 정의된 경우 함수를 이름으로 호출할 수 있다.엄밀히 말하면 이름으로 호출하는 것이 아닌 함수 객체를 가리키는 식별자(자바스크립트 엔진이 함수 호출을 위해, 함수 이름과 동일하게 만든)로 호출
이 된다. - 자바스크립트 함수는 일급 객체로 값처럼 자유롭게 사용할 수 있다
- 함수를 사용할 때는 함수 이름이 아니라 함수 객체를 가리키는 식별자를 사용해야한다.
- 🌟🌟🌟🌟🌟함수도 변수와 마찬가지로 런타임 이전에 자바스크립트 엔진이 돌면서 선언문으로 객체를 생성하고 함수 이름과 같은 식별자에 할당한다. 변수는 선언문 이전에 참조하면 undefined로 평가받지만 함수 선언문으로 정의한 함수는 선언문 이전에 호출이 가능하다. 그러나 선언문 방식이 아니라 함수 표현식으로 함수를 정의하면
변수 호이스팅
과 똑같이 동작해 undefined를 출력하게 된다 호이스팅의 조건은 할당이 아닌 선언이다1!! - 화살표 함수는 문법만 간소화된 것이 아니다. 생성자 함수로 사용할 수 없고, 기존 함수와 this 바인딩 방식이 다르고, prototype 프로퍼티가 없고, arguments 객체를 생성하지 않는다.
- 함수 선언문은 호출 전 선언이라는 규칙을 무시하기 때문에 표현식 사용이 권고된다.
- function 생성자 함수는 new를 이용하는데 정확히 동작하지 않을 수 있다.
- 함수에서 매개변수는 몸체 내부에서 변수와 동일하게 작동한다. 호출부 안에서 생성이 되고 undefined로 초기화, 이후 값이 순서에 의해 할당된다.
- 🌟🌟🌟🌟🌟자바스크립트는 매개변수와 인수의 갯수차이 체크나 타입 일치를 체크하지 않는다. 함수 선언시 매개변수는 이미 선언이 된 상태이며, 인수를 통해 할당이 되는 것이다. 모자르다면 undefined 상태로 연산이 진행되고, 초과된 경우 arguments 객체의 프로퍼티로 보관된다.
- 매개 변수는 최대 3개까지만, 이를 넘어가면 객체를 전달하는 것이 유리하다.
- return; 은 undefined를 반환한다.
- 🌟🌟🌟 함수에 객체를 인수로 준다는 것은, 곧 그 객체의 참조값을 준다는 것과 같다 즉, 함수 안에서 객체를 변경시키는 로직이 수행되면 실제 외부 객체가 영향을 받는다.
- return 개행 후 반환 값 작성시 세미콜론 자동 삽입 기능에 의해 에러.
- 즉시실행함수가 괄호로 묶고 호출하는 이유는, 함수리터럴을 먼저 평가해서 함수 객체를 생성하기 위함이다.
function foo (){}(); 가 에러가 나는 이뉴는 괄호가 그룹연산자로 평가되고
피연산자가 없기 때문이다.
재귀함수를 짤 때 함수 몸체 내부에서 호출 시 함수의 이름을 쓸 수도, 그 함수를 가리키는 식별자를 쓸 수도 있다.
중첩함수는 외부 함수 내부에서만 호출할 수 있다.
콜백함수란 다른 함수의 매개변수를 통해 그 내부로 전달되는 함수이다. callback 뜻이 an occasion when someone is asked to come back, especially for another job interview
계속 다시 불리기 때문인듯.
ES6부터는 함수는 문이 존재할 수 있는 어디에서든 정의가 가능하다.
자바스크립트에서 함수는 일급 객체이기 때문에 매개변수로도 전달이 가능하다. 매개변수로 전달되는 함수를 콜백함수라고 하고, 콜백함수를 전달받은 함수를 고차함수라고 한다.
콜백 함수의 호출 시점은 일고차 함수에 의해 호출 시점이 결정된다.
비순수함수: 외부의 어떤 것을 바꾸고, 외부 상태에 의존적인 함수
13. 스코프
- 모든 식별자(변수,함수,클래스)는 자신이 선언된 위치에 의해 다른코드가 자신을 참조할 수 있는 유효범위가 결정된다.
- 자바스크립트 엔진은 코드 실행 시 코드의 문맥을 고려한다.
- ⭐코드가 어디서 실행되며 주변에 어떤 코드가 있는지를 렉시컬 환경이라고 부른다
- 식별자는 파일명과 같다. 다른 폴더 안에 동일한 파일 이름이 있어도 충돌이 일어나지 않는 것을 생각해보자. 식별자는 해당 스코프(네임스페이스)안에서 유일해야 한다.
- ⭐⭐⭐var 키워드의 단점 중 하나로, 같은 스코프 안에서 중복 선언이 허용된다.
- ⭐⭐⭐변수는 자신이 선언된 위치에 의해 스코프가 결정된다. 전역변수는 전체에서 유효하며, 지역변수는 자신의 지역스코프와 그 하위 지역스코프에서 유효하다.
- 함수의 몸체 내부에서 정의한 함수를 중첩함수라고 한다. 스코프가 중첩함수에 의해 계층적 구조를 갖게 되는데 이를 스코프 체인이라고 한다.
- 최상위 스코프는 전역 스코프이다.
- ⭐변수를 참조할 때 자바스크립트 엔진은 변수를 참조하는 코드의 스코프부터 상위스코프 방향으로 선언된 변수를 검색한다.
- ❓렉시컬 환경은 스코프 안의 식별자를 모아놓은 자료구조이다.(?) 변수 선언 시 렉시컬 환경에 식별자가 키로 등록되고, 할당이 일어나면 값을 변경한다.
- 렉시컬 환경을 연결리스트처럼 연결시킨 것이 스코프 체인이다.
- 전역 레시컬 환경은 코드가 로드되면 곧바로 생성되고, 렉시컬 환경은 함수가 호출되면 곧바로 생성된다.
- 변수에만 스코프 개념이 적용되는 것이 아니라, 식별자(함수 등)를 검색할 때 스코프 개념을 적용시켜 검색하는 것이다.
- ⭐⭐⭐C나 자바 등 대부분의 프로그래밍 언어는 모든 코드블록(중괄호를 쓰는, if문 등..)이 지역스코프를 만든다. 이러한 특성을 블록 레벨 스코프라고 한다. 그러나 var키워드로 선언된 변수는 오로지 함수의 코드 블록 만을 지역스코프로 인정한다.
var i = 10;
for (var i = 0; i < 5; i++){
console.log(i);
}
console.log(i); //5
// for 문이 함수레벨 스코프가 아니라 전역 스코프에 해당하기 때문에
// 사실상 변수를 재할당 한 것과 같은 효과가 일어난다.
- 자바스크립트는 정적스코프(렉시컬 스코프)를 따른다. 함수가 어디서 정의되었는지에 의해 상위스코프가 결정된다.
var x = 1;
function foo(){
var x = 10;
bar();
}
function bar(){
console.log(x);
}
foo(); // 여기서는 10인지 1인지! 어디로 타고 가야하나.
bar(); // 여기서는 1이 확실해보인다.
- bar의 상위 스코프가 어디인 지가 관건이다. 만약 함수가 정의된 시점을 기준으로 한다면 bar에서는 1을 출력해야 할 것이고, 호출된 시점을 기준으로 한다면 foo() 가 10을 리턴해야 할 것이다.
- 함수가 호출된 위치는 상위 스코프 결정에 어떠한 영향도 주지 않는다
- 결과적으로 1을 두 번 출력한다.
14. 전역 변수의 문제점
- 전역 범위에서 호이스팅은 전역 변수나 함수를 대상으로 한다. 함수 내에 있는 변수 - 즉 지역 변수의 선언은 함수가 호출이 되어야 실행된다. 즉, 지역변수의 생명주기는 함수호출과 같이한다.
- 함수가 생성한 스코프는 렉시컬 스코프라 부르는 물리적 실체가 있고, 변수는 자신이 등록된 스코프가 메모리에서 해제될 때까지 유효하다. 할당된 메모리 공간은 누구도 참조하지 않을 때 가비지 콜렉터에 의해 해제된다.
- 단, 함수가 종료되어도 누군가 변수를 참조하고 있다면 변수가 살아남는다.
- 호이스팅은 스코프 단위로 동작한다
var x= 'global'
function foo(){
console.log(x);// 여기서, 호이스팅에 의해 선언은 된 상태이므로 undefined출력
var x= 'local'
}
foo();
console.log(x);//global
- 브라우저 환경에서 전역 객체는 윈도우로, 전역변수와 생명주기를 같이한다. 전역변수는 전역 객체의 프로퍼티이다.
- 전역 변수의 문제점:
- 생명주기가 길고, 스코프가 넓은만큼 여기저기서 훼손되고 의도치 않은 재할당이 일어나기 쉽다.
- 스코프 체인 상에서 종점에 존재하므로 검색 속도가 가장 느리다.
- 파일이 분리되어 있다고 해도 전역스코프를 공유한다는 점이다. 다른 파일에 동일한 이름의 전역변수가 있으면 의도치 않은 결과가 나올 수 있다.
- 대안
- 즉시실행함수: 즉시실행함수로 코드를 감싸면 모든 변수는 즉시실행함수의 지역 변수가 된다.
- 네임스페이스 객체: 네임스페이스 역할을 담당할 객체를 생성하고 전역변수처럼 사용하고 싶은 변수를 프로퍼티로 추가한다.
- 모듈 패턴: 클래스를 모방해서 관련있는 변수와 함수를 즉시실행함수로 감싼다. 그러면 메소드를 통해서만 필드를 조작할 수 있다.
- ES6모듈: 독자적인 모듈 스코프를 제공한다. 자세한 건 48장에서.
15. let, const와 블록레벨 스코프
호이스팅 → 선언문 이전에 선언이 됨. 런타임 이전에 암묵적 평가때문. 그러나 초기화여부는 변수 키워드에 따라 다르다.
- 유데미 클린코딩 강의 중, global 첫 출력이 undefined이다. 선언단계에서 undefined로 초기화 되기 때문이다.
- let은 재선언 금지, 블록레벨스코프,
호이스팅이 안되는 것처럼 보이는
특징이 있다. - var는 재선언이 가능하고 함수 레벨 스코프이기 때문에, 블록 안에서 재선언을 하면 값이 손상된다.
- let으로 선언한 변수를 선언문 이전에 참조하면 참조 에러가 발생한다.
- var로 선언한 변수는 선언 단계와 초기화 단계가 한번에 진행되지만(바로 undefined로 초기화), let은 초기화 단계와 선언 단계가 분리되어 진행된다.
- 선언 단계는 런타임 이전에 자바스크립트 엔진에 의해 암묵적으로 실행된다. 그러나 선언이 되었다고해서 참조할 수 있는 것은 아니다. undefined로 초기화를 해줘야 참조할 수 있는 것이다.
- 실제 런타임에서 변수 선언문 자리까지 코드를 읽어야 초기화 단계가 실행된다. 그리고 할당 문에서는 할당 단계가 실행된다.
- 아래 코드를 보면,
let foo =1;
{
console.log(foo);
let foo = 2;
}
전역 변수가 1이 나오는 것이 아니라 참조 에러가 나온다.
호이스팅의 정의 자체가, 선언문 이전에 선언이 되는 것이지, 선언문 이전에 초기화 되는 것이 아니다!
- var, let, const... 모두 호이스팅이 적용되지만 적용되지 않는 것처럼 우리가 인식하는 이유는(var말고 대상에 대해 ) 호이스팅이 참조 가능해야 하는 것처럼 느끼기 때문이다. 선언이 되어있으면 호이스팅이 된 것이다.
- var 로 선언한 변수는 전역 객체의 일부가 되어 window.변수로 접근이 가능하지만, let으로 선언한 변수는 보이지 않는 개념적인 블록(전역 렉시컬 환경의 선언적 환경 레코드)에 존재하게 된다.
- const 키워드는 반드시 선언과 동시에 초기화해야한다.
- const 키워드로 선언된 변수에 원시 값을 할당하면, 원시 값은 변경할 수 없는 값이고 const키워드에 의해 재할당이 금지되므로 할당된 값을 변경할 수 없다.
- 그러나 const에 객체를 할당하면, 객체 자체는 “조작이 가능하기 때문에” 재할당이 아니라, 값을 변경할 방법이 있다.
- 재할당은 메모리 주소를 교체하는 걸 의미한다.
- var 키워드로 선언하지 않아도 바로 foo=2 등으로 쓸 수 있는데 이는 암묵적 전역의 프로퍼티가 된다
16. 프로퍼티 어트리뷰트
객체는 프로퍼티로 이루어져있고, 프로퍼티는 데이터 프로퍼티와 접근자 프로퍼티로 나뉜다. 모든 객체는 prototype이라는 내부 슬롯(=의사 프로퍼티 (≠ 프로퍼티))를 가지며, 여기 접근하려면 proto를 이용해야한다. 프로퍼티 생성시 어트리뷰트도 자동으로 초기화된다.
내부슬롯 = 비공개 프로퍼티 =내부 상태 값 =[[괄호두개]] 정도로 이해하자.
특정 값으로 연결되는 내부 슬롯, 특정 동작을 수행하는 내부 메소드가 있고
모두 괄호 두개로 감싸서 표현한다.
그런데 내부슬롯은 객체의 프로퍼티가 아니다. 자바스크립트 엔진 내부의 로직이다.
프로퍼티 = 데이터 프로퍼티 + 접근자 프로퍼티
보편적으로 알고 있는 프로퍼티는 데이터 프로퍼티이고,
접근자프로퍼티는 get, set으로 정의되는 함수이다. getter나 setter 중 하나로만 정의될 수 있다.
객체 내에서 get 함수이름 () {} 으로 정의되고(이 함수를 getter라한다) 객체.함수이름 으로 사용한다.
set 함수이름 (입력값){} 으로 정의되고 객체.함수이름 = 입력값 으로 사용한다.
프로퍼티 어트리뷰트: 프로퍼티 자체에 있는 속성으로
데이터 프로퍼티 어트리뷰트는 value, writable, enumerable, configurable이 있다.
접근자 프로퍼티 어트리뷰트는 Get, Set, Enumerable, Configurable이 있다.
- 모든 객체는 프로토타입이라는 내부 슬롯을 갖는다.
__proto__
로 간접 접근이 가능하다. - 내부 슬롯은 자바스크립트 엔진 내부의 로직이므로 원칙적으로 접근할 수 없지만, 일부 내부 슬롯은 접근할 방법이 있고, 프로토타입도 그 중 하나이다.
- 자바스크립트엔진은 프로퍼티 생성시 프로퍼티 어트리뷰트를 기본값으로 정의한다. 프로퍼티 어트리뷰트란 내부슬롯 [[Value]], [[Writable]], [[Enumerable]], [[Configurable]]을 의미한다. 프로퍼티마다 각각 값, 쓰기 가능여부, 열거가능여부(for ... in), 재정의가능 여부 등등이 있는 것이다.
Object.getOwnPropertyDescriptor
메소드를 이용하면 그 프로퍼티에 대한 프로퍼티 어트리뷰트를 알 수 있다. 이 함수는 프로퍼티 디스크립터라는 객체를 반환한다.- 예시
const person ={
firstName : 'Ungmo',
lastName : 'Lee',
get fullName(){
return `${this.firstName} ${this.lastName}`};
},
set fullName(name){
[this.firstName, this.lastName] = name.split(' ');
}
};
- fullName으로 프로퍼티 값에 접근하면 내부적으로 [[Get]] 내부 메서드가 호출되어 다음과 같이 동작한다.
- 프로퍼티 키가 유효한지 확인한다. 문자열 또는 심벌이어야 하는데, fullName은 문자열이므로 유효하다.
- 프로토타입 체인에서 프로퍼티를 검색한다. person 객체에 fullName 프로퍼티가 존재한다.
- fullName 프로퍼티가 데이터 프로퍼티인지, 접근자 프로퍼티인지 확인한다. 접근자 프로퍼티이다.
- [[Get]]의 값인 getter 함수를 호출하여 결과를 반환한다.
Object.defineProperty
를 통해서 직접 프로퍼티의 어트리뷰트를 정의할 수도 있다.- 객체를 밀봉하거나 동결시킬 수 있다. Object.seal은 객체를 밀봉시켜 읽/쓰기만 가능하도록 만들고 Object.freeze는 객체를 읽기 전용으로 만든다.
- 객체의 밀봉/동결/확장여부는 Object.isFrozen/isSealed/isExtensible 등으로 확인이 가능하다.
- Object.freeze는 얕은 동결이기 때문에, 재귀호출로 동결시켜야 완벽히 동결시킬 수 있다.
17. 생성자 함수에 의한 객체 생성
\1. 리터럴 이외에도 new + Object 생성자함수로 객체를 생성하고, 프로퍼티와 메서드를 추가할 수 있다.
\2. 리터럴 방식은 비슷한 객체를 여러개만들기에는 너무 노가다이므로, 생성자 함수를 새로 만들고, 간편하게 객체를 만들 수 있다. 비슷한 속성을 갖는 객체를 여러 개 만들 때, 생성자 함수를 정해놓고 거기에 new를 붙여서 쓴다.
\3. this는 어떻게 쓰였는지에 따라 전역객체(window), 메서드를 호출한 객체(메서드로 호출 시), 생성될 객체(생성자함수) 등을 가리킨다. 자세한 건 22장에서!
⭐⭐⭐⭐4. new 와 결합된 것이 아니라 그냥 생성자 함수가 일반함수처럼 쓰이면, 객체가 생성되지 않고, 생성자 함수 안의 this는 전역 객체를 가리키게 된다. 생성자 함수도 일반 함수와 같지만, this 덕분에 new 연산을 할 수 있는 추가 기능이 달린 것!
바인딩이란 식별자와 값을 연결하는 과정이다.
⭐⭐⭐⭐5. 생성자 함수 안에는 객체 생성과정이 없으나 자바스크립트엔진이 이를 암묵적으로 수행한다. new 연산자 + 생성자 함수로 객체 생성 시 암묵적으로 빈 객체를 생성하고, this에 빈 객체가 바인딩되고, 그것이 반환된다. 초기화는 개발자의 몫이다. 그리고 생성자 함수 종료 시 암묵적으로 this가 반환된다.단, return을 쓰면 묵시적 반환이 무시된다.
- 함수는 객체이면서
호출이 가능
하기 때문에 추가적인 내부슬롯과 메서드를 가지고 있다. 함수 객체만을 위해서 Environment, FormalParameters 등의 내부 슬롯과 Call, Construct 등의 내부 메서드가 있다. - 함수는 반드시 callable하다. 함수는 constructor와 nonconstructor로 나뉜다.
- ECMAScript 사양에서 메서드란 메서드 축약 표현만을 의미한다.
일반 표현
var obj = {
sayHi : function(){
}
}
축약표현
const obj = {
sayHi(){
}
}
- ⭐⭐⭐⭐함수선언, 함수표현식, 프로퍼티로 쓰인 메서드는 일반 함수 =>constructable이다.
축약표현이나 화살표함수는 non-constructable.
- costructor 함수에 new 키워드를 쓰면 객체의 내부 메서드 construct가 호출되고, 그냥 호출하면 call이 호출된다.
- 어떤 함수든 생성방식에 따라 표현식이나 선언으로 생성되었다면 new 키워드와 상호작용할 수 있으나, 함수의 리턴 값이 객체 타입이 아니라면 그것이 무시되고 빈 객체가 반환된다.
- 생성자 함수로 작동할 수 있더라도 new키워드가 없으면 일반함수로 동작하고, 리턴문이 없으면 undefined를 반환한다. 또한 this는 window로 작동하여 window의 프로퍼티가 초기화된다.
- new.target은 함수 내부에서 이 함수가 new의 target인지 여부를 반환하는 함수이다. 이를 통해 생성자함수로 호출되었을 때와 일반 함수로 호출되었을 때의 동작을 분기할 수 있다.
- new.target을 지원하지 않는다면 intanceof 함수명을 통해 스코프세이프 패턴을 적용할 수 있다.
- Object, Functiuon 생성자 함수는 new 키워드 없이도 잘 작동하지만, 그렇지 못한 함수도 있다.
18. 함수와 일급 객체
일급 객체의 정의 : 객체와 동일하게 사용할 수 있다. ( 객체도 무명의 리터럴로 사용가능하며, 값으로 전달 가능)
- 무명의 리터럴로 생성할 수 있다. 즉, function 이름없이 (인자) {} 로 생성 가능
- 변수나 자료구조에 저장할 수 있다.
- 함수의 매개변수에 전달할 수 있다.
- 함수의 반환값으로 사용할 수 있다.
c, d 의 장점으로 인해서 함수형 프로그래밍을 할 수 있게 된다.
__proto__
는 Object.prototype의 접근자 프로퍼티이다.arguments
프로퍼티는 함수 내부에서 객체처럼 사용할 수 있다. 키를 인수의 순서(0,1,2..와 같은 인덱스), 값을 인수의 값으로 사용하는 객체이다.arguments 객체의 Symbol 프로퍼티는 arguments 객체를 순회 가능한 자료구조인 이터러블로 만들기 위한 프로퍼티이다. Symbol.iterator를 프로퍼티 키로 사용한 메서드를 구현하는 것에 의해 이터러블이 된다. 자세한 것은 34장 참조.
인자가 가변적일 때 순회용으로 arguments를 쓰면 좋다.
⭐ 이터러블 : 순회 가능한 자료구조. 유사배열객체와 다르다.
자바스크립트의 배열의 요소는 동일한 크기의 메모리 공간은 갖지 않아도 되며, 연속적으로 이어져 있지 않을 수도 있다. 즉, 원시값, 객체, 함수, 배열 등 어떤 값이든 모두 배열의 요소가 될 수 있다. 이러한 배열을 희소 배열(Sparse Array)이라 한다.
자바스크립트에서 배열은 쉽게말해 array 기반 고차함수가 가능한 녀석들을 말하고, 유사배열객체란 length와 순회만 가능한 객체를 말한다.
이터러블 객체
Symbol(심벌)은 변경 불가능한 원시 타입이다. 심벌은 중복되지 않는 고유 값을 갖기 때문에 기존 코드에 영향을 주지 않고 새로운 프로퍼티를 추가하기 위해 사용된다. React에서도 JSX를 생성할때 Symbol을 사용하고 있다. (참고: Why Do React Elements Have a $$typeof Property?)
이 외, Symbol은 ES6에서 추가된 데이터 타입으로 브라우저 console 창에서도 바로 확인할 수 있다.
Symbol 프로퍼티
이러한 프로퍼티들을 ECMAScript에서는 Well-Known Symbol이라 부르며, 자바스크립트 내부 알고리즘에서 사용된다.
위 이미지에서 볼 수 있듯이, Symbol에는
Symbol.iterator
메서드가 있는데, 이는 Array, String, Map, Set, TypedArray, arguments, DOM 컬렉션(NodeList, HTMLCollection)과 같은 이터러블 객체를for...of
문으로 순회할 수 있다. 이터러블 객체는 스프레드 문법과 배열 구조 분해가 가능하다.const num = [1, 2, 3] // 이터러블 객체 console.log(Symbol.iterator in num) // output: true // for...of문 for (const n of num) { console.log(n) // output: 1 2 3 } // 스프레드 문법과 배열 구조 분해 console.log([0, ...num]) // output: [0, 1, 2, 3]
하지만 유사 배열 객체는 이터러블 객체가 아닌 일반 객체이므로
for...of
문으로 순회할 수 없고, 배열 구조 분해도 불가하다. 하지만 객체 리터럴 내부에서는 스프레드 문법 사용이 가능하다. (단, arguments, NodeList, HTMLCollection은 유사 배열 객체이면서도 이터러블이다)
arguments는 배열이 아니기 때문에 Function.prototype.call , Function.prototype.apply 를 통해 배열을 만들고 간접호출해야 한다. 또는 rest parameter를 쓸 수도 있다.
caller : 자신을 호출한 함수를 가리킨다.
length: 매개변수의 개수를 가리킨다.
name 함수의 이름을 가리킨다. ⭐함수의 이름과 함수가 할당된 변수는 다르다.
⭐ constructor 라면 prototype 프로퍼티를 갖는다.
19. 프로토타입
- 객체지향 프로그래밍: 프로그램을 명령형의 절차지향적 관점이 아니라 독립적 객체의 집합으로 보는 것.
- 추상화: 필요한 속성만 간추려내는 작업. (나에게는 여러 속성이 있지만 이름나이만 추려냄)
- 속성을 통해 여러 개의 값을 하나의 단위로 구성한 자료구조를 객체라 한다.
- 상태를 나타내는 데이터, 그 데이터를 조작할 수 있는 동작을 하나의 논리적 단위로 묶어 생각.
- 객체 내부의 프로퍼티 중 데이터는 객체 고유의 특성이지만 메소드는 객체끼리 일치할 경우가 있는데(원이 객체고 넓이를 반환하는 getArea메소드를 생각해보자), 중복이 생기면 메모리가 낭비된다. 이를 프로토타입을 기반으로 상속을 통해 해결한다.
프로토타입은 생성자 함수의 프로퍼티이다.
그리고 동시에 모든 인스턴스의 부모이기도 하다.
여기서 잠깐. 생성자함수인지 아닌지는 ?
함수 선언문
function foo(){}
함수 표현식
const bar = function foo(){}
클래스
일 경우 생성자함수이다.
- 생성자 함수가 생성한 인스턴스가 모두 메서드를 공유하도록 프로토 타입에 추가하려면, 생성자함수.prototype.메소드이름으로 접근해서 정의할 수 있다
Circle.prototype.getArea = function () {
return Math.PI = this.radius ** 2;
};
- 모든 객체는 [[prototype]] 내부 슬롯을 가지며 내부슬롯의 값은 프로토타입의 참조(주소)이다, 생성될 때 객체 생성방식에 의해 값이 결정된다.
- 객체의 경우 객체 리터럴에 의해 생성된 객체의 프로토타입은 Object.prototype이고 생성자 함수에 의해 생성된 객체의 프로토타입은 위 원에서 정의한 것처럼 생성자 함수의 prototype에 바인딩 되어 있는 객체이다.
- ⭐프로토타입은 클로저처럼 직접 건드릴 수는 없다. 즉 객체.prototype으로 접근할 수는 없다. 그러나 생성자함수는 생성자함수.prototype으로 접근할 수 있다. 무엇이 더 상위레벨인지를 생각해보자. 생성자함수가 먼저 있고, 그 다음 프로토타입이 있고 그다음 객체가 있는 것이다. 하위에서 상위를 직접참조할 수 없다고 생각하자.
__ proto __
라는 접근자 프로퍼티를 이용한다. 접근자 프로퍼티는 get set 키워드로 정의되는 함수인데, 외부에서 쓸 때는 마치 값처럼 보인다. 할당문의 왼쪽에서 프로퍼티가 사용되면 setter 함수가 호출되고, 값을 줘야 하는 상황이면 getter가 호출된다.
- ⭐⭐⭐생성자함수 → 프로토타입 → 객체 순으로 이어져있다. 생성자함수에서 프로토타입에 접근 시 생성자함수.prototype으로 접근이 가능하고 프로토타입에서 생성자 함수로 접근할 때는 .constructor로 접근한다. 객체에서는 접근자 프로퍼티를 이용한 간접 접근(객체 자신의 내부슬롯롯이 프로토타입을 가리키고 있다. 내부슬롯의 값이 곧 프로토타입이다)이 가능하다.
- ⭐⭐⭐⭐⭐⭐즉 어떤 객체의 프로토타입에 접근할 때는 생성자함수로부터 접근하거나
__proto__
를 이용해 객체로부터 접근해야한다 - 내부슬롯과 내부메소드는 정식 프로퍼티가 아니다. 의사 프로퍼티이다.
- ⭐⭐⭐
__proto__
접근자 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아니라 Object.prototype의 프로퍼티이다. (여기서 Object는 객체를 생성하는 생성자함수) 프로토타입이 자신에게 접근하라고 내어준 접근자 프로퍼티라고 생각하자. 그리고 접근자 프로퍼티는 특정 값에 접근하기 위한 함수라는 것을 잊지 말자. [[Prototype]]의 값은 실제 프로토타입이다.
const person = {name:'Lee'};
console.log(person.hasOwnProperty('__proto__')); //false
console.log({}.__proto__===Object.prototype); //true
- ⭐⭐⭐⭐⭐⭐ 굳이 접근자 프로퍼티를 통해서 프로토타입을 get set하게 만들어 놓은 이유는, 직접 할당이 가능하면 상호참조로 프로토타입 체인이 무한 루프가 될 위험이 있기 때문이다. 따라서 접근자 프로퍼티를 통해 무한루프가 생길 시 에러를 반환하도록 안전 장치를 마련해 놓은 것이다.
__proto__
에 접근할 수 없는 경우도 있기 때문에, 코드 내에서 직접 사용하는 것은 지양하자. 대신,Object.getPrototypeOf
메서드를 이용해서 얻어올 수 있고,setPrototypeOf
를 이용해서 정할 수도 있다.- ⭐⭐⭐함수객체만은 prototype 프로퍼티를 갖는다. 이 함수가 생성자 함수로 쓰였을 때 그 함수를 통해 생성되는 인스턴스의 프로토타입을 가리킨다.
- ⭐17.2.5장 참고하면, non-constructor인 화살표함수와 ES6메서드 축약 표현으로 정의한 메서드는 prototype 프로퍼티를 소유하지 않으며 프로토타입도 생성하지 않는다. 즉, function 키워드로 정의된 생성자 함수가 프로토타입을 만든다.
// 화살표 함수는 non-constructor이다.
const Person = name =>{
this.name = name;
};
console.log(Person.hasOwnProperty('prototype')); //false
//ES6의 메서드 축약 표현도 마찬가지이다.
const obj = {
foo(){}
};
console.log(obj.foo.hasOwnProperty('prototype')); //false
- ⭐⭐ 모든 프로토타입은 .constructor 프로퍼티를 통해서 생성자 함수에 접근할 수 있다. 생성자 함수를 통해 생성된 인스턴스 또한 프로토타입의 프로퍼티를 쓸 수 있기 때문에, 인스턴스에서도 .constructor로 생성자 함수에 접근이 가능하다!
- 즉, 프로토타입은 proto 라는 접근자 프로퍼티와 .constructor라는 프로퍼티룰 객체로 상속해준다.
- new.target이란?(17.2.7)
- 생성자 함수가 new 연산자 없이 호출되는 것을 방지하기 위해 ES6에서 지원하는 기능. 함수 내부에서 new.target을 이용하면 new연산자와 함께 생성자 함수로써 호출되었는지 확인할 수 있다. 만약 new 연산자와 함께 호출되었을 시 new.target은 함수 자기 자신이고, 일반함수로서 호출되었다면 undefined이다.
- Object 생성자 함수에 인수를 전달하지 않거나 undefined 또는 null을 인수로 전달하면서 호출하면 내부적으로는 추상 연산 OrdinaryObjectCreate를 호출하여 Object.prototype을 프로토타입으로 갖는 빈 객체를 생성한다.
- 반면에 객체리터럴로 객체를 생성할 때는 OrdinaryObjectCreate를 호출하여 빈 객체를 생성하고 프로퍼티를 추가한다. OrdinaryObjectCreate를 호출하여 빈 객체를 생성하는 점은 동일하지만 new.target의 확인, 프로퍼티 추가라는 과정은 서로 차이가 있다. 즉, 객체 리터럴에 의해 생성된 객체는 Object 생성자 함수가 생성한 객체가 아니다
- ⭐결론적으로 리터럴과 생성자함수로 생성한 객체는 서로 생성 과정에서 조금 차이가 있을 수 있지만, 프로토타입은 같다.
- 프로토타입은 생성자 함수가 생성ㅇ되는 시점에 더불어 생성된다. 프로토타입과 생성자함수는 언제나 쌍으로 존재한다.
- 함수 선언문도 선언문이므로 런타임 이전에 실행이 되고, 이 때 프로토타입도 더불어 생성된다. 즉, 객체 생성 이전에 이미 객체화되어 존재한다.
- ⭐생성된 프로토타입도 객체이므로 프로토타입을 갖는데, Object.prototype이다.
- 객체는 어떤 방식으로 생성하든 OrdinaryObjectCreate에 의해 생성된다.
- OrdinaryObjectCreate는 필수적으로 자신이 생성할 객체의 프로토타입을 인수로 전달받는다.
- 빈 객체를 생성하고,
- 객체에 추가할 프로퍼티 목록이 있다면 추가하고,
- 인수로 전달받은 프로토타입을 자신이 생성한 객체의 Prototype 내부슬롯에 할당한다.
- 객체 리터럴에 의해 객체를 생성할 경우, Object.prototype이 프로토타입으로 전달된다.
- Object 생성자 함수에 의해 객체를 생성해도 Object.prototype이 인자로 전달된다.
- ⭐둘의 차이점은 프로퍼티 추가 방식으로, 사용자입장에서는 리터럴은 리터럴 내부에 추가하지만 Object 생성자 함수 방식은 빈 객체를 생성한 후 프로퍼티를 추가한다.
- ⭐⭐ 사용자정의 생성자함수(function키워드로 선언 또는 표현 + new) 로 객체 생성 시 생성자함수의 property에 바인딩 되어있는 객체가 OrdinaryObjectCreate로 전달된다. 즉, 사용자정의함수의 프로토타입 자체는 Object.prototype이아니기 때문에 hasOwnProperty와 같은 빌트인 메서드가없다.. 그러나, 사용자정의함수.prototype도 결국 객체이기 때문에 프로토타입을 가지고 있고, 결국 Object.prototype을 프로토타입으로 갖는다. 그래서 빌트인 메서드를 쓸 수 있다.
- 자바스크립트 엔진은 메서드가 없을 시 프로토타입 체인을 따라 올라가면서 메서드를 찾는다.
- ⭐⭐⭐ 프로퍼티는 프로토타입 체인을 타고 검색하지만, 프로퍼티가 아니라 그냥 식별자는 스코프 체인을 타고 검색한다. me.hasOwnproperty ... 를 하려면 일단 me를 스코프체인에서 찾아야한다.
- 이미 프로토타입 레벨에서 정의된 함수명과 동일한 이름으로 인스턴스레벨에서 재할당하면 인스턴스레벨만 교체가 되고, 여전히 존재하는 프로토타입 레벨의 프로퍼티는 가려지게 된다. 이를 프로퍼티 섀도잉이라고 한다. (인스턴스 입장에선 오버라이딩)
- 생성자함수.prototype으로 프로토타입에 접근 후 교체할 수 있는데, 교체를 하게되면 constructor가 생성자 함수가 아닌 Object를 가리키게 된다. constructor 프로퍼티가 없기 때문이다.
- 인스턴스를 통해 프로토타입을 교체하면 생성자함수에서 프로토타입에 접근할 수 없다. 생성자 함수와 인스턴스간 연결이 파괴되므로 다시 할당을 해줘야한다.
- ⭐ 객체 instance of 생성자함수 : 생성자 함수의 prototype에 바인딩된 객체 즉 생성자함수.prototype이 가리키는 대상이 객체의 프로토타입 체인 상에 존재한다면 true이다. 좌의 프로토타입 상에 우의 프로토타입이 존재하는가?
- Object.create(프로토타입, {프로퍼티 : 프로퍼티디스크립터객체}) 로 객체를 만들 수도 있다. null값 입력시 프로토타입 체인 종점에 존재하는 프로토타입이 생성된다.
- 정적 프로퍼티/메서드 : 생성자 함수 내에서 생성자 함수.프로퍼티로 정의하고, 객체 생성 없이도 쓸 수 있다.
- 생성자 함수의 정적프로퍼티/메서드는 인스턴스에서 접근할 수 없다. Object.create는 obj에서 접근이 불가능하다. 프로토타입 체인에 속하지 않기 때문이다.
- ⭐
in
연산자는 프로퍼티 유무를 반환하는데 프로토타입체인을 모두 검색한다.hasOwnProperty
는 이와 달리 고유의 프로퍼티인지 확인한다. - ⭐
for...in
문은 객체의 프로퍼티를 순회한다. 그 객체의 고유 프로퍼티 뿐만 아니라 프로토타입 체인을 순회하면서 프로토타입 어트리뷰트가 enumerable 이 true인 프로퍼티를 모두 열거한다. 또, 순서가 보장되지 않기 때문에 주의가 필요하다. - ⭐⭐⭐Object.keys, values, entries는 자신의 고유 프로퍼티만 배열로 반환한다.
20. strict mode
- 예제를 보자
function foo(){
x=10;
}
foo();
console.log(x);
- 결과적으로 10이 출력된다.
- 에러가 출력되어야 할 것 같지만 자바스크립트 엔진이 암묵적으로 전역 객체에 x 프로퍼티를 동적 생성한다.
- 잠재적인 오류의 발생 가능성을 줄이기 위해 strict mode가 탄생했다.
use strict
키워드를 이용해서 strict mode를 가동할 수 있다. 함수든 전역이든 무조건 선두에 두어야 한다.- 전역에 strict mode를 두는 것은 피하자. (전역에 선언하면 ’user strict’ 이후 부분에 대해서는 다 strict mode 처리가 된다.)
- strict mode의 특징
- 선언하지 않은 변수 참조 시 에러
- 변수, 함수, 매개변수 삭제 시 에러
- 중복된 이름 사용 시에러
- with 사용 시 에러
- 일반함수 내의 this가 global이 아닌 undefined로 출력됨.
- 함수 내에서 인수를 재할당해도 arguments 객체에 반영되지 않는다.
'Study Log' 카테고리의 다른 글
리액트 쿼리 공식문서 번역 및 요약 (0) | 2022.07.20 |
---|---|
git branch 공부하기 (0) | 2022.02.26 |
Redux Toolkit 알아보기 (0) | 2022.02.10 |
Redux 공부하기 (0) | 2022.01.01 |
브라우저 공부 (0) | 2021.11.20 |
댓글
이 글 공유하기
다른 글
-
리액트 쿼리 공식문서 번역 및 요약
리액트 쿼리 공식문서 번역 및 요약
2022.07.20 -
git branch 공부하기
git branch 공부하기
2022.02.26 -
Redux Toolkit 알아보기
Redux Toolkit 알아보기
2022.02.10 -
Redux 공부하기
Redux 공부하기
2022.01.01