브라우저 객체모델

브라우저 객체 모델(BOM)은 웹에서 사용하는 자바스크립트의 핵심입니다.
웹 페이지 콘텐츠와 무관하게 브라우저 기능을 노출하는 객체입니다. 제대로 된 명세가 없는 기간이 너무나 길어서 제조사별로 BOM이 확장되서 좋은 점도 있지만 문제도 많았습니다.
그런 이유로 현재도 표준화를 하고 있으며 이제 HTML5명세에서 BOM의 주요 부분을 다루고 있습니다.

window 객체

브라우저 인스턴스입니다.
브라우저 창의 자바스크립트 인터페이스 구실을 하며, ECMAScript Global 객체로 기능하기도 합니다.
즉 웹페이지에서 정의하는 모든 객체, 변수, 함수에서는 window가 Global객체 구실을 하며, window에 정의된 parseInt()등의 메서드를 이용합니다.

전역 스코프

Global객체 구실을 하므로 전역에서 선언한 변수, 함수는 모두 window 객체의 프로퍼티, 메서드가 됩니다.

전역으로 sayAge()라는 함수를 정의했다면 window.sayAge()로 사용 가능합니다.

특이한 점이 있는데 아래 예제를 봅시다.

var newValue = oldValue; //oldValue가 선언되지 않았으므로 에러가 발생합니다.

var newValue = window.oldValue //oldValue는 선언되지 않았으나 에러 대신에 undefined가 할당됩니다.

알아둬야 할점으로는 location, navigator 등 자바스크립트에서 무심코 전역이라고 생각 할 수 있는 많은 객체가 사실 window객체의 프로퍼티입니다.

창 사이의 관계와 프레임

페이지의 프레임이 있다면 각 프레임은 독자적인 window객체를 가집니다.
window객체를 각 프레임의 name을 프로퍼티로 가집니다.

<html>
    <head>
        <title>Example</title>
    </head>
    <frameset rows="160,*">
        <frame src="frame.htm" name="topFrame">
        <frameset cols="50%,50%">
            <frame src="anotherframe.htm" name="leftFrame">
            <frame src="yetanotherframe.htm" name="rightFrame">
        </frameset>
    </frameset>
</html>

상단에 프레임 하나
하단에 프레임 두개로 이루어 진 html소스입니다.

상단 프레임은 window.frames[0], window.frames[“topFrame”] 두가지 방법으로 참조 가능합니다.

top

위에서 frames선택을 window객체로 했습니다. 하지만 일반적으로 top객체를 통해 프레임을 선택 합니다.
top객체는 항상 최상위 프레임을 기리키는데 이는 곧 브라우저 창입니다.
top.frames[0], top.frames[“topFrame”]

parent

parent객체는 항상 현재 프레임 바로 상위인 부모 프레임입니다.
상위 프레임이 없을 때는 parent, top 모두 window와 일치합니다.

self

마지막 알아볼 window객체는 self입니다.
이 객체는 항상 window를 가리킵니다.

top, parent, self는 모두 window객체의 프로퍼티입니다.
window,parent,window.top등으로 사용가능합니다.
window.parent.parent.frames[0]같이도 사용할 수 있습니다.

창의 위치

창의 위치를 가져오거나 설정하는 프로퍼티와 메서드에 대한 설명을 하겠습니다.

창 위치를 판단하는 크로스 브라우저 코드를 봅시다. (브라우저마다 다른점이 있기때문)

var leftPos = (typeof window.screenLeft == "number")
            ? window.screenLeft : window.screenX;
var topPos = (typeof window.screenTop == "number")
            ? window.screenTop : window.screenY;

위 예제로 창 위치를 판단할 수 있습니다.
하지만 파폭 사파리, 크롬 등 브라우저 종류에 따라서 예외가 존재하기 때문에 일관적으로 알 수 있는 방법은 없습니다.
하지만 moveTo(), moveBy()메서드로 정확한 위치로 옮길 수 있습니다.

window.moveTo(0.0)    //창을 왼쪽 위 코너로
window.moveBy(0, 100);    //창을 아래쪽으로 100

window.moveTo(200, 300); //창을 200, 300으로
window.moveBy(-50, 0); //창을 왼쪽으로 50이동

브라우저에서 금지할 가능성도 있으며 ie7, 크롬, 오페라에서는 기본적으로 비활성화 되어있습니다.

창 크기

