서론
7 월 11 일~8 월 27 일, 한 달 반이 지났다. 주 단위로 시간이 지나가는 것이 특히 빠르게 느껴진다.
매일 바쁘지만 정작 무엇을 바쁘게 했는지 기억나지 않는다. 기간 동안
-
새로운 책을 전혀 펼치지 않았다 (블로그 작성 시 Node 와 JS 를 가끔 펼쳤을 뿐. 내용은 상당히 잊어버렸다)
-
에세이 1.5 편을 읽었다
-
Kindle 을 2 번 충전했지만 한 번도 사용하지 않았다
-
일본어 학습은 제 7 과까지 종료
-
월요일은 FEX 周刊 (한 번은 화요일에 발행됨), 금요일은 奇舞周刊 (이번 주는 凹凸实验室의 mock 淘宝造物節 파노라마가 좋았다)
최근에 무슨 책을 읽고 있나요, 추천할 만한 것이 있나요?
일본어 책,まとまった 시간이 없어서. 일상 업무에 익숙해지면 시간이 생길 것입니다
정말 그럴까? 왜まとまった 시간이 없는 걸까?
일. 문제 개요
URL 규격의 방법으로 host 를 추출한 후 host 를 판별하세요. 단일 문자로 판별하지 마세요
상황은 이렇다. 현재 페이지의 query string 에 url 파라미터가 포함되어 있어, 여기서 hostname 을 파싱해야 한다
location.hostname 으로 현재 페이지의 호스트명은 추출할 수 있다. 그렇다면 임의의 URL 문자열은? 비정규식 방안이 있는가?
이전에는 JS API 에 new URL() 등이 제공되지 않는다고 생각했다. 정규식으로 파싱해야 한다고만 생각했다
밖에 나가 상담했을 때, 세 명의 베테랑이 순식간에 3 가지 방안을 제시해 주었다
이. 해결 방안
표준 URL 형식:scheme://domain:port/path?query_string#fragment_id. 간단한 정규식 캡처로 순식간에 파싱 가능하다
이상한 (의도적으로 만든) URL 도 있다:
http://www.example.com/public/page/2015/index.html?url=http://12.23.34.45/hack.html?http://www.example.com//check.htm
http://www.example.com/public/page/2015/index.html?url=http://www.example.com @12.23.34.45/hack.html
P.S. 잘 보이게 하기 위해 url 파라미터 부분은 encode 하지 않았다. 이런 파라미터는 불온해 보인다
규격에 맞지 않는 URL 도 있어, 일반적인 정규식으로는 대응이 어렵다. 예를 들어:
// 이것은 어떻게 해석하나?
http://www.example.com/what??key=val?&{http:// @www.abc.com?query=2#45
// 이것은?
http://www.example.com:8899 @www.abc.com/what??key=val?&{http://?query=2#45
// 이것은?
http://www.example.com:$88;9,9 @www.abc.com$/what??key=val?&{http://?query=2#45
//...
1.a 태그로 URL 자동 파싱
var a = document.createElement('a');
a.href = 'http://www.example.com/news.php?id=10#footer';
var div = document.createElement('div');
for (var key in a) {
!(key in div) && console.log(`${key} = ${a[key]}`);
}
출력 결과는 다음과 같다:
// 리소스가 어디를 가리키는지 표시. 현재 창, 새 탭 등
target =
// UA 에 리소스 다운로드를 알림
download =
// 링크 클릭 시 지정 주소로 비동기 POST. 광고 통계에 사용
ping =
// 가리키는 리소스와 현재 리소스의 관계를 표시. 대체, 북마크 등
rel =
// 가리키는 리소스의 언어
hreflang =
// 가리키는 리소스의 MIME type
type =
// 요청 헤더 referer 필드 전략. 사용자 프라이버시 보호에 사용
referrerpolicy =
//
text =
// 폐기됨. 커스텀 모양 지원. 일련의 좌표점 전달
coords =
// 폐기됨. 가리키는 리소스의 문자 인코딩
charset =
// 폐기됨. 지정 name 의 태그로 이동
name =
// 폐기됨. 역관계. rel 의 반의어
rev =
// 폐기됨. 커스텀 모양 핫스팟 지정에 사용
shape =
// 가리키는 리소스의 URL, 또는 URL 의#fragment_id 부분
href = http://www.example.com/news.php?id=10#footer
origin = http://www.example.com
protocol = http:
username =
password =
host = www.example.com
hostname = www.example.com
port =
pathname = /news.php
search = ?id=10
hash = #footer
이것들은 a 태그 고유의 속성. 우리가 원하는 hostname 도 포함되어 있다. 즉, a 태그가 URL 파싱을 자동으로 완료한다. 프론트엔드에게 이것은 과거에 URL 파싱의 가장 저렴한 방법이었다:
var getHostname = function(url) {
var a = document.createElement('a');
a.href = url;
return a.hostname;
};
100% 신뢰할 수 있다. 아무리 복잡한 URL 도 브라우저를 속일 수 없다
2.JS URL API
var url = new URL('http://www.example.com:$88;9,9 @www.abc.com$/what??key=val?&{http://?query=2#45');
for (var key in url) {
console.log(`${key} = ${url[key]}`);
}
Chrome 에서의 출력 결과:
searchParams = %3Fkey=val%3F
href = http://www.example.com:$88%3B9,9 @www.abc.com%24/what?%3Fkey=val%3F#123http://?query=2#45
origin = http://www.abc.com%24
protocol = http:
username = www.example.com
password = $88%3B9,9
host = www.abc.com%24
hostname = www.abc.com%24
port =
pathname = /what
search = ?%3Fkey=val%3F
hash = #123http://?query=2#45
이 URL 은 너무 규격에 맞지 않아 UA 처리 세부사항에 차이가 있다. FF 에서의 결과는 다르다:
"href = http://www%2Eexample%2Ecom:$88%3B9,9 @www.abc.com$/what??key=val?&{http://?query=2#45"
"origin = http://www.abc.com$"
"protocol = http:"
username = www%2Eexample%2Ecom
password = $88%3B9,9
host = www.abc.com$
hostname = www.abc.com$
port =
pathname = /what
search = ??key=val?&&
searchParams = %3Fkey=val%3F
hash = #123http://?query=2#45
브라우저는 몰래 URL 클래스를 제공했다. ES5 도 ES6, 7 규격도 아니다. 현재는 실험적인 특성. 호환성은 다음과 같다:
Android4.0 webkitURL
Android4.4 URL
Safari6.0 webkitURL
Chrome32 URL
FF19 URL
IE10 URL
모바일에서는 거의 안심하고 사용 가능하다. 더 많은 호환성 정보는 URL - Web APIs | MDN 참조
var getHostname = function(url) {
return new URL(url).hostname;
};
3. 정규식 파싱
var parseUrl = function(url) {
var urlParseRE = /^\s*(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^: @\/#\?]+)(?:\:([^: @\/#\?]+))?) @)?(([^:\/#\?\]\[]+|\[[^\/\] @#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/;
var matches = urlParseRE.exec(url || "") || [];
return {
href: matches[0] || "",
hrefNoHash: matches[1] || "",
hrefNoSearch: matches[2] || "",
domain: matches[3] || "",
protocol: matches[4] || "",
doubleSlash: matches[5] || "",
authority: matches[6] || "",
username: matches[8] || "",
password: matches[9] || "",
host: matches[10] || "",
hostname: matches[11] || "",
port: matches[12] || "",
pathname: matches[13] || "",
directory: matches[14] || "",
filename: matches[15] || "",
search: matches[16] || "",
hash: matches[17] || ""
};
};
무서워서 울었다. 시험해 보자. 충분히 견고한가:
var url = parseUrl('http://www.example.com:$88;9,9 @www.abc.com$/what??key=val?&{http://?query=2#45');
for (var key in url) {
console.log(`${key} = ${url[key]}`);
}
출력 결과:
href = http://www.example.com:$88;9,9 @www.abc.com$/what??key=val?&{http://?query=2#45
hrefNoHash = http://www.example.com:$88;9,9 @www.abc.com$/what??key=val?&&
hrefNoSearch = http://www.example.com:$88;9,9 @www.abc.com$/what
domain = http://www.example.com:$88;9,9 @www.abc.com$
protocol = http:
doubleSlash = //
authority = www.example.com:$88;9,9 @www.abc.com$
username = www.example.com
password = $88;9,9
host = www.abc.com$
hostname = www.abc.com$
port =
pathname = /what
directory = /
filename = what
search = ??key=val?&&
hash = #123http://?query=2#45
FF 와 완전히 일치. 결과 신뢰도가 높다. 그럼 이 무적의 정규식을 해석해 보자:
/^ #href
\s*
( #hrefNoHash
( #hrefNoSearch
( #domain
([^:\/#\?]+:)? #protocol
(?:
(\/\/) #doubleSlash
( #authority
(?:
( #결과 취득 시 $7 은 스킵됨. 비캡처형 괄호 (?: 도 사용해야 함
([^: @\/#\?]+) #username
(?:
\:
([^: @\/#\?]+) #password
)?
)
@
)?
( #host
([^:\/#\?\]\[]+|\[[^\/\] @#?]+\]) #hostname
(?:
\:
([0-9]+) #port
)?
)
)?
)?
)?
( #pathname
(\/?(?:[^\/\?#]+\/+)*) #directory
([^\?#]*) #filename
)
)?
(\?[^#]+)? #search
)
(#.*)? #hash
/
위 분석에 따르면, 9 번째 왼쪽 괄호는 비캡처형 괄호 (?: 를 사용해야 한다. 값 취득 시 $7 을 스킵하지 않아도 된다. 다음과 같다:
var getHostname = function(url) {
// 9 번째 소괄호 변경
var urlParseRE = /^\s*(((([^:\/#\?]+:)?(?:(\/\/)((?:(?:([^: @\/#\?]+)(?:\:([^: @\/#\?]+))?) @)?(([^:\/#\?\]\[]+|\[[^\/\] @#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/;
var matches = urlParseRE.exec(url || "") || [];
return matches[10] || "";
};
눈이 부시다. 정규식 안녕.
삼. 방안 분석
a 태그
베테랑 1 은 프론트엔드 경험이 풍부하다.冷门技巧으로 순식간에 문제 해결
호환성 문제 없음 (몇 년 전의 기술). 순수 프론트엔드 방안. 간단하고 효과적. a 태그가 이렇게 강력할 줄은
더 많은 冷门技巧은 前端不为人知的一面--前端冷知识集锦 참조. 어제 새로 발견한 다른 선배. 추종한다
URL 클래스
베테랑 2 는 시야가 넓고 세부사항이 扎实하다
비표준 URL 클래스도 알고 있다. 나는 매일 console 을 사용하지만 이것에는 주목하지 않았다. 세심하지 않으면 경험이 줄어든다. 슈퍼마리오처럼
무적 정규식
베테랑 3 은 문제 해결 경험이 풍부하다. 리소스 축적이 많다
이 정규식, 무서워서 울었다. orz
사. 요약
일찍 출근해 늦게 퇴근하지만 경험치는 오르지 않는다. 나는 무엇을 하고 있는 걸까?
시간이 조각나 있다. 명확한 현재 시점 태스크가 없다. 고개를 들면 또 30 분이 지났다. 순식간에 또 주간 회의다……한 달 반이 지났다. 경험치는 조금도 오르지 않았다
이대로라면ただ의 코더로 전락한다 (3 년 근무 경험, 1 년 근무 경험)
아직 댓글이 없습니다