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
实现期望this
是document
,它将会失败。
再次解决这个问题,你可以做
$.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">