2

我正在查看一个操作 HTML A 标记的 javascript 代码,但我无法理解它如何设置“onclick”属性。似乎是在告诉它用索引变量 j 更新 ytplayer_playitem 然后调用 ytplayer_playlazy(1000)

但是所有括号是怎么回事?javascript 语法中的哪些细节允许这样设置?

var a = document.createElement("a");
a.href = "#ytplayer";
a.onclick = (function (j) {
    return function () {
        ytplayer_playitem = j;
        ytplayer_playlazy(1000);
    };
})(i);
4

5 回答 5

5

好吧,基本上, 的值onclick是一个在单击元素时将被调用的函数。当用户单击元素时,您希望发生的任何事情都会进入函数的主体。

您可以创建一个命名函数,然后将其分配给元素的 onclick 属性:

function youClickedMe() {
  ...
}
a.onclick = youClickedMe

但这会使命名空间混乱,其函数名称从未在其他任何地方引用。在你需要的地方创建一个匿名函数会更干净。通常,这看起来像这样:

a.onclick = function() { ... }

但是,如果我们用您的具体示例进行尝试:

a.onclick = function() { 
  ytplayer_playitem = something; // ??
  ytplayer_playlazy(1000); 
}

我们看到它对something播放的内容进行了硬编码。我假设原始代码是从一个循环中获取的,该循环会生成几个可点击的链接来播放;使用上面的代码,所有这些链接都会播放相同的内容,这可能不是您想要的。

到目前为止,一切都很简单,但下一个飞跃是棘手的地方。解决方案似乎很明显:如果你在一个循环中,为什​​么不在函数体内使用循环变量呢?

// THIS DOESN'T WORK
a.onclick = function() { 
  ytplayer_playitem = i; 
  ytplayer_playlazy(1000); 
}

看起来它应该可以工作,但不幸的i是,函数内部是指调用i函数时变量的值,而不是创建函数时的值。当用户单击链接时,创建所有链接的循环将完成并具有其最终值 - 可能是列表中的最后一项或大于该项目的索引,具体取决于循环的编写方式. 无论它的价值是多少,您都会再次遇到所有链接都播放相同项目的情况。i

通过使用返回值是另一个函数的函数,您的代码中的解决方案得到了一些元数据。如果将循环控制变量作为参数传递给生成函数,则它创建的新函数可以引用该参数并始终获取最初传入的值,无论外部参数变量的值发生了什么,因为:

function generate_onclick(j) {
    // no matter when the returned function is called, its "j" will be 
    // the value passed into this call of "generate_onclick"
    return function() { ytplayer_playitem = j; ytplayer_playlazy(1000); }
}

要使用它,请在循环中调用它,如下所示:

a.onclick = generate_onclick(i);

每个生成的函数都有自己的j变量,它永远保持它的值,而不是什么时候改变i。因此,每个链接都发挥了正确的作用;任务完成!

这正是您发布的原始代码正在做的事情,有一个小区别:就像我解释的第一步一样,作者选择使用匿名函数而不是定义一个命名函数。这里的另一个区别是他们也在定义匿名函数后立即调用它。这段代码:

a.onclick = (function (j) { ... })(i)

是这段代码的匿名版本:

function gen(j) { ... }
a.onclick = gen(i)

由于 JavaScript 的分号插入规则,匿名版本周围需要额外的括号;function (y) {...}(blah)编译为独立函数定义,后跟括号中的独立表达式,而不是函数调用。

于 2012-05-23T18:30:01.177 回答
3

“但是所有的括号是怎么回事?”

大多数括号只是在做你所期望的。

有一个额外的集合在技术上不是必需的,但通常用作函数正在被调用的提示。

                       // v-v---these are part of the function definition like normal
    a.onclick = (function (j) {
           //   ^-----------this and...v
        return function () {
            ytplayer_playitem = j;
            ytplayer_playlazy(1000);
        };
 //  v---...this are technically not needed here, but are used as a hint
    })(i);
 //   ^-^---these invoked the function like normal

“javascript语法中的哪些细节允许这样设置?”

结果是该函数被立即调用,并被传递i,以便它的值被j立即调用的函数中的参数引用。

这将创建一个变量范围,返回的函数将继续访问该范围。这样它总是可以访问j变量,而不是i在循环中被覆盖的变量。


