三月 5th, 2010

[译]理解删除

2 Comments, 前端开发, by army8735.

http://perfectionkills.com/understanding-delete/

原文作者是在读《Object-Oriented Javascript》的Function章节后有感,写下的一篇博文。

书中有一句话:function其实就是一个普通变量——它可以被复制到另一个变量中而不会被删除。如果这样解释的话,这里有个例子:

//army注:这一源代码片段是在Firebug下的Console控制台运行的,放在页面中需改写
var sum = function(a, b) {return a + b;}
var add = sum;
delete sum;
true
typeof sum;
"undefined"

忽略代码中省略的两个分号,你注意到这个代码片段的问题了吗?按那句话所说,删除sum不应该成功;delete语句返回的值不可能是true;typeof sum的结果不会是”undefined”。可是事实却非如此,所有的这一切都是因为:Javascript中不可能删除变量。至少不能以这种方式删除。

这是为什么呢?

要回答这个问题,我们需要理解delete操作符是怎么运作的:即什么可以删除、什么不可以删除、为什么?

原理

我们为什么可以这样删除对象的属性?

var o = { x: 1 };
delete o.x; // true
o.x; // undefined

但是变量就完全不同了:

var x = 1;
delete x; // false
x; // 1

方法声明也是:

function x(){}
delete x; // false
typeof x; // "function"

注意当一个属性不能被删除时,它会返回false。

要解释这些,我们首先要理解变量的实例化以及属性的特性——很遗憾,这些很少在Javascript书籍中被提到。下面这节将做个简明的介绍,它很容易理解。如果你不关心一切为何按照它目前的样子进行工作的话,可以跳过此节。

代码类型

在ECMAScript中共有3种执行方式:Global、Function、Eval。这些类型某种程度上正如其名,但还不足:

当一份源代码作为程序运行时,它在全局作用域内执行,就会被认为是Global代码。在浏览器环境中,script标签通常被解析为一段程序,因此它是全局代码。

直接运行在function内的代码显然是Function代码。例如浏览器中的<p onclick=”…”>。

另外,作为参数传入evel函数的源代码字符串会以Eval方式执行。我们很快就会看到其特殊性。

运行上下文

当ECMAScript代码运行时,它是发生在一个运行上下文(execution context)中的。运行上下文是个抽象实体,可以帮助我们理解作用域和变量实例化是如何工作的。这3种代码各自有自己的运行上下文。当一个function执行时,就可以说控制器进入Function运行上下文中;当全局代码执行时,就进入了Global运行上下文。

如你所知,运行上下文可以逻辑递归。首先一段代码肯定有自己的Global运行上下文;如果调用了function,那么function也有自己的运行上下文;如果function又调用了另外一个function……如此递归。甚至function调用了它本身,每次也会产生一个新的运行上下文。

激活变量对象/激活对象

每一个运行上下文都和一个变量对象(Variable Object)相关联。类似运行上下文,变量对象是一个抽象实体,一种描述变量实例化的机制。现在有趣的事情来了,源代码中的变量和函数声明,实际上成为这个变量对象上的一组属性。

当控制器进入Global方式的运行上下文时,一个全局对象被创建用来作为变量对象。这就解释了为何定义的全局方法成为了全局对象的属性。

/* remember that `this` refers to global object when in global scope */
var GLOBAL_OBJECT = this;

var foo = 1;
GLOBAL_OBJECT.foo; // 1
foo === GLOBAL_OBJECT.foo; // true

function bar(){}
typeof GLOBAL_OBJECT.bar; // "function"
GLOBAL_OBJECT.bar === bar; // true

ok,当全局变量成为全局对象的属性时,方法内的局部变量发生了什么事情呢?行为是类似的:它们变成了一个变量对象的一组属性。唯一的不同之处在于,此处的变量对象不是全局对象,但仍然是个激活对象。激活对象在每次进入方法创建运行上下文时都会被创建。

不仅function内的变量和方法声明会成为激活对象的属性,相同的事情还发生在传入的function参数身上,它在arguments下产生了一个Arguments对象。注意激活对象是内部机制,不能够被程序直接访问。