브라우저간의 차이 등으로 인하여 정확한 창크기를 알아낼 방법이 없습니다.
하지만 뷰 포트 크기는 알 수 있습니다.

var width = window.innerWidth;
var height = window.innerHeight;

if(typeof width != "number"){
    if(document.compatMode == "CSS1Compat"){
        width = document.documentElement.clientWidth;
        height = document.documentElement.clientHeight;
    }else{
        width = document.body.clientWidth;
        height = document.body.clientHeight;
    }
}

width가 숫자가 아닐경우 compatMode로 표준모드인지 확인합니다.
표준모드라면 documentElement를 사용하여 크기를 반환합니다.
표준모드가 아니라면 body를 사용하여 크기를 반환합니다.

브라우저 창 크기를 조절 할때는 resizeTo(), resizeBy()를 사용합니다.

window.resizeTo(100,100);
window.resizeBy(100,50); 100x100 -> 200x150
window.resizyTo(300,300);

ie7이상, 크롬, 오페라에서는 구현되지 않은기능입니다.

네비게이션과 열기

window.open() 메서드는 URL로 이동 후 브라우저 창을 새로 엽니다.
매개변수는 다음과 같습니다.
이동할 URL, 대상 창, 기능을 나타내는 문자열, 새 페이지가 브라우저 히스토리에서 현재 페이지를 대체할지에 대한 boolean값

팝업 창

두 번째 매개변수가 기존의 창/프레임 이름이 아니라면 세 번쨰 매개변수에 지정한 문자열로 새창이나 탭을 생성합니다.

window.open("http://www.w3schools.com", "_blank", "toolbar=yes,scrollbars=yes,resizable=yes,top=500,left=500,width=400,height=400");

위 예제와 같이 세 번째 매개변수는 새 창의 설정 정보를 나타내는 쉼표(,)로 구분된 문자열 입니다.
특이한 점은 새 창을 열 때만 적용됩니다.

팝업창에 세 번째 매개변수의 상세 옵션은 아래 주소를 참고 하시면 됩니다.

W3Schools - 바로가기

이름-값 쌍을 쉼표로 구분하고 있습니다.

새창에 대한 조작

새로 생성한 창을 오픈하기만 하는 것이 아닙니다.
window.open() 메서드는 새로 생성한 창에 대한 참조를 반환하며, 이 객체를 이용하여 세밀히 조절할 수 있습니다.

var openWin = window.open("http://www.w3schools.com", "_blank", "toolbar=yes,scrollbars=yes,resizable=yes,top=500,left=500,width=400,height=400");

//크기 조절
openWin.resizeTo(500, 500);

//이동
openWin.moveTo(100, 100);

//창닫기
openWin.close();

close()의 경우 window.open()으로 생성한 팝업 에서만 동작합니다.
주요 브라우저 창은 반드시 사용자 확인을 거쳐야만 닫을 수 있습니다.
허나 팝업창은 close()을 호출해서 사용자 확인 없이 닫을 수 있습니다.

새로 생성한 window객체에는 자신을 연 창을 참조하는 opener 프로퍼티가 있습니다.
이 프로퍼티는 팝업창의 최상위 window객체에만 정의 되며, window.open()을 호출한 창이나 프레임을 가리킵니다.

팝업 창은 자신을 연 창에 대한 포인터가 존재하며 반대는 존재하지 않습니다.

크롬에서는 새창을 생성한 창과 팝업창이 통신할 필요가 없다고 판단될 경우 아래와 같이 수동으로 분리 할 수 있습니다.

var openWin = window.open("http://www.w3schools.com", "_blank", "toolbar=yes,scrollbars=yes,resizable=yes,top=500,left=500,width=400,height=400");

openWin.opener = null;

물론 위와 같이 분리할 경우 다시 연결은 불가능합니다.

보안 제한

오래된 사이트는 팝업 창을 남용했습니다.
하지만 현재 브라우저 제조사들은 팝업창 설정을 제한하였습니다.
주소 표시줄이 안숨겨 진다던지, 상태바를 무조건 표시한다던지가 이에 해당합니다.
추가로 팝업창을 사용자의 행동에 의해서만 열 수 있게 하여서, 페이지를 아직 불러올때나, 마우스클릭, 키를 누르는등의 행동이 없이 팝업창이 뜨려할 때 에러가 표시될 수 있습니다.

팝업 차단

요즘 대부분의 브라우저는 기본 혹은 확장 프로그램등으로 팝업 차단시킨 경우가 많습니다.
이런 경우 window.open()이 null을 반환하기 때문에 null인지 확인하여 구분합니다.

