Archive for the ‘前端开发’ Category

接上篇。ie下错位几率相对而言有些大,于是基于interactive特性进行改善。这其实反而是走回老路了~
在意想之中的是,ie下十万分之一几率的错位果然降了,而且降到了没有一个错误……webkit内核依旧保持极低概率,奇怪的是firefox在上两次中从未出现错误的先提下,这次爆出了十位数的错位。
ie6
“BbAa” “162339″
“Aa” “858130″
“Bb” “859823″
“AaBb” “1551704″
ie7
“BbAa” “40125″
“Aa” “208591″
“Bb” “208680″
“AaBb” “376486″
ie8
“BbAa” “113878″
“Bb” “622694″
“Aa” “625345″
“AaBb” “1131155″
ie9
“BbAa” “24716″
“Aa” “113455″
“Bb” “113505″
“AaBb” “201967″
ie10
“BbAa” “1″
“BbAa” “8″
“Aa” “25″
“Bb”…

接上篇。尽管已经降到了十万分之一级别的几率,但并发下浏览器的不靠谱行为依旧存在。IE6-8下依旧为主要因素,这与使用者人数多也有关,基数大所以数量高。另外2次幂延迟算法对于单线程情况(aA)是能够完全纠正的,这点值得欣慰。
并发时,abAB、aABb、aAbB、ABba、abBA、AbBa、aBbA、aBAb几种情况按几率依次递减,想要完美解决必须全部纠正。在2次幂延迟算法纠正后,前面3个已经正确,但后面的还是错误的,它们可以统一概括为:ABba。结果原本应该赋给A的a跑到B的身上,互相错位了,所以要进行错位交换。
理论上前期不可能再进行处理了,我耗了很久发现前期处理是个悖论,剩下的可能只有运行时决定。
在运行时:
use(['a', 'b'], function(a, b) {
a.methodA();
b.methodB();
});
在错位情况下,a不具有methodA方法b才具有,b也反之。此时会抛异常。简单的做法是运行时try,捕捉到异常后交换错位的arguments再运行一次。看起来似乎不错,实际陷阱重重。
首先methodA和methodB可能方法名相同,甚至逻辑极为相似。这样就不会异常但运行结果是错的。这个概率很低。
然后这是在2个参数的情况下,2个以上排列比例太多,交换次数很恐怖,不切实际。
还有这只有1级递归,也就是a和b互相颠倒而已。如果a有依赖或b有依赖,所造成的错位可能有很多,那它们之间互相递归交换非常恐怖,根本不可能实现。
即使可以通过的define进行包裹一层,抽出factory返回的方法名来提升判断callback的参数顺序准确度,但依然不能解决上述所有问题。所以最后错位交换算法被pass。
IE下,通过getInteractiveScript的方法,可能提升并发准确度到百万分之一的级别,有待验证。

接上篇,之前用的加载script机制有问题,同时赋予了onload = onreadystatechange,这在同时支持的浏览器(ie9)下有影响,新的方法分开对待。
other success
BAba 1
Bb 1
BbAa 1
AaBb 1
BbAa 1
BAba 1
BbAa 1
Aa 2
AaBb 2
Bb 3
Aa 3
BAba 4
ABab 4
Aa 6
BbAa 6
ABab 6
Bb 8
ABab 12
AaBb 17
Bb 23
AaBb 26
ABab 30
Aa 32
Aa 76
Bb 102
ABab 155
ie7 success
ab 1
bAa 1
a 1
BaAb 61
ABba 83
BAba 12481
BbAa 26430
AaBb 69314
Aa 201493
Bb 202091
ABab 295501
ie6 success
aBb 1
AbBa 22
BAab 89
BAba 50511
BbAa 106465
AaBb 287284
Bb 835892
Aa 836129
ABab 1227774
webkit success
ABba 2
BaAb 7
BbAa 51452
Bb 254856
Aa 255189
AaBb 454364
FF success
BAba 2
ABab 109
BbAa 5512
Bb 35757
Aa 36034
AaBb 65577
ie10 success
BbAa 4
BAba 7
AaBb 12
Bb 35
Aa 45
ABab 59
opera success
ABab 6
Bb 3077
Aa 3092
AaBb 5990
ie9 success
BaAb 8
AbBa 8
BbAa 7968
BAba 17482
AaBb 26081
Bb 109073
Aa 109478
ABab 166921
ie8 success
AbBa 16
BAab 80
BAba 31761
BbAa 84319
AaBb 241514
Bb 616614
Aa 617047
ABab 874323
出现了onload抢先exec的现象且顺序不一致,比如 CcaABb,总数:123
B 1
BaAb 7
BaAb 8
AbBa 8
AbBa 16
AbBa 22
BaAb 61
顺序不一致,比如 CcAbBa,总数:381
ab 1
aBb 1
B 1
bAa 1
a 1
ABba 2
BaAb 7
BaAb 8
AbBa 8
AbBa 16
AbBa 22
BaAb 61
BAab 80
ABba 83
BAab 89
几率381/8226030 =0.000046316388342857974,主要集中在IE6-IE8。

