ES6 정리 javascript 2015

반응형

 

 

 

 

ES5

ECMAScript 는 Ecma 인터내셔널의 ECMA-262 기술 규격에 정의된 표준화된 스크립트 언어이다.

2009년도에 발표된 ES5 가 흔히 말하는 vanila script 의 기준이라 볼 수 있다.

IE9 에서 'use strict' 를 지원하지 않는 것 빼고는 대부분의 브라우저에서 이를 지원한다.

'use strict', String.trim(), Array.isArray(), Array.forEach(), Array.map(), Array.reduce(), Array.reduceRight()

Array.every(), Array.some(), Array.indexOf(), Array.lastIndexOf(), JSON.parse(), JSON.stringify(), Date.now()

'use strict' 란 strict 모드에서 javascript 코드를 수행하는 것이다.

선언하지 않은 variable, object 를 사용/수정/삭제 할 수 없다.

script나 function 시작 부분에 'use strict' 를 선언함으로서 사용한다.

 

function example1 () {

  i = 1;  // var 없는 변수는 전역이 됨. 만약 최상단에 'use strict' 사용하면 여기서 에러 남. 이렇게 사용 못하게 함.

  var j = 2;  // var 있으면 함수 스코프.

}

function example2 () {

  console.log(i);

}

example1();

example2();  // 1

console.log(j);  // 에러 남.

 

 

ECMAScript ES2016 ~ ES2020 정리

https://junhobaik.github.io/es2016-es2020/

 

 

ES6 = ES2015 임. ECMA Script 6

(ES6 lint 코딩 규칙 참고 사이트, JavaScript Standard Style (standardjs.com) )

 

* javascript 의 8가지 기본 타입 및 object 로 부터 파생된 Function 타입

const v1 = 1234;

const v2 = 123456789123456789123456789123456789n

const v3 = 'abc'

const v4 = true;

console.log(typeof v1, typeof v2, typeof v3, typeof v4)  // number bigint string boolean

 

const v5 = {}

const v6 = Symbol('foo')

const v7 = undefined

const v8 = null  // null 은 object 로 나온다. 초기 javascript 의 잘못 구현된 부분이었으나 하위 호환성으로 인해 그대로 사용

console.log(typeof v5, typeof v6, typeof v7, typeof v8)  // object symbol undefined object

 

function f1 () {}

console.log(typeof f1)  // function

 

class SomeClass {}

console.log(typeof SomeClass)  // function

 

// object 와 null 을 구분하기 위해서는 toString 을 하면 가능하다.

console.log(Object.prototype.toString.call(null))  // [object Null]

console.log(typeof [])  // object

console.log(Object.prototype.toString.call([]))  // [object Array]

 

// Symbol 은 유일한 속성 이름을 만들 때 사용, 이름 충돌 문제 해결 가능.

const idSymbol = Symbol('id')

const obj = { id: 1234 }

obj[idSymbol] = 456

console.log(obj)  // { id: 1234, Symbol(id): 456 }

const arr = []

console.log(arr[Symbol.iterator])  // 대표적 Symbol 에 iterator 가 있음

 

// 타입 변환 - String, Number, BigInt, Boolean

const v1 = String(1234)

const v2 = String(new Date())

console.log(typeof v1, v1)  // string 1234

console.log(typeof v2, v2)  // string Wed Feb 10 2021 09:44:43 GMT+0900 (대한민국 표준시)

const v3 = Number('1234')

const v4 = BigInt('1234')

console.log(typeof v3, v3)  // number 1234

console.log(typeof v4, v4)  // bigint 1234n

 

const v1 = Boolean(1234)

const v2 = Boolean(0)

console.log(typeof v1, v1)  // boolean true

console.log(typeof v2, v2)  // boolean false

const v3 = Boolean('abcd')

const v4 = Boolean('')

console.log(typeof v3, v3)  // boolean true

console.log(typeof v4, v4)  // boolean false

const v11 = !!1234

const v12 = !!0

const v13 = !!'abcd'

const v14 = !!''

 

// 그냥 Boolean, Number, ... 사용하는 것과 new 키워드 붙인 것과는 다르다.

// new 키워드 붙여서 만든 것은 object 를 생성하는 것.

// 추가적인 멤버를 사용할 것이 아니라면 new 로 object 생성하지 말고 그냥 기본형으로 사용하자.

console.log(typeof new Boolean(true))  // object

console.log(typeof new Number(1))  // object

console.log(typeof new String('abcd'))  // object

const s1 = new String('abcd')

s1.id = 1234

console.log('value: ', s1.valueOf())

console.log('id: ', s1.id)

 

// 값 비교

console.log(12 === 12); console.log('12' === '12'); console.log('12' === 12); console.log(0 === false); console.log(12 === true);

true true false false false

console.log(12 == 12); console.log('12' == '12'); console.log('12' == 12); console.log(0 == false); console.log(12 == true);

true true true true false  // 마지막은 true 일 것 같지만 false 이다. == 는 내부적으로 로직이 구현되어 있어 === 를 사용하길 추천.

 

// 숫자 파싱

console.log(Number.parseInt('12'))  // 12

console.log(Number.parseInt('12.34'))  // 12

console.log(Number.parseInt('12ab'))  // 12, . 이나 ab 처럼 문자열 만나면 끝내서 12 가 출력됨.

console.log(Number.parseFloat('12'))  // 12

console.log(Number.parseFloat('12.34'))  // 12.34

console.log(Number.parseFloat('12ab'))  // 12

console.log(Number.parseFloat('12.34.56'))  // 12.34

const v = Number.parseInt('ab')  // 숫자가 아닌 값을 넣어도 에러가 나는 것이 아니라 아래 처럼 NaN 이 된다.

console.log(v)  // NaN

console.log('v', Number.isNaN(v))  // v true

console.log('12', Number.isNaN(12))  // 12 false

const v = 1 / 0  // 0으로 나누어도 에러가 나는 것이 아니라 아래 처럼 Infinity 가 된다.

console.log(v)  // Infinity

console.log('Infinity', v === Infinity)  // Infinity true

console.log('Number.isFinite', Number.isFinite(v))  // Number.isFinite false

 

// Number 타입

자바스크립트 number 는 무조건 64 bit 부동소수점 (floating point) 방식을 사용함.

메모리 최적화에 불리. 32비트만 사용하고 싶어도 64비트만 가능. ArrayBuffer 를 통해 개선 가능.

// 부호(sign) 1 bit, 지수부(exponent) 11 bits, 가수부(fraction) 52 bits

// (-1)^부호 * rktnqn * 2^지수부

// 53 bits precision, 자바스크립트는 좀 더 복잡한 인코딩 방식을 사용함. 그래서 52 bits precision 아님.

// -(2^53 -1) ~ (2^53 -1)

// 9007199254740991, 약 16자리

console.log(Math.pow(2, 53) - 1)  // 9007199254740991

console.log(Number.MIN_SAFE_INTEGER)  // -9007199254740991

console.log(Number.MAX_SAFE_INTEGER)  // 9007199254740991

console.log(Number.MAX_VALUE) // 1.7976931348623157e+308, 이 큰 숫자를 가지고 +, - 하면 안전하지 않음.

console.log(NumberisSafeInteger(Number.MAX_SAFE_INTEGER))

console.log(NumberisSafeInteger(Number.MAX_SAFE_INTEGER + 1))

console.log(9007199254740995 - 10)  // 9007199254740986, 이상한 결과값.

console.log(9007199254740995n - 10n)  // 9007199254740985n, bigint 형으로 하면 정상값.

Number.isSafeInteger(a); Number.isSafeInteger(b), Number.isSafeInteger(a-b) 모두 true 이면 믿을 수 있다.

console.log(0.1 + 0.2 === 0.3)  // false 이다. 부동소숫점 계산을 하기 때문에 정확도에 한계가 있다.

console.log(0.1 + 0.2)  // 0.30000000000000004

console.log(Number.EPSILON)  // 2.220446049250313e-16 , 매우 작은 값을 의미.

function isSimilar (x, y) {

  return Math.abs(x - y) < Number.EPSILON

}

console.log(isSimilar(0.1 + 0.2, 0.3))  // true

console.log(Math.random())

console.log(Math.max(30, 10, 55))

