JavaScript函数别名似乎不起作用

时间:2009-06-17 14:20:09

标签: javascript function closures alias

我刚刚阅读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(为了清晰起见,插入&gt;&gt;&gt;):

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

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

我做错了吗?其他人可以重现这个吗?

另外,奇怪的是,我刚试过,它在IE8中运行。

6 个答案:

答案 0 :(得分:184)

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

在我介绍为什么你不能使用别名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并调用{{1功能。 getAge将正确返回getAge()


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

apply

在上面的行中,您不是让JavaScript决定范围,而是手动将范围作为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. 对象传递。 bob现在会返回getAge,即使您认为'您在200对象上调用了getAge


上述所有内容的重点是什么?函数“松散地”附加到JavaScript对象。例如。你可以做到

dave

让我们迈出下一步。

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对象调用,而JavaScript无法确定this的“范围”执行。所以它将全球'Window'作为'this'传递。现在ageMethod没有window属性,birthDate执行将失败。

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

ageMethod

以上所有都有意义吗?如果是,那么您将能够解释为什么您无法别名ageMethod.apply(dave); //returns 100.

document.getElementById

var $ = document.getElementById; $('someElement'); 使用$作为window进行调用,如果this实施预期getElementByIdthis,则会失败。< / p>

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

document

那为什么它在Internet Explorer中有效?

我不知道IE中$.apply(document, ['someElement']); 的内部实现,但jQuery源代码(getElementById方法实现)中的注释表示在IE中inArray。如果是这种情况,那么别名window == document应该在IE中有效。

为了进一步说明这一点,我创建了一个精心设计的例子。请查看下面的document.getElementById功能。

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

直接前进,getAge方法将console.log(yogi.getAge()); //Output: 100. 对象设为yogi并输出this


100

JavaScript interepreter将var ageAlias = yogi.getAge; console.log(ageAlias()); //Output: -1 对象设置为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。 :)

答案 1 :(得分:37)

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

>>> $ = 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();">

答案 2 :(得分:3)

这是一个简短的答案。

以下是该函数的(引用)的副本。问题是,当window对象被设计为存在document对象时,该函数就在window.myAlias = document.getElementById 对象上。

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

替代方案

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

    {{1}}

答案 3 :(得分:2)

另一个简短的答案,仅用于包装/别名console.log和类似的日志记录方法。他们都期望处于console背景中。

如果您或您的用户在using a browser that doesn't (always) support it遇到麻烦,可以使用console.log包装一些后备广告。这不是一个完整的解决方案,因为它需要扩大支票和后备 - 你的里程可能会有所不同。

使用警告的示例

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

答案 4 :(得分:1)

除了其他很棒的答案之外,还有简单的jQuery方法$.proxy

您可以这样使用别名:

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

或者

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

答案 5 :(得分:-6)

你实际上不能“纯别名”是预定义对象上的一个函数。因此,在没有包装的情况下,最接近别名的是保持在同一个对象中:

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