이번에는 이전 게시글에서 '쿠키'를 통해 로그인한 사용자 이름을 저장하는 것을 구현했다면 이번에는 '세션'을 이용해 본다.

- 쿠키 : 클라이언트쪽에 저장되어 보안성이 좋지 않음, 데이터의 수정이 될 수 있음

- 세션 : 서버쪽에 저장되어 쿠키보다 보안이 좋음

> 세션으로 구현하기 위해 랜덤 세션 id를 생성하고 세션 객체에 해당 랜덤 id를 키값으로 필요한 데이터를 담아두고

> 쿠키에 해당 랜덤 세션 키를 저장하는 방식으로 구현한다.

EX)

<app.js>

const http = require('http');
const fs = require('fs');
const url = require('url');
const qs = require('querystring');

const parseCookie = (cookie = '') => {
    // name=hyr;expires=ggg
    // [ name=hyr, expires=ggg]
    return cookie.split(';')
        // [ [name, hyr], [expires, ggg] ]
          .map(e => e.split('='))
          .reduce((acc, [key, val]) => {
              acc[key] = decodeURIComponent(val);
              return acc;
          }, {});
}

// 세션 객체 생성(서버 메모리에 기억)
let session = {

}

const server = http.createServer((req, res) => {
    let cookieStr = req.headers.cookie;
    let cookies = parseCookie(cookieStr);

    if(req.url.startsWith('/login')){
        let { query } = url.parse(req.url);
        let reqParams = qs.parse(query);

        // random int 값 생성
        let randomInt = +new Date();
        let expires = new Date();
        expires.setMinutes(expires.getMinutes() + 5);

        // randomInt 키값을 갖는 데이터를 session 객체에 동적 추가해줌
        session[randomInt] = {
            name : reqParams.name,
            expires
        };

        res.writeHead(302, {
            Location : '/',
            // session 키값을 쿠키에 저장해 놓는다.
            'Set-Cookie' : `sessionId=${randomInt}; Expires=${expires.toGMTString()}; HttpOnly; Path=/`
        });
        res.end();
    } else if(cookies.sessionId && session[cookies.sessionId].expires > new Date()) {
    	// 쿠키에 저장한 랜덤세션id가 존재하면서 유효시간이 남아있는 경우 접근
        res.writeHead(200, {'Content-Type' : 'text/html; charset=utf-8'});
        res.end(`<h1>${session[cookies.sessionId].name}님 환영합니다.`);
    } else { // login page
        fs.readFile('./server2.html', (err, data) => {
            res.end(data);
        });
    }
});

server.listen(8082, () => {
    console.log('8082 server is listening');
});

server.on('error', (error) => {
    console.error(error);
});

이번에는 쿠키와 세션에 서버 데이터를 담아 전달하는 방법에 대해 알아보겠습니다.

1. 개념 : 쿠키와 세션 모두 사용자의 데이터를 담을 수 있습니다. 하지만 가장 큰 차이점은

쿠키는 클라이언트에 쿠키가 저장이 되어 보안에 취약합니다.

세션은 서버에 저장되어 쿠키에 비해 보안적으로 안전합니다.

1. 쿠키 저장해 보기

// http 모듈 import
const http = require('http');
// 미리 생성한 html 파일을 읽기 위한 fs 모듈 임포트
const fs = require('fs');

const server = http.createServer((req, res) => { // req : 요청, res : 응답
    // 쿠키를 지정 : 키 = 값 형식으로 ; 를 기준으로 설정
    res.writeHead(200, {'Set-Cookie' : 'mycookie=value1'});
    res.writeHead(200, {'Set-Cookie' : 'mycookie2=value2'});
    
    res.end('cookie save test');
});

server.listen(8081, () => {
    console.log('8081 server is lisening');
});

node app.js 로 서버 실행 후 http://localhost:8081/ 로 브라우저를 열고 F12 개발자 도구를 켠 다음 'application'탭을 보면 설정한 쿠키값들이 보임을 알 수 있다.

mycookie = value1

mycookie2 = value2

 

이번엔 쿠키를 이용해 간단한 로그인을 구현해 보도록 하겠다.

로그인 페이지에서 입력한 사용자의 이름을 쿠키에 name=hyr 처럼 저장한 후 쿠키값이 있을 경우 hyr님 반갑습니다.를 응답한다.

<app.js>

// http 모듈 import
const http = require('http');
// 미리 생성한 html 파일을 읽기 위한 fs 모듈 임포트
const fs = require('fs');
const url = require('url');
const qs = require('querystring');

// 쿠키 파싱 함수
const parseCookies = (cookie = '') => {
    // cookie => mycookie=value1;mycookie2=value2
    
    // eachCookies = [mycookie=value1, mycookie2=value2]
    let eachCookies = cookie.split(';');
    // [ [mycookie, value1], [mycookie2, value2] ]
    return eachCookies.map(e => e.split('='))
                      .reduce((acc, [key, val]) => { // acc : 누적
                          acc[key.trim()] = decodeURIComponent(val); // acc에 동적으로 key값에 value추가
                          return acc;
                      }, {});    
}
    
const server = http.createServer((req, res) => { // req : 요청, res : 응답
    // 쿠키 접근(req.headers.cookie)
    console.log(req.headers.cookie); // 문자열이기 때문에 파싱이 필요
    
    // 쿠키 파싱 함수를 통해 쿠키객체 얻어옴
    const cookies = parseCookies(req.headers.cookie);
  
    // req.url > 요청한 url 주소가 담겨있다.
    if(req.url.startsWith('/login')){ // /login으로 시작하는 url요청일 경우
        // http://localhost:8081/login?name=sooingkr 에서 ?name=sooingkr 부분인 쿼리스트링 부분을 가져온다.
        // 해당 정보가 객체로 query에 들어오게 됨
        const { query } = url.parse(req.url); 
        const { name } = qs.parse(query); // querystring 모듈로 name값을 파싱함

        // 저장할 쿠키 데이터의 유효시간을 지정하기 위함
        const expires = new Date();
        expires.setMinutes(expires.getMinutes() + 5); // 제한 시간 설정
        res.writeHead(302, // 302 코드 > redirection코드로 Location에 지정한 곳으로 리다이렉션한다. 
            {
                Location : '/',
                // expire 유효시간 Http통신 요청만 가능, / 요청에 대해서만 등 다양한 옵션을 설정할 수 있다.
                'Set-Cookie' : `name=${encodeURIComponent(name)}; Expires=${expires.toGMTString()}; HttpOnly; Path=/`
            }
        );

        res.end('<p>end</p>'); // 요청 응답 끝
    } else if(cookies.name) { // 로그인을 통해 쿠키에 name값이 존재하면
        
        res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
        // 쿠키에 저장된 name값을 응답
        res.end(`${cookies.name}님 안녕하세요`);
    } else { // 로그인 페이지
        // 로그인 페이지 html 을 읽어 로그인 페이지로 이동
        fs.readFile('./server2.html', (err,data) => {
            res.end(data);
        });
    }
});

server.listen(8081, () => {
    console.log('8081 server is lisening');
});

 

<server2.html>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>로그인 페이지</title>
</head>
<body>
    <h1>로그인페이지 입니다.</h1>
    <form action="/login" method="GET">
        <input type="text" id="name" name="name" />
        <button type="submit">로그인</button>
    </form>
</body>
</html>

<결과>

로그인 페이지(server2.html)

로그인 후 화면

 

세션은 다음 페이지에서 이어 설명하도록 하겠습니다.

 

- express 를 사용해 편리하게 서버를 구성할 수 있지만, 그전에 util모듈을 이용해 서버를 구성해보자.

1. text/html로 응답

// http 모듈 임포트
const http = require('http');

const server = http.createServer((req, res) => { // req : 요청정보, res : 응답정보
    console.log('server start');

    // 응답 Header정보 설정
    // 1 파라미터 > 응답 상태(status) ex) 200 : 성공 코드
    // 2. Content-Type 을 text/html charset=utf-8로 지정해 한글이 깨지지 않도록 함
    res.writeHead(200, {'Content-Type' : 'text/html; charset=utf-8'});

    res.write('<h1>응답할 text/html 문자열</h1>');
    res.write('<p>여러번 호출할 수 있음</p>');

    res.end('<p>응답의 끝임을 알림</p>');
});

server.listen(8081, () => { // 8081 포트로 서버 리스닝
    console.log('server 8081 port is listening');
});

// 서버 에러 응답 이벤트 리스너
server.on('error', (error) => {
    console.error(error);
})

http://localhost:8081/ 호출 결과>

이처럼 <h1>응답할 text/html 문자열</h1> 처럼 text/html을 직접 write해서 응답할 수도 있지만 html 파일을 미리 생성해 놓고 읽어서 응답할 수도 있다.

 

2. html 파일을 미리 생성하고 html페이지 응답하기

<app.js>

// http 모듈 import
const http = require('http');
// 미리 생성한 html 파일을 읽기 위한 fs 모듈 임포트
const fs = require('fs');

const server = http.createServer((req, res) => { // req : 요청, res : 응답
    // 현재 경로의 server1.html 파일 읽기
    fs.readFile('./server1.html', (err, data) => {
        if(err) throw err; // 실패 예외처리
        // 읽은 파일 내용을 응답
        res.write(data);
    });
});

server.listen(8081, () => {
    console.log('8081 server is lisening');
});

<server1.html>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>FILE로 읽기</title>
</head>
<body>
    <h1>server1.html 페이지 입니다. 반갑습니다.</h1>
</body>
</html>

[ 팩토리 메서드(Factory Method) 패턴 ]
: 객체 생성을 Client단에서 하지 않고 별도로 객체를 생성하는 서브클래스(Factory Class)를 만들어 객체를 생성함으로써 결합도를 낮추고 코드 중복을 낮추는 디자인 패턴.

다음 코드는 Client단에서 직접 객체를 생성할 때 발생할 수 있는 문제점을 코드로 나타낸 예시이다.

조건에 따라서, 분기하며 객체를 생성할 경우 아래처럼 if 혹은 switch로 객체를 생성하는대 이때 해당 switch문은 여러군대서 작성될 수 있고
따라서, 객체를 생성할 때마다 중복 코드가 발생할 수 있게 된다.
그뿐만 아니라, 직접 객체를 생성함으로써 결합도도 높아져 클래스 수정시 많은 부분을 수정이 필요하게 된다.

public interface Animal {
    public String getName();
}

public class Dog implements Animal{

    private String name;

    public Dog(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }
}

public class Cat implements Animal {
    private String name;

    public Cat(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }
}

public class Rabbit implements Animal{
    private String name;

    public Rabbit(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }
}

main> main에 있는 switch문 여러군대서 중복되는 문제점 및 testClass와의 결합도가 높아지는 문제점이 있다.

@Test
    public void test6() throws Exception {

        String objName = "DOG";
        Animal obj = null;
        switch(objName) {
        case "DOG":
            obj = new Dog(objName);
            break;
        case "CAT":
            obj = new Cat(objName);
            break;
        case "RABBIT":
            obj = new Rabbit(objName);
            break;
        default:
            break;
        }

        System.out.println(obj.getName());
    }

위의 문제있는 코드를 '팩토리 메서드'패턴을 이용해 수정한 예)

  • 객체를 생성하는 것을 AnimalFactory라는 서브클래스에 위임한다.

    public class AnimalFactory {

      public static Animal createAnimalObj(String name) {
          Animal retAnimal = null;
          switch(name) {
          case "DOG":
              retAnimal = new Dog(name);
              break;
          case "CAT":
              retAnimal = new Cat(name);
              break;
          case "RABBIT":
              retAnimal = new Rabbit(name);
              break;
          }
          return retAnimal;
      }

    }

  • main

    @Test

      public void test6() throws Exception {        
          **Animal animalObj = AnimalFactory.createAnimalObj(objName);**
          System.out.println(animalObj.getName());
      }

1. Strategy Pattern

Created: Oct 08, 2019 10:27 AM
Tags: strategy pattern,전략 패턴

[ Strategy Pattern ]

  • : 자주 변동이 생기거나 기존 구조에 새로 추가될 경우 해당 알고리즘을 "캡슐화"해 주입함으로써

  • 클라이언트와는 독립적으로 알고리즘을 교환할 수 있게되는 디자인 패턴

    public abstract class Animal {

      // 각 객체에서 공통으로 사용될 이름 변수
      String name;
    
      public Animal(String name) {
          this.name = name;
      }
    
      public void doBehavior() {
          // Dog, Eagle, Penguin 각 객체 모두 공통으로 동일한 내용으로 호출되는 메서드
          doEat();
    
          // 각 동물 객체별로 재구현이 필요한 이동관련 메서드
          doMove();
    
          // 각 동물 객체별로 재구현이 필요한 울음소리 메서드
          doCry();
    
          if(canFly()) { // hook method로 각 객체에서 재구현해 실행여부를 결정(optional)
              doFly();
          }
      }
    
      public void doEat() {
          System.out.println(name + "(은) 얌얌 밥을 먹습니다.");
      }
    
      public abstract void doMove();
    
      public abstract void doCry();
    
      public boolean canFly() {
          return false;
      }
    
      public abstract void doFly();

    }

    public class Dog extends Animal{

      public Dog(String name) {
          super(name);
      }
    
      @Override
      public void doMove() {
          System.out.println(name + "(은) 네발로 기어갑니다.");
      }
    
      @Override
      public void doCry() {
          System.out.println(name + "(은) 왈왈 짓습니다.");
      }
    
      @Override
      public void doFly() {
          // TODO Auto-generated method stub
      }

    }

    public Eagle(String name) {

          super(name);
      }
    
      @Override
      public void doMove() {
          System.out.println(name + "(은) 날아갑니다.");
      }
    
      @Override
      public void doCry() {
          System.out.println(name + "(은) 쒱~ 하고 웁니다.");
      }
    
      @Override
      public void doFly() {
          System.out.println(name +"(은) 날게가 있어 날 수 있습니다.");
      }
    
      // hook method를 true로 바꿔 doFly()가 동작할 수 있도록 한다.
      @Override
      public boolean canFly() {
          return true;
      }

    }

    public class Penguin extends Animal{

      public Penguin(String name) {
          super(name);
      }
    
      @Override
      public void doMove() {
          System.out.println(name + "(은) 뒤뚱뒤뚱 걷습니다.");
      }
    
      @Override
      public void doCry() {
          System.out.println(name + "(은) 펭귄~하고 웁니다.");
      }
    
      @Override
      public void doFly() {
          System.out.println(name + "(은) 날개가 없어 날 수는 없습니다.");
      }
    
      @Override
      public boolean canFly() {
          return true;
      }

    }

    public class CommonModelTest {

      private static final Logger logger = LoggerFactory.getLogger(CommonModelTest.class);
    @Test
    public void test1() {
        Dog dog = new Dog("강아지");
        dog.doBehavior();

        Eagle eagle = new Eagle("독수리");
        eagle.doBehavior();

        Penguin penguin = new Penguin("펭귄");
        penguin.doBehavior();
        /*
          강아지(은) 얌얌 밥을 먹습니다.
            강아지(은) 네발로 기어갑니다.
            강아지(은) 왈왈 짓습니다.
            독수리(은) 얌얌 밥을 먹습니다.
            독수리(은) 날아갑니다.
            독수리(은) 쒱~ 하고 웁니다.
            독수리(은) 날게가 있어 날 수 있습니다.
            펭귄(은) 얌얌 밥을 먹습니다.
            펭귄(은) 뒤뚱뒤뚱 걷습니다.
            펭귄(은) 펭귄~하고 웁니다.
            펭귄(은) 날개가 없어 날 수는 없습니다.
         * */
    }    


}

위의 코드는 Dog, Eagle, Penguin 동물 객체의 행동을 구현하였는데, 동물의 공통 동작을 정하기 위해서 Template Method Pattern을 이용해서 구성하였다.

  1. 먼저, Dog, Eagle, Penguin은 공통적으로 doEat 밥을 먹는 메서드를 Animal에 구현하였고
  2. 울음소리를 나타내는 doCry는 Animal을 상속받는 각 동물객체에서 재정의하도록 하였다.
  3. 마지막으로 doFly는 강아지는 날 수 없음으로 Optional하게 선택할 수 있도록 Hook Method인 canFly()를 넣어 각 동물객체에서 재정의하여 doFly를 수행할지 여부를 지정할 수 있도록 하여 큰 틀을 구성하였다.

→ 이때, doFly 메서드의 구현방식을 변경하고(날 수 있는 녀석, 없는 녀석)

→ doLive라는 어디에 서식하는지에 대한 메서드를 추가한다고 했을 때

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

—> 1. 먼저, doFly 메서드는 Dog한테는 해당사항이 없음에도 override하고 있는 문제점

  1. doFly의 상세 구현 내용을 변경하기 위해 각 객체별로 접근해서 구현 내용을 일일이 바까주어

    야 하는 점.(지금은 객체가 3개뿐이지만, doFly메서드를 사용하는게 20,30개가 넘는다면?)

→ 따라서, 이럴 경우에는 doFly메서드 알고리즘을 별도로 "캡슐화"해서 주입하도록 해 언제든지 알고리즘을 교환하는 방식을 통해 클라이언트 실행단과의 종속성을 제거할 수 있다.

수정 방향)

  1. doFly메서드에 대해 "날수 있는", "날수 없는"의 알고리즘을 분리한다.
  2. 각 객체에 주입한다.

공통사항을 관리하는 Animal 추상 클래스 : 아까와 동일하다.

public abstract class Animal {
    // 각 객체에서 공통으로 사용될 이름 변수
    String name;

    public Animal(String name) {
        this.name = name;
    }

