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

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

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

> 세션으로 구현하기 위해 랜덤 세션 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
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
<!DOCTYPE html>
 
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <script>
 
    /*
        [ 함수(Function) ]
 
    1. 호이스팅(Hoisting)
        : 변수 선언, 함수선언(익명함수는 X) 을 위로 끌어올리는 성질로
        -> 아래의 예에서 a()를 호출시 자바스크립트는 최초 a함수를 위로끌어올려
        호이스팅하기 때문에 수행 가능하지만 b, c는 에러가 발생
 
        -> 아래 예시를 자바스크립트 수행 순서대로 표시해보면...
        function a(){...}
        var b;
        function bb(){...}
        var c;
        // 여기까지해서 변수 선언부와 함수 선언이 호이스팅 됨...
 
        console.log(a());
        console.log(b());
        console.log(c());
 
        b = bb;
        c = function(){...}
        가 되게됨...
    */
    console.log(a()); // 'a'
    //console.log(b()); // 변수 b가 호이스팅되어 위로 올려지고 bb()함수도 호이스팅되지만 b에
                      // 대입되지 않은 시점에 수행함으로 에러
    //console.log(c()); // 변수 c 선언만 호이스팅되고 c에는 undefined가 들어 있음으로 에러
 
    // 선언적 함수
    function a(){
      return 'a';
    }
    // 기명함수 표현식
    var b = function bb(){
      return 'b';
    }
 
    // 익명함수
    var c = function(){
      return 'c';
    }
    ///////////////////////////////////////////////////////////////////////////////////////
 
    /*
    ex) 아래의 예시를 한번 보자.
      팀작업을 하다보면 다른 사람과 동일한 이름으로 함수를 아래쪽에 한번 더 선언하게 될 수 있는데
      (실수로...)
      이 경우는 호이스팅을 같이 생각하면 어떻게 수행될까?
      -> 첫번째 sum함수 선언이 호이스팅되서 올라가고, 두번째 sum 함수 선언이 호이스팅되는데
        이때, 이미 sum함수가 있음으로 마지막에 선언된 sum함수로 덮어써지고 만다...
        따라서, sum(1,2)와 sum(3,4)가 모두 3, 7의 결과가 나온다.
        (우리 예상으론 a+b=3, 7이 나올것 같지만...)
 
      결론)
        -> 호이스팅에 의해 덮어써질 수 있음으로 함수 선언은 선언적 함수 방식보다
           함수 표현식(익명함수로 대입하는 등...)의 방식을 사용해서 정의하는 것이 좋다!
    */
    function sum(a,b){
      return "a + b = " + (a+b);
    }
 
    sum(1,2); // 3
 
    function sum(a,b){
      return (a+b);
    }
 
    sum(3,4); // 7
    //////////////////////////////////////////////////////////////////////////
    /*
    - 실행 컨텍스트란?
        : 함수 등이 실행되는 시점에 실행시 필요한 호이스팅, this 바인딩 등의 작업이 이루어져
          필요한 정보를 불러모은 하나의 집합체가 되는 작업.
    -> 호이스팅, 실행 컨텍스트를 따져서 아래 예시의 호출 순서에 대해 생각해 보자.
    -> 출력값은 어떻게 되는가?
      : 정답) 1 undefined 1 1
    */
    var a = 1;
 
    function outer(){
 
      console.log(a);
 
      function inner(){
        console.log(a);
        var a = 3;
      }
 
      inner();
 
      console.log(a);
 
    }
 
    outer();
    console.log(a);
 
    /*
    수행순서)
    1. var a; // 호이스팅
    2. outer함수 선언 호이스팅
    3. a = 1;이 대입됨
    4. outer()를 수행함과 동시에 outer함수의 실행 컨텍스트 생성
    5. outer함수 내에서
       inner함수 선언 호이스팅
    6. outer함수 내의 console.log(a) 수행 -> 1출력
    7. inner()를 수행하며 inner함수의 실행컨텍스트 생성
       var a; 호이스팅되며
       console.log(a) 수행하며 undefined가 출력됨(아래 var a=3에서 a가 호이스팅되지만 대입은 아직 전이기 때문)
       a = 3대입이 이루어짐
    8. outer함수 내의 inner()아래 console.log(a)가 수행됨 -> 1 출력
    9. 가장 아래의 console.log(a)가 수행되며 -> 1출력
    */
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
    /*
      [ 사용시점에 따라 달라지는 this의 의미(this 바인딩) ]
      - 각 상황별 this가 갖는 의미에 대해 알아보자.
      1. 전역 스코프에서 this의 의미 -> "window 객체"를 가리킴
      2. 함수 내부에서 this의 의미 -> "window 객체"를 가리킴
        -> 이 부분이 메서드 내의 this와 헷갈릴 수 있는데
        -> 함수와 메서드는 다름... 메서드는 -> 우리가 흔히 아는 클래스 내에 있는 메서드를 의미하는 것!
        -> 주의하자!! 함수 내의 this의 의미는 "window 객체!"
      3. 메서드 내부에서 this의 의미 -> 메서드 실행 앞까지(호출주체)가 this
          -> 생성자 함수든 리터럴 선언이든 그 안에 정의된 메서드는
          -> new a().print()일 경우 메서드 .앞까지 new a()가 this를 의미하게 됨!
      4. callback에서 this의 의미 -> 함수 내부에서와 동일.
      5. 생성자 함수에서 this의 의미 -> 인스턴스
    */
    // 1. 함수 내에서 this의 의미 ex)
    function a(){
      console.log("함수 내부에서 this의 의미");
      console.log(this); // window 객체...
    }
    function b(){
      function c(){
        console.log("함수 내의 내부 함수에서 this의 의미");
        console.log(this); // 아무리 내부함수여도 역시 window 객체...
      }
      c();
    }
 
    // 2. 메서드 내에서 this의 의미
    var d = {
      e : function(){
        function f(){
          console.log("메서드 내의 내부함수에서  this의 의미");
          console.log(this);
        }
        f();
      }
    }
    d.e(); // window 객체!
    // 여기서 메서드 내의 내부 함수여서 메서드 실행 앞까지가 this를 의미할 것 같지만
    // -> 역시 함수 내부이기 때문에 this가 가리키는 것은 window객체이게 됨!(함수 내부의 this는 무조건!!! window 객체)
    /*
    var a = {
      b : function(){
        console.log("메서드 안에서의 this의 의미");
        console.log(this);
      }
    }
    a.b(); // b()수행 . 앞까지 즉 a 인스턴스가 this
    */
 
    var a = 10;
    var obj = {
      a : 20,
      b : function(){ // 메서드
        console.log(this.a); // 20 -> 메서드 내에선 .앞
        function c(){
          console.log(this.a); // 10 -> 함수 내는 window객체
        }
        c();
      }
    }
    obj.b();
 
    // 그럼... 메서드 내의 내부함수에서 this가 객체를 가리키게 할 순 없을까?
    var obj2 = {
      a : 20,
      b : function(){
        var self = this; // 여기서 미리 this를 담음
        function cc(){
          console.log(self.a);
        }
      }
    }
 
    // 3. callback 함수 내에서 this의 의미 ex)
        // -> 함수에서와 동일 but 함수 수행시 this 바인딩을 지정할 수 있음(함수도 마찬가지)
    var callbackFunc = function(){
      console.log("callback 함수 내에서 this");
      console.log(this);
    }
    var obj3 = {
      a : 1,
      b : function(callBackFunc){
        callBackFunc();
      }
    }
    obj3.b(callbackFunc); // callback 함수 내에서 this -> window 객체
    /*
    [ 함수 실행시 this 바인딩 시켜 실행하는 몇가지 방법 ]
    1. func.call(this바인딩할녀석, 해당함수 매개변수...)
        -> func.call(obj3, 1,2,3)
    2. func.apply(obj3, [1,2,3])
    3. var d = func.bind(obj3, 1,2,3)
        -> 1,2번은 즉시 호출되어 수행되고 3번은 새로운 함수를 생성하게 됨
        -> 즉시 수행되는게 아님... this 바인딩만 결정해서 다른 함수의 매개변수 등으로 전달하고자 할 때 주로 사용됨.
    */
    var cb = function(){
      console.log("bind를 통해 this를 변경해 호출하자.");
      console.log(this);
    }
    var obj4 = {
      a : 1
    }
    setTimeout(cb.bind(obj4),100);
    // setTimeout의 첫번째 매개변수는 함수를 받는데 apply, call은 즉시 수행함으로 bind를 통해서 더해야함
    // 이때, cb함수 수행시 매개변수는 넘기지 않았고, cb함수 수행시 cb함수 내의 this의 의미만 obj4를 가리키도록 bind했음
    // 따라서 this는 obj4를 가리키게 됨.
 
    // 4. 생성자 함수 내에서 this의 의미 ex)
        // -> 인스턴스
    function constructorObj(){
      this.a = 10; // constructorObj 생성자를 통해 생성된 인스턴스를 가리킴(this)
      this.print = function(){
        console.log("이곳에서의 this는 ? ");
        console.log(this); // 메서드 내에서니까 new constructorObj() 가 됨.
      }
    }
    new constructorObj().print();
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /*
      [ prototype ]
      : 많이 들어밨을테지만... 간단히 알면 엄청 쉽고... 자세히 알면 너무너무 어려운... 우리 prototype님...
      -> 우리가 흔히 제일 많이 아는 사용 이유?)
        : 생성자 함수 등에서 메서드를 선언하고 new를 통해 인스턴스를 생성시 모든 인스턴스에서 중복으로 메서드를 위한
        : 메모리 공간을 할당해 비효율 적이니까 공유될 수 있는 prototype에 선언해 효율적이게 관리하고
        : 또, prototype을 통해 상속을 구현하기 위해서... 정도?
      -> 이정도까진 기본적이니까... 많이들 안다. 하지만 인스턴스.메서드() 를 수행했을 때 어떻게 prototype내의 메서드가
        수행되고 하는 원리에 대해 아는 사람은 그리 많지 않다... 아래부턴 그것에 대해 설명한다.
 
      ---------------------------------------------------------------------------------------------------------
      - 생성자 함수를 정의시 자동으로 생성자 함수 내에는 prototype 객체라는 프로퍼티가 생성되게 된다.
      - 이때, 생성자 함수를 new를 통해 인스턴스를 생성하게 되면
          -> 해당 인스턴스에는 __proto__ 라는 변수가 하나 생성되게 되는데 이 __proto__는 바로 생성자의 prototype을
          -> 가리키게 된다.
          -> 이때 중요한건!
                -> 인스턴스는 __proto__를 생략하고 prototype에 바로 접근할 수 있다.(__proto__는 생략이 가능하다.)
                ex) new obj().__proto__.testMethod() == new obj().testMethod()
 
      -> 위의 this바인딩과 연계해 얼마나 잘 이해했나 테스트해보자. 아래 예시를 보라.
    */
    function Person(n,a){ // Person 생성자 함수
      this.name = n;
      this.age = a;
    }
 
    Person.prototype.setOlder = function(){
      this.age += 1; // 메서드 내의 this는? .앞까지 호출대상
    }
    Person.prototype.getAge = function(){
      return this.age;
    }
 
    console.log("prototype EX");
    var youngRong = new Person('영롱',29);// 인스턴스 생성
    youngRong.__proto__.setOlder();
    console.log(youngRong.__proto__.getAge()); // NaN 발생 why?
    // -> 메서드내의 this는 메서드 앞 .까지가 호출대상임
    // 따라서, 여기선 youngRong.__proto__ 객체 즉 prototype객체가 this인데 해당
    // prototype내에 a를 정의한 적이 없음 따라서 undefined랑 1이랑 위에서 연산이 이루어져서 NaN가 나옴.
    console.log(youngRong.getAge()); // 29가 출력됨... youngRong이란 인스턴스를 가리키게 되니까... this가
    // 이처럼 __proto__를 생략하고 prototype에접근할 수 있는 것임...
    ////////////////////////////////////////////////////////////////////////////////////////////////////
 
    /*
      [  자바스크립트 상속을 구현하기 위해 반드시 이해해야하는 prototype 체이닝에 관하여... ]
      - 자 우리는 자바스크립트에서 모든 객체는 Object 객체로부터 파생된다라는걸 알고있을거다.(Object 최상위 객체)
      - 모르면... 난감...
      - 그럼 어떻게 모든 자바스크립트 객체들은 Object 객체를 상속받고 있고 또 Object 내에 정의된 toString(), valueOf()등의
      - 메서드를 사용할 수 있는걸까?
      -> Array도 객체임으로 Object를 똑같이 상속하고 있을 텐데 Array로 예를 들어보자.
 
                  Object 생성자  -------> Object prototype
                                          /              - toString()
                                        /                - valueOf()
                                      /                  - isPrototypeOf() .. 등의 메서드
                                    /
                                  /
      Array 생성자  ---------> Array prototype
          |                    /
         new                 /
          |                /
      Array 인스턴스 -- __proto__
      -> Array인스턴스의 __proto__는 Array prototype을 가리키고 있고 Array prototype은 Object의 prototype을 가리키고 있기 때문!
      따라서 __proto__는 생략이가능하기 때문에 배열에서 바로 new Array().toString()처럼 접근할 수 있는 것!
    */
    var arr = [1,2,3];
    console.log("Array Object 관계")
    console.log(arr.toString()); // Object prototype에 선언되있는 toString()을 수행함 -> 1,2,3
    arr.toString = function(){ // 이렇게 정의하면 클래스에 static 메서드로 정의하게 되는 것!
      return this.join('_'); // 메서드 내의 this임으로 .앞까지가 호출대상이 this
    }
    console.log(arr.toString()); // arr에 선언된 toString()이 호출되 -> 1_2_3 출력(직접 정의한 것 호출)
    var arr2 = [3,4,5];
    console.log("this bind 변경");
    console.log(arr.__proto__.toString.call(arr2)); // this 바인딩을 arr이 아니라 arr2로 해서 수행할 수도 있음 -> 3,4,5 출력
    // 그럼 우리가 여기서 알 수 있는건 머임?
    // 우리가 event처리할 때 event의 함수 내에서 this를 써서 이벤트가 발생한 돔객체를 가리키도록 하는데 이게 어떻게 가능할까?
    // -> 그래, addEventListener 내에서 전달된 callback함수 수행시 bind를 이벤트가 발생한 돔을 가리키도록 했기 때문이지...
    // -> 이런 이유도 모르고 그냥 이벤트 처리에서 this를 사용하는 사람들이 정말 많다...
 
    var obj5 = {
      a:1,
      b:{
        c:'c'
      }
    }
    console.log("obj5 : ");
    console.log(obj5.toString()); // Object prototype의 toString을 수행해 결과 -> [ Object Object ]
    // 이처럼 Object의 toString()은 우리가 원하는 결과를 얻을만큼 구체적으로 구현되어 있지 않음...
    // 따라서, 객체들의 toString()을 디버깅 용으로 찍어보기 위해 Object prototype을 재정의해 사용하는 것이 권장됨.
 
    obj5.toString = function(){
      var res = [];
      for(var key in this){ // this는 obj5객체를 가리키게 될 것이고, for in을 통해 각 프로퍼티 key가 var key에 들어오게 될 것임
        res.push(key + ': ' + this[key].toString());
      }
      return '{' + res.join(', ') + '}';
    }
 
    console.log(obj5.toString());
    // {a: 1, b: [object Object], toString: function (){
    //
    //   var res = [];
    //
    //   for(var key in this){ // this는 obj5객체를 가리키게 될 것이고, for in을 통해 각 프로퍼티 key가 var key에 들어오게 될 것임
    //
    //     res.push(key + ': ' + this[key].toString());
    //
    //   }
    //
    //   return '{' + res.join(', ') + '}';
    //
    // }}
 
    // b 프로퍼티는 원하는 결과를 얻기 어렵고 매 클래스마다 저렇게 toString을 재정의하기도 번잡함... 따라서 아래처럼 수정하자.
    // Object prototype의 toString을 재정의
    Object.prototype.toString = function(){
      var res = [];
      for(var key in this){ // prototype체인을 통해 인스턴스에서 메서드 수행시 메서드 앞까지가 this가 됨으로 해당 객체
                            // 가 된다.
        res.push(key + ': ' + this[key].toString());
 
      }
      return "{" + res.join(', ') + "}";
    }
    console.log(obj4.toString());
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////
     
  </script>
