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<keys.length; i++) {
      var 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()
  }
}

14.4 创建一个 JS 控制台

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

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

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 给我们输入表达式。为了简化示例,我们忽略错误结果。

本章完。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容