JavaScript函数别名似乎不工作

我只是读这个问题 ,想尝试别名的方法,而不是函数包装方法,但我似乎无法得到它的工作在Firefox 3或3.5beta4,或谷歌浏览器,在他们的调试窗口和在测试网页。

萤火虫:

>>> window.myAlias = document.getElementById function() >>> myAlias('item1') >>> window.myAlias('item1') >>> document.getElementById('item1') <div id="item1"> 

如果我把它放在一个网页上,对myAlias的调用给我这个错误:

 uncaught exception: [Exception... "Illegal operation on WrappedNative prototype object" nsresult: "0x8057000c (NS_ERROR_XPC_BAD_OP_ON_WN_PROTO)" location: "JS frame :: file:///[...snip...]/test.html :: <TOP_LEVEL> :: line 7" data: no] 

Chrome(为清楚起见,插入了>>>):

 >>> window.myAlias = document.getElementById function getElementById() { [native code] } >>> window.myAlias('item1') TypeError: Illegal invocation >>> document.getElementById('item1') <div id=?"item1">? 

在测试页面,我得到了相同的“非法调用”。

我做错了什么? 任何人都可以重现吗?

另外,奇怪的是,我只是试了一下,它在IE8中工作。

您必须将该方法绑定到文档对象。 看:

 >>> $ = document.getElementById getElementById() >>> $('bn_home') [Exception... "Cannot modify properties of a WrappedNative" ... anonymous :: line 72 data: no] >>> $.call(document, 'bn_home') <body id="bn_home" onload="init();"> 

当你在做一个简单的别名时,函数在全局对象上调用,而不是在文档对象上调用。 使用一种叫做closures的技术来解决这个问题:

 function makeAlias(object, name) { var fn = object ? object[name] : null; if (typeof fn == 'undefined') return function () {} return function () { return fn.apply(object, arguments) } } $ = makeAlias(document, 'getElementById'); >>> $('bn_home') <body id="bn_home" onload="init();"> 

这样你就不会松散对原始对象的引用。

在2012年,ES5有了新的bind方法,可以让我们以更奇特的方式来做到这一点:

 >>> $ = document.getElementById.bind(document) >>> $('bn_home') <body id="bn_home" onload="init();"> 

我深深地理解了这种特殊的行为,我想我已经找到了一个很好的解释。

在介绍为什么你不能别名document.getElementById ,我将尝试解释JavaScript函数/对象是如何工作的。

无论何时调用JavaScript函数,JavaScript解释器都会确定一个范围并将其传递给该函数。

考虑以下功能:

 function sum(a, b) { return a + b; } sum(10, 20); // returns 30; 

这个函数在Window范围内声明,当你调用它时,sum函数内部的值将是全局的Window对象。

对于“总和”功能来说,“this”的值并不重要,因为它没有使用它。


考虑以下功能:

 function Person(birthDate) { this.birthDate = birthDate; this.getAge = function() { return new Date().getFullYear() - this.birthDate.getFullYear(); }; } var dave = new Person(new Date(1909, 1, 1)); dave.getAge(); //returns 100. 

当您调用dave.getAge函数时,JavaScript解释器会发现您正在调用dave对象上的getAge函数,因此它this设置为dave并调用getAge函数。 getAge()将正确返回100


您可能知道在JavaScript中,您可以使用apply方法指定范围。 我们来试试。

 var dave = new Person(new Date(1909, 1, 1)); //Age 100 in 2009 var bob = new Person(new Date(1809, 1, 1)); //Age 200 in 2009 dave.getAge.apply(bob); //returns 200. 

在上面的行中,不是让JavaScript决定作用域,而是作为bob对象手动传递作用域。 getAge现在将返回200即使您认为您在dave对象上调用了getAge


以上是什么意思? 函数“松散地”附加到JavaScript对象。 比如你可以做

 var dave = new Person(new Date(1909, 1, 1)); var bob = new Person(new Date(1809, 1, 1)); bob.getAge = function() { return -1; }; bob.getAge(); //returns -1 dave.getAge(); //returns 100 

让我们走下一步。

 var dave = new Person(new Date(1909, 1, 1)); var ageMethod = dave.getAge; dave.getAge(); //returns 100; ageMethod(); //returns ????? 

ageMethod执行会抛出一个错误! 发生了什么?

如果你仔细阅读了我的上面几点,你会注意到dave.getAge方法被dave调用为this对象,而JavaScript不能确定ageMethod执行的'范围'。 所以它通过全球“窗口”作为“这个”。 现在,由于window没有birthDate属性, ageMethod执行将失败。