</head>


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

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;

}

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*
    [   계층형 쿼리    ]
    : 오라클 DBMS에서만 제공되는 강력한 기능이다.
    -> 계층적으로 출력되어야하는 경우에 굉장히 유용하다.
     
    ex)  스마트폰의 부품 구성도 정보가 저장되어 있는 테이블이 있다 하자
                스마트폰
                    |
    메인보드,  디스플레이,  배터리, 카메라
        |
CPU,  Memory
이런식으로 스마트폰아래에 메인보드, 디스플레이, 베터리, 카메라가 있고 메인보드 아래는 또 CPU, Memory가 있는 것처럼
계층형으로 데이터가 저장되어 있는 것이다.
 
데이터가 이런식으로 저장되어 있으려면 아래와 같이 보통 테이블에 저장되어 있을 것이다.
 
item_id, parent_id, product_name
100     null            스마트폰
101     100         메인보드
102     100         디스플레이
103     100         배터리
104     100         카메라
105     101         CPU
106     101         Memory
107     102         액정
 
이런식으로 parent_id에 부모에 해당하는 제품의 id를 할당해서 저장되어 있는데 이를 일반적으로는 계층형으로 출력할 수가 없다.
 
하지만, 오라클에서는 가능하다. start with, connect by 절을 이용한 계층형 쿼리를 이용해서!
 
