iColin 

Do what you want
 

“Javascript”目录存档

字符集和编码方式

2011年03月30日,星期三

什么是字符集和字符编码

根据wiki对现代字符集的定义,字符集是一个字符序列,将一个字符集对应到一指定集合中某一东西(比如自然数序列)的过程称之为字符集编码(比如将拉丁字母编码成ASCII字符集),这样的一个字符集和对应的自然数序列叫做编码字符集。比如字母“A”对应的整数是65,“B”对应的整数为66。通常将字符集通俗的理解为字符和数字的对应表。

字符集的历史

为了更形象描述字符集,这里引用一部分资料上的历史:

起源及ASCII的诞生

很久以前,一群人用八个可以开合的晶体管来组成不同状态来表示万物,这样的一个字节(byte)就可以表示256种字符(2^8),他们把前32种状态叫做控制码,控制码有特殊的用途,比如打印机遇到0×10就输出换行等。这样,他们继续把大小写字母、数字和标点符号都用字节的各种状态来表示,一直排到了127位,这样计算机就可以用存储英文数据。当时计算机普及率很低,这样的方案看起来很不错,于是大家把这种方案叫做ANSI的ASCII(Aemerican Standard Code for Information Interchange)编码。

随后,世界上其他国家的人也开始使用计算机,他们发现ASCII里面没有想要的字符,于是把127位之后的空位来表示他们所需的新字符和符号,这样一直排到了255号,128-255之间的字符称为扩展字符集。

GB2312字符集

到中国人开始使用计算机时,已经没有位置来表示中文字符,于是亲们把127位以后的字符废除掉,并规定:127位之前和ASCII字符集一样,127位后,每两个字节表示一个汉字(前后两字节叫做
高字节和低字节),这样大部分简体汉字都可以表示了,另外还把字母、数字和标点符号都重新收编了一次,这就是所谓的“全角字符”。这种字符集叫做GB2312,它是对ASCII的中文扩展。(以
前常听程序员默念:一个汉字的长度等于2个英文字符的长度)

GBK和GB18030字符集的由来

随着计算机的普及,中国人发现还有好多繁体字没法表示呢!于是再次取消低字节大于127位的限制,只要是第一个字节大于127位就表示这是一个汉字的开始,这种方案叫做GBK字符集,它完整包含了GB2312字符集,还增加了很多繁体字和特殊符号。
再后来,少数民族也用电脑了,还得给照顾他们,于是在GBK的基础上又增加了很多少数民族的文字,这样GB18030字符集便诞生了。GB2312,GBK,GB18030通称DBCS(Double Byte Character Set双字节字符集)

想一统宇宙的Unicode

当时,很多国家都搞出一套自己的字符集方案,这就给信息交换带来了不少麻烦。于是ISO决定解决这个问题:废除所有地区性字符集,制定了一套囊括所有字符的字符集。即Universal Multiple-Octet Coded Character Set,简称UCS,俗称Unicode。

关于Unicode字符集

Unicode规定:统一使用双字节(16位)来表示一个字符,对于ASCII的字符,编码序号保持不变,只是由原来的8位扩展到16位(在存储海量数据时有浪费空间的诟病),其他的文字和符号全部重新编码。Unicode用UTF(Unicode Transformation Format)来实现,UTF-8即每次编码8个字节。

关于UTF-8

UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,关于UTF-8编码方式,可以通过下面的表描述:

1
2
3
4
5
6
7
8
U+00000000 – U+0000007F:    0xxxxxxx
U+00000080 – U+000007FF:    110xxxxx 10xxxxxx
U+00000800 – U+0000FFFF:    1110xxxx 10xxxxxx 10xxxxxx
U+00010000 – U+001FFFFF:    11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
U+00200000 – U+03FFFFFF:    111110xx 10xxxxxx 10xxxxxx 10xxxxxx  10xxxxxx
U+04000000 – U+7FFFFFFF:    1111110x 10xxxxxx 10xxxxxx 10xxxxxx  10xxxxxx 10xxxxxx
 
//ps: Unicode在范围D800-DFFF中不存在任何字符

第一行的意思是:如果遇到0xxxxxxx(以0开头)的UTF8编码,则说明这是单字节表示的ASCII字符(0×00-0×7F)。

在一长串二进制中,有可能2-6个字节来表示一个字符,这样的话就需要额外的信息来描述多字节字符
的起始位置(starter),以及字节的长度(length)

接下来的几行表明:如果遇到10xxxxxx(以10开头)的字节,说明这是一个非ASCII字符中的一个字节,并且不是该字符的第一个字节编码。那么,除去以0和10开头的字节都是字符的首字节了。如果字节是110开头就
表示这是一个两字节字符,如果是1110,就表示这是一个三字节字符…以此类推,可以看出首字节的1的个数就表示这个字符占有的字节个数
(length)。

