Profilo di 8735army8735FotoBlogElenchiAltro Strumenti Guida

Blog


01 settembre

基于词法分析的jssc5(开发阶段)

jssc5开发版本测试地址:

http://jssc.googlecode.com/svn/trunk/jssc5/test.html

基于as3和js的。

草草做了个输入框,放在了googlecode的svn上,所以ie下看这个反而会解析为html,其它的可能会只是文字。勉强一下了。

仅写了ecmascript4的lexer(即javascript和actionscript),在词法分析上多做了一些语法分析上的纠错功能,语法分析的内容本次将暂不涉及到。用mootools、jquery、dojo的代码测试了下,目前所有已知bug都已修正。

本次jssc5是基于词法分析的,功能比以前大大增强,性能也有显著增加。但缺点就是必须为每种语言编写不同的lexer,所以将考虑优先一些常用语言的编写。
(注:是每种不是每一个,像javascript和actionscript就是同种语言属于ecmascript,而c、java、c++也可用同种lexer解析)。

可以输入一些明显错误代码,如:var i = 0s; 词法处理上会告知出现的错误并且暂停分析,错误后面的代码会被格式化后保存。

代码折叠已有,深度也已经计算出来,只是没有让它显示出来。:P

源代码过几天会放到googlecode的svn上,lgpl协议。由于语种过多,我个人熟悉的语言也就那几个,因此有兴趣的人欢迎来编写其它语言的lexer,主页上参与编写的人员将会增加你的名字。

截张工程图片纪念下,哈哈:

---

关于jssc的项目主页以及之前的老版本或者想使用它的,请看这里:

http://code.google.com/p/jssc/

27 agosto

as3中字符串相关操作测试(jssc5)

  源于新的jssc5的设想,开辟基于词法分析和语法分析的高亮器。当然,词法分析和语法分析我还在学习中,编译原理没好好学,大学时间那个悔啊……

 

  简单测试了下,主要是性能方面。hax曾经做过chrome下面正则的测试,实验证明google浏览器对正则的优化真的很牛X!不过我考虑仍将继续基于flash平台来做,原因很显然:词法分析必须遍历字符串,而js的遍历性能目前来看是实在不敢恭维的。

 

大概排序一下吧:

直接比较字符串最快:s == "string";

用as3内置的indexOf来寻找其次;

判断charCode和indexOf差不多:比如单个字符是否数字;

用isNaN结合parseInt来判断是否数字就比较慢了,但不限单个字符,大概是charCode的数倍到十倍,所以要综合考虑;

用正则最慢!这和js情况正好相反,但正则功能最强大,不过目前情况来看用到正则的可能性很少。

 

  唔,jssc5搞定后JAse研究就能迈进一大步了。

 

----

 

更新:

发现Array类的push()操作性能居然还不如手动写入?!

真晕啊,不知道Adobe搞什么鬼。

array.push(value)还不如array[array.length] = value;,当然舍却length用计数器还要快一点点。

20 luglio

一个js闭包的小例子

上午看到无忧脚本里一个闭包的小讨论贴,下午就碰到blue来说一个js问题。眼不转头不抬回了句:“代码不全。”结果才发现,是我智商不全。

 

举个小例子:

<ul>

  <li id="a1">1</li>

  <li id="a2">2</li>

  <li id="a3">3</li>

</ul>

写个代码:

for(var i = 0; i < 4; i++) {

  var oLi = document.getElementById("a" + i);

  oLi.onclick = function() {

    alert(i);

  }

}

 

你会认为每个li元素分配的点击事件是弹出各自的序号吗?如果答案是“是”,那么答案就错了。最终结果会是,所有弹出的均是4,根本不是1、2、3。因为i的变量只保存了一个,每个li弹出的都是i,i最后是4,并非我们所想每个li元素会保存自己的序号。

 

再来看blue给的代码:

function User( properties ) {
    //遍历对象属性,确保它作用域正确(如前所述)
    for ( var i in properties ) {
        //为属性创建获取器
        this[ "get" + i ] = function() {
            return properties[i];
        };
        //为属性创建设置器
        this[ "set" + i ] = function(val) {
            properties[i] = val;
        };
     }
}
//创建一个新user对象实例,传入一个包含属性的对象作为种子
var user = new User({
    name: "Bob",
    age: 44
});
//请注意name属性并不存在,因为它在properties对象中,是私有的
alert( user.name == null );
//然而,我们能够使用用动态生成的方法getname来访问它
alert( user.getname() == "Bob" );
//最后,我们能看到,通过新生成的动态方法设置和获取age都是可以的
//user.setage( 22 );
alert( user.getname());

 

最终会弹出什么?false、true和啥?当然不是Bob,会是44,这个原理和上面那个是一样的。

 

user.getname()中的那个i,在循环的时候确实是等于"name",于是this["get" + i]为类本身赋予了一个getname的方法,返回name的属性值,也就是Bob。可是在循环下一个的时候,i变成了age,于是return properties[i]也变成了return properties["age"]了,这就是原因所在。

 

既然知道为什么了,那么有希望拯救吗?

当然有!

 

一种方法是将变量存到别的地方上去,比如开头的那个例子将序号存到元素的name属性上;当然技巧性的就是使用闭包了。

function User( properties ) {
  for ( var i in properties ) {
    this[ "get" + i ] = (function(s) {
      return function() {
        return properties[s];
      }
    })(i);
    this[ "set" + i ] = function(val) {
      properties[i] = val;
    };
  }
}
var user = new User({
  name: "Bob",
  age: 44
});
alert( user.getname());

 

试一试,效果如何?通过匿名函数的方法,将变量存到实参上面,如此就保存了每个变量,防止最后被替换掉。

03 luglio

讲完mootools的native,翻译篇mt和jq比较文章

http://jqueryvsmootools.com/#mottos

今天开了个小会,讲解了mootools的navative实现,最后也提到目前前端用mootools、技术部用jquery的事情。我是比较喜欢mootools的,于是从javaeye的mootools圈子里找了篇经典分析文章。原文很长,上面是链接,我就翻译了一段看看。

格言反应了一切

如果你打开jquery首页,页面顶部是这样说的:

