第十六章:JavaScript
第十六章:JavaScript
QtQt QuickQML
Mozilla Developer Network
表面来看,JavaScript是一门非常普通的语言,与其它语言差别不大:
function countDown() {
for(var i=0; i<10; i++) {
console.log('index: ' + i)
}
}
function countDown2() {
var i=10;
while( i>0 ) {
i--;
}
}
function countDown() {
for(var i=0; i<10; i++) {
console.log('index: ' + i)
}
}
function countDown2() {
var i=10;
while( i>0 ) {
i--;
}
}
但一定要注意,JS有函数作用域(变量生命周期),而C++ 有块作用域(详见 函数及函数域 )
if ... else
,break
,continue
这些表达式也如预期那样工作。不限于整型值,switch case 也可以比较其它类型:
function getAge(name) {
// switch over a string
switch(name) {
case "father":
return 58;
case "mother":
return 56;
}
return unknown;
}
JS认为几种值可以视为false(如,false
,0
,""
,undefined
,null
)。比如,函数默认返回undefined
。要验证false,得使用===
严格相等操作符。==
等值操作符会先做类型转换再来判断是否相等。如果可能的话,使用更快和更好的===
严格相等操作符,它将判定一致性(详见比较操作符)。
在底层,javascript 有自己的处理方式。例如数组:
function doIt() {
var a = [] // empty arrays
a.push(10) // addend number on arrays
a.push("Monkey") // append string on arrays
console.log(a.length) // prints 2
a[0] // returns 10
a[1] // returns Monkey
a[2] // returns undefined
a[99] = "String" // a valid assignment
console.log(a.length) // prints 100
a[98] // contains the value undefined
}
对于习惯了C++ 或Java等面向对象语言的人来说,JS的运行机制有很大不同。JS不是纯粹的面向对象的语言,它是被称为基于原型的语言。每个对象都有原型对象。每个对象都是基于它的原型对象创建的。可以在Douglas Crockford编写的Javascript的优点一书中详细了解相关知识。
要测试JS的代码片段可以使用在线的JS 控制台 或构建一小段的QML代码:
import QtQuick 2.5
Item {
function runJS() {
console.log("Your JS code goes here");
}
Component.onCompleted: {
runJS();
}
}
JavaScript 手册
这里有一些QML与JS一起使用的简短的例子。关于在QML中如何使用JS,它会给你一些思路。
打印 QML 项目的所有键
Item {
id: root
Component.onCompleted: {
var keys = Object.keys(root);
for(var i=0; ivar key = keys[i];
// prints all properties, signals, functions from object
console.log(key + ' : ' + root[key]);
}
}
}
对象与JSON字符串间的相互转换
Item {
property var obj: {
key: 'value'
}
Component.onCompleted: {
var data = JSON.stringify(obj);
console.log(data);
var obj = JSON.parse(data);
console.log(obj.key); // > 'value'
}
}
当前日期
Item {
Timer {
id: timeUpdater
interval: 100
running: true
repeat: true
onTriggered: {
var d = new Date();
console.log(d.getSeconds());
}
}
}
通过名字调用函数
Item {
id: root
function doIt() {
console.log("doIt()")
}
Component.onCompleted: {
// Call using function execution
root["doIt"]();
var fn = root["doIt"];
// Call using JS call method (could pass in a custom this object and arguments)
fn.call()
}
}
创建一个JS控制台
Item {
id: root
Component.onCompleted: {
var keys = Object.keys(root);
for(var i=0; ivar key = keys[i];
// prints all properties, signals, functions from object
console.log(key + ' : ' + root[key]);
}
}
}
Item {
property var obj: {
key: 'value'
}
Component.onCompleted: {
var data = JSON.stringify(obj);
console.log(data);
var obj = JSON.parse(data);
console.log(obj.key); // > 'value'
}
}
Item {
Timer {
id: timeUpdater
interval: 100
running: true
repeat: true
onTriggered: {
var d = new Date();
console.log(d.getSeconds());
}
}
}
Item {
id: root
function doIt() {
console.log("doIt()")
}
Component.onCompleted: {
// Call using function execution
root["doIt"]();
var fn = root["doIt"];
// Call using JS call method (could pass in a custom this object and arguments)
fn.call()
}
}
我们将通过一个小例子来创建一个JS控制台。我们需要一个输入框,用户可以在其中输入他的 JS 表达式,预期应该有一个输出结果列表。由于这应该更像一个桌面应用程序,我们使用 Qt Quick Controls 模块。
注意
这个项目中的 JS 控制台对测试非常有益。增强的 Quake-Terminal 效果,也能够给客户留下深刻印象。要正确地使用它,需要控制 JS 控制台计算的范围,例如当前可见的屏幕,主要数据模型,单例核心对象,或以上所有因素。

