ES6에서 도입된 이터레이션 프로토콜은 순회 가능한 데이터 컬렉션을 만들기 위한 규약이다.
즉, ES6 이후부터는 순회 가능한 데이터 컬렉션을 이터레이션 프로토콜 규약을 준수하는 이터러블로 통일하였다.
이터러블 프로토콜
Symbol.iterator를 프로퍼티 키로 메서드 직접 구현하거나 프로토타입 체인을 통해 상속받은 Symbol.iterator 메서드를 호출 시 이터레이터 프로토콜을 준수한 이터레이터를 반환한다.
위의 규약을 준수한 객체를 이터러블이라고 한다. 이터러블은 다음이 가능하다.
for…of 문 순회
spread 문법
배열 디스트럭처링 할당의 대상
일반객체는 Symbol.iterator 메서드를 구현하거나 상속받지 않으므로 이터러블이 아니다.
이터레이터 프로토콜
이터러블의 Symbol.iterator 메서드 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환한다. 이터레이터는 next 메서드를 소유하고 next 메서드 호출 시 이터러블을 순회하며 value와 done 프로퍼티를 갖는 이터레이터 리절트 객체를 반환한다. 이터레이터는 이터러블 요소를 탐색하기 위한 포인터 역할을 한다.
이터레이션 프로토콜의 필요성
이터러블은 for..of문, 스프레드 문법, 배열 디스트럭처링 할당과 같은 데이터 소비자에 의해 사용되므로 데이터 공급자의 역할을 한다고 볼 수 있다.
다양한 데이터 공급자(Array, String, Map 등)가 이터레이션 프로토콜이라는 하나의 규약만 준수하도록 규정하면 데이터 소비자(for..of, 스프레드 문법 등)는 이터레이션 프로토콜만 지원하도록 구현하면 된다.
즉, 이터레이션 프로토콜은 다양한 데이터 공급자가 하나의 순회방식을 갖도록 규정하여 데이터 소비자가 효율적으로 다양한 데이터 공급자를 사용할 수 있도록 데이터 소비자와 데이터 공급자를 연결하는 인터페이스 역할을 한다.
결론
어떤 객체가 Symbol.iterator 메서드를 호출하여 이터레이터 프로토콜을 준수한 이터레이터를 반환한다.
위와 같은 규약을 이터러블 프로토콜이라 하며 이터러블 프로토콜을 준수한 객체를 이터러블이라 한다.
next 메서드를 소유한 이터레이터가 next 메서드 호출하면 이터러블 순회하면서 이터레이터 리절트 객체 반환한다.
위와 같은 규약을 이터레이터 프로토콜이라 하며 이터레이터 프로토콜을 준수한 객체를 이터레이터라고한다.
// 위 표현은 다음과 동일하다. constcreate = (id, content) => { return { id, content }; };
화살표 함수와 일반함수 차이
화살표 함수는 non-constructor로, 인스턴스 생성할 수 없다.
인스턴스 생성 불가
prototype 프로퍼티 없다
프로토타입 생성 불가
화살표 함수는 중복된 매개변수 이름 선언시 에러 발생
1 2
constarrow = (a, a) => a + a; // SyntaxError: Duplicate parameter name not allowed in this context
화살표 함수는 this, arguments, super, new.target 바인딩 갖지 않는다.
따라서 화살표 함수 내부에서 위의 것들을 참조할 때 스코프 체인상 가장 가까운 상위 함수 중 화살표 함수가 아닌 함수의 this, arguements, super, new.target을 참조한다.
주의사항 : 메서드를 화살표 함수로 정의하지 않기
단, 메서드를 화살표 함수로 정의하면 안된다.
1 2 3 4 5 6 7 8 9 10
// Bad const person = { name: "Lee", sayHi: () =>console.log(`Hi ${this.name}`), };
// sayHi 프로퍼티에 할당된 화살표 함수 내부의 this는 상위 스코프인 전역의 this가 가리키는 // 전역 객체를 가리키므로 이 예제를 브라우저에서 실행하면 this.name은 빈 문자열을 갖는 // window.name과 같다. 전역 객체 window에는 빌트인 프로퍼티 name이 존재한다. person.sayHi(); // Hi
메서드 정의할 때는 메서드 축약 표현을 사용하는 것이 좋다.
1 2 3 4 5 6 7 8 9
// Good const person = { name: "Lee", sayHi() { console.log(`Hi ${this.name}`); }, };
// 프로토타입이 null인 객체를 생성한다. 생성된 객체는 프로토타입 체인의 종점에 위치한다. // obj → null let obj = Object.create(null); console.log(Object.getPrototypeOf(obj) === null); // true // Object.prototype을 상속받지 못한다. console.log(obj.toString()); // TypeError: obj.toString is not a function
// 정적 프로퍼티/메서드는 생성자 함수가 생성한 인스턴스로 참조/호출할 수 없다. // 인스턴스로 참조/호출할 수 있는 프로퍼티/메서드는 프로토타입 체인 상에 존재해야 한다. me.staticMethod(); // TypeError: me.staticMethod is not a function
생성자 함수로 생성한 인스턴스로 정적 프로퍼티와 메소드를 참조, 호출할 수 없다.
생성자 함수가 생성한 인스턴스는 프로퍼티, 메소드를 참조할 때 프로토타입 체인 내에서만 할 수 있다.
정적 프로퍼티,메소드는 인스턴스의 프로토타입 체인 내에 없기 때문에 참조, 호출할 수 없다.
동적으로 생성시에 편의를 위해서 true로 설정된다. 그렇지 않은 경우 Object.defineProperty() 메서드를 사용할 때, 생략하는 어트리뷰트는 false로 설정된다.
writable : false
1 2 3
// [[writable]] 값이 false 인 경우 해당 프로퍼티의 [[value]]의 값 변경할 수 없다. // 에러 발생하지 않고 무시 person.lastName = "soondae";
enumerable : false
1 2
// [[enumerable]] 값이 false 인 경우 해당 프로퍼티는 for...in 문이나 Object.keys 등으로 열거할 수 없다. console.log(Object.keys(person)); // ['firstName']
configurable : false
1 2 3 4 5 6 7
// [[configurable]] 값이 false 인 경우 해당 프로퍼티를 삭제할 수 없다. // 삭제해도 에러없이 무시된다. delete person.lastName; // false
// 또한, 해당 프로퍼티를 재정의 할 수도 없다. // Object.defineProperty(person, 'lastName', { enumerable : true }); // Uncaught TypeError: Cannot redefine property: firstName
자바스크립트에서 함수란, 코드블록으로 일련의 문(statement)을 감싸서 하나의 실행 단위로 정의한 것이다.
함수를 목적에 맞게 사용하기 위해서는 함수이름, 매개변수, 인자 등을 알맞게 설정해줘야한다. 그렇지 않게 사용하는 것은 지양한다.
목적
함수를 사용하는 목적은 필요할 때마다 호출하여 일련의 코드들을 재사용하기 위해 사용한다.
1 2 3 4 5 6 7 8 9 10
functionreturnRank(name, tall) { // 이름과 키라는 매개변수를 통해 순위를 반환해주는 함수 return rank; }
// 100번째 줄 returnRank("yiju", 188);
// 300번째 줄 returnRank("kim", 160);
위와 같이 하나의 로직을 여러 곳에서 재사용하고 싶을 때 함수를 사용하면 함수 이름으로 가독성도 높아지고 100번째 줄이나 300번째 줄에서 코드가 문제가 발생했을 경우 returnRank 함수가 선언된 부분만 유지보수를 해주면 되기 때문에 유지보수가 간편해진다.
단, 함수는 목적에 맞게 가급적 작게 만들고 매개변수도 3개를 넘지 않도록 만들 것을 지향한다.
정의
함수를 정의하는 방식은 4가지 방식이 있다.
함수 선언문
함수 표현식
Function 생성자 함수
화살표 함수(ES6)
기본적인 함수 선언문과 함수 표현식에 대해 알아보자
자바스크립트에서 변수를 선언하면 암묵적으로 정의가 이뤄지기 때문에 선언과 정의가 모호하다.(MDN에서도 모호..) 위에서 함수 선언문이 평가되면 식별자가 암묵적으로 생성되고 함수 객체가 할당된다. 그렇기 때문에 “함수는 정의된다”로 표현한다. C언어에서 정의는 변수에 값을 할당하여 변수의 실체를 명확히 하는 것이다. 즉, 메모리 주소가 연결되면 정의라고 판단)
함수 리터럴
리터럴은 문자나 약속된 기호를 사용하여 값을 생성하는 표기법이다. 함수로 함수 리터럴을 사용하여 값을 생성할 수 있다. 함수 리터럴은 다음으로 구성되어 있다.
1 2 3
functionadd(x, y) { return x + y; }
function 키워드
함수 이름 (add) 함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자
매개변수 목록 (x,y)
함수 몸체({})
함수 선언문
1 2 3 4 5 6 7
// 함수 선언문 functionadd(x, y) { return x + y; }
// 함수 호출 (함수이름과 똑같은 식별자를 사용) add(2, 5);
함수 선언문은 표현식이 아닌 문이다.
함수 선언문은 함수 이름을 생략할 수 없다.
앞서 언급했듯이 함수 이름은 함수 몸체 내에서만 참조 가능하다. 그렇다면 함수를 호출하려면 어떻게 할 수 있을까?
함수 선언문으로 사용되면 자바스크립트 엔진이 암묵적으로 함수 이름과 똑같은 식별자를 생성하고 함수 객체를 할당한다.
함수 표현식
1 2 3 4 5 6 7
// 함수 표현식 var add = function (x, y) { return x + y; };
// 함수 호출 (함수 이름으로 호출) add(2, 10);
자바스크립트 함수는 객체타입의 값이기 때문에 변수에 할당하거나 프로퍼티의 값으로 될 수 있고 배열과 같은 자료구조의 요소가 될 수 있다.
이러한 성질 때문에 자바스크립트 함수는 일급객체다.
함수 표현식은 함수 이름 생략하는 것이 일반적이다.
함수 표현식은 표현식인 문이다. 즉, 값처럼 사용할 수 있다. ex)변수할당
중의적 코드 : 기명 함수 리터럴
함수 선언문은 함수 이름을 생략할 수 없으며 표현식이 아닌 문이므로 변수에 할당할 수 없다. 그러면 아래의 코드는 어떻게 동작할지 예상해보자.
변수 person의 원시타입의 값을 수정하였을 때, 서로의 원시타입의 값은 공유하지 않는다. 하지만 객체타입의 값을 바꾼 경우 서로의 객체타입의 값을 공유하는 모습을 볼 수 있다.
왜 그럴까? 앞서 언급한 내용으로 깊게 파헤쳐보자.
person, someone은 일치하지 않으니 서로 다른 주소값을 갖는다. 각각의 주소는 javascript {name:'yiju',fruit:{1:'peach',2:'strawberry'}} 객체 프로퍼티들의 주소를 담고있는 메모리 공간을 가리킨다.
예를 들면 name 식별자가 가리키는 주소, fruit 식별자가 가리키는 주소 이런식으로 프로퍼티의 주소들을 담고 있다.
여기서 name 식별자는 원시타입의 값을 가리키는 메모리 주소를 저장한다. javascript person.name = 'minseok' 을 실행하면 person.name과 someone.name이 서로 다른 주소값을 갖게된다.
fruit의 식별자 주소는 javascript {1:'peach',2:'strawberry'} 프로퍼티들의 주소를 담고있는 메모리 공간을 가리킨다. person.fruit와 someone.fruit가 서로 같다(메모리 주소가 같다). 즉, fruit 프로퍼티들의 주소가 담긴 메모리 공간을 가리키는 메모리 주소가 같다.
그리하여 javascript fruit['1'] = 'watermelon' 로 fruit 프로퍼티인 1을 수정하면 식별자인 1이 가리키는 메모리 주소가 바뀌게 되고 person과 someone 식별자가 식별자 1을 포함하는 프로퍼티들의 주소를 담은 메모리 공간을 가리키는 메모리 주소는 변하지 않으므로 서로 공유하게 된다.
객체타입의 깊은복사
객체타입의 깊은복사는 JSON.parse, JSON.stringify 메서드를 사용하여 구현할 수도 있고 lodash 라는 라이브러리를 사용한다.
하지만 JSON.stringify 방식으로는 함수(메서드)를 복사할 수 없기 때문에 재귀를 사용하거나 실무에선 라이브러리를 사용한다.