jquery是一个快速的、简单的javascript库,它为快速web开发者们简化了遍历html的document、事件句柄、动画、ajax接口。jquery的设计目的就是改变你书写javascript的方式。

而如果你进入mootools的网站,则会发现这句话:

mootools是一个健壮的、模块化、面向对象的javascript库,它是为中高级javascript程序员设计的。借以其优雅的设计、良好的文档、清晰的API,它能让你书写出强健的、灵活的、跨浏览器的代码。

我想这是一个真实的总结。如果你问我(你正在读这篇文章,所以在这里假设你问我),并且问的并不是孰优孰劣,而是哪个对你而言更适合?答案是:这两个框架并不是尝试做相同的事情。虽然在功能方面它们有相同之处,但它们做的事情却完全不同。

jquery描述自己时提到了HTML、事件、动画、ajax和web开发者,mootools则谈到了面向对象以及编写强健、灵活的代码。jquery期望改变你书写javascript的方式,mootools则为中高级javascript程序员而设计。

一定程度上考虑一个javascript库的方法是将其与工具箱作比较。mootools尝试将javascript语言本身变得更加理想(根据mootools作者所说),提供的API就像是javascript语言本身具有的或者增强的,并不仅仅限于DOM。jquery则是个让你轻松地使用独立的集合方法来友好控制DOM的工具箱。大部分人在写javascript程序时都将主要精力放在DOM上,所以在很多情况下,jquery正是你想要的。

当你用mootools写代码时,大部分情况下你感觉你就是在写javascript。如果你对javascript作为一门语言来说不感兴趣,那么学习mootools就是件很吃力的事情。反之若觉得javascript是门有趣、强健、富有表现力的语言,那么我的个人意见是,mootools则是更好的选择。

15 giugno

javascript中的数据类型、Object与Function

原文见:http://hi.baidu.com/erik168/blog/item/b33ea8c41e8b81c239db49ce.html

1. 数据类型

javascript中包含6种数据类型:undefined、null、string、number、boolean和object。其中,前5种是原始数据类型,object是对象类型。
object类型中包括Object、Function、String、Number、Boolean、Array、Regexp、Date、Globel、Math、Error,以及宿主环境提供的object类型。

2. 类型判断

通常在javascript中进行类型判断主要通过3种方式:typeof、instanceof、constructor。

2.1 typeof
typeof操作可能返回的类型为undefined、object、number、string、function、boolean。但是会有一些情况并不能完全判断准确。比如typeof new String('')的值为object。

2.2 constructor
有时候我们可能会很偷懒的使用a.constructor == String进行类型判断,但是constructor其实是不靠谱的东西。因为当我们调用a.constructor的时候,内部操作其实是ToObject(a).prototype.constructor(ToObject是什么,看下文分解)。
看下面一段代码就能明白:
String.prototype.constructor = Number;
alert('test'.constructor == String);
Result:false

或者

function MyClass() {
}
MyClass.prototype = {};
alert((new MyClass).constructor == MyClass);
Result:false

而且,通过constructor并不能判断出对象实例类型的继承关系。因为javascript的继承其实是通过原型链实现的(原型链是什么,看下文分解)。
另外,null.constructor会抛出运行时的TypeError,所以使用constructor除了不靠谱外,还可能伴随着异常的风险。

2.3 instanceof
例子:a instanceof String
关于object类型的判断,使用instanceof判断是比较靠谱的方法。instanceof所做的事情是,先取出类型对象(String)的prototype成员(String.prototype),然后和要判断类型的对象(a)的原型链中的对象逐个比较。当发现是一个对象的时候返回true,原型链中当前节点是null的时候返回false。

类型判断示例:判断一个变量是否是字符串类型
function isString(str) {
    return (typeof str == 'string' || str instanceof String);
}

3. 类型转换
ecma262中描述了以下几种类型转换的操作:(还有其他的比如ToInt32等,这里就不列了)
ToNumber:转换成number型
ToString:转换成string型
ToBoolean:转换成boolean型
ToObject:转换成object型
ToPrimitive:转换成原始类型

每种操作都描述了从什么类型转换成该类型的映射。比如上文的'a'.constructor中,就包含解析器使用ToObject将‘a’转换成object的一个隐式操作。

这里想要主要说的是ToPrimitive。ToPrimitive用于转换成原始数据类型。当要转换的量已经是原始类型时,会直接返回。如果要转换的是一个Object,那会调用[[DefaultValue]]方法做转换。([[DefaultValue]]是什么,下文分解)该方法可以传入一个hint参数,说明需要将Object转换成字符串或数字。如果要转换成字符串,则调用Object的toString方法,如果要转换成数字,则调用Object的valueOf方法。具体在运行时什么时候应该转换成什么类型,请参考ecma262中关于expression的描述部分。

------------------- 切割线:写累了,喝点水去 ----------------------

4. Object

除了5种原始类型外,一切都是Object,包括Object、Function、Array等等,他们的实例和构造器,都是Object。那Object是一个什么东西呢?
Object是一个:无序的成员集合
它是一个集合,说明它包含0-n个成员。而它是无序的。
每一个成员由以下3个部分组成:名称、值、特征集合

下面的代码中:
var obj = {'key': 'value'};
key就是成员名称,value就是值,obj这个Object从代码上看起来包含了一个成员,注意,是从代码上看而已。这里我们不去深究它先。

那特征集合是个什么东西呢?
javascript的对象成员可能包含下面几种特征的0个或多个:ReadOnly、DontEnum、DontDelete、Internal。
ReadOnly:拥有这个特征的成员是不能被程序修改的。
DontEnum:拥有这个特征的成员是不能被for in遍历的。
DontDelete:拥有这个特征的成员是不能被delete操作删除的。
Internal:代表这个成员是内部成员。通常内部成员不能被程序以任何方式访问,但是有些javascript的引擎实现将它以特殊方式暴露,使得可以访问对象的某些内部成员。

