键盘事件
撰写时间:2024-11-06
修订时间:2025-02-01
ASCII码表基本知识
计算机内部使用二进制的数值来编码。为确定各个字符在计算机存储时的唯一编码,美国先于1967年提出其自己的使用数值来代表字符的ASCII编码表,全称为American Standard Code for Information Interchange
,即美国信息交换标准码
。后经ISO 646确定为国际标准。
上世纪六十年代,人们经常在打电话或使用异地打印机时使用ASCII码表来传送文本信息。因此,ASCII码表中有一部分称为控制编码
,如编号为2
的开始正文
,编号为4
的传输结束
等等。这些古老的用途,现在当然用不上了。但这一部分现在看来奇奇怪怪的编码继续保存于ASCII码表中。
从发展阶段,ASCII码表分为ASCII基础码,及ASCII扩展码。
ASCII基础码
最早的时候,人们认为使用7位的编码方案就足够了。27 = 128,因此这种编码方案下共有128个字符。
ASCII控制编码
编号0到31,以及编号127代表控制字符,也称为不可打印字符
。
十进制编号 | 十六进制编号 | 符号标识 | 英文含义 | 中文含义 |
---|---|---|---|---|
0 | 0X00 | NUL | Null | 空字符 |
1 | 0X01 | SOH | Start of Heading | 标题开始 |
2 | 0X02 | STX | Start of Text | 正文开始 |
3 | 0X03 | ETX | End of Text | 正文结束 |
4 | 0X04 | EOT | End of Transmission | 传输结束 |
5 | 0X05 | ENQ | Enquiry | 查询 |
6 | 0X06 | ACK | Acknowledge | 感谢 |
7 | 0X07 | BEL | Bell | 响铃 |
8 | 0X08 | BS | Backspace | 回删 |
9 | 0X09 | HT | Horizontal Tab | 水平跳格 |
10 | 0X0A | LF | Line Feed | 换行 |
11 | 0X0B | VT | Vertical Tab | 垂直跳格 |
12 | 0X0C | FF | Form Feed | 开始制表,另起新页 |
13 | 0X0D | CR | Carriage Return | 回车 |
14 | 0X0E | SO | Shift Out | |
15 | 0X0F | SI | Shift In | |
16 | 0X10 | DLE | Data Link Escape | |
17 | 0X11 | DC1 | Device Control 1 | 设备控制1 |
18 | 0X12 | DC2 | Device Control 2 | 设备控制2 |
19 | 0X13 | DC3 | Device Control 3 | 设备控制3 |
20 | 0X14 | DC4 | Device Control 4 | 设备控制4 |
21 | 0X15 | NAK | Nagative Acknowledge | 不谢 |
22 | 0X16 | SYN | Synchronous Idle | 同步闲置 |
23 | 0X17 | ETB | End of Transmission Block | 传输块结束 |
24 | 0X18 | CAN | Cancel | 取消 |
25 | 0X19 | EM | End of Medium | 中部结束 |
26 | 0X1A | SUB | Substitute | 替换 |
27 | 0X1B | ESC | Escape | 退出 |
28 | 0X1C | FS | File Separator | 文件分隔 |
29 | 0X1D | GS | Group Separator | 分组 |
30 | 0X1E | RS | Record Separator | 记录分隔 |
31 | 0X1F | US | Unit Separator | 单元分隔 |
127 | 0X7F | DEL | Delete | 删除 |
设想,在上世纪六十年代,甲通过电话指导乙使用机械打字机打印文件时,使用了上面的标识符,您就明白这些编码存在的意义了。
可打印的基础码
ASCII基础码中剩下的编码,也即从32至126,均为可打印出来的字符。
ASCII扩展码
使用7位来存储编码只能表示128个编码,因此后面又出了使用8位的扩展编码,能表示28 = 256个字符。
编号从128至255共计128个ASCII编码为扩展码,为一些制表符、货币符号、数学符号及希腊字母等等。
但仅增加128个字符也同样不足以表示各种编码,因此,不同国家、地区、系统均出了不同的代码页(code page),以表示各不相同的字符。因此,ASCII扩展码因系统所使用的不同的代码页而有所变化。且不同的浏览器,对于相同的代码页,支持力度也不一样,从而导致一些字符无法显示出来。这些乱象,直接呼唤了Unicode编码的出现。
而Unicode编码又分为UTF-8, UTF-16, UTF-32等方式。因此,字符编码的完全标准化是一个时间较长的问题。
因此,对于ASCII扩展码,我们一般较少使用。若要使用,则应使用Unicode的方式。
Unicode及码位
很明显,ASCII码表因位数的限制,不能表示世界上所有的字符。Unicode应运而生。Unicode使用code point (码位)来唯一标识每个字符。
特定字符的码位是该字符在Unicode表中的编码位置。码位类似于ASCII值,但ASCII值是字符在ASCII码表中的位置,而码位是该字符在Unicode表中的位置。
可将Unicode视为ASCII的超集。Unicode是后面才设计的,为往前兼容,ASCII码表保持不变。因此,对于ASCII码表中的基础码,其码位与其ASCII值一样。
在印刷媒介、网络媒介上使用Unicode来表示一个字符的格式如U+9AD9
,其数字部分采用十六进制,代表汉字高
字符。则9AD9
即为该字符的码位。
码位与字符的相互转换
根据Unicode的code point来获取字符
既然每个码位能唯一标识每个字符,那么我们就能根据不同的码位来获取不同的字符。
JavaScript获取
在JavaScript中,要根据这个码位来获取相应的字符,共有2种方式:
第一种是直接使用字面符的方式:
首先,JavaScript使用\u
而不是U+
作为前缀,这与印刷媒介、网络媒介上的通用表示方法不同。其次,上面我们不能写成"\u" + "9AD9"
的形式。因为JavaScript将\u
视为一个独立的转义字符,且后面必须直接带有代表码位的字符串(4位大小写均可的十六进制数值,不足4位,前面须补0
)。
在一个字符串中同时使用多个Unicode字符:
如果上面每个Unicode字符之间带有空格,则空格也成为字符串的一部分内容。
对于超出BMP平面外的字符,可以使用如下语法:
上面的表示方法是通用的,照样可适用于码位为两字节的字面符。因此,高
也可表示为:
这种表达方式比较自由,而且十六进制数值若不足4位,无须在有前补0
:
如果希望将上面的码位分离出来以灵活处理不同的码位,则可使用第二种方法。这种方法通过调用String的静态方法fromCodePoint来获取字符:
一并获取多个字符:
fromCodePoint方法的参数是可变长的参数,因此上面使用...
展开操作符将数组展平为独立的多个个体。
HTML获取
而在HTML中,则可使用
的方式来代表此字符。
利用本站编写的HTML Unicode工具,选择4E00-9FBF
这个范围,则可看到此字符出现在9AD0
一行的第9
列中。而表上可显示出,4E00-9FBF
这个范围是CJK统一表意符号
,也即中日韩最常用的汉字区域(CJK = Chinese, Janpanese, and Korean)。
此外,在 MacOS X 系统中,可按⌃Control+⌘Command+Space来打开字符检视器
App,选自定义列表...
,也可选择查看所有的Unicode。
获取特定字符的码位
获取
对于特定字符,如果我们想知道该字符的Unicode码位,则可调用String的codePointAt方法:
所得到的结果39640是码位的的十进制表示。转换为十六进制:
有趣的应用
从上面可知,一些字符已经置于Unicode表中,例如:😊。
确实很酷。但我还想知道,除了它之外,Unicode表中还有多少个类似的图标可供我选择使用?答案是,从码位寻求解决问题的方法。
第一步,求出该字符的码位。好消息是,Unicode字符在代码中可以直接复制、粘帖。
第二步,在该码位上进行简单的加减,再获取新的字符:
两个字符的区别很小,新字符往左吐出了舌头。
有了上面的基础,我们就可以编写一个实用的函数了:
运行结果:
参数prev表示在当前字符的前面取多少个字符,next表示在当前字符的后面取多少个字符,gap用于控制当前字符与其他字符的空格间距。
Unicode码表实在是太大了,我们不好一个一个地搜寻特定的字符。但如果我们在网络上看到特定的字符,利用相似字符在Unicode码表中集中排列的特点,通过上面的小工具就可以一下子就可搜索出所有类似的表情字符。
当然,如果您喜欢来现成的,则也可以在Emoji Sequences找到所有的表情字符,或者图形索引版,或者更漂亮的图形版。
Unicode与编码的关系
Unicode只是简单地规范了哪个字符对应于哪个码位,如,笑脸字符😊
对应于码位0X1F60A。它并不规范在计算机内部如何存储、如何取出并解码。
上面我们多次调用的fromCodePoint仅告诉我们特定字符的码位是多少,但它并不告诉我们在计算机内部使用何种机制来存储。
而Unicode编码就专注于解决诸如使用应多少个字节来存储每个字符、这些字节如何排列、如何正确地取出一个字符所需的所有字节并解读为特定字符的问题。
目前主要有3种Unicode编码:UTF-8, UTF-16, UTF-32。
UTF-8是可变长的编码,分别使用1到4个字节来表示各个字符。
UTF-16也是可变长的编码,但它只使用2字节或4字节来表示各个字符。JavaScript使用UTF-16来存储字符串。
UTF-32统一使用4个字节来存储。
下面结合JavaScript的语言特性,谈谈其对各类编码的支持。
JavaScript对UTF-8的支持
JavaScript通过TextEncoder类,提供了通用的UTF-8的支持。该类将传入的码位流转换为由众多的UTF-8字节所组成的流。
TextEncoder的构造函数没有任何参数,意味着它的输出结果只有UTF-8一种。因此,其属性encoding永远只有一个值:
代码:
encoder对字符串A
(虽然这里只有一个字符)使用UTF-8进行编码,并将结果保存进view中。变量view的类型是Uint8Array,即无符号8位整数的数组。
上面代码可以看出,对于字符A
,其UTF-8编码只有一个字节,其值为65。这个值对应于ASCII值,或码位。
我们还可以调用encoder的encodeInto方法,将编码结果存储进指定的Uint8Array中。
从targetArr看出,天
这个汉字共有3个字节,分别为229, 164, 169。result提供了额外的信息:从JavaScript所使用的UTF-16编码转换为UTF-8的编码单元 (code unit,详见下面)数量为1,目标数组中被修改的字节数量为3。
UTF-16
JavaScript的字符串使用UTF-16编码。Char code是内存中存储的数据,是据以计算码位的依据。
何为Char Code
Char code不是指字符编码
,而是指字节码。什么意思?一步一步来。
首先,还是取上面的笑脸为例。
笑脸的码位为0X1F60A。先记住这一点。
调用charCodeAt方法,分别取出该字符的2个char codes。
第三步,进行神奇的运算:
发现了吗?变量codePoint的值等于result的值,它们都代表了该字符的码位!
将Char code转换为UTF-16编码
上面的charCode1及charCode2,其实就是JavaScript使用UTF-16编码来存储笑脸字符😊
的在内存中的字节数据。我们通过实践来验证:
通过硬编码的方式,将charCode1及charCode2的值用于构建一个Uint16Array,然后使用utf-16
对该数组中的字节流进行解码,得到了我们预期的效果。
转换细节
仍以上面类型化数组数据为例。一是如果数组元素的值小于等于0XFFFF,则直接取该元素值作为该字符的码位而予以解析。
0x2615的值小于0XFFFF,则它就是字符☕
的码位,直接解析。
Unicode规范规定:0xD800 - 0xDBFF此段为leading surrogate(开始代理段,也称high-surrogate code unit,高位代理编码单元);0xDC00 - 0xDFFF此段为trailing surrogate(结尾代理段,也称low-surrogate code unit,低位代理编码单元)。这两段将用于映射而予以保留。
由此在JavaScript内产生第二条规则:如果遇到两个数值,第1个数值c1位于0xD800 - 0xDBFF范围内,且第2个数值c2位于0xDC00 - 0xDFFF范围内,则该字符的码位为:
这就是上面我们的代码:
的由来。
JavaScript使用UTF-16解码的第三条规则是,如果一个编码单元位于高位代理编码单元或低位代理编码单元,但又无其他数值与其构成代理对,则直接取其值为码位。
由此可见,char code是UTF-16编码在内存中实际存储的数据,charCodeAt方法可获取这些数值,它们是JavaScript据以计算码位的依据。
响应键盘事件
下面代码,在按下特定键时,在网页上看到key及code属性值,在console面板中看到事件参数。
"${evt.key}", "${evt.code}"
`); console.dir(evt); };参数evt是KeyboardEvent的一个实例。与键码输入有关的属性有:
当我们输入A
时:
- charCode: 0
- code: "KeyA"
- key: "a"
- keyCode: 65
- keyIdentifier: "U+0041"
- which: 65
上面,charCode, keyCode及keyIdentifier这3个属性已被废弃。
注:code按QWERTY
的键盘布局返回键码。对于一些布局为QWERTZ
的键盘,当用户按下Z键时,code仍返回Y
。
综上,对于上面众多的属性,选用key属性,较为稳妥。但code属性值也包含了一些有用的信息。
按钮 | key属性值 | code属性值 |
---|---|---|
Insert | Help | Help |
Delete | Delete | Delete |
Home | Home | Home |
End | End | End |
Page Up | PageUp | PageUp |
Page Down | PageDown | PageDown |
Enter | Enter | Enter |
Backspace | Backspace | Backspace |
Esc | Escape | Escape |
F1 ... F10 | F1... F10 | F1... F10 |
Print Screen | F13 | F13 |
← | ArrowLeft | ArrowLeft |
→ | ArrowRight | ArrowRight |
↑ | ArrowUp | ArrowUp |
↓ | ArrowDown | ArrowDown |
Caps Lock | CapsLock | CapsLock |
Tab | Tab | Tab |
按钮 | key属性值 | code属性值 |
---|---|---|
Spacebar | Space | |
0 ... 9 | 0... 9 | Digit0... Digit9 |
a ... z | a... z | KeyA... KeyZ |
按钮 | key属性值 | code属性值 |
---|---|---|
Num Lock | Clear | NumLock |
+ | + | NumpadAdd |
- | - | NumpadSubstract |
* | * | NumpadMultiply |
/ | / | NumpadDivide |
Enter | Enter | NumpadEnter |
0 ... 9 | 0... 9 | Numpad0... Numpad9 |
. | . | NumpadDecimal |
组别 | 按钮 | key属性值 | code属性值 |
---|---|---|---|
1 | ~ | ~ | Backquote |
` | ` | ||
2 | _ | _ | Minus |
- | - | ||
3 | + | + | Equal |
= | = | ||
4 | | | | | Backslash |
\ | \ | ||
5 | : | : | Semicolon |
; | ; | ||
6 | " | " | Quote |
' | ' | ||
7 | < | < | Comma |
, | , | ||
8 | > | > | Period |
. | . | ||
9 | ? | ? | Slash |
/ | / |
上面均以下档键的名称来标识。
按键 | 位置 | key属性值 | code属性值 | 设置标志属性 |
---|---|---|---|---|
Shift | 左 | Shift | ShiftLeft | shiftKey |
右 | ShiftRight | |||
Control | 左 | Control | ControlLeft | ctrlKey |
右 | ControlRight | |||
Command | 左 | Meta | MetaLeft | metaKey |
右 | MetaRight | |||
Option | 左 | Alt | AltLeft | altKey |
右 | AltRight |
按住Option键,可方便地输入一些常用字符。访问key-events.html以测试。