这些内联函数被滥用了一点IMO。如果您简单地将其设为命名函数,它会变得更加清晰。

for(var i = 0; i < 10; i++) {
    // create the new element
    a.onclick = createHandler(i);
    // append it somewhere
}

function createHandler (j) {
    return function () {
        ytplayer_playitem = j;
        ytplayer_playlazy(1000);
    };
}

生成的处理程序完全相同,但代码不那么神秘。

于 2012-05-23T18:24:14.763 回答
1
a.onclick = (function (j) {
    return function () {
        ytplayer_playitem = j;
        ytplayer_playlazy(1000);
    };
})(i);

这会创建一个“闭包”,以确保i绑定到处理程序的值是i“当时”的值,而不是i一般情况下的值。

在您的代码中, 中的函数()是一个表达式,执行并传递了变量i。这是(i)你在最后看到的部分。在这个执行的函数表达式中,i成为局部变量j。这个执行的函数表达式返回要绑定的处理函数,该onclick事件携带的值ji“当时”


如果我没有使用闭包:

//suppose i is 1
var i = 1;

a.onclick = function () {
    ytplayer_playitem = i;
    ytplayer_playlazy(1000);
};

//and changed i
i = 2;

//if you clicked the <a>, it would not be 1 onclick but 2 because you 
//did not assign the value of i "at that time". i is "tangible" this way
于 2012-05-23T18:23:44.723 回答
1

对,我猜周围的代码是这样的:

for (var i = 0; i < playitems.length; i++) {
    // above code here
}

现在,您可以在这里做显而易见的事情,并onclick像这样分配属性:

a.onclick = function() {
    ytplayer_playitem = i;
    ytplayer_playlazy(1000);
};

然而,这不会很好地工作,因为值的i变化。无论单击哪个链接,最后一个链接都会被激活,因为此时的值i将是列表中的最后一个链接。

所以你需要防止这种情况发生。您需要通过创建一个新作用域来做到这一点,这是通过创建一个额外的函数来完成的,该函数会立即被调用:

(function (j) {
    // some code here
})(i);

因为i已经被传递到函数中,所以传递的是值而不是被保留的变量的引用。这意味着您现在可以定义一个引用正确值的函数。所以你得到了额外的函数来返回点击处理函数:

a.onclick = (function (j) { // j is the right number and always will be
   return function () { // this function is the click handler
       ytplayer_playitem = j;
       ytplayer_playlazy(1000);
   };
})(i);

所以每个a元素都有自己的点击处理函数,每个元素都有自己的独立j变量,即正确的数字。因此,单击链接时,它们将执行您希望它们执行的功能。

于 2012-05-23T18:27:49.430 回答
0
a.onclick = (function (j) {
  return function () {
      ytplayer_playitem = j;
      ytplayer_playlazy(1000);
  };
})(i);

您在这里拥有的是一个自调用匿名函数。让我们分解一下,首先用更简单的 ( return j + 1;) 替换函数的主体:

function( j ) { return j + 1; }

这是一个普通的匿名函数或闭包。这行代码是一个表达式,所以它有一个值,这个值就是一个函数。现在我们可以这样做:

var foo = function( j ) { return j + 1; }

foo( 5 );  // => 6

你知道这一点,我敢肯定——我们将匿名函数分配给变量foo,然后通过名称和参数调用函数i。但是,不是创建一个新变量,因为闭包是一个表达式,我们可以这样称呼它:

( function( j ) { return j + 1; } )( 5 );  // => 6

结果相同。现在,它只是返回j + 1,但在您的代码中它返回其他内容:另一个匿名函数:

return function() { /* ... */ }

当我们有一个返回函数的自调用匿名函数时会发生什么?结果是返回的“内部”函数:

a.onclick = ( function( j ) {
    return function() {
             ytplayer_playitem = j;
             ytplayer_playlazy( 1000 );
           }
  }
)( i );

If iis equal to 9thena.onclick现在将拥有一个与此等效的函数:

function() {
  ytplayer_playitem = 9;
  ytplayer_playlazy( 1000 );
}

正如其他人所指出的那样,这样做的用处在于,当( function( j ) { /* ... */ } )( i )被调用时,您正在捕获当时的并将放入而不是创建对值的引用,这可能(并且可能会)在以后发生变化。i ji

于 2012-05-23T18:49:48.933 回答