一个对象的Internal成员以[[xxxx]]的方式来表示。
下面列一些和本博有关的的Object可能包含的internal成员。
[[Class]]:表示该对象的类型。比如function Object的[[Class]]成员的值是"Function"
[[Get]](PropertyName):获取对象的属性值。
[[DefaultValue]](Hint):用于ToPrimitive进行类型转换时调用。hint参数可能的值为"string"或"number"
[[Prototype]]:[[Prototype]]成员实现了javascript中所谓的“原型链”。一个对象的[[Prototype]]成员可能是object对象,或者是null。只有Object.[[prototype]]为null,其他任何对象的[[Prototype]]成员都是一个Object
[[Call]]:function Object特有的成员,在函数被调用的时候,就是调用的[[Call]]。
[[Construct]]:function Object特有的成员,在函数作为构造器,被new操作符用于创建对象的时候,就是调用的[[Construct]]。
[[Scope]]:[[Prototype]]成员实现了javascript中所谓的“作用域链”。


------------------- 切割线:手开始酸了 ----------------------


5. function Object的创建过程

解析器在遇到function declaration或者function expression的时候,会创建一个function Object。步骤大致如下:
1) 解析形参和函数体
2) 创建一个native ECMAScript Object:F
3) 设置F的[[Class]]、[[Prototype]]、[[Call]]、[[Construct]]、[[Scope]]、length属性
4) 创建一个new Object():O
5) 设置O的constructor属性为F
6) 设置F的prototype属性为O

在这个创建过程里,要说明的几点是:
1) 步骤3中F的[[Prototype]]被设置为Function.prototype
2) 用户自定义的function,都会同时具有[[Call]]和[[Construct]]这两个internal属性
3) 解析器会自动给每一个function Object初始化一个prototype成员。而F.prototype.constructor == F,所以,当我们没有重新定义这个F的prototype成员的时候,F的实例的constructor成员是靠谱的。因为(new F).constructor其实方位的是F.prototype.constructor,而解析器默认初始化给你的F.prototype.constructor就是F。
4) 关于[[scope]]和作用域链的问题,下文分解

还要提的一点是,function declaration和function expression是不一样的

function declaration:
function fn() {}

function expression:
var a = function () {};
function () {};


------------------- 切割线:坐着怎么那么热呢 ----------------------


6. 原型链

首先要澄清的一点是,我们通常会使用myfunction.prototype的方式进行原型扩展,所以我们在听到“原型链”这个词的时候,会觉得这里的“原型”指的是myfunction.prototype。其实不是,“原型”指的是对象的[[Prototype]]。当然,对象的[[Prototype]]就是其真实构造器当前的prototype成员对象。

上文中有提过,一个我们通过程序创建的function Object,一定会包含[[Call]]和[[Construct]]这2个internal成员。它们做了什么事情呢?

[[Call]]:
1. Establish a new execution context using F's FormalParameterList, the passed arguments list, and the this value as described in 10.2.3.
2. Evaluate F's FunctionBody.
3. Exit the execution context established in step 1, restoring the previous execution context.
4. If Result(2). type is throw then throw Result(2). value.
5. If Result(2). type is return then return Result(2). value.
6. (Result(2). type must be normal.) Return undefined.

[[Construct]]:
1. Create a new native ECMAScript object.
2. Set the [[Class]] property of Result(1) to "Object".
3. Get the value of the prototype property of F.
4. If Result(3) is an object, set the [[Prototype]] property of Result(1) to Result(3).
5. If Result(3) is not an object, set the [[Prototype]] property of Result(1) to the original Object prototype object as described in 15.2.3.1.
6. Invoke the [[Call]] property of F, providing Result(1) as the this value and providing the argument list passed into [[Construct]] as the argument values.
7. If Type(Result(6)) is Object then return Result(6).
8. Return Result(1).

一切都很清楚了。当我们创建一个对象,也就是我们new的时候,调用的是function Object的[[Construct]]成员方法。在上面的描述中,3、4步描述了[[Prototype]]成员的创建过程,就是构造器的prototype成员。

好的,那回到之前,我们使用obj.property来获取obj对象的属性的时候,其实调用的是obj对象的internal方法[[Get]]。那我们看看[[Get]]方法调用做了哪些事情:
1. If O doesn't have a property with name P, go to step 4.
2. Get the value of the property.
3. Return Result(2).
4. If the [[Prototype]] of O is null, return undefined.
5. Call the [[Get]] method of [[Prototype]] with property name P.
6. Return Result(5).

可以看出来,当我们获取对象obj的某个成员的时候,会在obj对象自身成员里查找是否存在该成员。如果不包含,则到obj.[[Prototype]]这个对象中查找名字成员,如果还不存在,则到obj.[[Prototype]].[[Prototype]]这个对象里找,直到某个[[Prototype]]是null为止。查找的过程就是一个顺藤摸瓜的事情,这个藤就是我们所谓的“原型链”。

我不想说太多原型链和继承之间的关系与实现,这方面的资料在网络上已经太多太多。我只想把原型链脱光了告诉大家,原型链是什么。


------------------- 切割线:脑子发胀中 ----------------------


7. 函数调用过程与作用域链

讲到作用域链,就要扯到函数的调用。当我们有一个函数
function fn(param) {}
我们去调用它
fn(1);
这个时候解析器为我们做了什么呢?

有一定经验的javascript工程师也许会用过arguments、用过闭包、知道作用域,这一切的一切,都和execution context有关。

当我们进入一个函数调用的时候,解析器会为我们创建一个活动对象(Activation Object ),假设这里把这个活动对象叫做ac(为什么不叫ao呢,因为喜欢c)。然后做下面的事情:
1. 初始化arguments对象,并将它添加到这个ac中。这个时候,对象ac就拥有了一个name为arguments的成员。这里arguments初始化过程就不具体说了,感兴趣的可以看ecma262的章节10.1.8。
2. 解析形参,并使用函数调用时传递的参数初始化。在上面的调用例子fn(1)中,这个时候,ac就拥有了一个name为param的成员,这个成员的值为1。
3. 对function declaration进行初始化,为所有FunctionBody中的function declaration,创建function Object,并添加到对象ac中作为ac的成员。在这一步,假设ac中已经包含了同名属性,会被覆盖掉。
4. 对var声明进行初始化,为所有var声明,在对象ac中创建同名成员,并初始化为undefined。在这一步,假设ac中已经包含了同名属性,不会被覆盖掉。
5. 初始化作用域链,并将这个作用域链与当前的执行上下文相关联。这个作用域链是一个链式列表,最前段是进入函数调用时初始化出来的活动对象ac,然后后面跟着的是该函数的[[scope]]的成员。[[scope]]是个什么东西呢,就是这个链。假如函数体中有创建function Object,叫做innerFn,那innerFn的[[scope]]成员,就是这个作用域链。当innerFn被调用时,会初始化新的活动对象,新的作用域链。新的作用域链就是初始化自这个新的活动对象和innerFn的[[scope]]。

