input输入unicode零宽字符前端踩坑

简介: Unicode字符中有一类特殊的字符叫做零宽字符 ZWJ(zero width joiner),也叫非打印字符、不可见字符。正则的断言即叫零宽断言,意思即本身并不占用宽度,如比较出名的零宽空格

Unicode 统一码

也叫万国码、单一码,是计算机科学领域里的一项业界标准,包括字符集、编码方案等。统一码是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
4.png

Unicode 零宽字符

Unicode字符中有一类特殊的字符叫做零宽字符 ZWJ(zero width joiner),也叫非打印字符、不可见字符。正则的断言即叫零宽断言,意思即本身并不占用宽度,如比较出名的零宽空格ZWSP:U+200B。

零宽字符本质也是字符,对于计算机来说,它依然会占用空间,在 Unicode 字符集中拥有独立的编码,你在 Word 键入这一字符它仍会被计入字数统计,同样在代码中打印这类字符的长度可以看到也是会占用长度的。

零宽字符的宽度为 0,对于肉眼而言不可见,在我们常用的一些软件中并不会显示,比如浏览器、Excel...,这些字符通常被用于在阿拉伯文与印度语系等文字中,用于控制字符间是否产生连字的效果,在其他大多数语言中,你并不能直接打出这类字符。

常见的几种零宽字符有:

  • 零宽空格:U+200B
  • 零宽连接符:U+200D,常见的复杂Emoji表情即用到了该字符,用于表示多字符关系从而合成复杂新字符
  • 零宽非连接符:U+200C
  • 零宽非断空格符:U+FEFF
  • 左至右符:U+200E
  • 右至左符:U+200F
  • 蒙古文元音分隔符:U+180E

怎么能看见零宽字符?

直接复制到开发工具如 vscode 里,是可以直接看到这类字符的 unicode 码的。

在 windows 记事本中-右键,选择 “显示 Unicode 控制字符”,也可以‭看到这类特殊符号。
1.png

navicat 中查看mysql中的数据,也可以像记事本一样右键选择“显示 Unicode 控制字符”,光标聚焦的时候也可以看到有特殊符号显示出来。
2.png

还有复制粘贴进网页、记事本、输入框中,移动光标的时候,也可以发现,这类字符也是需要靠光标移动的。

零宽字符的应用场景

  • 字符加密解密
  • 数据防爬
  • 隐形水印
  • 缩短网址
  • 敏感词分隔过审
  • 空白评论、空白用户名
  • 输入内容翻转如:利用[U+202E]和[U+202C]这两个零宽字符,花了‮7万5‬千,其中的7万5会被翻转成5万7

零宽字符踩坑

之所以发现这个问题,也是从客户的一个 excel 模板中粘贴电话号码,最终发现莫名有特殊字符,开始还以为是输入组件的bug。
3.png

数据库是支持这种字符的,所以如果前后端未进行数据过滤,是会被直接存储到数据库中的,正常也是没什么问题。但是如果有搜索功能,用户在应用中直接手动输入检索的时候,因为手动输入是不包含特殊字符的,所以有可能导致匹配不出来,给人的感觉就是:明明看着一毛一样,一个字母一个字母对着敲的,咋就搜不出来呢!

在前端表单中,如果用户输入时是直接从其他地方如 excel 里复制的,就有可能包含这类看不见的零宽字符,据说从 iphone 手机的通讯录里复制的电话号码粘贴进excel中就有可能包含零宽字符。

当用户提交时,前端在控制台打印或者network里看提交的接口参数里也是看不到这类字符的,还有如果 input 输入框有限制最大输入长度 max-length,零宽字符也是会占用输入框的长度的。

前端解决方案

前端解决可以直接在输入或提交表单的时候通过 unicode 码去过滤这类特殊字符,但是每个输入框都要这样去做工作量太大了,而且都是重复性工作,最终想到了用 vue 的全局指令自己封装一个 v-trim 统一去过滤数据,正好项目中用的 element-ui 的输入框组件 trim 修饰符也有bug,直接一块解决了,后期使用和维护都比较方便。

directives目录下的 index.js,利用webpack提供的 require.context,自动注册目录下的指令文件,直接将文件名作为指令名:

// require.context是webpack提供的api用来创建上下文作用域
// 三个参数分别为:目录、是否搜索子文件夹、匹配文件名的正则
const fileInfo = require.context('./', false, /^\.\/(?!index.js).*\.js$/)
const fileNameList = fileInfo.keys() || []

export default {
   
   
  // 插件为对象时,Vue.use()会默认调用install
  install: function(Vue) {
   
   
    fileNameList.forEach(fileName => {
   
   
      const directive = fileInfo(fileName)
      // 提取路径中的文件名作为指令名称
      const directiveName = fileName.replace(/^(\.\/)([a-zA-Z0-9-_]+)\.js$/, '$2')
      // 注册指令
      Vue.directive(directiveName, directive.default || directive)
    })
  }
}

