在纯前端开发中,地址栏搜索词经常遇到编码不一致的问题。浏览器默认使用UTF-8,但百度、谷歌等搜索引擎对搜索词进行URL编码时通常使用GBK(或GB2312)编码,导致通过`decodeURIComponent`无法正常解码,出现乱码。本文总结一种不依赖后端,纯前端实现GBK/GB2312编解码的方案,核心思路是利用浏览器的`form`提交和`iframe`截获,或者利用`<script>`标签动态加载指定字符集的数据URI,完成字符串转换。
一、编码(支持GBK和GB2312)
编码的核心原理是:创建一个隐含的`<form>`表单,设置其`accept-charset`为目标字符集(如gb2312或gbk),并将表单的`target`指向一个隐藏的`<iframe>`。当表单提交时,浏览器会自动根据`accept-charset`对表单数据进行URL编码。通过一个回调函数`__encode__iframe__callback__`捕获iframe地址栏中的编码结果。注意:这种方案依赖同源策略,将表单的action设为当前页面URL,因此回调函数必须放在页面最前面。
代码实现:- if (parent.__encode__iframe__callback__) {
- parent.__encode__iframe__callback__(location.search.split('=')[1]);
- window.close();
- }
- function GBKEncode(str, charset, callback) {
- var form = document.createElement('form');
- form.method = 'get';
- form.style.display = 'none';
- form.acceptCharset = charset;
- // 兼容IE:修改文档字符集
- if (document.all) {
- window.oldCharset = document.charset;
- document.charset = charset;
- }
- var input = document.createElement('input');
- input.type = 'hidden';
- input.name = 'str';
- input.value = str;
- form.appendChild(input);
- form.target = '__encode__iframe__';
- document.body.appendChild(form);
- if (!window['__encode__iframe__']) {
- var iframe = document.createElement('iframe');
- iframe.setAttribute('name', '__encode__iframe__');
- iframe.style.display = 'none';
- iframe.width = '0';
- iframe.height = '0';
- iframe.scrolling = 'no';
- iframe.allowtransparency = 'true';
- iframe.frameborder = '0';
- iframe.src = 'about:blank';
- document.body.appendChild(iframe);
- }
- window.__encode__iframe__callback__ = function(result) {
- callback(result);
- if (document.all) {
- document.charset = window.oldCharset;
- }
- };
- form.action = window.location.href;
- form.submit();
- setTimeout(function() {
- form.parentNode.removeChild(form);
- iframe.parentNode.removeChild(iframe);
- }, 1000);
- }
- // 使用示例(注意:需先定义callback函数)
- GBKEncode('需要编码的字符', 'gb2312', function(data) { console.log(data); });
复制代码 使用Promise封装编码,方便异步调用:- var encode = function(str, charset) {
- charset = charset || 'gbk';
- return new Promise(function(resolve, reject) {
- try {
- GBKEncode(str, charset, function(data) {
- resolve(data);
- });
- } catch (e) {
- resolve('字符编码错误.' + e.toString());
- }
- });
- };
复制代码
二、解码(支持GBK、GB2312、Base64)
解码的原理是利用`<script>`标签的`charset`属性。我们创建一个带有`charset`属性的数据URI脚本`data:text/javascript;charset=gbk,...`。当浏览器加载这个脚本时,会按照指定的字符集解释脚本内容,从而将对字符串进行解码。具体做法是:定义一个随机函数名,将待解码的字符串直接作为JavaScript代码的一部分(注意必须保证字符串内容安全,避免XSS)。脚本加载后执行预设的回调函数。
代码实现:- function randomId() {
- var text = '';
- var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
- for (var i = 0; i < 5; i++) {
- text += possible.charAt(Math.floor(Math.random() * possible.length));
- }
- return text;
- }
- function _decode(str, charset, callback) {
- var script = document.createElement('script');
- var id = randomId();
- script.id = '_urlDecodeFn_' + id;
- window['_urlDecodeFn_' + id] = callback;
- var src = 'data:text/javascript;charset=' + charset +
- ',_urlDecodeFn_' + id + '("' + str + '");';
- src += 'document.getElementById("_urlDecodeFn_' + id + '").parentNode.removeChild(document.getElementById("_urlDecodeFn_' + id + '"));';
- script.src = src;
- document.body.appendChild(script);
- }
- // 使用示例
- _decode('%E4%BD%A0%E5%A5%BD', 'gb2312', function(data) { console.log(data); });
复制代码 Promise封装解码:- var decode = function(str, charset) {
- charset = charset || 'gbk';
- return new Promise(function(resolve, reject) {
- try {
- _decode(str, charset, function(data) {
- resolve(data);
- });
- } catch (e) {
- resolve('字符解码错误.' + e.toString());
- }
- });
- };
复制代码
三、注意事项
1. 跨域限制:编码方案中iframe的src为当前页面的URL,因此要求当前页面可以与自身通信(同源);解码方案使用data:脚本不存在跨域问题。
2. 安全性:解码方案将用户字符串直接嵌入脚本内容,若字符串包含特殊字符可能导致脚本执行异常或XSS。确保输入的字符串是经过URL编码的,或对特殊字符进行转义。
3. 兼容性:IE浏览器(使用document.all判断)需要在编码时临时修改`document.charset`;其他现代浏览器可直接使用`acceptCharset`。
4. 清理资源:编码方案在1秒后移除表单和iframe,避免DOM泄漏。
这种纯前端编解码方案在需要从浏览器地址栏获取GBK编码参数时非常实用,避免了后端代理或手动查表转换。实际使用中,建议先检测是否需要编解码(例如通过正则判断是否包含非ASCII字符或%xx编码),再调用对应函数。
参考:该方案核心思路来自前人的实践,此处对代码进行了整理和Promise封装,便于在项目中使用。 |