blog
blog copied to clipboard
英普特历险记
title: 英普特历险记 date: 2017-11-08
冢上有光怪,云气五色,上属于天,曼延数里。
前端世界就像格林童话中的黑森林,充满了冒险与惊奇,任一不起眼的小地方,里面潜藏的危险就够你狠狠地吃上一壶。一名前端漫游者,如果在没有全副武装的情况下随便进入一个领域,唯一能做的就是收拾好耐心,准备迎接一个光怪陆离的世界吧。
input标签作为表单提交中的常用元素,每次见到它的时候,我的感觉就是这样的。
就是这样一个小元素,最近在做一个需求的时候,可是让我好好confuse了一段时间。
一般来说,当用户提交表单数据时,前端都会对输入框中的数据做出一些限制,一个十分常见的需求就是对用户输入的字符的个数进行限制,比如今次产品想要限制用户输入的字符在十个以内。本着简单快速的原则,我直接定位到之前的一段代码--{val|maxlength:10}
,使用maxlength过滤器来进行input中的val个数限制。完美,add、commit、push、收工。
不一会,测试mm过来和你说:唉,为什么这个输入框在输入英文的时候可以正常截断,输入中文的时候却只能截断一半啊,你看就像这样,先给你记个bug啊,回头你给看看。
不对呀,难道这个过滤器工具有做了什么处理吗?先看一下源码吧。
_p.maxlength = {
get: function(value) {
return value || '';
},
set: function(value, limit) {
return _p.substr(value || '', limit).replace(/^\s+/g, '');
}
};
_p.substr = function(str, n) {
var r = /[^\x00-\xff]/g;
if (str.replace(r, 'mm').length <= n) {
return str;
}
var m = Math.floor(n / 2);
for (var i = m; i <= str.length; i++) {
if (str.substr(0, i).replace(r, 'mm').length > n) {
return str.substr(0, (i - 1));
}
}
return str;
};
可以发现,对于输入框中的中文字符,substr工具函数会将其替换为'mm',最后计算出的字符长度就会变成2个,直接导致输入中文的情况下,可输入长度减半。为什么公共代码库会对输入的内容进行这样的处理,这就需要我们复习一下JS中编码知识了。
javascript程序是使用Unicode字符集编写的。Unicode是ASCII和Latin-1的超集,并支持地球上几乎所有的语言并为其文字系统中的每个字符单位分配一个唯一的整数。ECMAScript3要求JavaScript必须支持Unicode2.1及后续版本,ECMAScript5则要求支持Unicode3及后续版本。所以,我们编写出来的javascript程序,都是使用Unicode编码的。
JavaScript采用的是UTF-16即16位编码的字符串,所以每个JavaScript字符串的每个元素都有一个16位的值,一个JavaScript字符串的元素则为一个16位的代码单元。众所周知,在Unicode编码中,英文占一个字节,汉字占两个字节。但是JavaScript中字符串的length属性是针对代码单元的,所以无论对于英文字符串还是中文字符串,理论上它们显示的长度应该一致。所以对于上面的代码,唯一的解释就是产品要求,输入的字符按照字节进行截断。真是反人类。
既然如此,我们就简单粗暴一点,直接按照字符串长度进行截断,这样岂不是简单高效。于是将上面代码改一改,得到下图的效果。
_p.maxlength = {
get: function(value) {
return value || '';
},
set: function(value, limit) {
var len = value.length;
var result = value;
if (len > 10) {
result = value.substr(0, len);
}
return result;
}
};
看起来好像完全能够符合我们的要求,但是细心的同学会发现,我在用中文输入法的时候,当敲到接近十个字符的时候,是一个拼音一个拼音的敲出来的。至于为什么要这么做,因为这种实现方式有一个致命的缺陷,就是当我快速的敲出一长串的汉字拼音时,会出现下面这种坑爹情况。
原因就在于中文输入法的虚拟拼音也被oninput事件监听。因为输入法会自动提供虚拟拼音,当虚拟拼音的个数超过限制时,字符被截断,无法生成汉字。
如何解决这个问题其实也很简单,浏览器提供的compositionstart及compositionend事件能够完美解决。先来看一下这两个事件的使用场景:
compositionstart 事件触发于一段文字的输入之前(类似于 keydown 事件,但是该事件仅在若干可见字符的输入之前,而这些可见字符的输入可能需要一连串的键盘操作、语音识别或者点击输入法的备选词。 当文本段落的组成完成或取消时, compositionend 事件将被激发 (具有特殊字符的激发, 需要一系列键和其他输入, 如语音识别或移动中的字词建议)。
需要注意的是,在实际使用中,还要结合input事件。毕竟输入不止有中文,如果没有虚拟输入,那么compositionstart和compositionend是无法触发的。
//compositionstart时,禁止input
var inputLock = false;
this.$refs.input.addEventListener('compositionstart', function() {
inputLock = true;
});
this.$refs.input.addEventListener('compositionend', function() {
var val = this.value;
inputLock = false;
if (val.length > 10) {
this.value = val.substring(0, 10);
}
});
this.$refs.input.addEventListener('input', function() {
if (!inputLock) {
var val = this.value;
if (val.length > 10) {
this.value = val.substring(0, 10);
}
}
});
最终实现效果如图。
看起来我们已经实现了产品要求的功能了,可以收工回家了。但是我们是不是忘了什么,有没有更简单的方法?input标签里有一个maxlength属性,为什么不拿来试一试呢?
<input type="text" maxlength=10>
把之前的代码全都删除,居然能完美的实现我们的需求,Orz。原来绕了一圈,答案就在题目里。。
后记
关于maxlength属性,mdn上有这么一段描述,“If the value of the type attribute is text, email, search, password, tel, or url, this attribute specifies the maximum number of characters (in UTF-16 code units) that the user can enter. For other control types, it is ignored. ”
比如说,如果input的type=number,那么maxlength是没有效果的,这时候,通过过滤器还是input事件进行控制就是任君喜欢了。
--by([email protected])
@kaola-blog-bot
input=英普特 666