(function(foo){

	var bar = 2;
	function baz(){}

	/*
	In abstract terms,

	Special `arguments` object becomes a property of containing function's Activation object:
	ACTIVATION_OBJECT.arguments; // Arguments object

	...as well as argument `foo`:
	ACTIVATION_OBJECT.foo; // 1

	...as well as variable `bar`:
	ACTIVATION_OBJECT.bar; // 2

	...as well as function declared locally:
	typeof ACTIVATION_OBJECT.baz; // "function"
	*/

})(1);

最后,eval中的变量声明会作为它被引用的上下文的变量对象的属性。eval代码只是使用它被调用的运行环境的变量对象。

var GLOBAL_OBJECT = this;

/* `foo` is created as a property of calling context Variable object,
which in this case is a Global object */

eval('var foo = 1;');
GLOBAL_OBJECT.foo; // 1

(function(){

	/* `bar` is created as a property of calling context Variable object,
	which in this case is an Activation object of containing function */

	eval('var bar = 1;');

	/*
	In abstract terms,
	ACTIVATION_OBJECT.bar; // 1
	*/

})();

属性特性

终于说到这里了。现在我们已经清楚变量声明时发生的事情,剩下就是理解属性特性。每个属性可以有任意个以下特性——只读、不可枚举、不可删除、内部的。你可以把它们理解为属性上的选项——有或者没有。因为今天讨论的目的,这里只说不可删除特性。

当声明变量或者函数时,它们成为变量对象(对于function代码来说是激活对象,对于全局代码来说是全局对象)的属性,这些属性产生的同时具有不可删除特性。然而,任何直接或者内部的属性分配在创建属性时都没有不可删除特性。这就是为什么有的属性可以删除有的不可以。

var GLOBAL_OBJECT = this;

/*  `foo` is a property of a Global object.
It is created via variable declaration and so has DontDelete attribute.
This is why it can not be deleted. */

var foo = 1;
delete foo; // false
typeof foo; // "number"

/*  `bar` is a property of a Global object.
It is created via function declaration and so has DontDelete attribute.
This is why it can not be deleted either. */

function bar(){}
delete bar; // false
typeof bar; // "function"

/*  `baz` is also a property of a Global object.
However, it is created via property assignment and so has no DontDelete attribute.
This is why it can be deleted. */

GLOBAL_OBJECT.baz = 'blah';
delete GLOBAL_OBJECT.baz; // true
typeof GLOBAL_OBJECT.baz; // "undefined"

内建和不可删除

一个属性上的特殊特性控制了它能否被删除。注意一些内建的的属性一开始就有不可删除特性,它们是永远不能被删除的。例如arguments、函数实例的length属性等。

(function(){

	/* can't delete `arguments`, since it has DontDelete */

	delete arguments; // false
	typeof arguments; // "object"

	/* can't delete function's `length`; it also has DontDelete */

	function f(){}
	delete f.length; // false
	typeof f.length; // "number"

})();

函数的形参同样也有不可删除特性:

(function(foo, bar){

	delete foo; // false
	foo; // 1

	delete bar; // false
	bar; // 'blah'

})(1, 'blah');

未声明的分配

未声明的变量会在全局对象上生成属性,除非在作用域链中于全局对象之前找到这个变量声明。现在我们知道了属性分配和变量声明之间的区别——后者具有不可删除特性而前者没有——这就解释了为何未经声明的变量可以被删除。

var GLOBAL_OBJECT = this;

/* create global property via variable declaration; property has DontDelete */
var foo = 1;

/* create global property via undeclared assignment; property has no DontDelete */
bar = 2;

delete foo; // false
typeof foo; // "number"

delete bar; // true
typeof bar; // "undefined"

注意在属性创建时特性已经被定义了(除了ie),再次分配的话并不会修改已经存在的特性。

/* `foo` is created as a property with DontDelete */
function foo(){}

/* Later assignments do not modify attributes. DontDelete is still there! */
foo = 1;
delete foo; // false
typeof foo; // "number"

/* But assigning to a property that doesn't exist,
creates that property with empty attributes (and so without DontDelete) */

this.bar = 1;
delete bar; // true
typeof bar; // "undefined"

Firebug的困惑

那么在Firebug中发生了什么?为何在控制台console中的声明变量可以被删除?还记得前面说的吗,当eval代码进行变量声明时有个特殊的行为,这些声明的变量没有不可删除特性,所以它们可以被删除。

通过eval删除变量

由于eval行为的特殊性,再加上ECMAScript允许我们删除没有不可删除特性的属性,在同一运行上下文中函数声明可以覆盖已经声明的变量。