인터벌과 타임아웃

브라우저에서 자바스크립트는 단일 쓰레드로 실행됩니다.
하지만 타임아웃, 인터벌을 통해 특정 시간에 실해오디게 조절할 수 있습니다.

타임아웃 - setTimeout()

window.setTimeout()메서드로 설정합니다.
매개변수는 두개 받습니다. 첫 번쨰는 실행할 코드, 하나는 코드를 실행할 때까지 기다리는 시간(밀리초) 입니다.

//eval() 처럼 사용 할 수 있습니다. 허나 성능이 떨어지므로 이렇게 사용하지 않습니다.
setTimeout("alert('helloworld!')", 1000);

//익명함수를 사용하여 써줍시다
setTimeout(function(){
    alert("helloworld!");
}, 1000);

자바스크립트는 단일 쓰레드입니다.
자바스크립트는 큐를 이용해 각 코드(작업)의 실행을 관리합니다.
각 작업은 큐에 추가된 순서대로 실행되며, 매개변수의 밀리초 만큼 기다린 후 작업을 큐에 추가시킵니다.
그런이유로 큐가 빈 상태면 바로 실행되겠지만 큐가 비어있지 않을 경우 코드는 차례를 기다려야 합니다.

setTimeout()은 해당 타임아웃의 숫자형 ID를 반환합니다.
그 ID를 이용하여 작업이 시작되기 전 취소도 가능합니다.

var timeoutID = setTimeout(function(){
    alert("helloworld!");
}, 1000);

//취소가 가능하다.
claerTimeout(timeoutID);

취소가 가능합니다.
하지만 작업이 이미 실행됐다면 호출해도 아무 효과 없습니다.

인터벌 - setInterval()

인터벌은 타임아웃과 비슷하지만 페이지가 종료되거나 인터벌을 취소하기 전에는 일정한 시간마다 코드를 반복실행 합니다.
setTimeout과 동일하게 실행할 코드, 대기시간(밀리초)로 받습니다.

//eval() 처럼 사용 할 수 있습니다. 허나 성능이 떨어지므로 이렇게 사용하지 않습니다.
setInterval("alert('helloworld!')", 1000);

//익명함수를 사용하여 써줍시다
setInterval(function(){
    alert("helloworld!");
}, 1000);

인터벌 또한 타임아웃 처럼 ID를 반환합니다.
반환된 ID와 clearInterval()메서드를 사용하면 아래와 같이 구현할 수 도 있습니다.

var num = 0;
var max = 5;
var intervalId = null;

function incNum(){
    num++;

    if(num == max){
        clearInterval(intervalId);
        alert("done");
    }
}

intervalId = setInterval(incNum, 500);

하지만 interval은 인터벌 사이의 시간을 정확히 보장하기 어렵고 가끔 일부 인터벌을 건너뛰기도 합니다.
실무에서는 아래처럼 타임아웃을 사용하는 것이 좋을것입니다.

var num = 0;
var max = 5;

function incNum(){
    num++;

    //최대값이 아니면 계속 새로운 타임아웃을 설정
    if(num < max){
        setTimeout(incNum, 500);
    } else {
        alert("done");
    }
}

setTimeout(incNum, 500);

위 코드는 인터벌 예제와 동일하게 동작합니다.

시스템 대화상자

alert, confirm, prompt메서드를 통해 사용자에게 시스템 대화상자를 제공합니다.
이 대화상자는 CSS가 아니라 운영체제/브라우저 설정에 따라 다릅니다.
이들 대화상자는 동기적이거 모달 성질이 있어 대화상자가 떠있다면 코드 실행이 중지되며 대화상자를 닫아야 재개됩니다.

alert()

일반적으로 에러처럼 사용자가 할 수 있는 일은 없으나 반드시 알려야 할때 사용합니다.

confirm()

얼럿 대화상자처럼 메시지를 표시하지만 결과를 사용자가 선택할 수 있습니다.
반환되는 값은 boolean값이며 일반적으로 아래와 같이 사용할 수 있습니다.

if(confirm("are you sure?")){
    alert("OK");
}else{
    alert("Cancel");
}

prompt()

이 대화상자에는 확인 취소 이외에도 사용자가 데이터를 입력하는 텍스트 박스가 사용자 입력을 기다립니다.
확인을 누를경우 텍스트 박스의 값을 반환하며, 취소를 선택 할 경우 null을 반환합니다.

