メインコンテンツへ移動

URL を解析するフロントエンド方案

無料2016-08-27#JS#利用a标签解析URL#正则解析URL#javascript URL class#如何解析URL串

毎日忙しいが、何に忙しかったか覚えていない

はじめに

7 月 11 日〜8 月 27 日、1 ヶ月半が過ぎ去った。週単位で時間が過ぎるのが特に速く感じる。

毎日忙しいが、何に忙しかったか覚えていない。期間中

  • 新しい本を全く開いていない(ブログ執筆時に Node や JS を少し開いた程度。内容はかなり忘れてしまった)

  • エッセイを 1.5 編読んだ

  • Kindle を 2 回充電したが、一度も使用していない

  • 日本語学習は第 7 課まで終了

  • 月曜日は FEX 周刊(一度だけ火曜日に発行されたことも)、金曜日は奇舞周刊(今週は凹凸实验室からの mock 淘宝造物節パノラマが良かった)

最近何の本を読んでますか、おすすめはありますか?

日本語の本、まとまった時間がないので。日常業務に慣れれば時間ができるでしょう

本当にそうか?なぜまとまった時間がないのか?

一。問題の概要

URL 規格の方法で host を取得し、host を判定してください。単一の文字で判定しないでください

シーンはこうだ。現在のページの query stringurl パラメータが含まれており、そこから 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?&&#123http:// @www.abc.com?query=2#45
// これは?
http://www.example.com:8899 @www.abc.com/what??key=val?&&#123http://?query=2#45
// これは?
http://www.example.com:$88;9,9 @www.abc.com$/what??key=val?&&#123http://?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?&&#123http://?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?&&#123http://?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?&&#123http://?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?&&#123http://?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 分が過ぎ去った。ふと気づくと、また週例会だ……1 ヶ月半が過ぎ去った。経験値は少しも上がっていない

このままでは、ただのコーダーに堕ちる(3 年の仕事経験、1 年の仕事経験)

参考資料

コメント

コメントはまだありません

コメントを書く