function x(){ }
var x;
typeof x; // "function"

注意函数声明是如何获得优先权并覆盖掉原有变量声明的,这是因为函数声明的实例化晚于变量声明的实例化,并且允许覆盖。不仅如此,它还会覆盖属性特性。假如我们通过eval方式来声明函数,也会这样。

var x = 1;

/* Can't delete, `x` has DontDelete */

delete x; // false
typeof x; // "number"

eval('function x(){}');

/* `x` property now references function, and should have no DontDelete */

typeof x; // "function"
delete x; // should be `true`
typeof x; // should be "undefined"

不幸的是,我在尝试这段代码的时候失败了,可能哪里出了点错误。

浏览器的让步

原理已然弄清,实践更为重要。浏览器们都按照标准做了吗?答案是:大部分是。

我对以下现代浏览器做了一个测试:Opera 7.54+、Firefox 1.0+、Safari 3.1.2+、Chrome 4+。
Safari 2.x和3.0.4在删除函数参数时有问题,这些属性看上去没有不可删除特性,因此它们竟被删除了。Safari 2.x的问题还不止这些——删除没有引用的变量(如delete 1,army注:我感觉这应该是常量才对)会抛出异常;函数声明具有可删除特性(变量声明没有);eval中的变量声明有不可删除特性(函数声明没有)。

类似的,Konqueror(3.5而非4.3)在删除没有引用的变量的时候会抛出异常,并且允许删除函数参数。

Gecko的不可删除特性bug

基于Gecko 1.8.x的浏览器——Firefox 2.x、Camino 1.x、Seamonkey 1.x等等——有个很有趣的bug:可以删除一个属性的不可删除特性,尽管这个属性是通过变量声明或者函数声明而产生的。

function foo(){}
delete foo; // false (as expected)
typeof foo; // "function" (as expected)

/* now assign to a property explicitly */

this.foo = 1; // erroneously clears DontDelete attribute
delete foo; // true
typeof foo; // "undefined"

/* note that this doesn't happen when assigning property implicitly */

function bar(){}
bar = 1;
delete bar; // false
typeof bar; // "number" (although assignment replaced property)

让人震惊的是,IE5.5到IE8居然全部通过了测试!除了那个删除没有引用的变量外。然而即使如此,IE却在另外的方面有着严重的bug!这些bug很隐晦,和全局变量有关。

IE的bug

在IE中(至少是6到8版本),下面的表达式会抛出异常。

this.x = 1;
delete x; // TypeError: Object doesn't support this action

再来看看另外一个,非常有趣:

var x = 1;
delete this.x; // TypeError: Cannot delete 'this.x'

这个情况看上去好像全局代码上的变量声明没有在全局对象上产生属性,甚至删除操作时会抛出异常。

this.x = 1;

delete this.x; // TypeError: Object doesn't support this action
typeof x; // "number" (still exists, wasn't deleted as it should have been!)

delete x; // TypeError: Object doesn't support this action
typeof x; // "number" (wasn't deleted again)

与之相反的是,未声明变量却具有可删除特性。

x = 1;
delete x; // true
typeof x; // "undefined"

但是你若通过this来引用全局的话却又会异常:

x = 1;
delete this.x; // TypeError: Cannot delete 'this.x'

如果概括一下的话,那就是在全局代码中delete this.x从未成功过。