directives目录下的 trim.js,去除输入框首尾空格和过滤零宽字符:

/**
 * 去除输入框首尾空格
 * ❶ element-ui 的输入框el-input加了 trim 会导致字符中间不能输入空格(文档上有说:不支持 v-model 修饰符)
 * ❷ 需同时过滤掉输入内容里的零宽字符,一般是用户直接从 excel 中直接复制粘贴进来的内容
 */

const TRIM = {
   
   
  inserted: el => {
   
   
    // 兼容第三方输入组件,如el-input
    const inputTag = el.tagName !== 'INPUT' ? el.querySelector('input') : el
    const handler = function(event) {
   
   
      const oldVal = event.target.value || ''
      // 过滤掉零宽字符和首尾空格
      const newVal = oldVal.replace(/[\u200b-\u200f\uFEFF\u202a-\u202e]/g, '').trim()
      if (oldVal != newVal) {
   
   
        event.target.value = newVal
        dispatchEvent(inputTag, 'input')
      }
    }
    el.inputTag = inputTag
    el._blurHandler = handler
    inputTag.addEventListener('blur', handler)
  },

  unbind: el => {
   
   
    const {
   
    inputTag } = el
    inputTag.removeEventListener('blur', el._blurHandler)
  }
}

function dispatchEvent(el, type) {
   
   
  const evt = document.createEvent('HTMLEvents')
  evt.initEvent(type, true, true)
  el.dispatchEvent(evt)
}

export default TRIM

在 vue 入口文件 main.js 中注册全局指令:

Vue.use 本身是一个函数,如果需要注册的插件是一个对象,就需提供 insatll 方法,Vue 会去执行它,同时传递一个Vue构造函数作为第一个参数,以及 use 中的其他参数,不依赖Vue去运行的我们可以直接用 Vue.prototype 去挂载到原型上就行了;需要和 Vue 构造函数进行交互的时候,才使用 Vue.use,如全局组件(像iview、element-ui)、全局过滤器、全局指令这些。

import Vue from 'vue'
import App from './App'
import router from './router'

// 全局指令
import directives from '@/directives'
Vue.use(directives)

new Vue({
   
   
  el: '#app',
  router,
  template: '<App/>',
  components: {
   
   
    App
  },
})

在vue页面中的输入框中使用:

<template>
  <div>
    <input v-trim />
    <el-input v-trim />
  </div>
</template>
相关文章
|
数据挖掘
深入分析:ERP系统的优势与劣势
深入分析:ERP系统的优势与劣势
942 3
|
Android开发 iOS开发 MacOS
APP备案公钥、证书MD5指纹/签名MD5值获取最简单方法
APP备案公钥、证书MD5指纹/签名MD5值获取方法,Android安卓平台、Windows平台、macOS平台,三个平台获取方法, Android平台使用 APP备案助手,各大安卓应用市场搜索 APP备案助手 即可,Windows/macOS平台使用jadx-gui工具。
7006 2
|
Linux
阿里云官方yum源
阿里云官方yum源
72645 0
|
前端开发 JavaScript 数据安全/隐私保护
前端 JS 经典:零宽字符
前端 JS 经典:零宽字符
418 0
|
8月前
|
存储 人工智能 安全
如何调用 DeepSeek-R1 API ?图文教程
首先登录 DeepSeek 开放平台,创建并保存 API Key。接着,在 Apifox 中设置环境变量,导入 DeepSeek 提供的 cURL 并配置 Authorization 为 `Bearer {{API_KEY}}`。通过切换至正式环境发送请求,可实现对话功能,支持流式或整体输出。
3091 15
|
11月前
|
数据可视化 搜索推荐 小程序
LowCode:低代码平台,2024国内十大主流低代码平台年终盘点
低代码平台是一种加速软件开发的高效工具,通过可视化和模型驱动的方式减少手动编码,快速构建应用。它能显著提升开发效率,降低开发成本,支持企业快速实现数字化转型。国内主流低代码平台如织信Informat、白码、钉钉宜搭等,各具特色,可根据企业需求选择合适的平台。私有化部署更是确保数据安全和定制化的重要手段。
|
数据安全/隐私保护 iOS开发 MacOS
Mac终端出现 brew command not found 解决
Mac终端出现 brew command not found 解决
699 3
|
编解码 JSON 自然语言处理
Qwen2-VL 全链路模型体验、下载、推理、微调实战!
经过了一年的不懈努力,今天通义千问团队对 Qwen-VL 模型进行重大更新——推出 Qwen2-VL。那么Qwen2-VL 有什么新功能呢?一起来看一下吧
Qwen2-VL 全链路模型体验、下载、推理、微调实战!
|
12月前
|
缓存 UED 网络架构
网站404该怎么解决
网站404错误通常表示用户尝试访问的网页不存在或无法找到
1620 0
CSS3 flex 布局在 wrap 换行模式下,让中间指定元素换行
CSS3 flex 布局在 wrap 换行模式下,让中间指定元素换行
1500 0