console.log(Math.pow(5, 3)

function getRandomInt (min, max) {

  return Math.floor(Math.random() * (max - min + 1)) + min

}

console.log(getRandomInt(0, 10))

console.log(getRandomInt(0, 10))

console.log(getRandomInt(0, 10))

 

// Boolean

NaN, 0, '' 은 논리연산 && , || 에서 false

 

&& 연산자에서는 false 가 나오면 더이상 뒤에 것을 검토하지 않는다.

const c1 = 123;

const c2 = 'abc'

const v1 = c1 && c2

const v2 = c1 && c2 && 0

const v3 = c1 && 0 && c2

console.log({ v1, v2, v3 })  // { v1: 'abc', v2: 0, v3: 0 }

 

|| 연산자에서는 true 가 나오면 더이상 뒤에 것을 검토하지 않는다.

const v4 = c1 || c2

const v5 = '' || c2

console.log({ v4, v5 })  // { v4: 123, v5: 'abc' }

 

위의 && 와 || 연산으로 부터의 값은 boolean 값이 아닐 수 있기 때문에

앞에 !! 를 붙여서 항상 boolean 이 되도록 만들어 줄 수 있다.

const v6 = !!(c1 && 0 && c2)

const v7 = !!(c1 || c2)

console.log({ v6, v7 })

 

// && 는 다음과 같이 조건문 처럼 사용할 수도 있다.

const c1 = 123

const c2 = 0

c1 && console.log('log1')

c2 && console.log('log2')

 

// || 는 다음과 같이 기본값을 입력하는 방법으로 사용할 수 있다.

const price = 0

const name = ''

const price2 = price || 1000

const name2 = name || '이름을 입력해주세요'

// nullish coalescing , 기본값을 입력하는 문법. || 보다 이게 원래 정석

const person = {}

const name = person.name ?? 'unknown'

const name = person.name === undefined || person.name === null ? 'unknown' : person.name

// nullish coalescing 은 || 와 비슷하지만 0, '' 에 대해 기본값이 사용되지 않는다.

const product = { desc: '', price: 0 }

const descInput = product.desc ?? '상품 설명을 입력하세요'

const priceInput = product.price ?? 1000

// nullish coalescing 을 && 나 || 같은 연산자와 함께 사용하려면 괄호로 묶어주어야 한다.

const name = ''

const title = ''

const text = (name || title) ?? 'foo'

// 아래와 같이 함수 nullish 에 따른 함수 호출에도 사용할 수 있다.

console.log(name ?? getDefaultValue())

console.log(name || getDefaultValue())

 

 

* optional chaining (이거랑 nullish coalescing 은 ES2020 이다.)

non-null assertion 런타임에 전혀 영향을 끼치지 않는 syntax sugar 뿐이고요, optional chaining 경우는 a?.b 문법을 사용했을 (a === null || a === undefined) ? undefined : a.b transform됩니다

 

옵셔널 체이닝은 null 에 대비하는거고 ! 는 강제로 null 을 벗겨내는건데 (이름이 먼지 기억이 안나네요..) 
그럼 person 이 string 일 경우는 person.name 할 때 오류는 안나고 person 이 null 일 경우에 person.name 하면 오류나고 그럼에도 불구하고 const name = person?.name || '' 해주는 것이 null 에 대한 오류도 방지하는 기능도 있으니 물음표를 붙여야 하는것이 맞겠네요? person 이 확실히 null 이 아니라는 보장이 없다면요.

 

const person = null

const name = person && person.name

const name2 = person?.name

const name = person === null || person === undefined ? undefined : person.name

 

const person = {

  // getName: () => 'abc'

}

const name = person.getName?.()

console.log(name)  // undefined

 

function loadData(onComplete) {

  console.log('loading...')

  onComplete?.()

}

loadData()

 

const person = { friends: null, mother: null }

const firstFriend = person.friends?.[0]  // 배열에서도 사용 가능.

const prop = 'name'

const name = person.mother?.[prop]  // 객체에서 동적으로 속성값 이름을 입력할 때.

 

const name = 

  person && 

  person.friends &&

  person.friends[0] &&

  person.friends[0].mother &&

  person.friends[0].mother.name

const name2 = person?.friends?.[0]?.mother?.name

const person = {}

const name = person?.friends?.[0]?.mother?.name ?? 'default name'  // nullish coalescing 과 함께.

 

 

* module 모듈 시스템 require vs import

commonjs 는 nodejs 가 기본적으로 따르는 모듈 규약이다. 동기적인 서버 프로그래밍용인 nodejs 에 맞게

module.exports 를 require 를 통해 가져온다. module.exports 는 exports 가 참조한다.

module.exports 를 직접 쓰는 것 보다 exports.xxx 를 통해 사용하는 것이 편하고 효율적이다.

exports = { foo: 'bar' } 와 같이 사용하면 기존의 module.exports 에 key value 를 넣는게 아니라서 불가능합니다.

module.exports = { key, value } 하게 될 경우 모두 리셋되어 방금 넣은 것만 남게 된다.

amd 는 브라우저 쪽에서 비동기적으로 파일 다운받아 모듈 로딩하기 위한 용도. requirejs 가 대표적인 로더이다.

ES6 에서 새롭게 import, export 가 생겼고 export xxx, export yyy, 그리고 export default aaa 는 한개만..

import, export 는 위의 두가지를 통합한 것. commonjs 방식으로 컴파일 된다고 한다.

 

하나의 파일이 하나의 모듈이다. 모듈 시스템이 있으면

변수와 함수를 다른 모듈로 내보내거나 다른 모듈에서 가져오는 것이 가능하다.

원래는 자바스크립트에는 모듈시스템이 없었다.

자바스크립트 언어 차원에서 모듈시스템이 들어간 것은 2015년이다. 그것이 ESM (ECMAScript Modules)

ESM 은 import export 구문을 사용하는 것을 말한다.

Node.js 에서는 처음에는 CommonJS  라는 모듈 시스템을 사용. 최근에는 노드에서도 ESM 을 사용할 수 있다.

Node.js 에서 ESM 을 사용하려면 package.json 에서 "type": "module" 을 추가해주어야 한다.

브라우저에서 ESM 을 사용하려면 type="module" 을 script 태그에 넣어주면 된다.

<script type="module" src="./a.js"></script>

-- b.js

console.log('b.js')

exports.sayHello = function (name) {

  console.log(`hello~ ${name}`)

}

-- a.js

const bModule = require('./b.js')

console.log('a.js')

bModule.sayHello('Jack')

==> CommonJS 방식을 동일하게 ESM 으로 작성한 것.

-- b.js

console.log('b.js')

export function sayHello (name) {

  console.log(`hello~ ${name}`)

}

-- a.js

import { sayHello } = from './b.js'

console.log('a.js')

sayHello('Jack')

 

ESM 의 다양한 사용 방식

-- 1.js

export let v1 = 123

export const v2 = 'abc'

export function v3 () {}

export default function sayHello () {  // 하나의 모듈에서는 하나의 default 만 설정 가능.

  console.log('hello~!')

}

-- 3.js

// default 로 내보낸 것은 괄호 없이 가져올 수 있다. 이때 이름은 마음대로 입력할 수 있다.

// 괄호 안에 있는 것은 반드시 내보낸 이름 그대로 가져와야 한다. as 를 사용해 이름을 변경할 수 있다.

import f1, { v1, v2, v3 as myV3 } from './1.js'  

console.log({ v1, v2, myV3 })

f1()

-- 2.js

let v1 = 123

const v2 = 'abc'

function v3 () {}

export { v1, v2, v3 as v4 }  //  이렇게 모아서 한 번에 내보낼 수도 있다. as 로 이름변경도 가능.

export default function sayHello () {

  console.log('hello~!')

}

-- 4.js

import * as myModule from './1.js'  // export 된 모든 것을 하나의 객체로 담을 수 있다.

console.log(myModule.v1)

console.log(myModule.v2)

console.log(myModule.v3)

// 이때 default 로 내보낸 것은 default 라는 속성 이름으로 사용할 수 있다.

// 이것은 sayHello 함수를 호출하는 것.

myModule.default() 

 

-- index.js 이와 같이 여러 모듈을 index.js 에서 관리를 할 수 있다.

export * from './programmer/java-dev.js'

export * from './programmer/python-dev.js'

export { haveDinnerTogether as default } from './family.js'

-- 1.js 이렇게 index.js 에서 필요한 코드를 가져올 수 있다.

import haveDinnerTogether, { makeJavaProgram, makePythonProgram } from './person/index.js'

 

ESM 은 CommonJS 와 달리 기본적으로 정적인 모듈 시스템이다.

컴파일 타임에 어떤 것을 내보내고 어떤 것을 가져오는지 결정이 되어야 한다.

// 이 코드는 에러가 난다.

if (Math.random() < 0.5) { 

  import { b1 } from './b.js'

  console.log(b1)

} else {

  import { c1 } from './c.js'

  console.log(c1)

}

// 동적으로 import 를 하고자 하면 dynamic import 라는 것을 사용해야 한다.

// import 를 함수처럼 사용할 수 있다.

if (Math.random() < 0.5) {

  import('./b.js').then(({ b1 }) => {

    console.log(b1)

  })

} else {

  import('./c.js').then(({ c1 }) => {

    console.log(c1)

  })

}

// import 함수는 Promise 를 반환하므로 async await 를 사용할 수도 있다.

async function main () {

  if (Math.random() < 0.5) {

    const { b1 } = await import('./b.js')

    console.log(b1)

  } else {

    const { c1 } = await import('./c.js')

    console.log(c1)

  }

}

main()

 

// 모듈을 처음에 한 번만 실행이 된다. 다른 곳에서 이미 import 된 것을 또 import 할 경우

// 처음에 모듈이 실행될 때 내보낸 것을 그대로 가져와서 사용하는 것이다.

// ESM 은 순환참조를 허용한다.

-- a.js

import { sayHello } from './b.js'

console.log('a.js')

export const NAME = 'Jack'  // 만약 이게 sayHello() 아래에 있다면 에러가 난다.

// 왜냐하면 NAME 아직 export 되기 전에 sayHello 를 실행하면 NAME 이 없어 에러.

sayHello()

-- b.js

import { NAME } from './a.js'

console.log('b.js')

export const sayHello  = () => {

  console.log('hello~!', NAME)

  // console.log('hello~!', aModuleObject.NAME)  // 이와 같다고 볼 수 있다.

}

-- index.js

import './a.js'  // 만약 b.js 라면 에러가 난다.

// b.js 에서 a.js 를 import 할 때 a.js 가 실행되고 sayHello 는 아직 export 되지 않은 상태이기 때문.

 

위와 같이 순환참조에 의한 에러가 날 수 있으므로 이를 방지하려면 하나의 파일 가령 해당 폴더의 index.js 에서

모든 모듈의 import 를 담당하는 것이다. 그리고 index.js 에서 각 모듈의 실행 순서를 담당.

그렇게 되면 util 밑의 모듈의 실행 순서는 index.js 에서 정의한 순서로 결정이 되어 있기 때문에

순환 참조가 있다고 하더라도 안심하고 사용할 수 있다. index.js 에 막 집어넣으라는 건 아니고 에러 안나게

순서를 고려해서 넣으라는 의미이다. 아래의 경우 만약 index.js 에서 a.js 부터 가져왔다면 에러가 날 것이다.

-- 1.js

import './util/index.js'

-- util/index.js

export * from './b.js'

export * from './a.js'

-- util/a.js

import { sayHello } from './index.js'

console.log('a.js')

export const NAME = 'Jack'

sayHello()

-- util/b.js

import { NAME } from './index.js'

console.log('b.js')

export const sayHello = () => {

  console.log('hello~!', NAME)

}

 

 

* let 은 블록 스코프

function hello() {

 

  for (let i = 0; i < 10; i++) {

    console.log(i)

  }

 

  console.log(i) // Uncaught ReferenceError: i is not defined

 

}

let 이나 const 도 호이스팅은 일어난다. 최상단에 undefined 우선 선언되고 아래 부분에서 값이 셋팅되는 것.

하지만 let 이나 const 는 값이 셋팅되기 전에 사용하려고 하면 에러를 내어준다.

let 이나 const 는 블록스코프이지만 블록 내부에서 블록 외부의 let, const 를 사용할 수 있다.

블록 내부에 동일한 변수명의 let, const 가 있다면 해당 변수가 선순위이다. 

 

 

* clojure 의 특성

javascript 와 swift 의 클로저, kotlin 의 람다는 특성이 조금 달라 보인다.

swift 의 GCD (Grand Central Dispatcher) 를 사용하는 쓰레드가

멀티코어/멀티프로세스의 멀티쓰레드와 개념이 조금 달라 보인다. (확인 결과 GCD 는 실제 멀티쓰레드인 듯 하다.)

swift 의 이벤트 루프는 런루프 라는 용어로 존재한다고 한다.

async 의 코루틴인 이벤트 루프로 보이는데 이를 쓰레드라 부르는데 확인이 좀 필요해 보인다.

 

var list = document.querySelectorAll('li')

for (var i = 0; i < list.length; i++) {

  list[i].addEventListener("click", function() {

    console.log(i + "번째 리스트 입니다.")

  }

}

 

var i 를 let i 로 해주면 i가 블록 스코프가 되어 원하는 동작을 한다.

하지만 var i 로 했을 경우 javascript 클로저의 특성 중 클로저 내부가 외부의 변수값을 참조하므로

for 동작 당시에는 list[i] 에 0,1,2,3 이 들어가지만 마지막 i 값은 4가 되며

모든 li 의 이벤트의 i 는 그대로 i 를 참조하여 클릭시  "4번째 리스트 입니다." 가 찍히게 된다.

let 대신 함수 스코프 내에 새로운 함수(클로저)를 만들어 i 를 인자로 넣어 해당 함수 내부의 지역변수에

값을 저장하고 그것 리턴 시켜주는 방식으로 구현하여도 가능하다.

 

 

* const 는 값을 변경 못한다.

Array 와 같은 참조형일 경우 배열의 각 값은 변경할 수 있지만 (swift 의 let 상수와 차이) 

다른 배열을 재할당 하지는 못한다.

참조형의 재할당도 막고 싶다면 immer 와 같은 것을 사용하여 방지하거나

Object.preventExtensions, Object.seal, Object.freeze 같은 것을 사용.

const bar = Object.freeze({ prop1: 'a' })

bar.prop1 = 'b'  // 에러 남. Can not assign to read only property of 'prop1' of object.

es6 기준으로는 const, let 만 사용하고 var 사용하지 않는다. var 사용도 가능은 하다.

우선 const 사용하고 필요시 let 을 사용한다.

 

function home() {

  const foo = [1, 2, 3, 3]

  foo[0] = "test"  // 에러 안남.

  foo = ["1", "2"]  // 에러 발생.

  const bar = 1

  bar = 2  // 에러 발생.

}

 

 

* String

let str = 'hello world'

let str2 = 'hello world 2'

str.startsWith(str2); str.endsWith(str2); str.includes('2');

console.log(str.includes('hello', 6))  // false, 6번째 인덱스부터 찾음. 따라서 false 임.

 

const text1 = '할 일 목록\n* 운동하기\n* 요리하기'

const text2 = `할 일 목록

* 운동하기

* 요리하기

`

 

const s2 = 'abcd'

s2[1] = 'z'

console.log(s2)  // abcd, 바뀌지 않음. 문자열은 immutable 임.

const input = 'This is my car. The car is mine'

const output = input.replace('car', 'bike')  // input 은 변경되지 않고 새로운 문자열 만들어 output 에 리턴.

console.log({ input, output })

console.log(input.replace(/car/g, 'bike'))  // replace 는 맨 처음 부분만 바꿔줌. 그래서 정규식으로 해야 모두 바꿈.

console.log(input.replaceAll('car', 'bike'))  // replaceAll 은 최근에 나온 거라 구형브라우저에서는 polyfill 필요. 위 정규식과 동일.

 

const s1 = 'This is my car. The car is mine'

console.log(s1.substr(0, 4))  // This

console.log(s1.substr(5, 2))  // is

console.log(s1.substr(16))  // The car is mine 16번째 부터 끝까지.

let pos = s1.indexOf(' ')

console.log(s1.substr(0, pos))

pos = s1.lastIndexOf(' ')

console.log(s1.substr(pos + 1))  // mine

console.log(s1.slice(5, 7))  // is 두번째 매개변수가 길이가 아니라 인덱스 임.

 

console.log('12'.padStart(5, '0'))  // 00012

console.log('123'.padStart(5, '0'))  // 00123

console.log('123'.padStart(5, '*'))  // **123

console.log('123'.padEnd(5, '*'))  // 123**

const s1 = 'This is my car. The car is mine'

console.log(s1.match(/T[^\s-]*/g))  // ['This', 'The'] , 대문자 T 로 시작하는 단어를 모두 찾아라.

 

 

* for of

var data = [1, 2, undefined, NaN, null, '']

Array.prototype.getIndex = function () {}

for (var i = 0; i < data.length; i++) {  // 순서 보장.

  console.log(data[i])

}

data.forEach(function (value, idx, array) { // array 는 forEach 를 호출한 배열.

  // ES6 부터는 Map, Set 도 지원.

  // reduce 가능 로직이면 forEach 나 map 보다는 reduce 사용을 권장.

  console.log('value is: ', value)  // empty 요소 순회 하지 않음. 가령 var arr = [1, 2]; arr[10] = 'last'; // 3~9 is empty

})

for (let idx in data) {  // for in 은 object 중심. 순서 보장 안 함. empty 요소 순회 하지 않음. 더이상 지원은 안 됨.

  // 디버깅용으로만 사용할 것. 성능상으로도 forEach 보다도 느림.

  if (data.hasOwnProperty(idx)) console.log(data[idx])

  console.log(data[idx])  // hasOwnProperty 로 필터링 안해줄 경우 확장 요소인 getIndex 함수가 출력됨.

}

for (let value of data) { 

  // 반복가능객체 (Array, Map, Set, String, TypedArray, arguments 등) [Symbol.iterator] 속성이 있는 컬렉션 대상

  // forEach 와 달리 break, continue, return 구문 사용 가능. 순서 보장. 컬렉션 전용.

  console.log(value)  // for in 의 문제점 해결, empty 요소 순회 함.

}

var str = 'hello world!!!'

for (let value of str) {

  console.log(value)  // strin

}

 

 

* spread operator

let pre = ['foo', 'bar', 123, null]

let newData = [...pre]

console.log(pre === newData)  // false

let newData2 = [0, 1, 2, 3, ...pre, 4]  // [0, 1, 2, 3, 'foo', 'bar', 123, null, 4]

function test (a, b, c) {

  return a + b + c

}

console.log(test.apply(null, pre))

console.log(test(...pre))

const obj = { a: 10, b: 20 }

const newObj = {...obj, c: 30}

console.log('is obj and newObj same? ', obj === newObj)

console.log(newObj)

const arr = [1, 2, 3]

const newArr = [...arr, 30]

console.log('is arr and newArr same? ', arr === newArr)

console.log(newArr)

new Date(...[2019, 11, 25])  // == new Date(2019, 11, 25) , 월은 11 이 12월을 의미

 

 

* from 메소드

function addMark () {

  let newData = []

  for (let i = 0; i < arguments.length; i++) {

    newData.push(arguments[i] + '!')

  }

  let newArray = Array.from(arguments);  // from 을 사용해서 Array 로 만들어줌.

  // querySelectorAll 로 가져온 요소 리스트도 Array like 객체임. from 이용해서 Array 로 만들어줄 수 있음.

  newData = arguments.map(function (value) {  // 에러 남. arguments 는 array like 객체임.

    return value + '!';

  })

  newData = newArray.map(function (value) {  // 에러 안 남.

    return value + '!';

  })

  console.log(newData)

}

addMark(1, 2, 3, 4, 5)

 

 

* filter, includes, from 을 사용해서 문자열 'e' 가 포함된 노드로 구성된 배열을 구함

let list = document.querySelectorAll('li')

let listArray = Array.from(list)

console.log(toString.call(listArray))

let eArray = listArray.filter(function (v) {

  return v.innerText.includes('e')

})

console.log(eArray.length)

 

 

* destructuring

let data = ['foo', 'bar', 'loo', 'rar', 'zoo', {title: 'some title', content: 'some text', created: 12345678}]

let [foo, bar] = data

console.log(foo, bar)

// let [ , bar] = data  // Identifier 'bar' has already been declared

;  // 없으면 여기 끊어지지 않는다.

[ , bar] = data

console.log(bar)

let [,,,,,someThing] = data

let {title, created} = someThing

console.log(title, created)

let [,,,,,{title: title2, created: created2}] = data

console.log(title2, created2)

const arr = [1]

const [a = 10, b = 20] = arr  // 기본값 설정 가능. a 는 1, b 는 20

let a = 1; let b = 1;

[a, b] = [b, a]  // 서로 값을 바꿀 때. 원래 값을 바꾸려면 제3의 변수가 필요하지만 비구조화 문법을 사용하면 간단하다.

const arr = [1, 2, 3]

const [first, ...rest1] = arr

console.log(rest1)  // [2, 3]

const [a, b, c, ... rest2] = arr

console.log(rest2)  // []

 

let obj = {

  name: 'foo',

  address: 'bar',

  age: 20

}

let {name, age} = obj

console.log(name, age)

let {name: myName, age: myAge} = obj

console.log(myName, myAge)

const obj = { age: 21, name: 'Jack' }

const { age, name } = obj

const { name, age } = obj  // age, name 과 같다. 순서 상관없다.

const { a, b } = obj  // a, b 에는 undefined 가 할당될 것이다.

const obj = { age: undefined, name: null, grade: 'A' }

const { age = 0, name = 'noName', grade = 'F' } = obj

console.log({ age, name, grade })  // { age: 0, name: null, grade: 'A' } , null 은 기본값이 할당되지 않는다.

function getDefaultAge () {

  console.log('hello')

  return 0

}

const obj = { age: 30, grade: 'A' }

const = { age = getDefaultAge(), grade } = obj  // 기본값으로 함수를 할당할 수 있다.

console.log(age)  // 기본값이 할당될 때에만 해당 함수가 호출된다.

const { age, ...rest } = obj

const people = [

  { age: 30, name: 'Jack' },

  { age: 31, name: 'Daniel' }

]

for (const { age, name } of people) {

  // ...

}

const [{ prop: x } = { prop: 123 }] = []  // x = 123 , 빈 배열이므로 뒤의 기본값 객체 자체가 할당됨.

const [{ prop: x } = { prop: 123 }] = [{}]  // x = undefined , {} 가 존재하므로 하지만 {} 내에 prop 은 없으므로 x 는 undefined.

const index = 1

const { [`key${index}`]: valueOfTheIndex } = { key1: 123 }  // valueOfTheIndex 가 별칭

console.log(valueOfTheIndex)  // 123 , 비구조화 문법에서 계산된 속성명을 사용할 때는 반드시 별칭을 사용해야함.

const obj = {}

const arr = []

({ foo: obj,prop, bar: arr[0] } = { foo: 123, bar: true })

console.log(obj)  // { prop: 123 }

console.log(arr)  // [ true ]

 

window.document.querySelector('div').addEventListener('click', function({type, target}) {

  console.log(type, target.tagName)

}

 

 

* Set

중복없이 유일한 값을 저장하려고 할 때. 이미 존재하는지 체크할 때 유용함.

let mySet = new Set()

console.log(toString.call(mySet))  // [object Set]

 

mySet.add('foo')

mySet.add('bar')

mySet.add('foo')

 

console.log(mySet.has('foo'))

 

mySet.delete('foo')

 

mySet.forEach(function (v) {

  console.log(v)

}

 

 

* WeakSet

참조를 가지고 있는 객체만 저장이 가능함.

객체 형태만 중복없이 저장하려고 할 때 유용하다.

let arr = [1, 2, 3, 4]

let ws = new WeakSet()

ws.add(arr)

// ws.add(111)  // Invalid Type

// ws.add('111')  // Invalid Type

// ws.add(null)  // Invalid Type

ws.add(function () {})

console.log(ws);

 

let arr1 = [1, 2, 3, 4]

let arr2 = [5, 6, 7, 8]

let obj = {arr1, arr2}  // {arr1: Array(4), arr2: Array(4)} 키값이 arr1, arr2 인 값들을 가지는 객체 생성

let ws1 = new WeakSet()

ws1.add(arr1)

ws1.add(arr2)

ws1.add(obj)

arr = null

console.log(ws1)  // 실제로는 arr 를 ws1 에 가지고는 있는 상태이지만

console.log(ws1.has(arr))  // 이렇게 has 를 통해서는 false 가 나와서 ws1 에서 삭제된 것으로 나온다.

// 객체들이 가비지 컬렉션 되는 형식으로 사용할 수 있다.

// 객체 형태만 저장할 수 있고 해당 객체를 null 해주면 WeakSet 상에서 삭제된 것으로 인식한다.

 

 

* Map & WeakMap

let wm = new WeakMap()

let myfun = function () {}

// 이 함수가 얼마나 실행되었는지를 알려고 할 때.

wm.set(myfun, 0)

let count = 0

for (let i = 0; i < 10; i++) {

  count = wm.get(myfun)

  count++;

  wm.set(myfun, count)

}

console.log(wm.get(myfun))

myfun = null;

console.log(wm.get(myfun))  // undefined

console.log(wm.has(myfun))  // false

 

 

* tagged template literals

const data = [

  {

    foo: 'foo',

    bar: 'bar',

    items: ['orange', 'apple', 'banana']

  },

  {

    foo: 'foo',

    bar: 'bar'

  }

]

const template = `<div>Hello, ${data[0].foo]}</div>

console.log(template)

// Tagged template literals

function fn (tags, foo, items) {

  console.log(tags)

  if (typeof items === 'undefined') {

    items = '<span style="color:red">no items</span>'

  }

  return (tags[0] + foo + tags[1] + items + tags[2])

}

data.forEach((v) => {

  let template = fn`<h2>Hello, ${v.foo} !!</h2> <h4>wishlist</h4><div>v.items}</div>`

  document.querySelector('#message').innerHTML += template

  console.log(template1)

})

// strings 의 갯수는 항상 expressions 의 갯수보다 한개가 많다.

function taggedFunc (strings, ...expressions) {

  console.log({ strings, expressions })

  return 123

}

const v1 = 10

const v2 = 20

const result = taggedFunc`a-${v1}-b-${v2}`  // { strings: ['a-', 'b-',''], expressions: [10, 20] }

 

function highlight (strings, ...expressions) {

  return strings.reduce(

    (acc, str, i) => 

      expressions.length === i

        ? `${acc}${str}`

        : `${acc}${str}<strong>${expressions[i]}</strong>`,

      '')

  )

}

const v1 = 10

const v2 = 20

const result = highlight`a ${v1} b ${v2}`  // hightlight(['a ', ' b ', ''], v1, v2) 과 동일함. 하지만 template 이 더 생산성 좋다.

console.log(result)  // a <strong>10</strong> b <strong>20</strong>

 

 

* Arrow function

화살표 함수와 일반 함수와 다른점은 this 와 arguments 가 바인딩되지 않는다는 점.

따라서 화살표 함수에서 arguments 가 필요하면 (...rest) => console.log(rest) 와 같이 사용하면 된다.

파라미터가 한 개이면 () 생략 가능 (a) => a + 1

let newArr = [1, 2, 3, 4, 5].map((v) => v * 2)

// this context of Arrow function

const myObj = {

  runTimeout () {

    setTimeout(function () {

      console.log(this === window)  // true

      this.printData()  // this.printData is not a function, so bind(this)

    }.bind(this), 200)

  },

  runTimeoutArrow () {

    setTimeout(() => {

      console.log(this === window)  // false, this는 myObj 를 가리킨다.

      this.printData()  // bind(this) 없이도 잘 수행된다.

    })

  },

  printData () {

    console.log('hi')

  }

}

myObj.runTimeout()

 

const el = document.querySelector('p')

el.addEventListener('click', function (evt) {

  console.log(this)  // [object HTMLParagraphElement], p Tag 를 가리킴.

})

const myObj = {

  register () {

    el.addEventListener('click', function (evt) {

      this.printData()  // this.printData is not a function, this 는 el 즉 p Tag 를 가리킴.

    }.bind(this))  // bind(this) 해주면 myObj 가 this 라서 function 의 this 가 myObj 가 됨.

  },

  register2 () {

    el.addEventListener('click', (evt) => {

      this.printData(evt.target)

    })

  },

  printData (el) {

    console.log('clicked!!', el.innerText)

  }

}

 

 

* function default parameter

function sum (value, size) {

  size = size || 1

  return value * size

}

console.log(sum(3))

function sum1 (value, size={value: 1}) {  // 그냥 size=1 도 된다.

  return value * size.value

}

console.log(sum1(3))

 

 

* rest parameters (매개변수에 ... 이 들어갔다하면 배열로 받는 것. spread operator 와 비교, 혼동할 수 있음)

function checkNum () {

  // call(null, 1, 2, 3) , apply(null, [1, 2, 3]) , null 부분은 this 를 의미. toString 함수라면 해당 객체의 toString 호출하는 의미.

  // toString.call(something) 은 타입을 출력하기 위한 트릭 중 하나. something.toString() 과 같은 의미.

  console.log(toString.call(arguments))  // '[object Arguments]'

  const argArray = Array.prototype.slice.call(arguments)  // arguments.slice() 와 같은 의미. 하지만 arguments 는 slice 메소드가 없다.

  // 그래서 Array 의 slice 메소드를 가져와서 Arguments 인 Array like 객체를 this 로 바꿔 slice 를 호출하는 것.

  console.log(toString.call(argArray))  // '[object Array]'

  const result = argArray.every((v) => typeof v === 'number')

  console.log(result)

}

const result = checkNum(10, 2, '55')

const result = checkNum(10, 2, 3, 4, 5, '55')

function checkNum1 (...argArray) {  // rest parameters , 매개변수에 ... 이 들어갔다하면 배열로 받는 것.

  console.log(toString.call(argArray))

  const result = argArray.every((v) => typeof v === 'number')

  console.log(result)

}

 

 

* class

자바스크립트에서 클래스는 없다. es6 에서 class 라는 키워드가 생긴 것이다.

es5 에서는 function 을 만들고 prototype 으로 메소드를 만들면

new 키워드를 통해서 function 의 this 에 정의된 프로퍼티를 객체의 this 에 복사하고

__proto__ 에 prototype 에 정의된 메소드를 복사하여 새로운 객체를 만들어 반환하는 방식이다.

function Health (name) {

  this.name = name

}

Health.prototype.showHealth = function () {

  console.log(this.name + '님 안녕하세요.')

}

const h = new Health('foo')

h.showHealth()

 

// es6 class 키워드로 위와 동일한 것을 구현. class 키워드 사용할 지 그냥 function 으로 할 지는 호불호.

class Health {

  constructor(name, lastTime) {

    this.name = name

    this.lastTime = lastTime

  }

  showHealth () {  // 결국 showHealth 는 prototype 에 들어간다.

    console.log('안녕하세요' + this.name)

  }

}

console.log(toString.call(Health))  // '[object Function]' 결국 함수이다.

const myHealth = new Health('foo')

console.log(myHealth)  

// [object Object] {lastTime: undefined, name: 'foo'} 이렇게 객체를 반환.

// __proto__ 에 showHealth, constructor 등이 들어 있다.

myHealth.showHealth()

 

class Person {

  constructor (name) {

    this.name = name

  }

  sayHello () {

    console.log(`hello~ ${this.name}!`)

  }

}

console.log(typeof Person)

console.log(Object.keys(Person.prototype))  // class 는 prototype 에 keys 로 직접 접근을 제한하도록 함.

console.log(Object.getOwnPropertyNames(Person.prototype)) getOwnPropertyNames 를 사용해야 함.

const person = new Person('Jack')

console.log(Object.keys(person))  // [ 'name' ] 클래스의 this 의 name 은 프로토타입이 아닌 객체 자체에 들어있다.

console.log(Person.prototype.constructor === Person)  // true

console.log(Person.prototype.sayHello)  // [Function: sayHello]

 

class Person {

  constructor (name) {

    this._name = name

  }

  get name () {  // getter 만 설정하면 readonly 로 사용할 수 있다.

    return this._name

  }

  set name (value) {

    if (value.length < 4) {

      console.log('name is too short')

      return

    }

    this._name = value

  }

  sayHello () {

    console.log(`I'm Person`)

  }

}

const person = new Person('Jack')

console.log(person.name)  // Jack

person.name = 'ab'  // name is too short

console.log(person.name)  // Jack

 

class Programmer extends Person {

  constructor (name, language) {

    super(name)  // super 를 반드시 호출해주어야 한다.

    this.language = language

  }

  // 만약 constructor 를 정의하지 않는다면 아래와 같은 constructor 가 기본적으로 사용된다.

  constructor (...args) {

    super(...args) 

  }

  sayHello () {

    // super.sayHello()

    this.__proto__.__proto__.sayHello.call(this)

    // this 와는 관련없이 super 를 사용한 메서드는 자기자신의 클래스를 기억하고 있다.

    // 이렇게 클래스를 기억하기 위해서 사용하는 변수를 자바스크립트 표준에서는 HomeObject 라고 부른다.

    Programmer.prototype.__proto__.sayHello.call(this)

    console.log(`I'm Programmer`)

  }

}

// class 는 결국 Function 이므로 prototype 속성을 갖는다.

// 상속받는 쪽의 프로토타입 객체는 부모의 프로토타입 객체를 프로토타입 체인으로 연결을 해서 갖고 있다.

console.log(Object.getPrototypeOf(Programmer.prototype) === Person.prototype)

// 자식의 프로토타입 객체는 부모의 프로토타입 객체를 프로토타입 체인으로 연결한다.

console.log(Programmer.prototype.__proto__ === Person.prototype)

// 따라서 이렇게 프로토타입 체인을 끊어주면 sayHello 의 super.sayHello() 부분에서 에러가 난다.

Object.setPrototypeOf(Programmer.prototype, {})

// 프로토타입 객체 뿐 아니라 클래스(함수) 자신도 프로토타입 체인으로 연결되어 있다.

console.log(Object.getPrototypeOf(Programmer) === Person)

// 위에서 언급했든 HomeObject 즉, 자기자신의 클래스를 기억하기 때문에 이렇게 메서드를 추출해서 호출할 수도 있다.

const f1 = person1.sayHello

f1() 

 

// 부모의 메서드 호출하려면 super.sayHello() 처럼 하면되고

// 자기자신의 메서드 호출하려면 this.getRandom() 처럼 하면된다.

 

class Person {

  age = 23  //  이렇게 = 등호를 사용하면 프로토타입이 아니라 객체 자신에 할당이 된다.

  // 이러한 문법을 클래스 필드 (class fields) 라고 부른다.

  constructor (name) {

    this.name = name

  }

  // 이렇게 멤버 변수 뿐만 아니라 메서드에서도 등호를 사용할 수 있다.

  printName = () => {  //  이 메서드도 프로토타입이 아니라 객체에 할당.

    // 이렇게 클래스 필드와 화살표 함수를 같이 사용하면 아래 setTimeout 같은 곳에

    // 이 printName 을 그대로 전달하여 사용할 수 있다. (정적, 아래 this 쪽 참고)

    // 화살표 함수의 this 는 생성 당시의 this 를 가리키기 때문에 this.name 이 의도한 대로 사용될 수 있다.

    console.log(this.name)

  }

  printName2 = function () { // 일반 함수의 this 는 함수를 호출한 주체를 가리킨다.

// 호출될 당시의 상황에 따라서 this 가 바뀜. (동적)

    console.log(this.name)

  }

}

// 이렇게 프로토타입 객체를 확인해보면 

console.log(Person.prototype.age, Person.prototype.printName) // undefined, undefined

const person1 = new Person('Jack')

const person2 = new Person('Jane')

person1.age = 100

console.log(person1.age, person2.age)  // 100 23 이렇게 각각 값을 가진다. 객체별로 관리가 된다. 등호 = 일 경우.

setTimeout(person1.printName, 100)  // Jack

setTimeout(person1.printName2, 100)  // undefined 호출된 당시에 setTimeout 의 콜백함수로 사용되었기 때문에 this 는 전역임.

 

// 부모 클래스에서 클래스 필드로 메서드를 정의하면 자식에서 super 를 사용해서 호출할 수 없다.

// super 는 프로토타입을 기반으로 동작함.

 

// static 키워드를 사용하면 정적 멤버 변수나 정적 메서드를 정의할 수 있다.

// 객체나 프로토타입에 정의되는 것이 아니라 생성자 함수 자체에 정의된다.

// static 키워드 붙은 것도 상속이 된다. 부모 자식 생성자 함수 끼리도 프로토타입 체인으로 연결되어 있다.

 

// public, protected, private

// # 을 앞에 붙이면 private 가 된다. 실제로 자식이나 외부에서 접근할 수 없다.

// protected 는 자식에서는 접근할 수 있다는 뜻이지만 현재 javascript class 에는 존재하지 않는다.

// 단, _xxx 와 같이 앞에 언더바를 붙여서 관례적으로 사용한다.

class Person {

  #name = ''

  _age = 23  // protected 로 취급하는 컨벤션.

  constructor (name) {

    this.#name = name

  }

  #sayHello () {  // 메서드도 private 로 사용할 수 있다. 아직 표준은 아니지만 곧 표준이 될 것이다.

    console.log(`hello~ ${this.#name}!`)

  }

}

