티스토리 뷰

소개

최선의 예제에 관한 글을 쓰는것은 다소 트릭으로 느껴지는 작업이다. 독자들 다수에게는, 자신이 지금 읽으려고 하는 글이 매우 명확하며, 실로 일리있는 것으로 보여질 것이다.

그러나, 웹을 둘러보고, 또 지난 수년간 내게 건네어진 다른 개발자들의 코드를 보면서 느낀 것은, 웹에서 실제 사용되는 코드의 세계에는 상당히 비상식적인 일들이 많으며, 또한 “일리있고, 논리적인 일들” 이라는 것은, 일단 당신이 프로젝트에 투입되고 프로젝트의 데드라인이 스물거리며 다가오고 있는 상황에서는 중요성 목록에서 저만치 밑으로 추락해 내려간다는 것이다.

따라서 나는 이 글을 쓰기로 결심했는데, 이 글은 지난 수년간 내가 축적해 온 예제와 조언들의 모음이며, 그들 대부분은 내가 직접 실험해 본 것들이다. 아래에서 조언하는 것들을 가슴에 새기고, 당신의 뇌 한편에 공간을 마련해서 그것들을 저장해두길 바란다 – 필요할 경우, 생각할 필요도 없이 적용할 수 있도록 말이다. 물론, 당신이 내 의견에 반대할 수 있으며, 반대의견은 아주 좋은 것이다 – 지금부터 읽어 나가는 것들에 대해 생각을 하고, 더 좋은 해결책을 찾을 수 있도록 악전고투해야 할 것이다. 어쨌든, 나는 아래의 원칙들을 지킨 결과 꽤 능력있는 개발자가 될 수 있었고, 다른 개발자들이 내 코드를 응용하는것도 더 쉬워졌다..

이 글은 아래와 같은 구조를 가진다:

  • 이름을 불러 주세요 – 쉽고, 짧고, 가독성 있는 변수/함수 이름들
  • 전역변수를 피해라
  • 엄격한 코딩 스타일에 집착하라
  • 필요한 만큼 주석을 달고, 남발하지는 말라
  • 다른 기술을 섞지 말아라
  • 필요하다면 축약표기를 사용해라
  • 모듈화 – 한가지 일에 한가지 함수
  • 점진적으로 발전시켜라
  • 설정과 번역을 받아들여라
  • 과도한 네스팅을 피해라
  • 루프를 최적화해라
  • DOM 접근을 최소화해라
  • 브라우저의 변덕에 굴복하지 말아라
  • 어떤 데이터도 신뢰하지 말아라
  • 자바스크립트로 기능을 추가하되, 너무 많은 내용을 추가하지는 말아라
  • 거인의 어깨 위에서 일해라
  • 개발 코드와 실제 코드는 다르다

이름을 불러 주세요 – 쉽고, 짧고, 가독성 있는 변수/함수 이름들

이것은 별로 머리를 쓸 필요도 없는 일임에도 불구하고, x1, fe2, xbqne 같은 변수이름 – 또는, 그 반대로, incrementorForMainLoopWhichSpansFromTenToTwenty, createNewMemberIfAgeOverTwentyOneAndMoonIsFull 같은 것들을 얼마나 자주 보게 되는지, 끔직할 정도이다.

이러한 것들 중 어떤것도 쓸모있어 보이지 않는다 – 좋은 변수명/함수명은 이해하기 쉽고, 그것이 어떤 일을 하는지 알 수 있게 하는 것이며, 그 이상도, 그 이하도 아니다. 우리가 피해야 할 함정은, 값과 기능을 이름에 합쳐서 넣는 것이다. 예를 들어, 법적으로 음주가 허용되는 나이인가() 와 같은 함수명이 18세 이상인가() 같은 함수명보다 좀더 쓸모있는데, 왜냐하면 나라별로 음주가 허용되는 나이가 다르기 때문이며, 나이에 따라 제한받는 것이 음주뿐만은 아니기 때문이다.

헝가리식 표기법은 받아들일만한 명명법인데(고려할만한 다른 명명법들도 있다), 이것을 사용할 경우, 단순히 “이것이 무엇인가” 뿐만 아니라 “무엇을 기대하는가” 역시 알 수 있기 때문이다.

예를 들어, familyName 같은 변수명을 써야 하고, 그 변수가 문자열이 될 거라면, 이것을 sFamilyName(역주:string(문자열)의 s 이다) 과 같이 쓸 수 있는데, 이것이 헝가리식 표기법이다. member라 이름지은 객체가 있다면 oMember(object(객체)의 o)와 같이 쓸 것이고, 불린 변수인 isLegal은 bIsLegal 과 같이 쓴다. 이와 같이 작성하면 어떤 사람에게는 대단히 많은 정보가 되지만, 다른 사람에게는 추가적인 부담이 되기도 한다 – 이 방법을 사용할지 안할지는 완전히 당신의 자유이다.

영어만을 사용하는 것 역시 괜찮은 방법이다. 프로그램 언어들은 영어로 되어 있고, 따라서 코드의 나머지 부분 역시 영어로 쓰는 것이 논리적이다. 한국인과 슬로베니아인이 작성한 코드를 한동안 디버깅해보았던 경험이 있는데, 그 언어를 모국어로 사용하지 않는 입장에서 그것은 결코 유쾌한 일은 아니었다고 얘기해야겠다..

당신의 코드를 하나의 나래이션으로 간주해보라. 행과 행을 읽어나가면서, 어떤 일이 일어나고 있는 것인지 이해할 수 있다면, 아주 좋다. 만약 당신이 논리적 흐름을 이해하기 위에 별도의 연습장에 그림을 그려야 할 필요가 있다면, 당신의 코드에는 추가적인 작업이 필요한 것이다. 현실 세계와의 비교가 필요하다면, 도스토예프스키의 저작을 읽어보는 것도 괜찮은데 – 나는 14개의 러시아 이름들(게다가 그중 4개는 필명이었다)이 등장하는 페이지에서 갈피를 잡지 못하게 되어 버렸다. 코드를 이런식으로 작성하면 안된다 – 코드가 예술품이 될 지는 모르겠지만, 결코 좋다고는 하기 어렵다.

전역변수를 피해라

전역 변수명과 함수명은, 믿을수 없을 정도로 나쁜 발상이다. 이유는, 하나의 페이지에 포함된 모든 자바스크립트 파일들이 하나의 영역에서 실행되기 때문이다. 만약 코드에 전역 변수명이나 함수명이 있다면, 당신의 스크립트 이후에 실행되는 스크립트에 같은 변수명/함수명이 있을 경우 그것을 덮어써버리게 된다.