那scope chain是什么作用呢?看下面的描述,来自10.1.4
During execution, the syntactic production PrimaryExpression : Identifier is evaluated using the following algorithm:
1. Get the next object in the scope chain. If there isn't one, go to step 5.
2. Call the [[HasProperty]] method of Result(1), passing the Identifier as the property name.
3. If Result(2) is true, return a value of type Reference whose base object is Result(1) and whose property name is the Identifier.
4. Go to step 1.
5. Return a value of type Reference whose base object is null and whose property name is the Identifier.
可以看出,我们在访问一个变量的时候,其实是从和当前执行上下文相关的作用域链中查找成员。

在程序正常在全局下的函数,其[[scope]]成员的值是global object,所以无论任何调用,在作用域链的尾端,一定会是global object。在浏览器宿主环境下,就是window。

------------------- 切割线:感叹中,怎么还没写完,唠唠叨叨的,受不了自己了 ----------------------

8. 函数调用过程中的this

在函数的调用中,this是个什么东西,又是由什么决定的呢?在ecma262中,这是个比较绕的东西,其描述散落在世界各地。

首先,在10.2.3中告诉我们:
The caller provides the this value. If the this value provided by the caller is not an object (note that null is not an object), then the this value is the global object.
我们可以知道,caller可以提供给我们this。如果没有提供,则this为global object。问题又来了,caller是怎么提供this的?

在11.2.3中,找到如下关于Function calls的描述:
The production CallExpression : MemberExpression Arguments is evaluated as follows:
1. Evaluate MemberExpression.
2. Evaluate Arguments, producing an internal list of argument values (see 11.2.4).
3. Call GetValue(Result(1)).
4. If Type(Result(3)) is not Object, throw a TypeError exception.
5. If Result(3) does not implement the internal [[Call]] method, throw a TypeError exception.
6. If Type(Result(1)) is Reference, Result(6) is GetBase(Result(1)). Otherwise, Result(6) is null.
7. If Result(6) is an activation object, Result(7) is null. Otherwise, Result(7) is the same as Result(6).
8. Call the [[Call]] method on Result(3), providing Result(7) as the this value and providing the list Result(2) as the argument values.
9. Return Result(8).
从步骤6、7中可以看出来,如果MemberExpression的结果是一个Reference的话,提供的this应该是GetBase(Reference),否则是空。步骤7中还有描述了6的结果是活动对象的情况,我们这里忽略。
又有疑问了,Reference?Reference是什么,GetBase又是什么?

我们在8.7中,找到了Reference的答案。这里的描述比较长,我只摘了可以满足我们需要的一段:
A Reference is a reference to a property of an object. A Reference consists of two components, the base object and the property name.

The following abstract operations are used in this specification to access the components of references:

GetBase(V). Returns the base object component of the reference V.
GetPropertyName(V). Returns the property name component of the reference V.
已经很明显了,一个Reference必须引用一个对象的一个属性。所以我们通过obj.method()来调用的时候,obj.method这个表达式生成了一个中间态的Reference,这个Reference的base object就是obj,所以GetBase的结果就是obj,于是obj被caller提供作this

我曾经看到很多文章,举了类似obj.method()这样的调用例子,认为obj就是caller,来解释这番话:
The caller provides the this value. If the this value provided by the caller is not an object (note that null is not an object), then the this value is the global object.

这其实是说不通的。
caller绝不可能是obj,否则被attachEvent的函数或对象方法,他们运行时的this就解释不通了。
所以,通过我们自己代码调用的函数,caller由脚本引擎执行控制所决定;在浏览器宿主环境通过事件触发的,caller由浏览器控制的行为所决定。


------------------- 切割线:坚持坚持,好不容易想写点正经东西,快了 ----------------------


9. 关于原型链的补充——原型链会不会是圆形链

这个问题是telei同学提出的。答案是:不会
回头看看[[Construct]]的步骤,我们可以发现,创建一个对象obj时,obj.[[prototype]]成员被赋予其构造器的prototype成员。但是当构造器的prototype成员被指向为另外一个对象的引用时,obj.[[prototype]]依然是其构造器的前prototype对象。

描述代码如下:(注释里是说明)
function A(){
         this.testA = new Function();
}
function B(){
         this.testB = new Function();
}

var a = new A();

B.prototype = a;
//a.[[prototype]] == {};(不是真的等,{}表示的是Function A初始的prototype object。下同)

var b = new B();
//b.[[prototype]] == a;
//b.[[prototype]].[[prototype]] == a.[[prototype]] == {};

A.prototype = b;

var a2 = new A();
//a2.[[prototype]] == b;
//a2.[[prototype]].[[prototype]] == b.[[prototype]] == a;
//a2.[[prototype]].[[prototype]].[[prototype]] == b.[[prototype]].[[prototype]] == a.[[prototype]] == {};

//最后测试一下,很搞笑的
alert(a instanceof A);


最后特殊的解释:好吧,上面代码的最后出现了很搞笑的事情,合乎语言的实现,但不合乎正常以及不正常地球人的逻辑。
我们知道,a对象是被A构造器创建出来的,所以a是A的实例。
但是,上面类型判断那里有讲,instanceof是通过构造器prototype成员与对象原型链的比较来判断的。所以当对象a被创建后,如果创建它的构造器的prototype发生了变化,a就和他妈(构造器)没任何关系了。
看到这里,你确定你还想要在实例化对象后,修改构造器的prototype成另外一个对象吗?
11 giugno

flash player,在浏览器下bug多多

先前知道了fp在ff下一大bug:fileuploadreference类无法传递cookie和session,这就造就了想在ff下实现验证用户上传组件的困难——除非能手动将cookie或session传递到flash里,再在upload的时候将它作为参数附加在urlloader上。