class Programmer extends Person {

  sayHello () {

    console.log(this.name)  // undefined

    console.log(this['#name'])  // undefined

  }

}

const person1 = new Programmer('Jack')

person1.sayHello()

console.log(person1.name)  // undefined

console.log(person1['#name'])  // undefined

 

// 자바스크립트에 내장된 클래스를 상속받아서 새로운 클래스를 만들 수도 있다.

class PersonArray extends Array {

  filterYouger (age) {

    // filter 를 적용하면 새로운 객체가 생성된다.

    // 이 때는 Array 의 객체가 생성되는 게 아니고 PersonArray 의 객체가 생성이 된다.

    return this.filter(item => item.age <= age)

  }

}

const arr = new PersonArray(

  { name: 'Jack', age: 20 }

  { name: 'Jane', age: 30 }

  { name: 'Joy', age: 40 }

)

const arr2 = arr.filterYouger(35)

console.log(arr2)

const arr3 = arr2.filterYouger(25)  // arr2 는 PersonArray 의 객체이므로 filterYounger 를 또 호출할 수 있다.

console.log(arr3)

 

// instanceof

class Person {}

function Person2 () {}

const person1 = new Person()

const person2 = new Person2()

const arr = []

console.log(person1 instanceof Person)

console.log(person2 instanceof Person2)  // 생성자 함수도 검사할 수 있다.

