我深入研究以了解这种特殊行为,并且我认为我找到了一个很好的解释。
在我解释为什么你不能使用别名 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。 :)