var manualLowercase = function(s) {
return isString(s) ? s.replace(/[A-Z]/g, function(ch) {
return String.fromCharCode(ch.charCodeAt(0) | 32);
}) : s;
};
var manualUppercase = function(s) {
return isString(s) ? s.replace(/[a-z]/g, function(ch) {
return String.fromCharCode(ch.charCodeAt(0) & ~32);
}) : s;
};
这两段代码用来处理字母大小写转换,由于某些国家(土耳其)使用为什么说这两段代码有意思?其实是觉得其中用 位运算处理字母大小写的代码很巧妙,其核心代码如下:toLowerCase()
和toUpperCase()
不能正确的转换字母大小写,因而需要手动的处理。
ch.charCodeAt(0) | 32 // 大写转小写
ch.charCodeAt(0) & ~32 // 小写转大写
在分析两段代码之前,先来回顾一下JavaScript中的两个概念:
整数和
位运算。 从严格意义上讲,ECMAScript中有两种类型的整数:有符号的整数(正数和负数)和无符号的整数(只有正数)。而默认情况下JavaScript中的整数都是有符号的。 而在不考虑ECMAScript中数字格式存储与转换(为32位)的情况下,实际上我们操作的都是32位的整数。而对于上面提到的有符号整数而言,其中前31位(
end<-start
)表示数字的值,最后1位表示符号位(
0
表示正,
1
表示负)。 这里提到的32位的整数在计算机底层都是使用二进制格式存储的,而这个二进制由
0
和
1
组成,其中每一位都有对应的十进制数字结果,整个二进制数值代表的十进制结果由所有这些位对应的十进制数字之和。 这篇文章中不考虑负数的情况,一个32位二进制格式的数字看起来如下所示,这里以10为例:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0
二进制数字计算的方式:前面提到了,这些二进制的数字实际上都是在计算机的底层完成的,而ECMAScript中刚好提供了二进制运算相关的操作符,这些操作符都是直接对运算数进行二进制操作的,并且都是发生在幕后的。 JavaScript中有7个位运算相关的运算符:number ( Math.pow(2, index))
,这里的number
表示二进制中对应位上的数值0/1,index
表示该数值在整个二进制格式的数字中的索引。注意一个二进制格式的起始点在右侧。 那么上面的数字就等于:1 * Math.pow(2,3) + 1 * Math.pow(2, 1) = 10
。
- 按位非(NOT) - 用一个波浪线"
~
"表示,对二进制的每一位进行取反操作,即将0
变成1
,将1
变成0
。 - 按位与(AND) - 用一个和好"
&
"表示,必须有两个操作数,先对齐二进制位,然后把对应位都为1
的为筛下来,其他的都为0
。 - 按位或(OR) - 用一个竖线"
|
"表示,也必须有两个操作数,对齐位之后只要对应位有1就筛下来,只有同时位0时才返回0
。 - 按位异或(XOR) - 用一个插入符号"
^
"表示,也必须有两个操作数,对齐位之后不同的返回1,相同的返回0。 - 左移 - 用两个小于号"
<<
表示,顾名思议,将操作数左移指定位数,右侧空位用0补齐。 - 有符号右移 - 用两个大于号"
>>
"表示,保留符号位,剩下的右移指定位。 - 无符号右移 - 用三个大于号"
>>>
"表示,往右侧移动指定位数。
ch.charCodeAt(0) | 32
这段代码通过正则表达式匹配到给定字符串中的每个大写字母:
A-Z
;接下来使用字符串对象的
charCodeAt()
方法拿到该字符对应的Unicode编码,恰好这个编码是一个数字;最后使用
按位或运算获取到另外一个数字。 为什么这里执行对数值32的
按位或运算呢?当然这肯定不是空穴来风。那么我们先从大写字母及对应的Unicode值分析看看。不难发现,
A-Z
对应的Unicode编码分别为
65-90
;而这写编码对应的二进制表示分别为:
1000001
...
1011010
。再看看小些字母对应的数据:其Unicode编码分别为:
97-122
,对应的二进制表示分别为:
1100001
...
1111010
。最后将它们放入一张表格中对比如下:
提示:使用
(1).toString(2)
便可以拿到每个数字对应的二进表示法的有效位。
大写字母二进制有效位 | 1000001 | ... | 1011010 |
小写字母二进制有效位 | 1100001 | ... | 1111010 |
1 * Math.pow(2, 5)
),32对应的二进制数值的有效位为:
100000
。 那么如何转换这里的第6位呢?我们的目的是将大写字母二进制数值第6位的0转换为1,而其他的位不变。最终我们只需要拿一个刚好第6位为1,其他位为0的二进制数值与大写字母的二进制数值进行位运算操作即可,这个能够用来进行有效位运算的二进制数值则为
100000
,而JavaScript中的
按位或操作刚好能有做到这一点。 而在JavaScript中,我们并不能直接操作一个二进制的数值,二进制的运算都是在低层完成的,在JavaScript中这些都是按位运算符的使命。那么,在前面使用
charCodeAt()
方法已经拿到了大写字母对应的Unicode编码-即一个有效的十进制数字;而
100000
对应的十进制数字为32。 由此得出结论,使用大写字母对应的Unicode编码与32作按位或运算便能正确的拿到其对应的小写字母的Unicode编码,其操作过程如下:
以大写字母A
为例:
1 | 0 | 0 | 0 | 0 | 0 | 1 |
1 | 0 | 0 | 0 | 0 | 0 | |
1 | 1 | 0 | 0 | 0 | 0 | 1 |
1100001
,对应的十进制数字为97(parseInt('1100001', 2))。最后使用String对象的
fromCharCode()
方法得到的字符便是大写字母
A
对应的小写字母
a
。 整个转换的过程中,所有的这些操作实际上都是在底层(?内存中)完成的。 上面剖析了大写字母转小写字母的过程。接下来再看看小写字母转大写字母。在上面的代码中,我们可以看到转大写字母的代码为:
javascript ch.charCodeAt(0) & ~32
首先,同大写字母一样,使用字符串对象(String)的
charCodeAt()
方法拿到对应的Unicode编码(也是一个十进制数值)。在上面的字母二进制数值对比表格中我们已经找到了规律:即转换每个字母对应的二进制数值的第6即可。那么如何将小写字母的二进制数值的第6位1转换为0,而其位不变呢? 前面将大写字母的第6位0转位1,我们使用了
按位或来保证将第6位正确的转换为1。而这一次小写转大写的过程中,我们必须保证正确的将第6位1转换为0,其他位不变即可。由此得出,这一次进行位运算的基本条件必须保证第二个操作数的第6位为0,而其他位该是1的是1,该是0的是0。 那么如何做到这一点呢?根据位运算的特点以及上面的分析,我们保证第6位不同即可,那么拿
011111
与小写字母的二进制数值进行
按位与运算运算即可。而对32进行
按位非运算的结果刚好为
011111
。
以小写字母a
为例
1 | 1 | 0 | 0 | 0 | 0 | 1 |
0 | 1 | 1 | 1 | 1 | 1 | |
1 | 0 | 0 | 0 | 0 | 0 | 1 |
这里不一定必须是根据前面的分析,这样就拿到了大写字母A对应的二进制数值,再对它编码便可以返回最终的大写字母。 至此,对AngularJS中这两段代码的分析就完成了。也算是对JavaScript中的位运算做了一次巩固,温习。 其实JavaScript中的位运算远远不止这一点,我们还可以使用其他位运算符做到很多事情。下面是一些例子,不妨分析一下其运算原理:011111
。比如拿一个完整的32位11111111111111111111111111011111
也可以。但是在上述环境中,011111
就能满足需求,而这个二进制数值对应的数值刚好是对32进行 按位非的运算结果。
// 获取0-max之间随机整数
function random(max) {
return Math.random() * max | 0;
// 获取 1-max之间的随机整数
// return Math.random() * max | 1
}
// 奇偶判断
function isOdd(number) {
return (parseInt(number) & 1) === 0;
}
function isEven(number) {
return (parseInt(number) & 1) === 1;
}
// 取整
function int(number) {
return number | 0;
}
// 取半
number >> 1;
// 2x
number << 1;
// 随机颜色
'#'+ ('000000' + (Math.random()*0xFFFFFF<<0).toString(16)).slice(-6);
// 还可以挖掘更多的技巧....
一些本文中用到的代码片段:
// 获取字符Unicode编码值
str.charCodeAt(0);
// 获取字符二进制数值有效位
str.charCodeAt(0).toString(2);
// 解析二进制数值
parseInt(binaryNumber, 2);
// 解析Unicode数值位对应的字符
String.fromCharCode(unicodeNumber);