QML代码用于描述界面,JavaScript用于处理逻辑。这篇文章用四个 demo 讲清 QML 里调用 JS 的四种常见方式。
- 内联 JS 函数 --- 直接在 QML 中定义函数,属性绑定和信号槽都能调用
- 导入外部 JS 文件 --- 把通用逻辑抽到
.js文件,多个 QML 文件共享 - JS 函数作为信号槽 --- 用
Connections把按钮信号接到 JS 函数 - WorkerScript 工作线程 --- 把耗时计算放到独立线程,避免阻塞主 UI
Demo 1 内联 JS 函数
在 QML 根元素上直接定义函数,既能在属性绑定里用,也能在信号处理器里调用。

演示代码
qml
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Layouts
FadeInAnimation {
function calculateArea(w, h) {
return w * h
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// ... 省略标题组件 TitleSeparator ...
Text {
text: "计算面积(属性绑定): " + calculateArea(this.width, this.height)
font.pointSize: 12
}
Button {
text: "点击计算(在槽函数中调用)"
font.pointSize: 12
onClicked: {
var result = calculateArea(100, 100)
textResult.text = "Area: " + result
}
}
Text {
id: textResult
font.pointSize: 10
}
Item { Layout.fillHeight: true }
}
}
关键逻辑解析
内联函数写在 FadeInAnimation 根元素上,作用域覆盖整个 QML 文件。
- 属性绑定中调用:
text: "..." + calculateArea(this.width, this.height),界面尺寸变化时结果自动刷新。 - 信号槽中调用:
onClicked里直接calculateArea(100, 100),和写普通 JS 没区别。
适合把一段小逻辑封装起来复用,比如格式化显示、简单计算。逻辑复杂或需要跨文件共享时,就适合拆成外部 JS 文件。
Demo 2 导入外部 JS 文件
把日期格式化、字符串处理这类通用函数抽到 Utility.js,通过 import "Utility.js" as Util 引入。

演示代码
qml
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Layouts
import "Utility.js" as Util
FadeInAnimation {
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 20
// ... 省略标题组件 TitleSeparator ...
Text {
text: "调用JS接口(formatDate): \n" + Util.formatDate(new Date())
font.pointSize: 12
}
Button {
text: "调用JS接口(toUpperCase)"
font.pointSize: 12
onClicked: {
textResult.text = Util.toUpperCase(textResult.text)
}
}
Text {
id: textResult
text: " abcdefg "
font.pointSize: 10
}
Item { Layout.fillHeight: true }
}
}
Utility.js:
js
.pragma library
function formatDate(date) {
return date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate()
}
function toUpperCase(name) {
return name.trim().toUpperCase()
}
关键逻辑解析
.pragma library必须加,它让所有 QML 实例共享同一个 JS 上下文。如果不加,每个引用文件都会有独立的一份状态。import "Utility.js" as Util给模块起别名,调用时用Util.xxx()。- 外部 JS 文件适合放工具函数、常量、数据转换逻辑,业务层 QML 文件只负责调用。
Demo 3 JS 函数作为信号槽
QML 里普通 JS 函数可以直接当槽用,配合 Connections 把信号和函数连起来。

演示代码
qml
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Layouts
FadeInAnimation {
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// ... 省略标题组件 TitleSeparator ...
Text {
text: "定义一个 JS 函数作为槽"
font.pointSize: 12
}
Button {
id: myButton
text: "触发信号"
font.pointSize: 12
}
Text {
property int counter: 0
id: myText
text: "Counter: " + counter
font.pointSize: 12
function handleButtonClick() {
myText.counter++
}
Connections {
target: myButton
function onClicked() { myText.handleButtonClick() }
}
}
Item { Layout.fillHeight: true }
}
}
关键逻辑解析
handleButtonClick()是定义在Text上的普通 JS 函数。Connections的target指向myButton,内部的onClicked会在按钮点击时触发。- 这种方式把信号源和响应逻辑解耦:按钮不知道 Text 的存在,Text 只负责更新自己。
和直接在 Button 的 onClicked 里改 myText.counter 相比,Connections 更适合跨组件、一对多的信号处理。
Demo 4 WorkerScript 工作线程
计算斐波那契数列这类递归任务会阻塞主 UI,用 WorkerScript 把它扔到独立线程。

