我认真研究了这种特定的行为,并认为我找到了一个很好的解释。
在深入讨论为什么你不能别名document.getElementById
之前,我将尝试解释一下JavaScript函数/对象的工作原理。
每当调用JavaScript函数时,JavaScript解释器确定一个作用域并将其传递给该函数。
考虑以下函数:
function sum(a, b)
{
return a + b;
}
sum(10, 20);
该函数在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();
当你调用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.
在上述代码行中,你手动传递了作用域
bob
对象,而不是让 JavaScript 自己决定。现在,即使你“认为”是在
dave
对象上调用了
getAge
,它也将返回
200
。
以上内容的重点是什么?函数与 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();
dave.getAge();
让我们迈出下一步。
var dave = new Person(new Date(1909, 1, 1));
var ageMethod = dave.getAge;
dave.getAge();
ageMethod();
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()
{
if(this.constructor == Person)
return new Date().getFullYear() - this.birthDate.getFullYear();
else
return -1;
};
this.getAgeSmarter = function()
{
return self.getAge();
};
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())
直截了当,getAge方法将
yogi
对象作为
this
参数,并输出
100
。
var ageAlias = yogi.getAge
console.log(ageAlias())
JavaScript解释器将window
对象设置为this
,因此我们的getAge
方法将返回-1
。
console.log(ageAlias.apply(yogi))
如果我们设置了正确的作用域,您可以使用
ageAlias
方法。
console.log(ageAlias.apply(anotherYogi))
如果我们传入其他人的对象,它仍然能正确计算年龄。
var ageSmarterAlias = yogi.getAgeSmarter
console.log(ageSmarterAlias())
ageSmarter
函数捕获了原始的this
对象,因此现在您无需担心提供正确的作用域。
console.log(ageSmarterAlias.apply(anotherYogi))
ageSmarter
的问题在于您无法将其作用域设置为其他对象。
var ageSmartestAlias = yogi.getAgeSmartest
console.log(ageSmartestAlias())
console.log(ageSmartestAlias.apply(document))
ageSmartest
函数如果提供了无效的作用域,将使用原始的作用域。
console.log(ageSmartestAlias.apply(anotherYogi))
你仍然能够向
getAgeSmartest
方法传递另一个
Person
对象。 :)