最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 科技 - 知识百科 - 正文

JavaScript探秘:SpiderMonkey的怪癖

来源:懂视网 责编:小采 时间:2020-11-27 20:30:33
文档

JavaScript探秘:SpiderMonkey的怪癖

JavaScript探秘:SpiderMonkey的怪癖:大家都知道,命名函数表达式的标识符只在函数的局部作用域中有效。但包含这个标识符的局部作用域又是什么样子的吗?其实非常简单。在命名函数表达式被求值时,会创建一个特殊的对象,该对象的唯一目的就是保存一个属性,而这个属性的名字对应着函数标识符,属
推荐度:
导读JavaScript探秘:SpiderMonkey的怪癖:大家都知道,命名函数表达式的标识符只在函数的局部作用域中有效。但包含这个标识符的局部作用域又是什么样子的吗?其实非常简单。在命名函数表达式被求值时,会创建一个特殊的对象,该对象的唯一目的就是保存一个属性,而这个属性的名字对应着函数标识符,属

大家都知道,命名函数表达式的标识符只在函数的局部作用域中有效。但包含这个标识符的局部作用域又是什么样子的吗?其实非常简单。在命名函数表达式被求值时,会创建一个特殊的对象,该对象的唯一目的就是保存一个属性,而这个属性的名字对应着函数标识符,属性的值对应着那个函数。这个对象会被注入到当前作用域链的前端。然后,被“扩展”的作用域链又被用于初始化函数。

在这里,有一点十分有意思,那就是ECMA-262定义这个(保存函数标识符的)“特殊”对象的方式。标准说“像调用new Object()表达式那样”创建这个对象。如果从字面上来理解这句话,那么这个对象就应该是全局Object的一个实例。然而,只有一个实现是按照标准字面上的要求这么做的,这个实现就是SpiderMonkey。因此,在SpiderMonkey中,扩展Object.prototype有可能会干扰函数的局部作用域:

Object.prototype.x = 'outer';
 
(function(){
 
 var x = 'inner';
 
 /*
 函数foo的作用域链中有一个特殊的对象——用于保存函数的标识符。这个特殊的对象实际上就是{ foo: <function object> }。
 当通过作用域链解析x时,首先解析的是foo的局部环境。如果没有找到x,则继续搜索作用域链中的下一个对象。下一个对象
 就是保存函数标识符的那个对象——{ foo: <function object> },由于该对象继承自Object.prototype,所以在此可以找到x。
 而这个x的值也就是Object.prototype.x的值(outer)。结果,外部函数的作用域(包含x = 'inner'的作用域)就不会被解析了。
 */
 
 (function foo(){
 
 alert(x); // 提示框中显示:outer
 
 })();
})();

不过,更高版本的SpiderMonkey改变了上述行为,原因可能是认为那是一个安全漏洞。也就是说,“特殊”对象不再继承Object.prototype了。不过,如果你使用Firefox 3或者更低版本,还可以“重温”这种行为。

另一个把内部对象实现为全局Object对象的是黑莓(Blackberry)浏览器。目前,它的活动对象(Activation Object)仍然继承Object.prototype。可是,ECMA-262并没有说活动对象也要“像调用new Object()表达式那样”来创建(或者说像创建保存NFE标识符的对象一样创建)。 人家规范只说了活动对象是规范中的一种机制。

好,那我们下面就来看看黑莓浏览器的行为吧:

Object.prototype.x = 'outer';
 
(function(){
 
 var x = 'inner';
 
 (function(){
 
 /*
 在沿着作用域链解析x的过程中,首先会搜索局部函数的活动对象。当然,在该对象中找不到x。
 可是,由于活动对象继承自Object.prototype,因此搜索x的下一个目标就是Object.prototype;而
 Object.prototype中又确实有x的定义。结果,x的值就被解析为——outer。跟前面的例子差不多,
 包含x = 'inner'的外部函数的作用域(活动对象)就不会被解析了。
 */
 
 alert(x); // 显示:outer
 
 })();
})();

不过神奇的还是,函数中的变量甚至会与已有的Object.prototype的成员发生冲突,来看看下面的代码:

(function(){
 
 var constructor = function(){ return 1; };
 
 (function(){
 
 constructor(); // 求值结果是{}(即相当于调用了Object.prototype.constructor())而不是1
 
 constructor === Object.prototype.constructor; // true
 toString === Object.prototype.toString; // true
 
 // ……
 
 })();
})();

要避免这个问题,要避免使用Object.prototype里的属性名称,如toString, valueOf, hasOwnProperty等等。

JScript解决方案

var fn = (function(){
 
 // 声明要引用函数的变量
 var f;
 
 // 有条件地创建命名函数
 // 并将其引用赋值给f
 if (true) {
 f = function F(){ }
 }
 else if (false) {
 f = function F(){ }
 }
 else {
 f = function F(){ }
 }
 
 // 声明一个与函数名(标识符)对应的变量,并赋值为null
 // 这实际上是给相应标识符引用的函数对象作了一个标记,
 // 以便垃圾回收器知道可以回收它了
 var F = null;
 
 // 返回根据条件定义的函数
 return f;
})();

最后我们给出一个应用上述技术的应用实例,这是一个跨浏览器的addEvent函数代码:

// 1) 使用独立的作用域包含声明
var addEvent = (function(){
 
 var docEl = document.documentElement;
 
 // 2) 声明要引用函数的变量
 var fn;
 
 if (docEl.addEventListener) {
 
 // 3) 有意给函数一个描述性的标识符
 fn = function addEvent(element, eventName, callback) {
 element.addEventListener(eventName, callback, false);
 }
 }
 else if (docEl.attachEvent) {
 fn = function addEvent(element, eventName, callback) {
 element.attachEvent('on' + eventName, callback);
 }
 }
 else {
 fn = function addEvent(element, eventName, callback) {
 element['on' + eventName] = callback;
 }
 }
 
 // 4) 清除由JScript创建的addEvent函数
 // 一定要保证在赋值前使用var关键字
 // 除非函数顶部已经声明了addEvent
 var addEvent = null;
 
 // 5) 最后返回由fn引用的函数
 return fn;
})();

文档

JavaScript探秘:SpiderMonkey的怪癖

JavaScript探秘:SpiderMonkey的怪癖:大家都知道,命名函数表达式的标识符只在函数的局部作用域中有效。但包含这个标识符的局部作用域又是什么样子的吗?其实非常简单。在命名函数表达式被求值时,会创建一个特殊的对象,该对象的唯一目的就是保存一个属性,而这个属性的名字对应着函数标识符,属
推荐度:
标签: js javascript 探秘
  • 热门焦点

最新推荐

猜你喜欢

热门推荐

专题
Top