    public void doBehavior() {
        // Dog, Eagle, Penguin 각 객체 모두 공통으로 동일한 내용으로 호출되는 메서드
        doEat();

        // 각 동물 객체별로 재구현이 필요한 이동관련 메서드
        doMove();

        // 각 동물 객체별로 재구현이 필요한 울음소리 메서드
        doCry();

        if(canFly()) { // hook method로 각 객체에서 재구현해 실행여부를 결정(optional)
            doFly();
        }
    }

    public void doEat() {
        System.out.println(name + "(은) 얌얌 밥을 먹습니다.");
    }

    public abstract void doMove();

    public abstract void doCry();

    public boolean canFly() {
        return false;
    }

    public abstract void doFly();
}

DoFly 인터페이스

: 날수 있는 알고리즘, 날수없는 알고리즘을 관리할 DoFly 인터페이스

public interface DoFly {
    // 나는 것과 관련된 메서드
    public void fly();
}

CanFly 클래스

: DoFly 인터페이스를 구현해 날 수 있는 알고리즘을 구현한 클래스로 ⇒ 알고리즘을 "캡슐화"했다.

public class CanFly implements DoFly{

    private String name;

    public CanFly(String name) {
        this.name = name;
    }

    @Override
    public void fly() {
        System.out.println("날 수 있습니다.");
    }

}

CanNotFly 클래스

: 위와 마찬가지로 날 수 없는 알고리즘을 캡슐화

public class CanNotFly implements DoFly{

    private String name;

    public CanNotFly(String name) {
        this.name = name;
    }

    @Override
    public void fly() {
        System.out.println("날개는 있지만 날  수 없습니다.");
    }

}

public class CanNotFly implements DoFly{

    private String name;

    public CanNotFly(String name) {
        this.name = name;
    }

    @Override
    public void fly() {
        System.out.println("날개는 있지만 날  수 없습니다.");
    }

}

Dog 클래스

: 강아지 객체로 Animal을 상속했으며 강아지는 날수없음으로 Animal의 canFly() hook method를 구현하지 않았다.(default false)

public class Dog extends Animal{

    public Dog(String name) {
        super(name);
    }

    @Override
    public void doMove() {
        System.out.println(name + "(은) 네발로 기어갑니다.");
    }

    @Override
    public void doCry() {
        System.out.println(name + "(은) 왈왈 짓습니다.");
    }

    @Override
    public void doFly() {
        // TODO Auto-generated method stub
    }

}

Eagle 클래스

: 아까는 doFly() 내부에 직접 상세구현했다면 이번엔 DoFly fly 를 선언해 생성자에서 CanFly 알고리즘을 주입하는 방식으로 바깠다.

펭귄의 경우 날 수 없음으로 CanNotFly가 주입될 것(즉, 알고리즘 교환이 용이하다.)

public class Eagle extends Animal {

    DoFly fly;

    public Eagle(String name) {
        super(name);
        fly = new CanFly(name); // 각 객체에서 구현한 것을 주입한다.(날 수 있는 알고리즘 객체를 넣음)
    }

    @Override
    public void doMove() {
        System.out.println(name + "(은) 날아갑니다.");
    }

    @Override
    public void doCry() {
        System.out.println(name + "(은) 쒱~ 하고 웁니다.");
    }

    @Override
    public void doFly() {
        fly.fly();
    }

    // hook method를 true로 바꿔 doFly()가 동작할 수 있도록 한다.
    @Override
    public boolean canFly() {
        return true;
    }

}

Penguin 클래스

public class Penguin extends Animal{

    DoFly fly;

    public Penguin(String name) {
        super(name);
        this.fly = new CanNotFly(name); // 펭귄은 날수 없는 알고리즘을 주입함
    }

    @Override
    public void doMove() {
        System.out.println(name + "(은) 뒤뚱뒤뚱 걷습니다.");
    }

    @Override
    public void doCry() {
        System.out.println(name + "(은) 펭귄~하고 웁니다.");
    }

    @Override
    public void doFly() {
        fly.fly();
    }

    @Override
    public boolean canFly() {
        return true;
    }

}

실행 main 클래스

public class CommonModelTest {
    private static final Logger logger = LoggerFactory.getLogger(CommonModelTest.class);


    @Test
    public void test1() {
        Dog dog = new Dog("강아지");
        dog.doBehavior();

        Eagle eagle = new Eagle("독수리");
        eagle.doBehavior();

        Penguin penguin = new Penguin("펭귄");
        penguin.doBehavior();
        /*
         *  강아지(은) 얌얌 밥을 먹습니다.
            강아지(은) 네발로 기어갑니다.
            강아지(은) 왈왈 짓습니다.
            독수리(은) 얌얌 밥을 먹습니다.
            독수리(은) 날아갑니다.
            독수리(은) 쒱~ 하고 웁니다.
            날 수 있습니다.
            펭귄(은) 얌얌 밥을 먹습니다.
            펭귄(은) 뒤뚱뒤뚱 걷습니다.
            펭귄(은) 펭귄~하고 웁니다.
            날개는 있지만 날  수 없습니다.
         * */
    }    


}

2. State Pattern

Created: Oct 15, 2019 10:51 AM
Tags: strategy pattern,상태 패턴,스테이트 패턴

  • 스테이트 패턴(State Pattern)

: 실세계의 많은 객체는 "상태"에 따라 서로 다른 업무를 처리하는 경우가 많다.
이때, 가장 직관적인 방법은 수행하는 행동(메서드)마다 상태를 조건으로 체크해서 조건별로 수행하는 코드를 다르게 작성하는 것이다.
이럴경우 문제는 메서드마다 조건문이 중복으로 들어가게 되고, 추가로 상태코드가 추가될 경우 모든 메서드를 수정해야하며, 가독성 또한 잃게 된다.

이런 문제점을 개선하기 위해 **"변경되는 사항"**인 "**상태**"를 캡슐화하고 해당 상태객체를 관리할 수 있는 인터페이스를 만들어 수행하는 메서드에서 상태에 따른 구체적인 구현을 하는 것이 아닌 메서드별로 상태객체를 관리하는 인터페이스에만 의존하게 되어 상태가 추가되더라고 메서드 내에서 구현이 변경되지 않을 수 있게 된다. 구현은 각 상태 객체가 담당하게 되어 조건식 등이 불필요해 진다.

스테이트 패턴의 필요성을 설명하는 예>

: 다음은 형광등 객체를 구현한 것으로 상태(OFF/ON)에 따라 turnOn메서드와 turnOff메서드의 처리가 다르게 되어 있다.

다행히 메서드가 두개여서 망정이지 더 많은 메서드가 존재시 매 메서드마다 상태를 조건문 분기하며 처리시 가독성도 떨어지고, 상태가 하나 추가되어도 모든 메서드를 전부 수정해야 하는 문제점이 발생한다.

    public class Light {
        private static final int OFF = 0;
        private static final int ON = 1;
        private int state;

        public Light() {
            state = OFF; // 최초 OFF 상태
        }

        public void setState(int state) {
            this.state = state;
        }

        public void turnOn() {
            if(ON == state) {
                System.out.println("이미 켜져있습니다.");
            } else { // OFF 상태
                System.out.println("불이 켜졌습니다.");
                state = ON; // OFF -> ON으로 상태 변경
            }
        }

        public void turnOff() {
            if(OFF == state) {
                System.out.println("이미 불이 꺼져있습니다.");
            } else {
                System.out.println("불이 꺼졌습니다.");
                state = OFF; // ON -> OFF
            }
        }
    }

main

        Light light = new Light(); // state == OFF
            light.turnOff(); 
            light.turnOn();
            light.turnOff();
            /*
            이미 불이 꺼져있습니다.
            불이 켜졌습니다.
            불이 꺼졌습니다.
             * */

만약, 이 예제에서 state가 sleeping(취침모드)가 추가된다면 어떻게 수정될까?

    public class Light {
        private static final int OFF = 0;
        private static final int ON = 1;
        private static final int SLEEPING = 2;
        private int state;

        public Light() {
            state = OFF; // 최초 OFF 상태
        }

        public void setState(int state) {
            this.state = state;
        }

        public void turnOn() {
            if(ON == state) {
                System.out.println("취침모드로 전환합니다.");
                state = SLEEPING;
            } else if(SLEEPING == state) {
                System.out.println("불이 켜졌습니다.");
                state = ON;
            } else { // OFF 상태
                System.out.println("불이 켜졌습니다.");
                state = ON; // OFF -> ON으로 상태 변경
            }
        }

        public void turnOff() {
            if(OFF == state) {
                System.out.println("이미 불이 꺼져있습니다.");
            } else { // ON or SLEEPING
                System.out.println("불이 꺼졌습니다.");
                state = OFF; // ON or SLEEPING -> OFF
            }
        }
    }

main

            Light light = new Light(); // state == OFF
            light.turnOff(); 
            light.turnOn();
            light.turnOn();
            light.turnOff();
            /*
            이미 불이 꺼져있습니다.
            불이 켜졌습니다.
            불이 꺼졌습니다.
             * */

상태가 하나 추가됐을 뿐인데 turnOn과 turnOff모두 조건이 추가되고 if문으로 인해 가독성도 떨어지게 되었다.

스테이트 패턴을 적용해 수정해보자.

상태들을 관리할 State 인터페이스

    public interface State { // State를 관리할 인터페이스
        public void turnOn(Light light);
        public void turnOff(Light light);
    }

On상태 클래스

    public class On implements State{
      // 각 상태별로 메서드를 구현시킴으로 별도의 조건문 분기가 필요치 않다.
        @Override
        public void turnOn(Light light) {
            System.out.println("취침모드로 전환됩니다.");
            light.setState(new Sleeping());

        }

        @Override
        public void turnOff(Light light) {
            System.out.println("불이 꺼집니다.");
            light.setState(new On());
        }

    }

Off상태 클래스

    public class Off implements State{

        @Override
        public void turnOn(Light light) {
            System.out.println("불이 켜집니다.");
            light.setState(new On());

        }

        @Override
        public void turnOff(Light light) {
            System.out.println("이미 불이 꺼져있습니다.");
        }

    }

Sleeping 상태 클래스

    public class Sleeping implements State {

        @Override
        public void turnOn(Light light) {
            System.out.println("불이 켜집니다.");
            light.setState(new On());
        }

        @Override
        public void turnOff(Light light) {
            System.out.println("불이 꺼집니다.");
            light.setState(new Off());
        }

    }

Light 클래스

    public class Light {
        private State state;

        public Light() {
            state = new Off(); // 최초 OFF 상태
        }

        public void setState(State state) {
            this.state = state;
        }

        public void turnOn() {
            state.turnOn(this); // interface인 State에 의존함으로 상태가 추가되어도 코드가 변경될 일이 없다.
            // turnOn메서드가 수행되고 Light객체의 상태를 변경시켜주기 위해 this로 Light객체를 전달한다.
        }

        public void turnOff() {
            state.turnOff(this);
        }
    }

main

            Light light = new Light(); // state == OFF
            light.turnOff(); 
            light.turnOn();
            light.turnOn();
            light.turnOff();
            light.setState(new On()); // 이렇게 중간에 state를 지정해줄 수도 있음
            light.turnOn();
            /*
            이미 불이 꺼져있습니다.
            불이 켜집니다.
            취침모드로 전환됩니다.
            불이 꺼집니다.
            취침모드로 전환됩니다.
             * */

이렇게 state pattern을 적용하게 되면 추가적으로 상태가 추가되더라도 Light 클래스 자체는 더이상 수정이 필요하지 않을뿐 아니라 가독성 또한 좋게 된다.

하지만, 이 코드에서도 약간 아쉬운 점이 있는데 각 상태 메서드 내에서 메서드를 수행하고 상태를 변경하기 위해 setState하는 과정에서 상태 객체를 매번 생성하게 된다.

이는 메모리 낭비하게 되어 성능을 저하시킬 수 있음으로 각 상태 객체를 "싱글톤"으로 수정하도록 하자.

최종 state pattern Ex)

On 클래스

    public class On implements State{

        private static On on = null;

        private On() {} // private로 생성을 제한

        public static On getInstance() {
            if(on == null) {
                on = new On();
            }
            return on;
        }

        @Override
        public void turnOn(Light light) {
            System.out.println("취침모드로 전환됩니다.");
            light.setState(Sleeping.getInstance());

        }

        @Override
        public void turnOff(Light light) {
            System.out.println("불이 꺼집니다.");
            light.setState(On.getInstance());
        }

    }

Off 클래스

    public class Off implements State{

        private static Off off = null;

        private Off() {}

        public static Off getInstance() {
            if(off == null)
                off = new Off();

            return off;
        }

        @Override
        public void turnOn(Light light) {
            System.out.println("불이 켜집니다.");
            light.setState(On.getInstance());

        }

        @Override
        public void turnOff(Light light) {
            System.out.println("이미 불이 꺼져있습니다.");
        }

    }

Sleeping  클래스

    public class Sleeping implements State {

        private static Sleeping sleeping = null;

        private Sleeping() {}

        public static Sleeping getInstance() {
            if(sleeping == null)
                sleeping = new Sleeping();

            return sleeping;
        }

        @Override
        public void turnOn(Light light) {
            System.out.println("불이 켜집니다.");
            light.setState(On.getInstance());
        }

        @Override
        public void turnOff(Light light) {
            System.out.println("불이 꺼집니다.");
            light.setState(Off.getInstance());
        }

    }

Light 클래스

    public class Light {
        private State state;

        public Light() {
            state = Off.getInstance(); // 최초 OFF 상태
        }

        public void setState(State state) {
            this.state = state;
        }

        public void turnOn() {
            state.turnOn(this); // interface인 State에 의존함으로 상태가 추가되어도 코드가 변경될 일이 없다.
            // turnOn메서드가 수행되고 Light객체의 상태를 변경시켜주기 위해 this로 Light객체를 전달한다.
        }

        public void turnOff() {
            state.turnOff(this);
        }
    }

main

            Light light = new Light(); // state == OFF
            light.turnOff(); 
            light.turnOn();
            light.turnOn();
            light.turnOff();
            light.setState(On.getInstance()); // 이렇게 중간에 state를 지정해줄 수도 있음
            light.turnOn();
            /*
            이미 불이 꺼져있습니다.
            불이 켜집니다.
            취침모드로 전환됩니다.
            불이 꺼집니다.
            취침모드로 전환됩니다.
             * */

스테이트 패턴은 기본 골격 구성이 스트레티지 패턴(전략패턴)과 같은데 둘의 차이점은

스트레티지 패턴 ⇒ 자주 변동사항이 생기는 메서드 및 항목을 캡슐화

스테이트 패턴 ⇒ 조건 상태를 캡슐화

하는게 차이점이다.

3. Template Method Pattern

Created: Oct 16, 2019 12:09 PM
Tags: template method pattern,템플릿 메서드 패턴

템플릿 메서드 패턴(Temlate Method Pattern)

=> 객체마다 대부분의 기능이 유사하되, 특정 부분만 구현이 다른경우 유용한 패턴. ( 대부분 기능이 유사할 때 발생하는 중복 문제 해결하며 다른 부분만 구현 )

Ex) 민수, 영희, 철수의 등교 패턴을 구현한 객체가 있다 했을 때

민수는 1. 잠에서 깨어나
2. 아침을 먹고
3. 씻고
4. 걸어서 등교
영희는 1. 잠에서 깨어나
2. 씻고
3. 자전거를 타고 등교
철수는 1. 잠에서 깨어나
2. 아침을 먹고
3. 씻고
4. 부모님 차를 타고 등교

라고 했을 때, 잠에서 깨어나, 씻는 부분은 공통으로 반복되고 아침을 먹는 유무, 등교방식만 변하는 것을 알 수 있다.

이때, 큰 틀은 유지하면서 다른 부분만 각 객체에서 구현하도록 할 때 템플릿 메서드 패턴이 유용하다.

템플릿 메서드 패턴 
         : 공통 수행하는 메서드는 상위 부모 추상클래스에서 구현하며 이 메서드를 **"템플릿 메서드"**라 한다.
         템플릿 메서드 내에서 수행할 때 다른 구현부는 하위 자식 객체에서 오버라이딩해 구현하도록 하며,
    이 메서드를 "hook **method/primitive method**"라 한다.

Ex)

Person Interface

public abstract class Person {
    public void goToSchool() { // => template method
        // 1. 잠에서 깬다.(공통)
        wakeUp();

        // 2. 아침먹는건 Optional
        if(isEatBreakfast()) { // hook method
            System.out.println("아침을 먹는다.");
        }

        // 3. 씻는다.(공통)
        washBody();

        // 4. 등교한다.(사람 객체마다 다른 부분) => hood method
        moveToSchool();
    }

    public void wakeUp() {
        System.out.println("잠에서 깨어난다.");
    }

    public boolean isEatBreakfast() { // 일종의 hook method
        return false; // 재구현하지 않으면 false
    }

    private void washBody() {
        System.out.println("씻는다.");
    }

    // hook method로 각 자식 객체(사람객체)마다 재구현해준다.
    public abstract void moveToSchool();
}

MinSoo 클래스

public class MinSoo extends Person{

    // 민수는 아침을 먹음으로 재구현
    @Override
    public boolean isEatBreakfast() {
        return true; // true 로 변경 아침먹는 부분 수행하도록...
    }

    @Override
    public void moveToSchool() {
        System.out.println("걸어서 등교한다.");
    }

}

YoungHee 클래스

public class YoungHee extends Person {

    // 아침은 안먹음으로 isEatBreakfast 구현하지 않으면 부모것 사용해 false

    @Override
    public void moveToSchool() {
        System.out.println("자전거를 타고 등교한다.");
    }

}

ChulSoo 클래스