UTF-8编码示例

看明白这个表以后,举例说明下字符的UTF-8编码。以“零”这个汉字为例:

“零”的unicode码是38646,对应十六进制码是ox96f6。转换成二进制为1001011011110110。根据上面的理论,“零”(0×96f6)在U+0800-U+FFFF区间内,需要用三个字节编码,把这些二进制码拆
分放到“1110xxxx 10xxxxxx 10xxxxxx”各个x处(从低位往高位放,高位不足补0),组合得11101001 10011011 10110110,“零”就是以这样的二进制字节流存储和传输的。

把上面得到的三个二进制数转换成十六进制,可得:0xE9,0×9B,0xB6,然后我们在javascript中用encodeURI方法对“零”编码,得到%E9%9B%B6,很眼熟吧,就是前面的3个二进制数的十六进制表示。(encodeURI是一种percent encoding,ECMA262上有记载,但据说该编码没有通过w3标准)

1
2
3
4
5
6
7
    // 说明:零的unicode为38646, 38646 == ox96F6 == 1001011011110110
    var z = '零' ;
    console.log(parseInt('11101001', 2).toString(16)); //输出 e9
    console.log(parseInt('10011011', 2).toString(16)); //输出 9b
    console.log(parseInt('10110110', 2).toString(16)); //输出 b6
 
    console.log(encodeURI(z).replace(/%/gi, '  ')); //输出 e9 9b b6

HTML和Javascript中使用Unicode

HTML和Javascript都是支持Unicode字符集的,所以你可以在HTML中直接使用Unicode码来表示字符,符号实体就是这样的应用。比如我们用">"或者">"来表示">",前一种方式叫做符号实体,后一种方式叫做实体编号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    // 说明:零的unicode为38646, 38646 == ox96F6 == 1001011011110110
 
    // 以下HTML语言在浏览器中会输出两个“零”
    <strong>&#38646;</strong>
    <strong>&#x96f6;</strong>
 
    var z = '零' ;
    // 在javascript也可以直接是用unicode码来表示字符
    console.log('\u96f6') ;  //输出 零
    console.log(String.fromCharCode('38646')); //输出 零
 
    // 查看“零”的Unicode码和十六进制码
    console.log(z.charCodeAt(0)); //输出 38646
    console.log(z.charCodeAt(0).toString(16)); //输出 96f6
 
    // escape编码——percent encoding,返回百分比格式的十六进制
    console.log(escape(z)); //输出 %u96f6

PS: 感谢stauren

参考:
http://en.wikipedia.org/wiki/Charset
http://en.wikipedia.org/wiki/Character
http://en.wikipedia.org/wiki/Unicode
http://en.wikipedia.org/wiki/Utf-8
http://stauren.net/log/fpev3c89q.html

关于reflow和repaint

2011年03月19日,星期六

最近又重新查阅了一些关于reflow(回流)、repaint(重绘)的资料,整理成笔记:

1. 什么是reflow和repaint

“Repaint is what happens whenever something is made visible when it was not previously visible, or vice versa, without altering the layout of the document. ”
“Reflow is a more significant change. It is the name of the web browser process for re-calculating the positions and geometries of elements in the document”

Repaint发生在当某些元素由不可见变为可见,或由可见变为可见的情况下,前提是不改变文档的layout模型。
Reflow是一个更加明显的改变,它是浏览器重新计算文档元素的尺寸和位置的一个过程。

2. 什么情况下会发生reflow和repaint

a) Repaint

  • Adding an outline to a element //增加outline属性
  • Changing the background color //改变背景色
  • Changing the visibility style //改变visibility属性

b) Reflow

  • Resize the window //调整浏览器窗口大小
  • A script manipulating DOM tree //脚本操作DOM
  • Manipulating the className property of an element //操纵className属性
  • Style changed that affect the layout //修改影响layout的样式
  • Adding or removing a stylesheet //添加一处样式表
  • Calculating offsetWidth and offsetHeight //计算offsetWidth和offsetHeight
  • Contents changed,such as typing text in an input box //内容改变,比如在文本框内输入文本
  • Activation of CSS pseudo classes  //激活伪类

3. reflow和repaint带来什么影响

过多Reflow和Repaint会导致DOM渲染变慢,甚至破坏layout模型

Repaint requires the engine to search through all elements to determine what is visible, and what should be displayed.
The engine must reflow the relevant element to work out where the various parts of it should now be displayed. Its children will also be reflowed to take the new layout of their parent into account. Elements that appear after the element in the DOM will also be reflowed to calculate their new layout, as they may have been moved by the initial reflows. Ancestor elements will also reflow, to account for the changes in size of their children. Finally, everything is repainted.

