메뉴 건너뛰기

Hodol's Blog

웹프로그래밍 삽질노트 20160119

 
목차
HTML 관련
일반
  • 태그에서 tabindex 속성을 사용하여 Tab키를 눌렀을 시 우선 순위를 정할 수 있다.
  • Root 태그 선언은 <html lang="ko">처럼 언어를 지정해주도록 하자.
A 태그의 책갈피 클릭 시 페이지의 상단으로 스크롤되는 현상 방지
<a href="#" onclick="javascript:func();">이벤트 리스너</a>
<a href="#">가짜 링크</a>
위와 같은 책갈피 링크에서 #는 페이지의 상단을 의미하기에 위의 코드로 작성된 링크를 클릭하게 되면 화면이 페이지 상단으로 이동하게 된다. 웹표준안이 중요한 이슈가 되기 전이었던 HTML 4.0 버전에서는 같은 코드로 페이지가 스크롤되지 않았기에 자바스크립트 이벤트 리스너 대용으로 또는 가짜 링크를 만들기 위해 위와 같이 사용했었다. 하지만 지금에 와서는 위의 링크를 클릭하게되면 화면이 페이지 상단으로 이동해버린다. 이를 해결하기 위해 href 속성을 기술하지 않으면 마우스오버 시 커서 모양이 손모양으로 바뀌지 않고, 링크 스타일이 표현되지 않는 등의 문제가 있다. 다음과 같은 방법이 있다.
<a href="javascript:func();">이벤트 리스너</a>
<a href="javascript:;">가짜 링크</a>
CSS 관련
  • Cascading은 선택자에 대한 점수체계를 의미한다. id, class, tag 선택자들 순으로 높은 점수가 매겨지며, 여러가지 선택자들을 사용하는 경우 선택자들에게 부여된 점수들을 합산하는 방식이다. 같은 점수를 가지면서 대상이 겹치는 두 설정의 경우 나중에 기술된 설정이 이전에 기술된 설정을 덮어쓴다.
  • CSS 파일 작성 시, @charset "UTF-8";을 먼저 명시하도록 하자.
Javascript 관련
Javascript 일반
  • Script는 언어 자체만으로는 하나의 완성된 프로그램을 만들 수 없으며 일반적으로 인터프린터 언어임을 의미한다.
  • Javascript의 오브젝트와 JSON 오브젝트는 매우 유사하게 보이지만 전자는 메소드를 가질 수 있으나 후자는 데이터만 포함할 수 있다. 따라서 Javascript 오브젝트와 JSON 오브젝트는 다르다.
  • delete 명령으로 메모리를 해제할 수 있다.
    var obj.prop = "some string";
    ...
    delete obj.prop;
  • obj = { ftn1: function () {...} } 으로 선언된 경우, obj.ftn1은 메모리에 적재된 함수 자체를 가리키고, obj.ftn1()은 함수가 실행된 결과를 가리킨다.
  • 빈 배열은 Falsy Value가 아니므로 빈 배열을 체크할 땐 length로 체크한다. 예를 들어, 다음과 같은 경우, if 문은 true를 출력한다.
    var a=[];
    if(a) {
        console.log("true");
    }
    else {
        console.log("false");
    }
    일반적인 프로그래밍의 상식과 좀 다르게 작동하는데 이를 해결하기 위해서 다음과 같이 배열의 길이를 이용하도록 하자.
    var a=[];
    if(a.length > 0) {
        console.log("true");
    }
    else {
        console.log("false");
    }
  • 조건문에서 == 대신 ===을 사용하자. 전자는 변수형을 은근슬적 무시하는데 이게 작성자의 의도대로 동작하지 않을 수 있다. 편의성을 제공하기 위해 이런 식으로 디자인된 듯한데 아무래도 실수를 유도할 수 있는 방식이다.
  • 자바스크립트는 네트워크에 물린 스크립트 언어이므로 네트워크가 지연되는 경우를 고려하면서 스크립트를 작성해야 한다. 특히 여러 함수들을 실행하는 경우, 콜백 함수를 이용한 비동기 처리를 하는 것이 좋다. 비동기 처리는 이벤트 루프의 큐에 함수를 할당하는 방식으로 한다.
    ftn1 (x,y);
    ftn2 (a,b);
    위의 형태가 되면 ftn1ftn2가 동시에 실행되는 수가 있다. ftn2의 호출 시점이 중요한 경우, 이를 조정하기 위해 ftn2ftn1의 인자로 받아서 ftn1에서 조정하는 방식으로 함수를 디자인 한다:
    ftn1 (x,y,function () {
        ftn2(a,b);
    });
  • 페이지 DOM 객체들이 모두 다운로드된 상태에서 스크립트를 실행하기 위하여 window.onload를 이용하거나, 스크립트를 <body>태그 최하단에 위치하도록 한다.
  • 자바스크립트 변수는 호이스팅(Hoisting)이라는 개념이 있다. 스크립트 중간에 변수가 선언되었더라도, 자바스크립트 엔진은 이 변수가 스크립트의 상단에서 선언되었다고 가정하게 된다. 여기서 스크립트의 상단이란, 해당 변수가 지역 변수이면 함수 블럭의 상단을 의미한다. 다음 스크립트는 아무런 문제 없이 콘솔에 a = 1을 출력한다.
    a = 1;
    var a;
    console.log('a = ' + a); // a = 1 출력
    반면, 다음 ECMAscript6 구문은 Undefined 에러를 출력한다.
    b = 1;
    let b;
    console.log('b = ' + b); // Undefined Error 출력
    참고 사이트: http://chanlee.github.io/2013/12/10/javascript-variable-scope-and-hoisting/