演示代码
qml
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Layouts
import QtQml.WorkerScript
FadeInAnimation {
property bool isProcessing: false
property string resultText: ""
WorkerScript {
id: myWorker
source: "script.mjs"
onMessage: function (msgObj) {
isProcessing = false
if (msgObj.error) {
resultText = "错误: " + msgObj.error
} else {
resultText = "计算结果: " + msgObj.result +
"\n耗时: " + msgObj.time + " ms" +
"\n任务ID: " + msgObj.taskId +
"\n原始消息: " + JSON.stringify(msgObj.originalMessage)
}
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// ... 省略标题组件 TitleSeparator ...
Row {
spacing: 15
Button {
id: btnCalc
text: "计算斐波那契数列"
font.pointSize: 12
enabled: !isProcessing && myWorker.ready
onClicked: {
isProcessing = true
resultText = "计算斐波那契数列 ..."
myWorker.sendMessage({
'taskId': 1001,
'n': textField.text,
'timestamp': Date.now()
})
}
}
TextField {
id: textField
text: "36"
width: 60
height: btnCalc.height
placeholderText: "请输入数值"
font.pointSize: 10
}
}
Text {
text: resultText
font.pointSize: 10
}
Item { Layout.fillHeight: true }
}
}
script.mjs:
js
function fibonacci(n) {
function fib(n) {
if (n <= 1) return n
return fib(n - 1) + fib(n - 2)
}
return fib(n)
}
WorkerScript.onMessage = function(message) {
var startTime = Date.now()
var result = fibonacci(message.n || 30)
var processingTime = Date.now() - startTime
WorkerScript.sendMessage({
result: result,
time: processingTime,
taskId: message.taskId,
originalMessage: message
})
}
关键逻辑解析
WorkerScript的source指向.mjs脚本,该脚本在独立线程中运行。- 主线程通过
sendMessage()发数据,工作线程通过WorkerScript.onMessage接收并处理。 - 工作线程处理完后用
WorkerScript.sendMessage()把结果发回主线程,主线程在onMessage里更新界面。 enabled: !isProcessing && myWorker.ready防止重复提交和线程未就绪时点击。
注意:WorkerScript 里不能访问 QML 组件和主线程变量,只能通过消息对象传递数据。
对比表格
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 单文件内的小段逻辑 | 内联 JS 函数 | 写法最直接,和 QML 融为一体 |
| 多文件共享工具函数 | 外部 .js 文件 + .pragma library |
一处维护,全局共享状态 |
| 跨组件信号响应 | JS 函数 + Connections |
发送端和接收端解耦 |
| 耗时计算、大数据处理 | WorkerScript |
不阻塞主 UI 线程 |
运行验证
- Qt Creator 打开
qml_js_interaction/CMakeLists.txt; - 按
Ctrl+R运行; - 分别点击四个 demo 的按钮,观察面积计算、字符串转换、计数器和斐波那契结果。
扩展复用方向
- 把内联的面积计算函数改成通用坐标转换函数,供多个控件调用。
- 外部 JS 文件里增加网络请求参数拼接、日期格式化等工具函数。
- 用
Connections把多个按钮的信号接到同一个槽函数,实现统一的事件分发。 - WorkerScript 里做图片解码、文件解析、排序等耗时任务,主线程只做进度展示。
已验证环境:
- Qt 版本:Qt 6.8.2、Qt 6.11.1
- 操作系统:Windows 11
- GitHub:QML-Minimal-Demos/qml_js_interaction