jQuery.param()的纯JavaScript等效方法

22

jQuery.param() 接受一个键值对数组,并将其转换为一个字符串,您可以在HTML请求中用作查询字符串。例如,

a = {
      userid:1,
      gender:male
    }

会被转换为

userid=1&gender=male

我正在尝试在Google Apps脚本的服务器端调用外部API,这些API需要较长的查询字符串。我本来会使用jQuery的param函数,但在Google的服务器端似乎没有简单的方法可以使用jQuery。
你能给我一段纯JavaScript代码,实现相同的功能吗?
这是jQuery的实现在这里,但我不想冒险简单地复制它并删除与“traditional”相关的代码,以免遗漏任何关键细节。

4
我发现这是一个很好的解决方案:https://www.npmjs.com/package/jquery-param - James Gentes
8个回答

32
你也可以用纯JavaScript来做,但是你需要写更多的代码行数。试试这个:

a = {
      userid:1,
      gender: "male",
    }

url = Object.keys(a).map(function(k) {
    return encodeURIComponent(k) + '=' + encodeURIComponent(a[k])
}).join('&')

console.log(url)

输出是这样的:
userid=1&gender=male

1
谢谢Amynut。这就是我想的,但我想确保没有遗漏任何微妙之处,并以最JSic(对于JavaScript而言,Pythonic是指Python)的方式完成。在jquery实现中的一个微妙之处是“return s.join("&").replace(r20, "+");”。你不需要实际上用+号替换它吗?MDN建议这在POST而不是GET时是相关的。https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Description - Neil
9
嵌套键怎么样?你需要递归或将对象展开。 - alexserver
1
值得一提的是,它不支持数组/对象。{a:[1,2],b:{c:3}} 应该转换为 a[]=1&a[]=2&b[c]=3 而不是 a=1,2&b=[object Object] - oriadam

4

ES6版本允许像使用encodeURI(getUrlString({a: 1, b: [true, 12.3, "string"]}))一样转换嵌套对象和数组。

getUrlString (params, keys = [], isArray = false) {
  const p = Object.keys(params).map(key => {
    let val = params[key]

    if ("[object Object]" === Object.prototype.toString.call(val) || Array.isArray(val)) {
      if (Array.isArray(params)) {
        keys.push("")
      } else {
        keys.push(key)
      }
      return getUrlString(val, keys, Array.isArray(val))
    } else {
      let tKey = key

      if (keys.length > 0) {
        const tKeys = isArray ? keys : [...keys, key]
        tKey = tKeys.reduce((str, k) => { return "" === str ? k : `${str}[${k}]` }, "")
      }
      if (isArray) {
        return `${ tKey }[]=${ val }`
      } else {
        return `${ tKey }=${ val }`
      }

    }
  }).join('&')

  keys.pop()
  return p
}

2

这里大多数解决方案在处理数组/对象值时都会失败。

以下代码将支持一级数组/对象:

const param = obj => Object.entries(obj).map(
    pair => Array.isArray(pair[1]) ?
        pair[1].map(x=>`${encodeURIComponent(pair[0])}[]=${encodeURIComponent(x)}`).join('&') :
        typeof pair[1] === 'object' ? 
        Object.entries(pair[1]).map(x=>`${encodeURIComponent(pair[0])}[${x[0]}]=${encodeURIComponent(x[1])}`).join('&') :
        pair.map(encodeURIComponent).join('=')).join('&')

示例:

param({a:1,b:'b'})
"a=1&b=b"

param({a:1,b:2,c:[1,2],d:{e:'abc',f:4}});
"a=1&b=2&c[]=1&c[]=2&d[e]=abc&d[f]=4"

param({a:1,b:2,c:[1,2,'"'],d:{e:'a"b[c]',f:4}});
"a=1&b=2&c[]=1&c[]=2&c[]=%22&d[e]=a%22b%5Bc%5D&d[f]=4"

但是请注意,它在第二层嵌套时失败:

param({a:1,b:{c:[1,2]}});
"a=1&b[c]=1%2C2"

更新:

如果您不需要支持IE11,请使用URLSearchParams API。请参见error这个答案

function urlString(params)
{
    if (!params)
        return '';
    const urlParams = new URLSearchParams();
    for (const [name, value] of Object.entries(params))
        urlParams.append(name, value || '');
    return urlParams.toString();
};

1

@Amaynut的回答很棒。但我做了一些简化:

const obj = {
  userid: 1,
  gender: 'male'
}

const params = Object.keys(obj).map((k) => encodeURIComponent(k) + '=' + encodeURIComponent(obj[k])).join('&')

或者使用ES6模块化进行模块化:

util.js

export default {
  params (obj) {
    return Object.keys(obj).map((k) => encodeURIComponent(k) + '=' + encodeURIComponent(obj[k])).join('&')
  }
}

and use like this:

import util from './util'

const q = {
  userid: 1,
  gender: 'male'
}

const query = util.params(q)

console.log(query)

1
或者 Object.entries(obj).map(pair => pair.map(encodeURIComponent).join('=')).join('&') - Albin
太棒了@Albin,但要注意这些es的新功能的兼容性,祝你好运:-D - csdxf
当然可以!使用 polyfills 来保证兼容性。例如,Polyfill.io 就可以解决这个问题。 - Albin
感谢您的变化。我发现@amaynut的代码更易于阅读。在我看来,一行代码并不总是表示简化。 - basecode
值得一提的是,它不支持数组/对象。{a:[1,2],b:{c:3}} 应转换为 a[]=1&a[]=2&b[c]=3 而不是 a=1,2&b=[object Object] - oriadam
Google Apps Script不支持导入。 - Rubén