如何解决这个问题? 简单,

 ageMethod.apply(dave); //returns 100. 

以上所有都是有意义的吗? 如果是这样,那么你将能够解释为什么你不能别名document.getElementById

 var $ = document.getElementById; $('someElement'); 

$是用this window调用的,如果getElementById实现期望thisdocument ,它将会失败。

再次解决这个问题,你可以做

 $.apply(document, ['someElement']); 

那么为什么它在Internet Explorer中工作?

我不知道IE中的getElementById的内部实现,但在jQuery源代码( inArray方法实现)中的一个评论说,在IE中, window == document 。 如果是这样的话,那么别名document.getElementById应该在IE中工作。

为了进一步说明这一点,我创建了一个详细的例子。 看看下面的Person函数。

 function Person(birthDate) { var self = this; this.birthDate = birthDate; this.getAge = function() { //Let's make sure that getAge method was invoked //with an object which was constructed from our Person function. if(this.constructor == Person) return new Date().getFullYear() - this.birthDate.getFullYear(); else return -1; }; //Smarter version of getAge function, it will always refer to the object //it was created with. this.getAgeSmarter = function() { return self.getAge(); }; //Smartest version of getAge function. //It will try to use the most appropriate scope. this.getAgeSmartest = function() { var scope = this.constructor == Person ? this : self; return scope.getAge(); }; } 

对于上面的Person函数,下面是各种getAge方法的行为。

我们使用Person函数创建两个对象。

 var yogi = new Person(new Date(1909, 1,1)); //Age is 100 var anotherYogi = new Person(new Date(1809, 1, 1)); //Age is 200 

 console.log(yogi.getAge()); //Output: 100. 

直接,getAge方法获取yogi对象,并输出100


 var ageAlias = yogi.getAge; console.log(ageAlias()); //Output: -1 

JavaScript inte inteter将window对象设置this ,我们的getAge方法将返回-1


 console.log(ageAlias.apply(yogi)); //Output: 100 

如果我们设置了正确的范围,可以使用ageAlias方法。


 console.log(ageAlias.apply(anotherYogi)); //Output: 200 

如果我们通过其他人的对象,它仍然会正确计算年龄。

 var ageSmarterAlias = yogi.getAgeSmarter; console.log(ageSmarterAlias()); //Output: 100 

ageSmarter函数捕获了原来的this对象,所以现在你不必担心提供正确的范围。


 console.log(ageSmarterAlias.apply(anotherYogi)); //Output: 100 !!! 

ageSmarter的问题是,你永远不能将范围设置为其他对象。


 var ageSmartestAlias = yogi.getAgeSmartest; console.log(ageSmartestAlias()); //Output: 100 console.log(ageSmartestAlias.apply(document)); //Output: 100 

如果提供了一个无效范围, ageSmartest函数将使用原始范围。


 console.log(ageSmartestAlias.apply(anotherYogi)); //Output: 200 

你仍然可以通过另一个Person对象getAgeSmartest 。 🙂

这是一个简短的答案。

以下是该函数的一个副本(引用)。 问题是,现在函数在window对象上时,它被设计为生活在document对象上。

 window.myAlias = document.getElementById 

替代品是

  • 使用包装(已经由FabienMénager提到)
  • 或者你可以使用两个别名。

     window.d = document // A renamed reference to the object window.d.myAlias = window.d.getElementById 

另一个简短的答案,只是包装/别名console.log和类似的日志记录方法。 他们都期望在console上下文中。

当包装console.log和一些fallback时,这是可用的,以防您或您的用户在使用不支持(总是)支持的浏览器时遇到麻烦。 但这不是一个完整的解决方案,因为它需要扩大支票和后备 – 你的里程可能会有所不同。

使用警告的例子

 var warn = function(){ console.warn.apply(console, arguments); } 

然后像往常一样使用它

 warn("I need to debug a number and an object", 9999, { "user" : "Joel" }); 

如果你喜欢看到你的日志参数包含在一个数组中(大部分时间我都这么做),用.apply(...)替换.apply(...) .call(...)

应该使用console.log()console.debug()console.info()console.warn()console.error() 。 另请参阅MDN上的console

控制台 萤火虫

除了其他很好的答案,还有简单的jQuery方法$ .proxy 。

你可以像这样别名:

 myAlias = $.proxy(document, 'getElementById'); 

要么

 myAlias = $.proxy(document.getElementById, document); 

您实际上不能在预定义的对象上“纯化别名”函数。 因此,最接近你可以得到没有包装的别名是通过呆在同一个对象:

 >>> document.s = document.getElementById; >>> document.s('myid'); <div id="myid">