var globalName = 'globalName'; // 전역변수
function outer() {
console.log('외부에서 전역호출', globalName);// 외부에서 전역호출
var outName = 'outName';
function inner() {
console.log('내부에서 전역호출', globalName); //내부에서 전역호출
console.log('내부에서 외부호출', outName);
var inName = 'inName';
}
inner();
}
outer();
console.log(outName); // outName is not defined
console.log(inName); // inName is not defined
렉시컬 스코프 (Lexical Scope) : (어휘적 범위) 함수는 선언될때 범위가 만들어진다.
var name = 'Mike';
function log() {
console.log(name);
}
function wrapper() {
var name = 'Sally';
log();
}
wrapper();
case2) 함수 표현식은 런타임 이전에 변수에 undefined라고 초기화만 시켜두고, 런타임에서 객체가 된다.
console.dir(sub) // output: undefined
console.log(sub(2, 5)) // output: Uncaught TypeError: sub is not a function
// 함수 표현식 예시
var sub = function(x, y) {
return x + y
}
case3) 화살표함수는 런타임 이전에 변수에 undefined라고 초기화만 시켜두고, 런타임에서 객체가 된다.
console.dir(sub) // output: undefined
console.log(sub(2, 5)) // output: Uncaught TypeError: sub is not a function
// 화살표 함수 예시
var sub = () => {
return x + y
}
this 관점에서의 차이
- 화살표함수는
this 가 없다. 자기만의 this 가 없다. 이 this 를 찾을때 자기 스코프 안에서 없으면, 함수가 실행되는 위치에서 스코프를 뒤져본다. (스코프 체인 순서대로 뒤진다)
bind, apply, call 사용불가
(참고) 이름도 없다. 익명함수 / arguments도 없다
- 함수선언문에서
this의 값은 함수를 호출하는 방법에 의해 결정된다.
case1) 함수선언문에서 다른 호출방법에 의한 this
// 호출하는 방법에 따라 결정된다.
var someone = {
name : 'Mike',
who : function(){
console.log(this)
}
}
// 호출방법1 -> 누가호출했니? someone
someone.who();
var myWho = someone.who;
// 호출방법2 -> 누가호출했니? global(브라우저)
myWho();
case2) 함수선언문과 화살표함수에서의 this
-> 같은 결과를 냈지만, 함수선언문에서는 함수를 호출하는 방법에 의해 this 가 결정되기 때문에, global(브라우저) 가 불렀기 때문에 window 를 리턴한다.
화살표함수는 자체 this 가 없기 때문에, 스코프 체인 순서대로 this 를 찾는다. 가장 밖에 있는 global(브라우저)가 리턴된다.
// 함수선언문
function who() {
console.log(this)
}
who()
// 화살표함수
const when = () =>{
console.log(this)
}
when();
case3) 함수선언문은 누가 호출했는지 확인하여 someone을 this로 받아서 age를 잘 리턴하지만, 화살표함수는 자체 this 가 없기 때문에 스코프 체인에 의해 window로 올라가서 name을 찾으니 없어서 안나온다.
let someone = {
age : 10,
name : 'Mike',
LogAge : function() {
console.log(this.age);
},
LogName : ()=>{
console.log(this.name)
}
}
someone.LogAge();
someone.LogName();
case4) 함수선언문 안에!
함수선언문으로 버튼에 클릭 이벤트를 단 경우, -> 누가 호출했나? -> button 이 호출했다 -> this 는 button
함수선언문으로 버늩에 클릭 이벤트를 달고 + this 를 bind 한 경우 -> this 를 someone으로 묶어준다
화살표함수로 버튼에 클릭 이벤트를 단 경우, -> 화살표 함수는 this 가 없으니까, 스코프 체인에 의해 상위의 함수 선언문안에 있는 this 가 리턴되어, gender 값을 가져올 수 있다.
다양한 이터러블이 각자의 순회방식을 갖는다면, 데이터 소비자는 모든 방식을 각각 지원해야한다.
효율성을 위해 프로토콜(규칙)을 정의
이터러블은 Lazy evaluation(지연평가)를 통해 값을 생성한다.
100개의 값이 필요할때 처음부터 100개를 만드는 것이아니고, 하나씩 next -> next-> next로 하나씩 만든다.
할당이 이루어지기 전까지는 데이터를 생성하지 않는다.
iteration 프로토콜
데이터 컬렉션을 순회하기 위한 프로토콜(규칙)
이터레이션 프로토콜(규칙)을 준수한 객체는 for문으로 순회할 수 있고, spread문법으로 펼칠 수 있다.
이터레이션 프로토콜 (대분류) 안에 이터러블 프로토콜과 이터레이터 프로토콜이 있다.
iterable 프로토콜
- 순회 가능한 자료구조 (for 문)
- Spread 문법 적용가능
- Symbol.iterator 메소드를 소유한다 -> iterator 를 리턴한다.
- iterable 프로토콜(규칙)을 준수한 객체를 iterable 이라 한다
interable 객체 예시 : Array
const array = [1, 2, 3];
// 배열은 Symbol.iterator 메소드를 소유한다.
// 따라서 배열은 이터러블 프로토콜을 준수한 이터러블이다.
console.log(Symbol.iterator in array); // true
// 이터러블 프로토콜을 준수한 배열은 for...of 문에서 순회 가능하다.
for (const item of array) {
console.log(item);
}
[실행결과]
iterable 하지 않은 객체 예시 : Object
const obj = { a: 1, b: 2 };
// 일반 객체는 Symbol.iterator 메소드를 소유하지 않는다.
// 따라서 일반 객체는 이터러블 프로토콜을 준수한 이터러블이 아니다.
console.log(Symbol.iterator in obj); // false
// 이터러블이 아닌 일반 객체는 for...of 문에서 순회할 수 없다.
// TypeError: obj is not iterable
for (const p of obj) {
console.log(p);
}
[실행결과]
t
iterator 프로토콜
- next 메소드 소유 -> (value, done) 프로퍼티를 갖는 iterator result 객체를 리턴한다.
- next 메소드는 이터러블의 각 요소를 순회하기 위한 포인터의 역할
- iterator 프로토콜(규칙)을 준수한 객체를 iterator 이라 한다
const array = [1, 2, 3];
// 배열은 Symbol.iterator 메소드를 소유한다.
// 따라서 배열은 이터러블 프로토콜을 준수한 이터러블이다.
console.log(Symbol.iterator in array); // true
// 이터러블 프로토콜을 준수한 배열은 for...of 문에서 순회 가능하다.
for (const item of array) {
console.log(item);
}
// Symbol.iterator 메소드는 이터레이터를 반환한다.
const iterator = array[Symbol.iterator]();
// iterator는 next 메소드를 갖는다.
// next 메소드는 value, done 속성을 갖는 Result객체를 리턴한다
let iteratorResult = iterator.next();
console.log(iteratorResult); // {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}
[실행결과]
Javascript ES6에서 제공하는 내장 이터러블
Array, String, Map, Set, TypedArray, DOM data structure, Arguments
Symbol 객체의 프로퍼티 중 length와 prototype을 제외한 프로퍼티들을 Well-Known Symbol 이라 부른다
이미지출처: 모던 자바스크립트 Deep Dive
이런 Well-Known Symbol 은 자바스크립트 엔진에 "상수"로 존재하며, 자바스크립트 엔진은 각각 이 상수들을 참조하여 일정한 처리를 한다.
예를 들어 어떤 객체가 Symbol.iterator 를 프로퍼티로 갖고 있으면(메소드로 가지고 있으면) 이 객체는 이터레이션 프로토콜을 따는 것으로 생각하고 이터레이터로 동작시킨다. -> 이터레이터를 커스텀하여 만들 수 있다!
Javascript 내장 이터러블들은 그럼, Symbol.iterator를 프로퍼티로 이미 갖고 있다는 뜻
* Symbol 은 ES6에서 추가된 원시 데이터 유형 (기존: Boolean, Null, Undefined, Number, String, +Symbol)
* Symbol("abc") 같은 문자열을 할당해도 다르게 인식된다, 고유한 존재
* Object에서 키값으로 Symbol을 사용하는 경우 : String과의 차별성, 객체에 같은 속성을 string으로 추가시, 에러가난다. Symbol로 추가시 충돌이 없다.
for...of문
우리가 자주 사용하던 for문도 내부적으로 이터레이터의 next 메소드를 호출하여 이터러블을 순회하고, next 메소드가 반환하는 result 객체의 value 속성 값을 변수에 할당한다. 그리고 done 속성 값이 true가 될때까지 순회한다.
for문의 내부 동작 방식을 코드로 표현해보면 다음과 같다
// 이터러블
const iterable = [1, 2, 3];
// 이터레이터
const iterator = iterable[Symbol.iterator]();
for (;;) {
// 이터레이터의 next 메소드를 호출하여 이터러블을 순회한다.
const res = iterator.next();
// next 메소드가 반환하는 이터레이터 리절트 객체의 done 프로퍼티가 true가 될 때까지 반복한다.
if (res.done) break;
console.log(res);
}
커스텀 이터러블
1) 이터러블 객체 만들기
const fibo = {
[Symbol.iterator]() {
let [pre, cur] = [0,1];
const max = 10; // 10까지 피보나치
// Symbol.iterator 메소드(지금 정의하는 메소드)는 iterator를 반환해야함
// iterator는 next 메소드를 가져야함
// next 메소드는 { value, done } 객체를 반환해야함
return {
next() {
[pre, cur] = [cur, pre+cur];
return {
value : cur,
done : cur >= max
}
}
}
}
}
for (const num of fibo) {
console.log(num)
}
// spread 문법
console.log([...fibo])
// 디스트럭처링
const [first, second, ...rest] = fibo;
console.log(first, second, rest)
[실행 결과]
2) 이터러블을 생성하는 함수 만들기
function fiboIterableMaker(max) {
let [pre, cur] = [0, 1];
// Symbol.iterator 메소드(지금 정의하는 메소드)는 iterator를 반환해야함
// iterator는 next 메소드를 가져야함
// next 메소드는 { value, done } 객체를 반환해야함
return {
[Symbol.iterator]() {
return {
next() {
[pre, cur] = [cur, pre+cur];
return {
value : cur,
done : cur >= max
}
}
}
}
}
}
// 이터러블을 반환하는 함수에 이터러블의 최대값을 전달한다.
console.log(fiboIterableMaker(10))
for (const num of fiboIterableMaker(10)) {
console.log(num); // 1 2 3 5 8
}
[실행결과]
3) 이터러블이면서 이터레이터 객체를 생성하는 함수 만들기
// 이터러블 & 이터레이터 객체를 반환
// 이터러블이려면 Symbol.iterator 메소드 가져야함
// 이터레이터는 next 메소드 가져야함
// next는 value, done 리절트 리턴해야함
function fiboIterableIterator(max){
let [pre, cur] = [0,1]
return {
[Symbol.iterator](){
return this; // 지금의 이터레이터를 리턴
},
next() {
[pre, cur] = [cur, pre+cur];
return {
value : cur,
done : cur >= max
}
}
}
}
// iter는 이터러블
let iter = fiboIterableIterator(10);
console.log('iter', iter) //iter {next: ƒ, Symbol(Symbol.iterator): ƒ}
for (const num of iter) {
console.log(num) // 1,2,3,5,8
}
// iter는 이터레이터
console.log(iter.next()); //{value: 21, done: true}
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
/* css */
/* <a> elements with a title attribute */
a[title] {
color: red;
}
/* <a> elements with an href matching "https://example.org" */
a[href="https://example.com"] {
color: green;
}
/* <a> elements with an href containing "example" */
a[href*="example"] {
font-size: 2em;
}
/* <a> elements with an href ending ".org" */
a[href$=".org"] {
font-style: italic;
}
/* <a> elements whose class attribute contains the word "logo" */
a[class~="logo"] {
padding: 2px;
}