QML:Lambda 函数意外运行
QML: Lambda function works unexpectedly
我认为 QML 支持 lambda 函数,因为 JavaScript 支持匿名函数,而且函数首先是 class 对象,但它们并不像我预期的那样工作。拿这个代码:
Item {
property var items: []
function handler( item ) {
console.log( item );
}
Component.onCompleted: {
for ( var i = 0; i < 3; ++i ) {
var item = someObj.createObject();
item.someValueChanged.connect( function() {
handler( item ); } );
items.push( item );
console.log( "Adding:", item );
}
}
Component {
id: someObj
Item {
property bool someValue: false
Timer {
running: true
onTriggered: {
parent.someValue = true;
}
}
}
}
}
我正在尝试使用 lambda function() { handler( item ); }
以便在发出 someObj::someValueChanged
信号时将发射项传递给 handler( item )
函数。
我假设每个循环都会创建一个新的 lambda 实例,并且 item
引用将携带创建的 someObj
实例的引用在该循环中(即 item
将被 lambda 捕获)。但事实并非如此,因为输出是:
qml: Adding: QQuickItem_QML_1(0x2442aa0)
qml: Adding: QQuickItem_QML_1(0x2443c00)
qml: Adding: QQuickItem_QML_1(0x2445370)
qml: QQuickItem_QML_1(0x2445370)
qml: QQuickItem_QML_1(0x2445370)
qml: QQuickItem_QML_1(0x2445370)
如您所见,要么在每个循环中替换整个函数,要么只替换 item
引用,因此最终只引用最后创建的 someObj
。有人可以向我解释为什么 lambda(如果它是这样的话)不能按我期望的方式工作吗?这是 QML 问题,还是一般的 JavaScript 问题?
尝试这样的事情:
item.someValueChanged.connect(function(capture) {
return function() {
handler(capture)}
}(item))
很直观,对吧? :D
如果 JS 使用 "block scope",每个循环迭代将引用 3 个不同的 item
,它会 "work as expected"。但是 "function scope" 只有一个 item
被引用,它引用了它的最终值,因此需要使用那个 hack 来及时 "capture" 每个值。
只是为了解释一下,如果不是很明显,信号连接到一个处理程序,该处理程序由一个函数仲裁,该函数在特定时间将参数值捕获为离散对象,用于喂给处理程序。
希望最初的 Qt 5.12 版本能够通过引入对 let
、a.k.a 块范围变量的支持来解决这个问题。
更新:我可以确认使用 5.12,它现在可以正常工作了:
let item = someObj.createObject(); // will produce 3 distinct obj refs
解决了问题(谢谢!),但我们可以对其进行简化,以便更清楚到底发生了什么。 'inner' 函数需要匿名,但 'outer' 函数不需要匿名。对外部函数使用普通函数会使代码更容易理解,并且更容易自我记录,因为该函数有一个名称。外部函数创建一个信号处理程序,因此等效代码可以是:
var signal_handler = create_signal_handler(item);
item.someValueChanged.connect(signal_handler);
...
function create_signal_handler(item)
{
return function() { return handler(item); }
}
我认为 QML 支持 lambda 函数,因为 JavaScript 支持匿名函数,而且函数首先是 class 对象,但它们并不像我预期的那样工作。拿这个代码:
Item {
property var items: []
function handler( item ) {
console.log( item );
}
Component.onCompleted: {
for ( var i = 0; i < 3; ++i ) {
var item = someObj.createObject();
item.someValueChanged.connect( function() {
handler( item ); } );
items.push( item );
console.log( "Adding:", item );
}
}
Component {
id: someObj
Item {
property bool someValue: false
Timer {
running: true
onTriggered: {
parent.someValue = true;
}
}
}
}
}
我正在尝试使用 lambda function() { handler( item ); }
以便在发出 someObj::someValueChanged
信号时将发射项传递给 handler( item )
函数。
我假设每个循环都会创建一个新的 lambda 实例,并且 item
引用将携带创建的 someObj
实例的引用在该循环中(即 item
将被 lambda 捕获)。但事实并非如此,因为输出是:
qml: Adding: QQuickItem_QML_1(0x2442aa0)
qml: Adding: QQuickItem_QML_1(0x2443c00)
qml: Adding: QQuickItem_QML_1(0x2445370)
qml: QQuickItem_QML_1(0x2445370)
qml: QQuickItem_QML_1(0x2445370)
qml: QQuickItem_QML_1(0x2445370)
如您所见,要么在每个循环中替换整个函数,要么只替换 item
引用,因此最终只引用最后创建的 someObj
。有人可以向我解释为什么 lambda(如果它是这样的话)不能按我期望的方式工作吗?这是 QML 问题,还是一般的 JavaScript 问题?
尝试这样的事情:
item.someValueChanged.connect(function(capture) {
return function() {
handler(capture)}
}(item))
很直观,对吧? :D
如果 JS 使用 "block scope",每个循环迭代将引用 3 个不同的 item
,它会 "work as expected"。但是 "function scope" 只有一个 item
被引用,它引用了它的最终值,因此需要使用那个 hack 来及时 "capture" 每个值。
只是为了解释一下,如果不是很明显,信号连接到一个处理程序,该处理程序由一个函数仲裁,该函数在特定时间将参数值捕获为离散对象,用于喂给处理程序。
希望最初的 Qt 5.12 版本能够通过引入对 let
、a.k.a 块范围变量的支持来解决这个问题。
更新:我可以确认使用 5.12,它现在可以正常工作了:
let item = someObj.createObject(); // will produce 3 distinct obj refs
var signal_handler = create_signal_handler(item);
item.someValueChanged.connect(signal_handler);
...
function create_signal_handler(item)
{
return function() { return handler(item); }
}