var result = prompt("what's your name?", "");
if(result !== null){
    alert("welcome, " + result);
}

기타 특이사항

이런 대화상자들에는 대화상자를 차단할 수 있는 체크박스를 제공합니다.
체크박스를 체크하고 대화상자를 닫을경우 이후 해당 페이지의 시스템 대화상자가 모두 차단됩니다.
차단 여부는 개발자가 알 수 없으니 사용자가 브라우저를 다시 키던지 해야 합니다.

그외 find(), print()메서드를 제공합니다.

window.print(); //인쇄 대화상자 표시
window.find(); //찾기 대화상자 표시

이 메서드는 사용자가 대화상자에서 무엇을 한지 알수없으므로 활용이 어렵고 비동기 적입니다.
대화상자 차단기능의 영향도 받지 않습니다.

location객체

현재 창에 불러온 문서정보, 일반적인 내비게이션 기능을 제공합니다.
window의 프로퍼티이며 document의 프로퍼티입니다.
URL을 파싱해서 몇 가지 조각으로 분리해 각각을 프로퍼티로 저장합니다.

프로퍼티이름 값 예제
hash #contents
host www.test.com:80
hostname www.test.com
href http://www.test.com
pathname /users
port 80
protocol http:
search ?name=kendrick

Query String 확장

search가 들고있는 쿼리스트링 값은 매개변수를 하나씩 분리해서 제공하지 않습니다.
아래 함수를 이용하면 쿼리스트링을 파싱해서 객체를 반환합니다.

function getQueryString(){
    //물음표 뒤의 쿼리스트링 가져오기
    var qs = (location.search.length > 0 ? location.search.substring(1) : "");
    var args = {};
    var items = qs.length ? qs.split("&") : [];
    var item = null;
    var name = null;
    var value = null;

    //for에서 사용
    var i = 0;
    var len = items.length;

    for(i = 0 ; i < len; i++){
        item = items[i].split("=");
        name = decodeURIComponent(item[0]);
        value = decodeURIComponent(item[1]);

        if(name.length){
            args[name] = value;
        }
    }

    return args;
}

위와같이 사용하여 각 매개변수에 쉽게 접근이 가능합니다.

location조작

location객체를 조작해서 페이지를 이동할 수 있습니다.

location.assign("http://sonim1.tistory.com");

//아래 예제는 assign()메서드를 명시적으로 호출한 것과 같습니다.
window.location = "http://sonim1.tistory.com";
location.href = "http://sonim1.tistory.com";

즉시 새 URL로 이동하며 브라우저의 히스토리 스택에 기록이 추가됩니다.

페이지 이동은 이 세가지 방법 중 location.href를 설정하는 방법이 가장 자주 쓰입니다.

location의 프로퍼티를 변경하면 현재 페이지에 영향이 있으며 새 값이 바로 반영됩니다.
(hash는 예외)

이 방법으로 URL이 수정되면 히스토리 스택에 기록이 되서 사요자가 뒤로가기 버튼을 클릭해 이전 페이지로 돌아갈 수 있습니다.

location.has = "#hashchange";
location.search = "?name=name";
location.hostname = "www.naver.com";
location.pathname = "mydir";

replace() 메서드는 히스토리 스택에 기록을 남기지 않습니다.
그런이유로 replace()를 호출해서 페이지 이동할 경우, 뒤로가기를 눌렀을 때 replace호출 하기 전 페이지로 가지 않습니다. 전전 페이지가 히스토리 스택에 존재한다면 그 페이지로 이동합니다.

reload()는 현재 페이지를 다시 불러옵니다.
매개 변수없이 호출하면 페이지를 가능한 가장 효과적인 방법으로 다시 읽습니다.
마지막 요청 이후에 페이지가 바뀌지 않았다면 브러우저 캐시에서 읽어옵니다.
서버에서 읽어오게 할려면 true매개변수를 넘깁니다.

location.reload();        //가능하면 캐시에서 가져옴
location.reload(true);    //항상 서버에서 가져옴

reload()는 코드 마지막에 두는 편이 최선입니다.

navigator 객체

넥스케이프 내비게이터 2에서 도입된 객체입니다.
브라우저를 구별하는 방법의 표준입니다.

브라우저에 따라 객체에서 지원하는 프로퍼티가 다르며 아래 사이트를 통해서 확인 하실 수 있습니다.

W3School - Navigator Object

일반적으로 브라우저 타입을 판단하는데 사용합니다.