console.log(arr instanceof Array)  // 배열 리터럴 문법을 사용해도 Array 인지 검사할 수 있다.

 

//  instanceof 는 프로토타입을 기반으로 검사.

class Person {}

class Programmer extends Person {}

const person1 = new Programmer()

console.log(person1 instanceof Programmer)  // true

console.log(person1 instanceof Person)  // true

console.log(person1 instanceof Object)  // true

console.log(person1.__proto__ === Programmer.prototype)  

console.log(person1.__proto__.__proto__ === Person.prototype)

console.log(person1.__proto__.__proto__.__proto__ === Object.prototype)

 

// instanceof 는 프로토타입 기반으로 검사하지만 Symbol.hasInstance 라는 함수를 가지고 있으면 이것을 사용.

class Person {

  constructor (personId) {

    this.personId = personId

  }

  static [Symbol.hasInstance](obj) {

    return !!obj.personId  // instanceof 가 personId 라는 속성이 있으면 Person 의 객체라고 판단함.

  }

}

const person1 = new Person()

const person2 = {

  personId: 2

}

const person3 = {}

console.log(person1 instanceof Person)  // true

console.log(person2 instanceof Person)  // true

console.log(person3 instanceof Person)  // false , personId 라는 속성이 없으므로...

 

// 자바스크립트에서는 다중 상속을 지원하지 않는다.

 

 