전역 명명을 피하는 몇가지 방법들이 있는데, 이제 그것을 하나씩 살펴보겠다. 당신이 3개의 함수와 하나의 변수를 갖고 있다고 가정해보자:

<ol style="padding-left: 2em; ">
  • var current = null;
  • function init(){...}
  • function change(){...}
  • function verify(){...}
  • </ol>

    객체 리터럴을 사용해서, 그것들이 덮어써지는 것을 막을 수 있다:

    <ol style="padding-left: 2em; ">
    
  • var myNameSpace = {
  • current:null
  • init:function){...},
  • change:function){...},
  • verify:function){...}
  • }
  • </ol>

    이렇게 하면 되는데, 단점이 있다 – 함수를 호출하거나 변수의 값을 변경하기 위해서는 항상 주 객체의 이름을 통해야만 한다: init() 는 이제 myNameSpace.init() 이고, current는 이제 myNameSpace.current 이고, 이런 식이다. 이것은 좀 번거롭고 반복적이다.

    익명의 함수로 전체를 감싸서 영역을 보호하는 것이 좀 더 쉽다. 이것은 또한 function name() 의 문법을 name:function) 으로 바꿀 필요도 없다. 이러한 기법을 모듈 패턴이라고 부른다:

    <ol style="padding-left: 2em; ">
    
  • myNameSpace = function(){
  • var current = null;
  • function init(){...}
  • function change(){...}
  • function verify(){...}
  • }();
  • </ol>

    하지만, 여기에도 문제는 있다. 이것들 중 어떤것도, 더이상 외부에서 사용할 수 없게 된다. 외부에서 사용하고자 한다면, 이것들을 return 선언으로 감싸야 한다:

    <ol style="padding-left: 2em; ">
    
  • myNameSpace = function(){
  • var current = null;
  • function verify(){...}
  • return{
  • init:function){...}
  • change:function){...}
  • }
  • }();
  • </ol>

    가만히 보면, 이것은 하나를 다른것과 연결한다는 것, 그리고 문법이 바뀐다는 점에서 첫번째 상자에서의 예제와 흡사해져버렸다. 따라서, 나는 다음(모듈 패턴 드러내기, 라고 읽곤 한다)과 같이 하는 것을 선호하는데:

    <ol style="padding-left: 2em; ">
    
  • myNameSpace = function(){
  • var current = null;
  • function init(){...}
  • function change(){...}
  • function verify(){...}
  • return{
  • init:init
  • change:change
  • }
  • }();
  • </ol>

    속성과 메서드를 반환하는 대신, 그것에 대한 포인터를 반환하고 있다. 이렇게 하면 외부에서 함수를 호출하고 변수에 접근하기 위해 myNameSpace 를 거쳐야 할 필요가 없어진다.

    이것은 또한 함수에 대해 공통적인 별명을 갖게 하는데, 이렇게 함으로서 내부적 링크에서는 길고 서술적인 이름을 사용하면서도, 외부에서 호출할때는 짧은 이름으로 호출할 수 있게끔 한다:

    <ol style="padding-left: 2em; ">
    
  • myNameSpace = function(){
  • var current = null;
  • function init(){...}
  • function change(){...}
  • function verify(){...}
  • return{
  • init:init
  • set:change
  • }
  • }();
  • </ol>

    myNameSpace.set()를 호출하면 change() 메서드가 실행된다.

    함수나 변수 중 어떤것도 외부에서 사용할 필요가 없다면, 간단하게 전체를 또 하나의 괄호로 감싸버리면 되는데, 이렇게 하면 따로 이름을 주지 않아도 실행되게 된다:

    <ol style="padding-left: 2em; ">
    
  • (function(){
  • var current = null;
  • function init(){...}
  • function change(){...}
  • function verify(){...}
  • })();
  • </ol>

    이렇게 함으로서 모든것을 하나의 작은 포장단위로 분리하며 외부에서는 접근할 수 없도록, 하지만 내부에서는 아주 쉽게 변수와 함수를 공유할 수 있게 된다.

    엄격한 코딩 스타일에 집착하라

    자바스크립트 문법에 있어서, 브라우저들은 대단히 관대하다. 하지만 이것을 이유로, 브라우저의 특성에 의존하는 허술한 코드를 작성해서는 안된다.

    당신의 코드의 문법 점수를 측정해보는 가장 쉬운 방법은, 그것을 JSLint — 자바스크립트 유효성 검증 툴에서 실행해보는 것이다. 이것은 문법 경고와 그 의미에 대해 상세한 보고서를 제공한다. 코드를 저장할 때 자동으로 유효성검증을 해 주는 에디터 확장들도 있다(예를 들어, JS Tools for TextMate).

    JSLint 의 결과를 살펴보게 되면 – 개발자인 Douglas Crockford 가 말하듯이 – 상처를 받을 수도 있다. 어쨌든 이것을 통해 전보다 훨씬 나은 코드를 사용하게 되었으므로, 나는 TextMate JS 를 설치해서 내 코드가 JSLint의 정밀한 검사를 받게끔 하고 있다.

    깔끔하고 유효한 코드는, 고쳐야 할 혼동스러운 버그가 적다는 것을 의미하고, 다른 개발자에게 쉽게 전달하는 것을 의미하며, 더 나은 보안을 의미한다. 만약 당신이 코드를 실행시키기 위해 특정한 핵에 의존한다면, 같은 핵을 사용하는 사람에게는 보안 노출이 되어 버린다. 또한, 이러한 핵 들이 브라우저에 의해 계속해서 고쳐지고 있기 때문에, 당신의 코드가 브라우저의 다음 버전에서는 작동을 중지할 수도 있다.

    유효한 코드는 또한 스크립트를 통해 다른 형식으로 변경할 수 있다 – 핵을 이용한 코드를 변경하기 위해서는 수작업이 필요하다.

    필요한 만큼 주석을 달고, 남발하지는 말라

    주석은 다른 개발자들(그리고, 몇달동안 다른 작업을 하다가 이 코드를 다시 보게 될 당신 역시)에게 보내는 메세지이다. 주석을 쓸 것인지 말 것인지에 대해 수년간 격한 논쟁들이 있어 왔는데, 여기에 있어서의 주된 논점은, 좋은 코드는 그 자신을 잘 설명한다는 것이다.

    이러한 논점에서 내가 흠으로 지목하는 것은, 설명이란 것은 극히 목적적인 것이라는 것이다 – 모든 개발자들이 이 코드가 무슨 일을 하고 있는지 정확히 동일한 이해를 가질 것이라고 예상할 수는 없다.

    주석을 올바르게 추가한다면, 아무도 피해를 입지 않는다. 이 글의 마지막 부분에서 이 문제를 다시 다루겠지만, 당신이 코드에 남겨둔 주석을 최종 사용자가 본다면 그것은 뭔가 잘못된 것이다.

    다시한번 말하자면 문제는 중용 이다. 중요한 할 말이 있다면 주석을 남기고, 주석을 남길 것이라면 /* */ 속에 남겨라. //를 이용하는 한줄의 주석은, 만약 사람들이 당신의 주석을 제거하지 않고 코드를 최소화(역주:공백문자를 제거하여 코드를 압축할 때 줄바꿈을 제거하는 경우가 많다)할 경우 문제를 일으킬 소지가 있으며, 일반적으로 말해서 융통성이 없다

    만약 당신이 코드의 일부를 나중에 사용할 목적으로, 혹은 디버그 할 목적으로 주석처리한다면, 이 팁이 대단히 유용할 것이다:

    <ol style="padding-left: 2em; ">
    
  • module = function(){
  • var current = null;
  • function init(){
  • };
  • /*
  • function show(){
  • current = 1;
  • };
  • function hide(){
  • show();
  • };
  • */
  • return{init:initshow:showcurrent:current
  • }();
  • </ol>

    주석을 닫는 */ 앞에 //를 추가하면, /*앞에 /를 추가하거나 뺌으로서, 아주 쉽게 스크립트 블럭 전체를 주석화하거나, 동작하는 코드로 바꿀 수 있다:

    <ol style="padding-left: 2em; ">
    
  • module = function(){
  • var current = null;
  • function init(){
  • };
  • /*
  • function show(){
  • current = 1;
  • };
  • function hide(){
  • show();
  • };
  • // */
  • return{init:initshow:showcurrent:current
  • }();
  • </ol>

    위와 같이 만들어진 코드에서는, 주석을 여는 /* 앞에 하나의 /를 추가함으로서 이어지는 여러 줄의 주석을 두개의 한줄 주석으로 만들어서, “주석을 취소하는” 효과가 있고, /* 와 */ 사이에 있는 코드가 실행되도록 만든다. 맨 앞의 /를 제거해서 전체 블럭을 다시 쉽게 주석처리 할 수 있다.

    큰 규모의 응용 프로그램에서는, 주석을 JavaDoc style로 문서화하면 대단히 많은 이점이 있다 – 코드를 작성함으로서, 전반적인 문서의 씨를 뿌리는 것이다. 야후! 의 인터페이스 라이브러리가 성공적이었던 배경에는 이것이 포함되어 있으며, 당신의 프로그램을 위해 동일한 문서를 만드는 도구도 있다. 자바스크립트에 좀 더 익숙해지기 전에는, 이러한 것에 대해 크게 염려할 필요는 없다 – 자바독 을 이 글에서 언급한 것은 글의 완결성을 위한 것이다.

    다른 기술을 섞지 말아라

    당신이 문서 내에서 필요로 하는 모든 것들을 자바스크립트와 DOM로 만들어 낼 수 있기는 하지만, 이것이 가장 효율적인 방법은 아니다. 다음의 코드는, mandatory 클래스를 가진 모든 인풋 필드에 대해, 그 내용이 비어 있다면 붉은색 테두리를 그린다..

    <ol style="padding-left: 2em; ">
    
  • var f = document.getElementById('mainform');
  • var inputs = f.getElementsByTagName('input');
  • for(var i=0,j=inputs.length;i
  • </ol>

    이것은 동작한다. 하지만 이후 당신이 이러한 스타일(1px solid red)에 변경을 가하고 싶다면, 당신은 자바스크립트를 검색해서 필요한 위치를 찾고, 그것을 변경해야 할 것이다. 변화가 복잡할수록 수정작업이 힘들 것이다. 또한, 모든 자바스크립트 개발자들이 CSS에 능숙하거나 흥미를 갖고 있는 것은 아니므로, 결과물을 손에 쥐기 위해서는 많은 작업이 필요할 것이다. error 라는 이름의 클래스를 하나 추가함으로서, 스타일 정보가 CSS – 좀 더 적합한 위치 – 내에 있도록 할 수 있다:

    <ol style="padding-left: 2em; ">
    
  • var f = document.getElementById('mainform');
  • var inputs = f.getElementsByTagName('input');
  • for(var i=0,j=inputs.length;i
  • </ol>

    1

    CSS가 문서 전체에 걸쳐 캐스케이드 되도록 설계되었기 때문에, 이러한 방식이 훨씬 효율적이다. 예를 들어, 당신이 특정 클래스를 가진 모든 div 요소를 숨기고 싶다고 하자. 이렇게 하기 위해서 모든 div 들에 대해 루프를 돌리고, 클래스를 체크해서 스타일 정보를 변경할 수 있다. 최근의 브라우저에서는 CSS 선택자를 이용해서 스타일 정보를 변경할 수 있다. 이것을 하기 위해 가장 쉬운 방법은 자바스크립트를 이용해서 부모 요소에 클래스를 추가하고, CSS의 element.triggerclass div.selectorclass{} 라인에 걸쳐 다른 문법을 적용하는 것이긴 하다. div를 숨기는 실제 작업은 CSS디자이너에게 맡기는 것이 좋다 – 그는 그렇게 하기 위한 가장 좋은 방법을 알고 있을 것이기 때문이다.

     

     

    필요하다면 축약표기를 사용해라

    축약표기는 변칙적인 주제이다: 한가지 면에서는 당신의 코드 길이를 줄여 주지만, 다른 면에서는 당신의 코드를 넘겨받은 개발자들이 그 축약의 의미를 잘 파악하기 어려울 수 있기 때문이다. 사용할 수 있는(사용해야 하는) 작은 목록을 보여주겠다.

    아마도, 당신이 자바스크립트에서 사용하는 것 중 가장 범용적인 것은 객체이다. 구식 방법에서는 객체를 다음과 같이 표기했는데:

    <ol style="padding-left: 2em; ">
    
  • var cow = new Object();
  • cow.colour = 'brown';
  • cow.commonQuestion = 'What now?';
  • cow.moo = function(){
  • console.log('moo');
  • }
  • cow.feet = 4;
  • cow.accordingToLarson = 'will take over the world';
  • </ol>

    그러나, 이렇게 사용한다면 객체의 이름을 각각의 속성이나 메서드에 모두 사용해야 하고, 이것은 좀 번거로울 것이다. 대신, 다음의 구조 – 객체 리터럴이라고 불린다 – 를 사용하는 것이 좀 더 낫다:

    <ol style="padding-left: 2em; ">
    
  • var cow = {
  • colour:'brown',
  • commonQuestion:'What now?',
  • moo:function){
  • console.log('moo');
  • },
  • feet:4,
  • accordingToLarson:'will take over the world'
  • };
  • </ol>

    자바스크립트에서 배열은 다소 혼란을 일으킨다. 아마도, 많은 수의 스크립트에서 다음과 같이 배열을 정의한 것을 보게 될 것인데:

    <ol style="padding-left: 2em; ">
    
  • var aweSomeBands = new Array();
  • aweSomeBands[0] = 'Bad Religion';
  • aweSomeBands[1] = 'Dropkick Murphys';
  • aweSomeBands[2] = 'Flogging Molly';
  • aweSomeBands[3] = 'Red Hot Chili Peppers';
  • aweSomeBands[4] = 'Pornophonique';
  • </ol>

    의미없는 반복이다. [ ]를 사용하여 축약하면 훨씬 빠르게 작성할 수 있다:

    <ol style="padding-left: 2em; ">
    
  • var aweSomeBands = [
  • 'Bad Religion',
  • 'Dropkick Murphys',
  • 'Flogging Molly',
  • 'Red Hot Chili Peppers',
  • 'Pornophonique'
  • ];
  • </ol>

    몇몇 교습서에서 “연결된 배열associative array” 이라는 개념을 보게 될 것이다. 이것은 잘못된 표현인데, 인덱스가 아니라 명명된 속성을 가진 배열은 사실 객체이며 그렇게 정의되어야 하기 때문이다.

    조건들은 “3벌식 표기법ternary notation” 으로 축약할 수 있다. 예를 들어, 다음의 구조는 다른 변수의 값에 따라 1 또는 -1로 변수의 값을 정의하고 있다:

    <ol style="padding-left: 2em; ">
    
  • var direction;
  • if(x > 100){
  • direction = 1;
  • } else {
  • direction = -1;
  • }
  • </ol>

    이것을 한줄로 쓴다면:

    <ol style="padding-left: 2em; ">
    
  • var direction = (x > 100) ? 1 : -1;
  • </ol>

    물음표 앞에 있는것은 전부 조건이다. 바로 이어서 나오는 값은 조건이 참 일 경우이며, 콜론 뒤에 나오는 값은 조건이 거짓인 경우이다. 3벌식 표기법은 중첩될 수 있는데, 가독성을 생각해서 나는 그렇게 하지 않는다.

    자바스크립트에서 종종 벌어지는 다른 상황은, 변수가 정의되지 않았을 경우 초기값을 주는 것이다. 다음과 같이:

    <ol style="padding-left: 2em; ">
    
  • if(v){
  • var x = v;
  • } else {
  • var x = 10;
  • }
  • </ol>

    ||를 사용해서 축약표기할 수 있다(역주:자바스크립트에서 or 의 의미로 사용한다):

    <ol style="padding-left: 2em; ">
    
  • var x = v || 10;
  • </ol>

    v가 정의되지 않았을 경우 x에 10을 대입한다 – 아주 간단하다.

    모듈화 – 한가지 일에 한가지 함수

    이것은 일반적인 프로그래밍 원칙 중에서 가장 좋은 것이다. 한가지 일을 하는 함수를 작성함으로서, 다른 개발자들이 디버그하거나 코드를 수정할 때 어떤 코드블럭이 어떤 기능을 수행하는지 파악하기 위해서 코드 전체를 들여다 볼 필요가 없어지게 된다.

    이러한 원칙은 일반적인 작업을 위한 도우미 함수를 만들때도 마찬가지이다. 여러가지 다른 함수들에 걸쳐서 같은 작업을 반복한다면, 좀 더 보편적인 도우미 함수를 만들고 그 기능이 필요한 것에서 그것을 재활용하는것이 낫다.

    또한, 입력과 출력을 분리하는 것 역시 함수 자체 내에서 허우적대는것보다는 훨씬 낫다. 예를 들어, 새로운 링크를 만들어내는 도우미 함수를 만들고 싶다고 하자. 이런식으로 할수도 있는데:

    <ol style="padding-left: 2em; ">
    
  • function addLink(text,url,parentElement){
  • var newLink = document.createElement('a');
  • newLink.setAttribute('href',url);
  • newLink.appendChild(document.createTextNode(text));
  • parentElement.appendChild(newLink);
  • }
  • </ol>

    물론 이것은 동작한다. 하지만, 링크를 적용하는 요소에 따라서 다른 속성을 추가해야 할 수도 있다. 예를 들어:

    <ol style="padding-left: 2em; ">
    
  • function addLink(text,url,parentElement){
  • var newLink = document.createElement('a');
  • newLink.setAttribute('href',url);
  • newLink.appendChild(document.createTextNode(text));
  • if(parentElement.id === 'menu'){
  • newLink.className = 'menu-item';
  • }
  • if(url.indexOf('mailto:')!==-1){
  • newLink.className = 'mail';
  • }
  • parentElement.appendChild(newLink);
  • }
  • </ol>

    이렇게 되면 함수는 더욱 고정적이 되고, 다른 상황에 적용하기가 힘들어진다. 이것을 더 간결하게 하려면, 링크를 반환하는 함수를 만들고, 다른 경우들은 그것을 필요로 하는 주 함수에서 처리하는 것이다. 즉, addLink() 함수를 좀 더 범용적인 createLink() 함수로 만든다:

    <ol style="padding-left: 2em; ">
    
  • function createLink(text,url){
  • var newLink = document.createElement('a');
  • newLink.setAttribute('href',url);
  • newLink.appendChild(document.createTextNode(text));
  • return newLink;
  • }
  • function createMenu(){
  • var menu = document.getElementById('menu');
  • var items = [
  • {t:'Home',u:'index.html'},
  • {t:'Sales',u:'sales.html'},
  • {t:'Contact',u:'contact.html'}
  • ];
  • for(var i=0;i
  • </ol>

    모든 함수들이 단 한가지의 일만 처리하도록 함으로서, 주 함수인 init() 에서 그것들 모두를 포함하도록 할 수 있다. 이렇게 하면 의존성을 체크하기 위해 문서 전체를 뒤적거릴 필요 없이 프로그램의 기능을 쉽게 바꾸고, 특정 기능을 제거할 수 있다.

     

    점진적으로 발전시켜라

    점진적 발전, 개발의 원칙으로서의 이 개념은 점진적 발전과 우아한 후퇴에서 상세히 다루어지고 있다. 요점만 말한다면, 당신이 해야 하는 일은 특정 기술에 의존하지 않고 동작하는 코드를 작성하는 것이다. 자바스크립트의 경우로 한정한다면, 스크립트가 불가능한 경우(예를 들어 블랙베리, 또는 광신적인 보안 정책들 때문에)라 해도 사용자들이 자신의 목적을 달성할 수 있어야 한다는 것이다. 그들이 켤 수 없는, 혹은 켜고 싶어하지 않는 스크립트가 없다고 해서 그들의 목적이 거부되어서는 안된다.

    자바스크립트 없이도 간단하게 해결할 수 있는 문제를 해결하기 위해, 복잡하게 이리저리 꼬인 거대한 규모의 스크립트를 얼마나 자주 만들게 될지 당신이 미리 안다면 무척 놀랄 것이다. 내가 만났던 한가지 예 는, 다양한 데이터: 웹, 이미지, 뉴스 등 를 검색하는 검색 페이지에서였다.

    첫 버전에서는, 서로 다른 검색옵션들은 action 속성을 덮어쓰게끔 만들어진 각각의 링크를 통해서, 서버의 서로 다른 스크립트에 접근함으로서 검색하는 방식이었다.

    문제는 이것이다: 자바스크립트가 꺼져 있는 경우에도 링크는 여전히 보이지만, 모든 검색은 기본값인 웹 검색의 결과를 가져왔으며 전혀 바뀌지 않았다. 해결책은 간단했다: 링크 대신 라디오 버튼을 사용해서 검색 옵션을 선택하도록 했고, 복잡한 작업은 서버에서 이루어지도록 한 것이다.

    이렇게 함으로서 모든 경우에 검색이 정확히 작동했을 뿐만 아니라, 사람들이 사용한 검색옵션 선호도를 통계내는것도 쉬워졌다. 정확한 HTML 구조를 사용함으로서 우리는 폼 액션을 변경하기 위한 자바스크립트와 검색옵션 통계를 위한 스크립트 모두를 없애버렸고, 환경과 무관하게 모든 사람이 검색을 사용할 수 있도록 했다.

    설정과 번역을 받아들여라

    당신의 코드를 유지보수하기 쉽게끔 하고 또한 간결하게 만드는 가장 성공적인 팁 중 하나는, 시간이 흐름에 따라 변할 것으로 생각되는 모든 것을 포함하는 설정 객체를 만들어 두라는 것이다. 이러한 설정 객체에는 당신이 만들어내는 요소들에 사용될 텍스트(버튼의 값과 이미지를 위한 대체텍스트 역시 포함된다), CSS 클래스, ID, 그리고 인터페이스에서 사용하는 일반적인 매개변수 등이 포함된다.

    예를 들어, Easy YouTube player 는 다음과 같은 설정 객체를 포함하고 있다:

    <ol style="padding-left: 2em; ">
    
  • /*
  • This is the configuration of the player. Most likely you will
  • never have to change anything here, but it is good to be able
  • to, isn't it?
  • */
  • config = {
  • CSS:{
  • /*
  • IDs used in the document. The script will get access to
  • the different elements of the player with these IDs, so
  • if you change them in the HTML below, make sure to also
  • change the name here!
  • */
  • IDs:{
  • container:'eytp-maincontainer',
  • canvas:'eytp-playercanvas',
  • player:'eytp-player',
  • controls:'eytp-controls',
  • volumeField:'eytp-volume',
  • volumeBar:'eytp-volumebar',
  • playerForm:'eytp-playerform',
  • urlField:'eytp-url',
  • sizeControl:'eytp-sizecontrol',
  • searchField:'eytp-searchfield',
  • searchForm:'eytp-search',
  • searchOutput:'eytp-searchoutput'
  • /*
  • Notice there should never be a comma after the last
  • entry in the list as otherwise MSIE will throw a fit!
  • */
  • },
  • /*
  • These are the names of the CSS classes, the player adds
  • dynamically to the volume bar in certain
  • situations.
  • */
  • classes:{
  • maxvolume:'maxed',
  • disabled:'disabled'
  • /*
  • Notice there should never be a comma after the last
  • entry in the list as otherwise MSIE will throw a fit!
  • */
  • }
  • },
  • /*
  • That is the end of the CSS definitions, from here on
  • you can change settings of the player itself.
  • */
  • application:{
  • /*
  • The YouTube API base URL. This changed during development of this,
  • so I thought it useful to make it a parameter.
  • */
  • youtubeAPI:'http://gdata.youtube.com/apiplayer/cl.swf',
  • /*
  • The YouTube Developer key,
  • please replace this with your own when you host the player!!!!!
  • */
  • devkey:'AI39si7d...Y9fu_cQ',
  • /*
  • The volume increase/decrease in percent and the volume message
  • shown in a hidden form field (for screen readers). The $x in the
  • message will be replaced with the real value.
  • */
  • volumeChange:10,
  • volumeMessage:'volume $x percent',
  • /*
  • Amount of search results and the error message should there
  • be no reults.
  • */
  • searchResults:6,
  • loadingMessage:'Searching, please wait',
  • noVideosFoundMessage:'No videos found : (',
  • /*
  • Amount of seconds to repeat when the user hits the rewind
  • button.
  • */
  • secondsToRepeat:10,
  • /*
  • Movie dimensions.
  • */
  • movieWidth:400,
  • movieHeight:300
  • /*
  • Notice there should never be a comma after the last
  • entry in the list as otherwise MSIE will throw a fit!
  • */
  • }
  • }
  • </ol>

    이것을 모듈 패턴의 일부로 포함시키고 공개한다면, 이행자들이 당신의 모듈을 초기화initialize하지 않고도 필요한 부분만을 덮어 쓸 수 있도록 할 수 있을 것이다.

    코드의 관리가 간단하게끔 하는 것은 정말로 중요한 일이다. 이후 당신의 코드를 수정하는 사람이, 수정할 곳을 찾기 위해 코드 전체를 읽는 일은 피해야 한다. 만약 이것이 분명하지 않다면, 당신의 코드는 완전히 갈아엎어지거나, 아니면 난도질당하거나, 둘 중 하나이다. 이렇게 난도질당한 코드는 업그레이드가 필요한 시점에서 업그레이드를 할 수 없고, 재사용성은 사라진다.

    과도한 중첩을 피해라

    코드를 네스팅하는 것은 그 로직을 설명하고, 읽기 편하게 만들지만, 너무 과하게 중첩시킨다면 당신이 하고자 하는 것을 따라가기 힘들게 만든다. 코드를 읽는 사람이 횡스크롤을 하거나, 그들의 코드 에디터가 긴 줄을 자동 줄바꿈하게끔 하는 것(들여쓰기에 들인 당신의 노력은 수포로 돌아간다)은 피해야 한다..

    네스팅의 다른 문제는 변수명과 루프이다. 일반적으로 첫번째 루프를, 증가 식별자로 i를 사용하여 시작하고, j, k, l 하는 식으로 네스팅시킬 것이다. 이렇게 한다면 순식간에 코드가 망가지는데, 이렇게:

    <ol style="padding-left: 2em; ">
    
  • function renderProfiles(o){
  • var out = document.getElementById(‘profiles’);
  • for(var i=0;i
  • </ol>

    범용적인 변수명, ul 과 li 를 사용하고 있기 때문에, 네스트된 목록 아이템에 대해서 nestedul과 datali를 필요로 한다. 목록의 네스팅이 더 필요하다면, 더 많은 변수명이 필요하고, 더 네스팅하면 더 많은 변수명, 더, 더. 이 경우, 각각의 객체에 대해 네스팅된 목록을 만들어내는 작업을 별도의 함수로 분리하고, 필요할 때에 호출하는 것이 훨씬 합리적이다. 이렇게 한다면 또한 루프 속에 또 루프를 만드는 일을 피할 수 있다. addMemberData() 함수는 극히 범용적이며, 필요할 때 아무때나 사용할 수 있다. 이러한 생각을 실행에 옮겨서, 코드를 다음과 같이 재작성할 것이다:

    <ol style="padding-left: 2em; ">
    
  • function renderProfiles(o){
  • var out = document.getElementById(‘profiles’);
  • for(var i=0;i
  • </ol>

     

     

    반복을 최적화하라

    정확하게 사용하지 않는다면, 루프는 상당히 느려진다. 가장 많이 범하는 실수 중의 하나는, 매 루프마다 배열의 길이를 다시 읽어들이는 것이다:

    <ol style="padding-left: 2em; ">
    
  • var names = ['George','Ringo','Paul','John'];
  • for(var i=0;i
  • </ol>

    이렇게 한다면, 루프가 돌 때마다 자바스크립트는 배열의 길이를 읽어와야 한다. 배열의 길이를 별도의 변수에 저장함으로서 이것을 피할 수 있다:

    <ol style="padding-left: 2em; ">
    
  • var names = ['George','Ringo','Paul','John'];
  • var all = names.length;
  • for(var i=0;i
  • </ol>

    더 짧게 쓴다면, 루프 앞의 선언에서 두번째 변수를 만들어내는 방법이 있다:

    <ol style="padding-left: 2em; ">
    
  • var names = ['George','Ringo','Paul','John'];
  • for(var i=0,j=names.length;i
  • </ol>

    한가지 더 확실히 해야 하는 것은, 복잡한 계산은 루프 밖에서 처리한다는 것이다. 이것은 정규식 – 그리고 더 중요한 것은 – DOM 조작을 포함한다. DOM 노드 생성은 루프 안에서 할 수 있지만, 문서에 삽입하는 것은 삼가하는것이 좋다. DOM에 관한 중요한 규칙은 다음 섹션에서 볼 수 있다.

     

     

     

    DOM 접근을 최소화하라

    브라우저에서 DOM에 접근하는 것은 상당한 비용을 소모하는 일이다. DOM은 대단히 복잡한 API이고, 브라우저에서 표현할 때 많은 시간을 소모할 수 있다. 당신의 컴퓨터가 이미 무언가를 하면서 대부분의 자원을 소모한 상태에서 복잡한 웹 응용프로그램을 실행시켜 보라 – 변화가 늦게 반영되거나, 절반만 보여지거나 할 것이다.

    당신의 코드가 속도를 유지하고, 브라우저를 느리게 하지 않기 위해서, DOM 접근을 할 수 있는 최소한으로 줄여라. 계속해서 요소를 만들고 적용해대는 대신, 문자열을 DOM 요소로 바꾸는 함수를 만든 뒤, 이 함수를 마지막에 호출함으로서, 브라우저가 화면을 표현하는 과정을 여러번 방해하지 말고 한번에 끝내도록 하라.

    브라우저의 변덕에 굴복하지 말라

    특정 브라우저에 기반하는 코드를 쓰는 것은, 당신의 코드를 관리하기가 어렵게 되고, 아주 빨리 낡아버리게 하는 일이다. 불보듯 뻔하다. 웹을 돌아본다면, 아주 많은 숫자의 스크립트가 특정 브라우저를 가정하여 만들어졌고, 새로운 버전이 나오거나 다른 브라우저에서 실행할 때 즉시 실행을 멈춰버리는 것을 발견하게 될 것이다.

    시간과 노력의 낭비이다. 우리가 진행하고 있는 과정에서 개요를 설명하고 있는, 표준에 기반해서 코드를 작성해야 한다. 웹은 모두를 위한 것이며, 설정하는 일에 능숙한 소수 엘리트를 위한 것이 아니다. 브라우저 시장은 급변하므로, (특정 브라우저를 위한 코드를 작성했다면) 계속해서 코드를 수정해야 할 것이다. 이것은 재미있지도, 효율적이지도 않다.

    만약 어떤 마법같은 코드를 작성했는데 이것이 특정 브라우저에서만 동작하고, 게다가 당신이 정말로 그것을 필요로 한다면, 그 스크립트를 따로 분리해내고 브라우저 이름과 버전을 사용해서 명명하는 것이 좋다. 이렇게 함으로서, 언젠가 그 브라우저를 사용할 수 없게 될 경우 그 기능을 쉽고 빠르게 제거할 수 있다.

    어떤 데이터도 신뢰하지 말아라

    코드와 데이터의 보안에 대해, 항상 염두에 두어야 할 요점은 어떠한 데이터도 신뢰해선 안된다는 것이다. 당신의 시스템을 해킹하고자 하는 악의적인 사용자만을 말하는 것이 아니다 : 이것은 평범한 사용성에서 출발하는 이야기이다. 사용자는 부정확한 데이터를 입력한다. 이것은 불변의 사실이다. 그들이 멍청해서가 아니라, 그들이 바쁘고, 다른 일에 주의를 분산해야 하고, 혹은 당신의 설명이 그들을 혼란시켰을 수도 있다. 예를 들어, 나는 지금 막 호텔을 예약했는데, 숫자를 잘못 입력하는 바람에 6일이 아닌 1개월을 예약해 버렸다… 난 내가 상당히 영리하다고 믿고 있는데 말이다.

    요약하자면, 당신의 시스템으로 전송되는 모든 데이터는 깔끔해야 하고, 정확히 당신이 원하는 것이어야 한다. 특히, 서버에서 전송받은 URL의 일부를 추려내서 매개변수로 사용하는 경우에 이것은 정말 중요하다. 자바스크립트에서는, 함수로 보내어지는 매개변수들의 타입을 체크(typeof 키워드를 이용해서)해보는 것이 아주 중요하다. 이어지는 예제는, 만약 members가 배열이 아닌 경우(예를 들자면, 배열이 아닌 문자열이 입력되었을 경우 문자열의 각 문자를 나열하는 목록을 만들 것이다)에는 에러이다:

    <ol style="padding-left: 2em; ">
    
  • function buildMemberList(members){
  • var all = members.length;
  • var ul = document.createElement('ul');
  • for(var i=0;i
  • </ol>

    이것이 작동하게끔 하기 위해, members 의 타입을 체크해서 그것이 배열인지 확인해야 한다:

    <ol style="padding-left: 2em; ">
    
  • function buildMemberList(members){
  • if(typeof members === 'object' &&
  • typeof members.slice === 'function'){
  • var all = members.length;
  • var ul = document.createElement('ul');
  • for(var i=0;i
  • </ol>

    배열은 좀 혼동스러운데, 자신의 타입을 객체라고 반환하기 때문이다. 배열임을 확인하기 위해서, 배열만이 가지고 있는 메서드로 체크해보면 된다.

    보안에서 거리가 먼 다른 하나는, DOM 에서 정보를 읽고 그것을 비교해보는 일 없이 사용하는 것이다. 예를 들어, 나는 자바스크립트 기능을 멈추게 한 코드를 디버그해본 적이 있었다. 코드가 에러를 만든 이유-나와는 관계가 없었는데-는 사용자 이름을 페이지 요소의 innerHTML 에서 읽어내고는 그것을 매개변수 삼아서 함수를 호출한 것이다. 사용자 이름은 UTF-8 문자셋의 어떤 문자라도 올 수 있는데, 이것은 큰따옴표와 작은따옴표 역시 허용된다는 뜻이다. 이러한 문자가 삽입됨으로서 문자열은 종료될 것이고, 그 뒤에 따라오는 나머지는 에러를 만들어내는 데이터가 된다. 추가하자면, FireBug 나 Opera Dragonfly 같은 툴을 이용해서 HTML을 변경할 줄 아는 사용자는 사용자 이름을 어떤 것으로든 변경할 수 있고, 당신의 함수에 무엇이라도 주입할 수 있다

    유효성 검증을 클라이언트사이드에서만 수행하는 입력양식들에서 같은 일이 벌어질 수 있다. 난 이전에 사용할 수 없는 이메일 주소에 등록한 일이 있었는데, 다른 옵션을 제공하는 선택을 다시 써버렸기 때문이다. 입력양식은 서버에서 검증되지 않았고, 에러를 내지 않은 채 진행되어버렸다.

    DOM 과 연동할 경우, 접근해서 변경하고자 하는 요소가 실제로 존재하는지, 그리고 그 요소가 어떤 요소이길 기대하는지 점검하도록 하라. 그렇지 않으면, 당신의 코드는 동작하지 않거나 화면을 이상하게 표현하는 버그를 초래할 것이다.

     

     

    자바스크립트로 기능을 추가하되, 너무 많은 내용을 추가하지는 말아라

    다른 예제들에서 봐 왔겠지만, 자바스크립트를 이용해서 방대한 양의 HTML을 구축하는 것은 매우 불안하고, 망쳐지기 쉬운 작업이다. 특히 IE에서, 그것이 아직 페이지를 로딩중일때 innerHTML 을 이용해서 문서를 수정하고 조작하는 것은 온갖 종류의 골칫거리들로 스스로 뛰어드는 것이다(이러한 비참함과 비통함의 이야기를 보고 싶다면, Google에서 “operation aborted error”를 검색해보면 된다).

    페이지 관리라는 관점에서 본다 해도, 대량의 마크업을 자바스크립트로 하는 것은 끔찍한 발상이다. 모든 관리자가 당신과 같은 수준의 기술을 가진 것이 아니며, 당신의 코드를 완전히 망쳐 놓을 가능성이 있다.

    내가 자바스크립트에 대단히 의존적인 프로그램을 개발해야 했었을 때, HTML 템플릿을 미리 만들어두고 이 템플릿을 Ajax로 불러오는것이 훨씬 합리적인 것을 발견했다. 이렇게 하면, 관리자들이 HTML 구조와, 무엇보다도 중요한, 텍스트(내용)를 당신의 자바스크립트 코드와 충돌하는 일 없이 수정할 수 있다. 필요한 것은, 그들에게 어떤 ID 속성들이 필요한 것이며, 반드시 그 순서대로 있어야 하는 HTML 구조에 대해서 일러두는 것 뿐이다. 이러한 설명은 HTML 내부 주석을 통해서 할 수 있다(그리고 그 템플릿을 실제로 로딩할때는 주석을 없앤다. the Easy YouTube template의 소스를 참고해서 예제로 삼으면 될 것이다).

    아래의 스크립트에서는, 정확한 HTML 컨테이너가 있을때 템플릿을 불러오고, 그 후에 setupPlayer() 메서드에 있는 이벤트 핸들러들을 할당한다:

    <ol style="padding-left: 2em; ">
    
  • var playercontainer = document.getElementById('easyyoutubeplayer');
  • if(playercontainer){
  • ajax('template.html');
  • };
  • function ajax(url){
  • var request;
  • try{
  • request = new XMLHttpRequest();
  • }catch(error){
  • try{
  • request = new ActiveXObject("Microsoft.XMLHTTP");
  • }catch(error){
  • return true;
  • }
  • }
  • request.open('get',url,true);
  • request.onreadystatechange = function(){
  • if(request.readyState == 4){
  • if(request.status){
  • if(request.status === 200 || request.status === 304){
  • if(url === 'template.html'){
  • setupPlayer(request.responseText);
  • }
  • }
  • }else{
  • alert('Error: Could not find template...');
  • }
  • }
  • };
  • request.setRequestHeader('If-Modified-Since','Wed, 05 Apr 2006 00:00:00 GMT');
  • request.send(null);
  • };
  • </ol>

    이렇게 함으로서, 사람들은 자바스크립트 코드를 변경할 필요 없이 플레이어를 자국어로 번역하고, 그들이 원하는 방식으로 바꿀 수 있다.

    거인의 어깨 위에서 일하라

    지난 수년간, 자바스크립트 라이브러리들과 프레임워크들이 웹 개발 시장을 지배하기 시작하게 된 것에는 어떠한 반대도 없었다. 그리고 이것은 결코 나쁜 것이 아니다 – 정확히 사용한다면 말이다. 좋은 자바스크립트 라이브러리들은 한가지 일을 하고, 그 한가지 일만 한다: 브라우저간 비호환성을 메꾸고 브라우저 지원에 뚫린 구멍을 메워줌으로서 개발자로서의 당신의 삶을 편안하게 해준다. 이러한 라이브러리들은 예측 가능하고 기능적인 기반을 제공한다.

    처음 배울때는 라이브러리 없이 해보는 것이 좋은데, 어떤 일이 일어나고 있으며 무엇이 문제인지 알 수 있기 때문이다. 하지만 웹 사이트 개발을 실제로 시작한 후에는 라이브러리를 쓰는 것이 좋다. 직접 처리해야 할 문제들의 숫자가 줄어들고, 버그의 범위를 한정지을 수 있다 – 예측하기 힘든 브라우저 문제가 아니다.

    내가 개인적으로 좋아하는 것은 the Yahoo User Interface library (YUI) 이며, jQueryDojo,Prototype 들도 괜찮다. 하지만 그밖에도 수많은 좋은 라이브러리들이 있으므로, 당신에게 맞고 당신이 하는 일을 가장 잘 도와줄 것을 찾아야 할 것이다.

    라이브러리 사이의 충돌도 있으므로, 하나의 프로젝트에 여러개의 라이브러리를 사용하는 것은 좋지 않다. 이렇게 하게 되면 또다른 불필요한 복잡함과 관리의 문제를 낳게 될 것이다.

    개발 코드와 실제 코드는 다르다

    마지막으로 말하고 싶은 것은 자바스크립트 자체에 관한 이야기는 아니며, 이후 당신의 개발 방향 전체와 관련된 이야기이다. 자바스크립트에 가하는 약간의 수정으로도 웹 프로그램의 성능과 기능성에 즉각적인 변화를 가져오므로, 관리의 편의성은 제껴두고 할 수 있는만큼 코드를 최적화하고 싶은 심한 유혹을 느끼게 될 것이다.

    자바스크립트의 성능을 극적으로 끌어올릴 수 있는 꽤 괜찮은 트릭들이 많이 있다. 그들 중 대부분은, 코드를 이해하고 관리하기 어렵게 된다는 단점을 가지고 있다.

    안전하고, 잘 동작하는 자바스크립트를 작성하기 위해, 우리는 이러한 순환을 끊고, 동료 개발자가 아니라 컴퓨터를 위해 코드를 최적화하는 습관을 버려야 한다. 다른 언어들에서는 아주 흔한 일이지만 자바스크립트 프로그래머들 사이에서는 아직 많이 알려지지 않은 것이다. 완성된 스크립트에서는 공백을 지우고, 주석을 없애고, 배열 참조를 통해 문자열을 치환하고(IE가 모든 문자열에 대해 문자열 객체를 만드는 – 심지어 조건절에서도 – 것을 방지하기 위해), 브라우저에서 자바스크립트가 날아다니게 할 수 있는 모든 작은 튜닝들을 행해라.

    처음의 코드가 이해하기 쉽고, 다른 개발자들이 확장하기 쉽게끔 만드는데 집중한다면, 완벽한 스크립트를 만들수 있을 것이다. 조급하게 최적화하는 것을 반복한다면 결코 그렇게 될 수 없다. 당신 자신을 위해, 혹은 브라우저를 위해 개발하지 말아라 – 당신에게서 작업을 넘겨받을 다음 개발자를 위해 작업해라.

    요약

    자바스크립트에서 주된 요령은 쉬운 길을 피하는 것이다. 자바스크립트는 실로 융통성있는 언어이고, 개발 환경으로서 대단히 너그러운 편이다 – 그렇기 때문에, 문제를 해결하는 것 처럼 보이는 허술한 코드를 만들어내기 쉽다. 그렇지만 이 허술한 코드는 몇 달 뒤에 돌아와서 당신을 물어뜯을 것이다.

    당신이 웹 개발자 직업을 가져야 한다면, 자바스크립트 개발은 주변(역주:핵심이 아니라는 의미로) 지식의 영역에서 절대적인 필요성으로 변신한다. 지금 바로 시작한다면 운이 좋은 편인데, 나를 비롯한 많은 사람들이 이미 수많은 실수들을 저질렀고 이러한 시행착오를 통해 배워왔기 때문이다. 우리는 이러한 지식을 알려줄 수 있다..

    저자에 관하여

    Chris Heilmann은 지난 10년간 웹 개발자로서 활동해 왔으며 취미삼아 라디오 저널리즘에 관한 일도 한다. 그는 영국에 있는 야후에서 트레이너와 리더 개발자로 근무하고 있으며 유럽과 아시아의 front end 코드의 품질을 관리한다.

    그의 블로그는 Wait till I come 에 있으며 많은 소셜 네트워크에서 “codepo8”로 알려져 있다.

    1. 역주 : 결과적으로 <input class=’mandatory error’> 라는 요소가 만들어지는데, 이것이CSS 규칙에 부합하는 올바른 선택자이긴 하지만 일부 브라우저에서는 이러한 형태의 다중 선택자를 지원하지 않고 있다.

    댓글
    공지사항
    최근에 올라온 글
    최근에 달린 댓글
    Total
    Today
    Yesterday
    «   2024/12   »
    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
    글 보관함