Javascript 변수 범위
꽤 충격이었다. 자바스크립트의 변수 범위가 블럭 단위가 아닌 함수 단위었다니! 이제껏 이것도 모르고 잘도 써 왔었다.
  • 자바스크립트의 변수 범위(Variable Scope)는 블럭-수준(block-level)이 아닌 함수-수준(function-level)로 구분되어진다.
  • 전역 변수와 같은 이름의 변수를 함수 내 지역 변수로 사용하려면 먼저 var를 이용하여 선언하여야 한다. 그러지 않으면 함수 내에 기술되는 해당 변수는 전역 변수로 사용된다.
  • 따라서 굳이 블락 단위의 지역 변수가 필요하다면 다음과 같이 즉시 실행 함수 블락으로 감싸준다.
    (function () {
        var local_var = 1;
        console.log(local_var);
    }())
  • 따라서 다음과 같이 서로 다른 두 버튼 객체에 이벤트 리스너를 달면 두 버튼 모두 같은 기능을 하게 된다.
    var btn = document.getElementsByTagName("button");
    for(var i=0; i<btn.length; i++) {
        btn[i].addEventListener("click", function () {alert(i);}, false);
    }
    왜냐하면 변수 ifor 문 바깥의 전역 변수로 인식되고, 어느 버튼을 누르더라도 alert() 함수가 호출될 때에는 이 전역 변수 i의 값을 읽기 때문이다. 즉, 각 버튼에 달린 callback 함수는 alert(i)이지 alert(0), alert(1)들이 아니다. 따라서 이 i를 지역 변수화시켜줄 필요가 있다. 다음과 같이 즉시 실행 함수 블럭으로 감싸고 함수 내에 새로운 지역 변수를 사용한다.
    var btn = document.getElementsByTagName("button");
    for(var i=0; i<btn.length; i++) {
        ( function () {
            var j = i;
            btn[j].addEventListener("click", function () {alert(j);}, false);
        } () )
    }
    또는 다음과 같이 즉시 실행 함수의 인자로 넘기는 방법도 있다.
    var btn = document.getElementsByTagName("button");
    for(var i=0; i<btn.length; i++) {
        ( function (var j) {
            btn[j].addEventListener("click", function () {alert(j);}, false);
        } (i) )
    }
    참고 사이트: http://stackoverflow.com/questions/19586137/addeventlistener-using-for-loop-and-passing-values
  • 모든 전역변수는 window 객체의 속성이 된다.
  • setTimeout() 함수의 변수는 전역 범위에서 실행된다. 즉, setTimeout() 함수 안에 선언된 모든 함수 역시 전역 범위에서 실행된다.