接上篇。另外对于基础性原理不清楚的请查看射雕的介绍:http://lifesinger.wordpress.com/2011/11/11/get-url-in-module-loader/
通过1周的海量数据多次测试与研究,我和射雕发现了所有浏览器下都有的共同bug:onload事件先于script标签的加载发生。这个结论直接表明,目前所有对于script的onload的侦听都不是完美的!一旦有了这个低几率问题,那些莫名的错误会让人十分困惑。
介于此,我尝试使用2^ delay的算法来延迟onload的运行。它的理论基础来源于抑制网络风暴的算法:即onload先检查script的exec,没有时延迟2 ^ 0单位时间后再检查、再没有延迟2 ^ 1、2 ^ 2……以此类推。
在昨天的海量观测数据下面,结果是惊喜的。没有1次发生了error,全部正确。但是依然存在低几率次序打乱的情况,这是接下来要着手解决的问题(或者干脆忽略掉)。
最后祝福下Firefox和IE10,你们的顺序是100%正确的。
other success
AaBb 1
BbAa 1
Aa 1
AaBb 1
Bb 1
BAba 1
ABab 1
BbAa 1
BAba 1
ABab 1
Aa 1
AaBb 2
BbAa 3
Aa 3
BAba 3
Bb 4
Aa 5
BbAa 8
ABab 11
Bb 12
ABab 23
AaBb 23
Aa 26
AaBb 28
Bb 28
Aa 91
Bb 97
ABab 171
opera success
BbAa 1
BAab 1
ABba 1
BaAb 1
ABab 28
Bb 2742
Aa 2783
AaBb 5484
ie8 success
ABba 2
AbBa 8
BAab 17
BaAb 29
BAba 30901
BbAa 82405
AaBb 233970
Bb 599658
Aa 600632
ABab 853122
webkit success
ABba 2
BAab 2
BaAb 4
AbBa 5
BbAa 50606
Aa 246173
Bb 246419
AaBb 441956
ie10 success
BAba 2
AaBb 8
BbAa 9
Aa 29
Bb 31
ABab 36
ie9 success
AbBa 2
BaAb 4
BAab 30
ABba 159
BAba 8915
BbAa 15603
AaBb 35080
Aa 104627
Bb 105027
ABab 149314
ie7 success
AbBa 8
BaAb 12
BAab 35
ABba 81
BAba 11900
BbAa 25614
AaBb 66317
Aa 193519
Bb 193970
ABab 282723
ie6 success
ABba 10
AbBa 14
BAab 21
BaAb 57
BAba 50272
BbAa 104528
AaBb 275505
Bb 811928
Aa 813622
ABab 1189557
FF success
ABab 90
BbAa 5578
Bb 34745
Aa 34866
AaBb 63339

十一月 10th, 2011

onload次序测试

28 Comments, 前端开发, by army.

问题的起源是这样的:
假设加载有2个脚本A和B,而a和b则是附加在各自脚本上的onload侦听。众所周知的,IE下A加载完成exec后a并不是保证立刻触发的,非IE下正常。但是,倘若前提是A的加载完成和B的加载完成顺序确定,即A->B,那么a->b的顺序能不能得到保证?即:有可能是AaBb、ABab,但绝不可能有ABba。
自己测是发现不了问题,但显然范围太小不足以说明情况,所以我借助土豆的某个次要页面流量,做了个大范围精度测试。
目标:测出IE6、7、8、9下面的顺序情况。
理由:懂的入。
用例:用户有25%的几率加载a.js、25%的几率加载b.js、剩余50%全部加载,以此模拟出各种可能被缓存的情况——即可能先缓存a,然后再加载a和b。
数据结构:第一位的0和1标明页面是否发生error,是的话1,否的话0;后面大写字母是script标签被加载exec时记录,小写字母是其onload触发时记录;再后面数字是次数。
结果:近千万份数据,可能因对浏览器ua的判断稍稍有些误差。
error
ie6
“1Aab” “3410″
“1a” “2979″
“1b” “2901″
“1Bba” “1575″
“1Bab” “136″
“1Aba” “61″
“1″ “56″
“1Aa” “12″
“1A” “10″
“1ab” “9″
“1Bb” “7″
“1ABab” “4″
“1AB” “1″
ie7
“1b” “771″
“1a” “743″
“1Aab” “738″
“1Bba” “376″
“1″ “108″
“1Bab” “23″
“1Aba”…