今天,我们伟大的ie又冒出来一个令人啼笑皆非的bug:若想使用ExternalInterface的call方法(调用外部js),这个swf的<object>必须跟个唯一的id。否则的话,哼哼,对不起,报的错都能让人一头雾水:

null为空或不是对象,代码0,行1,char ***。

若按照这个错误去找,omg,第一行可是doctype声明,找死也找不到错误所在的。不得不说,adobe的孩子flash player和浏览器们的婚姻,似乎不像想象中的那么顺利。

15 maggio

百度global.js读后感 & flash在ie6下的bug

[url]http://hi.baidu.com/ui/scripts/global.js[/url]
地址在上面,这里就不贴了,随后讲到哪里的话会贴下片段。可能以后baidu会变更,但也应该很容易找到。
这篇主要是我一时兴起写下的,达人级就不必看了。主要为了加深下印象,另外给公司的前端部门那几位瞅瞅。
---
/* declare namespace */
(function(){
 var namespaces = [ "System","BdElement", "BdBrowser", "BdEvent", "BdUtil", "BdAjax","BdString"];
 for(var i = 0, j = namespaces.length; i < j; i ++){
  if(window[namespaces[i]] != 'object')
   window[namespaces[i]] = {};
 }
})();
开头是baidu申明的命名空间,分为:System、Element、Browser、Event、Util、Ajax、BdString几个部分。其中Util我没细看,因为这玩意儿和网站耦合的比较紧,就懒得读了,其中还有个hi_tracker,猜想和百度hi相关。其它的东西倒是值得读一读的。
接下来的/* attach methods */很长,还是分块儿来吧~
/* BdBrowser scope */
var ua = navigator.userAgent.toLowerCase();
var isStrict = document.compatMode == "CSS1Compat",
 isOpera = ua.indexOf("opera") > -1,
 isSafari = (/webkit|khtml/).test(ua),
 isSafari3 = isSafari && ua.indexOf('webkit/5') != -1,
 isIE = !isOpera && ua.indexOf("msie") > -1,
 isIE7 = !isOpera && ua.indexOf("msie 7") > -1,
 isGecko = !isSafari && ua.indexOf("gecko") > -1,
 isBorderBox = isIE && !isStrict,
 isWindows = (ua.indexOf("windows") != -1 || ua.indexOf("win32") != -1),
 isMac = (ua.indexOf("macintosh") != -1 || ua.indexOf("mac os x") != -1),
 isAir = (ua.indexOf("adobeair") != -1),
 isLinux = (ua.indexOf("linux") != -1),
 isSecure = window.location.href.toLowerCase().indexOf("https") === 0;
// remove css image flicker
if(isIE && !isIE7){
try{
    document.execCommand("BackgroundImageCache", false, true);
}catch(e){}
}
var browsers = {
isOpera : isOpera,
isSafari : isSafari,
isSafari3 : isSafari3,
isSafari2 : isSafari && !isSafari3,
isIE : isIE,
isIE6 : isIE && !isIE7,
isIE7 : isIE7,
isGecko : isGecko,
isBorderBox : isBorderBox,
isLinux : isLinux,
isWindows : isWindows,
isMac : isMac,
isAir : isAir
};
for(var p in browsers){
BdBrowser[p] = browsers[p];
}
这里主要是处理针对不同浏览器的操作的。可能有人眼熟了:这不是Ext嘛!没错,对于js这种宿主环境很多的语言来说,相互学习借鉴本身就是一种提倡的方法。其实不止于此,其它的不也都是么?
上面这段主要讲判断浏览器以及https等放到BdBrowser下面。值得注意的是其中有个针对ie6的bug的修复——即ie6特殊情况下的图片闪烁bug,就是用document.execCommand("BackgroundImageCache", false, true);搞定的。
/* BdElement scope */
    window.Bd$ = BdElement.check = function(id){
        return typeof id == 'string' ? document.getElementById(id) : id;
    }
    BdElement.removeNode = isIE ? function(){
        var d;
        return function(node){
            if(node && node.tagName != 'BODY'){
                d = d || document.createElement('DIV');
                d.appendChild(node);
                d.innerHTML = '';
            }
        }
    }() : function(node){
        if(node && node.parentNode && node.tagName != 'BODY'){
            node.parentNode.removeChild(node);
        }
    }
百度自己的getElementById和removeChild。前者实现为全局的Bd$,用jquery的用户可能很熟悉$("#id"),就是这种形式;后者添加在BdElement上,方法为removeNode,和IE特有的方法同名。
由于IE的removeChild存在内存泄漏,所以现在基本上用的都是这种方法:将要移出的node放在一个div中,然后讲div的innerHTML清空。也有人提出outHTML等多种改进方案,但通用性稳定性等尚无法确保,所以主流方案还是这样。
/* BdEvent scope */
    BdEvent.addEvent = function(el, fn, handler){
        if(isIE){
            el.attachEvent("on" + fn, handler);
        }else{
            el.addEventListener(fn, handler, false);
        }
    }
    BdEvent.removeEvent = function(el, fn, handler){
        if(isIE){
            el.detachEvent("on" + fn, handler);
        }else{
            el.removeEventListener(fn, handler, false);
        }
    }
两个添加事件侦听方法,仅需区别是否IE即可,无需过多解释。
BdEvent.addDOMLoadEvent = (function(){
        // create event function stack
        var load_events = [],
            load_timer,
            script,
            done,
            exec,
            old_onload,
            init = function () {
                done = true;
                // kill the timer
                clearInterval(load_timer);
                // execute each function in the stack in the order they were added
                while (exec = load_events.shift())
                    setTimeout(exec, 10);
                if (script) script.onreadystatechange = '';
            };
            return function (func) {
                // if the init function was already ran, just run this function now and stop
                if (done) return func();

                if (!load_events[0]) {
                    // for Mozilla/Opera9
                    if (document.addEventListener)
                        document.addEventListener("DOMContentLoaded", init, false);
                    // for Internet Explorer
                    /*@cc_on @*/
                    /*@if (@_win32)
                        document.write("<script id=__ie_onload defer src=//0><\/scr"+"ipt>");
                        script = document.getElementById("__ie_onload");
                        script.onreadystatechange = function() {
                            if (this.readyState == "complete")
                                init(); // call the onload handler
                        };
                    /*@end @*/

                    // for Safari
                    if (/WebKit/i.test(navigator.userAgent)) { // sniff
                        load_timer = setInterval(function() {
                            if (/loaded|complete/.test(document.readyState))
                                init(); // call the onload handler
                        }, 10);
                    }
                    // for other browsers set the window.onload, but also execute the old window.onload
                    old_onload = window.onload;
                    window.onload = function() {
                        init();
                        if (old_onload) old_onload();
                    };
                }
            load_events.push(func);
        }
 })();
