QML Book 第十四章 JavaScript

14.JavaScript(JavaScript)

本章的作者:jryannel

** 注意: **
最新的构建时间:2016/03/21
这章的源代码能够在assetts folder找到。

JavaScript 是 Web 客户端开发的通用语言。它也开始主要由节点 js 在 Web 服务器开发上牵引。因此,它是作为声明式命令式 QML 语言的一种很好的补充。QML 本身作为声明式语言用于表达用户界面层次结构,但限于表达操作代码。有时你需要一种表达操作的方式,这里有 JavaScript 发挥作用。

** 注意: **
Qt 社区中有一个关于在现代 Qt 应用程序中有关 QML/JS/QtC ++ 的正确混合的问题。普遍同意的推荐方法是将应用程序的JS部分限制在最小限度,并在 QtC++ 内部实现业务逻辑,并在 QML/JS 内部实现 UI 逻辑。这本书趋向这种边界的划分,通常对于一个产品的开发这不一定是正确的混合方式,不是对于所有人都适用。最重要的是根据你的团队技能和个人品味而定。在接受推荐的时候保持你的怀疑。

这里有一个简短的例子,如 JS 看起来像,混合在 QML 中:

Button {
  width: 200
  height: 300
  property bool checked: false
  text: "Click to toggle"

  // JS function
  function doToggle() {
    checked = !checked
  }

  onTriggered: {
    // this is also JavaScript
    doToggle();
    console.log('checked: ' + checked)
  }
}

所以 JavaScript 可以作为一个独立的 JS 函数在 QML 中的许多地方,作为一个 JS 模块,它可以在每一个属性绑定的右侧。

import "util.js" as Util // import a pure JS module

Button {
  width: 200
  height: width*2 // JS on the right side of property binding

  // standalone function (not really useful)
  function log(msg) {
    console.log("Button> " + msg);
  }

  onTriggered: {
    // this is JavaScript
    log();
    Qt.quit();
  }
}

在使用 QML 定义用户界面时,使用 JavaScript 完成功能。那么我们需要写多少的 JavaScript 才合适呢? 这取决于我们的风格和你对 JS 开发的熟悉程度。JS 是一种松散型语言,这使得你很难发现类型缺陷。函数参数接受不同类型的变量值,会导致非常难发现严重的 Bug。发现缺陷的方法是严格的单元测试或者验收测试。因此如果你在 JS 中开发真正的逻辑(不是粘贴代码)你应该使用测试优先的方法。通常使用这种混合开发非常成功的团队(Qt/C++ 与 QML/JS),他们都会最小化前段逻辑中使用的 JS,在后端 QtC++ 中完成更加复杂的工作。后端遵循严格的单元测试,这样前段的开发者可以信任这些代码并且专注于用户界面的需求。

** 注意: **
一般来说:后端开发人员是功能驱动的,前端开发人员是用户需求驱动的。

14.1 Browser/HTML 与 QtQuick/QML

浏览器用于呈现 HTML 并在运行时执行与 HTML 相关联的 Javascript。现在的现代 Web 应用程序包含更多的 JavaScript,然后是 HTML。浏览器中的 Javascript 是一个标准的 ECMAScript 环境,具有一些浏览器添加。浏览器中的典型 JS 环境知道访问浏览器窗口的 window 对象。还有 jQuery 用来提供 CSS 选择器的基本 DOM 选择器。另外还有一个 setTimeout 函数在一段时间后调用函数。除此之外,环境是类似于 QML/JS 的标准 JavaScript 环境。

不同的是 JS 出现在 HTML 和 QML 中的形式。在HTML中,我们只能在事件处理程序中添加 JS(例如,页面加载,鼠标按下)。例如,我们的 JS 在页面加载时正常初始化,这与 QML 中的 Component.onCompleted 相当。例如,我们不能使用 JS 进行属性绑定(至少不直接,AngularJS 会增强 DOM 树以允许这些,但是这与标准的 HTML 很不同)。

所以在 QML 中的 JS 更加优秀,并且与 QML 的渲染树高度集成。使得语言更具有可读性。除了这些,开发过 HTML/JS 应用程序的人会觉得在 QML/JS 中开发起来非常容易上手。

14.2 语言

本章不介绍 JavaScript 的一般介绍。还有其他书籍,JavaScript 的一般介绍,请访问 Mozilla 开发者网站。

在表面上 JavaScript 是一种非常常见的语言,与其他语言没有什么不同:

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、0、""、undefined、null)。 例如,函数默认返回 undefined。要测试假使用 === 身份运算符。 == 等于运算符将执行类型转换以测试相等性。如果可能,使用更快更好的 === 严格的等价运算符来测试身份(请参阅比较运算符)。

