★단축평가
* 'Cat' && 'Dog' => 결과는 'Dog'로 나옴, 두 개의 연산자가 모두 true여야 하므로 논리 연산의 결과를 결정한 두 번째 피연산자의 평가 결과인 'Dog' 문자열을 그대로 반환함
* 'Cat' || 'Dog' => 결과는 'Cat', 두 개의 연산자 중 하나라도 true면 되므로 첫 번째인 'Cat'이 true이므로 그대로 반환(반대로 저기 'Cat'부분이 거짓이라면 뒤의 부분이 결과로 들어감)
=> 단축평가 사용 : 객체가 null인지 확인 후 프로퍼티 참조 시, 함수의 인수를 초기화 시 주로 사용함
★JS etc
* 객체의 프로퍼티 키는 식별자이고 키를 중복 선언 시 나중에 선언한 프로퍼티가 먼저 선언한 프로퍼티를 덮어씀 & 객체는 배열과 달리 프로퍼티를 열거 시, ★순서를 보장하지 않음
* 프로퍼티 이름이 유효한 js이름이 아니거나 예약어인 경우 프로퍼티 값은 대괄호 표기법으로 읽어야 함, 대괄호 표기법 사용 시 대괄호 내 들어가는 프로퍼티 이름은 반드시 문자열이어야 함.
* 프로퍼티 동적 생성 : 객체가 소유하고 있지 않은 프로퍼티 키에 값 할당 시, 주어진 키와 값으로 프로퍼티를 생성해 객체에 추가함.
* 프로퍼티 삭제 : delete 연산자를 통해 객체의 프로퍼티를 삭제할 수 있는데 피연산자는 반드시 프로퍼티의 키여야 함!
* Object.assign : 타깃 객체로 소스 객체의 프로퍼티를 복사 함, 이 때 소스 객체의 프로퍼티와 동일한 프로퍼티를 가진 타겟 객체의 프로퍼티들은 소스 객체의 프로퍼티로 덮어쓰기 됨(리턴값으로 타깃 객체를 반환)
=> merge1은 타겟 객체가 o1이였으므로 타겟 객체가 변경이 되므로 변경을 시키지 않으려면 merge2처럼 빈 객체를 통해 assign을 해야 함(Object.assign은 shallow copy만 지원, deep copy X)
* Object.freeze() : 객체를 불변 객체로 만들 수 있음(하지만 이것도 내부 객체는 변경이 가능함.)
=> 객체의 내부 객체도 freeze하려면? deepFreeze 사용하기
※ 이렇게 두 개를 사용해 불변 객체를 만드는 법은 번거롭고 성능 이슈로 큰 객체에는 사용하지 않은 것이 좋음
- Facebook에서 제공하는 Immutable.js를 사용 : List, Stack, Map, OrderedMap, Set, OrderedSet, Record와 같은 영구 불변 데이터 구조를 제공함(npm install immutable설치해 사용)
=> ex) Map모듈을 임포트해 사용
* 매개변수(Parameter, 인자) : 함수의 작업 실행을 위해 추가적 정보가 필요한 경우 매개변수를 지정(변수와 동일동작), 인수를 전달받지 못한 매개변수는 undefinde로 초기화 됨.
*인수(Argument) : 함수에 전달된 인수는 매개변수에 할당(매개변수는 변수와 동일하게 메모리 공간확보), 매개변수보다 인수 개수를 더 많이 전달한 경우 초과된 인수는 무시
* Call-by-value : 함수 호출시 원시 타입 인수를 함수에 매개변수로 전달할 때 매개변수에 값을 복사해 함수로 전달(이 때, 매개변수를 통해 값이 변경되어도 원시 타입의 값은 변경되지 않음)
* Call-by-reference : 참조 타입(객체 등)의 인수를 함수에 전달 시 매개변수에 값이 복사되지 않고 객체의 참조값이 매개변수에 저장되어 함수로 전달되는 방식(얘는 매개변수에서 객체의 값이 변경되었다면 전달되어진 참조형의 인수 값 또한 같이 ☆변경된다!!)
* return : 배열등을 이용해 한번에 여러 개의 값 리턴가능함, 반환 생략 가능(이 때, undefined 반환), return 만나면 함수 실행 중단 후 함수를 호출한 코드로 되돌아감(return이후의 다른 구문은 실행x)
* 함수도 객체여서 프로퍼티를 가질 수 가 있다!, JS는 함수 호출 시 정의에 따라 인수를 전달하지 않아도 에러x
☆ arguments객체 : JS의 특성 때문에 런타임 시 호출된 함수의 인자 갯수를 확인하고 이에 따라 동작을 달리 정의할 필요가 있을 수 있는데 이 때, 유용하게 사용(매개변수 개수가 확정되지 않은 가변 인자 함수를 구현시 유용), arguments객체는 유사배열객체이다!(배열 메소드 사용시 Function.prototype.call/apply사용해야 함) // arguments.length = 함수 호출 시 인자의 개수
* caller 프로퍼티 : 자신을 호출한 함수를 의미함 return func.caller 지정 후 func2(func) 실행 시 caller은 func2가 출력
* func.length : 해당 함수의 매개변수 개수를 의미함.
* name 프로퍼티 : 기명함수 경우 함수명을 값으로 갖고 익명함수는 빈문자열을 값으로 갖는다.
★Prototype : 모든 객체는 [[prototype]]이라는 내부 슬롯이 있음, 이 내부 슬롯은 프로토타입 객체를 가리킴, 프로토타입 객체란 프로토타입 기반 객체 지향 프로그램의 근간을 이루는 객체로 객체 간 상속 구현을 위해 사용됨(즉, 다른 객체에 공유 프로퍼티를 제공하는 객체를 의미)
* __proto__프로퍼티 : [[prototype]]내부 슬롯이 가리키는 프로토타입 객체에 접근하기 위해 사용하는 접근자 프로퍼티, 내부 슬롯에는 직접 접근은 할 수 없고 간접적인 접근 방법을 제공하는 경우에 한해 접근이 가능함(이 때, __proto__접근자 프로퍼티를 통해 간접적으로 프로토타입 객체에 접근 가능)
=> 이 때, __proto__프로퍼티는 객체가 직접 소유하는 프로퍼티가 아닌 모든 객체의 프로토타입 객체인 Object.prototype객체의 프로퍼티이다! 모든 객체는 상속을 통해 __proto__접근자 프로퍼티를 사용할 수 있음
* prototype프로퍼티 : 함수 객체만이 소유하는 프로퍼티(일반 객체에는 없음!!), 함수가 객체를 생성하는 생성자 함수로 사용 시, 생성자 함수가 생성한 인스턴스의 프로토타입 객체를 가리킨다.
★즉시 실행 함수 : 정의와 동시에 실행되는 함수로 IIFE라고 함, 최초에 한번만 호출되며 다시는 호출 할 수 없어 최초 한번만 실행이 필요한 초기화 처리등에 주로 사용 // (func(){})이렇게 전체를 감싸야 함 - 전역변수 사용 억제
=> JS의 가장 큰 문제 중 하나는 파일이 분리 되어 있더라도 글로벌 스코프가 하나여서 글로벌 스코프에 선언된 변수, 함수는 코드 내의 어디서든 접근이 가능하다는 게 문제, 따라서 다른 스크립트 파일 내에 동일한 이름으로 명명된 함수나 변수가 동일 스코프에 존재 시 원치 않은 결과를 가져올수 있음 -> 즉시 실행 함수 내에 처리 로직을 모아 둬 혹시 있을 수도 있는 변수명, 함수명의 충돌을 방지하는 목적으로 사용됨(특히 jQuery 같은 라이브러리는 코드를 즉시 실행 함수 내에 정의 시, 라이브러리의 변수들이 독립된 영역 내에 있게 되어 여러 라이브러리들을 동시에 사용해도 충돌 문제 방지 가능)
* 재귀함수(Recursive) : 피보나치 수열, 팩토리얼 등에 사용(탈출 조건을 반드시 만들어야 함-stackoverfloew에러 발생)
=> 재귀함수는 반복문 보다 직관적으로 이해하기 쉬운 구현이 가능한 경우에만 한정적으로 적용하는 것이 바람직
★콜백함수 : 함수를 명시적으로 호출하는 방식이 아닌 특정 이벤트가 발생 시, 시스템에 의해 호출되는 함수(대표적으로 이벤트 핸들러 처리), 콜백 함수는 주로 비동기식 처리 모델에 사용(처리가 종료되면 호출 될 함수(콜백함수)를 미리 매개변수에 전달하고 처리가 종료되면 콜백함수를 호출 하는 것!),
* 콜백함수는 콜백 큐에 들어가 있다 해당 이벤트 발생 시 호출됨, 콜백함수는 클로저이므로 콜백큐에 단독으로 존재하다 호출되더라도 콜백함수를 전달받은 함수의 변수에 접근이 가능하다!!!
★Object.prototype.toString : 객체를 나타내는 문자열을 반환
타입 반환 함수 만들기
이것을 통해 동적 타이핑의 오류를 막기 위해 ex) sum함수에 숫자 타입만 받을 수 있도록 조건 설정
=> 하지만 이 방법으로는 객체의 상속 관계까지 체크할 수 없음
* instanceof : 피연산자인 객체가 우향에 명시한 타입의 인스턴스인지 여부를 알려줌, 이 때 타입이란 constructor를 말하며 프로토타입 체인에 존재하는 모든 constructor를 검색해 일치하는 정보가 있다면 true 반환 함!
★strict mode : JS의 문법을 보다 엄격히 적용해 기존에 무시되던 오류를 발생시킬 가능성이 높거나 JS엔진의 최적화 작업에 문제를 일으킬 수 있는 코드에 대해 명시적 에러를 발생함, strict mode가 제한하는 오류는 물론 코딩 컨벤션을 설정 파일 형태로 정의하고 강제 할 수 있기에 보다 강력한 효과를 얻을 수 있음.
=>stricti mode 사용 : 전역 선두or 함수 몸체의 ☆선두에 'use strict';를 추가 - 이 때, 전역 및 함수단위로 적용하는건 문제 발생가능이 높음(라이브러리가 non-strict mode 이거나 strict모드가 적용된 함수가 참조할 함수 외부 컨텍스트에 strict를 적용하지 않을 시 생기는 문제 등)
=>따라서!! strict mode는 즉시실행함수로 감싼 스크립트 단위로 적용하는 것이 바람직함.
* JS함수는 호출 시, 매개변수로 전달되는 인자값 이외에 arguments객체와 this를 암묵적으로 전달 받음.
★ this : 기본적으로 this는 전역객체에 바인딩 됨, 전역함수&내부함수&메소드의 내부함수&콜백함수 등 전부 전역객체에 바인딩(내부 함수는 무조건 전역객체에 바인딩 되어 apply, call, bind나 self처럼 직접 this를 바인딩 해 사용)
* 생성자 함수 : 일반 함수와 생성자 함수에 특별한 형식적 차이는 없고 함수에 new 연산자를 붙여 호출하면 해당 함수는 생성자 함수로 동작 함 - new 연산자로 생성자 함수 호출 시 생성자 함수로 생성된 빈 객체에 바인딩, 즉 생성자 함수 내부 this는 함수에 의해 생성된 인스턴스를 가리킴(일반 함수는 this가 전역 객체에 바인딩) =>주의!! 생성자 함수를 new 없이 호출된다면 일반 함수로서 호출되는 오류 발생(일반함수에 new 붙여도 오류)
* Lexical Scoping : 스코프는 함수를 호출 할 때가 아닌 함수를 어디에 선언하였는지에 따라 결정됨!!
★ 클로저 : 현재 상태를 기억하고 변경된 최신 상태를 유지하는 상황에 가장 유용하게 사용됨!
=> 1번은 즉시 실행 함수로 함수를 반환하고 즉시 소멸함. 이 때, 반환한 함수는 자신이 생성되었을 때의 렉시컬 환경에 속한 변수 isShow를 기억하는 클로저(false) -> 2번은 클로저를 이벤트 핸들러로 이벤트 프로퍼티에 할당 해 클로저를 제거하지 않는 한 클로저가 기억하는 렉시컬 환경의 변수 isShow는 소멸하지 않음(즉 현재 상태를 기억) -> 3번은 버튼 클릭 시 이벤트 프로퍼티에 할당된 이벤트 핸들러인 클로저가 호출되어 isShow의 변수 값을 변경함(변수 isShow는 클로저에 의해 참조되고 있기 때문에 유효하며 자신의 변경된 최신 상태를 계속해서 유지함!!)
※ 클로저 사용 시 전역 변수 사용 억제! : 예로 버튼 클릭한 횟수를 누적해 화면에 표시되는 카운터를 만들 때, 전역 변수로 counter를 선언 시, increase호출 직전의 counter값이 반드시 0이여야 함(전역변수이기에 누구나 접근가능, 변경가능한 취약 부분이 있음) => 지역변수로 바꿔 함수내에 counter변수를 선언하면 접근하는 건 막을 수 있지만 increase함수가 호출될 때마다 0으로 초기화 되기에 언제나 1표시(변경된 이전 상태를 기억하지 못함) => 이것을 클로저로 해결
=> 스크립트 실행 시, 즉시실행함수가 호출되고 변수 increase에는 함수 function(){return ++counter;}가 할당됨 - 요 함수가 자신이 생성됐을 때의 렉시컬 환경을 기억하는 클로저임, 즉시실행함수는 호출 이후 소멸되지만 이 즉시실행함수가 반환한 함수는 변수 increase에 할당 되어 자신이 선언되었을 때의 렉시컬 환경인 즉시실행함수의 스코프에 속한 지역변수 counter를 기억한다. 따라서!! 즉시실행함수의 변수 counter에 접근이 가능하고 변수 counter는 자신을 참조하는 함수가 소멸될 때까지 유지된다.(변수counter는 외부에서 직접 접근 불가능해 의도되지않은 변경 방지)
*클로저 활용 예제 2)
=> makeCounter는 고차 함수, 이 함수가 반환하는 함수는 자신이 생성되었을 때의 렉시컬 환경인 함수 makeCounter의 스코프에 속한 변수 counter를 기억하는 클로저!, 이 함수는 인자로 전달받은 보조 함수를 합성 해 자신이 반환하는 함수의 동작을 변경 가능(이 때! 반환된 함수는 자신만의 독립된 렉시컬 환경을 갖는다! 이는 함수 호출할때마다 새로운 렉시컬 환경이 생성도기 때문 -> increaser과 decreaser에 할당된 함수는 각각 자신만의 독립적인 렉시컬 환경이기에 자유 변수 counter를 공유하지 않아 카운터의 증감이 연동되지않음) => 두 독립된 렉시컬 환경을 하나로 만들기
* 생성자 함수를 생성해서 해결
=>여기서의 counter변수는 this에 바인딩된 프로퍼티가 아닌 변수이다!(변수이기에 외부에서 접근 x) & 인스턴스의 메소드인 increase와 decrease는 클로저이기에 자신이 생성됐을 때의 렉시컬 함수의 변수인 counter에 접근이 가능함 - 클래스 기반 언어의 private를 흉내내기 가능
★ 프로토타입 체인 : 모든 객체는 프로토타입이라는 다른 객체를 가리키는 내부 링크를 가지고 있어 이를 통해 직접 객체를 연결할수 있는데 이를 프로토타입 체인이라 함, 이를 이용해 생성자 함수 내부의 메소드를 생성자 함수의 prototype 프로퍼티가 가리키는 프로토타입 객체로 이동 시 생성자 함수에 의해 생성된 모든 인스턴스는 프로토타입 체인을 통해 프로토타입 객체의 메소드를 참조하는게 가능 함.
* 더글라스 크락포드가 제안한 프로토타입 객체에 메소드 추가하는 방식
★ 상속(Inheritance) : JS는 기본적으로 프로토타입을 통해 상속을 구현(객체가 다른 객체로 직접 상속), JS에서의 상속 구현 방식으로는 클래스 기반 언어의 상속 방식을 흉내내는 의사 클래스 패턴 상속(Pseudo-classical Inheritance)과 프로토타입으로 상속을 구현하는 프로토타입 패턴 상속(Prototypal Inheritance)이 있다.
1) 의사 클래스 패턴 상속 : 자식 생성자 함수의 prototype 프로퍼티를 부모 생성자 함수의 인스턴스로 교체해 상속을 구현, 이 때 부모/자식 모두 생성자 함수를 정의 해야 함.
=> 이 상속의 문제점! 1. new 연산자를 통해 인스턴스를 생성(JS의 프로토타입 본질에 모순되는 것) 2. 생성자 링크의 파괴(프로토타입 객체를 인스턴스로 교체하는 과정에서 constructor의 연결이 깨지게 됨) 3. 객체리터럴(기본적으로 생성자 함수를 사용하기에 객체리터럴 패턴으로 생성한 객체의 상속에는 적합하지 않음 - 변경 방법이 없기 때문)
2) 프로토타입 패턴 상속 : 의사 클래스 패턴 상속보다 간단하고 위 3가지 문제점들을 해결
※ 객체 리터럴 패턴으로 생성한 객체에 상속 적용
=> Object.create함수는 IE9이상에서 정상적으로 동작하므로 크로스 브라우징에 주의! 이 함수의 폴리필(Polyfill : 특정 기능이 지원하지 않는 브라우저를 위해 사용할수 있는 코드 조각이나 플러그인)로 동작하게끔 함.
=> 상속 핵심 부분임 : 비어있는 F생성자 함수를 생성함, F의 프로퍼티에 매개변수로 전달받은 객체를 할당, F를 생성자로 해 새로운 객체를 생성하고 반환함
★ 캡슐화(Encapsulation)와 모듈 패턴(Module Pattern) : 캡슐화는 관련있는 멤버 변수와 메소드를 클래스와 같은 하나의 틀 안에 담고 외부에 공개될 필요가 없는 정보는 숨기는 것(정보 은닉), JS에선 private가 제공x
=>person함수는 객체를 반환하고 이 객체 내 메소드 getName, setName은 클로저로서 private 변수 name에 접근 가능 - 요러한 방식을 모듈 패턴이라 한다!!(캡슐화와 정보 은닉을 제공함)
* 모듈패턴 주의 : 1.private 멤버가 객체, 배열일 경우 반환된 해당 멤버의 변경이 가능함 - 객체 반환 시 반환 값은 얕은 복사로 private 멤버의 참조값을 반환하게 됨(외부에서도 값 변경가능) => 이를 회피 하기 위해서는 객체를 그대로 반환하지 않고 반환해야 할 객체의 정보를 새로운 객체에 담아 반환해야 함!(반드시 객체 전체가 그대로 반환되어야 하는 경우는 deep copy로 복사본을 만들어 반환한다)
2. person 함수가 반환한 객체는 person함수 객체의 프로토 타입에 접근 할 수 없다(상속을 구현 할 수 없다.)
=> 반환된 객체가 함수 person의 프로토타입에 접근할 수 없다는 것은 person을 부모 객체로 상속 할수 없다라는 의미임, 이 문제를 해결하기 위해선 객체를 반환하는 것이 아닌 함수를 반환 해야 함!
=> 해결완료!
* 캡슐화를 구현하는 패턴은 다양하고 각각의 패턴에는 장단점이 있다, 이 장단점을 분석하고 파악하는 게 보다 효율적 코드를 작성하는데 중요!!
'JS' 카테고리의 다른 글
| Optional Chaining / JS Array.some() / 논리 연산자 (2) | 2023.11.09 |
|---|---|
| JSON의 역할 및 속도 저하 (0) | 2023.11.08 |
| JS 메소드 (4) | 2023.05.30 |
| JS 5주차 개념 정리 (0) | 2023.05.25 |
| JS 4주차 개념 정리 (0) | 2023.05.25 |