Манипулирование URL'ами в JavaScript

    Из года в год, сталкиваюсь с одной и той же проблемой. Как добавить, изменить или удалить параметр к некоторому адресу в строковом виде. Быстро и грязно это можно делать с помощью, например, регулярных выражений или найти каке-то готовое решение. Зачастую также может потребоваться, к примеру, подменить путь в адресе или изменить протокол с HTTP на HTTPS и т.д.

    В целом, это хочется делать просто и понятно. При этом хочется разумного компромиса. Я встречал некоторые библиотеки, которые дают мощный функционал, но при этом по объему — десятки килобайт JavaScript кода. Несколько десятков килобайт, чтобы, например, подменить параметр в QueryString? Эх…


    В общем, написал свое решение, которое нашел удобным, в первую очередь, для себя. При решении также позаимствовал накопленный опыт товарища Jan Wolter по поводу парсинга QueryString в JavaScript (осторожно — английский). Это хоть несколько и увеличило объем кода, зато способно избавить от некоторых проблем.

    Итак, мое решение можно забирать отсюда — github.com/Mikhus/jsurl. Лицензия — MIT, так что делайте все, что захотите. Минифицированный и пожатый gzip'ом код получился менее килобайта, что должно быть приемлимым. Зависимостей от каких-либо других библиотек не наблюдается. API — простой: сначала превращаем строку с адресом в объект Url и далее обращаемся с ним в зависимости от ситуации — либо как со строкой, либо как с объектом. Например, так:

    var u = new Url('http://user:pass@example.com:8080/some/path?foo=bar#anchor');
    // На заметку: если в конструктор не передавать параметр - будет использован текущий адрес документа
    
    // глянем, что у нас есть:
    alert( 'Исходный URL: ' + u);
    
    // теперь поменяем что-нибудь:
    u.hash = 'new-anchor';  // заменим в исходном адресе анкор на новый
    u.protocol = 'https';   // изменим протокол
    u.pass = '';            // уберем пароль
    u.query.foo = 'baz';    // изменим значение параметра foo в QueryString
    u.query.bar = [1,2,3];  // добавим новые параметры bar в QueryString со значениями 1, 2 и 3, т.е. bar=1&bar=2&bar=3
    
    // посмотрим, что получилось:
    alert( 'Измененный URL: ' + u);
    


    Любые замечания, доработки, найденные баги, форки и другие кошерные вещи — приветствуются и благодарятся.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 50

      0
      Как раз сегодня столкнулся с проблемой парсинга url, нашел medialize.github.io/URI.js/, но очень уж мощный, мне столько не надо, попробую jsurl, спасибо.
        0
        А разве массив не должен выглядеть так?
        bar[]=1&bar[]=2&bar[]=3
          +7
          Это PHP-style. В других случаях, то что для вас так очевидно может дать совершенно иной результат. Если вам нужно именно это — не стесняйтесь поменять под свои нужды.
            0
            Спасибо, я уже написал свой велосипед :) Только он для QML. Честно говоря, вариант без скобок до этого не встречал, но проверил, работает.
            Кстати, в этих параметрах можно даже объект передать, только это ненадежно и он при обратном преобразовании может превратится в массив :)
              0
              Спасибо, теперь понятно.
            +7
            Честно признать поражён что такая важная функция работы встроенного языка интерактивности веб-страниц до сих пор не реализована нативно, ведь она не менее важна, как работа с DOM, например.
              +1
              И тем не менее — имеем то, что имеем :)
                0
                Вообще много чего логичного в этих браузерах нет — проблема не только в этой области а во всей человеческой деятельности. Вон тут мысли проскакивали — поддержку jQuery в браузер «вкомпилить».

                Може т Вас немного успокоит тот факт, что «недоделанность» технологий/продуктов повышает стоимость труда разработчиков (что для разработчиков хорошо — ибо доп. хлеб)
                +8
                Parsing URLs with the DOM!

                // This function creates a new anchor element and uses location
                // properties (inherent) to get the desired URL data. Some String
                // operations are used (to normalize results across browsers).
                 
                function parseURL(url) {
                    var a =  document.createElement('a');
                    a.href = url;
                    return {
                        source: url,
                        protocol: a.protocol.replace(':',''),
                        host: a.hostname,
                        port: a.port,
                        query: a.search,
                        params: (function(){
                            var ret = {},
                                seg = a.search.replace(/^\?/,'').split('&'),
                                len = seg.length, i = 0, s;
                            for (;i<len;i++) {
                                if (!seg[i]) { continue; }
                                s = seg[i].split('=');
                                ret[s[0]] = s[1];
                            }
                            return ret;
                        })(),
                        file: (a.pathname.match(/\/([^\/?#]+)$/i) || [,''])[1],
                        hash: a.hash.replace('#',''),
                        path: a.pathname.replace(/^([^\/])/,'/$1'),
                        relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1],
                        segments: a.pathname.replace(/^\//,'').split('/')
                    };
                }
                


                var myURL = parseURL('http://abc.com:8080/dir/index.html?id=255&m=hello#top');
                 
                myURL.file;     // = 'index.html'
                myURL.hash;     // = 'top'
                myURL.host;     // = 'abc.com'
                myURL.query;    // = '?id=255&m=hello'
                myURL.params;   // = Object = { id: 255, m: hello }
                myURL.path;     // = '/dir/index.html'
                myURL.segments; // = Array = ['dir', 'index.html']
                myURL.port;     // = '8080'
                myURL.protocol; // = 'http'
                myURL.source;   // = 'http://abc.com:8080/dir/index.html?id=255&m=hello#top'
                
                  +5
                  я всегда буду смотреть исходный код перед тем, как комментировать…
                    +4
                    Мне этот вариант больше нравится.
                      +5
                      res = parseUrl("http://example.com/path?query=m%26m%3Ds%3E")
                      console.log(res.params)
                      Object { query="m%26m%3Ds%3E"}
                      
                      ай-ай-ай. А как же unescape?
                      +1
                      IPv6-адреса нормально парсятся?
                        0
                        Все зависит от того, что вы имеете ввиду под понятием нормально. jsfiddle.net/H55PU/1/ Так нормально? Тогда ответ — да, нормально.
                          0
                          Так — нормально.
                        +2
                        Хорошая, понятная, простая библиотечка. Спасибо!
                        Еще бы ее как amd модуль оформить и в репозиторий jam'а добавить — было бы супер!
                          +1
                          Спасибо за идею. Добавлено в jam.

                          Доступна для установки как:

                          jam install jsurl
                            0
                            Спасибо! Будем пользоваться.
                          0
                          Как быстро и надежно получить абсолютный путь из относительного? Вот так:
                          function resolveURL(baseURL, relativeURL) {
                          	var html = document.implementation.createHTMLDocument("");
                          	var base = html.createElement("base");
                          	base.setAttribute("href", baseURL);
                          	var img = html.createElement("img");
                          	img.setAttribute("src", relativeURL);
                          	html.head.appendChild(base);
                          	html.body.appendChild(img);
                          	return img.src;
                          }
                          
                            0
                            Как-то сложно, глупо и ещё и два тега в тело страницы добавляется. А картинку убрать за собой не судьба?

                            upd: хотя глуплю, добавляется всё это на фейковую страницу, но всё-равно операции с дом ради такого использовать — стрёмно.
                              0
                              Я в своё время делал для этого такой велосипед (т.к. не нравилась обработка #… при задании base):
                              function qualifyURL(url, base) {
                                  if (!url || /^([a-z]+:|#)/.test(url)) return url;
                                  var a = document.createElement('a');
                                  if (base) {
                                      a.href = base;
                                      a.href = a.protocol + (url.charAt(0) == '/' ? (url.charAt(1) == '/' ? '' : '//' + a.host) : '//' + a.host + a.pathname.slice(0, (url.charAt(0) != '?' && a.pathname.lastIndexOf('/') + 1) || a.pathname.length)) + url;
                                  } else {
                                      a.href = url;
                                  };
                                  return a.href;
                              };
                                +1
                                  0
                                  Принято, спасибо.
                                0
                                А что делать с такими параметрами: ?asd[color]=red&asd[company]=sotmarket?
                                Такой сложный параметр не будет обработан?
                                  +2
                                  Как я понимаю, можно использовать обычный для js синтаксиси обращения к объектам: u.query['asd[color]']
                                    0
                                    Свасибо, homm, вы понимаете абсолютно верно
                                  –5
                                  Позволяет менять адрес в адресной строке, без перезагрузки страницы. Как в VK.

                                  balupton.com/projects/jquery-history
                                    +9
                                    Нативный и простой вариант, если кто не знает:

                                    var parser = document.createElement('a');
                                    parser.href = "http://example.com:3000/pathname/?search=test#hash";
                                     
                                    parser.protocol; // => "http:"
                                    parser.hostname; // => "example.com"
                                    parser.port;     // => "3000"
                                    parser.pathname; // => "/pathname/"
                                    parser.search;   // => "?search=test"
                                    parser.hash;     // => "#hash"
                                    parser.host;     // => "example.com:3000"
                                    
                                      0
                                      Вот это супер! :)
                                        +2
                                        Цитирую товарища deepsweet:
                                        «я всегда буду смотреть исходный код перед тем, как комментировать…» :)
                                          –1
                                          А где то уже есть?
                                            0
                                            Да, там именно это и есть — github.com/Mikhus/jsurl/blob/master/url.js

                                            var
                                             ...
                                            link   = d.createElement( 'a'),
                                            ..;
                                            
                                            link.href = url;
                                            
                                            for (var i in map) {
                                            	self[i] = link[map[i]] || '';
                                            }
                                            


                                            Не находите?
                                              0
                                              Я же написал простой, а из этого кода сложно понять, что можно использовать к примеру a.hostname.
                                        +4
                                        a = new Url('//example.com?x&x=1')
                                        b = new Url('//example.com?x=1&x')
                                        
                                        a.query.x // "1"
                                        b.query.x // ["1", ""]
                                        

                                        Неаккуратно как-то, не?
                                          +3
                                          Вот за это я люблю хабр. Все найдут и все покажут :) Спасибо, bubug, баг исправлен
                                          0
                                          habrahabr.ru/post/65407/

                                          Все вроде написано еще в 2009 году. А колошматить здоровенные DOM-объекты с кучей свойств и зависимостей ради того чтобы получить пару готовых свойств — это моветон.
                                            +1
                                            Не холивара ради, а полноты картины для php.js (в частности parse_url, без минимизации 722 байта в сжатом виде.)
                                              +2
                                              Не парсит QueryString. Исходная задача, которую я, как писал выше, ставил перед собой — изменить часть URL (например, параметры QueryString или протокол или что-то еще) — не решается с помощью данной функции. Это так, для полноты картины, а не холивара ради :)
                                                0
                                                Для этого можно воспользоваться parse_str, а в обратную сторону http_build_query :)
                                                  +1
                                                  И это нам дает, как я понимаю, «без минимизации 722 байта в сжатом виде.»? :)
                                                    –1
                                                    Увы, все вместе выливается в два раза больший объем — 1516 байт. Уж для статистики, Ваш файл имеет размер 926 байт.
                                                    Обрабатывал JSMin + gzip

                                                    Чертов колдун, в рот мне ноги! :)))
                                                      0
                                                      Если возьмете уже минифицированный «url.min.js» из моего репозитория на Github'е и пожмете gzip'ом, получите 900 вместо 926, сэкономив примерно 2.8% :)
                                                        –1
                                                        А чем Вы пользуетесь для минификации?
                                                          +1
                                                          В данном случае просто этим — javascriptcompressor.com/
                                                            0
                                                            О! С этим обфускатором и 7z (gzip deflate) у меня получилось 878 и 1408 байт соответсвенно
                                              0
                                                +1
                                                В драфте нового стандарта предусмотрен конструктор URL. Работа с ним отличается от вашей реализации. Не претендуя на уникальность решения, вот мой пример реализации: gist.github.com/termi/5443716 (У меня используется старый драфт, сейчас функции для работы с search поменяли)
                                                  0
                                                  Спасибо, это действительно интересно. Вот теперь думаю, делать ли версию, совместимую с этим стандартом или подождать-посмотреть куда ветер подует…
                                                    0
                                                    Т.к. это Living Standard реализовывать совместимую версию можно уже сейчас, но актуальна она будет только после того, как хотя бы один браузер реализует эту версию стандарта

                                                Only users with full accounts can post comments. Log in, please.