0
ES6 gives us some nice primitives:

    // Function that parses an object of string key/value params to build up
    // a string of url params
    // requires an object with no nested values
    export function parseUrlParams(urlParams) {
        const joinByEquals = (pair) => pair.join('=')
        const params = Object.entries(urlParams).map(joinByEquals).join('&')
        if (params) {
            return `?${params}`
        } else {
        return ''
        }
    }

在这里查看它的运行效果: https://www.webpackbin.com/bins/-KnpOI6hb1AzTDpN3wS7


Google Apps Script 不支持 export - Rubén

0
这有点令人沮丧。这里的解决方案似乎都不能真正地为任何JSON对象生成实际的“x-www-form-urlencoded”数据,相当于JQuery。有些接近了,但在子项上失败了。我从几个解决方案中拼凑了代码,得到了适用于任何JSON对象的工作版本:
function formData (obj) {
  return Object.keys(obj).map((k) => encodeURIComponent(k) + '=' + encodeURIComponent(JSON.stringify(obj[k]))).join('&')
}

顺便说一下,我不得不在FastSpring的API中使用这个,因为由于某种奇怪的原因,他们在2020年只接受x-www-form-urlencoded数据。因为这是近十年来第一次API没有接受JSON,所以我花了一整天的时间在这上面。


0
这是一个更新的版本,来自@oriadam / @error的URLSearchParams答案,支持多级嵌套。
const urlString = (data) => {
    if (data == null) { return ""; }
    
    const urlParams = new URLSearchParams();
    const rbracket = /\[\]$/;

    const add = (name, valueOrFunction) => {
        const value = typeof valueOrFunction === "function" ? valueOrFunction() : valueOrFunction;
        urlParams.append(name, value == null ? "" : value);
    };

    const buildParams = (prefix, obj) => {
        if (Array.isArray(obj)) {
            obj.forEach((value, index) => {
                if (rbracket.test(prefix)) {
                    add(prefix, value);
                } else {
                    const i = typeof value === "object" && value != null ? index : "";
                    buildParams(`${prefix}[${i}]`, value);
                }
            });
        } else if (typeof obj === "object" && obj != null) {
            for (const [name, value] of Object.entries(obj)) {
                buildParams(`${prefix}[${name}]`, value);
            }
        } else {
            add(prefix, obj);
        }
    };

    if (Array.isArray(data) || data instanceof NodeList) {
        // If an array or NodeList was passed in, 
        // assume that it is a collection of form elements:
        data.forEach(el => add(el.name, el.value));
    } else {
        for (const [name, value] of Object.entries(data)) { 
            buildParams(name, value);
        }
    }

    return urlParams.toString();
};

这是基于jQuery 3.5.1源代码的,因此对于相同的输入,它应该产生与$.param相同的输出。唯一的区别是空格将被编码为+而不是%20
我已经提供了Fiddle,其中使用了jQuery测试用例,所有测试都通过了。

编辑:如果您需要支持不支持URLSearchParams类的环境,则需要使用数组和encodeURIComponent方法的组合,如其他答案所示:

const urlString = (data) => {
    if (data == null) { return ""; }
    
    const urlParams = [];
    const rbracket = /\[\]$/;

    const add = (name, valueOrFunction) => {
        let value = typeof valueOrFunction === "function" ? valueOrFunction() : valueOrFunction;
        if (value == null) { value = ""; }

        urlParams.push(`${encodeURIComponent(name)}=${encodeURIComponent(value)}`);
    };

    const buildParams = (prefix, obj) => {
        if (Array.isArray(obj)) {
            obj.forEach((value, index) => {
                if (rbracket.test(prefix)) {
                    add(prefix, value);
                } else {
                    const i = typeof value === "object" && value != null ? index : "";
                    buildParams(`${prefix}[${i}]`, value);
                }
            });
        } else if (typeof obj === "object" && obj != null) {
            for (const [name, value] of Object.entries(obj)) {
                buildParams(`${prefix}[${name}]`, value);
            }
        } else {
            add(prefix, obj);
        }
    };

    if (Array.isArray(data)) {
        // If an array was passed in, 
        // assume that it is a collection of form elements:
        data.forEach(el => add(el.name, el.value));
    } else {
        for (const [name, value] of Object.entries(data)) { 
            buildParams(name, value);
        }
    }

    return urlParams.join("&");
};

更新的测试Fiddle


Google Apps Script 不支持 NodeList - Rubén
URLSearchParams 也不受支持。 - Rubén
@Rubén 嗯,那很糟糕。至少很容易删除 NodeList 引用并将 URLSearchParams 替换为数组 - 请参阅我的更新答案。 - Richard Deeming
感谢您的回复和更新,Richard。虽然这并非必须,但在Google Apps Script上测试代码可能是个好主意,因为这是OP的目标平台。您已经这样做了吗?另外,请注意问题和答案都有版本历史记录。考虑到这一点,我认为没有必要将某个部分标记为“编辑”(关于此事在[meta]上有讨论)。 - Rubén

-1
export function param( params ) {
    const p = new URLSearchParams;
    for( const [ key, value ] of Object.entries( params ) ) {
        p.set( key, String( value ) );
    }
    return p.toString();
}

3
请添加关于上面代码如何帮助的解释。 - Nae
Google Apps Script 不支持 export - Rubén

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接