간단히 객체 생성하기

function getObj() {
let name = "yoon"
const getName = function() {
return name;
}
const setName = function(newName){
name = newName
}
const printName = function(){
console.log(name)
}

return {getName,setName,name}
}

obj= getObj();
console.log(obj.getName)



Destructuring Array & Object
Array
let arr = ["yoon","kim","baek","lee"]

let[yoon,,baek] =arr;

console.log(yoon)


object 
let obj = {
name : "yoon",
addr : "korea",
age : "35"
}

//특정 값만 호출할수 있음
//let {name, age} = obj;
//console.log(name, age)

//다른이름으로 재할당
let {name:myname , age:myage} = obj
console.log(myname,myage)

Destructuring을 활용하면 원하는 키값이나 배열 값을 원하는대로 뽑아 올수 있다.
더이상 array[0] 이런식으로 데이터를 뽑지 않아도 됨 배열에 값을 할당하고 그 값으로 데이터를 호출할 수 있다 

Destructuring 을 활용하여 event 객체 전달 Destructuring을 활용하면 단순히 객체를 전달해서 호출하는 것 뿐만아니라.
원하는 객체 요소만 뽑아 호출이 매우 간편함 
//
//기존 소스
document.querySelector("div").addEventListener("click",function(evt){
console.log(evt)
})
//Destructuring 활용 소스
document.querySelector("div").addEventListener("click",function({type,target}){
console.log(type ,target.tagName)
})

유일한 값을 저장해야할 때 Set을 이용해 유니크한 배열 만들기
 추가 add 
삭제 : delete
let myset = new Set();
console.log(toString.call(myset))


myset.add("yoon")
myset.add("kim")
myset.add("park")
myset.add("park")

myset.delete("yoon")

myset.forEach(function(v){
console.log(v) //result : kim,park
})

참조를 가지고 있는 객체만 저장하기 weakSet() 
객체 형태를 중복없이 저장하려고 할 때 유용함 

참조를 가지고 있는객체를 추가하는것은 정상
let arr = [1,2,3,4,5]
let ws = new WeakSet();

ws.add(arr)

console.log(ws)

참조를 가지고 있지 않는 객체를 추가할 경우 TypeError: Invalid value used in weak set 오류를 뿜어낸다.
let arr = [1,2,3,4,5]
let ws = new WeakSet();

ws.add(arr)
//참조가 없는 객체를 입력하는 것은 오류
ws.add(null)
ws.add(1111)
ws.add("1111")


console.log(ws)



key / value로 이루어진 자료구조 형태 Map & WeakMap
array를 보완한 자료 구조가 --> set 과 WeakSet이라면 
Object를 보완한 자료구조가 --> map과WeakMap이다
WeakSet과 마찬가지로 WeakMap은 참조한 객체만을 추가할 수 있으며 참조가 없을 경우 오류가 나온다.
let wm = new WeakMap();
let myfunc = function(){}

//이함수가 얼마나 실행됐지를 알려고 할때?

wm.set(myfunc,0);

let count = 0;
for(let i=0; i<10; i++ ){
count = wm.get(myfunc)
count++;
wm.set(myfunc,count)
}

console.log(wm.get(myfunc)) // function => 10

console.log(wm.has(myfunc)) //true

myfunc = null

console.log("가비지 컬랙터 사용 후 : " + wm.has(myfunc)) //false


응용해보기 우선 일반 예제 

//공간을 계산 예제 일반

function Area(height,width) {
this.height= height;
this.width = width;
}

Area.prototype.getArea = function(){
return this.height * this.width;
}

let myarea = new Area(10,20)

console.log(myarea.getArea()) //result == 200;

console.log(myarea.height) //result == 10
console.log(myarea.width) //result == 20

weakMap을 활용하여 프라이빗한 객체를 생성할 수 있음

//공간을 계산 예제 WeakMap활용하여 프라이빗한 객체를 만들수 있다
//다만 값을 전역 변수영역에 저장해야한다는 단점이 있음

let wm = new WeakMap();

function Area(height,width) {
wm.set(this,{height,width})
}

Area.prototype.getArea = function(){
const {height,width} = wm.get(this)
return height * width;
}

let myarea = new Area(10,20)

console.log(myarea.getArea()) //result == 200;

//접근 불가능
console.log(wm.get(this)) //result == undefined
console.log(myarea.width) //result == undefined


obj를 이용한 예제 

// Object를 활용한 예제
const obj = {};

function Area(height,width) {
obj.height= height;
obj.width = width;
}

Area.prototype.getArea = function(){
return obj.height * obj.width;
}

let myarea = new Area(10,20)


console.log(myarea.getArea()) //result == 200;

console.log(obj.width)//result == 20;
console.log(obj.height)//result == 20;

화살표 함수 
대표적인 콜백함수인 setTimeout함수를 통해 화살표 함수를 시작해보자 


setTimeout(function(){
console.log(" 일반 타임아웃 ")
},1000)

간단하다 function 대신 => 를 넣어주면 된다.

setTimeout(()=>{
console.log("---->>")
},1000)

map을 활용한 예제 
let newArr = [1,2,3,4,5].map(function(value,index,obj){
return value * 2
})
console.log(newArr)

역시 function 대신 => 를 넣어서 변환할 수 있다.
리턴을 넣어도 되지만 빼도 정상 작동하는 것을 확인 할 수 있다.
let newArr2 =[1,2,3,4,5].map((v) => v * 2 )
console.log(newArr)

지금까지 공부한 것을 토대로 봤을 때 ES6는 엄청 편리하다 
그러나! 
아직 인터넷 익스플로러에서 지원이 미비하기 때문에 BABEL을 이용하여 ES5로 변환하여 사용해야 한다는 것을 잊으면 안된다.
어허이 