addDOMLoadEvent是个值得细看的方法,其中主要的是“DOMContentLoaded”侦听,这个是大部分情况下替代window.onload的方案。由于window.onload是等候页面所有内容加载完成才执行,所以图片等大资源会造成严重的延迟问题,而DOMContentLoaded则是在DOM树建成之后出发,这才是我们想要的。可惜的是,ff、opera等浏览器对此标准都有了良好的支持,但是即使是IE8,也没看到丝毫“悔改”的念头,最后注意代码中注释掉的部分,这是以前曾经流行过的IE方法,原理是创建空的有defer属性的script标签,根据其onreadystatechange来判断是否DomReady。但这主要有2个缺点:https需要特殊判断;页面中有iframe时须得iframe加载完。
所以代码中用的方法是:dom标准浏览器用DOMContentLoaded,safari不断侦听document.readyState,其它浏览器继续用window.onload()。这虽然解决了不少问题,但并不完美。ie还是需要特殊待遇——doScroll。这里并没有doScroll,其主要原理是IE下只有当DOM树构建完成后才能doScroll,所以便可建立一个侦听,不断地地document.body.doScroll('left');,成功的话说明DOM加载完毕。注意这句需要放在try里面,这样出错的话便可继续触发侦听,直到成功(页面DOM加载完)为止。
/* BdAjax scope */
    BdAjax.getXHR = function(){
        var xhr = null;
        try{
            return (xhr = new XMLHttpRequest());
        }catch(e){}
        for(var i = 0, a = ['MSXML3','MSXML2','Microsoft']; i < a.length; i ++){
            try{
                xhr = new ActiveXObject(a[i]+'.XMLHTTP');
                break;
            }catch(e){}
        }
        return xhr;
    }
BdAjax创建xhr的工具,倒是省写了些代码,可以借来用用。不过似乎xhr的设计意图没让开发人员直接使用,而是下面的request。
BdAjax.request = function(url, json){
        var xhr = this.getXHR();
        if(!xhr){
            throw new Error("cant't initialize xhr instance.");
        }
  var options={};
        options.method    = (json.method || 'get').toLowerCase();
        options.asyn      = true;
        options.onSuccess = json.onSuccess || function(){};
        options.onFailure = json.onFailure || function(){ new Image().src = "/sys/statlog/1.gif?m=ajax-request&v=" + encodeURIComponent(url) + "&t=" + new Date().getTime();};
      
       
        options.postData = json.postData || null;
        xhr.open(options.method, url, options.asyn);
  if("post" == options.method.toLowerCase()){
            xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        }
        xhr.onreadystatechange = function(){
            if(xhr.readyState == 4){
                if(xhr.status == 0 || xhr.status == 200){
                    options.onSuccess(xhr);
                }else{
                    options.onFailure(xhr);
                }
            }
        }
        xhr.send(options.postData);
    }
request首先创建一个xhr,第一个参数url无须解释,第二个json倒是有点小复杂,这个名字容易使人误解,以为是传回的数据。其实是要ajax的一些参数,叫params倒是更好。不过从格式来看baidu的ajax请求默认就是json,所以有这个名字倒也不为怪。整体方法理解起来并不难,咱不多说了,这种风格倒是值得借鉴的。
BdAjax.loadJS=(function()
 {
  var head ;
  return function(jsUrl){
   head = head || document.getElementsByTagName("head")[0];
   var s=document.createElement("script");
   s.type="text/javascript";
   s.src=jsUrl;
   head.appendChild(s);
  }
 })();
loadJS,就是把script标签append到head上。我想不通多判断一下head有何意义……
BdString.trim=function(str)
 {
  return str.replace(/(^[\s\t\xa0\u3000]+)|([\u3000\xa0\s\t]+$)/g, "");
 }
 BdString.byteLength = function(str){
  return str.replace(/[^\x00-\xFF]/g, "ly").length;
 }
 BdString.subByte = function(s, n){
  if(this.byteLength(s)<=n)
   return s;
  for(var i=Math.floor((n=n-2)/2),l=s.length; i<l; i++)
   if(this.byteLength(s.substr(0,i))>=n)
    return s.substr(0,i) +"\u2026";
  return s;
 }
三个关于String的方法:trim去除两边空白;byteLength将双字节字符替换成两个英文字母再计算长度;subByte有点特殊,截取几个字节的字符串。奇特的是,它首先将最后增加的\u2026——也就是半个省略号算上,所以你要截取2个字节的字符串,只会返回\u2026,只有当截取的字符串大于2个时,它才会真正起作用。
 
另外今天遇到一个ie6的flash的bug,查了半天终于在别人的空间里找到了,真郁闷!
“FLASH在IE6下Error 2032错的处理
被这个问题折腾了2天。
Flash在IE6中POST数据时会出现ioerror Error #2032
这个错误是Gzip和no-cache同时出现造成的。
解决方法是在返回的页面里增加HTTP头,
Response.AppendHeader("Cache-Control", "no-cache, no-store, must-revalidate, max-age=-1");
  
总结:为什么昨天突然爆发呢?服务器没做任何修改,难道是MS搞的鬼???”
27 aprile

jssc 4.2迟来的测试

http://army.javaeye.com/blog/360024

起因hax对于jssc 4.2的dojo的9000行代码测试,性能差到难以想象;后来改进的innerHTML的事件侦听,结果才勉勉强强。

“今天才忙的过来测试了一下,ie、ff、chrome都试了试。