我们用Qt Creator来创建Qt Quick UI project类型的工程,以使用Qt Quick 控件。给工程起个名为JSConsole。向导完成后,我们已经拥有了一个应用的基本结构,这个应用有一个窗体、一个退出菜单。
为了能输入,我们使用TextField和一个Button,以对输入内容进行计算。表达式的计算结果使用一个有列表模型ListModel的列表视图ListView来展示,两个标签来展示表达式和对其的计算结果。
我们的应用程序会分为两个文件:
JSConsole.qml
:应用的主要视图jsconsole.js
:用于计算用户表达式的脚本库
JSConsole.qml
Application window 程序窗体
// JSConsole.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Window
import "jsconsole.js" as Util
ApplicationWindow {
id: root
title: qsTr("JSConsole")
width: 640
height: 480
visible: true
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit()
}
}
}
Form
ColumnLayout {
anchors.fill: parent
anchors.margins: 9
RowLayout {
Layout.fillWidth: true
TextField {
id: input
Layout.fillWidth: true
focus: true
onAccepted: {
// call our evaluation function on root
root.jsCall(input.text)
}
}
Button {
text: qsTr("Send")
onClicked: {
// call our evaluation function on root
root.jsCall(input.text)
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Rectangle {
anchors.fill: parent
color: '#333'
border.color: Qt.darker(color)
opacity: 0.2
radius: 2
}
ScrollView {
id: scrollView
anchors.fill: parent
anchors.margins: 9
ListView {
id: resultView
model: ListModel {
id: outputModel
}
delegate: ColumnLayout {
id: delegate
required property var model
width: ListView.view.width
Label {
Layout.fillWidth: true
color: 'green'
text: "> " + delegate.model.expression
}
Label {
Layout.fillWidth: true
color: delegate.model.error === "" ? 'blue' : 'red'
text: delegate.model.error === "" ? "" + delegate.model.result : delegate.model.error
}
Rectangle {
height: 1
Layout.fillWidth: true
color: '#333'
opacity: 0.2
}
}
}
}
}
}
调用脚本库
计算函数jsCall
并非由其本身完成表达式的计算的,为清晰易读起见,这部分逻辑被移动到了JS模块(jsconsole.js
)。
import "jsconsole.js" as Util
function jsCall(exp) {
const data = Util.call(exp)
// insert the result at the beginning of the list
outputModel.insert(0, data)
}
注意
为了安全起见,我们不使用 JS 中的eval
函数,因为这将允许用户修改本地作用域。我们使用 Function 构造函数在运行时创建一个 JS 函数,并将我们的作用域作为 this 变量传入。由于每次创建函数时它不充当闭包并存储自己的范围,因此我们需要使用this.a = 10
将值存储在函数的作用域内。此作用域由脚本设置为作用域变量。
jsconsole.js
// jsconsole.js
.pragma library
const scope = {
// our custom scope injected into our function evaluation
}
function call(msg) {
const exp = msg.toString()
console.log(exp)
const data = {
expression : msg,
result: "",
error: ""
}
try {
const fun = new Function('return (' + exp + ')')
data.result = JSON.stringify(fun.call(scope), null, 2)
console.log('scope: ' + JSON.stringify(scope, null, 2), 'result: ' + data.result)
} catch(e) {
console.log(e.toString())
data.error = e.toString()
}
return data
}
调用函数返回的数据是一个带有返回值、表达式和错误属性的 JS 对象:data: { expression: "", result: "", error: "" }
。我们可以直接在 ListModel 中使用这个 JS 对象,然后从委托中访问它,例如delegate.model.expression
为我们提供了输入的表达式。