let data = [{"placCd":"JH_KR_02","facCd":"HT05","inspDtm":"16:22:08","inspArtCd":"INSP_046","inspArtNm":"소입유조 C/V",
"adtVal":60,"evalExpl":"검사규격미정의","hetJhnLot":"HT05201812300024"}... 생략


let newData = [...data]

/*
let array = newData.filter(function(v){
return v.adtVal > 800;
})
*/

/*
let array = newData.filter(function(v){
return v.inspArtNm.includes("C/V")
})

*/

/*
let array = newData.filter(function(v){
return v.inspArtNm.startsWith("소입유조")
})

*/
/*
let array = newData.filter(function(v){
return v.inspArtNm.endsWith("(Hz)")
})
*/

console.log(array)


scope

자바스크립트의 변수선언 방법은 var 였으나 모던 자바스크립트에 와서는 
let과 const가 추가되었다.

var 가 함수단위의 스코프 였다면 
let과 const 는 블럭 단위의 스코프라고 설명 할 수 있다.

const 는 상수를 의미하고 let은 변수를 의미함
재할당
// var의 경우var a = 1
a = 2
console.log(a) // 2
var a = 3
console.log(a) // 3

// let의 경우let b = 1
b = 2
console.log(b) // 2
let b = 3 // SyntaxError: Identifier 'b' has already been declared

// const의 경우const c = 1
c = 2 // TypeError: Assignment to constant variable



스코프 

var a = 1 let b = 2

if (true) {
var a = 11
let b = 22
console.log('a = ' + a) // 11
console.log('b = ' + b) // 22}

console.log('a = ' + a) // 11
console.log('b = ' + b) // 2

function func() {
var a = 111
let b = 222
console.log('a = ' + a) // 111
console.log('b = ' + b) // 222}

func()

console.log('a = ' + a) // 11
console.log('b = ' + b) // 2


 앞서 말했듯이 let은 블럭단위 스코프 이기 때문에 위와 같은 결과를 낸다.  
 또한 let은 재할당이 가능한 반면에 const는 재할당이 불가능 하기 때문에 상황에 맞게 잘 사용해야 한다.

let과 클로저

클로저 :  클로저는 독립적인 (자유) 변수를 가리키는 함수이다. 또는, 클로저 안에 정의된 함수는 만들어진 환경을 ‘기억한다’. 라고 함
간단하게 말하면 함수내에서 함수를 정의하고 사용하면 클로저라고 생각하면 된다.


function getClosure() {
var text = 'variable 1';
return function() {
return text;
};
}

var closure = getClosure();
console.log(closure()); // 'variable 1'


위의 getCloseure()함수는 함수를 리턴하고  리턴한 함수는 getClosure의  text변수를 리턴하고 있음 
함수가 리턴되는 함수 즉 클로저 




위에 4가지 언어명이 나와있는 리스트를 클릭 하면 몇번째 항목인지 콘솔로 리턴해주는 코드가 있다 
그러나 위에 결과 값에 처럼 어느 언어를 찍어도 결과값이 나올 뿐이다.
이유는 이벤트 핸들러가 var i 의 결과값을 공유하기 때문에 1을 찍어도 2를 찍어도 결과적으로 최종값인 4가 나올수 밖에 없다.  이를 해결하기 위해서 스코프를 추가하여
i를 지역변수화 해서 수정할 수 있다  그러나.  let 키워드를 이용해서도 해결이 가능하다.





const - 할당된 내용 지키기


function home(){
var home = "지역변수";
home="지역변수에 할당"
console.log(home)
}

home(); // 결과값 : "지역변수에 할당"

정상적으로 할당 된다.


function home(){
let home = "지역변수";
home="지역변수에 할당"
console.log(home)
}

home(); // 결과값 : 에러 발생
    
const 는 재할당 할 경우 타입과 상관없이 오류를 발생시킨다.  

그러므로 ES6에서는
const를 기본으로 사용하되 변경이 필요한 부분은 let을 사용하면 되고 var는 사용하지 않는 것이 좋다

그렇다고 해서 const 가 불변은 아니다 오브젝트 및 배열에서 값을 변경하거나 추가하는 것은 가능하다. 
아래 코드를 확인하자 
function home(){
const list = [1,2,3,4,5]
list.push(99)
console.log(list)
}

home(); // [1,2,3,4,5,99]

immutable array  (불변의) 를 만드는 방법

function home(){
const list = [1,2,3,4,5]
list.push(99)
list2 = [].concat(list,100000)
console.log(list,list2)
}

home(); //[1,2,3,4,5,99] [1,2,3,4,5,99,100000]
이렇게 만들면 list 를 계속 추가해도 1과 2 앞으로만들 리스트는 변하지 않고 추가할수있다.


ES2015의 String 메소드
startsWith
endsWith
includes 
사용방법은 아래 코드를 확인

let str = "hello world! ^^l"
let matcheStr = "hello"
let matcheEnd = "^^l"

console.log(str.startsWith(matcheStr)) //true

console.log(str.endsWith(matcheEnd)) //true
console.log(str.includes("^^")) //true


for of 순회하기

let str = "hello world! ^^l"
for(let value of str){
console.log(value)
}

한글자씩 순회하며 보여준다.

for in은 어레이에서는 사용하지 말아야 한다. 왜냐하면 for in의 경우 문제가 생김 어허이

spread operator - 배열의 복사  / 펼침연산자.

let data = [1,2,3,4,5]
let newData = [...data]
console.log(data,newData) // [1,2,3,4,5] [1,2,3,4,5]
console.log(data === newData) //false
...배열명 을 넣으면 해당 배열을 넣은 곳에 펼쳐 입력한다  완전 다른 객체가 생성된다.
data === newData  == false 인 이유다.

spread operator  - 배열의 활용 

let data = [100,200,300]

function sum (a,b,c){
return a+b+c
}

console.log(sum.apply(null,data))

console.log(sum(...data))


from 메소드로 진짜 어레이 만들기

기존 자바스크립트에서 arguments 를 활용하는 메소드 코드가 있다 

arguments를 이용하여 파라미터 없이 변수를 받을 수 있다 자주사용되는 패턴은 아니지만 변수가 가변적일 때 사용할 수 있는 방법이다.
ES6에서 map을 이용하여 동일한 효과를 낼수 있는데 아래와 같은 오류가 발생한다. 원인은 arguments 가 배열이 아니기 때문에 map을
사용할수가 없어서 발생하는 오류이다





Array.from()를 이용하여 arguments 를 배열로 변환해주면 해결된다.










 


자바스크립트에서 

'1' + 1 = ?
'2' * 3 = ?
'1' + 2 + 3 * 4 = ?



'1' + 1 = 11
'2' * 3 = 6
'1' + 2 + 3 * 4 = 1212


자바스크립트는 컴파일러가 없으니 테스트가 최선이다.
직접실행해서 결과를 검증하자.

자바스크립트 TDD 를 해보자!


테스트의 개념
  1. 단위(유닛) 테스트
                인풋에 따라 계산한 결과를 아웃풋으로 내놓는 것.  ( 단위테스트는 함수를 테스트 하는 것이라고 생각하면됨)
                단위테스트 , 준비 , 실행  , 단언(검증) 단계로 나뉘어집니다.



테스트 주도개발

레드, 그린 , 리펙터 순환 구조

테스트하기 쉬운 코드

관심사의 분리

함수는 하나의 기능만 해야함



자바스크립트의 테스트 프레임워크  - 재스민 프레임워크 


재스민 프레임워크 다운 후 SPEC RUNNER.HTML 파일을 클릭하면 성공 간단하쥬?

SPECRUNNER.HTML 파일 구조

//자스민 프래임워크 로드
  <script src="lib/jasmine-3.3.0/jasmine.js"></script>
  <script src="lib/jasmine-3.3.0/jasmine-html.js"></script>
  <script src="lib/jasmine-3.3.0/boot.js"></script>

//테스트 할 소스
  <!-- include source files here... -->
  <script src="src/Player.js"></script>
  <script src="src/Song.js"></script>

//테스트 파일
  <!-- include spec files here... -->
  <script src="spec/SpecHelper.js"></script>
  <script src="spec/PlayerSpec.js"></script>

형식으로 이루어져있음 

테스트 꾸러미 (test suite)
테스트 스펙 it
기대식과 매쳐
expect(결과값).toBe(기대하는 값) 
스파이  spyOn(감시할 객체, 감시할 메소드)

테스트 형식
describe('hello world', ()=> { // 테스트 스윗: 테스트 유닛들의 모음
it('true is true', ()=> { // 테스트 유닛: 테스트 단위
expect(true).toBe(true) // 매쳐: 검증자
})




테스트 할 수 없는 코드

<button onclick="counter++; countDisplay()"> 증가</button>
<span id="counter-display">0</span>
<script>
var counter = 0;

function countDisplay() {
var el = document.getElementById('count-display');
el.innerHTML= count
}

</script>

위 코드의 문제 점은 무엇일까?

       1.관심사의 미분리
       2.전역변수의 충돌
       3.재사용 어려움


1.관심사의 미분리
관심사가 분리되지 않았음 클릭 이벤트가 처리기를 인라인 형태로 정의한 점
한가지 코드는 하나의 역할만 해야함 


<button onclick="counter++; countDisplay()"> 증가</button>

카운트 증가와 디스플레이 2가지의 역할을 하고 있음 


  1. 전역 변수의 충돌 
var counter 전역변수를 사용하여 어지럽힌 점  전역변수 사용은 전형적인 안티 패턴이기 때문에 사용하더라도 제한적으로 사용 해야함

  1. 재사용 어려움
var el = document.getElementById('count-display')


함수에 count-display를 하드코딩 했기 때문에 다른 선택자를 찾으려면 해당 함수를 사용할수없고 새로운 함수를 만들어야 하기 때문에 재사용이 어려움
open-close 원칙에 의거 쉽게 추가하면안되고 확장에는 언제든 열려있어야 한다. 



자바스크립트에서 문제를 해결 하는 방식에는 모듈 패턴이라는 것이 있음
모듈 패턴이란 ? 함수로 데이터를 감추고 모듈 API를 담고 있는 객체를 반환하는 형태
모듈 패턴에는 1. 임의 모듈 패턴 , 2. 즉시 실행 함수 기반의 모듈 패턴이 있음

모듈생성원칙
  1. 한가지 모듈에는 한가지 역할만 한다.
  2. 모듈에 사용할 객체가 있다면 의존성 주입형태로 제공해야한다.


임의모듈패턴으로 기존코드 수정하기
  기존의 클릭카운터 소스를 수정해보자.

A 우선 getValue()는 카운트 값을 반환하도록 수정해보자.

  1. 실패하는 테스트 코드 작성하기


describe('App.ClickCounter', ()=> {
describe('getValue()', ()=> {
it('초기값이 0인 카운터 값을 반환한다', ()=> {
// todo
const counter = App.ClickCounter()
expect(counter.getValue()).toBe(0)
})
})
})

당연히 실패한다 모듈을 만든적이 없으니까.

     2. 모듈을 만들어 보자
var App = App || {}

App.ClickCounter = () =>{
return {
getValue() {
return 0
}
}
}

ClickCounter   getValue() 불러왔을 때 0을 호출하는 모듈을 생성 후 재스민으로 테스트 하면 오류가 나지 않는다.


  1. 모듈을 수정하자 카운터가 증가해야하나 현재는 상수로 박아놨기 때문에 그부분을 변수로 처리 후 수정해보자. 리펙토링 단계
var App = App || {}

App.ClickCounter = () =>{
let value = 0;
return {
getValue() {
return value
}
}
}


정상이다.
이 것이 테스트 주도 개발의 한 사이클이라고 보면될것 같다.

레드(오류) --> 그린(패스) --> 블루(리펙터)  반복


두번째 스펙
B. ClickCounter 의 increase() 는  카운터 값을 1씩 증가시킨다.

  1. 테스트 코드를 작성한다  (레드)
describe('increase()', ()=> {
it('카운터를 1 올린다', ()=> {
var counter = App.ClickCounter()
counter.increase();

expect(counter.getValue()).toBe(1);
})
})
})


실행하면 당연히 오류다   왜냐하면 increase() 모듈을 작성한적이 없쟈나쟈나

  1. increase() 모듈을 작성한다 
var App = App || {}

App.ClickCounter = () => {
let value = 0

return {
getValue() {
return value
},
increase(){ //increase() 모듈 실행 시 value 값을 1 증가 시킨다.
value++;
}
}
}

이후 테스트 하면 당연히 정상이다.

이번에는 테스트 코드를 수정해보자.


describe('App.ClickCounter', ()=> {
describe('getValue()', ()=> {
it('초기값이 0인 카운터 값을 반환한다', ()=> {
const counter = App.ClickCounter()
expect(counter.getValue()).toBe(0)
})
})

describe('increase()', ()=> {
it('카운터를 1 올린다', ()=> {
const counter = App.ClickCounter()
counter.increase();
expect(counter.getValue()).toBe(1);
})
})
})



전체 코드를 봤을 때 중복 코드가 보인다.
const counter = App.ClickCounter()

중복 코드는 옳지 않다. 
우선 describe 실행에 순서가 있다.

describe (()=> {
    beforeEach(()=>{  1번 실행
    afterEach(()=>{ 3번 실행
    it(()=>{ 2번 실행 
    
it실행 전에 무조건 beforeEach가 실행 되기 때문에 중복코든는 beforeEach로 이동시키자.

describe('App.ClickCounter', ()=> {
let count //전역변수
beforeEach(()=>{
counter = App.ClickCounter() //중복된 부분을 beforeEach 부분으로 추출했다.
})
describe('getValue()', ()=> {
it('초기값이 0인 카운터 값을 반환한다', ()=> {
expect(counter.getValue()).toBe(0)
})
})

describe('increase()', ()=> {
it('카운터를 1 올린다', ()=> {
counter.increase();
expect(counter.getValue()).toBe(1);
})
})
})

중복코드 수정 후 정상적으로 테스트 코드가 통과한다.


언제나 DRY 한 코드를 작성하기 위해 노력해야한다.
DRY는 
DO IT REPEAT YOURSELF 



클릭카운트 뷰 모듈 만들기 
화면에 보이는 부분을 만들어 보자!

dom에 반영하여 화면에 보이게 만드는 영역을 만들어보자!

첫번째 스펙
ClickCounterView 모듈의 updateView()는 카운터 값을 출력한다.

생각해봐야 할 점 
  1. 데이터를 조회할 때마다 ClickCounter를 얻어와야 함 
  2. 데이터를 출력할 돔 엘리먼트를 테스트 해야함

주입하자!
ClickCounter는 객체를 만들어 파라미터로 전달받자.
데이터를 출력할 돔 엘리먼트도 만들어 전달 받자. 

  1.  테스트 코드를 작성하자.
describe('App.ClickCountView', ()=> {
let clickCounter,updateEl,view
beforeEach(()=>{
clickCounter = App.ClickCounter();
updateEl = document.createElement('span')
view = App.ClickCounterView(clickCounter,updateEl) // ClickCounter와 updateEl 을 view 에 주입시킴
})
describe('updateView()', ()=> {
it('ClickCounter의 getValue() 값을 출력한다', ()=> {
const counterValue = clickCounter.getValue();
view.updateView();
expect(updateEl.innerHTML).toBe(counterValue.toString())
})
})
})
당연히 오류난다. 왜냐? 모듈을 안만들었으니까 
연습을 위해 3단계를 계속 반복하고 있음

  1. 모듈을 만들어보자!
var App = App || {}

App.ClickCounterView = (clickCounter,updateEl)=>{
return{
updateView() {
updateEl.innerHTML = clickCounter.getValue();
}
}
}


ClikcCounterVier모듈 의존이 정상적으로 작동하는지 확인하는 테스트 케이스를 만들어보자.

expect(기대값).toThrowError() 함수를 통해 만들수있다.

describe('App.ClickCountView', ()=> {
let clickCounter,updateEl,view
beforeEach(()=>{
clickCounter = App.ClickCounter();
updateEl = document.createElement('span')
view = App.ClickCounterView(clickCounter,updateEl)
})

//에러를 체크해주는 부분
it("클릭카운터가 널일 경우 에러를 발생한다." , ()=>{
const clickCounter = null;
const updateEl = document.createElement('span');
const actual = () => App.ClickCounterView(clickCounter,updateEl);

expect(actual).toThrowError();


})
describe('updateView()', ()=> {
it('ClickCounter의 getValue() 값을 출력한다', ()=> {
const counterValue = clickCounter.getValue();
view.updateView();
expect(updateEl.innerHTML).toBe(counterValue.toString())
})
})
})


var App = App || {}

App.ClickCounterView = (clickCounter,updateEl)=>{
if(!clickCounter) throw error("clickCounter is null") // clickCounter 가 null일 경우 throw error 를 발생시켜준다.
return{
updateView() {
updateEl.innerHTML = clickCounter.getValue();
}
}
}


두번째 스펙 
ClickCountView 모듈의 increaseAndUpdateView();는 카운트 값을 증가하고 그 값을 출력한다.

간단히 보면 이미 만든 ClickCounter의 increase() 함수를 실행하고 
updateView 함수를 실행하는 기능임

테스트 더블을 통해 테스트 할 수 있다
다음 5가지를 통칭하여 테스트 더블이라고 함 
  1. 더미 : 인자를 채우기 위해 사용
  2. 스텁 : 더미를 개선하여 실제 동작하게끔 리턴값을 하드코딩함
  3. 스파이 : 스텁과 유사 내부적으로 기록을 남기는 추가기능
  4. 페이크 : 스텁에서 발전한 실제 코드 운영에서는 사용할수 없다. 
  5. 목 : 더미 , 스텁, 스파이를 혼합한 형태
자스민에서는 테스트 더블을 스파이스라고 부른다. 
spyOn(), createSpy() 함수를 사용 할 수 있다.


모듈 실행 시 spyOn(감시할객체 ,'인자') 를 넘겨줌 
특정한 행동을 한뒤  bar()
감시한 객체가 실행되었는지 체크할 수 있음
expect(감시할객체.인자).toHaveBeenCalled();함수로 체크가능



describe('App.ClickCountView 모듈', () => {
let udpateEl, clickCounter, view

it('ClickCounter를 주입하지 않으면 에러를 던진다', ()=> {
const clickCounter = null
const updateEl = document.createElement('span')
const actual = () => App.ClickCountView(clickCounter, updateEl)
expect(actual).toThrowError(App.ClickCountView.messages.noClickCounter)
})

it('updateEl를 주입하지 않으면 에러를 던진다', ()=> {
const clickCounter = App.ClickCounter()
const updateEl = null
const actual = () => App.ClickCountView(clickCounter, updateEl)
expect(actual).toThrowError(App.ClickCountView.messages.noUpdateEl)
})

beforeEach(()=> {
updateEl = document.createElement('span')
clickCounter = App.ClickCounter();
view = App.ClickCountView(clickCounter, updateEl)
})

describe('updateView()', () => {
it('ClickCounter의 getValue() 실행결과를 출력한다', ()=> {
const counterValue = clickCounter.getValue()
view.updateView()
expect(updateEl.innerHTML).toBe(counterValue.toString())
})
})


// 테스트 영역
describe('increaseAndUpdateView()는', ()=> {
it('ClickCounter의 increase 를 실행한다', ()=> {
spyOn(clickCounter,'increase')
view.increaseAndUpdateView();
expect(clickCounter.increase).toHaveBeenCalled()


})
it('updateView를 실행한다', ()=> {
spyOn(view,'updateView')
view.increaseAndUpdateView();
expect(view.updateView).toHaveBeenCalled();
})
})
})


당연히 오류 난다 모듈을 추가해 보자! 

var App = App || {}

App.ClickCountView = (clickCounter, updateEl) => {
if (!clickCounter) throw new Error(App.ClickCountView.messages.noClickCounter)
if (!updateEl) throw new Error(App.ClickCountView.messages.noUpdateEl)
return {
updateView() {
updateEl.innerHTML = clickCounter.getValue()
},
//추가 영역
increaseAndUpdateView(){
clickCounter.increase();
this.updateView();

}
}
}

App.ClickCountView.messages = {
noClickCounter: 'clickCount를 주입해야 합니다',
noUpdateEl: 'updateEl를 주입해야 합니다'
}


정상이다.!!!!

세번째 스펙을 만들어보자 
클릭 이벤트가 발생하면 increaseAndUpdateView()가 발생한다.

생각해보자 
클릭 이벤트를 어떻게 체크할 것인가???
이 역시 주입해서 체크 가능하다.

클릭 이벤트 핸들러를 바인딩할 트리거를 추가한다.
triggerEl 이라는 전역변수를 생성하고  button 을 할당해주었다.
ClickCountView 에 파라미터가 너무 많아  { } 로 묶어서 전달
describe('App.ClickCountView 모듈', () => {

let udpateEl, triggerEl, clickCounter, view

beforeEach(()=> {
updateEl = document.createElement('span')
triggerEl = document.createElement('button')
clickCounter = App.ClickCounter();
view = App.ClickCountView(clickCounter, {updateEl ,triggerEl})
})
describe('네거티브 테스트', ()=> {
it('ClickCounter를 주입하지 않으면 에러를 던진다', ()=> {
const actual = () => App.ClickCountView(null, {updateEl})
expect(actual).toThrowError(App.ClickCountView.messages.noClickCounter)
})

it('updateEl를 주입하지 않으면 에러를 던진다', ()=> {
const actual = () => App.ClickCountView(clickCounter, {null})
expect(actual).toThrowError(App.ClickCountView.messages.noUpdateEl)
})
})

describe('updateView()', () => {
it('ClickCounter의 getValue() 실행결과를 출력한다', ()=> {
const counterValue = clickCounter.getValue()
view.updateView()
expect(updateEl.innerHTML).toBe(counterValue.toString())
})
})

describe('increaseAndUpdateView()는', ()=> {
it('ClickCounter의 increase 를 실행한다', ()=> {
spyOn(clickCounter, 'increase')
view.increaseAndUpdateView()
expect(clickCounter.increase).toHaveBeenCalled()
})
it('updateView를 실행한다', ()=> {
spyOn(view, 'updateView')
view.increaseAndUpdateView()
expect(view.updateView).toHaveBeenCalled()
})
})

it('클릭 이벤트가 발생하면 increseAndUpdateView를 실행한다', ()=> {
// todo

spyOn(view,'increseAndUpdateView')
//클릭
triggerEl.click();
expect(view.increaseAndUpdateView).toHaveBeenCalled();
})
})


모듈 수정

var App = App || {}

//App.ClickCountView = (clickCounter, updateEl) => {
//이 부분을 {updateEl, TriggerEl} -> options로 처리

App.ClickCountView = (clickCounter, options) => {
if (!clickCounter) throw new Error(App.ClickCountView.messages.noClickCounter)
if (!options,updateEl) throw new Error(App.ClickCountView.messages.noUpdateEl)

//return으로 처리하던 부분으 view 변수에 담은 후 view 를 리턴하는형태로 변경

const view = {
updateView() {
options.updateEl.innerHTML = clickCounter.getValue()
},

increaseAndUpdateView() {
clickCounter.increase()
this.updateView()
},
}

options.triggerEl.addEventListener('click',()=>{
view.increaseAndUpdateView();
})


return view
}

App.ClickCountView.messages = {
noClickCounter: 'clickCount를 주입해야 합니다',
noUpdateEl: 'updateEl를 주입해야 합니다'
}




모듈을 화면에 붙여보자!!!
<html>
<body>
//화면에 영역을 생성 한다.
<span id="counter-display"></span>
<button id="btn-increase">Increase</button>

//생성한 모듈을 호출한다.
<script src="ClickCounter.js"></script>
<script src="ClickCountView.js"></script>

<script>
(() => {
const clickCount = App.ClickCounter();
const updateEl = document.querySelector('#counter-display')
const triggerEl = document.querySelector('#btn-increase')

const view = App.ClickCountView(clickCount,{updateEl,triggerEl});
view.updateView();

})()
</script>
</body>
</html



실행화면

increase 버튼을 클릭하면 정상적으로 숫자가 증가한다.


















https://programmers.co.kr/learn/courses/30/lessons/12919?language=java#


나의 풀이



베스트 풀이 

import java.util.Arrays;

public class FindKim {
    public String findKim(String[] seoul){
        //x에 김서방의 위치를 저장하세요.
        int x = Arrays.asList(seoul).indexOf("Kim");

        return "김서방은 "+ x + "에 있다";
    }


JVM 작동원리

JVM 은 자바응용프로그램을 실행하기 위한 런타임 엔진의 역할을 함 
JVM은 자바코드에 있는 MAIN 메소드를 실제 호출하는 것 
JVM은 JRE의 일부

JAVA 응용 프로그램은 WORA( WRITE ONCE RUN ANYWHERE) 라고 함 이는 프로그래머가 하나의 시스템에서 JAVA 코드를 개발할수 있고 조정없이 다른 JAVA 지원 시스템에서 JAVA 코드가 실행될 수 있음을 의미 이것은 JVM 때문에 모두 가능




 클래스 로더의 역할 
  로드, 연결, 초기화 
로드 : 클래스 로더는 .CLASS 파일을 읽고 해당 데이터를 생성한 다음 메소드 영역에 저장 
        각 .CLASS 파일에 대해 JVM은 메소드 영역에 다음 정보를 저장함 
      1. 로드된 크래스 및 적접적인 부모 클래스의 정규화된 이름
      2. .CLASS 파일이 CLASS 또는 INTERFACE 또는 ENUM과 관련이 있는지 여부 
      3. 변수와 메소드의 정보 등 

.CLASS 파일을 로드한 후 JVM은 클래스 유형의 오브젝트를 작성하여 힙 메모리에 표기합니다. 이 객체는 java.lang패키지에 미리 정의된 class유형이며 이객체 참조를 얻으려면 object 클래스의 getClass() 메서드를 사용가능



연결 : 확인, 준비, 해결

확인 : .class 파일의 정확성을 보장한다. 즉, 유효한 컴파일러에 의해 파일 포맷 및 생성 여부를 확인한다 확인에 실패하면 런타임 예외 java.lang.VerifyError가 발생
준비 : JVM은 클래스 변수에 메모리를 할당하고 메모리를 기본값으로 초기화한다.
해결방법 : 이는 기호 참조를 유형에서 직접 참조로 대체하는 과정이다. 이 작업은 참조된 도면요소를 찾기 위해 방법 영역을 탐색하여 수행된다.

초기화 : 이단계에서 모든 정적변수는 코드 및 정적블록에 정의된 값으로 지정됩니다. 


일반적으로 세가지 클래스 로더가 존재
  1. 부트 스트랩 클래스 로더 : 모든 jvm구현에는 부트스트랩 클래스 로더가 있어야 함 java_home/ jre /lib  디렉토리에 있는 핵심 java api 클래스를 로드 하며 이경로는 일반적인 부트스트랩 경로라함 
  2.  확장 클래스 로더 : 부트 스트랩 클래스로더의 자식정도로 해석가능  java_home/jre/lib/ext(확장경로)또는 java.ext.dirs 시스템 특성으로 지정된 다른디렉토리에 있는 클래스를 로드. sun.mics.Launchers $ExtClassLoader클래스에 의해 java로 구현
  3. 시스템/응용 프로그램 클래스 로더 : 확장 클래스 로더의 자식정도로 해석가능 내부적으로 java.class.paht에 매핑된 환경 변수를 사용


// Java code to demonstrate Class Loader subsystem
public class Test
{
    public static void main(String[] args)
    {
        // String class is loaded by bootstrap loader, and
        // bootstrap loader is not Java object, hence null
        System.out.println(String.class.getClassLoader());
  
        // Test class is loaded by Application loader
        System.out.println(Test.class.getClassLoader());
    }
}    

산출:
null
sun.misc.Launcher$AppClassLoader@73d16e93



    실행 방법
계층구조 원칙에 따라 클래스를 로드 함. 시스템 클래스 로더는 확장 클래스 로더에 요청하여 부스트스랩 클래스 로더에 요청을 위임함  부트스트랩 경로에 클래스가 있으면 클래스가 로드되고 그렇지 않으면 다시 요청이 확장 클래스로더로 전달되고 없으면 다시 시스템 클래스 로더로 전달됨 최종적으로
클래스를 로드하지 ㅇ못하면 실행 시 classNotFoundException이 발생함




JVM 메모리






메소드 영역 : 메소드 영역에는 정적변수를 포함하여 클래스명, 직접적인 부모클래스 이름, 메소드 변수정보와 같은 모든 클래스 레벨 정보가 저장JVM당 1개의 영역이 있으며 이는 공유됨

힙 영역 : 모든 객체 정보가 저장됨 , JVM당 하나의 영역만 존재하며 공유됨

스택 영역 : 모든 스레드에 대해 JVM은 여기에 저장된 하나의 런타임 스택을 만듬 이 스택을 활성화 레코드/ 스택 프레임이라고 함 
해당 메소드의 모든 로컬 변수는 해당 프레임에 저장됨 스레드가 종료된 후에는 런타임 스택이 JVM에 의해 삭제됨 공유되지 않음

PC 레지스터 : 스레드의 현재 실행 명영의 주소를 저장함 각 스레드는 별도의 PC 레지스터를 보유함 
원시 메소드 스택 : 모든 스레드으ㅔ 대해 별도의 원시 스택이 작성됨 네이티브 메소드 정보를 저장함 


실행엔진 (EXCUTION ENGINE)
실행 엔진은 .CLASS(바이트 코드)를 실행 함  한줄씩 바이트코드를 읽고 다양한 메모리 영역에 있는 데이터와 정보를 사용하고 지침을 실행 

실행엔진의 분류
  1. 인터 프리터 : 바이트 코드를 한줄씩 해설한 다음 실행 ,
  2. JIT(JUST - IN TIME COMPILEER) 인터프리터의 효율성을 높이는데 사용 , 전체 바이트 코드를 컴파일하여 원시 코드로 변경하므로 인터프리터가 반복적인 메소드 호출을 볼때마다 JIT가 해당부분에 대한 직접 원시 코드를 제공하므로 효율적이다.
  3. 가비지 콜렉터  : 참조ㅈ되지 않은 객체를 파괴한다. ? 


































JDK , JRE ,JVM 간의 차이점
 
자바 개발 키트 (JDK)
JAVA Development Kit (JDK) 는 JAVA 응용 프로그램 및 애플릿을 개발하는데 사용되는 소프트웨어 개발환경
여기엔는 JAVA Runtime Environment(JRE) , 인터프리터/로더, 컴파일러(JAVAC) 아카이버(JAR) 문서 생성기(JAVADOC)  및 자바개발에 필요한 기타도구가 포함됨

자바 런타임 환경(JRE)
JAVA Runtime Environment의 약자이며 JAVA RTE 로 표현하기도 합니다.JAVA 응용프로그램을 실행하기 위한 최소 요구사항을 제공합니다. 그것으로 구성되어 JAVA가상머신 JVM 핵심 클래스 및 지원 파일 

JDK = JRE + DEVELOPEMENT TOOL
JRE = JVM + Library Classes


JDK = 자바 프로그램을 개발하고 실행할수 있는 환경을 제공 JDK 는 자바개발자만 사용
JRE  = 컴퓨터에 JAVA 프로그램만 실행 할수있는 환경을 제공하는 설치 패키지 
JVM =  JDK와 JRE모두에 포함되어있거나 포함되어있어 JDK와 JRE에서 매우 중요한 부분을 차지함 
모든 자바 프로그램은 JVM으로 들어가고 JVM은 JAVA프로그램을 라인단위로 실행해야 하므로 인터프리터라고도 합니다.

JRE와 JDK의 작동방법

JRE의 구성
  1. 배치기술 
  2. AWT
  3. IDL
  4. 기타기본라이브러리
  5. JVM
 JRE 작동방법


클래스 로더 
클래스 로더는 프로그램 실행에 필요한 모든 클래스를 로드합니다 로컬 파일 시스템의 네임 스페이스를 네트워크를 통해 가져온 네임스페이스와 분리하여 보안을 제공합니다.

바이트코드 검사
JVM 형식을 검사하고 불법적 코드를 검사하는 BYTE CODE VERIFIER 를 통해 코드를 삽입합니다. 

인터프리터 
런타임 시 바이트코드는 인터프리터에 의해 로드되고 검사되고 실행됩니다.
인터프리터의 역할 
  1. 바이트 코드 실행
  2. 기본 하드웨어에 대한 적절한 호출









두 작업 모두를 다음과 같이 표시





























1. 스프링 부트란?


8. Introducing Spring Boot

Spring Boot makes it easy to create stand-alone, production-grade Spring-based Applications that you can run. We take an opinionated view of the Spring platform and third-party libraries, so that you can get started with minimum fuss. Most Spring Boot applications need very little Spring configuration.

You can use Spring Boot to create Java applications that can be started by using 

java -jar

 or more traditional war deployments. We also provide a command line tool that runs “spring scripts”.



Our primary goals are:

Provide a radically faster and widely accessible getting-started experience for all Spring development.

Be opinionated out of the box but get out of the way quickly as requirements start to diverge from the defaults.

Provide a range of non-functional features that are common to large classes of projects (such as embedded servers, security, metrics, health checks, and externalized configuration).

Absolutely no code generation and no requirement for XML configuration.

출처: https://docs.spring.io/spring-boot/docs/2.0.3.RELEASE/reference/html/


영어다.. 다음에  알아보자..



하지만 난 의지의 한국인이니 번역기를 돌려보자!!  


모든 스프링 개발을 위해 매우 빠르고 폭넓게 액세스 할 수 있는 시작 경험을 제공합니다.
설정을 일일이 설정하지 않아도 설정을 제공해 줍니다 하지만 사용자가 원하는 대로 설정을 쉽고 빠르게 변경 가능합니다.
많은 종류의 프로젝트(예: 내장형 서버, 보안, 메트릭, 상태 점검 및 외부 구성)에 공통적으로 사용되는 다양한 비기능적 기능을 제공합니다.
코드 생성도 없고 XML 구성도 필요하지 않습니다.


그렇다고 한다.

간단하게 말해 쉽고 편하다 그럼 사용해보자.




2. 스프링 부트 프로젝트 만들기


1. start.spring.io 에서 프로젝트 생성하기 


a. 프로젝트 빌드 툴을 선택 가능 

            - 메이븐, 그래 들

b. 언어 선택 가능

            -java , kotlin , Groovy

c. Dependencies 선택 가능

            -web  , JPA  ETC..


d. group id와 Artifact

     Groupid와 Artifact는?


Groupid     

    groupid는 프로젝트마다 접근할 수 있는 고유한 이름을 정의

    groupId는 package 명명 규칙을 따르도록 한다  하위 그룹은 얼마든지 추가할 수 있다

    com.naver.maven , com.naver.gradle 

    프로젝트 구조를 사용하면 잘 구분되는 groupId를 만들 수 있다.

   com.naver.maven.report,  com.naver.maven.plugin



Artifact

 일반적으로 소프트웨어 분야에서의 artifact는 소프트웨어 개발 프로세스에 의해 생산되는 무언가를 의미한다. 소프트웨어와 관련된 문서나 실행 파일이 될 수 있을 것이다.


maven에서 artifact 란 용어는 메이븐 빌드의 결과로 얻을 수 있는 일반적인 jar 나 war 또는 여타의 실행 파일을 의미한다. maven에서 artifact는 시스템의 groupId, artifactId, version 에 의해 구분되며 이는 빌드시 필요한 의존성(일반적으로 다른 jar 파일들)을 구분하는 데 사용된다. 


추가로 Version

숫자와 점으로 이루어진 일반적인 버전 형태를 사용한다(1.0, 1.1, 1.0.1, …).

버전에 SNAPSHOT이 포함될 경우 배포 시마다 최종 버전을 체크해서 업데이트해준다고 한다. 

*빌드 시 마다가 아닌 DAILY 기준


선택 후  Generate Project 클릭하면 프로젝트 생성 완료


2. IDE에서 생성 


a.Spring initializr

b.Maven

c.Gradle


본인이 편한 방식을 프로젝트를 생성하면 된다.

난 메이븐이 편함으로 메이븐으로!

프로젝트 생성 시 그룹아이디, atifact id, version 정보 입력 후 생성


스프링부트 문서에 보면 메이븐 시작시 parent 계층을 추가해주어야 함 물론 필수는 아니다.


https://docs.spring.io/spring-boot/docs/1.4.1.RELEASE/reference/htmlsingle/#using-boot-maven

<parent>를 이용해 spring-boot-starter-parent를 상속하는 이유는 스프링 부트의 의존성 관리를

지원받기 위해서다 

즉 지원받을 필요 없거나 직접 설정할 수 있다면 상속받을 필요가 없다. 다만 상속받는 것이 겁나 편하다.

스프링 부트를 사용하는 가장 원초적 이유가 편하고 쉽다 라는 것을 생각해봤을 때..

특별한 이유가 없다면 상속받는 게 좋은 것 같다. 


spring-boot-starter-parent 관한 자세한 내용은 해당 블로그를 참고 하자. http://eglowc.tistory.com/38


추가로 내장 톰캣 및 웹 구축을 편하게 하기 위해 

spring-boot-starter-web를 추가한다.

https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web/2.0.5.RELEASE

spring-boot-starter-web는 RESTfully, Spring MVC를 사용하는 애플리케이션을 포함한 웹 구축용 Starter. Tomcat을 기본 내장형 컨테이너로 사용



폼에 사용할 dependency를 추가 


거의 다 온 것 같다.



실행 시킬 파일을 생성 한다. 

스프링을 실행시킬 클래스를 하나 만들자.

이름은 크게 상관없다. 나는 Application.java로 만들었다.

@SpringBootApplication 어노테이션을 이용해 스프링 부트를 실행할 파일 이란 것을 정의해 줍니다.


@SpringBootApplication 어노테이션을 추가하면 아래의 어노테이션이 자동으로 포함

@Configuration 

          현재 클래스가 설정 파일임을 정의해주는 어노테이션

@EnableAutoConfiguration

       스프링 부트 클래스 경로 세팅 및 Bean을 추가시켜 주는 어노테이션

@ComponentScan

         다른 컴포넌트, 서비스 및 설정 등을 찾게 도와주는 어노테이션


main 메서드를 생성한 후

SpringApplication.run(Application.class,args); 

라고 작성해 주고 서버를 실행하면 스프링 부트가 정상 작동하는 것을 확인할 수 있다.

추가로!

Application.java 위치에 관한 내용

최상위 패키지 구조에 등록을 해야한다.  

@SpringBootApplication에 포함된 @Componet 어노테이션은 빈으로 등록할 수 있는 모든 것을 빈으로 등록하기 때문에 최상위가 아닌 java 패키지 바로 아래 파일이 등록될 경우 빈으로 이용할 수 있는 모든 것을 우리가 원하지 않아도 등록해 버린다.

그러므로

com.naver.maven  상위 패키지에 등록을 하는 것이 좋다.



+ Recent posts