public class ChulSoo extends Person {

    @Override
    public boolean isEatBreakfast() { // 아침 먹음
        return true;
    }

    @Override
    public void moveToSchool() {
        System.out.println("부모님 차를 타고 등교한다.");
    }

}

main

    MinSoo p1 = new MinSoo();
        p1.goToSchool();
        YoungHee p2 = new YoungHee();
        p2.goToSchool();
        ChulSoo p3 = new ChulSoo();
        p3.goToSchool();
        /*
         잠에서 깨어난다.
        아침을 먹는다.
        씻는다.
        걸어서 등교한다.
        잠에서 깨어난다.
        씻는다.
        자전거를 타고 등교한다.
        잠에서 깨어난다.
        아침을 먹는다.
        씻는다.
        부모님 차를 타고 등교한다. 
         * */

[ 자바8 - 스트림(Stream) ]



1. 스트림(Stream)이란?
  : 자바8부터 추가된 기능으로 "컬렉션", "배열"등의 저장 요소를 하나씩 순차적으로 참조하며
    함수형 인터페이스(람다식)을 적용해 반복적으로 처리할 수 있도록 해주는 기능이다.
    (for문,if문등으로 처리하는 것보다 한줄 두줄로 간단하게 처리가 가능하다.)

2. 스트림의 구조
  1) 스트림의 생성
  2) 중개 연산 : 스트림에서 특정 조건에 해당하는 결과의 스트림을 생성한다.
  3) 최종 연산 : 스트림의 항목들을 통해 특정 결과값을 도출한다.

  ex) Collection등의 객체집합.스트림생성().중개연산1().중개연산2().중개연산n().최종연산();

3. 사용법
  1. 스트림의 생성
    List studys = Arrays.asList("Java","Phython","Oracle","MySQL");
    studys.stream(); // 스트림 생성 방법 1
    studys.parallelStream(); // 여러 쓰레드를 통해 처리하는 병렬 스트림 생성(장단점이 있음)
    // 요소가 적거나 프로젝트 내에 사용되는 쓰레드 개수가 많을 경우 오히려 오버헤드가 생길 수 있음.

  2. 중개 연산
    1) Filter : 특정 조건에 맞는 요소들만 추려내 새로운 stream을 생성한다.
      List studys = Arrays.asList("Java","Phython","Oracle","MySQL");
      studys.stream().filter((x)->(x.contains("y"))); // (y가 포함된 Phython, MySQL를 갖는 스트림을 리턴한다.)

    2) Map
      : 각요소마다 특정 연산을 한 결과를 갖는 스트림을 생성한다. (1,2,3) 스트림에 각요소에 2씩 곱한 스트림 -> (2,4,6) 처럼 사용
      List studys = Arrays.asList("Java","Phython","Oracle","MySQL");
      studys.stream().map(x -> x.concat("END")); // 각 요소에 END문자열을 붙인 스트림을 리턴

    3) sorted : 정렬된 결과 stream을 리턴
List studys = Arrays.asList("Java","Phython","Oracle","MySQL");
studys.stream().sorted(); // 오름차순 정렬
studys.stream().sorted(Comparator.reverseOrder()); // 역순 정렬
studys.stream().sorted((a,b) -> { // Comparator 직접 구현을 통한 sorted메서드 사용
return Integer.compare(a.length(), b.length());
});

    4) distinct : stream내의 중복을 제거
       studys.stream().distinct();

    5) limit : stream요소에서 n개까지의 항목을 포함한 stream을 리턴함
       skip : stream요소에서 앞 n개를 제외하고 stream을 리턴함

       studys.stream().limit(3);
 studys.stream().skip(3);

    6) mapToLong, mapToInt, mapToDouble : 기존 stream요소를 Long, Int, Double형 항목을 갖는 스트림으로 리턴

       studys.stream().mapToLong((num)->(Long.parseLong(num)));

    7) 최종 연산
// 요소의 출력
// 1) forEach : 반복을 돌며 처리
studys.stream().forEach(System.out::println);
// 2) reduce : 각 항목을 순회하며 결과를 누적하여 반환
studys.stream().reduce((a,b) -> a + "," + b); // Java,Phython,Oracle,MySQL 리턴(누적한 문자열)
// 3) findFirst(), findAny() : stream의 첫번째 항목요소를 Optional 타입으로 반환한다.
// 두 최종연산 모두 비어있는 스트림에서 빈 Optional객체를 리턴함
// 병렬 스트림의 경우 findAny()메서드를 사용해야 정확한 연산 결과를 반환할 수 있다.
Optional result1 = studys.stream().findFirst();
// OptionalInt result2 = studys.stream().findFirst();
// result2.getAsInt();

// 4) 요소의 검사
// 1. anyMatch() : 해당 스트림의 일부 요소가 특정 조건을 만족할 때 true반환
// 2. allmatch() : 해당 스트림 모든 요소가 특정 조건을 만족할 때 true반환
// 3. noneMatch() : 해당 스트림 모든 요소가 특정 조건을 만족하지 않을 때 true반환
studys.stream().allMatch((x)->(x.contains("y"))); // 모든요소가 y를 포함할 때 true아니면 false

// 5) 요소의 통계
// count(), min(), max()
studys.stream().count();
studys.stream().min(Comparator.naturalOrder());

// 6) 요소의 연산
// sum(), avaerage()
studys.stream().mapToInt((x)->(Integer.parseInt(x))).sum();

// 7) stream을 List등의 컬렉션으로 리턴 : collect()
List str = studys.stream().collect(Collectors.toList());

// 각 요소의 길이가 짝수이면 true, List<짝수문자열항목>으로, 홀수이면 false, List<홀수문자열항목>으로 저장
Map<Boolean, List> temp = studys.stream().collect(Collectors.partitioningBy((x)->(x.length()%2==0)));
List eventStr = temp.get(true);  // 짝수집합
List oddStr = temp.get(false); // 홀수집합

 

-----------------------------------------------------------------------------------------------------------------------------------

List str = Arrays.asList("ao","b","co");
str.stream().filter(x->x.contains("o"));
str.stream().mapToInt(x->Integer.parseInt(x));
str.stream().limit(2);
str.stream().skip(1);
str.stream().sorted();
str.stream().sorted(Comparator.reverseOrder());
str.stream().findFirst().get();
str.stream().allMatch(x->x.contains("o"));
str.stream().collect(Collectors.toList());
Map<Boolean,List> temp = str.stream().collect(Collectors.partitioningBy(x->x.length()%2==0));
List evenStr = temp.get(true);
List oddStr = temp.get(false);
str.stream().forEach(System.out::printf);
str.stream().count();
str.stream().reduce((a,b)->(a+b));

'스터디 > Java' 카테고리의 다른 글

직렬화(serializable)와 serialVersionID란?  (0) 2018.10.22
자바 정규표현식  (0) 2018.10.19

[ 직렬화(serializable)와 serialVersionID란? ]



객체를 파일에 쓰거나, 파일에서 객체로 읽어오거나 혹은 네트워크를 통해 객체를 전송할 때 serializable 인터페이스를 구현(직렬화)하는 것을 본적 있을 것이다.


그림 직렬화란 무엇일까?

쉽게 말해


직렬화??

 - 객체를 전송하는 것은 파일로도 네트워크를 통해 서버로, 프로그램과 프로그램 사이에 많을 것이고

   또한, 서로 다른 언어를 사용하는 이들이 데이터를 주고 받으려면 주고받는 쪽이 모두 이해할 수 있는 언어가 필요할 것이다.

예를들자면

A -> B로 보낼 때 A는 프랑스 사람이고 B는 한국 사람인데 둘 다 서로의 언어는 모르지만 둘 다 영어는 할 줄 안다치면

둘이 데이터를 주고받을 때 객체를 영어로 변환(직렬화)하는 것이다. 그리고 주고 받으면 자신들의 언어로 역직렬화 하는 것!


그럼 비유를 마저 들어

serialVersionID는 무엇일까?

- 서로 데이터를 주고 받는 과정에서 여러군대와 주고받다보면 어떤 클래스인지 정확히 구분이 어려운 경우가 있을 것이다.

  즉, 주고받은 클래스 객체를 고유하게 구분하는 코드와 같은 것!


따라서, 주로 JAVA DTO 클래스에 implements Serializable했을 때 

final static Long serialVersionID = ~~ 라고 지정을 하지 않으면 노란색 삼각형으로 warning이 발생하는 것을 볼 수 있다.

하지만, 구현하지 않을 경우는 자바 컴파일러가 자동으로 default로 생성해주게 된다.

하지만, 경고는 보기 싫으니까 클래스 명에서 ctrl + 1눌러 serialVersionID를 자동으로 생성해주도록 하자.

(간혹 자동생성이 안뜨는 경우... 자동생성 플러그인을 이용하자.)


 

'스터디 > Java' 카테고리의 다른 글

스트림(Stream) - Java8  (0) 2019.07.11
자바 정규표현식  (0) 2018.10.19

[ 자바 정규 표현식(RegExpress) ]



자바 정규표현식 사용법)

boolean result = Pattern.matches("정규표현식", 검증데이터);

"정규표현식"에는 "^[0-9a-zA-z]*$" 과 같은 정규표현식이 들어가고, 검증데이터는 해당 정규표현식이 맞는지 확인할 데이터가 들어간다.

결과는 true/false로 return 된다.



- 다음은 정규표현식 문법)

표현식

 설명 

 ^

 문자열의 시작

 문자열의 종료

 .

 임의의 한 문자 (문자의 종류 가리지 않음)

 단, \ 는 넣을 수 없음

 *

 앞 문자가 없을 수도 무한정 많을 수도 있음

 앞 문자가 하나 이상

 앞 문자가 없거나 하나있음

 []

 문자의 집합이나 범위를 나타내며 두 문자 사이는 - 기호로 범위를 나타낸다. []내에서 ^가 선행하여 존재하면 not 을 나타낸다.

 {}

 횟수 또는 범위를 나타낸다. ex) {2,3} : 2글자에서3글자 {2}2글자

 ()

 소괄호 안의 문자를 하나의 문자로 인식 

 |

 패턴 안에서 or 연산을 수행할 때 사용

 \s

 공백 문자

 \S

 공백 문자가 아닌 나머지 문자

 \w

 알파벳이나 숫자

\W 

 알파벳이나 숫자를 제외한 문자

\d 

 숫자 [0-9]와 동일

\D 

 숫자를 제외한 모든 문자

 정규표현식 역슬래시(\)는 확장 문자
 역슬래시 다음에 일반 문자가 오면 특수문자로 취급하고 역슬래시 다음에 특수문자가 오면 그 문자 자체를 의미

(?i) 

 앞 부분에 (?i) 라는 옵션을 넣어주면 대소문자를 구분하지 않음


위의 문법 정보를 가지고 몇가지 연습을 해보자.

-> 먼저 데이터가 문자열로 넘어온다면 문자열 시작 : ^, 문자열 끝 : $ 로 감싸주면 될 것이고, 문자열이 아니면 안쓰면 된다.


EX)

1. 숫자데이터만 

->  ^[0-9]*$ 또는 ^\d*$

// "234" 이런식으로 문자열로 숫자가 감싸져 넘어오는 경우를 체크할 수 있다. 234 숫자 타입으로 넘길 경우 [0-9]*로만 해도 됨.

2. 영문자만

-> ^[a-zA-z]*$

// [] 안의 패턴은 and일 경우 ,로 구분할 필요 없이 쭉 이어쓸 수 있다.

3. 한글만

-> ^[가-힣]*$

4. 영어&숫자만

-> ^[a-zA-Z0-9]*$

5. E-MAIL(이메일) : ex) dudfhd705@gmail.com

-> ^[a-zA-Z0-9]+\@[a-zA-Z]+\.[a-zA-Z]+$

// 사이에 *가 아니라 +가 와야하는 이유는 해당 부분은 한글자 이상 반드시 있어야함으로 *는 없거나 1글자 이상이기 때문에 없을 수도 있어서

6. 핸드폰 : ex) 010-3456-2361, 010 - 3456 - 2361, 010 3456 2361, 01034562361

-> ^01(0|1|[6-9])$\s?\-?\s?(\d{3,4})\s?\-?\s?\d{4}$

// \s? : 공백이 올수도 있고 안올 수도 있고 \-? : - 특수문자가 올수도 있고 안올수도 있고

7. 주민등록번호

900317-1033334라고 가정

-> \d{6}\-[1-4]\d{6}

8. IP 주소

196.168.1.213 라고 가정

-> \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}



그럼 실제로 자바에서 Email 검증을 위한 코드 Ex)

                String regEx = "^[a-zA-Z0-9]+\\@[a-zA-Z]+\\.[a-zA-Z]+$";

boolean regCheck = false;

regCheck = Pattern.matches(regEx, "dudfhd705@gmail.com");

if(regCheck) { // true

// 이메일 형식이 맞는 경우

}else { // false

// 이메일 형식이 아닌 경우

}


'스터디 > Java' 카테고리의 다른 글

스트림(Stream) - Java8  (0) 2019.07.11
직렬화(serializable)와 serialVersionID란?  (0) 2018.10.22

[ 자바스크립트 중고급 내용 별도 정리(함수,호이스팅,prototype,실행컨텍스트 등. ]


- 해당 내용은 class 상속을 제외한 자바스크립트 중,고급 내용을 정리한 것입니다. 



'스터디 > 자바스크립트중고급' 카테고리의 다른 글

1. 함수(Function) 및 객체(Object)  (0) 2018.09.27

[  함수(Function) ]

  -종류)

    1. 선언적 함수 : 함수에 이름이 있는 함수로 호이스팅의 대상이 된다.(호이스팅은 별도로 정리)

      function circle(radius){

        ~~

        return something;

      }

    2. 익명 함수 : 함수에 이름이 없어 주로 변수에 대입해서 사용하는 함수(호이스팅의 대상이 되지 않음)

                    -> 따라서 순차적으로 수행되기 때문에 요즘은 익명함수로 선언하는 것이 추천됨.

      var anonymousFunc = function(raidus){

        ~~

        return something;

      }

    3. 람다 함수 : 일회성으로 사용하기 위한 목적으로 사용되는 함수

      (function(radius){

        ~~

        return something;

      })(25)


  - 매개변수가 정해지지 않은 함수 처리 방식)

    -> 자바스크립트는 arguments 라는 배열을 제공함

    -> arguments[0], arguments[1], ... 하면 각 순서대로 넘긴 매개변수 값을 가져올 수 있다.

    -> arguments.length : 실재 넘긴 매개변수 개수

    -> arguments.callee 는 함수 정의부를 의미한다.

    -> 따라서, arguments.callee.length 하면 선언된 매개변수 개수

    testFunc(10,20,30,40); // 선언적 함수는 호이스팅되기 때문에 수행 가능.

    function testFunc(a, b){

      if(arguments.callee.length != arguments.callee){

        alert("선언된 함수의 매개변수 개수와 실재 호출시 넘긴 매개변수 개수가 다릅니다.");

        alert("선언 매개변수 개수 : " + arguments.callee.length);

        alert("실재 호출시 넘긴 매개변수 개수 : " + arguments.length);

        // arguments[0] // 10

        // arguments[1] // 20

        // arguments[2] // 30

      }

    }


  - 인코딩/디코딩 내장함수)

    -> 한글 등을 넘길때 값에 문제가 생길 수 있어 인코딩에 사용되는 내장함수이다.

    escape(str) <-> unescape(str)

    encodeURIComponent(str) <-> decodeURIComponent(str)


  - 숫자 판별 내장함수)

    isNaN() : 숫자면 true 아니면 false를 return함.

  - 정수 변환 내장함수)

    parseInt()

  - 실수 변환 내장함수)

    parseFloat()

-------------------------------------------------------------------------------------------


[  객체(Object)  ]

객체 선언 방식 종류)

  1. new Object()를 통한 방식 -> 거의 사용 안함...

  2. 객체 리터럴 방식

    var circle = {

      변수 : "값",

      메서드1 : function(){

        ~~

        return something;

      },

      메서드2 : function(){}

      ...

    }


    -new 를 통해 객체를 여러개 생성할 수 없음으로 3번 생성자를 이용한 방식이 제일 자주 사용된다.

    -객체 선언 이후에 객체에 메서드, 변수를 추가하는 방법)

      circle.새변수명 = 값;

      circle.새메서드명 = function(){

         ~~

      }

      ->*** 이런 방식 말고도 변수나 문자열을 통해서도 동적으로 변수, 메서드를 추가할 수 있다.

      circle["새변수명"] = 값;

      circle["새메서드명"] = function(){};

      var newFunction = "print";

      circle[newFunction] = function(){} 처럼 동적으로 생성할 수 있다.

  3. 생성자 함수를 이용한 방식

    1) 선언적 함수를 이용하는 방식

      function constructorObject(a, b){

        this.변수명 = 값;

        this.메서드명 = function(){}

        ...

      }

    2) 익명함수를 이용하는 방식

      var constructorObject = function(a,b){

        this.변수명 = 값;

        this.메서드명 = function(){}

      }


    new constructorObject(10,20);

    처럼 new 연산자를 통해 인스턴스를 생성해서 사용하며, 이때 this는 자기 자신의 객체를 의미하게 된다.

    -> 자바스크립트에서 각 사용처 별로 this가 굉장히 다른데 추후에 정리...

    -> 일단, 생성자 함수를 통해 생성된 객체에서의 this는 자기 자신의 객체를 의미

    -> 함수 내에서 this는 window 객체를 의미


    역시 익명함수에서도 new를 통해 생성한 객체에 추가로 변수, 메서드를 추가하고 싶은 경우

    var newObject = new constructorObject(10,20);

    newObject.newVal = "새로운 값 추가";

    newObject["newMethod"] = function(){} 처럼 추가할 수 있다.


    *** [ 객체에서의 prototype ]

    -> prototype 사용 이유)

        생성자 함수를 통해 선언된 것을 new 키워드를 통해 객체 생성시 모든 인스턴스들은

        생성자 함수 내에 선언된 메서드 공간을 중복적으로 할당받게 된다.

        하지만 이러한 중복된 메서드를 공간을 객체마다 각기 할당받는 것은 비효율 적임으로

        공유될 수 있는 prototype을 통해 한군대에서만 정의될 수 있도록 해주는 방식을 사용한다.

    클래스명.prototype.변수명 = "값"; // 이 값은 해당 클래스명으로 생성되는 모든 객체에서 공유된다.

    클래스명.prototype.메서드명 = function(){}

    // 한번에 여러개를 뭉뚱그려 정의하고 싶다면...

    클래스명.prototype = {

      // 해당 클래스의 prototype을 통째로 재정의할 수도 있다.

      변수명 : "값",

      메서드1 : function(){},

      메서드2 : function(){},

      ...

    }


  - Date 내장 객체 자주사용되는 메서드)

    var date = new Date();

    date.getFullYear();

    date.getMonth() + 1; // 시작값이 0부터여서 1을 더해야함

    date.getDate(); // 일

    date.getHours();

    date.getMinutes();

    date.getSeconds();


