Javascript 为什么此指令中的所有键绑定都会被最后一个键绑定覆盖?

Javascript 为什么此指令中的所有键绑定都会被最后一个键绑定覆盖?,javascript,angularjs,Javascript,Angularjs,我试图创建一个指令,将特定的按键绑定到控制器作用域中指定的函数,但所有回调函数似乎都被包含绑定的对象中的最后一个回调函数覆盖。我已经尝试使用keymaster.js和mousetrap.js来绑定具有相同结果的事件 Javascript代码: angular.module('app', ['directives', 'controllers']); angular.module('directives', []) .directive('keypress', [function () {

我试图创建一个指令,将特定的按键绑定到控制器作用域中指定的函数,但所有回调函数似乎都被包含绑定的对象中的最后一个回调函数覆盖。我已经尝试使用keymaster.js和mousetrap.js来绑定具有相同结果的事件

Javascript代码:

angular.module('app', ['directives', 'controllers']);

angular.module('directives', [])
.directive('keypress', [function () {
    return function (scope, element, attrs) {
        // console.log(scope, element, attrs);
        var attribute = scope.$eval(attrs.keypress || '{}');
        for (var k in attribute) {
            console.log('binding ' + k + ' as ' + attribute[k]);
            Mousetrap.bind(k, function() { return attribute[k](scope, element); });
        }
    };
}]);

angular.module('controllers', [])
.controller('TodoController', function($scope) {
    $scope.shortcuts = {
        'w': function () { console.log('w'); },
        's': function () { console.log('s'); },
        'a': function () { console.log('a'); },
        'd': function () { console.log('d'); }
    };
});
HTML文件:

<html>
  <head>
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.js"></script>
    <script src="https://raw.github.com/ccampbell/mousetrap/master/mousetrap.min.js"></script>
    <script src="/javascript/app.js"></script>
  </head>
  <body>
    <div ng-app="app">
      <div ng-controller='TodoController' keypress='shortcuts'>Foo</div>
    </div>
  </body>
</html>

无论我是按“w”、“a”、“s”还是“d”,为什么总是将“d”写入控制台?

您陷入了一个常见的陷阱:JavaScript中的变量总是函数作用域。执行此操作时:

for (var k in attribute) {
    console.log('binding ' + k + ' as ' + attribute[k]);
    Mousetrap.bind(k, function() { return attribute[k](scope, element); });
}
使用该
bind()
,您将创建四个闭包,它们都关闭在变量
k
上,但它们都关闭在同一个变量上。循环的每次运行都不会得到一个新的循环。
console.log
工作正常,因为可以立即使用
k
的值。闭包在实际运行之前不会计算
k
,到那时,它的值已更改为循环完成时的值

根据您的目标受众,目前解决此问题的最简单方法是使用
let
而不是
var
let
确实可以阻止作用域(其工作原理与您的预期相符),但这是一项相当新的发明,我不确定它的支持程度

否则,要获得新的作用域,您需要一个新函数:

for (var k in attribute) {
    (function(k) {
        console.log('binding ' + k + ' as ' + attribute[k]);
        Mousetrap.bind(k, function() { return attribute[k](scope, element); });
    })(k);
}

这会将外部
k
传递到函数的内部
k
,每次都是不同的变量。您也可以将其拆分为一个小工厂函数,但对于这么小的函数,我不想麻烦。

您陷入了一个常见的陷阱:JavaScript中的变量始终是函数范围的。执行此操作时:

for (var k in attribute) {
    console.log('binding ' + k + ' as ' + attribute[k]);
    Mousetrap.bind(k, function() { return attribute[k](scope, element); });
}
使用该
bind()
,您将创建四个闭包,它们都关闭在变量
k
上,但它们都关闭在同一个变量上。循环的每次运行都不会得到一个新的循环。
console.log
工作正常,因为可以立即使用
k
的值。闭包在实际运行之前不会计算
k
,到那时,它的值已更改为循环完成时的值

根据您的目标受众,目前解决此问题的最简单方法是使用
let
而不是
var
let
确实可以阻止作用域(其工作原理与您的预期相符),但这是一项相当新的发明,我不确定它的支持程度

否则,要获得新的作用域,您需要一个新函数:

for (var k in attribute) {
    (function(k) {
        console.log('binding ' + k + ' as ' + attribute[k]);
        Mousetrap.bind(k, function() { return attribute[k](scope, element); });
    })(k);
}

这会将外部
k
传递到函数的内部
k
,每次都是不同的变量。您也可以将其拆分为一个小的工厂函数,但对于这么小的函数,我就不麻烦了。

太好了,非常感谢!因此,通过定义并随后调用该匿名函数,我们实际上保留了k的值,而不是引用?@iand675:这实际上不是关于值与引用的关系,而是关于如何创建不同的变量。循环的每次迭代都使用相同的变量。匿名函数的每次调用都会创建一个新变量,但它们碰巧都有相同的名称。太好了,非常感谢!因此,通过定义并随后调用该匿名函数,我们实际上保留了k的值,而不是引用?@iand675:这实际上不是关于值与引用的关系,而是关于如何创建不同的变量。循环的每次迭代都使用相同的变量。匿名函数的每次调用都会创建一个新变量,但它们碰巧都具有相同的名称。