일、UI 레이어의 느슨한 결합
느슨한 결합은 각 레이어가 "최소 지식의 원칙"을 따르거나, 각 레이어가 자신의 역할을 하고 권한을 넘어서지 않도록 요구합니다:
-
HTML: 구조 레이어
-
CSS: 표현 레이어
-
JS: 동작 레이어
각 레이어의 기능에 대해 적절한 설명이 있습니다: HTML 은 명사 (n), CSS 는 형용사 (adj) 와 부사 (adv), JS 는 동사입니다
세 레이어는 밀접하게 관련되어 있어 실제 응용에서 권한을 넘어서기 쉽습니다:
1. CSS 에서 JS 분리
CSS 표현식은 사용하지 않도록 하세요. 사용해야 한다면 해당 코드를 hack 에 배치하여 유지보수를 쉽게 하세요
2. JS 에서 CSS 분리
ele.style.attr 및 ele.cssText 는 사용하지 마세요. 클래스 이름 조작로 대체해야 합니다
3. HTML 에서 JS 분리
onclick 등 속성으로 이벤트 핸들러 함수를 직접 지정하는 것은 하지 마세요. 이벤트 리스너 추가 방식으로 대체해야 합니다
일반적으로 <script> 태그에 JS 코드를 직접 임베드하는 것은 하지 마세요. 외부 JS 파일에 배치하도록 하세요. 물론 기능이 단일하고 재사용이 필요 없는 코드는 HTML 에 직접 임베드할 수 있습니다. 예를 들어 폼 유효성 검사 코드 등
4. JS 에서 HTML 분리
innerHTML 로 하드코딩하는 것은 하지 마세요. 다음 3 가지 방식으로 대체할 수 있습니다:
-
Ajax 를 사용하여 서버에서 HTML 문자열을 가져와 하드코딩을 피합니다
-
간단한 클라이언트 측 템플릿을 사용합니다. 2 가지 구현 방식이 있습니다:
-
주석으로 템플릿 문자열을 携带합니다
-
script 태그로 템플릿 문자열을 携带합니다. type 속성을 브라우저가 인식할 수 없는 값으로 설정하고, 템플릿 문자열을 쉽게 가져올 수 있도록 script 태그에 id 속성도 설정합니다. 예를 들어:
<script type="text/x-my-template" id="list-item"> <li><a href="%s">%s</li> </script>이 방식을 권장합니다. 템플릿 문자열을 더 쉽게 가져올 수 있기 때문입니다
-
-
복잡한 클라이언트 측 템플릿을 사용합니다. 예를 들어 jade, ejs
P.S. HTML 과 CSS 의 결합 해제는 JS 프로그래밍과 관련이 없으므로 책에 해당 내용이 없습니다
이、전역 변수 적게 사용하기
1. 전역 변수가 가져오는 문제
-
이름 충돌
-
코드가 견고하지 않음. 함수에 필요한 모든 외부 데이터는 매개변수로 전달해야 하며, 전역 변수로 매개변수를 전달해서는 안 됩니다
-
테스트가 어려움. 전체 전역 환경을 재구축해야 함
2. 암시적 전역 변수
암시적 전역 변수 방식으로 전역 변수를 선언하는 것은 하지 마세요. 모든 변수 선언에 var 키워드를 포함하는 것을 권장합니다
암시적 전역 변수를 피하려면 엄격 모드 ("use strict";) 를 활성화해야 합니다. [IE10+] 에서 지원합니다
3. 단일 전역 변수 방식
-
네임스페이스 사용. 네임스페이스 충돌을 피하는 방법을 제공합니다:
// 최상위 네임스페이스 var app = { /* * 하위 네임스페이스 생성/가져오기, 체인 호출 지원 */ namespace: function(ns) { var parts = ns.split("."), object = this, i, len; for (i = 0, len = parts.length; i < len; i++) { if (!object[parts[i]]) { object[parts[i]] = {}; } object = object[parts[i]]; } return object; // 체인 호출 지원 } } // 테스트 app.namespace("Consts").homepage = "http://ayqy.net/"; app.namespace("Consts").author = "ayqy"; // http://ayqy.net/, ayqy alert(app.Consts.homepage + ", " + app.Consts.author); -
모듈화
AMD/CMD. 확장 지식은 다음과 같습니다:
CommonJS 는 일련의 이론 규격입니다 (예: JS 의 이론 규격은 ES). SeaJS 와 RequireJS 는 모두 CommonJS 의 Modules 부분에 대한 구체적인 구현입니다
CommonJS 는 브라우저 외부 (서버 측) 의 JS 를 위해 제정되었으므로 동기 모듈 로딩입니다. SeaJS 는 CommonJS 의 구현 중 하나입니다. RequireJS 도 CommonJS 의 구현이지만 비동기 모듈 로딩이며 브라우저의 싱글 스레드 환경에 더 부합합니다
요약: CommonJS 의 Modules 부분은 모듈화된 코드 관리의 이론을 제안했습니다. JS 를 모듈화하여 로딩할 수 있도록 하기 위해 RequireJS, SeaJS 등 다양한 구현을 모듈화 스크립트 로더라고 부를 수 있습니다
- CMD: 공통 모듈 정의. 예를 들어 SeaJS
- AMD: 비동기 모듈 정의. 예를 들어 RequireJS
모두 코드 모듈을 정의하는 규격으로, 모듈화된 스크립트 로딩을 용이하게 하고 응답 속도를 향상시킵니다
CMD 와 AMD 의 차이점:
-
CMD 은 의존 관계를近くに 배치. 사용하기 편리. 모듈 내부에서 필요할 때마다 가져올 수 있어 미리 의존 항목을 선언할 필요가 없으므로 성능 면에서 약간의 저하가 있습니다 (모듈 전체를 순회하여 의존 항목을 찾아야 함)
-
AMD 는 의존 관계를 사전에 배치. 의존 항목을 엄격하게 선언해야 하며, 로직 내부의 의존 항목 (소프트 의존) 은 비동기 로딩과 콜백 처리 방식으로 해결합니다
4. 제로 전역 변수 방식
IIFE(즉시 실행 함수 표현식) 를 사용하여 구현합니다. 재사용이 필요 없는 기능 모듈의 경우 IIFE 를 사용하여 전역 변수를 완전히 제거할 수 있으므로 일반적으로 IIFE 는 네임스페이스/모듈화 방식을 보조하는 데 사용됩니다
삼、이벤트 처리
1. 전형적인 사용법 (좋지 않음)
// 이벤트 핸들러
function handleClick(event) {
var popup = document.getElementById("popup");
popup.style.left = event.clientX + "px";
popup.style.top = event.clientY + "px";
popup.className = "display";
}
// 이벤트 핸들러 추가
ele.addEventListener("click", handleClick);
2. 애플리케이션 로직 분리 (조금 더 좋음)
var app = {
// 이벤트 처리
handleClick: function(event) {
this.showPopup(event);
},
// 애플리케이션 로직
showPopup: function(event) {
var popup = document.getElementById("popup");
popup.style.left = event.clientX + "px";
popup.style.top = event.clientY + "px";
popup.className = "display";
}
};
// 이벤트 핸들러 추가
// P.S. 이벤트 핸들러는 메서드 선언이지 메서드 호출이 아니므로 매개변수를 전달할 수 없어 추가의 익명 함수가 필요합니다
ele.addEventListener("click", function() {
app.handleClick(event);
});
3. 이벤트 객체 전달하지 않기 (가장 좋음)
var app = {
// 이벤트 처리
handleClick: function(event) {
this.showPopup(event.clientX, event.clientY); // 매개변수 변경
},
// 애플리케이션 로직
showPopup: function(x, y) { // 형식 매개변수 변경
var popup = document.getElementById("popup");
popup.style.left = x + "px";
popup.style.top = x + "px";
popup.className = "display";
}
};
// 이벤트 핸들러 추가
ele.addEventListener("click", function() {
app.handleClick(event);
});
"이벤트 객체를 전��해서는 안 된다"는 최적화 원칙이며, [JS 고급서의 최적화 부분](http://ayqy.net/blog/JS 学习笔记 12_优化/) 에서도 언급되었지만, 이 책은 상세한 이유를 제시했습니다
이벤트 객체를 직접 전달하는 것에는 다음과 같은 단점이 있습니다:
-
인터페이스 정의가 불명확. 매개변수의 역할이 알려지지 않음
-
테스트가 어려움 (event 객체를 재구축?)
사、null 과 비교 적게 하기
1. 기본 값 감지
typeof 로 감지하지만, typeof null 이 object 를 반환한다는 점에 주의하세요. 이는 그다지 과학적이지 않습니다. JS 는 null 을 빈 객체에 대한 참조로 간주하기 때문입니다
하지만 === null 로 DOM 요소를 감지하는 것은 합리적입니다. null 이 document.getXXByXXX 의 가능한 출력 중 하나이기 때문입니다
2. 참조 값 감지
instanceof 는 하위 유형을 정확하게 감지할 수 없으며, fun 과 arr 을 감지하는 데는 사용하지 마세요. 프레임을 넘을 수 없기 때문입니다
- fun 감지
typeof 를 사용하여 일반 메서드 감지; in 을 사용하여 DOM 메서드 감지
- arr 감지
Object.prototype.toString.call(arr) === "[Object Array]" 를 사용하여 감지
주의: ES5 에 네이티브 Array.isArray() 메서드가 있습니다. [IE9+] 에서 지원합니다
3. 속성 감지
in 과 hasOwnProperty() 를 조합하여 감지
주의: [IE8-] 의 DOM 요소는 hasOwnProperty() 를 지원하지 않습니다. 사용하기 전에 먼저 감지해야 합니다
오、설정 데이터 분리
1. 설정 데이터에는 어떤 것이 있는가?
-
하드코딩된 값
-
장래에 변경될 가능성이 있는 값
예를 들어:
-
URL
-
사용자에게 표시해야 하는 문자열
-
반복 사용되는 고유 값
-
설정 (예: 페이지당 표시할 목록 항목 수)
-
변경될 가능성이 있는 모든 값 (유지보수가 어려운 것은 모두 설정 데이터로 간주)
2. 설정 데이터 분리
먼저 애플리케이션 로직에서 설정 데이터를 분리합니다. 가장 간단하게는 모든 설정 데이터를 계층적으로 사용자 정의 config 객체에 저장할 수 있습니다
3. 설정 데이터 저장
JS 파일을 사용하여 설정 데이터를 저장할 수 있습니다. 로딩이 쉽지만 설정 데이터는 JS 구문을 엄격하게 준수해야 하여 실수하기 쉽습니다. 저자는 설정 데이터를 형식이 간단한 속성 파일로 저장한 후 도구를 사용하여 JSON/JSONP/JS 형식 파일로 변환하여 로딩하는 것을 권장합니다. 저자는 자신이 작성한 도구 props2js 를 권장합니다
육、사용자 정의 오류 던지기
1. Error 의 본질
예상치 못한 것을 표시하고, 묵시적 실패를 피하며, 디버깅을 용이하게 하는 데 사용됩니다
2. JS 에서 오류 던지기
다른 유형을 던지는 것은 하지 마세요. 호환성을 위해 Error 객체를 던지세요. 예를 들어:
throw "error: invalid args"; // 일부 브라우저는 이 문자열을 표시하지 않음
3. 오류 던지기의 장점
오류를 정확하게 위치시킬 수 있습니다. 오류 메시지 형식 권장: 함수명 + 오류 원인
4. 언제 오류를 던져야 하는가
일반적으로 사용되는 메서드 (유틸리티 메서드) 의 오류만 던집니다. 일반 원칙:
-
이상한 버그를 수정한 후 사용자 정의 오류를 몇 개 추가하여 오류가 다시 발생하지 않도록 해야 합니다
-
코드를 작성할 때 특정 지점이 큰 문제를 일으킬 수 있다고 생각되면 사용자 정의 오류를 던져야 합니다
-
코드가 다른 사람이 사용하도록 작성된 경우, 다른 사람이 사용할 때 마주칠 수 있는 문제를 생각하고 사용자 정의 오류에서 힌트를 제공해야 합니다
5. try-catch 문
finally 는 자주 사용되지 않습니다. catch 의 return 에 영향을 주기 때문입니다
빈 catch 블록을 남기지 마세요. 묵시적 실패는 문제를 더 복잡하게 만들 수 있습니다
6. 몇 가지 오류 유형
네이티브의 몇 가지 오류 유형 인스턴스를 던지고 instanceof 와 협력하여 대상 오류 처리를 할 수 있습니다
P.S. 구체적인 오류 유형 및 오류 처리에 대한 자세한 정보는 [黯羽轻扬:JS 학습 노트 8_ 오류 처리](http://ayqy.net/blog/JS 学习笔记 8_错误处理/) 를 참조하세요
칠、객체 소유권 존중
1. 어떤 객체가 우리 것이 아닌가?
-
네이티브 객체 (Object, Array 등)
-
DOM 객체 (예: document)
-
BOM 객체 (예: window)
-
라이브러리 객체 (예: JQuery)
2. 구체적 원칙
-
메서드를 재작성하지 마세요
-
새 메서드를 추가하지 마세요. 라이브러리 기능을 변경해야 한다면 라이브러리에 플러그인을 개발할 수 있습니다
-
메서드를 삭제하지 마세요. 사용하고 싶지 않다면 삭제하지 말고 폐기됨으로 표시하기만 하면 됩니다
주의: delete 는 프로토타입 속성에는 무효하며, 인스턴스 속성에만 유효합니다
function Fun() {
this.attr1 = 1; // 인스턴스 속성
}
Fun.prototype.attr2 = 2; // 프로토타입 속성
// 테스트
var obj = new Fun();
alert(obj.attr1 + ", " + obj.attr2); // 1, 2
delete obj.attr1;
delete obj.attr2;
alert(obj.attr1 + ", " + obj.attr2); // undefined, 2
delete Fun.prototype.attr2;
alert(obj.attr1 + ", " + obj.attr2); // undefined, undefined
3. 더 나은 방법
- 객체 기반 상속
즉, 새 객체를 복제합니다. 새 객체는 자신의 것이므로 마음대로 변경할 수 있습니다
- 타입 기반 상속
주의: DOM/BOM/Array 를 상속해서는 안 됩니다. 지원이 좋지 않기 때문입니다
P.S. 객체 상속/타입 상속의 구체적 구현에 대해서는 [黯羽轻扬:JS 의 6 가지 상속 방식 재이해](http://ayqy.net/blog/重新理解 JS 的 6 种继承方式/) 를 참조하세요
- 퍼사드 패턴
실제로는 합성입니다. 상속/합성은 모두 코드 재사용을 구현하는 방법이기 때문입니다. 퍼사드 패턴에 대한 자세한 정보는 黯羽轻扬:디자인 패턴 - 퍼사드 패턴(Facade Pattern) 를 참조하세요
약간의 잡담: facade 와 adapter 의 차이는 전자가 새 인터페이스를 생성하고, 후자는 기존 인터페이스를 구현할 뿐이라는 점입니다. 저자는 핵심을 찌릅니다
4. polyfill 의 장단점
polyfill 은 "구형 브라우저에서 표준 API 를 복제하는 JavaScript 보충"입니다. "표준 API"는 HTML5 기술 또는 기능 (예: Canvas) 을 가리킵니다. "JavaScript 보충"은 이러한 표준 API 를 지원하지 않는 브라우저에서 동적으로 JavaScript 코드나 라이브러리를 로딩하여 시뮬레이션할 수 있음을 가리킵니다. polyfill 은 표준 API 를 시뮬레이션하므로 이러한 API 에 대한 개발을 모든 브라우저의 미래를 향한 방식으로 수행할 수 있습니다. 최종 목표는: 이러한 API 에 대한 지원이 절대 다수가 되면 추가 작업 없이 polyfill 을 편리하게 제거할 수 있는 것입니다.
polyfill 에 대한 자세한 정보는 博客园:[번역]shim 과 polyfill 의 차이점은 무엇인가? 를 참조하세요
장점: 필요 없을 때 쉽게 제거할 수 있습니다
단점: polyfill 구현이 표준과 완전히 일치하지 않으면 번거롭습니다
polyfill 을 사용하지 않는 것을 권장합니다. 대신 네이티브 메서드 + 퍼사드 패턴을 사용하는 것이 더 유연합니다
5. 변조 방지
엄격 모드를 활성화해야 합니다. 비엄격 모드에서는 묵시적 실패의 디버깅이 어렵기 때문입니다
팔、브라우저 감지
1. UA(User Agent) 감지
UA 감지를 해야 한다면 앞쪽이 아닌 뒤쪽으로 감지하도록 하세요. 뒤쪽은 더 이상 변하지 않기 때문입니다
P.S. 저자는 UA 가 변할지 여부를 신경 쓸 필요가 없다고 생각합니다. 이유는 UA 를 설정하는 사용자도 그렇게 하는 결과를 알아야 하기 때문입니다
2. 기능 감지
기능 감지의 일반 형식은 다음과 같습니다:
-
표준 방식 시도
-
특정 브라우저 구현 방식 시도
-
지원하지 않으면 논리적 피드백 제공 (예: return null)
예를 들어:
function setAnimation (callback) {
// 1. 표준 방식 시도
if (window.requestAnimationFrame) { // 표준
return requestAnimationFrame(callback);
}
// 2. 특정 브라우저 구현 방식 시도
else if (window.mozRequestAnimationFrame) { // Firefox
return mozRequestAnimationFrame(callback);
}
else if (window.webkitRequestAnimationFrame) { // WebKit
return webkitRequestAnimationFrame(callback);
}
else if (window.oRequestAnimationFrame) { // Opera
return window.oRequestAnimationFrame(callback);
}
else if (window.msRequestAnimationFrame) { // IE
return window.msRequestAnimationFrame(callback);
}
// 3. 지원하지 않을 경우 논리적 피드백
else {
return setTimeout(callback, 0);
}
}
3. 기능 추론 완전히 금지
한 기능으로 다른 기능을 추론하는 것은 할 수 없습니다. 오리처럼 생긴 것이 반드시 오리처럼 꽥꽥거리는 것은 아니기 때문입니다
4. 브라우저 추론 완전히 금지
기능을 기반으로 브라우저를 추론하는 것은 하지 마세요. 전형적인 예:
if (document.all) { // IE
// ...
}
이렇게 하는 것은 옳지 않습니다. 특징으로 어떤 것을 설명하려고 해서는 안 됩니다. 조건이 너무 적거나 너무 많아 설명이 부정확해질 수 있습니다
5. 도대체 어느 것을 사용해야 하는가?
직접 기능 감지를尽量 사용하고, 안 될 경우에만 UA 감지를 사용하세요. 추론에 대해서는 전혀 고려하지 마세요. 사용할 어떤 이유도 없습니다
참고 자료
- 『Maintainable JavaScript』

아직 댓글이 없습니다