实验前提是了解amd规范:http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition,国内很多社区或个人或公司或开源项目都已有一些具体实现,不再赘述。
另外,这里只有概念,不涉及具体算法和代码。
版本控制
土豆网现有的js版本控制逻辑是为每个更新发布的js文件自动更新版本号,它以下划线+版本号数字为文件名结尾(不包括后缀名):

而在amd中,我们自己实现的异步加载模块机制,譬如说是个use方法,需要使用a.js这个模块。那么不可能每更新一次a.js,所有使用到它的地方都手动改为use(‘a_2.js’)。
实际情况是:源代码里一直写的是use(‘a.js’),至于版本控制,它是在服务器端自动追加的:

红色字体部分需要特殊解释一下,它并不是正则替换掉所有use(‘a.js’)的地方,而是做了一个映射或者说路由。以a.js为key、a_2.js为value存入一个HashMap中,然后use(‘a.js’)方法执行时读取到映射关系,将key转换为value后再加载。
这样做的好处是既不影响规范的id语义,又屏蔽了版本号对开发人员的细节可见度,甚至当迫不得已时想使用某个古老版本亦可:use(‘a_1.js’)——因为匹配不到映射关系,所以会直接加载a_1.js这个模块。
智能依赖构建
有时候一个页面上的js要使用到多个模块,为了节省请求数量,我们希望将多个模块合并到这一个js中。为此引入了@import语法,它以注释形式出现在文件头部,服务器端构建工具会自动将导入的js文件合并到一起。这个过程是递归的,也就是说会导入可能所有需要导入的子孙文件。
事实上,这个方法早已屡见不鲜,很多构建工具都是这样做的,只是大部分都是客户端构建,由开发人员手动打包文件后发布。将这一过程移植到服务器端可省却许多不必要的麻烦。
值得一提的是,打包构建会影响到版本映射时的逻辑。譬如我们use(‘a.js’),但a.js已经被导入到当前文件中了,因此映射逻辑会忽视掉它——这个id的模块已经存在,不会再去异步加载了,映射还有什么用呢?
更进一步
手动导入其实不仅仅限于导入依赖或者导入模块,导入其它不相关的东西都行。有时候,在使用一个模块时,这个模块有依赖、依赖的模块还会继续依赖……我们希望一下子全部导入所有依赖到这个文件,而不想手动@import。较好的做法是增加一个关键字比如@build,它会递归将所有依赖打包到一个文件中。
这个步骤我还没有做。
是否需要final
经过讨论有这样一个观点:a.js被b.js和c.js所依赖,a.js更新后原本b和c都会自动追加版本映射(如果是导入则自动更新导入),但是c并不想随之自动更新,c想等自己文件有更新后才去更新导入和依赖的东西,因此需要增加一个@final关键字指定。
经过思考我觉得它带来的好处和带来的坏处一样多:构建和版本映射系统将变得复杂; 阅读维护上增加歧义;等c下次更新时你可能已经忘记了依赖的哪些模块上次没有自动更新。所以这个指令还是不要的好,每次更新一个模块后,所有依赖和导入的都随之自动更新,由测试人员在测试环境根据文件列表全部测试到位。
实际上我们也应该这样做,一个底层的被大量依赖的模块必须经过严格的测试,它被设计成被大量依赖的说明它是全局性的,即使不存在于amd中也是被写成全局代码,在更新时一样要测试到位。
允许并存
开发人员手动合并还存在一种冲突现象:这个页面用到了a.js和b.js,一个人改了a另外一个人改了b,他俩在发布测试环境时会互相覆盖对方的结果。服务器端智能构建可以很好的解决这一问题:一个人发a的源文件而另外一个人发b的源文件,同时在测试环境测试;a测试完成后a发布上线即可,不会发布到b,反之亦然。
甚至某个模块想要设计时也可以是“平滑过渡”的。试想由于某些原因,底层模块a要升级了,但并不是所有地方都兼容新版本,此时我们可以新建个底层模块a2作为新版本,兼容的地方手动替换a2,不兼容的继续使用a。这得益于模块化的松耦合特性。
单元测试
有了模块化之后,单元测试便有了前提。具体怎么测试就不探讨了,这是属于测试人员的范畴。至于为什么说有了前提,那是因为所有模块文件都是异步加载的,在线上环境可能被合并构建成了一个文件,但是只要将全部js代理成源代码(源代码里只写了类似@import的东西,并没有合并,合并是在服务器端最后发布做的),所有导入都变成异步加载了,这有利于分离调试并定位错误。