- 콜백(Callback) 함수란? )

: 별거없다... 함수 호출시 매개변수에 함수를 넘겨서 해당 함수 내에서 특정 시점에 넘긴 함수를 수행하기 위한 목적으로 사용된다.

-> ajax등에서 success시 로딩 이미지를 없앤다거나 success가 완료된 시점에 특정 작업 등을 하려할 때 많이 사용된다.

function print(sum){

document.write(sum);

}

function testMethod(callbackF, a, b){

var sum = 0;

sum = a + b;

callbackF(sum); // 전달받은 callbackF(print메서드)를 수행한다.

}

testMethod(print, 10, 20);

[ CSS animation ]

: transition을 통해 몇초동안 변하게 하는 것 외에도 지속적으로 변화가 계속 반복해서 발생되도록 할 때 animation을 사용한다.


<!DOCTYPE html>

<html lang="en" dir="ltr">

  <head>

    <meta charset="utf-8">

    <title>애니메이션 스터디</title>

    <style media="screen">

      .box{

        width:200px;

        height:200px;

        background-color:yellow;

        color:white;

      }

      .box:hover{

        background-color:red;

        color:blue;

        animation: 3s multiStepAnimation infinite; /*생성한 keyframes를 애니메이션으로 등록한다.*/

      }


      @keyframes animationName {

        from{ /*상태가 2개일 때는 from, to로만 구성해도 된다.*/

          transform:none;

          /* transform:rotate(0deg);

          transform:scale(1); */

        }

        to{

          transform:scale(2);

          transform:rotate(360deg);

        }

      }


      @keyframes multiStepAnimation{

        0%{

          transform:rotate(0deg);

        }

        25%{

          transform:rotate(90deg);

        }

        50%{

          transform:rotate(180deg);

        }

        75%{

          transform:rotate(250deg);

        }

        97%{

          transform:rotate(360deg);

        }

        100%{

          transform:rotate(0deg);

        }

      }

    </style>

  </head>

  <body>

    <div class="box">애니메이션</div>

  </body>

</html>



[ 3. 자식 요소들의 배치를 편리하게 해줄 flex ]

: float를 이용해 요소들을 배치할 경우 box-sizing:border-box 등 margin, padding 등에 따른 배치를 구성하기가 기존에는 까다로웠지만 최근에는

 flex를 이용해 편리하게 배치할 수 있다.


<!DOCTYPE html>

<html lang="en" dir="ltr">

  <head>

    <meta charset="utf-8">

    <title></title>

    <style media="screen">

      /*

      flex를 사용함으로써 자식요소에 일일이 CSS 스타일을 적용할 필요가 없다.

      -> 부모 요소에만 적용하면 된다.!(엄청나군...)

      */

      .father{ /* 자식요소들의 배치는 부모에서만 지정하면 됨... */

        display : flex; /*flex를 이용하겠다.*/

        justify-content: flex-start;

        /*

        justify-content 속성 : 요소들의 가로 정렬을 담당한다.(만약 flex-direction:column으로 된 경우 세로 정렬을 담당하게 됨.)

        justify-content: center // 중앙 정렬

                       : flex-start // 왼쪽(만약 flex-direction:row-reverse일 경우 오른쪽 정렬)

                       : flex-end // 오른쪽 정렬(만약 flex-direction:row-reverse일 경우 왼쪽 정렬)

                       : space-between // 자식 요소들 사이 간격이 일정하게 배치 됨.

                       : space-around // 자식 요소 주변 공간이 일정하게 배치 됨.

        */

        align-items: center;

        /*

        align-items 속성 : 요소들의 세로 정렬을 담당한다.(만약, flex-direction:column일 경우 가로 정렬을 담당하게 됨)

        align-items : center // 세로 중앙 정렬

        align-items : baseline // 세로 시작 지점부터 배치

        align-items : stretch // 쭉 당겨서 첨부터 끝까지 닿도록 배치

        align-items : flex-start // top부터 정렬 (만약 flex-direction:column-reverse일 경우 아래부터 정렬)

        align-items : flex-end // bottom부터 정렬(colum-reverse일경우 위부터 정렬)

        */

        flex-direction:row;

        /*

        flex-direction 속성 : flex 자식들의 배치 방향을 결정지음

        flex-direction : row // 왼쪽부터 오른쪽으로 수평 배치 1,2,3,4,...

        flex-direction : row-reverse // 수평배치하되 거꾸로 4,3,2,1 로 배치

        flex-direction : column // 수직으로 배치

        flex-direction : column-reverse // 수직으로 배치하되 역순으로 배치

        */

        flex-wrap: wrap;

        /*

        flex-wrap 속성 : 화면을 축소할 경우 자식들의 배치를 어떻게할 것인가를 결정 지음

        -> 따로 설정하지 않으면 default가 flex-wrap : nowrap임 따라서, 배치된 그 상태로 크기를 줄여가며

        배치는 바끼지 않도록 줄어들게 됨

        flex-wrap : wrap; // 으로 지정시, 웹브라우저를 축소해 배치가 깨지게 되면 자동으로 아래로 내려가도록 함

        */


      }

      .box{ /* 자식 요소들은 그냥 크기 정도만 설정하면 됨... */

        width:200px;height:200px;background-color:yellow;margin-top:5px;margin-right:5px;

      }

    </style>

  </head>

  <body>

    <div class="father">

      <div class="box"></div>

      <div class="box"></div>

      <div class="box"></div>

      <div class="box"></div>

      <div class="box"></div>

      <div class="box"></div>

      <div class="box"></div>

      <div class="box"></div>

    </div>

  </body>

</html>



-> 추가적으로 flex를 연습해볼 수 있는 사이트

http://flexboxfroggy.com/#ko

[ CSS3의 transform을 통한 2D, 3D 제어 ] 

-> 단, IE8아래부턴 적용되지 않는다.


1.  translae : 현재 위치한 기준점을 기준으로 요소를 x, y, z 축으로 이동한다. -> position:relative를 이용해 대채할 수 있다.

   transform : translate(x축으로 이동할 거리, y축으로 이동할 거리)

   transform : translateY(y축으로 이동할 거리)

   transform : translateX(x축으로 이동할 거리)

   transform : translateZ(z축 즉 앞뒤로 이동할 거리) // Z축은 3D이기 때문에 perspective : 원근감값 을 지정해 주어야 한다.


2. skew : x, y 축으로 요소를 비튼다

 transform : skew(x축으로 비틀정도deg, y축으로 비틀정도 deg)

 transform : skewX(x축으로 비틀정도deg)

 transform : skewY(y축으로 비틀정도 deg)


3. scale : x, y 축으로 대상을 확대한다.

 transform : scale(1.5) 

 transform : scaleX(2)

 transform : scaleY(0.5)


4. roate : x, y 축을 기준으로 대상을 회전한다.

 transform : roate(x축 회전 deg, y축 회전 deg)

 transform : roateX(x축 회전 정도 deg)

 transform : roateY(y축 회전 정도 deg)


5. transition : all 0.5s 

-> transform같은 경우는 hover되었을 때나 그런 경우 지정되는 경우가 많은데 이때, hover되기 전과 후에 css 변화에 대해 몇초에 걸쳐

    변화가 이루어 질 것인가에 대한 부분으로 이것을 지정하지 않으면 요소가 변형되는 것이 티가 나지 않아 효과를 확인하기 어렵다.

-> 이때, transition : all 0.5s(몇초에 걸쳐 변할지) 를 지정할 때는 원본에 지정해야 한다.

ex) 특정 문구가 hover시 밖에서 안으로 들어오는 예제일 경우

.testClass{position:relative;}

.testClass p{position:absolute; left:-100%; top:0%; transition:all 0.5s;} // -100%를 줘 밖으로 틔어 나가있게 했다.

.testClass p:hover{left:0%;} // hover시 텍스트가 안으로 들어오게 left:0%; 로 바까준다.

-> 이때 변경이 이러나기 전인 원본에 transition:all 0.5s 를 지정해 주어야 한다.


6. transform-origin : transform이 되는 기준점을 변경하는 속성 

transform-origin : center center // 요소의 중심을 기준점으로

transform-origin : left top // 왼쪽 위를 기준점으로 변경

transform-origin : 50% 50% // 요소 중심을 기준점으로

transform-origin : 50px 20px


7. perspective : 원근감 정도( 값이 낮을 수록 왜곡 정도가 심해진다.)

-> 3D 적인 효과를 낼 때에는 적용해야 한다.

[    CSS관련 Tip    ]

: 개인 백업용 반응형 제작을 위해 기억하고 있어야할 CSS Tip들을 기록한다.


1)  < block 요소 / inline 요소 / inline-block 요소 >

- block 요소 특징)

1) 자동 줄개행이 된다. (float나 flex를 이용하지 않는한 자동 개행된다.)

2) width와 height를 지정하지 않으면 부모의 width와 height를 100% 채운다.

3) 자식으로 inline요소와 block요소를 모두 가질 수 있다.

- inline 요소의 특징)

1) 줄개행이 되지 않는다. 옆으로 쭉 붙어 올 수 있다.

2) width와 height를 지정할 수 없다. inline요소의 크기는 content의 크기가 된다.

3) 자식요소로 inline요소는 포함할 수 있지만 block요소는 포함할 수 없다.

- inline block 요소의 특징)

1) block요소와 inline요소의 특징을 모두 갖는다.

-> block요소처럼 width와 height를 지정할 수 있다.

-> 그러면서도 inline요소처럼 자동개행되지 않고 옆으로 붙어 올 수 있으며

-> inline요소의 특징도 갖기 때문에 text-align을 통한 inline요소 정렬도 할 수 있다.


각 요소 전환 방법)

- display:block // block 요소로...

- display:inline // inline 요소로...

- display:inline-block // inline block 요소로...

2. < 요소들을 정렬하는 다양한 방법 >

- inline 요소내에서 정렬하는 방법)

- 수평정렬 방법) text-align: center | right | left | justify(양쪽정렬)

- 수직정렬 방법) vertical-align: middle | right | left ..

- block 요소를 정렬하는 방법)

- 수평정렬 방법) margin : 0px(위,아래여백) auto

- 수직정렬 방법) line-height: (block요소의 height값) px

- position:absolute로 배치되어 있는 block요소를 정렬하는 방법)

- 수평, 수직 정렬 방법)

top:50%;

left:50%;

margin-left : -(block요소의 넓이/2)px;

margin-top: -(block요소의 높이/2)px;


3. < 배경 이미지 관련 속성 정리 >

- background-color : rgba(255,255,255,0.5) // 맨 마지막 값은 alpha(투명도...) #555 처럼 지정할 수도 있음... 투명도 지정하려면 rgba로해야함..

- background-image : url(이미지 경로); // 이미지 배경 지정

- background-repeat : no-repeat(default) | repeat-x(x축으로반복) | repeat-y(y축으로반복) | repeat(x,y축으로 반복)

- background-position : center(가로) center(세로)

  : 10px 10px

  : 50% 50%

- background-size : cover(가득채우기) | contain(가득채우되 이미지 비율을 유지하며 가득채움)

- background-attachment : fixed | scroll(default) // fixed로 지정시 스크롤시에도 이미지가 해당 위치에 그대로 유지됨(이미지 스킨만 바끼는 듯한 효과줄 때 유용)


-> 축약형) background: #666 url(...) no-repeat center center


4. < 그림자 효과 >

- box-shadow : +-(가로방향)px +-(세로방향)px (번짐정도)px #666(그림자색상) // block요소 그림자 효과

- text-shadw : 동일 // 텍스트 그림자 효과


5. < Full Screen 제작 하기 >

- 영역을 풀스크린으로 잡을 때 block요소의 width는 아무때나 %로 줄 수 있지만, height값은 자식요소가 없을 경우 %로 지정할 수가 없다.

-> 따라서, width, height 모두 풀스크린으로 100%를 주기 위해서는 

-> position:fixed로 지정해 주어야 한다.

-> 그렇게 가장 바깥 block요소를 fixed로 지정시 그곳에 붙는 모든 자식요소는 이후부터 width, height모두 %로 크기를 지정할 수 있다.

ex)

.outerArea{

position:fixed;

width:100%;

height:100%;

background : url(...) no-repeat center center;

background-size: cover;

}


6. < 컨텐츠 넘침 처리... >

CSS로 제어하다보면 컨텐츠가 영역을 넘어갈 경우 어떻게 처리해야 될지 결정지어야 할 경우가 있다.

이때, 1. 보이게 할지 2. 안보이게 할지 3. 스크롤이 생기게 할지 등을 지정할 수 있다.


- overflow: visible // 영역을 넘어도 무조건 보이게 한다. 

- overflow: hidden // 넘어가는 영역은 짤라서 안보이게 한다.

-> overflow:hidden의 경우는 이런 용도 말고도 float처리를 위해 사용되기도 한다. 다음 7번에서 설명...

- overflow: scroll // 넘어가지 않아도 처음부터 무조건 스크롤이 생겨있게 됨

- overflow: auto // 넘어가지 않으면 스크롤이 안생기고 넘어갈 경우만 스크롤이 생기게 됨.


7. < float 속성에 관하여... >

최근에는 flex를 이용해서 자식 컨테이너들을 배치할 수 있어 float의 사용을 최소화할 수 있지만 그래도 사용해야 하는 경우가 더러 존재한다.

float는 block요소를 가로로 이어 붙일 수 있도록 해주는 방법을 제공하는데

한번 적용한 float의 속성을 해제하기 위해서는 clear:both같은 방법으로 해제하곤 했다. 아니면 전후 처리자 :after 등을 이용하기도 하는데

이는 많은 문제가 발생할 수 있어 권장되지 않는다.


따라서!! 결론)

float가 지정된 자식들을 포함하는 부모에 overflow:hidden 속성을 주면 된다.!

* 참고)

-> 반응형 제작을 위해서는 자식들에 float 속성을 지정시 해당 부모 안에 있는 모든 자식요소에 float속성을 줘야

    미디어 쿼리로 제작시 틀어짐 없이 반응형을 제작할 수 있게됨을 알아야 한다.



8. < 우리가 간과하고 사용하기 쉬운 padding이란 녀석과 box-sizing:border-size >

block요소의 박스모델에 관해 이야기할 때 이 박스란 녀석들은 margin(외부여백), border(선두께), padding(내부여백), 컨텐츠 로 구성된다고 알고 있을 것이다.

따라서 우리가 block요소의 넓이, 높이를 생각할 때 그 넓이 = margin + border + content 가 된다.

이때, 우리가 해당 요소에 width, height를 지정한 상태에서 padding값을 지정하면 어떻게 될까?

-> 기존 width, height에 해당 padding값이 더해지게 된다.! 난 다 더해서 100px width를 의도햇는데 10px 내부 여백을 줌으로써 전체 크기가 120px이

    되어 버린다는 점이다.

-> 그럼 이럴땐 어떻게 해결해야할까? 다시 100px의 크기가 되려면?

1) padding으로 지정할만큼 width값을 빼주면 된다. -> 하지만... 귀찮다.(비추천)

2) box-sizing:border-box를 이용하자. 이것을 이용하면 width와 height로 지정한 크기 내에서 사이즈를 자동으로 조절해서 크기를 맞춰주게 된다.


9. < position에 관하여... >

: 최근에는 반응형으로 제작시 외부 layout에 영향을 주는 container들을 제외하고 그 각 container안에 들어가는 요소들은 position:absolute를 통해

  배치하는 것이 효율이 좋은데 이때 알아야 하는 position 속성에 관해 설명한다.

- position : fixed // scroll을 해도 화면 특정 위치에 계속 보이도록 배치할 때 사용한다. ex) aside영역에 뜨는 광고, 메뉴 nav 등...

top : ()px

left : ()px ...

- position : absolute // position:relative가 지정된 부모를 기준으로 절대좌표로 대상을 배치한다.(정말 많이 쓰인다...) 만약, position:relative가 지정된

    // 부모가 없을시 최상단 body까지 찾아가 body를 기준으로 배치하게 된다.

- position : relative // 너무 너무 너무 너무 너무 중요하다.

-> 2가지 부분에서 사용이 되는데

1) position:absolute의 기준점이 된다.

2) 레이아웃에 영향을 주지 않으면서 일부 움직이고 싶을 때 사용된다.