在引擎罩下,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 的用于 OO 语言的人来说,JS 也是如此。JS 不是纯粹的 OO 语言,它是所谓的基于原型的语言。每个对象都有一个原型对象。基于他的原型对象创建一个对象。请阅读 Javascript the Good Parts by Douglas Crockford 一书中的更多信息。

要测试一些小的 JS 代码片段,我们可以使用在线 JS 控制台或仅构建一小部分 QML 代码:

import QtQuick 2.5

Item {
  function runJS() {
    console.log("Your JS code goes here");
  }
  Component.onCompleted: {
    runJS();
  }
}

14.3 JS 对象

在使用 JS 时,有一些更常用的对象和方法。这是他们的小集合。

  • Math.floor(v), Math.ceil(v), Math.round(v) —— 对浮点数向下取整、向上取整、四舍五入取整
  • Math.random() —— 创建 0 到 1 之间的随机数
  • Object.keys(o) —— 从对象获取键值(包括QObject)
  • JSON.parse(s), JSON.stringify(o) —— JS 对象和 JSON 字符串之间的转换
  • Number.toFixed(p) —— 调整精度
  • Date —— 日期操作

我们也可以在以下位置找到它们:JavaScript 参考

这里有一些小而有限的例子,如何使用 JS 与 QML。它们告诉我们如何在 QML 中使用 JS 的方法。

** 打印 QML 元素中的所有键 **

Item {
  id: root
  Component.onCompleted: {
    var keys = Object.keys(root);
    for(var i=0; i

** 将对象解析为 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()
  }
}

14.4 创建一个 JS 控制台

作为一个例子,我们将创建一个 JS 控制台。我们需要一个输入字段,用户可以输入他的 JS 表达式,理想情况下应该有输出结果列表。因为这应该更像是桌面应用程序,我们使用 QtQuick Controls 模块。

** 注意: **
我们下一个项目中的 JS 控制台可以真正有益于测试。增强了 Quake-Terminal 效果,也是打动客户的好主意。要明智地使用它,我们需要控制 JS 控制台评估的范围,例如。当前可见屏幕,主要数据模型,单例对象或其它的东西。

QML Book 第十四章 JavaScript_第1张图片
jsconsole

我们用 Qt Creator 使用 QtQuick 控件创建一个 Qt Quick UI 项目。我们称之为 JSConsole 项目。 向导完成后,我们已经有一个应用程序的基本结构,并显示一个应用程序窗口和一个菜单来退出应用程序。

对于输入,我们使用 TextField 和 Button 发送用于测试的输入。表达式评估的结果使用 ListView 以 ListModel 为模型显示,两个标签显示表达式和评估结果。

// part of JSConsole.qml
ApplicationWindow {
  id: root

  ...

  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 {
                      width: ListView.view.width
                      Label {
                          Layout.fillWidth: true
                          color: 'green'
                          text: "> " + model.expression
                      }
                      Label {
                          Layout.fillWidth: true
                          color: 'blue'
                          text: "" + model.result
                      }
                      Rectangle {
                          height: 1
                          Layout.fillWidth: true
                          color: '#333'
                          opacity: 0.2
                      }
                  }
              }
          }
      }
  }
}

评估函数 jsCall 本身不进行评估,这已经被移动到 JS 模块(jsconsole.js)以更清晰的分离。

// part of JSConsole.qml

import "jsconsole.js" as Util

...

ApplicationWindow {
  id: root

  ...

  function jsCall(exp) {
      var data = Util.call(exp);
      // insert the result at the beginning of the list
      outputModel.insert(0, data)
  }
}

为了安全起见,我们不使用 JS 的 eval 函数,因为这将允许用户修改本地范围。我们使用 Function 构造函数在运行时创建一个 JS 函数,并将其作为这个变量传递给我们。由于函数在每次不作为闭包并存储自己的范围时创建,因此我们需要使用 this.a = 10 将该值存储在该函数范围内。该范围由脚本设置到作用域变量。

// jsconsole.js
.pragma library

var scope = {
  // our custom scope injected into our function evaluation
}

function call(msg) {
    var exp = msg.toString();
    console.log(exp)
    var data = {
        expression : msg
    }
    try {
        var fun = new Function('return (' + exp + ');');
        data.result = JSON.stringify(fun.call(scope), null, 2)
        console.log('scope: ' + JSON.stringify(scope, null, 2) + 'result: ' + result)
    } catch(e) {
        console.log(e.toString())
        data.error = e.toString();
    }
    return data;
}

来自调用函数的数据返回是一个具有结果,表达式和错误属性的 JS 对象 data: { expression: {}, result: {}, error: {} } 我们可以直接在 ListModel 中使用这个 JS 对象,然后从代理访问它。model.expression 给我们输入表达式。为了简化示例,我们忽略错误结果。

本章完。

你可能感兴趣的:(QML Book 第十四章 JavaScript)