很奇怪的是,flash在这三种浏览器的运行效率居然有略微的不同,其中ff上跑完9000行代码大概是2.5秒,随机分布有时会略微低一些,比如2.4秒等;而ie和chrome都要慢一些,大概在2.7秒、2.8秒的样子。

上面只是传递到flash分析再传递回来分析结果的时间,还未加上innerHTML,假如加上,chrome就明显占了优势,大概在3.7秒左右;ff则是4.1秒左右;ie最惨,占了4.6秒。

至于js分析9000行代码能达到500ms的效率我感到很吃惊,因为之前的版本以及使用hl的结果,让我感到js的分析能力远远达不到这个要求;在flash中由于对其性能的信任,我没有用正则,直接进行的字符串遍历。有机会到要试一试正则在flash中比字符串遍历的性能到底有没有提高,提高多少。”

后续进行中,期待Dr.hax的js分析器,将会对目前js和as在分析上的局面有哪些探知和改变?
12 aprile

我的JAse被通过了!!!

第三代(个)预研项目JAse,近日终于被通过了!仔细看一下邮件日期,是4月10号。

在2007年初的jssc刚开始开发时,我就想过这只是静态的语法高亮,假如是累死ide那样的拥有动态、输入、提示、纠错的该怎么办。时隔两年,当jssc取得不错的成绩时,JAte应运而生,带我走进文本编辑器的领域。

然 而既是如此,类似ide的念头还在,但却无从下手。所以我努力地去啃编译原理,夜以继日地思索解决方法、算法,甚至希望在梦中能够如发现DNA双螺旋结构 一般灵感突现。遗憾的是,至今没有什么实质性的进展。同时可喜的是,我终于在网络上找到一个有点类似的项目——MegaxEdtor,并且也和作者取得联 系,期待他精彩的博客解说~

地址:http://www.cppblog.com/megax/archive/2009/04/10/75830.html

接下来才是正题,我的JAse被通过了!

要在code.google.com申请新的开源项目JAse(参见JAte名字来历),结果和sourceforge有冲突,没办法只好和冲突作者取得联系,以求得他的同意。这要多亏了地道的帮助,和对方答复了几封邮件,最终取得成功。

几封邮件纪念如下:

---

Donnerbauer 发送至

Hi out there,

could you please tell me, what kind of project your "jase" is (compared to mine)?

Regards

Ruediger

---

army8735  发送至  Donnerbauer

Thanks for your reply!
 
My project is a web-based richtext editor written with action  script  3 and java script . (jase = Java script &Action script Syntax Editor)
 
Features:
Keyword highlighting
Syntax autocomplete
Error detecting/correcting
 
My goal is to make it a web-based IDE for various file types(e.g., java script , action  script , c++...). I haven't considered a multi-language release but decide to make a Chinese version first.
 
Regards
army8735

---

Donnerbauer 发送至

Hi army8735,

I've just answered the google name request - good luck with your
project. If you like, you can use "Jase" as your project name. If you
are going to do a german translation one day, don't hesitate to ask me;-).

Regards

Ruediger
26 marzo

我猜对了!关于hax的IE全局变量格的DID。

 
起源是这里,Dr. hax为我们详细讲解了ie中的DID,其中至少分裂出2个变量格,结尾给我们留了一个谜,谁能找出第3个变量格?
 
结果我蒙对了,获得奖品如下:
=====
 
hax 7 小时前   回复
Army 写道

猜一下,难道是ie里对dom节点的id直接引用?
中!

发奖品了。。。奖品选项如下:

A. 与Dr. Hax在张江地铁站周边方圆5公里以内的食堂里共进午餐
B. 与Dr. Hax在张江地铁站周边方圆5公里以内的食堂里共进晚餐
C. 获得由Dr. Hax签名的《Ajax实战:实例详解》一本
D. 获得没有Dr. Hax签名的《Ajax实战:实例详解》一本
 
===
 
我猜对了!我猜对了!我猜对了!
11 marzo

最近在看《编译原理》

《编译原理》,也就是龙书三、紫龙书。的确够难的,可能是我读过的最难懂的一本书了——当然,除了霍金的《时间简史》。我估计半年都读不完。

目前进度是第4章文法分析了吧,前面有的地方跳过去了,因为暂时感觉不需要,比如说设计编译器以及编译成汇编什么什么的,我需要的是对jssc有帮助的地方。

回想2007年一时冲动写下破烂不堪的jssc1到目前为止,真没想到会有如此的发展经历。在读《编译原理》的时候,我居然发现自己的算法分析的设计和书中许多地方不谋而合!当然我那点小聪明根本不及《编译原理》的九牛一毛,但我所希望的是,下一个版本中能够带来更强的特性。

最近坛子里看到龙博编辑器的作者说“要让老外们来模仿我们”云云的话,不由得赞叹一下。以前也是我模仿syntaxhighlighter,现在恐怕也快要倒过来了吧。所以我去看《编译原理》,准备可能的jssc5,设计出前端最好的高亮器。

目前想到的就是靠as3的高效率和oop,用真正的文法分析来分析语法,能够提炼类、类型、类属性、类方法,能够对某些可能的错误给出错误提示。这个想法显得有些夸张,人有多大胆,地有多大产么?
17 febbraio

jssc 4.1 has released!

http://code.google.com/p/jssc/

众人拾柴火焰高,经过一段时间的发布和回馈,jssc4.1的版本公布出来,以供开发者们使用。这次做的比较大的革新有:css控制,自定义颜色,标记语言增强,jsp语法支持,预留接口。

1.除却高亮颜色部分外,其余已经尽可能地交由css控制。我想这也是所有人都希望看到的。

2.为了保持对word等富文本复制的功能,一定程度上牺牲了css控制高亮颜色的便捷。不过除了默认颜色外,可以使用对外提供的reset方法来重新自定义颜色。比如目前的示例就是使用:
reset({
js: {
key: "f33"
},
java: {
key: "39f"
},
as: {
key: "333", num: "333", string: "333"
}
})
重新定义了js、java、as的部分颜色,这也是为什么看上去显得很怪异的原因(我为了让效果更醒目点,当然你可以设置得比我这漂亮多了)。reset()传入一个object,里面的动态属性设置可以参照Config.as文件。

