JavaScript函数别名似乎不起作用

Kev*_*Kev 81 javascript alias closures function

我只是在阅读这个问题并且想尝试别名方法而不是函数包装方法,但我似乎无法在它们的调试窗口和Firefox 3或3.5beta4或Google Chrome中使用它们.在测试网页中.

萤火虫:

>>> window.myAlias = document.getElementById
function()
>>> myAlias('item1')
>>> window.myAlias('item1')
>>> document.getElementById('item1')
<div id="item1">
Run Code Online (Sandbox Code Playgroud)

如果我把它放在一个网页中,对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]
Run Code Online (Sandbox Code Playgroud)

Chrome(为了清晰起见插入了>>>):

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

在测试页面中,我得到了相同的"非法调用".

难道我做错了什么?其他人可以重现这个吗?

另外,奇怪的是,我只是尝试了它在IE8中工作.

Sol*_*ogi 184

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

在我介绍为什么你不能使用别名之前document.getElementById,我将尝试解释JavaScript函数/对象的工作原理.

每当您调用JavaScript函数时,JavaScript解释器都会确定范围并将其传递给函数.

考虑以下功能:

function sum(a, b)
{
    return a + b;
}

sum(10, 20); // returns 30;
Run Code Online (Sandbox Code Playgroud)

此函数在Window作用域中声明,当您调用它时this,sum函数内部的值将是全局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.
Run Code Online (Sandbox Code Playgroud)

当你调用dave.getAge函数时,JavaScript解释器会看到你在dave对象上调用getAge函数,所以它设置thisdave并调用该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.
Run Code Online (Sandbox Code Playgroud)

在上面的行中,您不是让JavaScript决定范围,而是手动将范围作为bob对象传递.即使你'认为'你打电话给对象,getAge现在也会回来.200getAgedave


上述所有内容的重点是什么?函数"松散地"附加到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
Run Code Online (Sandbox Code Playgroud)

让我们迈出下一步.

var dave = new Person(new Date(1909, 1, 1));
var ageMethod = dave.getAge;

dave.getAge(); //returns 100;
ageMethod(); //returns ?????
Run Code Online (Sandbox Code Playgroud)

ageMethod执行会抛出错误!发生了什么?

如果你仔细阅读我的上述观点,你会注意到该dave.getAge方法是dave作为this对象调用的,而JavaScript无法确定ageMethod执行的"范围" .所以它将全球'Window'作为'this'传递.现在由于window没有birthDate属性,ageMethod执行将失败.

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

ageMethod.apply(dave); //returns 100.
Run Code Online (Sandbox Code Playgroud)

以上所有都有意义吗?如果是,那么您将能够解释为什么您无法使用别名document.getElementById:

var $ = document.getElementById;

$('someElement'); 
Run Code Online (Sandbox Code Playgroud)

$如果实现期望,则调用windowas ,它将失败.thisgetElementByIdthisdocument

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

$.apply(document, ['someElement']);
Run Code Online (Sandbox Code Playgroud)

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

我不知道getElementByIdIE中的内部实现,但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();
    };

}
Run Code Online (Sandbox Code Playgroud)

对于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
Run Code Online (Sandbox Code Playgroud)
console.log(yogi.getAge()); //Output: 100.
Run Code Online (Sandbox Code Playgroud)

直接前进,getAge方法获取yogi对象this和输出100.


var ageAlias = yogi.getAge;
console.log(ageAlias()); //Output: -1
Run Code Online (Sandbox Code Playgroud)

JavaScript interepreter将window对象设置为this,我们的getAge方法将返回-1.


console.log(ageAlias.apply(yogi)); //Output: 100
Run Code Online (Sandbox Code Playgroud)

如果我们设置了正确的范围,您可以使用ageAlias方法.


console.log(ageAlias.apply(anotherYogi)); //Output: 200
Run Code Online (Sandbox Code Playgroud)

如果我们传入一些其他人物,它仍然会正确计算年龄.

var ageSmarterAlias = yogi.getAgeSmarter;    
console.log(ageSmarterAlias()); //Output: 100
Run Code Online (Sandbox Code Playgroud)

ageSmarter函数捕获了原始this对象,因此您现在不必担心提供正确的范围.


console.log(ageSmarterAlias.apply(anotherYogi)); //Output: 100 !!!
Run Code Online (Sandbox Code Playgroud)

问题ageSmarter是你永远不能将范围设置为其他对象.


var ageSmartestAlias = yogi.getAgeSmartest;
console.log(ageSmartestAlias()); //Output: 100
console.log(ageSmartestAlias.apply(document)); //Output: 100
Run Code Online (Sandbox Code Playgroud)

ageSmartest如果提供的作用域无效,该函数将使用原始作用域.


console.log(ageSmartestAlias.apply(anotherYogi)); //Output: 200
Run Code Online (Sandbox Code Playgroud)

您仍然可以将另一个Person对象传递给getAgeSmartest.:)

  • 很好的答案.我不知道它有多偏离主题. (44认同)
  • 我在stackoverflow上看到的最好的答案之一. (8认同)
  • 为IE工作的原因+1.其余的有点偏离主题,但总的来说仍然有用.:) (7认同)

Mac*_*ski 39

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

>>> $ = 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();">
Run Code Online (Sandbox Code Playgroud)

当您执行简单别名时,将在全局对象上调用该函数,而不是在文档对象上调用该函数.使用一种名为闭包的技术来解决这个问题:

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();">
Run Code Online (Sandbox Code Playgroud)

这样您就不会丢失对原始对象的引用.

在2012年,bindES5 的新方法允许我们以更高级的方式执行此操作:

>>> $ = document.getElementById.bind(document)
>>> $('bn_home')
<body id="bn_home" onload="init();">
Run Code Online (Sandbox Code Playgroud)

  • 这实际上是一个包装器,因为它返回一个新函数.我认为Kev问题的答案是,由于上述原因,这是不可能的.您在此描述的方法可能是最佳选择. (4认同)