以前写过一个旧版本的,现在做了不少变化,最主要修改是分离出2个文件:html5formcore.js和html5form.js。前者用以模拟不支持的低版本浏览器行为,后者则是在其基础上与ui相关的部分。另外也增加了submit类型的各种属性,取消了浏览器默认的表现行为。
以下便是2个版本,第一个没有ui,第二个基于前者小弄了个ui上去。说明和源码都在链接页面里。
http://army8735.org/Ai/test/html5formcore.html
http://army8735.org/Ai/test/html5form.html

已经修改为晚绑定,这意味着即使元素是后添加进来的,也可以完全模拟html5元素的特性。另外改写使得其对jq1.4版本也有兼容。

二月 28th, 2011

jquery的html5form插件

1 Comment, 前端开发, by army.

http://www.matiasmancini.com.ar/jquery-plugin-ajax-form-validation-html5.html
前阵子在这个地址发现了jq的html5form插件,api设计得不错,功能上稍有欠缺,于是乎借鉴了思想重新写了一个:
http://army8735.org/Ai/html5form.html
暂时提取了较为常用的html5表单属性做成了jq插件,样式自己先凑合的,如此可以节省和统一大量的重复校验和表现工作。那些错误以及提示信息均为新生成的绝对定位节点。
input类型有:email、url、number、date、time、search、color几种类型,这些类型根据不同浏览器的支持表现各不相同。

比如number在webkit下支持非常好,输入框尾部有微调按钮;
color和date在opera下支持非常好,直接内置弹出一个选择框用鼠标点一下即可选择; 在不支持的比如ie下,它会默认解读为type=”text”,无任何影响。

较为常用的属性有:placeholder、autofocus、novalid、required、pattern、maxlength。

placeholder在chrome下支持最好,占位符和输入文字自动用2种颜色表示,其它的webkit用同一种颜色表示;
autofocus也是chrome支持最好,ie等需模拟,多个autofocus将以最后一个为准;
novalid指明这个表单无需验证便可提交;
required表明这个input必须填写;
pattern是自定义正则验证,比如示例中自定义的中文输入框,必须输入中文;
maxlength指定最大输入文字长度,在webkit中超过了就无法输入(safari没校验输入法,chrome没校验回车),其它的不行。textarea上使用了这一属性,在输入时会自动提示长度;
step、max、min等均为新特性,高级浏览器中会体现到,低级浏览器中不出现,渐进增强吧。

这些属性可以组合起来使用,用firebug查看下源码,看看input上的属性即可理解。

API: $(‘form’).html5form(callback);
Callback: 一个回调function,可省略,在form的submit通过html5校验之后触发,this指向form本身,返回值为form.submit的返回值(return false阻止默认提交行为)。
源码,还只是个雏形:
(function() {

var body = $(document.body),
PLACE_HOLDER_CLASS = ‘td_placeholder’,
ERROR_CLASS = ‘td_error’,
TIP_CLASS = ‘td_tip’,
TYPE_VALID = {
‘url’: {
pattern: /^[a-zA-z]+:\/\/[^\s]*$/,
message: ‘url格式不合法’
},
‘email’:…

getScript: function( url, callback ) {
return jQuery.get(url, null, callback, “script”);
},
以上是jq的getScript源代码,只提供了个url和callback,但是遗漏了2个很有特性的参数:charset和async。前者会在页面与script脚本编码不同的时候用到,后者则是个很犀利的特性。
奇怪的是,在jq底层的ajax方法中,却能找到charset的设置:
ajax: function( origSettings ) {
//…
if ( s.dataType === “script” && type…

http://flashteam.tencent.com/post/18/externalinterface-and-javascript/
tencent的flashteam发布过2篇关于ExternalInterface的详解以及优化,着实给广大人民群众带来了不少利益。但是其中有点隐患。比如文中所说的__flash__escapeXML优化:一是少了对&符号的转义,这个一般情况下的确可以省略,因为很少字符串数据中会出现&符号,但是一旦出现就会被转化为&;二是可怜的<被写成了≶,说实话我为了这个bug郁闷了几个星期才发现是字母拼错了……
加入&的解析,不能直接加在正则替换callback中,因为某些隐秘的情况下会出现重复转义&的情况,而且非常难查。所以5次转义最好情况也只能被优化为2次:
//覆盖flash默认通信方法,提高性能
window.__flash__escapeXML = function(s) {
var keywords = {
“\”" : “"”,
“<” : “&lt;”,
“>” : “&gt;”,
“\”" : “&apos;”
};
return s.replace(/&/g, “&amp;”).replace(/(['"<>])/g, function(a, b) {
var…