start with (가장 상위의 root node의 조건) // 위의 예에서는 스마트폰은 parent_id가 null이다. -> 따라서, start with parent_id IS NULL로 표현할 수 있다.
connect by (계층을 이루는 조건) // -> connect by prior item_id = parent_id 이때, prior은 item_id의 부모값을 가지고 온다는 의미
-> 따라서 connect by parent_id = prior item_id 로 표현해도 된다.
 
*/
 
// ex) employees테이블은 사원의 employee_id(사번) 정보가 있고 상사의 사번이 manager_id에 저장되어 있다. 이를 이용해 계층형 쿼리를 출력해 보자.
select level, emp.employee_id, emp.first_name || emp.last_name 이름, jb.job_title 직책
from employees emp, jobs jb
where emp.job_id = jb.job_id
start with emp.parent_id IS NULL
connect by prior emp.employee_id = emp.parent_id

[    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를 추적하면서 위치값을 더해감)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script type="text/javascript">
    function getX(element) {
      var x = 0; // x좌표
      while(element){
        x += element.offsetLeft;
        element = element.offsetParent; // 해당 element를 감싸고있는 부모 element
      } // 부모 element를 타고 올라가면서 x좌표값을 더해간다.
    }
 
    function getY(element) {
      var y = 0;
      while(element) {
        y += element.offsetTop;
        element = element.offsetParent;
      }
    }
  </script>



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이다.


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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html>
 
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
   
    <ul id="target">
      <li>HTML</li>
      <li>CSS</li>
    </ul>
    <input type="button" onclick="callAppendChild();" value="appendChild()" />
    <input type="button" onclick="callInsertBefore();" value="insertBefore()" />
 
    <script type="text/javascript">
      function callAppendChild() {
        var target = document.getElementById("target");
 
        // id="target"인 ul에 삽입할 li태그를 생성한다.
        var new_li = document.createElement("li"); // <li></li>
        // 새로 생성한 li태그에 넣을 텍스트 엘리먼트를 생성한다.
        var new_txt = document.createTextNode("insert after");
        // li에 텍스트 노드를 붙인다.
        new_li.appendChild(new_txt); // <li>insert after</li>
 
        // li를 target의 마지막 자식 <li>CSS</li> 뒤에 붙인다.
        target.appendChild(new_li);
      }
 
      function callInsertBefore() {
        var target = document.getElementById("target");
 
        // id="target"인 ul에 삽입할 li태그를 생성한다.
        var new_li = document.createElement("li"); // <li></li>
        // 새로 생성한 li태그에 넣을 텍스트 엘리먼트를 생성한다.
        var new_txt = document.createTextNode("insert before");
        // li에 텍스트 노드를 붙인다.
        new_li.appendChild(new_txt); // <li>insert after</li>
 
        // li를 target의 마지막 자식 <li>CSS</li> 뒤에 붙인다.
        target.insertBefore(new_li, target.firstChild.nextSibling); // insertBefore(붙일node, 어떤대상 앞에 붙일지)
        // 여기서 target.firstChid.nextSibling은 <li>HTML</li>를 의미하게 된다. 즉 그 앞에 붙게됨
      }
    </script>
  