-> 만약 레이아웃에 영향을 주지 않으면서 살짝 다른 영역을 침범하게 구성하고 싶다면?

position:relative; top:-20px; 이런식으로 주면 주변 영역에 영향을 주지 않고 구성할 수 있다.

만약, CSS3의 transform을 사용해서도 할 수 있는데 -> transform:translateY(-20px) 처럼해서도 할 수 있다.(현재 기준점에서 Y축으로 이동)


10. < 박스들이 복잡하게 margin값을 갖는것처럼 구성될 때 >

이때 상자들 사이에 margin이 있는 그림처럼보이더라도 margin으로 상자들 사이를 띄워서 구성하면 안된다.

why? -> 반응형 제작시 죄다 틀어져 버릴테니까... 따라서 상자들의 padding값과 box-sizing:border-box를 이용해서 박스들의 배치를 구성지어야 한다.

(먼가 자세히 그림넣어 설명하기가 귀찮다... 나만을 위한 백업 용도니까... 대충써야지..)


11. < CSS 선택자에서 nth-of-child를 사용하는 대신 nth-of-type(n)을 사용하자! >


12. < semantic Tag로 사용할 수 있는 영역은 semantic Tag로 구성하자... >

- <header>헤더 영역</header>

- <nav>네비게이션 메뉴 영역</nav>

- <figure>멀티미디어 요소들이 오는 영역(중앙광고등...)</figure>

- <section>

<article>세부 영역</article>

<article>세부 영역2</article>

   </section>

- <footer><address>주소영역</address>푸터</footer>

- <aside>오른쪽 사이드 광고 등처럼 부수적인 영역</aside>


13. 제일 중요!! < 자식의 높이가 결정되면 부모의 높이값을 반드시 지워주자! >

-> 자식요소의 높이값이 지정된 상태에서 부모의 높이값을 해제하면 자식들이 차지하는 영역만큼 부모의 크기가 자동으로 지정된다.

-> 이처럼 자식의 높이가 결정되면 반드시 부모의 높이값을 지워주어야 반응형을 제작할 때 쉽게 반응형으로 제작이 가능하다.

(정말... 어떻게 강조를 해야할지 모를 정도로 중요하다...)


14. < 반응형 제작 Tip >

: 위의 자식높이가 결정되면 부모의 높이값을 지워주는 것, layout으로 배치된 컨테이너 내에서 position:absolute로 배치를 했다면

-> 실제 반응형으로 웹페이지를 태블릿, 모바일용으로 제작하는 것은 5분도 걸리지 않고 제작할 수 있다.


- 반응형 제작시 기억해야할 유의사항)

1. 가로 넓이는 고정 px이면 안되고 요소들의 넓이를 다 합쳤을 때 100%가 되어야 함(margin포함 넓이에 영향주는 모든 요소)

2. 높이와 폰트는 고정 px이어야 한다.

3. 세로 높이는 반응형 제작시 줄일 수 있으면 줄여주자.

- 태블릿 반응형 제작 방법)

1. 웹에서 적용한 CSS코드를 그대로 붙인다.

2. 바로 위의 반응형 제작시 유의사항을 지키면서 컨테이너들의 넓이값을 고쳐나간다.(100%되도록...)

3. 바꾸지 않는 부분들은 삭제한다.(소거법)

4. 필요시 css 코드를 추가해도 된다.

5. 줄일 수 있으면 높이는 줄여준다.


- 모바일 반응형 제작 방법)

1. 태블릿에 적용한 CSS코드를 그대로 모바일 미디어 쿼리에 붙여준다.

2. 유의사항에 맞게 고쳐주되!! 중요한 점은 절대 바꾸지 않는 부분들도 삭제하면 안된다.!!

-> 태블릿 코드는 삭제시 웹코드가 적용되서 괜찮았지만, 모바일은 공통 부분을 삭제시 태블릿 코드가 적용되는게 아니라 웹코드가 적용되니까

     만은 차이가 발생하게 된다.

-> 한 컨테이너가 한칸을 차지하기 위해 width:100%로 지정하여 기존 margin-right값들이 필요없게 되었더라도 이를 삭제하면 안되고

    margin-right:0%; 나 margin-right:0px처럼 고쳐주어야 한다.!(매우 매우 중요)


15. < CSS의 state >

- hover : 마우스를 요소 위로 가져갔을 때

- active : 요소를 클릭했을 때

- focus : input 상자나 textarea등을 클릭했을 때

- visited : 주로 a태그에서 한번 눌른적이 있는 경우

div:active{

 background-color:blue;

}

[    Eclipse환경에서 Run을 시켰을때 빌드를 통해 메모리를 확보하는 중간에 Eclipse가 응답없음으로 바뀌는 에러입니다. ]


'Unable to execute dex: GC overhead limit exceeded GC overhead limit exceeded'

해결방안)

이클립스에서 사용하는 메모리가 부족함으로

Eclipse폴더에 있는 eclipse.ini파일을 수정하면 됩니다.

-Xms40m
-Xmx384m

이와 같은 내용(메모리 사용 40/384)을

-Xms1024m
-Xmx1024m

혹은 그 이상으로 설정해줍니다.

[ 자바스크립트로 사용자 OS 버전 확인하는 코드(IE버전 X 운영체제 판별)]


자바스크립트로 IE8 과 같은 브라우저 버전 체크하는 코드는 많은데 막상 사용자의 운영체제가 무슨 버전인지까진 판별하는 코드가 잘 없었는데


다른분 티스토리에서 보고 유용한거같아 공유하려합니다.


조금더 덧대서 해당 윈도우 운영체제에 로그인한 사용자명의 폴더를 알아야할 경우


var net = new ActiveXObject ( "WScript.NetWork" );

var userName = net.UserName;

strFilePath = "C:\\Users\\" + userName + "\\AppData\\Local\\" + nanumTechnologiesPath; 코드를 사용하시면 로그인한 userName도 얻어올 수 있습니다.


이 아래는 자바스크립트로 운영체제 종류와 버전을 판별하는 코드입니다.


// JavaScript Document

// 만든이 : 다섯방울, THREE™ (http://the3.tistory.com)

// 주소 : http://the3.tistory.com/17

// Data : 2015. 01. 28

// Version : 0.2

// 참조 http://www.openspc2.org/userAgent/

// OS 버전 보기


var uanaVigatorOs = navigator.userAgent;

var AgentUserOs= uanaVigatorOs.replace(/ /g,'');

var Ostxt="";

var OSName="";

var OsVers="";


// This script sets OSName variable as follows:

// "Windows"    for all versions of Windows

// "MacOS"      for all versions of Macintosh OS

// "Linux"      for all versions of Linux

// "UNIX"       for all other UNIX flavors 

// "Unknown OS" indicates failure to detect the OS


new function() {

    var OsNo = navigator.userAgent.toLowerCase(); 


    jQuery.os = {

        Linux: /linux/.test(OsNo),

        Unix: /x11/.test(OsNo),

        Mac: /mac/.test(OsNo),

        Windows: /win/.test(OsNo)

    }

}


function OSInfoDev(){

if($.os.Windows) {

if(AgentUserOs.indexOf("WindowsCE") != -1) OSName="Windows CE";

else if(AgentUserOs.indexOf("Windows95") != -1) OSName="Windows 95";

else if(AgentUserOs.indexOf("Windows98") != -1) {

if (AgentUserOs.indexOf("Win9x4.90") != -1) OSName="Windows Millennium Edition (Windows Me)" 

else OSName="Windows 98"; 

}

else if(AgentUserOs.indexOf("WindowsNT4.0") != -1) OSName="Microsoft Windows NT 4.0";

else if(AgentUserOs.indexOf("WindowsNT5.0") != -1) OSName="Windows 2000";

else if(AgentUserOs.indexOf("WindowsNT5.01") != -1) OSName="Windows 2000, Service Pack 1 (SP1)";

else if(AgentUserOs.indexOf("WindowsNT5.1") != -1) OSName="Windows XP";

else if(AgentUserOs.indexOf("WindowsNT5.2") != -1) OSName="Windows 2003";

else if(AgentUserOs.indexOf("WindowsNT6.0") != -1) OSName="Windows Vista/Server 2008";

else if(AgentUserOs.indexOf("WindowsNT6.1") != -1) OSName="Windows 7";

else if(AgentUserOs.indexOf("WindowsNT6.2") != -1) OSName="Windows 8";

else if(AgentUserOs.indexOf("WindowsNT6.3") != -1) OSName="Windows 8.1";

else if(AgentUserOs.indexOf("WindowsNT6.4") != -1) OSName="Windows 10";

else if(AgentUserOs.indexOf("WindowsPhone8.0") != -1) OSName="Windows Phone 8.0";

else if(AgentUserOs.indexOf("WindowsPhoneOS7.5") != -1) OSName="Windows Phone OS 7.5";

else if(AgentUserOs.indexOf("Xbox") != -1) OSName="Xbox 360";

else if(AgentUserOs.indexOf("XboxOne") != -1) OSName="Xbox One";

else if(AgentUserOs.indexOf("Win16") != -1) OSName="Windows 3.x";

else if(AgentUserOs.indexOf("ARM") != -1) OSName="Windows RT";

else OSName="Windows (Unknown)";

if(AgentUserOs.indexOf("WOW64") != -1) OsVers=", WOW64";

else if(AgentUserOs.indexOf("Win64;x64;") != -1) OsVers=", Win64 on x64";

else if(AgentUserOs.indexOf("Win16") != -1) OsVers=" 16-bit";

else OsVers=" on x86";

} else if ($.os.Linux) {

if(AgentUserOs.indexOf("Android") != -1) { OSName = getAndroidDevName(); }

else if(AgentUserOs.indexOf("BlackBerry9000") != -1) OSName="BlackBerry9000";

else if(AgentUserOs.indexOf("BlackBerry9300") != -1) OSName="BlackBerry9300";

else if(AgentUserOs.indexOf("BlackBerry9700") != -1) OSName="BlackBerry9700";

else if(AgentUserOs.indexOf("BlackBerry9780") != -1) OSName="BlackBerry9780";

else if(AgentUserOs.indexOf("BlackBerry9900") != -1) OSName="BlackBerry9900";

else if(AgentUserOs.indexOf("BlackBerry;Opera Mini") != -1) OSName="Opera/9.80";

else if(AgentUserOs.indexOf("Symbian/3") != -1) OSName="Symbian OS3";

else if(AgentUserOs.indexOf("SymbianOS/6") != -1) OSName="Symbian OS6";

else if(AgentUserOs.indexOf("SymbianOS/9") != -1) OSName="Symbian OS9";

else if(AgentUserOs.indexOf("Ubuntu") != -1) OSName="Ubuntu";

else if(AgentUserOs.indexOf("PDA") != -1) OSName="PDA";

else if(AgentUserOs.indexOf("NintendoWii") != -1) OSName="Nintendo Wii";

else if(AgentUserOs.indexOf("PSP") != -1) OSName="PlayStation Portable";

else if(AgentUserOs.indexOf("PS2;") != -1) OSName="PlayStation 2";

else if(AgentUserOs.indexOf("PLAYSTATION3") != -1) OSName="PlayStation 3";

else OSName="Linux (Unknown)";

if(AgentUserOs.indexOf("x86_64") != -1) OsVers=", x86_64";

else if(AgentUserOs.indexOf("i686") != -1) OsVers=", i686";

else if(AgentUserOs.indexOf("i686 on x86_64") != -1) OsVers=", i686 running on x86_64";

else if(AgentUserOs.indexOf("armv7l") != -1) OsVers=" Nokia N900 Linux mobile, on the Fennec browser";

else if(AgentUserOs.indexOf("IA-32") != -1) OsVers=" 32-bit";

else OsVers="";

} else if ($.os.Unix) {

OSName="UNIX";

} else if ($.os.Mac) {

if(AgentUserOs.indexOf("iPhoneOS3") != -1) OSName="iPhone OS 3";

else if(AgentUserOs.indexOf("iPhoneOS4") != -1) OSName="iPhone OS 4";

else if(AgentUserOs.indexOf("iPhoneOS5") != -1) OSName="iPhone OS 5";

else if(AgentUserOs.indexOf("iPhoneOS6") != -1) OSName="iPhone OS 6";

else if(AgentUserOs.indexOf("iPhoneOS7") != -1) OSName="iPhone OS 7";

else if(AgentUserOs.indexOf("iPhoneOS8") != -1) OSName="iPhone OS 8";

else if(AgentUserOs.indexOf("iPad") != -1) OSName="iPad";

else if((AgentUserOs.indexOf("MacOSX10_1")||AgentUserOs.indexOf("MacOSX10.1")) != -1) OSName="Mac OS X Puma";

else if((AgentUserOs.indexOf("MacOSX10_2")||AgentUserOs.indexOf("MacOSX10.2")) != -1) OSName="Mac OS X Jaguar";

else if((AgentUserOs.indexOf("MacOSX10_3")||AgentUserOs.indexOf("MacOSX10.3")) != -1) OSName="Mac OS X Panther";

else if((AgentUserOs.indexOf("MacOSX10_4")||AgentUserOs.indexOf("MacOSX10.4")) != -1) OSName="Mac OS X Tiger";

else if((AgentUserOs.indexOf("MacOSX10_5")||AgentUserOs.indexOf("MacOSX10.5")) != -1) OSName="Mac OS X Leopard";

else if((AgentUserOs.indexOf("MacOSX10_6")||AgentUserOs.indexOf("MacOSX10.6")) != -1) OSName="Mac OS X Snow Leopard";

else if((AgentUserOs.indexOf("MacOSX10_7")||AgentUserOs.indexOf("MacOSX10.7")) != -1) OSName="Mac OS X Lion";

else if((AgentUserOs.indexOf("MacOSX10_8")||AgentUserOs.indexOf("MacOSX10.8")) != -1) OSName="Mac OS X Mountain Lion";

else if((AgentUserOs.indexOf("MacOSX10_9")||AgentUserOs.indexOf("MacOSX10.9")) != -1) OSName="Mac OS X Mavericks";

else if((AgentUserOs.indexOf("MacOSX10_10")||AgentUserOs.indexOf("MacOSX10.10")) != -1) OSName="Mac OS X Yosemite";

else OSName="MacOS (Unknown)";

if(AgentUserOs.indexOf("Intel") != -1) OsVers=" on Intel x86 or x86_64";

else if(AgentUserOs.indexOf("PPC") != -1) OsVers=" on PowerPC";

else OsVers="";

} else {

OSName="Unknown OS";

}

  var OSDev = OSName + OsVers;

  return OSDev;

}


// Android의 단말 이름을 반환

function getAndroidDevName() {

var uaAdata = navigator.userAgent;

var regex = /Android (.*);.*;\s*(.*)\sBuild/;

var match = regex.exec(uaAdata);

if(match) {

var ver = match[1];

var dev_name = match[2];

return "Android " + ver + " " + dev_name;

}

return "Android OS";

}


// OSInfoDev() 는 OS이름과 버전 출력하는 함수

// AgentUserOs 는 userAgent 출력

i n v i t a t i o n

티스토리 초대장

+ 남은 초대장 수 : 0

안녕하세요!

티스토리에 보금자리를 마련하시려는 여러분께 초대장을 배포해 드리려고 합니다. 많은 갯수는 아니지만 앞으로 생기는 대로 배포를 위한 글을 올릴 예정입니다.

티스토리 블로그는 초대에 의해서만 가입이 가능합니다. 원하시는 분은 댓글에 E-mail 주소를 남겨주시면 초대장을 보내드립니다. 

초대장 요청은 반드시 비밀 댓글로 작성해주세요. 공개댓글 작성시 드리지 않습니다.

요청하실 때는 이메일과 초대장이 필요한 이유(어떤 목적으로 개설할 것인지)를 간략하게 적어주시면 좋을 것 같습니다. 보고 7분께 나누어 드릴게요.

혹시 이번이 아니더라도 다음 달부터 꾸준히 나눔할 예정이니 자주 방문해주세요.

대신, 초대장 받아만 놓고 개설하지 않으실 분들은 신청을 지양해주세요

초대장을 보내드리고 바로 개설하시지 않으신 분들은 초대장을 회수할 수도 있으니 바로 개설해주세요!

Yes

이런 분들께 드립니다!

1. 이메일 주소가 정상적인 분
2. 블로그를 시작하려는 이유를 남겨주신 분!
3. 비밀댓글로 작성해 주시는 분
No
이런 분들께 드리지 않아요!
1. 이메일 주소가 의심되는 분!
2. 이메일 주소를 남기지 않으신 분
3. 이유도 없이 달라고 하시는 분!
티스토리 이래서 좋아요!
1. 이미지, 동영상, 오디오, 파일까지! 무한 용량과 강력한 멀티미디어를 올릴 수 있어요!
2. 스킨위자드로 스킨을 내맘대로~ 거기에 기능 확장 플러그인까지!
3. 내가 원하는대로 myID.com으로 블로그 주소를 만들 수 있어요!


반드시, 초대장 받고 확실히 개설해 운영하실 분들만 신청해 주세요. 받으시고 일정 시간이 지나도 개설하지 않으시는 분들은 초대장 다시 회수하도록 하겠습니다.

[Text Node 제어와 Document 및 스크린, 스크롤 위치 제어]



1. [ Text Node의 제어 ]

- 텍스트 데이터 확인 : ELEMENT.data or ELEMENT.nodeValue
- 텍스트 데이터 조작
-- element.appendData("추가할 텍스트 문자열") : 맨 마지막 부분에 문자열 추가
-- element.deleteData( startIndex, length ) : 해당 index부터 몇글자 지울지
-- element.insertData( startIndex, "추가할 문자열" ) : 해당 index 위치에 삽입
-- element.replaceData( startIndex, length, "바꿀 문자열" ) : 해당 index부터 length글자만큼을 새로운 바꿀 문자열로 교체
-- element.substringData( startIndex, length ) : 해당 인덱스부터 length개만큼 가져옴