我在2009年9月发现了的这一问题,Garrett Smith回答我说“IE中全局变量对象被实现为一个JScript对象,全局对象却被实现为一个宿主对象(host object)”。(army注:延伸阅读hax的文章:http://hax.javaeye.com/blog/349569

我们可以通过一些测试在某种程度上验证这一回答。注意this和window看上去引用了同一对象(如果===全等操作符可以信赖的话),但是变量对象(函数返回的值)却和this不同。

/* in Global code */
function getBase(){ return this; }

getBase() === this.getBase(); // false
this.getBase() === this.getBase(); // true
window.getBase() === this.getBase(); // true
window.getBase() === getBase(); // false

删除和宿主对象

关于delete操作的规则如下:

  • 如果操作数中没有引用值,返回true
  • 如果对象没有直接属性,返回true
  • 如果属性存在并且具有不可删除特性,返回false
  • 其它情况,移除属性并且返回true

然而,IE环境的delete操作在删除宿主对象时却经常让人捉摸不透。

/* "alert" is a direct property of `window` (if we were to believe `hasOwnProperty`) */
window.hasOwnProperty('alert'); // true

delete window.alert; // true
typeof window.alert; // "function"

最好的习惯是从不信任宿主对象。

ES5严格模式

ECMAScript 5的严格模式带来了哪些条规?它引进了诸多限制。比如删除引用变量、函数参数和标志符的时候会抛出SyntaxError。另外,如果属性包含[[Configurable]] == false键值对,删除时会抛出TypeError。

(function(foo){

"use strict"; // enable strict mode within this function

var bar;
function baz(){}

delete foo; // SyntaxError (when deleting argument)
delete bar; // SyntaxError (when deleting variable)
delete baz; // SyntaxError (when deleting variable created with function declaration)

/* `length` of function instances has { [[Configurable]] : false } */

delete (function(){}).length; // TypeError

})();

还有,删除未声明变量时也会抛出SyntaxError:

"use strict";
delete i_dont_exist; // SyntaxError

这和在严格模式中使用未声明的变量类似:

"use strict";
i_dont_exist = 1; // ReferenceError

在7yue的blog上看到fp10.1的表现让人兴奋不已,希望Adobe摆脱“很有压力,才有推力”的形象……

http://www.7yue.com/post/430.html

整体流程

终于说到整体流程上了。之前的文章一直在解说as中如何来做词法分析,js方面丝毫未提,更没说jssc到底在页面上是怎么工作的。现在,就来详细地解释一下。

环境需求:

  1. 现代浏览器(废话)
  2. 装有Adobe Flash Player插件,版本号为9或9以上
  3. javascript启用
  4. 页面中放入jssc.swf文件

大致流程:

  1. swf文件载入完毕
  2. 内嵌在swf中的js首先被执行,寻找页面中所有符合规则的pre节点
  3. 顺序遍历这些pre节点,提取每个pre节点的文本内容(即原始代码)
  4. 将原始代码传递给swf
  5. swf对传递来的原始代码进行词法分析,并生成一段结果html片段(即一串li节点)
  6. 将结果传递回js
  7. js对结果进行包装(一串li节点放入ol节点中,以及标题头、边距和复制等等)
  8. 将对应的原始pre节点隐藏(display:none)
  9. 将新生成的内容插入原始pre节点的前面

可以看出,js和as耦合的地方在哪里,以及它们之间是如何相互合作的。下面将对以上9个步骤细细说来,希望更多的开发者能提出改进意见~

1.载入swf

这里没什么好说的,在页面底部加入flash标签即可。值得注意的是不同浏览器中标签有所区别,理想情况下可以使用swfobject来插入标准的html标签。不过为了兼容以及简单,一般我是这样做的:

<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="jssc5" style="position:absolute;visibility:hidden;">
	<param name="movie" value="jssc5.swf"/>
	<param name="flashvars" value="find=brush"/>
	<embed src="jssc5.swf" flashvars="find=brush" name="jssc5" type="application/x-shockwave-flash" style="position:absolute;visibility:hidden;"/>
</object>

这里面有许多可配置的地方。

objectidembedname必须一致且唯一(应该是唯二才对),为了满足不同浏览器的需要。我设为jssc5,这也是默认值。如果和页面上某个节点有冲突需要修改,那么就需要传入参数修改配置。比如说变成jssc5_modify,就得在flashvars中这样做:

<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="jssc5_modify" style="position:absolute;visibility:hidden;">
	<param name="movie" value="jssc5.swf"/>
	<param name="flashvars" value="find=brush&swf=jssc5_modify"/>
	<embed src="jssc5.swf" flashvars="find=brush&swf=jssc5_modify" name="jssc5_modify" type="application/x-shockwave-flash" style="position:absolute;visibility:hidden;"/>
</object>

注意到param标签和embed标签中都得改,为了满足浏览器兼容需要……

同样,jssc会占用一个全局js变量名jssc(注意这个jssc是指变量名字符串,而前者是指软件),这是为了在js和as之间互相调用通信必须的。如果这个变量名和页面上的js变量有冲突,那么就会造成错误。所以它也可以进行修改配置,假如要改成jssc_js,就得在flashvars中这么做:

<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="jssc5_modify" style="position:absolute;visibility:hidden;">
	<param name="movie" value="jssc5.swf"/>
	<param name="flashvars" value="find=brush&swf=jssc5_modify&js=jssc_js"/>
	<embed src="jssc5.swf" flashvars="find=brush&swf=jssc5_modify&js=jssc_js" name="jssc5_modify" type="application/x-shockwave-flash" style="position:absolute;visibility:hidden;"/>
</object>

最后,还有两个可以配置的参数:cssurl。前者是指新生成的结果节点的class名称,你可以用firebug看到默认的亦是jssc;后者是指定swf的路径,这是为了在非ie浏览器下做copy使用(ie可以直接调用api做复制功能),从这个意义上说,整个swf文件其实是有两种功能——词法分析和复制按钮。这么做是为了减少http请求,同时js融合在as里也是为了减少http请求。

2.内嵌的js执行

js首先会寻找页面上所有符合规则的pre节点,那么是什么规则的?默认情况下,pre的class名称如果这样开头,js就会认为它是正确的(其实并不一定要求是开头,出现在其它位置也可以,但为了良好的习惯推荐如此):

<pre class="brush:html"></pre>

这段被高亮的代码其实就是它本身。brush键名html键值。假如想要高亮一段js代码,那么就需要更改键值:

<pre class="brush:js"></pre>

其它语言道理相同。另外,可以高亮的代码必须是jssc本身已经支持的语法种类,不支持的话也会被格式化,但没有高亮效果。

细心的人可能已经发现,brush键名和最开始那段代码中出现的相一致。没错,假如pre节点的class名称也有冲突,不能使用brush,要改成brush_modify,那么就要如此修改配置:

<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="jssc5_modify" style="position:absolute;visibility:hidden;">
	<param name="movie" value="jssc5.swf"/>
	<param name="flashvars" value="find=brush_modify&swf=jssc5_modify&js=jssc_js"/>
	<embed src="jssc5.swf" flashvars="find=brush_modify&swf=jssc5_modify&js=jssc_js" name="jssc5_modify" type="application/x-shockwave-flash" style="position:absolute;visibility:hidden;"/>
</object>

注意param和embed中要修改2次。

在找到所有符合规则的pre节点之后,js将这些节点的引用存储下来,然后一一遍历它们。下面的经过就是每次遍历一个pre节点时所发生的。

3.遍历提取内容

提取pre节点的内容很简单,即我们熟知的innerText。不过在不同浏览器中也有让人头疼的地方。以前在做jssc4的时候,firefox2版本的innerText容量有限制,如果代码过多,提取的内容就不完整。另外也有低版本浏览器不支持innerText的可能性发生。所以综合考虑我写了个如下的js方法:

function getText(node) {
	var code = node.textContent || node.innerText;
	if(!code && node.firstChild) {
		code = node.firstChild.nodeValue;
	}
	return code || "";
}

4.传递内容给as

这是as定义好的接口,js和as可以通过ExternalInterface类来实现互调。不再多说。

5.as做词法分析

这是前几篇一直在说的内容,不再多说。

6.传递结果给js

同第4点。

7.js包装结果

生成的结果其实是一串li节点,js要做的就是创建一个ol节点,然后设置innerHTML为传递来的结果字符串。如此最基本的高亮代码就完成了。之后是设置标题、起始行数、边距、复制功能和鼠标移入高亮当前行背景。

其中边距是指ol节点的paddingLeft值,因为代码行数不一样,所以左边距一定是根据行数动态进行计算的。在js中我的公式如下:

oOl.style.paddingLeft = Math.max((line.length + 2) * 9, 30) + "px";

8.隐藏pre节点

最后是将本次循环的这个pre节点隐藏。注意,这里是隐藏不是删除!因为后面还要用到代码复制功能,所以只能隐藏不能删除!

9.插入结果

将包装好的结果插入本次循环pre节点的前面,这轮循环就算走完了,再继续下一轮循环。

结束语

好了,整个系列文章算结束了。欢迎大家提出好的建议和想法,或者直接参与到开发当中来,亦或以别的方式来参与(比如说做wordpress的插件)。如果你能做出比jssc更好的高亮插件,那么我也会放弃jssc,投入新的、更好的项目中去的——这话说的好假:)