Javascript에서 DOM 객체 얻기
DOM 객체를 얻는 것은 어려운 일이 아니다.
document.getElementById("myelt");
위와 같은 방식으로 DOM 객체를 얻는 메서드를 활용하면 된다. 하지만 조건이 둘 이상인 객체를 얻으려면 조금 귀찮아진다. 예를 들어, A 태그 중에 href 속성의 값이 http://hodol.kr로 시작하는 객체들을 찾는다면 아마 for 문을 돌려서 조건에 맞는 것을 추려내어야 할 것이다. 그러나 이는 CSS에서는 아주 쉬운 작업이다. CSS는 다음과 같은 선택자(Selector)를 제공한다.
a[href^="http://hodol.kr"] { font-size: 20px; }
매우 간단하다. 이렇게 간단한 CSS 선택자를 자바스크립트 안에서 쓸 수 없을까? 다행히 자바스크립트는 다음과 같은 CSS 선택자와 비슷한 방식으로 작동하는 메서드를 지원한다.
var x = document.querySelectorAll('a[href^="http://hodol.kr"]');
AJAX 동기방식 호출
Ajax는 페이지 밖에서 자료를 얻어올 수 있을 뿐만 아니라 자료를 받는 동안 callback 함수를 반복적으로 호출하여 자료의 다운로드의 상태에 따른 작업들을 진행하면서 뒤에 기술된 코드들을 작동시키는 기능이 있다. 이러한 작동 방식을 비동기 방식이라고 한다. 반대로 자료를 받은 후, 이를 처리하는 callback 함수를 호출하고, 뒤에 기술된 코드들을 순차적으로 작동시키는 방식을 동기 방식이라고 한다. 즉, 비동기 방식은 외부자료와 내부자료가 동기화되지 않았더라도 작업을 하는 것이고, 동기 방식은 외부자료와 내부자료가 동기화해서 동일한 상태에서 작업을 진행한다....뭐 이런 의미이다. Ajax은 동기/비동기 방식 모두 지원한다. 설정 방법은 XMLHttpRequest 객체의 open메서드의 세 번째 인자값으로 설정한다. 설정값이 true이면 비동기 방식, false이면 동기 방식으로 작동한다. 단순히 자료 임포팅을 하는 것이라면 동기 방식을 사용하면 된다.
var httpRequest = getXMLHttpRequest();
httpRequest.onreadystatechange = callbackFunc;
httpRequest.open("GET","url/data.php", true);
httpRequest.send(null);
doSomething();
위의 코드는 서버와의 통신 상태에 따라 callbackFunc() 함수가 반복적으로 호출되며 doSomething() 함수는 서버와의 통신 상태와 상관없이 호출된다. 반면에
var httpRequest = getXMLHttpRequest();
httpRequest.onreadystatechange = callbackFunc;
httpRequest.open("GET","url/data.php", false);
httpRequest.send(null);
doSomething();
위의 코드는 서버와의 통신 상태에 따라 callbackFunc() 함수가 반복적으로 호출되며 doSomething() 함수는 서버와의 통신이 완결된 후, 마지막 callbackFunc() 함수가 정상 종료된 후에 호출된다.
Javascript Prototype
자바스크립트 프로토타입은 객체의 원형을 정의한다. 같은 생성자들에 의해 생성된 객체들은 같은 프로토타입을 공유한다. 인스턴스의 데이터들은 자주 바뀌지만 메서드들은 거의 변하지 않는다. 따라서 각 메서드들을 각 인스턴스마다 할당하는 것은 메모리 자원의 낭비가 된다. 따라서 인스턴스들이 공유할 수 있는 프로토타입에 메서드를 할당하여 공유 메서드를 이용함으로써 이 문제를 해결할 수 있다. 프로토타입의 멤버와 메서드는 object_constructor.prototype.memberName로 접근할 수 있다. 또한, 인스턴스에서 프로토타입의 메서드를 오버라이딩하는 것이 가능하다. 이를 이용하여 다른 언어의 클래스 상속을 구현하는데 사용된다. 다음의 경우는 metaObjectobject를 상속한다.
metaObject_constructor.prototype = new objectContructor();
모든 객체는 Object객체를 최상위 객체로 가진다. object.call(anotherObj, memberName) 메서드는 메서드가 실행되는 동안 객체의 this멤버가 가리키는 객체가 anotherObj가 되게 만들어서 anotherObjmemberName을 호출한다. MetaHuman.prototype = human.prototype 를 하면 Metahuman.prototypehuman.prototype이 같은 메모리를 가리킨다. (자바스크립트는 딥카피를 안함) 따라서 MetaHuman.prototype = new Human(); 으로 Human의 프로토타입을 (딥)카피 받는다.
ECMASCRIPT 6
  • let은 블락단위 지역변수 범위를 지원하는 변수 선언 키워드이다.
  • let으로 선언된 전역 변수는 window 객체의 멤버가 아니다.
  • let으로 선언된 변수는 호이스팅되지 않는다.
  • let으로 같은 범위에서 중복으로 변수 선언을 할 수 없다.
  • let으로 함수 내부에 변수를 선언할 경우, 함수의 인자로 들어오는 변수와 같은 이름을 사용할 수 없다.
  • const는 상수 선언을 하고 선언 시에만 값을 할당할 수 있으며, 선언 시 값을 할당하지 않으면 에러가 나며 블락 단위로 변수 범위가 지정된다.
  • const로 선언된 상수가 배열인 경우, 배열의 레퍼런스만 상수 취급하고 배열의 멤버는 가변 변수 이다.
  • const a = Object.freeze(객체);를 하면 객체 안의 멤버도 상수처럼 고정할 수 있지만, 이것도 멤버가 배열일 경우 그 멤버의 레퍼런스만 고정하는 것이다.
  • `(역따옴표)를 이용하여 템플릿을 사용할 수 있다. 예를 들어, `문자열 <엔터> ${변수명}`로 문자열을 사용하면 엔터도 먹고 지정한 위치에 변수값이 들어간다. `${`문자열 ${변수명}`}`처럼 중복하여 사용 가능하다.
  • 모듈 또는 라이브러리는 다음과 같은 방식으로 정의하고 끌어다 쓴다.
    //export.js
    export function a() {...}

    //import1.js
    import {a} from ‘./export’
    a();

    //import2.js
    import * as e from ‘./export’
    e.a();
    // export2.js
    function a() {}
    function b() {}
    export default {a, b}

    //import1.js
    import i from ‘./export2’
    i.a();
    i.b();

    //import2.js
    import {a, b} from ‘./export2’
    a();
    b();
  • Arrow 함수는 기존 함수의 클래스 생성자로써의 활용과 순수 함수로써의 활용 사이의 혼동을 피하기 위한 순수한 함수용 표기법이다.
    sum = (num1, num2) => {
        return num1 + num2;
    }
  • 기존의 함수와 프로토타입을 통한 클래스 사용은 앞서 설명한 것과 같이 혼동을 야기한다. ECMAScript에서는 다음과 같이 클래스를 선언한다. 클래스 안에 선언된 메서드는 프로토타입의 메서드로 선언되며 모두 Arrow 함수 형태로 선언된다. 클래스 안의 메서드에서 new 키워드를 사용할 수 없다.
    class PersionClass {
        constructor(name) {
            this.name = name;
    }

        sayName() {
            console.log(this.nave);
        }
    }
  • 참고 사이트
    http://exploringjs.com/es6/
    https://leanpub.com/understandinges6/read
AJAX 크로스 도메인 요청하기
Ajax로 외부 서버에 요청을 하면 작동을 하지 않는다. 이는 보안 정책 중 하나인 동일 출처 정책(Same-Origin Policy) 때문으로 자바스크립트는 프로토콜, 호스트명, 포트가 같은 곳에서만 자료를 요청할 수 있다. 예를 들어, A라는 서버의 관리자 a는 B라는 서버에 있는 B 서버 관리자 b의 권한으로만 얻을 수 있는 데이터가 보고 싶다. 그래서 a는 A에서 제공되는 웹페이지에 B 서버의 데이터를 요청하는 코드를 숨겨넣고 B 서버에 접속할 수 있는 유일한 IP를 사용하는 b의 PC C에서 b가 그 페이지에 접속하도록 유도한다면 a는 B 서버의 데이터를 b 몰래 얻을 수 있다. 또는 반대로 b가 B 서버의 데이터를 변조하여 이를 끌어다 쓰는 A 서버에서 제공되는 페이지를 통해 제 삼자를 해킹하고 a에게 누명을 씌우는 방법도 있을 것이다. 아무튼 보안 상 동일 출처 정책을 기본적으로 사용해야 하지만 데이터를 이 서버 저 서버에서 받아다가 가공해야하는 현대에서는 약간 골칫거리가 된다. 이를 해결하기 위해 다음과 같은 편법이 있다.
  • 웹 브라우저에서 동일 출처 정책을 강제로 비활성화한다.
  • 데이터를 JSON 방식으로 주고 받는다. JSON이나 css, js 같은 리소스 파일은 동일 출처 정책의 영향을 받지 않는다.
이 외에 교차 출처 공유(Cross-Origin Resouce Sharing, 이하 CORS)을 통해 통신을 하는 것이다. 이는 웹페이지 요청을 받은 서버가 클라이언트와 다른 서버 간의 통신을 허락하는 개념이다. 즉, 위의 두번째 예제에서 a는 b가 제공하는 B의 서버의 데이터를 믿겠다는 것이다. 더 이상 자세한 내용은 따로 공부하도록하고 CORS를 설정하는 방법은 다음과 같다. Httpd 아파치 서버의 설정 파일에 Directory, Location, Files, VirtualHost 섹션이나 .htaccess 파일에 다음과 같은 줄을 추가하고 http를 재구동을 하면 된다.
Header set Access-Control-Allow-Origin "*"
참고 사이트: http://adrenal.tistory.com/16