Heim > Artikel > Web-Frontend > JavaScript-Erkundung: SpiderMonkeys Macken
Wie wir alle wissen, ist der Bezeichner eines benannten Funktionsausdrucks nur im lokalen Bereich der Funktion gültig. Aber wie sieht der lokale Bereich aus, der diesen Bezeichner enthält? Es ist eigentlich ganz einfach. Wenn ein benannter Funktionsausdruck ausgewertet wird, wird ein spezielles Objekt erstellt, dessen einziger Zweck darin besteht, eine Eigenschaft zu speichern, deren Name dem Funktionsbezeichner und deren Wert dieser Funktion entspricht. Dieses Objekt wird am Anfang der aktuellen Bereichskette eingefügt. Die „erweiterte“ Scope-Kette wird dann in der Initialisierungsfunktion verwendet.
Eine Sache, die hier sehr interessant ist, ist die Art und Weise, wie ECMA-262 dieses „spezielle“ Objekt definiert (das Funktionsbezeichner enthält). Der Standard besagt, dass dieses Objekt „so erstellt werden soll, als ob Sie den neuen Object()-Ausdruck aufrufen würden“. Wenn Sie diesen Satz wörtlich verstehen, dann sollte dieses Objekt eine Instanz des globalen Objekts sein. Allerdings gibt es nur eine Implementierung, die dies im wahrsten Sinne des Wortes tut, wie es der Standard verlangt, und diese Implementierung ist SpiderMonkey. Daher kann in SpiderMonkey die Erweiterung von Object.prototype den lokalen Bereich einer Funktion beeinträchtigen:
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 })(); })();
Spätere Versionen von SpiderMonkey änderten jedoch das oben genannte Verhalten, möglicherweise weil es als Sicherheitslücke angesehen wurde . . Mit anderen Worten, „spezielle“ Objekte erben Object.prototype nicht mehr. Wenn Sie jedoch Firefox 3 oder niedriger verwenden, kann dieses Verhalten erneut auftreten.
Ein weiterer Browser, der interne Objekte als globale Objektobjekte implementiert, ist der Blackberry-Browser. Derzeit erbt sein Aktivitätsobjekt (Aktivierungsobjekt) noch Object.prototype. ECMA-262 sagt jedoch nicht, dass aktive Objekte „wie das Aufrufen des neuen Object()-Ausdrucks“ (oder wie das Erstellen von Objekten mit NFE-Kennungen) erstellt werden sollten. Die Standards anderer Leute besagen lediglich, dass das Aktivitätsobjekt ein Mechanismus in den Standards ist.
Okay, werfen wir einen Blick auf das Verhalten des BlackBerry-Browsers:
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 })(); })();
Aber das Zauberhafte ist, dass die Variablen in der Funktion sogar mit dem vorhandenen Object.prototype übereinstimmen, wenn ein Mitglied Wenn ein Konflikt auftritt, sehen Sie sich den folgenden Code an:
(function(){ var constructor = function(){ return 1; }; (function(){ constructor(); // 求值结果是{}(即相当于调用了Object.prototype.constructor())而不是1 constructor === Object.prototype.constructor; // true toString === Object.prototype.toString; // true // …… })(); })();
Um dieses Problem zu vermeiden, vermeiden Sie die Verwendung von Eigenschaftsnamen in Object.prototype, wie z. B. toString, valueOf, hasOwnProperty usw.
JScript-Lösung
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; })();
Abschließend geben wir ein Anwendungsbeispiel für die Anwendung der oben genannten Technologie, bei dem es sich um einen browserübergreifenden addEvent-Funktionscode handelt:
// 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; })();