[    노드 제거    ]

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



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
 
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
   
    <ul>
      <li>HTML</li>
      <li>CSS</li>
    <li id="target">JavaScript</li>
    </ul>
    <input type="button" onclick="callRemoveChild();" value="removeChild()" />
    <script>
        function callRemoveChild(){
            var target = document.getElementById('target');
            // target의 부모임으로 body태그를 가리키게 되고 body태그 자식인 target element를 지우게 된다.
            target.parentNode.removeChild(target);
        }
    </script>
  



[    노드 바꾸기    ]

노드 바꾸기에는 아래 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)    ]


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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<!DOCTYPE html>
 
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
   
    <!--
      [ 이벤트 등록 방식 ]
      1. inline 방식 :
            이벤트를 이벤트 대상의 태그 속성으로 지정하는 것.
         - 단점)
              : 정적인 html과 동적(제어역할)인 javascript를 분리시킬 수 없다.
         - 장점)
              : 쉽고, 가독성이 좋다.
    -->
    <!-- 자기자신을 참조하는 불편한 방법 -->
    <input type="button" id="target" onclick="alert('Hello, ' + document.getElementById('target').value);"
      value="button" />
    <!--this를 통해서 간편하게 참조할 수 있다. -->
    <input type="button" onclick="alert('Hello, ' + this.value);" value="button2" />
 
    <!--
      2. 프로퍼티 리스너를 통한 이벤트 등록 방식
        : 이벤트 대상에 해당하는 객체의 프로퍼티로 이벤트를 등록하는 방식이다.
        장점)
          : inline방식에 비해 HTML과 Javascript를 분리할 수 있어서 선호되지만,
            addEventListener 방식을 더욱 추천한다.!
    -->
    <input type="button" id="target2" value="button" />
    <script type="text/javascript">
      var t = document.getElementById("target2");
      t.onclick = function(event) {
        // event객체를 통해서 현재 발생한 이벤트에 대한
        // 다양한 정보를 얻을 수 있다.
        // event의 target속성을 통해 클릭한 객체를 가져올 수 있다.
        alert('Hello, ' + event.target.value);
        // 하지만, IE8 이하 버전에서는 인자로 event객체를 받지 않아 동작하지 않는다.
        console.dir(event); // .dir : 객체의 프러퍼티를 보기 쉽게 보여준다.
      }
      // 이를 해결하기 위해서는 다음과 같이 하면 된다.
      t.onclick = function(event) {
        // IE8이하에서는 첫번째 매개변수로 event를 받지 않아서 window에 있는 event객체
        // 를 통해 접근해야한다. 따라서
 
        // event객체가 존재하면 쓰고, 없으면 window.event를 사용한다.
        var event = event || window.event;
        // 이때, window.event에서는 target속성이 없고, srcElement속성을 쓴다.
          // event.target 속성이 존재하면 쓰고, 없으면 event.srcElement속성을 쓴다.
        var target = event.target || event.srcElement;
        alert('Hello, ' + event.target.value);
      }
    </script>
 
 
    <input type="button" id="target4" value="button" />
    <input type="button" id="target5" value="button" />
    <input type="button" id="target6" value="button" />
 
    <script type="text/javascript">
      // 3. addEventListener를 통해 이벤트를 등록하는 방법
      <!--
        위의 방식과 달리, 이 방식은 여러개의 이벤트 핸들러를 등록할 수 있다.
        프러퍼티 방식에 onclick에 동일한 메서드를 두번 등록하면 나중에 등록한
        메서드만 적용되지만, addEventListener의 경우 동일한 이벤트를 두개 등록할 수 있고
        순차적으로 수행되게 된다.
      -->
      <input type="button" id="target3" value="button3" />
      var t2 = document.getElementById('target3');
      t2.addEventListener("click",function(event){
        alert(1);
      });
      t2.addEventListener("click",function(event){
        alert(2);
      })
      // 이때, 버튼 클릭시 1과 2 순차적으로 경고창이 뜨게 된다.
      // t.onclick=function~~를 두번 등록했다면 이후에 등록된 것만 적용될 것이다!
 
 
      // 하지만, addEventListener의 경우도 IE8이하의 버전에서는 동작하지 않는다.
      // -> IE8이하에서는 attachEvent를 사용해야한다.
      // 다음은 크로스 브라우징을 고려한 코드이다.
 
      var t3 = document.getElementById('target4');
      if ( t3.addEventListener ) { // IE8 이상 버전은
        // addEventListener가 존재하지 않으면 undefined가 뜰 것이고 js에서 undefined는
        // false를 의미한다.
        t3.addEventListener("click", function(event) {
          alert(event.target.value);
        };
      }else if ( t3.attachEvent ) { // IE8 이하 버전은
        t3.attachEvent("onclick", function(event) { // attachEvent는 click이 아니라 on을 포함해 써줘야 한다.
          alert(window.event.srcElement.value); // IE8이하는 매개변수 첫번째에 event객체를 받지 않기 때문
        }
      }
 
      // EX)
      var t4 = document.getElementById('target4');
      var t5 = document.getElementById('target5');
      function btn_listener(event) {
        switch ( event.target.id ) {
          case 'target4' :
            alert(4);
            break;
          case 'target5' :
            alert(5);
            break;
        }
      }
      t4.addEventListener("click",btn_listener);
      t5.addEventListener("click",btn_listener);
 
    </script>
  


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

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<head>
    <style>
        html{border:5px solid red;padding:30px;}
        body{border:5px solid green;padding:30px;}
        fieldset{border:5px solid blue;padding:30px;}
        input{border:5px solid black;padding:30px;}
    </style>
</head>
 
    <fieldset>
        <legend>event propagation</legend>
        <input type="button" id="target" value="target">
    </fieldset>
    <script>
    function handler(event){
        var phases = ['capturing', 'target', 'bubbling']
        // event 객체의 target속성을 통해 접근하는 경우 가장 구체적으로 클릭된 element 객체를 가지고 오게된다.
        // 이때는 전파와 상관없이 클릭된 객체를 가리키게 된다.
        // 하지만, this키워드의 경우 event가 발생된 영역에 있는 element를 가리키게 되는데
        // 지금의 경우는 button을 클릭해도 그 버튼은 fieldset 이벤트 영역에 있고 이는 또 body, html 영역에 있기 때문에
        // 해당 이벤트가 발생하면서 속해 있는 영역 element를 가리키게 된다.(전파된 영역의 객체)
        // 따라서, 캡처링이기 때문에 html->body->field->button순으로 나오게 된다.
        // 이에 반해, event.target.nodeName의 경우는 button을 클릭시 button, fieldset을 클릭시 fieldset이 나오게 된다.
        console.log(event.target.nodeName, this.nodeName, phases[event.eventPhase-1]);
    }
    document.getElementById('target').addEventListener('click', handler, true);
    document.querySelector('fieldset').addEventListener('click', handler, true);
    document.querySelector('body').addEventListener('click', handler, true);
    document.querySelector('html').addEventListener('click', handler, true);
    </script>
    <script type="text/javascript">
      /*
        [ 이벤트의 전파 ]
        : 위의 코드를 보면 input 태그로 만든 button은 fieldset에 속해 있고, filedset은 body태그에 속해있다.
          또, body태그는 html태그에 속해있는데
          button, fieldset, body, html 모두에 이벤트 리스너를 등록해 놓고
          제일 안에 있는 button을 클릭하게되면, button이벤트만 동작하는 것이 아니라 이벤트가 전파되어
          fieldset, body, html 모두 이벤트가 발생하게 된다. 이를 이벤트의 전파라고 하는데
 
          [ 이벤트 전파의 종류 ] 에는 2가지가 있다.
          button을 클릭시
          1. Capturing(캡처링) : button -> fieldset -> body -> html 순으로 이벤트가 수행되는 전파
          2. Bubbling(버블링) : html -> body -> fieldset -> button 순으로 이벤트가 수행되는 전파
          -> 하지만, 캡처링은 IE의 낮은 버전에서는 지원하지 않는 경우가 많아 선호되지 않고
          주로 버블링이 선호된다.
 
          이때, element.addEventListener(첫번째,두번째,세번째) 에서 세번째 인자는 캡처링 유무를 나타내게 되는데
          캡처링 방식으로 할 경우 true를 기술해주고, 버블링 방식으로할 때는 생략하거나 false를 입력해 주면 된다.
          -> element.addEventListener("click",function(){},false)
      */
 
      /*
        [ 이벤트 전파의 방지 ]
        : 이벤트 전파를 막는 메서드는 event객체의 stopPropagation()메서드이다.
        예를들어, 전파되던 이벤트를 body에서 더이상 전파되지 않도록 한다면(버블링일 경우)
        (body element).addEventListener("click",function(event){event.stopPropagation()},false) 를 해주면 된다.
      */
    </script>



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

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<!DOCTYPE html>
 
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
   
    /*
      [ 기본 이벤트를 막는 방법 ]
      : 예를들어 a태그는 href에 지정된 주소로 이동하는게 기본이벤트이고, form태그의 submit 버튼은 전송하는 역할처럼
        각 태그는 기본적인 이벤트를 가진 것들이 있다.
        이러한 기본 이벤트를 막는 방법에 몇가지가 있는데
 
        1. inline 방식의 경우
        2. 프로퍼티 방식의 경우
          :
        3. addEventListener 방식의 경우
          : event객체의 preventDefault() 메서드를 수행하면 된다. 단!) IE9버전 이하에서는 동작하지 않음으로
            -> event.returnValue = false;로 해주면 된다.
    */
 
    // 1. Inline 방식
    <p>
      <label>prevent event on</label><input id="prevent" type="checkbox" name="eventprevent" value="on" />
    </p>
    <p>
      // 위의 체크박스가 체크된 경우 return false하게 된다.
      <a href="http://opentutorials.org" onclick="if(document.getElementById('prevent').checked) return false;">opentutorials</a>
    </p>
    <p>
      <form action="http://opentutorials.org" onsubmit="if(document.getElementById('prevent').checked) return false;">
          <input type="submit" />
      </form>
    </p>
 
    <a id="googleA" href="http://google.com" onclick="return test();">구글로 이동</a>
    <script type="text/javascript">
      var test = function (event){
        return false; // 기본 동작을 막기 위함
      }
    </script>
 
 
 
    // 2. 프로퍼티 방식으로 기본 동작을 취소하는 방법
    <script>
      document.querySelector('a').onclick = function(event){
          if(document.getElementById('prevent').checked) // 체크가 되어 있는 경우에
              return false; // return false 하면 a태그의 기본 동작을 막을 수 있다.
      };
 
      document.querySelector('form').onclick = function(event){
          if(document.getElementById('prevent').checked)
              return false;
      };
 
    </script>
 
 
 
    // 3. addEventListener의 경우 -> event.preventDefault() or event.returnValue = false;
    <script type="text/javascript">
      var test2 = function(event){
        if ( document.getElementById('prevent').checked)
          event.preventDefault(); // 기본 동작을 막는다.
      }
      document.querySelector('a').addEventListener("click",test2,false);
      document.querySelector('form').addEventListener("click",test2,false);
    </script>
  



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

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<!DOCTYPE html>
 
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
   
    <pre>
      [ FORM 태그 관련 대표 이벤트 ]
      1. submit
        : form에서 submit 버튼을 클릭해 form내부의 데이터가 action으로 지정한 곳으로 전달될 때 발생.
      2. click
        : 클릭했을 때 발생되는 이벤트
      3. focus
        : ELEMENT가 포커스를 얻었을 때(입력가능한 상태) 가 되었을 때 발생하는 이벤트
      4. blur
        : ELEMENT가 포커스를 잃었을 때 발생하는 이벤트
    </pre>
    <!-- EX -->
    <form id="formID" action="http://www.google.com" method="post">
      이름 : <input id="nameID" type="text" value="" />
      <input type="submit" value="제출" />
    </form>
    <script type="text/javascript">
      var areaElement = document.getElementById("formID");
      // form태그에 submit 이벤트를 건다.
      areaElement.addEventListener("submit",function(event){
 
        if ( event.target.nameID.value.length < 1 ) {
          // 클릭된 폼태그의 nameID를 가진 엘리먼트의 입력된 값이 한글자보다 작은 경우
          event.preventDefault(); // 기본이벤트를 방지함 -> 즉, submit발생을 막음
          // addEventListener의 경우 기본동작을 막기 위해 return false대신 preventDefault()를 사용한다.
          alert("이름은 반드시 입력되어야 합니다.");
        }
 
      });
    </script>
 
    <script type="text/javascript">
      // focus를 얻었을 경우 이벤트를 발생
      var nameElement = document.getElementById("nameID");
      nameElement.addEventListener("focus",function(event){
        alert(event.target.id + "가 포커스를 얻었습니다.");
      });
      // focus를 잃었을 경우 이벤트를 발생
      nameElement.addEventListener("blur",function(event){
        alert(event.target.id + "가 포커스를 잃었습니다.");
      });
    </script>
  



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

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!DOCTYPE html>
 
  <head>
    <meta charset="utf-8">
    <title></title>
    <script type="text/javascript">
      function unloadF(){
        alert("해제");
      }
    </script>
  </head>
   
    <pre>
      [ 문서의 로딩과 관련된 load 이벤트와 unload 이벤트 ]
      1. load
        : html태그가 전부 로딩되어 접근할 수 있는 상태가 되었을 때 발생하는 이벤트이다.
      2. unload
        : html 태그가 메모리에서 내려가기 직전에 호출되는 이벤트이다.
        -> 현재 창이 닫히기 직전에 특정 작업을 처리 후 닫고자 할 때 사용하면 좋다.
 
      load 이벤트가 중요한 이유는, 특정 ELEMENT에 접근하는 스크립트를 load 이벤트 없이
      선언할 때에는 접근하려는 ELEMENT보다 아래쪽에 위치시켜야 한다는 것이다.
 
      why? 아직 메모리에 올라오지도 않은 상태에서 스크립트가 접근하려하면 존재하지 않는
      ELEMENT를 참조하게 되니까 오류가 발생하게 된다.
 
      따라서, 바디태그 맨 밑쪽에 위치하는 것이 좋지만 그렇게 하지 않고도
      load 이벤트 발생시(태그가 다 로딩되면) 해당 스크립트가 동작하도록 해줘도 된다.
 
      또한, load와 unload 이벤트는 inline방식으로는 body태그에서 수행해주면 된다.
    </pre>
    <script type="text/javascript">
      // 해당 스크립트는 areaID div 보다 위에 위치해 있음으로 본래 오류가 나야하지만
      // 나지 않도록 load함수로 수행토록 하겠다.
 
      // 프러퍼티 이벤트 방식
      window.onload = function(){
        document.getElementById("areaID").innerHTML = "데이터 넣기";
      }
      // 이때, onload 프러퍼티로 이벤트를 지정할시 단점은 무엇일까?
      // -> 만약 본인이 onload 이벤트를 두번 지정할 경우 더 아래에서 지정한
      // onload이벤트로 덮어버려 이전에 선언한 onload는 동작하지 않게 된다.
      // 따라서, onload 이벤트 지정시 addEventListener를 통해 등록해주도록 하자.
      window.addEventListener("load",function(){
        document.getElementById("areaID").style.border = "1px solid red";
      });
    </script>
    <div id="areaID"></div>
 
 
  


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

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

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

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

event.shiftKey, event.ctrlKey

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

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<!DOCTYPE html>
 
     
        <div id="target" style="border:1px solid red; width:100px;height:100px;"></div>
 
        <script type="text/javascript">
          /*
            1. [ contextmenu 이벤트 : 마우스 우클릭 관련 이벤트 ]
            : 해당 속성을 이용해 요소 검사를 못하도록 마우스 우클릭을 막을 수 있다.
          */
          var targetObj = document.getElementById("target");
          targetObj.addEventListener("contextmenu",function(event){
            // event.type -> 발생한 이벤트의 종류를 알 수 있다.
            if ( event.type == "contextmenu" ){ // 이벤트가 contextmenu이면
              event.preventDefault(); // 기본동작을 막음(마우스 우클릭 방지)
              alert("우클릭 불가능");
            }
          });
        </script>
 
        <!--
          2. [ change 이벤트 ]
            : select 박스, input, textarea 등의 값이 바낄때마다 호출되는 메서드
        -->
        <select id="sel">
          <option value="JAVA">JAVA</option>
          <option value="CSS">CSS</option>
        </select>
        <script type="text/javascript">
          document.getElementById("sel").addEventListener("change",function(event){
            alert("변경된 값 : " + event.target.value);
          });
        </script>
 
 
        <!--
          3. [ 클릭 이벤트시 보조키를 같이 누른지 여부를 체크하는 방법 ]
 
            1) shift키 : event.shiftKey
            2) ctrl키 : event.ctrlKey
        -->
        <div id="target2" style="border:1px solid blue;width:100px;height:100px;"></div>
        <script type="text/javascript">
          document.getElementById("target2").addEventListener("click",function(event){
            if ( event.shiftKey ) { // click시 shift키를 같이 누른 경우
              alert("click + shift key");
            }else if ( event.ctrlKey ) {
              alert("click + ctrl key");
            }else {
              alert("just click");
            }
          });
        </script>
 
        <!--
          4. [ 클릭이벤트가 발생한 마우스 좌표를 구하는 clientX, clientY ]
        -->
        <div id="target3" style="border:1px solid green;width:100px;height:100px;"></div>
        <script type="text/javascript">
          document.getElementById("target3").addEventListener("click",function(event){
            alert("클릭 좌표 : ( " + event.clientX + " , " + event.clientY + " )");
          });
        </script>
    


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


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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
<!DOCTYPE html>
 
  <head>
    <meta charset="utf-8">
    <title></title>
    <script type="text/javascript">
      // [ 객체(object) ]
      // 1. 빈 객체 생성
      var book = {};
      // 2. 선언과 동시에 객체 생성
      var book2 = {
        title : 'book2의 제목',
        page : 12
      };
      // 3. 세번째 객체 생성 방법
      var book3 = new Object();
 
      // [ 동적으로 객체의 속성(프로퍼티)를 선언하기 ]
      book.title = 'book1의 제목';
      book.page = 20;
      // 동적으로 객체의 프로퍼티에 객체를 할당하기
      book2.chapter1 = {
        title : 'chapter1의 제목',
        page : 30
      };
      book.chapter1 = new Object();
      book.chapter1.title = 'book chapter1의 제목';
      book.chapter1.page = 102;
      // 이처럼 언제든지 동적으로 새로운 속성(프로퍼티)를 할당할 수 있다.
      alert("book chapter1의 제목과 페이지 : " + book.chapter1.title + " : " + book.chapter1.page);
      alert(book2["chapter1"]["title"]);
 
      // [ 객체에 특정 속성(프로퍼티)가 존재하는지 확인하기 ]
        // "프로퍼티명" in 객체명
      if ( "title" in book ) {
        alert("book객체에 title 속성이 존재합니다.");
      }
 
      // [ 객체가 어떤 객체인지 확인하기 ]
        // instanceof
      if ( book instanceof Object ) {
        alert("book은 Object의 객체입니다.")
      }
 
      // [ 객체의 속성(프로퍼티) 삭제하기 ]
        // delete 객체명.프로퍼티명
      delete book.title; // delete는 아예 속성을 제거하는 것으로 빈 문자열이 들어가는게 아니라
      // 아예 사라지게 됨 따라서 아래에서 book.title을 찍어보면 undefined가 나오는 것을 알 수 있다.
 
      // [ 배열에 접근하는 두가지 방법 ]
      // 1. 객체명.속성명
      // 2. 객체명["속성명"]
          // : 두번째 방법은 []안에 문자열이 들어오게 된다. 첫번째는 문자열이 아닌 키워드!
          // 이게 무슨 의미가 있을까?
          //  만약, 사용자나 개발자에 의해서 객체의 속성명이 정해지게 된다면 객체명.속성명으로는
          //  조회를 할 수 없을 것이다. 왜? 아직 해당 속성명이 무엇이 될지 알 수 없으니까
          //  하지만, []안에 문자열로 접근하는 이 방식은 그 것이 가능하다.
      // EX)
      var inputVar = "newProperty"; // 사용자가 속성명으로 하고 싶은 명칭을 입력 받음
      book[inputVar] = "newProperty의 값"; // book.inputVar은 불가능하지만, 이건 가능하다.
      alert(book[inputVar]);
 
      // 이때!!!, 어떤 변수명이 넘어올지 알 수 없을 때 객체 내에 있는 속성명을 조회할 수 있는 방법은??
        // (for 변수 in 객체명) 을 사용하면 된다.
        // 그러면 객체의 속성(프로퍼티)명이 변수에 하나씩 담기게 된다.
      //EX)
      for (propertyVal in book) {
        alert(propertyVal + " 속성의 값은 : " + book[propertyVal]);
      }
 
      // 값이 null이거나 undefined가 아닌 경우를 체크하는 코드
      if ( !book.title ) {
        alert("book.title은 null도 undefined도 아닙니다.");
      }
 
      //////////////////////////////////////////////////////////////////////////////////////////
 
      // [ 배열(Array) ]
      // JS의 배열은 자바와 달리 다양한 타입을 한 배열에 담을 수 있다.
 
      // 1. 배열의 선언 방식
        // 1)
          var arr1 = [1,2,3,4];
        // 2)
          var arr2 = new Array(5)[1,2,3,4,5];
      // 2. 다양한 타입을 담을 수 있는 배열
        var arr3 = [1,true,false,"string",{title:'제목',page:12}]; // 객체조차 담을 수 있다.(배열안에 배열도 가능)
 
      // 3. 배열에서 제공하는 다양한 메서드 및 리터럴
        // 1) length : 배열의 element 갯수를 반환하는 리터럴이다.
            alert("arr3배열의 element개수는 : " + arr3.length);
 
        // 2) join() : 배열의 각 element를 합쳐서 한 문자열로 반환한다.
            // join() 은 기본형으로 [1,2,3,4].join()시 => "1,2,3,4"를 반환하고
            // join("-")은 [1,2,3,4].join('-') => "1-2-3-4";를 반환한다.
            alert([1,2,3,4].join('-'));
 
        // 3) split('') : join과 반대로 특정 단위를 기준으로 배열을 반환한다.
            var arrString = "1,2,3,4";
            var arrSplit = arrString.split(",");
            alert(arrSplit.toString()); // 배열의 toString()은 각 배열원소를 ,로 묶어 문자열로 반환한다.
 
        // 4) concat() : 현재 배열에 특정 element를 추가한다.
            var arr4 = [1,2,3,4];
            arr4 = arr4.concat(5,6,7);
            alert(arr4.toString());
 
        // 5) slice(startIndex, lastIndex+1) : 시작인덱스 ~ 끝인덱스 하나 전까지 배열을 잘라낸다.
            var arr5 = [1,2,3,4,5,6];
            arr5 = arr5.slice(2,4); // 3,4
            alert(arr5.toString());
 
        // 6) splice(startIndex, lastIndex+1, 추가할 값들...)
            // : 시작인덱스 ~ 끝인덱스 까지는 배열의 값을 잘라내고 해당 배열에는 잘래내진 값들을 뺀 값들이 들어있다.
            // 만약, 3번째 인자를 입력시 startIndex부터 3번째 인덱스에 입력한 값들이 추가되게 된다.
            // EX)
            var arr6 = [1,2,3,4,5,6,7];
            var arr7 = arr6.splice(1,4); // arr7에는 2,3,4가 들어가게 되고, arr6에는 1,5,6,7이 남아 있게 된다.
            alert("arr6 : " + arr6.toString());
            alert("arr7 : " + arr7.toString());
 
            var arr8 = [1,2,3,4,5,6,7];
            arr8.splice(2,0,true,false,"string"); //2번 인덱스부터 0개 빼지 안하겠다는 거고 2번인덱스부터 true, false, String을 추가한다.
            alert("arr8 : " + arr8.toString()); // 1,2,true,false,String,3,4,5,6,7
 
        // 7) sort() : 배열의 정렬
            // : default는 문자열 오름차순이다. 따라서, 숫자를 정렬하고 싶을 땐 익명 함수를 이용해야한다.
            // EX)
              // 1) default
                var arr9 = [3,4,7,6,9,2,1];
                alert("sort : " + arr9.sort().toString());
              // 2) 숫자 기준
                var arr10 = [3,4,12,5,22];
                arr10 = arr10.sort(function(a,b){
                  return (a-b);
                });
                alert("sort : " + arr10.toString());
 
          // 8) push()와 pop()
              // push() : 마지막 배열요소에 새 element 값을 추가한다.
              // pop() : 마지막 배열요소를 꺼낸다.
            var arr11 = [1,2,3,4];
            arr11.push("true");
            alert("push : " + arr11.toString());
            var arr12 = [1,2,3,4];
            arr12 = arr12.pop();
            alert("pop : " + arr12);
    </script>
  </head>
   
 
  


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