接上篇,继续做的测试,有了点新发现。解说测试之前,先说明下用flash检测大写锁定键是否开启的原理。

前提要求是:普通的input文本输入框(无论是text还是password),用户输入还是以html的input组件为基础,并非在其上加入透明的flash输入框或者其它技术平台的组件;无干扰性,用户输入没有任何其它干扰的感觉。

以此,唯一的解决办法就是js结合swf来控制。input的各种事件侦听(获得焦点、失去焦点、按键触发)都只能由js来完成,而对当前键盘状态的检测则交给flash来处理。它们之间有个很大的障碍——flash对当前键盘状态的识别必须首先获得焦点,而我们在input中输入内容的时候焦点却在input上,这成为最大的矛盾

利用js控制页面焦点可以解决这一问题,除了webkit内核的浏览器。我们可以写出如下的伪代码:

oInput.onfocus = function() {
	oSwf.focus(); //flash获得焦点
	oSwf.detect(); //flash进行检测,并根据检测结果调用某个js函数
	setTimeout(function() {
		oInput.focus(); //input重新获得焦点,无干扰用户输入
	}, 0);
}

你可能困扰为何要用setTimeout来使input重新获得焦点,这是为了让用户输入时感觉不到焦点在input和flash之间走了一圈,就好象什么事都没有发生一样。至于为什么一定要用setTimeout,那是为了模拟线程同步,确保重新获得焦点这一操作发生在最后。