플러그인 감지

브라우저에서 특정 플러그인이 설치되어있는지 확인 할 수 있습니다.

//IE에서는 동작하지 않습니다.
function hasPlugin(name){
    name = name.toLowerCase();
    for(var i = 0 ; i < navigator.plugins.length; i++){
        if(navigator.plugins[i].name.toLowerCase().indexOf(name) > -1){
            return true;
        }
    }
    return false;
}

//플래시 찾음
console.log(hasPlugin("Flash"));

plugins의 프로퍼티는 다음과 같습니다.

  • name : 플러그인 이름
  • description : 플러그인 설명
  • filename : 플러그인의 파일 이름
  • length : 플러그인이 처리하는 마임 타입 숫자

IE는 넷스케이프 스타일 객체를 지원하지 않기때문에 플러그인 탐지가 어렵습니다.

처리기 등록

registerContentHandler(), registerProtocolHandler() 메서드가 있습니다.
이 메서드는 웹사이트가 특정한 타입의 정보를 처리 가능하다는걸 나타냅니다.

온라인 RSS리더나 온라인 이메일 애플리케이션은 이 메서드를 사용해서 파일 확장자에 데스크톱 애플리케이션을 연결하듯 자신을 마임 타입 핸들러로 등록합니다.

navigator.registerContentHandler("application/rss+xml", "http://www.somereader.com?feed=%s", "Some Reader");

첫 번째 매개변수는 RSS 피드 마임타입
두 번째 매개변수는 RSS피드 URL을 넘겨받아 처리할 URL
%s는 RSS피드의 URL인데 브라우저가 자동 삽입합니다.
이를 실행하면 RSS 피드에 대한 요청을 보내면 지정한 URL로 이동하고 적절한 방법으로 요청을 처리합니다.

프로토콜도 마찬가지입니다.
mailto, ftp등 프로토콜을 처리합니다.
예제로 기본 이메일 클라이언트로 등록하는 코드는 아래와 같습니다.

navigator.registerProtocolHandler("mailto", "http://www.somemailclient.com?cmd=%s", "Some Mail Client");

screen 객체

window의 프로퍼티이며, 프로그램 관련 용도가 거의 없는 객체입니다.
브라우저 별로 screen객체에서 지원하는 프로퍼티가 다르며 자세한 프로퍼티 정보는 아래 주소에서 확인 가능합니다.

W3Schools - Screen

모바일 장치에서는 조금 다르게 동작하는데
iOS는 항상 세로모드 크기를 반환하는 반면, 안드로이드는 landscape변경 될때마다 screen.width, screen.height값을 업데이트 합니다.

history 객체

창을 연 이후 사용자의 내비게이션 히스토리를 보관합니다.
URL을 몰라도 뒤로가기 앞으로 가기가 가능합니다.
이때는 go() 메서드를 이용합니다.

//뒤로
history.go(-1);

//앞으로
history.go(1);

//두페이지 앞으로
history.go(2);

go 대신 아래 메서드를 사용할 수도 있습니다.

//뒤로
history.back();

//앞으로
history.forward();

length 프로퍼티로 스택의 기록 개수를 확인 할 수도 있습니다.
하지만 앞인지 뒤인지 위치를 구분하지 않고 전체 개수를 반환합니다.

창이나 탭, 프레임에서 연 첫 번째 페이지에서 history.length는 항상 0입니다.
즉 아래와 같이 첫 페이지임을 알 수 있습니다.

if(history.length == 0){
    //사용자가 이 페이지를 처음으로 열었을 때 실행할 코드
}

이 기능을 이용해서 뒤로가기, 앞으로 가기를 할 수 있는 커스텀 버튼을 만들 수도 있겠죠

마치며

BOM요소를 이용해서 창에대한 여러가지 조작을 할 수 있다는 것을 알아보았습니다.
navigator객체를 이용한 클라이언트 탐지에 대해서 자세하게 다루지 않았는데 다음 포스트에서 주제 이기때문에 간단하게 넘어갔습니다. 다음 포스트에서 자세하게 다룰 예정입니다.
좋은 하루 되세요

이 포스트는 프론트엔드 개발자를 위한 자바스크립트(인사이트)에서 발췌한 내용이 포함되어 있습니다.

내용 전문이 아니기 때문에 자세하게 알고싶으신 분은 프론트엔드 개발자를 위한 자바스크립트(인사이트) 서적을 참고 하시길 바랍니다.