1
2
3
4
5
6
7
8
9
10
<ul>
<li id="target">html</li>
<li>css</li>
<li>JavaScript</li>
</ul>
<script>
var t = document.getElementById('target').firstChild;
console.log(t.nodeValue);
console.log(t.data);
</script>

<!DOCTYPE html>
<html>
<head>
<style>
#target{
font-size:77px;
font-family: georgia;
border-bottom:1px solid black;
padding-bottom:10px;
}
p{
margin:5px;
}
</style>
</head>
<body>
<p id="target">Cording everybody!</p>
<p> data : <input type="text" id="datasource" value="JavaScript" /></p>
<p> start :<input type="text" id="start" value="5" /></p>
<p> end : <input type="text" id="end" value="5" /></p>
<p><input type="button" value="appendData(data)" onclick="callAppendData()" />
<input type="button" value="deleteData(start,end)" onclick="callDeleteData()" />
<input type="button" value="insertData(start,data)" onclick="callInsertData()" />
<input type="button" value="replaceData(start,end,data)" onclick="callReplaceData()" />
<input type="button" value="substringData(start,end)" onclick="callSubstringData()" /></p>
<script>
var target = document.getElementById('target').firstChild;
var data = document.getElementById('datasource');
var start = document.getElementById('start');
var end = document.getElementById('end');
function callAppendData(){
target.appendData(data.value);
}
function callDeleteData(){
target.deleteData(start.value, end.value);
}
function callInsertData(){
target.insertData(start.value, data.value);
}
function callReplaceData(){
target.replaceData(start.value, end.value, data.value);
}
function callSubstringData(){
alert(target.substringData(start.value, end.value));
}
</script>
</body>
</html>





2. [ Document(문서)의 Element 위치 알아내기 ]

var positionInfo = Element.getBoundingClientRect();
-> getBoundingClientRect()는 width, height, top, left, right, bottom 프러퍼티를 갖는 객체이다.
- width : element의 가로 길이
- height : 세로길이
- top : viewport(아래에서 설명) 맨 위에서부터 element까지 y축 길이
- left : viewport 맨 좌측에서부터 element까지 x축 길이
- right : viewport 맨 좌측에서 element 맨 오른쪽까지 길이
- bottom : viewport 맨 위에서 element 맨 아래까지 길이

1) -> 하지만, getBoundingClientRect()는 구버전 브라우저에서는 제공하지 않는 경우도 있다. 따라서,
element의 좌표를 구할 때 element.offsetLeft, element.offsetTop을 사용하면 된다. == element.getBoundingClientRect().left|top

- 테두리를 제외한 width와 height 값 구하기
- element.clientWidth
- element.clientHeight


2) -> 때떄로, 어떤 Element들은 해당 Element를 감싸고 있는 부모 Element가 position static인 경우 부모로 부터의 위치를 기준으로
자신의 offsetLeft, offsetTop값을 반환하게 된다.
따라서, 문서에서의 정확한 위치를 구하기 위해서는 다음과 같은 코드를 사용할 수 있다.
( 해당 Element를 포함하고 있는 부모 Element를 추적하면서 위치값을 더해감)




3. [ viewport란? 스크롤을 고려한 문서내의 Element의 위치 ]

- 1) Viewport(뷰포트)

: 문서 전체의 크기가 있을 때 예를들어 우리가 브라우저를 줄이게 될 경우 전체 Document가 다 보이지 않고
  창 크기에 맞춰 일부분의 문서만 보일 것이다.
  이때, 보이는 영역이 ViewPort이다.

--> 우리가 아까 위에서 element.offsetLeft, element.offsetTop 등이 viewport에서부터의 거리를 나타내고 있는 것이다.

따라서, 스크롤이 개입되게 되면, 해당 위치는 문서 전체를 기준으로했을 때 정확하지 않게 될 수 있다.

   2) 스크롤의 위치는 어떻게 구할까?

-> window.pageYoffset
: y축 스크롤을 움직인 거리(문서가 가려진 y축 길이)
-> window.pageXoffset
: x축 스크롤을 움직인 거리(문서가 가려진 x축 길이)

    3) viewport에서부터의 거리와 스크롤과의 관계

-> 스크롤을 내릴 수록 pageYoffset값은 커질 것이다. 그러면 offsetTop값을 기준으로 보면 어떻게 될까?

    스크롤이 내려간만큼 그 길이는 짧아지게 된다.

예를들어, A라는 Element의 offsetTop값이 맨 처음 200이고 pageYoffset이 0이었을 때
스크롤을 40px만큼 내려 pageYoffset이 40이 되면

offsetTop값은 200 - 40 인 160이 되게 된다.

전체문서에서 보이는 viewport를 기준으로 하기 때문이다.

따라서, 전체 문서에서의 위치를 구하려면 어떻게 해야할까?

--> element.offsetTop(뷰포트좌표) + window.pageYoffset(스크롤된 정도)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<style>
body{
padding:0;
margin:0;
}
div{
border:50px solid #1065e6;
padding:50px;
margin:50px;
}
#target{
width:100px;
height:2000px;
}
</style>
<div>
<div id="target">
Coding
</div>
</div>
<script>
var t = document.getElementById('target');
setInterval(function(){
console.log('getBoundingClientRect : ', t.getBoundingClientRect().top, 'pageYOffset:', window.pageYOffset);
}, 1000)
</script>




4. [ 스크롤 제어 ]

: 스크롤을 특정 위치로 이동시키는 방법

-> window.scrollTop(x, y); // x축, y축

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<style>
body{
padding:0;
margin:0;
}
div{
border:50px solid #1065e6;
padding:50px;
margin:50px;
}
#target{
width:100px;
height:2000px;
}
</style>
<input type="button" id="scrollBtn" value="scroll(0, 1000)" />
<script>
document.getElementById('scrollBtn').addEventListener('click', function(){
window.scrollTo(0, 1000);
})
</script>
<div>
<div id="target">
Coding
</div>
</div>



5. [ Viewport의 크기와 사용자 모니터 크기 구하기 ]

- Viewport 크기

: window.innerWidth
  window.innerHeight

- 사용자 모니터 크기

: screen.width
: screen.height

[   Node 객체에 대하여(NODE 객체와 NODE객체 컨트롤하기)   ]



-> 자바스크립트의 모든 DOM ELEMENT들은 최상위로 NODE를 상속하고 있기 때문에 NODE가 가지고 있는 속성을 사용할 수 있다.


Node 객체는 Node 간의 관계 정보를 담고 있는 일련의 API를 가지고 있다. 다음은 관계와 관련된 프로퍼티들이다.


[ 1. Node의 자식 및 형제 Node에 접근하는 프러퍼티 ]

  • Node.childNodes
        자식노드들을 유사배열에 담아서 리턴한다.
  • Node.firstChild
        첫번째 자식노드
  • Node.lastChild
        마지막 자식노드
  • Node.nextSibling
        다음 형제 노드
  • Node.previousSibling
        이전 형제 노드

-> 이렇게 노드에 접근할 때 중요한 점은 줄바꿈 텍스트 또한 NODE를 상속하기 때문에 자식으로 가져올 때 주의해야 한다는 점이다. 아래의 크롬브라우저의 테스트 창을 보면서
    이해하기 바란다

   EX) Crome Browser 개발자도구(F12) console창에서 테스트하는 예
<body id="start">
<ul>
<li><a href="./532">html</a></li>
<li><a href="./533">css</a></li>
<li><a href="./534">JavaScript</a>
<ul>
<li><a href="./535">JavaScript Core</a></li>
<li><a href="./536">DOM</a></li>
<li><a href="./537">BOM</a></li>
</ul>
</li>
</ul>
</body>





[    2. Node의 타입을 알 수 있는 nodeType속성과 Node 이름을 알 수 이는 nodeName 속성    ]


노드 작업을 하게 되면 현재 선택된 노드가 어떤 타입인지를 판단해야 하는 경우가 있다. 이런 경우에 사용할 수 있는 API가 nodeType, nodeName이다. 


  • Node.nodeType
        node의 타입을 의미한다.  -> 타입값은 숫자로 나오게 된다. 주로 1(element), 3(TextNode), 9(document node) 를 주로 사용하게 된다.
  • Node.nodeName
        node의 이름 (태그명을 의미한다.)


  [ nodeType의 종류 ]


EX)


-> nodeType의 경우 1,2,3 숫자를 기억하기 어려울 수 있음으로 상수로 이를 대체하여 사용할 수도 있다.

EX)



[    노드 추가관련 프러퍼티    ]

노드의 추가와 관련된 API들은 아래와 같다.

노드를 추가하기 위해서는 추가할 엘리먼트를 생성해야 하는데 이것은 document 객체의 기능이다. 아래 API는 노드를 생성하는 API이다.



[    노드 제거    ]

노드 제거를 위해서는 아래 API를 사용한다. 이 때 메소드는 삭제 대상의 부모 노드 객체의 것을 실행해야 한다는 점에 유의하자.






[    노드 바꾸기    ]

노드 바꾸기에는 아래 API가 사용된다.



<ul>
<li>HTML</li>
<li>CSS</li>
<li id="target">JavaScript</li>
</ul>
<input type="button" onclick="callReplaceChild();" value="replaceChild()" />
<script>
function callReplaceChild(){
var a = document.createElement('a');
a.setAttribute('href', 'http://opentutorials.org/module/904/6701');
a.appendChild(document.createTextNode('Web browser JavaScript'));
var target = document.getElementById('target');
target.replaceChild(a,target.firstChild); // 새로 만든 a태그를 target의 첫번째 자식 엘리먼트와 교체한다.(즉 텍스트 JavaScript를 대체)
}
</script>



문자열로 노드 제어



위의 예들은 Node 객체를 생성한 뒤 제어했지만, 문자열로 붙이고 지우고 등을 할 수 있다. 아래는 그와 관련된 프러퍼티와 메서드이다.

1. innerHTML

innerHTML는 문자열로 자식 노드를 만들 수 있는 기능을 제공한다. 또한 자식 노드의 값을 읽어올 수도 있다. 


2. outerHTML

outerHTML은 선택한 엘리먼트를 포함해서 문자열로 가지고 오거나, 자신을 포함해서 문자열로 노드를 대체할 수 있다.


3. insertAdjacentHTML()

좀 더 정교하게 문자열을 이용해서 노드를 변경하고 싶을 때 사용한다.


EX) innerHTML

<ul id="target">
<li>HTML</li>
<li>CSS</li>
</ul>
<input type="button" onclick="get();" value="get" />
<input type="button" onclick="set();" value="set" />
<script>
function get(){
var target = document.getElementById('target');
alert(target.innerHTML); // target 내부의 HTML과 CSS li를 가지고 오게 된다.(문자열로)
}
function set(){
var target = document.getElementById('target');
target.innerHTML = "<li>JavaScript Core</li><li>BOM</li><li>DOM</li>";
}
</script>


EX) outerHTML

<ul id="target">
<li>HTML</li>
<li>CSS</li>
</ul>
<input type="button" onclick="get();" value="get" />
<input type="button" onclick="set();" value="set" />
<script>
function get(){
var target = document.getElementById('target');
alert(target.outerHTML); // target 전체를 가지고 오게 된다.(문자열로)
}
function set(){
var target = document.getElementById('target');
target.outerHTML = "<ol><li>JavaScript Core</li><li>BOM</li><li>DOM</li></ol>";
// ul태그 전체가 통째로 ol로 대체되게 된다.
}
</script>


EX) insertAdjacentHTML(arg1, arg2)

<ul id="target">
<li>CSS</li>
</ul>
<input type="button" onclick="beforebegin();" value="beforebegin" />
<input type="button" onclick="afterbegin();" value="afterbegin" />
<input type="button" onclick="beforeend();" value="beforeend" />
<input type="button" onclick="afterend();" value="afterend" />
<script>
function beforebegin(){
var target = document.getElementById('target');
target.insertAdjacentHTML('beforebegin','<h1>Client Side</h1>'); // 맨 첫번째 자식 앞에 삽입
}
function afterbegin(){
var target = document.getElementById('target');
target.insertAdjacentHTML('afterbegin','<li>HTML</li>'); // 첫번째 자식 뒤에 삽입
}
function beforeend(){
var target = document.getElementById('target');
target.insertAdjacentHTML('beforeend','<li>JavaScript</li>'); // 끝 자식 전에 삽입
}
function afterend(){
var target = document.getElementById('target');
target.insertAdjacentHTML('afterend','<h1>Server Side</h1>'); // 끝 자식 뒤에 삽입
}
</script>


[    이벤트(EVENT)    ]



[ 이벤트(EVENT)의 전파 및 전파를 막는 방법 ]



[ 태그의 기본 이벤트를 막는 방법 ]



[ FORM 태그의 대표 이벤트 종류 ]



[ 문서의 태그의 로딩과 관련된 load와 unload 이벤트 ]


[ 그 외 알아두면 좋은 이벤트 ]

- contextmenu event : 마우스 우클릭시 발생

-> 마우스 우클릭 방지 이벤트(요소 검사(F12)를 방지하기 위해 사용 가능)

- shift, ctrl 키와 같이 클릭했는지 유무를 알 수 있는 event 프러퍼티

event.shiftKey, event.ctrlKey

- 클릭한 곳의 마우스 좌표를 알 수 있는 event.clientX, event.clientY


[    5판 7장 객체(Object)와 배열(Array)    ]



i n v i t a t i o n

티스토리 초대장

+ 남은 초대장 수 : 9->0장

안녕하세요!

티스토리에 보금자리를 마련하시려는 여러분께 초대장을 배포해 드리려고 합니다. 많은 갯수는 아니지만 앞으로 생기는 대로 배포를 위한 글을 올릴 예정입니다.

티스토리 블로그는 초대에 의해서만 가입이 가능합니다. 원하시는 분은 댓글에 E-mail 주소를 남겨주시면 초대장을 보내드립니다. 

초대장 요청은 반드시 비밀 댓글로 작성해주세요. 공개댓글 작성시 드리지 않습니다.

요청하실 때는 이메일과 초대장이 필요한 이유(어떤 목적으로 개설할 것인지)를 간략하게 적어주시면 좋을 것 같습니다. 보고 7분께 나누어 드릴게요.

혹시 이번이 아니더라도 다음 달부터 꾸준히 나눔할 예정이니 자주 방문해주세요.

대신, 초대장 받아만 놓고 개설하지 않으실 분들은 신청을 지양해주세요

초대장을 보내드리고 바로 개설하시지 않으신 분들은 초대장을 회수할 수도 있으니 바로 개설해주세요!

Yes

이런 분들께 드립니다!

1. 이메일 주소가 정상적인 분
2. 블로그를 시작하려는 이유를 남겨주신 분!
3. 비밀댓글로 작성해 주시는 분
No
이런 분들께 드리지 않아요!
1. 이메일 주소가 의심되는 분!
2. 이메일 주소를 남기지 않으신 분
3. 이유도 없이 달라고 하시는 분!
티스토리 이래서 좋아요!
1. 이미지, 동영상, 오디오, 파일까지! 무한 용량과 강력한 멀티미디어를 올릴 수 있어요!
2. 스킨위자드로 스킨을 내맘대로~ 거기에 기능 확장 플러그인까지!
3. 내가 원하는대로 myID.com으로 블로그 주소를 만들 수 있어요!


반드시, 초대장 받고 확실히 개설해 운영하실 분들만 신청해 주세요. 받으시고 일정 시간이 지나도 개설하지 않으시는 분들은 초대장 다시 회수하도록 하겠습니다.

대용량 데이터베이스 솔루션 조광원 선생님 동영상 강의를 구했다. 책으로만 보기엔 너무 힘들어서...


비록 좀 오래된 강의라 화질이 좋진 않지만 도움이 되겠지? 

  [ 비밀번호 관리 ]


  : 데이터베이스에서 항상 기밀로 유지해야 한다.

    -> 비밀번호 관리 정책이 있어야 한다.

    

    - 사용자 암호를 관리하기 위해서는 profile을 사용한다.

      - profile 생성 구문 형식 )

          create profile 프로파일명 limit

            옵션1,

            옵션2, .....

    

      - 사용자에게 profile을 적용하는 방법 )

          - 기존유저일 경우 : ALTER USER

          - 새로추가하는 유저일 경우 : CREATE USER

          명령을 이용해서 profile을 적용할 수 있다.

    

    - [  암호 관리 유형  ] 

      

      - 1)비밀번호 입력 횟수 제한하는 방법 : 제한횟수 이상의 로그인 시도시 사용자 계정을 lock 시킴.

      

      - 2)비밀번호의 유효기간을 설정하는 방법    

          : 유효기간이 지나면 암호를 재설정하여 사용하도록 함.

      

      - 3)비밀번호의 재사용 금지 방법

          : 암호 재설정시 기존에 사용한 암호를 다시 사용할 수 없도록 하는 방법.


      - 4)복잡한 암호 설정 방법을 사용

          : 0자 이상, 특수문자 포함 등을 포함시켜 패스워드를 생성하게 하는 패스워드 설정 정책

          -> 오라클에서 제공하는 함수를 사용해서 복잡한 암호를 사용하도록 설정할 수 있다.

          -> (password_verify_function 함수) 

*/


-- 1)비밀번호 입력 횟수를 제한하는 profile을 생성

create profile profile_test limit

  FAILED_LOGIN_ATTEMPTS 3 -- 3번 시도 실패하면 

  PASSWORD_LOCK_TIME 5; -- 5일간 패스워드에 lock을 검