对于Repaint,需要浏览器引擎搜索整个DOM节点,然后决定各部分该如何呈现。
对于Reflow,引擎需要reflow相关的元素来计算各部分的现实方式,该元素的子节点也要根据新的layout模型reflow,出现在该元素之后的元素也需要reflow来计算新的layout模型,因为他们可能被初始化reflow移除掉,并且该元素的祖先元素也要reflow来计算这些子级元素的改变,总之,所有的都被repaint了。

下图列举了各浏览器对于各种样式修改引发的reflow次数:
reflow time by various browser

4. 如何避免reflow

  • Reduce unnecessary DOM depth //减少不必要的DOM层级
  • Making several style changes at once //一次修改多个样式
  • Avoid tables for layout //避免使用table布局
  • Avoid css expression //避免使用cssExpression
  • Apply animations to elements that are position fixed or absolute //让动画元素绝对或者固定定位
  • Trading smoothness for speed //牺牲流畅度来换取速度
  • Avoid inspecting large numbers of nodes //避免大规模操作节点
  • Avoid modifications while traversing the DOM //避免边渲染DOM边改变DOM
  • Cache DOM values in script variables //缓存DOM信息

5. 实例

Bug描述:winxp的IE7/8浏览器中,搜索页面会在页面加载后异步载入部分DOM结构,此时后面的DOM内容位置偏离了原始位置(见红框部分DOM内容)。
bug-caused-by-reflow

分析:javascript脚本操作DOM时引起的reflow导致祖先节点reflow和同一个祖先节点下的临近节点的reflow。在reflow时,layout模型发生了变化,部分浏览器引擎重新计算DOM的位置发生了一些错误。

6. 参考文献

区分IE和非IE的最短判断

2011年02月27日,星期天

由于会议邀请者的系统时间出错,我被早到了一个小时到达会议室,闲着无聊,就顺便看看新闻,在JE里面看到一个“区分IE和非IE的最短判断”,帖子中的解释是:根据IE在对数组使用toString方法时报错来判断,代码大致如下:

1
2
3
4
5
if (+[1,]){
    alert('您正在使用非IE浏览器');
}else {
    alert('您正在使用IE浏览器');
}

Well,这里使用了一元加法运算符,对这个不了解到可以看看下面的代码(关于对数组使用一元加法运算符,我暂时没找到正规文档,只好以实例来说明来推测):

1
2
3
4
// 当数组只有1个元素时,返回这个数组元素
alert(+[1]);
// 当数组长度大于1时,返回NaN
alert(+[1,2]);

好吧,其实我想说“区分IE和非IE的最短判断”的是根据IE和标准浏览器对数组容错处理不一致的特性来实现的。

标准浏览器会忽悠数组中最后一个”,” (当作这是开发者不小心写上去的), 而IE则不会忽略它,而且用undefined来填充最后一个数组元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = [1,] ;
// 查看数组a的length属性 枚举数组
// 通过这两步可以测试出非IE是忽略掉数组中最后一个空元素的
// 而IE却用undefined来填充最后一个元素
alert([1,].length);
for (var i = 0, le = a.length ; i < le; i++ ) {
    alert(a[i]);
}
 
// 由上述得知 +[1,] 相当于 +[1, undefined] 
// 根据上面一元运算符的实例,你不难想到在IE下+[1,]会返回NaN了
alert(+[1,]);
alert(+[1,undefined]);

—– split —–

BTW:最近时间排得紧,走路都在想些事情,以至于生活方面的事情都有点昏昏的,买东西忘了拿找零,取钱忘记了拿。一心无二用,以后做什么事情都要专注,哪怕是取钱,也要盯着带着印钞机余温的RMB从ATM中徐徐吐出,然后收回卡,放好钱包再走人。

jQuery源码阅读笔记(1)

2011年01月16日,星期天

“阅读jQuery源码”,在2010年尾时,我写下了这个plan。该是执行的时候了。