* Object assign

object assign 은 es5 부터 나옴.

Object.create 는 new 키워드의 원래 방식이라고 볼 수 있다. 이게 원래 가장 표준적인 방식.

prototype (__proto__) 를 가지는 객체를 생성하는 메소드임.

var healthObj = {

  showHealth: function () {

    console.log('오늘 운동시간: ' + this.healthTime)

  }

}

const myHealth = Object.create(healthObj)

// 아래와 같이 직접 객체 변수를 넣어주어야 한다.

myHealth.healthTime = '11:20'

myHealth.name = 'foo'

console.log(myHealth)

// Object.assign 을 통해 좀 더 깔끔하게 객체 변수를 넣어줄 수 있다.

const myHealth1 = Object.assign(Object.create(healthObj, { name: 'foo', lastTime: '11:20' })

console.log(myHealth1)

 

Object.assign 은 immutable 객체를 만드는 방법에 사용되기도 한다.

const previousObj = { name: 'foo', lastTime: '11:20' }

const myStatus = Object.assign({}, previousObj, { 'lastTime': '12:30', 'name': 'bar', 'age': 25 })

const myStatus1 = Object.assign(previousObj, {name: 'bar1'})

console.log(myStatus)

console.log(previousObj === myStatus)  // false, 맨 앞에 빈 객체를 넣어주면 immutable 하게 객체를 생성할 수 있다.

console.log(previousObj === myStatus1)  // true, Object.assign 은 맨 앞의 객체에 뒤의 객체를 합성하는 것.

 

Object.assign 은 immutable 객체 생성용으로 많이 사용되며 Object.setPrototypeOf 는 prototype 에 추가할 때 사용.

const healthObj = {

  showHealth: function () {

    console.log('exercise time: ' + this.healthTime)

  },

  setHealth: function (newTime) {

    this.healthTime = newTime

  }

}

const newObj = Object.setPrototypeOf({ name: 'foo', lastTime: '11:20' }, healthObj)

console.log(newObj === healthObj)  // false

console.log(newObj)

// setPrototypeOf 의 Polyfill 구조.

// __proto__ 에 직접 넣는 건 좋지 않다고 하였으나 이제는 __proto__ 는 표준이 됨.

// 하지만 성능성으로도 그렇고 그냥 setPrototypeOf 를 사용하는게 권장됨. 

Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {

  obj.__proto__ = proto  

  return obj

}

 

setPrototypeOf 는 상속 구조를 만들 때 유용하다.

__proto__ 하위의 __proto__ 하위의 __proto__ ... 과 같은 식으로 만들어지며

__proto__ 를 따라서 쭉 내려가며 해당 멤버가 있는지 찾는다.

// parent

const healthObj = {

  showHealth: function () {

    console.log('today exercise time: ' + this.healthTime)

  },

  setHealth: function (newTime) {

    this.healthTime = newTime

  }

}

// child obj

const healthChildObj = {

  getAge: function () {

    return this.age

  }

}

Object.setPrototypeOf(healthChildObj, healthObj)

const childObj = Object.setPrototypeOf({

  age: 22

}, healthChildObj)

childObj.setHealth('11:55')

childObj.showHealth()

 

 

* object, array

const obj = {

  age: 30,

  name: 'Jack'

}

const obj2 = new Object({

  age: 30,

  name: 'Jack'

})

// Object 에는 객체 자체의 멤버메소드는 사용되는 건 거의없고 Object.keys 와 같이 유용한 클래스 메소드들이 있다.

console.log(Object.keys(obj))  // ['age'm 'name']

console.log(Object.values(obj))  // [30, 'Jack']

console.log(Object.entries(obj))  // [['age', 30], ['name', 'Jack']]

for (const [key, value] of Object.entries(obj)) {  // destructuring 통해서 쉽게

  console.log(key, value)

}

obj.city = 'seoul'

obj.age = 20

delete obj.city

delete obj['name']

 

// 단축속성명, shorthand property names

const name = 'Jack'

const obj = {

  age: 30,

  name,

  getName() {

    return this.name

  }

}

 

// 계산된 속성명, computed property names

function makeObject1(key, value) {

  const obj = {}

  obj[key] = value

  return obj;

}

function makeObject2(key, value) {

  return ( [key]: value }  // [key] 이렇게 대괄호로 감싸주는 방식을 computed property names 라고 한다.

}

 

const arr = [1, 2, 3]

const arr2 = new Array(1, 2, 3)

console.log(typeof arr === 'object')  // true , Array 는 Object 타입이다.

console.log(Object.values(arr))  // [ 1, 2, 3 ] , 그래서 Object 의 values 도 사용할 수 있다.

 

map, filter, reduce

some (하나라도 만족하면 true), every, includes, find (값), findIndex (인덱스)

push, pop

splice : 아이템을 삭제하고 추가하는 것을 동시에 할 수 있음. 기존 배열을 수정함. not immutable

const arr = [1, 2, 3]

arr.splice(1, 1); console.log(arr);  // [1, 3] , 1번 인덱스에서 1개를 삭제

arr.splice(1, 0, 10, 20, 30); console.log(arr)  // [1, 10, 20, 30, 2, 3] , 1번 인덱스에서 0개 삭제 후 해당 위치에 아이템 추가

arr.splice(1, 3, 40, 50); console.log(arr)  // [1, 40, 50, 2, 3] 

arr.sort()  // [1, 3, 40, 50]

// 음수를 반환하면 a, b 의 순서를 변경하지 않겠다는 의미

arr.sort((a, b) => (a % 10) - (b % 10))  // % 연산으로 1의 자리만 보겠다는 의미

console.log(arr)  // [40, 50, 1, 3]  // 40 과 50 의 1의 자리 숫자는 0이기 때문에 앞으로...

 

 

* first class citizen

일급함수: 함수가 다른 변수처럼 취급되면 그 언어는 일급함수를 가지고 있다고 한다.

const add10 = function (a) {

  return 10 + a

}

function apply (arr, op) {

  return arr.map(op)

}

apply([1, 2, 3], add10)

function makeAdd(v1) {

  return function (v2) {

    return v1 + v2

  }

}

const add3 = makeAdd(3)

console.log(add3(10))

const add7 = makeAdd(7)

console.log(add7(10))

 

// call stack

// global execution context

// execution context

//    lexical environment, {변수이름: 값}

 

function printLog (a, ...rest) {}  // 아래와 동일하다고 볼 수 있다.

function printLog (a) {

  const rest = Array.from(arguments).splice(1)

}

 

// named parameter

function getValues1 (numbers, greaterThan, lessThan) {

  return numbers.filter(item => greaterThan < item && item < lessThan)

}

function getValues2 ({ numbers, greaterThan, lessThan }) {  // 중괄호를 사용한다.

  return numbers.filter(item => greaterThan < item && item < lessThan)

}

const numbers = [10, 20, 30, 40]

const result1 = getValues1(numbers, 5, 25)

const result2 = getValues2({ numbers, greaterThan: 5, lessThan: 25 })  // 중괄호를 사용한다.

 

function getValues({ numbers, greaterThan = 0, lessThan = Number.MAX_VALUE }) {  // 기본값은 동일한 방식.

  return numbers.filter(item => greaterThan < item && item < lessThan)

}

const numbers = [10, 20, 30, 40]

console.log(getValues({ numbers, greaterThan: 5, lessThan: 25}))

console.log(getValues({ numbers, greaterThan: 15 }))

console.log(getValues({ lessThan: 25, numbers }))

// getValues(numbers, undefined, 25)  // named parameter 아니면 중간에 undefined 로 자리를 채워야 한다. 불편.

 

function f1 ({ p1, p3, ...rest }) {

  console.log({ p1, p3, rest })

}

f1({ p1: 'a', p2: 'b', p3: 'c', p4: 'd' })  // { p1: 'a', p3: 'c', rest: { p2: 'b', p4: 'd' } }

f1({ p1: 'a', p2: 'b' })  // { p1: 'a', p3: 'b', rest: {} }

 

 

* this

function Counter () {

  this.value = 0

  this.add = amount => {  // 화살표 함수의 this 는 생성될 당시의 this 를 가리킴. 생성될 당시라는 건 Counter() 함수.

// 이 화살표 함수가 생성될 당시의 this 로 고정이 됨. (정적)

    this.value += amount

  }

}

const counter = new Counter()

console.log(counter.value)  // 0

counter.add(5)

console.log(counter.value)  // 5

const add = counter.add

add(5)

console.log(counter.value)  // 10

 

function Counter2 () {

  this.value = 0

  this.add = function (amount) {  // 일반 함수의 this 는 함수를 호출한 주체를 가리킨다.

// 호출될 당시의 상황에 따라서 this 가 바뀜. (동적)

    this.value += amount

    console.log(this === window)  // 아래의 counter2.add(5) 는 false, add(5) 는 true

  }

}

const counter2 = new Counter2()

console.log(counter2.value)  // 0

counter2.add(5)  // counter2 가 이 add 함수를 호출한 주체가 되는 것이고 따라서 this 는 counter2 를 가리킴.

console.log(counter2.value)  // 5

const add = counter2.add

add(5)  // 주체가 따로 보이지 않기 때문에 this 는 전역 객체 (브라우저는 window, 노드는 global) 를 가리킴.

console.log(counter2.value)  // 5

 

// class 의 경우도 동일하다.

class Counter3 {

  value = 0

// 일반 함수로 정의했을 때의 this 는 동적으로 결정.

  add (mount) {

    this.value += amount

  }

// 화살표 함수로 정의했을 때의 this 는 정적으로 결정.

  add = (mount) => {  // 아, 그래서 React 에서... ES6 이후 bind(this) 대신 이렇게...

    this.value += amount

  }

}

 

// 객체 내부의 함수는

const counter3 = {

  value: 0,

  add: function (amount) {

    this.value += amount

    console.log(this === window)  // 아래의 counter3.add(5) 는 false, add(5) 는 true

  },

  value2: 0,

  add2: (amount) => {

// 이 화살표 함수를 생성할 당시의 this 는 이 화살표 함수를 감싸고 있는 일반 함수가 없기 때문에

// 항상 전역 객체를 가리키게 된다.

// 따라서 이 화살표 함수가 실행되어도 항상 전역 객체를 가리키기 때문에

// 이객체의 value2 는 증가하지 않는다. 그래서 아래에 모두 0 이 나온 것.

    this.value2 += amount

  }

}

console.log(counter3.value)  // 0

counter3.add(5)  // counter3 가 add 일반함수를 호출한 주체가 되는 것이고 따라서 this 는 counter3 를 가리킴.

console.log(counter3.value)  // 5

const add3 = counter3.add

add3(5)  // 주체가 따로 보이지 않기 때문에 this 는 전역 객체 (브라우저는 window, 노드는 global) 를 가리킴.

console.log(counter3.value)  // 5

console.log(counter3.value2)  // 0

counter3.add2(5)

console.log(counter3.value2)  // 0

const add32 = counter3.add2

add32(5)

console.log(counter3.value2)  // 0

 

 

* callback, Promise, AsyncAwait

funciton requestData1 (callback) {

  callback(data)

}

function requestData2 (callback) {

  callback(data)

}

function onSuccess1 (data) {

  console.log(data)

  requestData2(onSuccess2)

}

function onSuccess2 (data) {

  console.log(data)

}

requestData1(onSuccess1)

 

// 위의 콜백 방식을 promise 방식으로 처리

requestData1()  // onSuccess1 즉, resolve 에 들어가는 data 가 전달되는 Promise 객체를 반환하는 함수

  .then(data => {

    console.log(data)

    return requestData2()  // onSuccess2 즉, resolve 에 들어가는 data 가 전달되는 Promise 객체를 반환하는 함수

  })

  .then(data => {

    console.log(data(

    // ...

  })

 

// 위의 방식은 직렬 처리. requestData1 끝나고 requestData2 가 처리된다.

// 이와 같이 하면 병렬 처리.

requestData1().then(data => console.log(data))

requestData2().then(data => console.log(data))

// 이와 같이 여러 Promise 를 병렬로 처리하고 싶은 경우 Promise.all 을 사용할 수 있다.

// Promise.all 함수는 Promise 객체를 반환한다. 그래서 then 메서드 사용가능.

// Promise.all 함수가 반환하는 Promise 객체는 입력된 모든 Promise 객체가 fulfilled 상태가 되어야 fulfilled 상태가 된다.

// 하나라도 rejected 상태가 된다면 Promise.all 함수가 반환하는 Promise 객체도 rejected 상태가 된다.

Promise.all([requestData1(), requestData2()]).then(([data1, data2]) => {

  console.log(data1, data2)

})

// Promise.all 의 배열에 Promise 객체가 아닌 그냥 값을 넣으면 fulfilled 상태의 해당 값을 가진 Promise 객체가 된다.

 

// Promise.race 라는 함수도 있다.

// 여러 개의 Promise 중에서 가장 빨리 settled 상태가 된 Promise 를 반환

Promise.race([requestData(), new Promise((_, reject) => setTimeout(reject, 3000))])

  .then(data => console.log('fulfilled', data))

  .catch(error => console.log('rejected'))

 

// Promise 의  then 은 새로운 Promise 를 반환하니 주의.

function requestData() {

  const p = Promise.resolve(10)

  return p.then(data => {  // then 에서 리턴되는 Promise 를 리턴해야 한다.

    return data + 20

  })

  return p // <== 이런 오류를 범할 수 있다.

}

 

// Promise 를 중첩해서 사용하는 것은 콜백함수처럼 복잡해질 수 있으므로 하지 말아야 한다.

requestData()

  .then(result => {

    return requestData2(result).then(result2 => {  // 이런식으로 사용하지 말 것.

      console.log({ result2 })

    })

  })

  .then(() => console.log('end')

// ==>

requestData()

  .then(result => {

    return requestData2(result)  // 이렇게 할 것.

  })

  .then(result2 => {  // 이렇게 할 것.

    console.log({ result2 })

  })

  .then(() => console.log('end')

 

const p1 = new Promise((resolve, reject) => {})

const p2 = Promise.reject('error message')  // rejected 상태의 Promise 객체를 만듬.

const p3 = Promise.resolve(param)  // fulfilled 상태의 Promise 객체를 만듬.

 

// 대기중 (pending)

// 성공 (fulfilled), 실패 (rejected) => settled

// pending 상태에서만 다른 상태로 변할 수 있다.

 

requestData().then(onResolve, onReject)

Promise.resolve(123).then(data => console.log(data))   // 123

Promise.reject('error').then(null, data => console.log(data))  // error

 

Promise.reject('err')

  .then(() => console.log('then 1'))  // 두번째 파라미터가 없으므로 그냥 통과

  .then(() => console.log('then 2'))  // 두번째 파라미터가 없으므로 그냥 통과

  .then(

    () => console.log('then 3'),

    () => console.log('then 4')  // rejected 상태의 Promise 객체가 여기에서 걸려서 수행됨.

  )

  .then(

    () => console.log('then 5'),  // then 4 부분에서 아무것도 리턴하지 않았으므로 정상 수행되었기에 여기가 수행됨.

    () => console.log('then 6')

  )

// then 4

// then 5

 

Promise.resolve(10)

  .then(data => {

    console.log('onThen', data)  // 10

    return data + 1

  })

  . catch(error => {

    console.log('onCatch')

    return 100

  })

  .finally(() => {

    console.log('onFinally')  // onFinally

  })

  .then(data => {

    console.log('onThen', data)  // onThen 11

    return data + 1

  })

 

Promise.reject(10)

  .then(data => {

    console.log('onThen', data)

    return data + 1

  })

  . catch(error => {

    console.log('onCatch')  // onCatch

    return 100

  })

  .finally(() => {

    console.log('onFinally')  // onFinally

  })

  .then(data => {

    console.log('onThen', data)  // onThen 100

    return data + 1

  })

 

Promise 는 비동기 상태를 값으로 다룰 수 있기 때문에 async await 보다 큰 개념이다.

Promise 는 객체로 존재하지만 async await 는 함수에 적용되는 개념이다.

 

async function getData () {  // async 키워드를 사용해 정의하면 async await 함수가 된다.

  return 123

}

getData().then(data => console.log(data))  // async 함수는 Promise 객체를 리턴한다.

 

async function getData () {

  return Promise.resolve(123)  // async 함수에서 리턴값이 Promise 객체라면 그 상태와 데이터를 그대로 반환.

}

getData().then(data => console.log('fulfilled', data)).catch(data => console.log('rejected', data)  // fulfilled 123

 

async await 함수 내부에서 await 키워드를 사용할 수 있다.

오른쪽에 Promise 객체를 입력하면 그 Promise 가 settled 상태가 될 때꺄지 기다립니다.

function requestData (value) {

  return new Promise(resolve =>

    setTimeout(() => {

      console.log('requestData', value)

      resolve(value)

    }, 1000)

  )

}

async function printData () {

  const data1 = await requestData(10)

  const data2 = await requestData(20)

  console.log(data1, data2)

}

printData()

 

async function getData () {

  console.log('getData 1')

  await Promise.reject()  // 여기 rejected 가 되어서 아래는 실행되지 않는다.

  console.log('getData 2')

  await Promise.resolve()

  console.log('getData 3')

}

getData().then(() => console.log('fulfilled')).catch(error => console.log('rejected'))  // getData1 rejected

 

일반 함수 async 키워드가 붙지 않은 함수 내에서 await 를 사용하면 에러가 발생한다.

 

function getDataPromise () {

  asyncFunction1()

    .then(data => {

      console.log(data)

      return asyncFunc2()

    })

    .then(data => {

      console.log(data)

    })

}

==> 위의 Promise 사용 로직을 동일하게 async await 사용하여 구현한 로직.

async function getDataAsync () {

  const data1 = await asyncFunc1()

  console.log(data1)

  const data2 = await asyncFunc2()

  console.log(data2)

}

 

async await 가 가독성이 높다.

비동기 함수 간의 의존성이 높아질수록 그 가독성 차이가 더욱 뚜렷이 나타난다.

function getDataPromise () {

  return asyncFunc1()

    .then(data1 => Promise.all([data1, asyncFunc2(data1)]))

    .then(([data1, data2]) => {

      return asyncFunc3(data, data2)

    })

}

async function getDataAsync () {

  const data1 = await asyncFunc1()

  const data2 = await asyncFunc2(data1)

  return asyncFunc3(data1, data2)

}

 

async await 에서 병렬로 실행되게 하려면 Promise 를 리턴받아 직접 await 해주면 된다.

function asyncFunc1 () {

  return new Promise(resolve => {

    console.log('처리 중1')

    setTimeout(() => {

      resolve(10)

    }, 1000)

  })

}

function asyncFunc2 () {

  // 위와 동일...

}

async function getData () {

  const p1 = asyncFunc1()

  const p2 = asyncFunc2()

  const data1 = await p1  // Promise 객체에 직접 await 가 붙을 경우

  const data2 = await p2  // 바로 이것도 수행된다. 즉, 병렬 수행

  console.log({ data1, data2 })  // 그리고 다 위의 p1, p2 모두 settled 되면 이게 찍힌다.

 

  만약 다음과 같이 하면 asyncFunc1 의 Promise 가 settled 되고 asyncFunc2 가 수행된다.

  const data1 = await asyncFunc1()  // 이렇게 Promise 를 리턴하는 함수를 실행 앞에 await 일 경우 

  const data2 = await asyncFunc2()  // 먼저 asyncFunc1 의 Promise 가 settled 되어야 이게 수행된다. 일반적...

  console.log({ data1, data2})

}

getData()

 

// ES6 이전부터 Promise 를 구현한 다양한 라이브러리들이 존재함.

// 그래서 ES6 의 Promise 가 아니더라도  then 이라는 메소드를 가진 객체를 Thenable 이라고 부르며

// Promise 와 유사하게 사용할 수 있다. 그래서 await 우측에서도 사용할 수 있다.

class ThenableExample {

  then (resolve, reject) {

    setTimeout(() => resolve(123), 1000)

  }

}

async function asyncFunc () {

  const result = await new ThenableExample()

  console.log(result)

}

asyncFunc()

 

 

* generator

제너레이터 객체가 next 메서드를 갖고 있다는 사실은 제너레이터 객체가 iterator 라는 것을 암시함.

function* f1 () {

  console.log('f1-1')

  yield 10  // 처음 next 실행시 여기까지 수행되고 { value: 10, done: false } 가 반환됨.

  console.log('f1-2')

  yield 20

  console.log('f1-3')

  return 'finished'

}

const gen = f1()  // f1 을 수행하면 제너레이터 객체가 반환된다.

console.log(gen.next())  // f1-1 { value: 10, done: false } next 를 수행하면 value, done 속성값을 가진 객체를 반환.

console.log(gen.next())  // f1-2 { value: 20, done: false }

console.log(gen.next())  // f1-3 { value: 'finished', done: true }

 

function* f1 () {

  if (true) {

    return 'finished'

  }

  console.log('f1-1')

  yield 10

  console.log('f1-2')

  yield 20

  console.log('f1-3')

}

const gen = f1()  // f1 을 수행하면 제너레이터 객체가 반환된다.

console.log(gen.next())  // { value: 'finished', done: true }  done: true 임.

console.log(gen.next())  // { value: undefined, done: true } 이 뒤 부터는 실행되지 않음. undefined 그리고  done 은 true

console.log(gen.next())  // { value: undefined, done: true } 실행되지 않음. 단지 value: undefined, done: true 인 객체만 반환됨.

 

function* f1 () {

  console.log('f1-1')

  yield 10

  console.log('f1-2')

  yield 20

  console.log('f1-3')

  return 'finished'

}

const gen = f1()

console.log(gen.next())  // f1-1 { value: 10, done: false }

console.log(gen.return('abc'))  // { value: 'abc', done: true } 제너레이터 함수 내에서 return 한 것과 동일한 효과.

console.log(gen.next())  // { value: undefined, done: true }  한번 done 이 true 가 되면 그 이후는 실행이 되지 않는다.

 

function* f1 () {

  try {

    console.log('f1-1')

    yield 10

    console.log('f1-2')

    yield 20

  } catch (e) {

    console.log('f1-catch', e)

    yield 30

    console.log('f1-3')

    yield 40

    console.log('f1-4')

  }

}

const gen = f1()

console.log(gen.next())  // f1-1 { value: 10, done: false }

console.log(gen.throw('some error'))  // f1-catch some error { value: 30, done: false }

console.log(gen.next())  // f1-3 { value: 40, done: false }

console.log(gen.next())  // f1-4 { value: undefined, done: true }

 

 

* iterator, iterable ( 제너레이터도 interator 이면서 iterable 이다. )

const arr = [10, 20, 30]

const iter = arr[Symbol.iterator]()

console.log(iter,next())

 

iterator, iterable

iterator 조건

  next 메서드를 갖고 있다.

  next 메서드는 value 와 done 속성값을 가진 객체를 반환한다.

  done 속성값은 작업이 끝났을 때 참이 된다.

iterable 조건

  Symbol.iterator 속성값으로 함수를 갖고 있다.

  해당 함수를 호출하면 iterator 를 반환한다.

 

function* f1 () {

  // ...

}

const gen = f1()

console.log(gen[Symbol.iterator]() === gen)

 

function* f1 () {  // for of 에서 iterator done: true 될 때까지, spread 연산자에서 done: true 될 때까지.

  yield 10

  yield 20

  yield 30

}

for (const v of f1()) { // 10, 20, 30

  console.log(v)

}

const arr = [...f1()]

console.log(arr)  // [ 10, 20, 30 ]

 

 

* generator 심화

generator 를 사용하면 수행되는 당시마다 값을 구하기 때문에 메모리 측면에서 이점이 많다.

iterator, iterable 을 이용하면 함수형 프로그래밍에서 많이 사용되는

map, filter, take 와 같은 함수들을 구현할 수 있다.

아래는 generator 함수를 사용하여 구현한 함수들이다. iterable 을 입력으로 받고 있다.

generator 덕분에 새로운 배열 객체를 생성하지 않습니다. ES6 에서 제공하는 함수의 경우 새로운 배열 객체를 생성하여 반환함.

연산이 필요한 시점만 실행이 된다. 자원에 있어 효율적이다.

function* map (iter, mapper) {

  for (const v of iter) {

    yield mapper(v)

  }

}

function* filter (iter, test) {

  for (const v of iter) {

    if (test(v)) {

      yield v

    }

  }

}

function* take (n, iter) {

  for (const v of iter) {

    yield v

    if (--n <= 0) return

  }

}

const values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

const result = take(

  3,

  map(

    filter(values, n => n % 2 === 0),

    n => n * 10

  ) 

)

console.log([...result])  // [ 20, 30, 60 ] 실제로 실행은 전개연산자 부분에서 일어난다.

제너레이터 함수를 호출하면 실제 실행되는 것이 아니라 제너레이터 객체만 생성됨.

값이 필요한 순간에만 iterable 을 통해서 필요한 연산을 수행한다.

이렇게 필요한 순간에만 연산하는 방식을 지연 평가(lazy evaluation) 라고 부른다.

제너레이터를 사용해서 얻게 되는 또 다른 장점은 필요한 연산만 수행된다는 점이다.

필요한 연산만 수행하기에 값을 무한대로 표기하는 것도 가능하다.

function* naturalNumbers () {  // 자연수의 집합.

  let v = 1

  while (true) {

    yield y++

  }

}

const values = naturalNumbers()

const result = take(

  3,

  map(

    filter(values, n => n % 2 === 0),

    n => n * 10

  )

)

console.log([...result])

 

yeild* 오른쪽에 iterable 이면 무엇이든 올 수 있다.

function* g1 () {

  yield 2

  yield 3

}

function* g2 () {

  yield 1

  yield* g1()  // 제너레이터 함수에서 다른 제너레이터 함수를 호출

  yield* [2, 3]  // 배열은 iterable 이다.

  yeild 4

}

console.log(...g2())  // 1 2 3 4

위의 yield* [2, 3] 부분은 아래와 같이 동작한다고 이해할 수 있다.

function* g2 () {

  yield 1

  for (const value of [2, 3]) {

    yield value

  }

  yield 4

}

 

제너레이터 함수는 외부로부터 데이터를 받아서 사용할 수 있다.

function* f1 () {

  const data1 = yield

  console.log(data1)

  const data2 = yield

  console.log(data2)

}

const gen = f1()

gen.next()  // 첫 next 는 제너레이터 함수의 첫 번째 yield 를 만날 때까지 실행. 따라서 입력 인수는 의미가 없다.

gen.next(10)  // 10 입력값은 첫번째에 멈춰있던 yield 의 반환값이 됨. 그래서 data1 은 10이 됨.

gen.next(20)  // 다음  yield 에서 멈춰있게 되고, 20 입력값은 멈춰있던 yield 부분의 반환값이 되어 data2 에 들어간다.

 

제너레이터 함수는 다른 함수와 협업 멀티태스킹 (cooperative multitasking) 을 할 수 있다.

멀티태스킹은 여러 개의 태스크를 실행할 때 하나의 태스크가 종료되기 전에 멈추고 다른 태스크가 실행되는 것을 말한다.

제너레이터는 실행을 멈추고 재개할 수 있기 때문에 멀티태스킹이 가능.

협업이라는 단어가 붙는 이유는 제너레이터가 실행을 멈추는 시점을 자발적으로 선택하기 때문이다.

반대로 실행을 멈추는 시점을 자발적으로 선택하지 못하면 선점형 멀티태스킹 (preeemptive multitasking) 이라고 한다.

일반적으로 OS 에서는 선점형 멀티태스킹을 사용. OS 는 한 순간에 하나의 프로세스를 일정 시간만큼만 실행을 한다.

그리고 그 시간이 지나면 그 프로세스의 실행을 종료함. 이렇게 강제로 실행을 종료하기 때문에 선점형 멀티태스킹을 사용한다고 할 수 있다.

제너레이터 함수는 yield 키워드를 통해서 자발적으로 자신의 실행을 멈춘다.

function* minsu () {

  const myMsgList = [

    '안녕 나는 민수야',

    '만나서 반가워',

    '내일 영화 볼래?',

    '시간 안 되니?',

    '내일 모래는 어때?'

  ]

  for (const msg of myMsgList) {

    console.log('수지: ', yield msg)

  }

}

function suji () {

  const myMsgList = ['', '안녕 나는 수지야', '그래 반가워', '...']

  const gen = minsu()

  for (const msg of myMsgList) {

    console.log('민수: ', gen.next(msg).value)

  }

}

suji()

 

제너레이터 함수에서 예외처리

function* getFunc () {

  throw new Error('some error')

}

function func() {

  const gen = getFunc()

  try {

    gen.next()

  } catch (e) {

    console.log('in catch')

  }

}

func()

 

 

* Prototype

// Prototype 접근의 공식적인 방법은  getPrototype 메서드 사용하는 것.

// __proto__ 가 공식적으로는 브라우저 상에서만 표준이지만 거의 모든 자바스크립트 엔진이 이렇게 한다.

// 즉 getPrototype 을 사용해서 person.__proto__ 를 가져오는 것이다.

const person = {

  name: 'Jack'

}

const prototype = Object.getPrototypeOf(person)

console.log(typeof prototype)  // object

console.log(person.__proto__ === prototype)  // true

 

const person = {

  name: 'Jack'

}

const programmer = {

  language: 'javascript'

}

Object.setPrototypeOf(programmer, person)

// programmer.__proto__ = person

console.log(Object.getPrototypeOf(programmer) === person)  // true

console.log(programmer.name)  // Jack

 

const person = {

  name: 'Jack'

}

const programmer = {

  language: 'javascript'

}

const frontendDev = {

  framework: 'react'

}

Object.setPrototypeOf(programmer, person)

Object.setPrototypeOf(frontendDev, programmer)

console.log(frontendDev.name, frontendDev.language)

cosole.log(

  frontendDev.__proto__.__proto__.name,

  frontendDev.__proto__.language

)

 

const person = {

  name: 'Jack'

}

const programmer = {

  language: 'javascript'

}

Object.setPrototypeOf(programmer, person)

programmer.name = 'jane'  // programmer 에 넣는다. 따라서 __proto__ 에는 Jack 이 그대로 있다.

console.log(programmer.name)  // jane

console.log(person.name)  // Jack

 

// for in, hasOwnProperty, Object.keys

const person = {

  name: 'Jack'

}

const programmer = {

  language: 'javascript'

}

Object.setPrototypeOf(programmer, person)

for (const prop in programmer) {  // for in 은 __proto__ 까지 탐색한다.

  console.log(prop)  // language, name

}

for (const prop in programmer) {

  if (programmer.hasOwnProperty(prop)) {  // __proto__ 가 아닌 자신의 것이면 true 

    console.log(prop)  // language

  }

}

for (const prop of Object.keys(programmer)) {  // __proto__ 가 아닌 자신의 keys 만 가져온다.

  console.log(prop)  // language

}

 

// new 키워드로 객체를 생성해주는 함수를 생성자 함수라 한다. 첫글자를 대문자로 하는 것이 좋다.

function Person (name) {

  // this = {}  // new 키워드로 생성시 최초 이렇게 this 에 빈 객체를 할당해준다.

  this.name = name

  // return this  // 그리고 함수가 종료하기 직전에 그 this 를 반환을 해준다.

}

const person = new Person('Jack')

console.log(person.name)  // Jack

 

// 생성자 함수로 new 키워드를 통해 기본 타입 객체를 생성하는 법.

// 각각의 리터럴 문법으로 생성할 수 있으며 그렇게 더 많이 사용한다.

const obj = new Object({ a: 123 })  // const obj = { a: 123 }

const arr = new Array(10, 20, 30)  // const arr = [10, 20, 30]

const num = new Number(123)  // const num = 123

const str = new String('abc')   // const str = 'abc'

console.log({ obj, arr, num, str })

console.log(Object.getPrototypeOf(obj) === Object.prototype)

console.log(Object.getPrototypeOf(arr) === Array.prototype)

console.log(Object.getPrototypeOf(num) === Number.prototype)

console.log(Object.getPrototypeOf(str) === String.prototype)

// 기본형의 string 이나 number 는 원래 객체가 아니다. 하지만 속성에 접근하는 문법을 사용하면 임시로 객체처럼 동작한다.

// 임시로 만들어진 객체의 프로토타입은 해당 생성자 함수의 prototype 속성을 참조한다.

console.log(num.toFixed === Number.prototype.toFixed)

console.log(num.toFixed())

 

// 모든 함수는 prototype 이라는 속성을 가지고 있다.

// setPrototype 된 객체의 __proto__ (모든 객체는 프로토타입을 가진다) 와 비교하여 혼동하지 말 것.

// new 키워드로 생성된 객체의 프로토타입은 그 생성자 함수의 prototype 속성을 가리킨다.

function Person (name) {

  this.name = name

}

const person = new Person('Jack')

console.log(Person.prototype)

console.log(Object.getPrototypeOf(person) === Person.prototype)

 

// 함수의 prototype 속성(객체) 에는 constructor 라는 속성이 있다.

// constructor 는 그 함수를 가리킨다. 즉, 자기 자신을 가리킨다.

function Person (name) {

  this.name = name

}

console.log(Person.prototype.constructor === Person)

// Person 은 함수이므로 Person.constructor 는 Function 생성자 함수를 가리킨다.

console.log(Person.constructor === Function) // Person.__proto__ 에 가서 constructor 를 찾는다.

 

// 메모리를 절약하려면 함수의 prototype 속성에 멤버를 정의는 것이 좋다.

 

function Person (name) {

  this.name = name

}

const person = new Person('Jack')

console.log(Object.getPrototypeOf(Person) !== Person.prototype) // 함수의 프로토타입은 그 함수의 prototype 속성과는 다르다.

 

// new 키워드로 생성된 객체는 생성자 함수의 prototype 속성과 같다.

console.log(Object.getPrototpyeOf(person) === Person.prototype) 

// 함수의 prototype 속성은 객체이다. 해당 객체의 프로토타입은 Object 생성자 함수의 prototype 속성과 같다.

console.log(Object.getPrototypeOf(Person.prototype) === Object.prototype)

// Person 이라는 생성자 함수는 함수이기 때문에 그 프로토타입은 Function 이라는 생성자 함수의 prototype 속성과 같다.

console.log(Object.getPrototypeOf(Person) === Function.prototype)

 

// Object 생성자 함수도 함수이기 때문에 그 프로토타입은 Function 의 prototype 속성과 같다.

console.log(Object.getPrototypeOf(Object) === Function.prototype)

// Function 이라는 생성자 함수의 prototype 속성은 객체이므로 Object 생성자 함수의 prototype 속성을 가리킨다.

console.log(Object.getPrototypeOf(Function.prototype) === Object.prototype)

// Object 생성자 함수의 prototype 속성은 마찬가지로 객체이지만 특별하게 null 을 가리킨다.

console.log(Object.getPrototypeOf(Object.prototype) === null)

// 그래서 Object.prototype 이 프로토타입 체인에서 가장 마지막을 담당한다.

반응형

댓글

Designed by JB FACTORY