-- 만든 profile을 HR(기존에 있는 사용자) 계정에 적용하기

alter user hr profile profile_test;

-- 3번 로그인 실패시 5일 lock이 걸리게 됨


-- lock을 풀어주는 방법

alter user hr account unlock;


-- 다시 lock을 걸기

alter user hr account lock;


-- 사용자 계정에 lock이 걸렸는지 확인해 보기(dba_users에서...)

select username, account_status AS lock유무, to_char(lock_date,'yy/mm/dd hh24:mi') AS 잠긴날짜,

       profile AS 적용된profile

from dba_users

where username = 'HR';





-- 2)profile에 유효기간 설정하기 

create profile profile_test2 limit

  FAILED_LOGIN_ATTEMPTS 3

  PASSWORD_LOCK_TIME 5

  password_life_time 90 -- 90일동안 패스워드가 유효할 수 있도록 설정함

  password_grace_time 5; -- 90일 지난 다음 유예기간을 설정할 수 있음

  -- 이 유예기간 동안은 로그인할 때마다 경고메시지가 뜨게됨

  -- 이때의 5일은 90일 다음날부터 카운트되는 것이 아닌, 사용자가 90일이 지나고

  -- 최초로 로그인을 시도한 날부터 카운트 되게 됨.



-- dba가 계정을 만료시키는 방법)

  --  alter user 사용자ID password expire;

  

  

-- 3)비밀번호 재사용을 금지시키는 방법)

create profile profile_test3 limit

  password_reuse_time 30 -- 설정했던 패스워드를 30일간은 사용할 수 없음

  password_reuse_max unlimit; 

  

create profile profile_test4 limit

  password_reuse_time unlimit

  password_reuse_max 3; -- 지금 사용하는 비밀번호를 3번 바껴야 다시 해당 비밀번호를 사용할 수 있게 됨

  

  -- password_reuse_time과 password_reuse_max는 서로 상호 베타여서 하나가 값이 있으면 하나는 unlimit으로 되어야 한다.!!

  



  [ DB 사용자 관리(생성,변경,삭제) 및 사용자 정보 알아보기 ]


  - 사용자 계정 : 논리적인 의미

  

  - SYS계정

      : 데이터베이스내의 모든 권한을 갖는 최상위 레벨의 사용자

      

  - SYSTEM 계정

      : SYS 사용자로부터 "DBA권한"을 받은 사용자

      . 새로운 사용자를 추가, 변경, 삭제할 수 있다.

      . 사용자별 공간할당, 패스워드관리, 세션관리 등을 할 수 있다.

      . 데이터베이스 오브젝트(테이블, 뷰, 트리거...)는 "사용자별로" 생성된다.

      . 데이터베이스 오브젝트를 생성한 사용자를 그 오브젝트의 소유자(owner)라 한다.




--[ 사용자 생성 ]

--  : dba권한을 가진 사용자만 가능하다.

-- 형식 ) create user 사용자ID identified by 패스워드


--        해당 사용자가 생성하는 객체가 저장될 테이블 스페이스도 지정할 수 있다.


--        create user 사용자ID identified by 패스워드 default tablespace 테이블스페이스명

--        temporary tablespace temp /*정렬을 위한 temporary tablespace도 생성 가능*/

--        quota 20M on appl_data quota 10M on system; /*tablespace 용량 할당*/


-- 테이블스페이스를 지정하지 않으면, 기본적으로 system tablespace를 사용하게 된다.

create user user1 identified by user1

  default tablespace test_1;

  

-- 사용자 정보 검색 : dba_users에서 검색

select username from dba_users;

select * from dba_users;


/*

  [ 사용자 변경 ]

  - 패스워드 변경 >

      alter user 사용자명 identified by 바꿀비밀번호;

  

  - 사용자의 테이블스페이스 변경>

      alter user 사용자명 default tablespace 바꿀테이블스페이스명;

      

  - 사용가능 용량 변경

      alter user 사용자명 quota 10M on 테이블스페이스명; 

  



alter user user1 identified by 1234;

alter user user1 quota 10 on test_1;


-- 전체 사용자에 대한 정보를 검색할 때

  -- 테이블스페이스 정보, 패스워드, 아이디를 포함해 사용자의 정보를 볼 수 있다.

select * from dba_users;

-- 현재 로그인한 사용자에 대한 정보만을 검색할 때

select * from user_users;



  [ 사용자 제거 ]

  형식 ) 

        drop user 사용자ID;

        

        -- 해당사용자가 가지고 있는 객체(뷰,트리거 등)도 전부 삭제하는 방법(cascade)

          -- 잘 사용하지 않는다.(신중하게 해야한다. 대부분 위 방법으로 삭제한다.)

        drop user 사용자ID cascade;

*/

create user user2 identified by user2;

select * from dba_users;


-- 사용자 제거

drop user user2;


select username from dba_users where username = 'user2';


-- 현재 자신의 계정에 대한 정보를 검색할 때

select * from user_users;



  [ 사용자 공간 사용 정보 알아보는 방법 ]

  

    - 사용자 공간 정보 알아보기 위한 딕셔너리 : user_ts_quotas, dba_ts_quotas

*/

select * from USER_TS_QUOTAS;


select * from SYS.DBA_TS_QUOTAS;

-- MAX_BYTES 항목이 -1인 값의 의미는 최대 용량 제한이 없음을 의미(unlimited)





  [ 사용자의 세션 정보를 알아보는 딕셔너리 ]

  -> v$session

  현재 접속중인 사용자들의 세션 정보를 알 수 있다.


select sid, serial#, username, program from v$session;

-- program : 어떤 프로그램을 통해 접속했는지

-- username : 사용자ID


-- HR에 접속한 것의 세션을 끊어버리기

-- 세션을 중지시키기 : alter system kill session 'SID,SERIALNUM';

alter system kill session '93,275';

select sid, serial#, username, program from v$session;


/*

  [ 인덱스(Index)의 개념/종류/주의사항/활용,관리 ]

  

  1. 인덱스(Index)란???

    : 어떤 데이터가 HDD(하드디스크)의 어디에 있는지 위치 정보를 가진 주소록과 같은 개념.

    -> (데이터 - 위치주소(ROWID)) 쌍으로 저장하고 관리됨

    

    - 목적)

      : 빠르게 쿼리 검색을 하오기 위함

      

    데이터 위치 정보(주소) : ROWID -> 총 10Byte

*/

-- 데이터 튜플의 rowid를 검색해 보기

select rowid, empno, ename from scott.emp where empno = 7521;

-- 결과 : 

/*

  ROWID             EMPNO   ENAME

AAAE+3AAEAAAAFfAAC   7521    WARD

*/

-- [ ROWID의 구조 ] : AAAE+3AAEAAAAFfAAC

/*

  -AAAE+3 : 데이터 오브젝트의 번호

  -AAE : 파일 번호

  -AAAAFf : 블럭 번호

  -AAC : ROW(튜플) 번호

*/


/*

  2. [ 인덱스의 생성 원리 ]

  

    : 전체 테이블을 스캔(Table Full Scan) -> PGA내의 Sort Area에서 정렬(Sort) 공간 부족시 Temporary tablespace 이용해 정렬

                                        -> 정렬한 데이터를 기반으로 HDD Block에 기록

    -> 인덱스는 데이터가 ""정렬"" 되어 들어간다!!

*/


/*

  3. [ 인덱스 구조와 작동 원리(B-TREE 인덱스 기준) ]

  

    : 테이블(Table)과 인덱스(Index)의 비교

    - 테이블은 컬럼이 여러개, 데이터가 "정렬되지 않고" 입력된 순서대로 들어간다.

                            vs

    - 인덱스(Index)는 컬럼이 "Key컬럼(사용자가 인덱스를 지정하라고 지정한 컬럼)"과 "ROWID컬럼" 두개로 이루어져 있다.

      (오름차순, 내림차순으로 정렬 가능)

*/

select * from emp where empno = 7902; -- 를 찾을 때

/*

  데이터 파일의 블록이 10만개가 있다고 할 때 sql문을 수행한다면,

  1) 서버 프로세스가 구문파싱 과정을 마친 후 DB Buffer 캐시에 empno가 7902인 정보가 있는지를 먼저 확인한다.

  2) 해당 정보가 캐시에 없다면 디스크 파일에서 7902정보를 가진 블럭을 찾아서 DB Buffer 캐시로 가져온 뒤 해당 정보를 사용자에게 보여줌

  이 경우에

    - Index가 없는 경우 -> 7902정보가 디스크 어떤 블럭에 있는지 모름으로 10만개 전부 DB Buffer 캐시로 복사한 뒤 Full Scan으로 찾게 됨.

    - Index가 있는 경우 -> where절의 조건으로 준 컬럼이 Index의 Key로 생성되어 있는지 확인한 뒤, 인덱스에 먼저 가서 7902정보가

                          어떤 ROWID를 가지고 있는지 확인한 뒤 해당 ROWID에 있는 블럭만 찾아가서 db Buffer 캐시에 복사하게 됨.

*/


/*

  4. [ 인덱스의 종류 ]

    1) B-TREE 인덱스(Index)

        : OLTP(Online Transaction Processing : 실시간 트랜잭션 처리)

          -> 실시간으로 데이터 입력과 수정이 일어나는 환경에 많이 사용함.

        

    2) BITMAP 인덱스(Index)

        : OLAP(Online Analytical Processing : 온라인 분석 처리)

          -> 대량의 데이터를 한꺼번에 입력한 뒤 주로 분석이나 통계 정보를 출력할 때 많이 사용함.

          -> 데이터 값의 종류가 적고 동일한 데이터가 많을 경우에 많이 사용함

        

        (1) [ B-Tree 인덱스 ]

          B : binary, balance 의 약자

          

              ROOT block(branch block에 대한 정보)

               |

            Branch Block(Left Block에 대한 정보)

               |

              Leaf Block(실제 데이터들의 주소)

          

        (1-1) [ B-Tree 인덱스의 종류 ]

          (A) Unique Index  

              : 인덱스 안에 있는 컬럼Key값에 중복되는 데이터가 없다.(성능이 좋음)

              -> 마치 Unique 제약조건과 유사하다. 따라서, Unique 제약조건 사용시에도 자동으로 UNIQUE INDEX가 생성된다.

또한, 기본키를 생성해도 오라클은 자동으로 UNIQUE INDEX를 생성하게 되는데 이때 UNIQUE나 기본키 객체명과 동일하게 생성된다. 


              - 생성 방법)

                SQL > create unique index 인덱스명

                      on 테이블명(key로지정할컬럼명 1 ASC|DESC, 컬럼명2...);

                ASC : 오름차순 정렬(기본값)

                DESC : 내림차순 정렬

                

                EX)

                -- dept테이블과 같은 테이블 하나 생성

                SQL > create table dept2 as select * from dept;

               

                -- dname을 key로하는 unique index를 생성

                SQL > create unique index idx_dept2_dname on dept2(dname); 

                -- 튜플 하나 추가

                SQL > insert into dept2 values(50,'개발부','서울');

                -- 위의 추가한 튜플과 dname값이 일치하는 다른 튜플을 하나 추가할라하면 unique인덱스이기 때문에

                -- 에러가 발생한다.

                SQL > insert into dept2 values(60,'개발부','인천');

                -- 이미 들어가 있는 dname이기 때문!!

            

            (B) Non Unique Index 

                : 중복되는 데이터가 들어가야 하는 경움(key로 지정한 필드의 중복된 값이 들어갈 수 있다.)

                

                - 생성 문법)

                SQL > create index 인덱스명 on 테이블명(컬럼명1 ASC|DESC, 컬럼명2....);

                

                EX)

                   -- 테스트용 테이블 새로 생성

                   SQL > create table dept3 as select * from dept;

                

                   -- dname을 Key로하는 Non-Unique index 생성

                   SQL > create index idx_dept3 on dept3(dname);

                 

                   SQL > insert into dept3 values(50,'개발부','서울');

                 

                   -- 중복되는 dname을 가진 튜플이 삽입이 가능하다.

                   SQL > insert into dept3 values(60,'개발부','인천');


            (C) FBI 인덱스( Function Based Index ) : 함수기반 인덱스

              : - 인덱스는 where절에 오는 조건 컬럼이나 조인에 쓰이는 컬럼으로 만들어야 한다.

                - 인덱스를 사용하려면 where절의 조건을 절대로 다른 형태로 가공해서 사용하면 안된다.

                

                예를들자면)

                  where절의 조건이 sal + 100 > 200 이러한 조건일 경우

                  단순히 index컬럼을 sal로만 지정하게 되면 인덱스가 적용되지 않고 검색쿼리가 수행되게 됨

                  -> 따라서, 인덱스도 테이블명(sal+100)의 형태로 "함수기반 인덱스"로 사용해야 함

                  

                  EX)

                    SQL > create index idx_dept_fbi on emp(sal+100);

                    -- 이때, emp테이블에는 sal+100 컬럼은 없지만 인덱스를 만들 때 저 연산을 수행해서 인덱스를 만듬

                    

                    (주의 사항)

                    - 임시적인 해결책은 될 수 있어도 근본적인 처방은 아니다.

                    - sal + 100을 인덱스로 생성했는데 쿼리 조건이 변경되면 인덱스를 다시 생성해야 한다.

                    - FBI는 기존의 인덱스를 활용할 수 없다.(단점)

              

              (D) Descending Index : 내림차순으로 인덱스를 생성한다.

                    : 큰 값을 많이 조회하는 SQL에 생성하는 것이 좋다.

                    ex) 최근 날짜부터 조회, 회사 매출 조회

                    

                    SQL > create index idx_emp_sal on emp(sal desc);

                    

                    (주의사항)

                      : 하나의 메뉴에 오름차순과 내림차순을 한번에 조회할 경우

                      -> 오름차순, 내림차순 두 개의 인덱스를 만들면 DML의 성능에 악영향을 미침

                      -> 이때는, 힌트를 사용한다.(아래나 위에서부터 읽도록 할 수 있다.)

                

              (E) 결합 인덱스(Composite Index) 

                  : 인덱스 생성시에 두개 이상의 컬럼을 합쳐서 인덱스를 생성하는 인덱스

                  -> 주로 where 절의 조건이 되는 컬럼이 2개 이상으로 and로 연결되는 경우 사용된다.

                  -> 잘못 생성하게 되면 성능에 영향을 미칠 수 있다.

                    (컬럼의 순서에 따라 효율에 차이가 있다.) -> 보통, 자주 사용하는 컬럼을 앞에 위치시키는 것이 좋다.

                    

                    생성 방식)

                      SQL > create index 인덱스명 on 테이블명(컬럼명1, 컬럼명2);

                      

                    EX) (성별, 이름) 두개의 컬럼으로 인덱스를 생성하는 경우

                      SQL > create index idx_emp_composite on emp(gender,dname);

                      

                      -- 생성된 인덱스는 정렬되어 데이터를 저장시키게 되는데

                      -- 이때, 인덱스 생성시 기술한 컬럼순으로 정렬이 된다. 

                      -- 즉, gender(성별)을 기준으로 asc로 정렬한 뒤 같은 성별일 때

                      -- dname을 asc로 정렬해 인덱스를 생성하는 것

                      

                      -- 생성된 인덱스가 어떤 모습으로 저장되어 있는지 검색해 보자.

                      EX) emp테이블의 empno와 deptno 두개의 컬럼을 조건으로 하는 인덱스 생성

                          SQL > create table emp3 as select * from emp;

                          SQL > create index idx_emp3_composite on emp3(deptno,empno);

                          

                          -- 정렬된 상태로 저장되어있는 인덱스 모습을 보기

                          SQL > select * from emp3 where empno > 0 and deptno > '0';

                        


                          

                          -- 이때, 검색 쿼리가 deptno가 20이면서 empno가 7902인 사원정보를 검색한다면

                          SQL > select * from emp3 where deptno = 20 and empno = 7902;

                          

                          -- 인덱스 생성시 dept 컬럼을 앞에 기술했기 때문에 dept=20을 먼저 찾기위해

                          -- 4번 검사를 하고 이때 20이 나옴으로 empno 7369검사(1번) 다음 deptno 검사 1번

                          -- 다시 empno 검사(1번) 식으로해서 최종 찾는 데이터까지

                          -- 총 9번의 검사를 해 찾게된다.

                          

                          -- 이런식으로 검사가 이루어지기 때문에 주의할 사항이 생기는데

                          (deptno, empno)로 생성할지 (empno, deptno)로 생성할지에 따라 검사 효율이 다르게 나타날 수 있다.

                          

                          이때는 평균적인 검사 효율을 어느정도 계산을 해 바서 인덱스를 생성하는 것이 중요하다!!(신중히 생성하자)

       

       

        (2-1) [ BITMAP 인덱스의 종류 ]

            : 데이터 값의 종류가 적고 동일한 데이터가 많을 경우에 많이 사용된다.

            

            Bitmap Index를 생성하려면 데이터의 변경량이 적어야 하고, 값의 종류도 적은 곳이 좋다.

            일반적으로 OLAP환경에서 많이 생성하게 되는대

            

            Bitmap Index는 어떤 데이터가 어디에 있다는 지도정보(MAP)를 Bit로 표기하게 된다.

            데이터가 존재하는 곳은 1로 표시하고, 데이터가 없는 곳은 0으로 표기한다.

            정보를 찾을 때 1인 값만 찾게 된다!

            

            SQL > create bitmap index 인덱스명 on 테이블명(컬럼);

            

            bitmap index를 생성하면 성별 컬럼 값의 종류대로 map이 생성된다.

            남자 : 1 0 1 0 0 -> 남자데이터가 1,3번 튜플에 있다.

            여자 : 0 1 0 1 1 -> 여자데이터가 2,4,5 튜플에 있다.

            

            이때 문제되는게

            bitmap index사용하고 있는 상태에서 만약 컬럼 값이 새로 하나 더 생긴다면?

            기존의 Bitmap Index를 전부 수정해야한다.

            

            -> B-Tree Index는 관련 블럭만 벽여되지만 Bitmap Index는 모든 맵을 다 수정해야 한다는 문제점!

            -> Bitmap Index는 블럭 단위로 lock을 설정해서 같은 블럭에 들어있는 다른 데이터도 수정작업이 안되는 경우가

               종종 발생한다.

                       

*/


