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>
相关文章
|
6天前
|
存储 数据处理 开发者
ABAP 如何把 unicode 代码点转换成字符
ABAP 如何把 unicode 代码点转换成字符
22 0
|
3天前
43.编写一个程序,判断用户输入的字符是否是数字,若是数字,则输出“a numerical character”
43.编写一个程序,判断用户输入的字符是否是数字,若是数字,则输出“a numerical character”
14 3
|
6天前
|
Python
Python小技巧:判断输入是否为汉字/英文/数字
Python小技巧:判断输入是否为汉字/英文/数字
|
6月前
验证input输入框(字母,数字,符号,中文)
验证input输入框(字母,数字,符号,中文)
Golang:输出Emoji表情符号
Golang:输出Emoji表情符号
179 0
Golang:输出Emoji表情符号
|
机器学习/深度学习 人工智能 自然语言处理
[oeasy]python0129_unicode_中文字符序号_十三道大辙_字符编码解码_eval_火星文
[oeasy]python0129_unicode_中文字符序号_十三道大辙_字符编码解码_eval_火星文
107 0
[oeasy]python0129_unicode_中文字符序号_十三道大辙_字符编码解码_eval_火星文
|
编解码
Debug栏打印时自动把Unicode编码转化成汉字
Debug栏打印时自动把Unicode编码转化成汉字
129 0
Debug栏打印时自动把Unicode编码转化成汉字
|
存储 开发工具 git
[oeasy]python0015_十六进制_hexadecimal_字节形态_hex函数
[oeasy]python0015_十六进制_hexadecimal_字节形态_hex函数
106 0
[oeasy]python0015_十六进制_hexadecimal_字节形态_hex函数
|
数据采集 存储 开发工具
[oeasy]python0013_ASCII码表_英文字符编码_键盘字符
[oeasy]python0013_ASCII码表_英文字符编码_键盘字符
105 0
[oeasy]python0013_ASCII码表_英文字符编码_键盘字符
|
Go 数据安全/隐私保护
Go语言写的一个大小写字母加数字密码生成并写入Excel的程序
看过不少用Go语言写的大小写加上数字组成的密码,但是发现大部份的代码混编的密码中数字有一定的机率不会出现,所以写了这个代码以保证一定会出现数字,并把容易混淆的字母及数字取消,并保存成excel。
175 0