我选择的jQuery版本是1.4.4,在逐行阅读分析jQuery代码前,我首先快速阅读了jQuery的全部代码,大致了解了jQuery源码的结构:整个jQuery源码是一个自执行的匿名函数。给匿名函数有个传递一个window参数,这样做便于压缩源码内的window字符串。在源码中,通过自执行函数来将jQuery和$变量保留给全局作用域供用户使用(#898)。

在接下来的时间,我将jQuery源码分成几大块进行逐行阅读分析。本文记录了第一部分的一些笔记。

构造jQuery对象(#20-#898)

jQuery对象并不是new jQuery()生成的,而是实例化构造函数jQuery.fn.init()生成的,看看源码中这几个关系:

1
2
3
4
5
6
7
8
jQuery.prototype = jQuery.fn #99
//  jQuery的fn属性和其原型是指向同一个对象
 
jQuery.fn.init.prototype = jQuery.fn  #327
// 把jQuery的原型挂在jQuery.fn.init的原型链上,以便于外部的jQuery对象能访问jQuery原型链上的所有方法
 
jQuery.extend = jQuery.fn.extend #329
// 他们指向同一个扩展方法

这个部分伊始定义了很多正则,比如判断查询器字符类型的,判断JSON数据相关的,判断浏览器userAgent等待。还有定义一些Object方法的别名。

jQuery的原型函数

在#99开始定义jQuery的原型,jQuery大部分功能都是通过其的静态方法来实现的,原型中的方法只是暴露给外部的API接口,其中:

  • init方法根据传递的selector参数查询出对应的对象并返回;
  • get方法会根据传递参数返回指定的对象元素;
  • pushStack方法将传递的对象压入jQuery对象中;
  • each 你懂的;
  • ready 判断DOM是否加载完毕;
  • eq 根据参数获取指定位置的数组元素;

(全文…)

jQuery 动画组件(Animate)浅析

2010年12月26日,星期天

之前自己在做js动画组件时遇到了JS时钟精度问题,遂去参考下jQuery的处理方式,顺便把jQuery的动画组件部分简要分析了一遍。

jQuery组件都是由一个API接口函数暴露给用户的,组件的核心功能由有底层函数去完成。JQuery动画组件的接口函数就是animate。在了解animate之前,先了解到jQuery中动画相关的几个属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 用来设置定时器的变量
var timerId ;
 
jQuery.extend({
    // 修正一些参数,比如在回调函数中加入队列控制,并把这些修正后的参数放置一个对象里面返回
    speed: function( speed, easing, fn ) {},
 
    // 缓冲函数,决定动画运行方式
    easing: {},
 
    // 用来存储 执行动画实例单步的方法 的数组(有点拗口)
    timers: [],
 
    // 动画构造函数
    fx: function( elem, options, prop ) {}
 
});

结合我在源码中加的一些注释,简要描述jQuery的动画过程:
animate方法,首先调用speed方法去修正参数,然后用枚举或者队列的方式去执行多个动画。接下来,遍历动画中要修改的属性,修正属性值,并将每个属性生成一个动画实例。调用实例custom方法将属性从开始值过渡到结束值。

custom方法,把动画的单步操作(step方法)压入到前面提到的timers数组中,并调用tick遍历执行所有动画。

step方法——动画的单步操作,计算动画的逝去时间,并将逝去时间与预定动画时长的比值作为动画的变化比值,将比比值结合动画缓冲函数,计算出动画变化值。如果动画逝去时间超过预定动画时长,则将属性更新到最后一帧,并调用预定的回调函数。返回false,表示该动画实例已完成。

tick方法——定时遍历执行所有动画实例的step方法。如果step方法返回为false(该动画实例已完成),则将包含执行step方法的函数从timers数组中移除,如果timers数组为空(全部动画实例都运行完毕),执行stop方法。

stop方法——终止动画,停止定时器,并清空定时器变量timerId

看完jQuery源码,自己也改造了一个。满足最基本的功能呢 查看Demo

下面是JQuery动画组件的大部分源码(我加了一些注释):
(全文…)

IE下setTimeout 的bug

2010年11月6日,星期六

用Js封装了一个常用的动画组件 (googleCode) ,发现动画完成所需的时间在IE和在其他浏览器下有很大的差别。

开始以为是组件中调用的函数过于复杂,导致IE计算太耗时,拖延了整个动画的完成时间。于是选择了最简单的函数来实现动画,但问题依旧存在。排除了函数的复杂性,那就只可能是setTimeout的问题了。

setTimeout(code, millisec); 在指定的毫秒数后调用函数或者表达式。(我在后面将称“指定的毫秒数”为调用间隔)

疑问:IE是否在我指定的毫秒数之后执行了函数呢?

把代码精简到最少,继续测试,下面的的一段代码用来连续10次调用run函数(调用间隔是10ms),执行完后弹出的结果是调用10次函数耗费的总毫秒数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script type="text/javascript">
//< ![CDATA[
var animAlex = function (d) {
    var t = 0 ;
    var run = function () {
        if (t < d){
            t++;
            //尝试修改这里的参数 从0到15 都会按15计算
            setTimeout(run, 10);
        }else {
            alert(+ new Date() - s );
        }
    } ;
    run();
} ;
var s = + new Date() ;
animAlex(10);
//]]>
</script>

点击查看demo

忽略函数执行时间(这里,函数的执行时间确实可忽略),调用10次函数所需时间的理论值等于调用间隔和执行次数的乘积,这里调用间隔是10ms , 执行了10次,则总需时间 10×10 = 100ms ,当然浏览器不可能完全精准。但误差也应该保证在正负10ms以内。

面对现实,执行结果是:
(全文…)