JavaScript 函数别名似乎不起作用
Posted
技术标签:
【中文标题】JavaScript 函数别名似乎不起作用【英文标题】:JavaScript function aliasing doesn't seem to work 【发布时间】:2010-11-03 16:30:42 【问题描述】:我只是在阅读 this question 并想尝试使用别名方法而不是函数包装器方法,但我似乎无法让它在 Firefox 3 或 3.5beta4 或 Google Chrome 中工作,两者都在他们的调试窗口和测试网页中。
萤火虫:
>>> 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 中运行。
【问题讨论】:
在 IE8 中——它可能是某种魔术函数或其他东西。 我已将 cmets 添加到 ***.com/questions/954417 的错误答案中,并将它们指向此处。 对于支持 Function.prototype.bind 方法的浏览器:window.myAlias = document.getElementById.bind(document);搜索 MDN 文档,会有一个 bind shim。 【参考方案1】:您必须将该方法绑定到文档对象。看:
>>> $ = 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();">
当你做一个简单的别名时,这个函数是在全局对象上调用的,而不是在文档对象上调用的。使用一种称为闭包的技术来解决这个问题:
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();">
【讨论】:
这实际上是一个包装器,因为它返回一个新函数。我认为 Kev 问题的答案是,由于您上面描述的原因,这是不可能的。您在这里描述的方法可能是最好的选择。 感谢您的评论,我还没有仔细看清楚这一点。那么,另一个问题的答案中的语法完全是伪造的吗?有趣...【参考方案2】:除了其他很好的答案,还有简单的 jQuery 方法$.proxy。
你可以这样别名:
myAlias = $.proxy(document, 'getElementById');
或者
myAlias = $.proxy(document.getElementById, document);
【讨论】:
+1 因为这是一个可行的解决方案。但是,严格来说,幕后$.proxy
也是一个包装器。【参考方案3】:
我深入研究以了解这种特殊行为,并且我认为我找到了一个很好的解释。
在我解释为什么你不能使用别名 document.getElementById
之前,我将尝试解释 javascript 函数/对象是如何工作的。
每当您调用 JavaScript 函数时,JavaScript 解释器都会确定一个范围并将其传递给该函数。
考虑以下函数:
function sum(a, b)
return a + b;
sum(10, 20); // returns 30;
此函数在 Window 范围内声明,当您调用它时, sum 函数内的 this
的值将是全局 Window
对象。
对于'sum'函数,'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'作为'this'。现在window
没有birthDate
属性,ageMethod
执行将失败。
如何解决这个问题?很简单,
ageMethod.apply(dave); //returns 100.
以上所有内容都有意义吗?如果是这样,那么您将能够解释为什么您无法为 document.getElementById
起别名:
var $ = document.getElementById;
$('someElement');
$
以window
为this
调用,如果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
对象为this
并输出100
。
var ageAlias = yogi.getAge;
console.log(ageAlias()); //Output: -1
JavaScript 解释器将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
。 :)
【讨论】:
+1 了解 IE 的工作原理。其余的有点离题,但总的来说还是有帮助的。 :) 优秀的答案。不过,我看不出它是如何偏离主题的。 确实很好的答案。 here 写了一个稍微短一些的版本。 我在 *** 上看到的最佳答案之一。 为什么它可以在 IE 中运行?因为:***.com/questions/6994139/… — 所以这个方法实现实际上可能是 return window[id] ,所以它可以在任何上下文中工作【参考方案4】:另一个简短的答案,仅用于包装/别名 console.log
和类似的日志记录方法。他们都希望在console
上下文中。
这在包装 console.log
时可以使用一些后备,以防您或您的用户在 using a browser that doesn't (always) support it 时遇到麻烦。不过,这不是该问题的完整解决方案,因为它需要扩展检查和后备 - 您的里程可能会有所不同。
使用警告的示例
var warn = function() console.warn.apply(console, arguments);
然后照常使用
warn("I need to debug a number and an object", 9999, "user" : "Joel" );
如果您希望将日志记录参数包装在一个数组中(我经常这样做),请将 .apply(...)
替换为 .call(...)
。
应该使用console.log()
、console.debug()
、console.info()
、console.warn()
、console.error()
。另见console
on MDN。
consolefirebug
【讨论】:
【参考方案5】:这是一个简短的答案。
以下复制(引用)该函数。问题是现在函数在 window
对象上,而它被设计为存在于 document
对象上。
window.myAlias = document.getElementById
替代品是
使用包装器(Fabien Ménager 已经提到)或者你可以使用两个别名。
window.d = document // A renamed reference to the object
window.d.myAlias = window.d.getElementById
【讨论】:
【参考方案6】:您实际上不能在预定义对象上“纯别名”函数。因此,在不进行包装的情况下最接近别名的方法是保持在同一个对象内:
>>> document.s = document.getElementById;
>>> document.s('myid');
<div id="myid">
【讨论】:
这有点不正确。只要函数实现使用“this”,您就可以“纯别名”函数。所以这取决于。 对不起,我的意思是在 getElementById 的上下文中。你说得对。我已经稍微改变了答案以反映这一点......以上是关于JavaScript 函数别名似乎不起作用的主要内容,如果未能解决你的问题,请参考以下文章
在特定时间播放音频文件的 JavaScript 程序似乎不起作用
为啥函数 lsqcurvefit 在 Matlab 2015b 中似乎不起作用?