/*

  5. [ 인덱스 주의사항 ]

    

    - 인덱스를 사용하면 무조건 효율이 좋을까? NO

   

    -> [ 인덱스는 DML에 취약 ]

    근거)

      1) INSERT 작업의 경우

        : "index split"현상이 발생할 수 있다.

        - Index Split이란? : 인덱스의 Block들이 하나에서 두개로 나누어지는 현상

        -> 인덱스는 데이터가 순서대로 정렬되어 저장되게 되는데, 기존 블럭에 여유 공간이 없는 상황에서

           그 블럭에 새로운 데이터가 입력되어야 하는 경우

           오라클은 기존 블럭의 내용 중 일부를 새 블럭에다가 기록한 다음 기존 블럭에 빈 공간을 만들어서

           새로운 데이터를 추가하게 된다.

           --> 따라서, 성능면에서 매우 불리하다.

           a)Index Split은 새로운 블럭을 할당 받고 key를 옮기는 복잡한 작업을 수행

           b)Index Split이 이루어지는 동안 해당 블럭에 대해 키 값이 변경되면 안되므로 DML이 블로킹된다.

            enq:TX-index contention 대기 이벤트 발생(RAC-gc current split)

      

      2) DELETE 작업의 경우

        : 일반적인 테이블에서 데이터가 delete될 경우 해당 위치 데이터가 지워지고 그 공간을 사용 가능하다.

                        vs

          하지만, Index에서 데이터가 delete될 경우 -> "데이터는 지워지지 않고, 사용하지 않는다는 의미의 표시만 해두게 된다!"

            --> 즉, 테이블에 2만건의 데이터가 있었는데 1만건을 삭제해도 Index에는 데이터가 2만건이 존재한다는 말이된다.

            -> 인덱스를 사용해도 수행속도를 기대하기는 힘들다.

            

      3) UPDATE 작업의 경우

        : 인덱스에는 UPDATE란 작업이 존재하지 않기 때문에

          기존의 데이터를 DELETE한 다음 새로운 값의 데이터를 INSERT하는 두번의 과정으로 작업이 발생하는데

          따라서, 다른 DML작업보다 더 큰 부하를 주게 된다.

   


[ 최종적으로 인덱스 생성시 고려해야할 사항 ]


1 일반적으로 테이블 전체 로우 수의 15%이하의 데이터를 조회할 때 인덱스를 생성한다.


2 테이블 건수가 상당히 적다면 굳이 인덱스를 만들 필요가 없다. -> 테이블 건수가 적으면 인덱스를 경유하기보다 테이블 전체를 스캔하는 것이 더 빠르다.


3 인덱스 생성시 컬럼은 유일성 정도가 좋거나 범위가 넓은 값을 가진 컬럼을 지정하는 것이 좋다. (NULL값을 많이 갖는 컬럼은 피하는 것이 좋다.)


4 결합 인덱스 생성시에는 컬럼의 순서가 중요하다.

-> 보통, 자주 사용하는 컬럼을 앞에 지정한다.


5 테이블에 만들 수 있는 인덱스의 수는 제한이 없으나, 너무 많이 만들면 오히려 성능 부하가 발생한다.

why? -> 인덱스 생성을 해 놓으면 해당 테이블에 DML 작업(insert, delete, update)시 인덱스에도 수정작업이 동시에 발생하기 때문에 과도하게 많은 인덱스를 생성해 놓으면

오히려 성능 부하가 걸릴 수 있다.

-> 일반적으로, 테이블 당 최대 5개를 넘지 않는 것이 좋다.


6 데이터의 검색보다 수정, 삭제, 삽입 작업이 빈번한 테이블에는 인덱스를 생성하지 않는 것이 좋다.

-> 인덱스는 DML작업에는 성능이 좋지 않기 때문에 검색을 위주로 하는 테이블에 생성하는 것이 좋다.(위에서 언급한 성능 이슈들이 발생할 수 있다.)


7 인덱스 생성시 무엇보다 가장 중요한 점은 SQL 쿼리가 인덱스를 잘 활용할 수 있게끔 짜여져야 한다는 것이다.(쿼리를 잘 짜서 만들자!)       

*/

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


  [ 인덱스의 활용,관리의 예 ]

  
  1. [ 인덱스를 조회하기 위한 딕셔너리 ]
    - user_indexes, user_ind_columns
    - dba_indexes, dba_ind_columns


-- DEPT2 테이블에 적용된 인덱스 검색 방법(user_indexes 딕셔너리에서)
select table_name, index_name
from user_indexes
where table_name = 'DEPT2';

-- EMP 테이블에 적용된 인덱스 검색
select table_name, index_name
from user_indexes
where table_name = 'EMP';

-- [index를 rebuild하는 방법]
  -- 인덱스는 한번 만들어 놓으면 영구적으로 좋은 성능을 가질수 없기 때문에
  -- 항상 관리를 해주어야 한다. 그 방법이 rebuild 하는 것이다.
  
  -- 테스트용 테이블 생성
  create table test_rebuild(
    no number
  );
  -- 테스트용 데이터 1000개 넣기
  begin
    for i in 1..1000 loop
      insert into test_rebuild values(i);
    end loop;
  end;
  /
  commit;
  -- 들어간 데이터 확인
  select * from test_rebuild;
  
  -- 인덱스 생성
  create index idx_test
  on test_rebuild(no);
  
  -- 인덱스의 상태 조회
  analyze index idx_test validate structure; --인덱스 상태를 분석함
  
  -- 인덱스 분석 쿼리를 수행하면 index_stats 딕셔너리에 해당 결과가 반영되게 된다.
  -- 또한,  
  -- 테이블 데이터 삭제시 인덱스에는 삭제되지 않고 사용하지 않는다는 표시만 해 두는대
  -- 이때, 관리가 필요함 따라서, 인덱스에 삭제상태로 표시한 데이터가 몇개 있는지(DEL_LF_ROWS_LEN)를
  -- index_stats 에서 조회하는데
  -- 아래 구문은 삭제한데이터/총있는데이터 비율을 구한다.
  select (DEL_LF_ROWS_LEN / LF_ROWS_LEN) * 100 AS balance
  from index_stats;
  -- 0이 나온다면 좋은 상태임을 알 수 있다.
  
  -- 그럼, 데이터를 삭제해 보자.
  delete from test_rebuild where no between 1 and 400; -- 1 ~ 400까지 데이터 삭제
  -- 남은 데이터 개수 : 600개
  select count(*) from test_rebuild;
  
  -- 다시한번 인덱스 분석을 해 주어야 함 index_stats에 반영될 수 있도록...
  analyze index idx_test validate structure;
  
  -- 39.6~~~%가 나오게 됨 -> 40%정도가 인덱스의 균형이 좋지 않다는 것을 의미함.
  select (DEL_LF_ROWS / LF_ROWS) * 100 AS balance
  from index_stats;
  
  select * from index_stats;
  select DEL_LF_ROWS from index_stats; -- 400
  
  select LF_ROWS from index_stats; -- 1000
  
  -- 40%를 성능을 위해 수정하기 위해선 rebuild 작업이 필요하다.
  alter index idx_test rebuild; -- rebuild 명령으로 인덱스를 수정한다.
  
  -- 다시 분석해서 보면
  analyze index idx_test validate structure;
  select (DEL_LF_ROWS / LF_ROWS) * 100 AS balance from index_stats; -- 0이 나오게 됨
  
  -- 이처럼 인덱스는 rebuild를 통해 꾸준히 관리가 필요하다.
  
  
  -- [ 인덱스 활용 예 ]
  create table emp3(
    no number,
    name varchar2(10),
    salary number
  );
  
  insert into emp3 values(1, '강호동', 200);
  insert into emp3 values(2, '이경규', 300);
  insert into emp3 values(3, '이경실', 100);
  insert into emp3 values(4, '유재석', 400);
  insert into emp3 values(5, '홍길동', 150);
  insert into emp3 values(6, '홍길자', 250);
  
  select * from emp3;
  
  -- index 생성
  create index idx_name
  on emp3(name);
  
  -- where절 조건으로 인덱스 컬럼으로 지정한 name을 쓰지 않았음으로 인덱스를 거치지 않고
  -- 결과가 나오기 때문에 정렬이 되어 있지 않고 출력되게 된다.
  select name from emp3;
  
  -- where절에 name조건을 주었기 때문에 인덱스를 거쳐 정렬된 상태로 나오게됨
  select name from emp3 where name > '0'; -- 이처럼, order by 효과를 대신할 수 있음
  
  -- order by시에도 정렬을 위한 수행 시간이 필요한대 인덱스를 활용하면 이러한 시간을 
  -- 줄일 수 있다.
  
  -- [인덱스를 활용해서 최소값을 구해보자]
  
  -- 먼저 index를 쓰지 않고 찾아오는 방식
  select min(name) from emp3; -- 내부적으로 정렬을 한번해서 찾아오게 됨 0.04초 걸렷음.
  
  -- 인덱스를 쓴 경우
  select name from emp3 where name > '0' and rownum = 1; -- 정렬이 발생하지 않고
  -- 가져올 수 있기 때문에 상당히 빠름 -> 0초로 찍힘
  
  -- 최대값도 해보자.
  select max(name) from emp3; -- 인덱스 사용하지 않은 경우 0.002초(정렬이 먼저 발생하기 때문)
  
  -- 인덱스의 hint를 사용해 최대값을 구해오는 방법
    -- hint? : 
    -- 아래의 경우는 실행계획을 담당하는 옵티마이저에게 ~~게 할거라고 알려주는 건대
    -- 즉, 기본적으로는 asc지만, desc로 지정해 가장 큰 값이 위로 올라와 있게 되기 때문에
    -- rownum = 1을 통해 최대값을 가져올 수 있게된다.
  select /*+ index_desc(emp3 idx_name)*/ name
  from emp3
  where name > '0' and rownum = 1;
  
  -- rownum없이도 구해올 수 있는 방법(위의 방법은 인덱스가 수정되는 과정에서 문제가 발생할 수 있음)
  -- 이 방식을 -> "first_row max방법" 이라 부른다.
  select /*+ index_desc(emp3 idx_name)*/ max(name)
  from emp3
  where name > '0';
  
  
  -- 사용하지 않는 인덱스의 경우 삭제하는게 좋다.(주의사항에서 언급되어 있다.)
  -- 이렇게 삭제시 정말 사용하지 않을건지 알고 삭제하는 것이 중요한데
  -- 11g 버전에서는 사용하지 않는 상태로 만들어서 테스트 해볼 수 있도록 제공하는데
  -- invisible 인덱스란 개념을 제공한다.
    -- : 인덱스를 삭제하기 전에 사용안함 상태로 만들어 테스트 할 수 있는 기능.
  
  -- salary 컬럼으로 emp3테이블의 인덱스 생성
  create index idx_sal on emp3(salary);
  
  select table_name, index_name, visibility 
  from user_indexes
  where table_name = 'EMP3';
  
  -- 인덱스를 사용안함 상태로 바꾸기
  alter index idx_sal invisible;
  -- 다시 조회하면 invisible상태로 바껴있음을 알 수 있음.
  -- 실행계획을 세우는 옵티마이저가 해당 인덱스를 사용하지 않겠다는 의미
  -- 하지만, 인덱스가 지워진건 아니고 보여지지 않은 것과 같은 효과란 것
  select table_name, index_name, visibility 
  from user_indexes
  where table_name = 'EMP3';
  
  -- 다시 visible 상태로 바까보기
  alter index idx_sal visible;
  
  


[ 리얼포스(RealForce) 87U 저소음 차등 10주년 텐키리스 정전용량 무접점 키보드 개봉기 및 타건 영상 ]


안녕하세요, 그동안 하이엔드급 키보드를 장만하고 싶었는데 드디어!!! 


리얼포스 87U 저소음 차등 10주년 텐키리스 정전용량 무접점 키보드를 구입하게 되어 개봉기를 작성하게 되었습니다.! 개인마다 느끼는 것이


다를 수 있으니 주관성이 개입된 점 이해바랍니다.


먼저, 인터넷으로만 보고 주문하려다가 아무래도 오래쓸 거고 키감 때문에 사는 만큼 타건샵에 직접 방문해서 쳐보고 구입하였습니다. 


저는 직장인이어서, 7시면 문을 다 닫아버리는 용산 전자상가에는 가보기가 쉽지 않더라고요 그래서 8시까지 시간이 넉넉하게 하는


신용산역 5,6번 출구 쪽에 있는 리더스키  라는 타건샵에 방문해서 직접 타건을 해 보았습니다.


누구나 알듯이 무접점 방식으로 키보드 중에 최상위 브랜드이죠 


필코 마제나 레오폴드도 좋지만 타건샵에서는 리얼포스 위주로 타건을 해 보았습니다. 우선 리얼포스 87U는 87개의 키가 있다는 뜻이고


리얼포스 87 10주년 제품은 55g 균등 45g 균등, 차등, 저소음 차등으로 나뉘게 되는데요 제가 구매한 것은 저소등 차등 제품입니다.


일단 저는 개발자여서 게이밍 보다는 오래도록 편하게 칠 수 있는 점에 주목해서 고르게 되었고


직접 타이핑을 30분 이상 해 본 결과 균등은 제 기준에서는 다소 누르는 압력이 필요하다고 느꼈습니다. 좀 편하게 치고 싶을 땐 부담스러울 것 


같다고 해야할까요??


제가 산 차등식 같은 경우는 손가락 누르는 곳마다 3가지로 나뉘게 됩니다. 새끼 손가락은 30g, ESC부분은 55g, 그 외는 45g로 되어 있어서


어떤 분들은 너무 약하게 눌러야 되지 않나 하시는 분들도 있는데 전 오히려 너무 편하게 느껴졌고, 심지어 키보드 치면서 오타가 정말 많이


줄어드는 듯한 느낌을 받을 수가 있었습니다. 


맨 마지막 부분에 제 노트북 일반 키보드와 비교해서 타건영상을 올릴 건데 그때 영상을 참고하시면 좋을 것 같고요


타건 느낌은 기계식 청축, 갈축 등이 찰칵 찰칵 거리는 느낌이라면 약간 서걱 서걱 하면서 중후하고 묵직한 느낌입니다. 리얼 포스 무접점은 


제작은 Topre에서 되었고, 유통은 레오폴드에서 국내 유통을 담당하고 있습니다.


그럼 이제, 개봉기를 시작해 보도록 하겠습니다.~


먼저, 레오폴드 봉투에 담겨져 있는 모습입니다. 벌써 부터 기대되내요ㅎㅎㅎ


봉투에서 꺼내니 레오폴드 키보드 상자와 파우치가 나오내요 파우치는 리더스키 사장님이 따로 챙겨 주셨습니다.


상자를 보면 레어폴드가 적혀있는 동봉 씰도 있고요


상자를 열어보면, 레오폴드 키보드 외에 게임하는 사람들에겐 중요한 'A','S','D','W' 키가 서비스로 들어 있고,

빨간 ESC키캡과 Caps Lock, Ctrl 키캡이 여분으로 더 들어 있습니다. 


여분으로 들어 있는 키캡을 찍은 사진입니다.



키보드 뒷면 모습입니다. 깔끔하게 되어있내요 


여분으로 들어 있는 키캡 중 빨간 ESC와 ASDW 키를 교체한 모습입니다. Standard 형태일 때보다

먼가 조금 더 느낌있어진 것 같내요? 아닌가 ㅎ

키캡 교체할 때 키캡 빼는 도구가 있으면 좋은대 만약 없으신 분들은 스카치 테잎을 키캡 양 옆에 붙인다음에 잡아당기면 쏙하고 빠지니까

참고하셔서 고체하시면 좋을 것 같네요

키캡의 경우 PBT로 되어 있어 내구성이 좋고, 염료승화 각인 방식으로 되어 있어 일반적인 방식보다 키보드에 새겨진 글씨가 훨씬 안지워진다고 하내요 


마지막으로 제일 중요한 타건 영상입니다.

핸드폰을 바로 옆에 두고 촬영해서 그렇지만 실재 들리는 것보다 저소음 키보드라 소리가 훨신 작습니다. 비교를 위해서 일반 노트북 키보드로 타이핑 했을 때의 소리도 아래 영상에 올려놓겠습니다. 참고로 노트북은 LG사의 그램입니다.

타건시 치고 있는 내용은 비교를 위해 공통된 문장을 치기 위해서

"동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리 나라만세 가나다라마바사~타파하"까지를 입력한 것입니다.


일반적인 노트북인 LG그램 키보드 소리도 사실 그렇게 크지 않지만 위의 저소음 리얼포스는 훨신 작은 소리라는 것을 알 수 있습니다.

하지만, 소리는 작아도 키감은 차등이라 그런지 굉장히 편안하고 쫀쫀한 중후한 느낌을 지니고 있습니다.


여기까지 리얼포스 87U 10주년 저소음 차등 제품에 대한 리뷰를 마치겠습니다. 


감사합니다.

+ Recent posts