3.对于html、xml的支持被增强了。一些自封闭标签的折叠功能被修正,同时还有另外一些隐藏的bug也被修复。

4.在一些人的大力支持下(我想同时也是要求下@_@),jsp语法被支持。值得注意的是,jsp的高亮颜色并不能直接被自定义。因为jsp解析是继承html的解析并内置组合了一个java解析器,因此颜色部分是由html和java共同决定的,更改另外两者的同时就等于更改了jsp。
示例中jsp颜色的怪异就是因为java的部分颜色被我故意改乱了。

5.在init()方法的第三个参数中,多出了一个留下来的方法接口。它的作用是当客户端不支持flash player或者版本不足时使用这个方法,比如可以异步加载sh或者jssc老版本等。

最后的话是我在上传jssc4.1时发现syntaxhighlighter的2.0信息也被公布了,我没细看。呵呵,不知道它会带来哪些变化。我所希望的就是那个预留方法可以使用sh2.0。
25 dicembre

我楞IE三。

最近实验JAte的缘故,发现了2个很恶心的bug。jssc4的制作已经发现ie的一个ExternalInterface的恶心地方了,没想到现在又发现2个。

1.jssc4中发现的bug:
当出现js通过ExternalInterface接口调用as,as接受请求通过ExternalInterface回调js时,如此循环ie下有6次的上限,其余浏览器未发现。
解决办法是在js调用as时,把调用这句话放在一个function中,然后setTimeout(function, 0)即可。

2.JAte实验bug之一:
用adobe推荐办法取得swf对象时,如果有js的方法对象存在与swf相同id或name的情况,window[swfname]在ie下会取得js的方法对象而不是swf对象。这是个很诡异的地方,因为alert测试都会输出object,不过前者是[object Object],后者是[object]。输出其tagName便可发现,前者undefinded,后者是OBJECT。

3.JAte实验bug之二:
依然是as先调用js,然后js回调as。假如页面中写好一个test()方法,然后加入swf,再as去调用它,它再回调as,一切正常。
如果是用js产生swf对象(即js先createElement一个div,div的innerHTML是加入swf对象的html代码),as先调用js一切正常,js再回调as的话……虽然能找到这个swf对象,但恶心的是,ie下会报错说没有这个方法,其它均正常。
12 dicembre

时隔很久了,JSSC4 beta版终于亮相……

下面两个页面都行,我自己的空间到期了,只好借用下别人的~

http://ff9.ffsky.cn/temp/jssc4/index.html

http://www.daygood.cn/attachment/jssc4/

自从jssc ver 3 rc版发布之后,我就因为刚毕业而一直处于半消失状态,因此很长一段时间也没有更新。期间收到很多朋友们的来信,社区消息也好、qq也好、msn也好、email也好,总之是不少。许多建议都是很有价值的,当然也是很有难度的,嘿~

好吧,闲话不说,jssc4的新版本终于即将来到,而它将带来什么变化呢?请往下看:

1.平台变迁。

其实在叫《jssc》这个名字有点儿不适合了,因为它已经“不纯”了。不过为了延续习惯,还是继续下去吧。之所以“不纯”的原因,原因是分析处理的大头已经放在了as上,js只是以调用和生成者的身份出现。

2.速度提升。

js来执行高亮分析的性能一直是让人头疼的问题,即使jssc2已经做得很好了,但依然不容乐观。显然,解释执行的js代码不仅慢,而且在各个浏览器上的表现都不一样。那么为何不另辟蹊径呢?

jssc4中主要的分析工作变成了由action script 3来执行,as3的速度和跨平台甚至对oop支持可好得太多了。于是这样做带来的速度提升,是显而易见的。

3.富文本复制。

fins希望在选择代码后复制到word等编辑器中能够连带颜色一块儿复制过去,这在之前的版本中是无法办到的。因为若要复制,高亮后的结果必须是<font color="(color)">code</font>或者<span style="color:#(color)">code</span>的形式。若想纯js办到,需要牺牲掉很多东西。然而在jssc4中,这些都可以了,因为这一切都是在flash编译期间完成的事。

4.扩展。

这可能是需要特殊提及一下的事情了。

jssc4由于主分析工作是由as3来完成的,因此若需要修改、扩展等,都必须修改as代码重新编译才行。想要定制自己的高亮器的话,都必须这样做。

5.大小。

可以看到swf文件目前只有8k多,编译成abc字节码后大小的确很令人惊喜啊。

---

当然有人会这样问:极少数人的浏览器没有flash播放器或者级别太低怎么办?

答案就是不支持的话代码不会被高亮,仍旧原样显示,这算是一种折中吧。代码放在pre或者textarea里,定义好css,也不会乱。

制作过程中仍旧有许多问题,解决后有很多心得,将陆续发布出来讨论。一些改进仍需,请回帖发言……

源文件:http://ff9.ffsky.cn/temp/jssc4/jssc4.rar
22 ottobre

AS3的socket接收数据

《Fantasy Sky》的进展顺利,一切继续着。今天下午把10种职业基本补齐了,除了小偷还缺4张截图,小事而已。于是想来张全家福,突然发现bug。

同时登陆多个账号时,头几个显示还正常,后面的就看不见前面同张地图内的人了。debug半天,发现数据完全正常,接收发送都处理到了,可真杀死脑细胞啊。

最终在客户端想了个办法把每次socket接收的数据都打印出来看看,终于发现问题所在:

swf在接收socket的数据时,是用触发侦听进行的。然而可能服务器端两次发送的数据由于延迟原因同时到达客户端了,此时由于程序编写的问题,就会出现冲突。

比如接收到 str1 这一段数据,客户端判别应该生成一个人物a;接收到 str2 就该生成b。由于延迟,二者合到一起了,客户端读取的是 str1 + str2 的内容,错误就出现在这里了。

解决得办法就是换用xmlsocket对象,它用\0字符为结尾标识每次传送数据的结束,不会起冲突;又或者仍然用低层的socket对象,它有readInt()方法,可以每次在数据开头设个长度,用来标识此次接收多少字节。

前者容易,后者更强大。综合来说,我先用前者试一试。