就几行代码看上去很简单,实际上有几个隐蔽的信息藏在其中。在flash获得焦点并进行检测之后,input重新获得焦点,试问:这样会不会再次触发onfocus?答案是肯定的,如果不做任何处理的话,这将是个死循环;另外,input还应该侦听onblur,没道理用户在离开输入框之后还在进行提示(除非你能忍受),那么onblur这个侦听函数也会进行不断重复;更恐怖的在下面,我为每一步标上了序号,以表示它们的执行步骤:

function result() {
	//1
	//调用的结果函数,用以让提示区域显示或隐藏
}
oInput.onfocus = function() {
	//2
	oSwf.focus(); //flash获得焦点
	oSwf.detect(); //flash进行检测,并根据检测结果调用结果函数
	//3
	setTimeout(function() {
		//4
		oInput.focus(); //input重新获得焦点,无干扰用户输入
	}, 0);
	//5
}
oInput.onblur = function() {
	//6
	//失去焦点后该怎么做,略
}

猜一猜这个步骤顺序是多少?你永远也答不对,因为即使在trident(ie为代表)和gecko(firefox为代表)两种内核下,它们也不相同(别提webkit了,搞定那个bug先)。

在trident中,input被用户激活后(比如说用鼠标点击),先触发focus,因此2会先执行;接下来flash获得焦点,再调用结果方法,1会被执行;继续回到onfocus方法,顺序走完3和5;onblur被触发,执行一下6;最后是setTimeout那里的4。

很奇怪的现象,因为在2时,input失去焦点而swf获得焦点,此时并没有触发onblur去执行6,反而是先执行的1;之后还有没执行完的3和5;在接下来才能轮到6。由此我们可以观察出一些trident内核的优先级顺序设计的端倪。

再来看看gecko,input被激活后,依然会先触发focus,2第一个执行;接下来也是flash获得焦点,不过在这之前input要先失去焦点,因此先触发了onblur,执行了6;然后flash进行判断,并调用结果方法,执行了1;再往后就类似了,3和5搞定后setTimeout的4最后。

探索完原理后,才有可能继续开发。我也只“刨根问底”到这个程度上,欢迎有兴趣的同学继续“往祖坟上刨”,彻底翻出来赤裸裸的现实。后续如果有新的进展,我会继续把它写出来。:)

http://army8735.org/2009/09/15/82.html

曾经考虑过用swf来检测密码输入框大写锁定键是否被打开,并且也进行了一个小实验,基本是成功的。时隔多日,最近想要把它完善一下,突然发现在chrome中根本不起作用!

一步步排查下来,终于发现在webkit内核的浏览器下,以往的使得swf获取焦点的方法根本不起作用。随之google一番,发现老外早已发现这个bug,并且目前webkit仍未解决:

http://stackoverflow.com/questions/594821/object-focus-problem-with-safari-and-chrome-browsers

这样就影响到了部分功能:比如想使用flash的检测按键功能,但它必须要求flash是在获得焦点的情况下才行。使用js可以在ie和gecko内核下轻松办到,webkit只能继续等待了。