从URL的参数合并说起

1,734 阅读5分钟

是什么?

比如 URL1 :domain?a=1&b=2 ,URL2:domain?a=1&c=3 ,合并之后是 domain?a=1&b=2&c=3 。就是URL1的参数和URL2参数取出来,去重,再合并在一起,生成一个新的URL。

为什么?

「为什么」其实也就是场景了。比如a页面里有m个链接,点击这些链接的时候,希望a页面的URL参数追加到这些链接里。目的是把上游的URL参数传递下去,做到一些线索或者数据的追踪。

一些知识点

以下列举本方案用到一些的知识点,简单做些说明。

string相关函数

1. substring(start, stop)

该函数截取字符串的一个子段,两个参数的含义都是指字符串的下标,但返回的是下标之间的子串。第二个参数可选,不写默认到结尾。比如

'abcd'.substring(1, 2) // b
'abcd'.substring(2) // cd

2. substr(start, length)

第一个参数也是下标,第二个参数指想要的长度。第二个可选,不写也是到结尾。效果同上。

'abcd'.substr(1, 2) // bc
'abcd'.substr(2) // cd

Array相关函数

1. [].filter

作用是对数组进行过滤,接受一个回调函数,回调函数返回false的话,过滤掉该项。通常如下使用:

// 过滤掉空字符串
['a', '', 'c', '', 'e'].filter(it => it) // ['a', 'c', 'e']
// 过滤掉奇数
[1, 2, 3, 4, 5].filter(it => it%2 === 0) // [2, 4]

2. [].map

遍历数组,返回一个新的数组,通常使用如下:

[1, 3, 5, 7].map(it => it*2) // [2, 6, 10 ,14]

'a=1&b=2&c=3'.split('&') // ['a=1', 'b=2', 'c=3']
	.map(it => it.split('=')) // [['a', '1'], ['b', '2'], ['c', 3]]

3. [].reduce

遍历数组,返回你想要的任何数据类型,可以这样用:

// 求和
[1, 2, 3].reduce((o, i) => o+i, 0)

// 分组
[1, 3, 4, 8, 9].reduce((o, i) => {
	i % 2 === 0 ? o.even.push(i) : o.odd.push(i)
	return o
}, {odd: [], even: []})
// 代码对数组奇偶分组,结果如下
// {"odd":[1, 3, 9], "even":[4, 8]}

这个函数真的是一个好东西,特别棒,有时间可以多品品,对以后写代码绝对受益。

object相关函数

1. Object.keys

获取对象的所有keys,如下:

Object.keys({a:1, b:2}) // ['a', 'b']

2. Object.entries

获取对象的属性与对应的值组成的数组。如下:

Object.entries({a:1, b:2}) // [['a', 1], ['b', 2]]

该需求会使用到以上这些知识点,而且用到恰到好处。怎么做呢?我们一步步做,且看分享:

如何做?

1. 获取URL的query部分

主要逻辑:

  1. 没有query,也就是没有?号,直接返回空
  2. 有hash且在query之后,返回两者直接的部分
  3. 其余,返回query之后的部分。
function findUrlQuery (url) {
  const queryIndex = url.indexOf('?') + 1
  if (queryIndex < 1) return '' // 没有query直接返回空

  const hashIndex = url.indexOf('#')
  if (hashIndex > 0 && hashIndex > queryIndex) { // 如果有hash且在query之后
    return url.substring(queryIndex, hashIndex) // 获取?号与#之间的字符串。eg: a?m=1#cc
  } else { // 没有hash或者hash在query之前直接取query之后的部分
    return url.substring(queryIndex)
  }
}

注释已经写的很清楚,如何获取URL里的query部分,获取之后我们就可以解析各个参数了。

2. 解析query为对象

主要逻辑:

  1. 获取query
  2. 以&符分割为数组
  3. 过滤掉空的
  4. map成键值对
  5. 过滤掉空的建或者空的值
  6. 转化为对象
function parseUrlQuery (url) {
  if (!url) return {}

  return findUrlQuery(url) // 找到URL的query部分
    .split('&') // 按照&符分割。eg:a=1&b=2&c=3&&m=&
    .filter(it => it) // 过滤掉分割出来的空字符串
    .map(it => it.split('=')) // 再次分割。上面有例子
    .filter(kv => kv[0] && kv[1]) // 过滤掉key或者value为空的query
    .reduce((o, kv) => { // 转化为对象
      o[kv[0]] = kv[1] // 2. 给对象加属性及值
      return o // 3. 放入下一次迭代
    }, {}) // 1. 传入一个{}没有属性的对象。
}

这是相当清晰且简便的解析query的方法了,而且代码读起来朗朗上口。这是函数式编程的魅力。

3. 合并两个URL的query部分

主要逻辑:

  1. 获取目标URL的所有keys
  2. 获取源URL的所有键值对
  3. 过滤掉已经在目标URL里存在的key
  4. 把剩余的源键值对转化为query
  5. 如果目标URL存在query
  6. 直接把源query追加到目标URL的?号之后。
  7. 如果目标URL里只有hash,追加到hash之前。
  8. 除上,直接追加
/**
 * 合并两个URL的query部分
 * @param {string} target 被合并的URL
 * @param {string} source 待合并的URL
 */
function mergeUrlQuery (target, source) {
  if (!source) return target
  if (!target) return ''
  const targetQueryKeys = Object.keys(parseUrlQuery(target)) // 获取目标URL所有key
  const sourceQuery = Object.entries(parseUrlQuery(source)) // 获取源URL所有键值对
    .filter(kv => !targetQueryKeys.includes(kv[0])) // 过滤掉与目标URL重复的key
    .map(kv => kv.join('=')) // 重新把键值join在一起
    .join('&') // 再次join
  if (!sourceQuery) return target

  const queryIndex = target.indexOf('?') + 1
  if (queryIndex > 0) { // 如果目标URL里已有query,直接在?号后加新的query
    return target.substr(0, queryIndex) + sourceQuery + '&' + target.substr(queryIndex)
  } else { // 如果没有query
    const hashIndex = target.indexOf('#')
    if (hashIndex > 0) { // 且有hash。那就在hash之前追加新的query
      return target.substr(0, hashIndex) + '?' + sourceQuery + target.substr(hashIndex)
    } else { // 除上,直接追加query
      return target + '?' + sourceQuery
    }
  }
}

注释清晰明了。代码也是清晰明了。需要解释什么吗?不需要了,上面的知识点能够理解,这些代码就能理解。

总结

通过这篇文章,你至少了解

如何获取URL的参数?

这个是再常用不过的功能了,我这里的实现可以说是相当简单,可以细品。

还有另一个

如果把对象转换为URL参数?

例子中有,就在第三个函数里,相当简洁,可以细品。

当然,那些知识点还有整个思路也是相当受用的。