ECMAScript 语言的标准是由 Netscape、Sun、微软、Borland 等公司基于 JavaScript 和 JScript 锤炼、定义出来的。
ECMAScript 仅仅是一个描述,定义了脚本语言的所有属性、方法和对象。其他语言可以实现 ECMAScript 来作为功能的基础,正如 JavaScript 那样。这个实现又可以被扩展,包含特定于宿主环境的新特性,比如 QML 就引入了 Qt 对象系统中的信号与槽,还增加了动态属性绑定等非常有特色的新功能。
作为一种全新的编程语言,QML 有三个核心:
- ECMAScript
- Qt 对象系统
- Qt Quick 标准库
一、语法
下面一个个来看 ECMAScript 的基础概念。
1. 区分大小写
与 C++ —样,变量、函数名、运算符以及其他一切东西都是区分大小写的,也就是说, 变量 area 和 Area 是不同的。
2. 弱类型
与 C++ 不同,ECMAScript 中的变量没有特定的类型,定义变量时只用 var 运算符,可以将它初始化为任意的值,你可以随时改变变量所存储的数据类型(实际上应当尽量避免这样做)。例如:
var i = 0; console.log(i); i = "hello" console.log(i);
尽管在语法上这么做没有问题,但好的编码习惯是一个变量始终存放相同类型的值。
3. 语句后的分号可有可无
C、C++、Java 等语言都要求每条语句以分号(;)结束。ECMAScript 则允许开发者自行决定是否以分号结束一行代码。如果没有分号,ECMAScript 就把这行代码的结尾看作该语句的结束(与 Lua、Python、Visual Basic 相似)。在每条语句后使用分号是好的编码习惯,因为 QML 难免要与 C++ 代码混合使用,这样做有助于你保持一致的编码习惯,避免思维混乱。 下面两行代码的语法都是正确的:
var background = "white"; var i = 0
4. 注释
ECMAScript 借用了 C、Java 等语言的注释语法,支持两种类型的注释—单行注释和多行注释。
单行注释: // single-line comment 多行注释: /* * the root element of QML */
5. 代码块
代码块表示一系列应该顺序执行的语句,这些语句被封装在左花括号({)和右花括号(})之间。例如:
if(focus == true){ border.width =2; border.color = "blue"; }
二、变量
在 ECMAScript 中使用 var 运算符声明变量,与 C++ 类似,变量名需要遵循一定的规则。
1. 变量声明
变量用 var 运算符加变量名来定义。例如:
var i = 0;
在这个例子中,声明了变量 i 并把它初始化为 0。你也可以不初始化,在用到时再初始化。
一个var语句可以定义多个变量。例如:
var i = 0 , name = "j";
这个例子定义了变量 i,初始化为数字;还定义了变量 name,初始化为字符串。你看到了,这和 C++ 或 Java 不同,一个 var 语句定义的多个变量可以有不同的类型。
2. 变量命名规则
变量命名需要遵守两条简单的规则:
- 第一个字符必须是字母、下画线(_)或美元符号($)。
- 余下的字符可以是下画线、美元符号或者任何字母或数字字符。
下面这些变量名都是合法的:
var test; var objectName; var —phone; var $1;
为了代码的可读性,在命名变量时还应该遵循一定的命名风格。因为 Qt 是基于 C++ 的应用框架,QML 又是 Qt 框架的一部分,这里建议和 Qt C++ 代码采取同样的命名风格—驼峰命名法。
对于变量(包括函数名),以小写字母开始,单词之间采用驼峰命名法。对于类名,以大写字母开始,单词之间采用驼峰命名法。
三、原始类型
ECMAScript 有 5 种原始类型,即 Undefined、Null、Boolean、Number 和 String。每种原始类型定义了它包含的值的范围及其字面量表示形式。
ECMAScript 提供了 typeof 运算符来判断一个值的类型,如果这个值是原始类型,typeof 还会返回它具体的类型名字;而如果这个值是引用值,那么 typeof 统一返回 ”object” 作为类型名字。示例如下:
import QtQuick 2.2 Rectangle { Component.onCompleted:{ var name = "Zhang San Feng"; console.log(typeof name); // 输出:qml:string console.log(typeof 60); // 输出:qml:number } }
变量 name 的类型是 string,字面量 60 的类型是 number。其中 “qml:” 是使用 console.log 输出信息时携带的前缀。
1. Undefined 类型
Undefined 类型只有一个值,即 undefined。当声明的变量未初始化时,该变量的默认值就是 undefined。例如:
var temp;
上面的代码声明了变量 temp 但并未显式地讲行初始化,它的值将被设置为 undefined, 这和 C++ 不同。ECMAScript 的这一特性—未初始化的变量也有固定的初始值,我们可以将一个变量和 undefined 比较来实现一些业务逻辑。比如:
var runOnce; ... if(runOnce == undefined){ runOnce = true; }else{ ... }
当函数没有明确的返回值时,返回的值也是 undefined,如下所示:
function blankFunc(){} console.log(blankFunc() == undefined); // 输出:true
2. Null 类型
Null 类型也只有一个值,即 null。
你可以显式地将一个变量初始化为 null,然后据此实现一些逻辑。
3. Boolean 类型
Boolean 是 ECMAScript 中最常用的类型之一,它有 true 和 false 两个值。
4. Number 类型
Number 类型是最特殊的,它既可以表示 32 位的整数,也可以表示 64 位的浮点数。你在 QML 代码中直接输入的任何数字都被看作是 Number 类型的字面量。
下面的代码声明了存放整数值的变量:
var integer = 10;
下面的代码使用十六进制的字面量来初始化存放整数值的变量:
var hexNumber = Oxlf; var hexNumber2 = OxEA;
数字类型的最大值是 Number.MAX_VALUE,最小值是 Number.MlN_VALUE,它们定义了 Number 值的外边界,所有的 ECMAScript 数都必须在这两个值之间。
不过,由表达式计算生成的数值可以不落在这两个数之间。当计算生成的数值大于 Number.MAX_VALUE 时,它将被赋值为 Number.POSITIVEJNFINITY,即正无穷大;当生成的数值小于 Number.MIN_VALUE 时将被赋值为Number.NEGATIVEJNFINITY,即负无穷大。
5. String 类型
ECMAScript 中的 String 类型是作为原始类型存在的,它存储 Unicode 字符,对应的 Qt C++ 类型为 QString。当你混合 C++ 和 QML 编程时,所有的 QString 类型的变量都会被映射为 ECMAScript 中的 String。
字符串字面量可以用双引号(")或单引号(')来声明。而在 Qt 中,只能用双引号, 单引号表示字符。为了一致性,建议你尽可能不要使用单引号表示字符串。在 ECMAScript 中没有字符类型,这也是为什么你可以使用单引号来表示字符串的原因。下面的两行代码都是有效的:
var name = 'Lv Bu'; var name = "Guan Yu";
四、类型转换
如果一种编程语言不支持类型转换,那真是无法想象。在 ECMAScript 中,类型转换非常简单。
1. 转换成字符串
Boolean、Number、String 三种原始类型,都有 toString() 方法,可以把它们的值转换为字符串。比如下面的代码在 Qt 中可以正常运行:
var name = "Zhang San Feng"; console.log(name.toString()); Console.log(true.toString()); var visible = false; console.log(visible.toString()); var integer = 3.14159; console.log(integer.toString());
或有疑问:String 还有 toString() 方法,不多余吗?确实有点儿多此一举,不i寸,ECMAScript 规定所有对象都有 toString() 方法,因此这个必须有。
Number 类型的 toString() 方法还可以按基转换,比如:
var integer = 13; console.log (integer.toString(16)); // 输出: D
如果你不指定数基,那不管原来是用什么形式声明的 Number 类型,toString() 都按十进制输出。
2. 转换成数字
parselnt() 和 parseFloat() 可以把非数字的原始值转换成数字,前者把值转换为整数,后者把值转换成浮点数。这两个方法只能用于 String 类型,如果你对其他类型调用它们, 返回值将是奇葩的 NaN。
parselnt() 和 parseFloat() 会扫描字符串,直到遇到第一个非数字字符时停止,将转换的结果返回。比如parselnt(”2014年")
将会返回 2014。对于 parseFloat(),会将第一个小数点作为有效字符,而 parselnt() 则不会。
下面是一些示例:
var numl = parselnt ("2014 年"); // 输出:2014 var num2 = parselnt ("OxC"); // 输出:12 var num3 = parselnt ("3.14"); // 输出:3 var num4 = parselnt ("green"); // 输出:NaN_ var num5 = parseFloat ("3.14159"); // 输出:3.14159 var num6 = parseFloat ("1.3.3"); // 输出:1.3 var num7 = parseFloat ("Am I Float"); // 输出:NaN
parselnt() 还支持基模式,下面是一些示例:
var numl = parselnt ("AK47", 16); // 输出:10 var num2 = parselnt ("AK47", 10); // 输出:NaN var num3 = parselnt ("010", 8); // 输出:8 var nun4 = parselnt ("010", 10); // 输出:10
需要注意的是,代表浮点数的字符串必须以十进制形式表示,比如parseFloat(“OxFE”)
,返回 NaN。
3. 强制类型转换
如果你是 C/C++ 程序员,对强制类型转换一定又爱又恨。ECMAScript 也支持强制类型 转换,有三种转换:
Boolean(value)
,把 value 转换成 Boolean 类型。Number(value)
,把value转换为数字(整数或浮点数)。String(value)
,把value转换成字符串。
(1)Boolean()
当要转换的值是一个非空字符串、非0数字或对象时,Boolean() 函数将返回 true。如果该值为空字符串、数字0、undefined 或 null,它将返回 false。一些示例:
var bl = Boolean; // 返回 false var b2 = Boolean("Qt Quick"); // 返回 true var b3 = Boolean(456); // 返回 true var b4 = Boolean(0); // 返回 false var b5 = Boolean (null); // 返回 false var b6 = Boolean (new ArrayO); // 返回 true
(2)Number()
Number() 的强制类型转换与 parselnt() 和 parseFloat() 方法的不同之处在于:Number() 转换的是整个值!parselnt() 和 parseFloat() 只转换第一个无效字符之前的字符串。比如parseFloat ("1.3.3")
返回 1.3,而 Number ("1.3.3")则返回 NaN。
对可转换为数字的字符串,Number() 会自己决定调用 parseInt() 还是 parseFloat()。比如Number ("250")
返回 250, Number ("1.3")
返回 1.3。
(3)String
String() 可以把任何值转换为字符串,它与调用 toString() 方法的唯一不同在于:对 null 或 undefined 值强制类型转换可以生成字符串而不引发错误:
var null2String = String(null); // "null" var oNull = null; var s2 = oNull.toString (); // error
五、函数
ECMAScript 中的函数,就是具名的、可重复使用的代码块。另外,ECMAScript 不支持函数重载。
1. 函数语法
函数语法如下:
function functionName(arg1, arg2, ..., argN){ // 要执行的代码 }
function 是定义函数时必须使用的关键字。functionName可以任意取,符合变量命名规则即可。 arg1 到 argN 是函数的参数,当然也可以没有参数。花括号内是要执行的代码块。
无参函数示例:
function quitApp(){ Qt .quit (); }
带参函数示例:
function showError(msg){ console.log("error - ", msg); } function travel(country, city){ console.log("Welcome to ", city, " , ", country); }
当我们使用函数参数的时候,参数就像不带 var 运算符的变量声明一样。这与 C++ 中必须给函数参数指明类型大相径庭。
2. 函数的返回值
ECMAScript 中的函数,默认都是有返回值的,即便你没有显式使用 return 语句,它也会返回 undefined。如果你想把函数运算的结果返回给调用它的地方,可以使用 return 语句。下面是个简单的示例:
function add(numberl, number2){ var result = number1 + number2; console.log(number1, "+" ,number2, result); return result; }
你可以这样调用 add() 函数:var ret = add(100, 34);
。
六、运算符
ECMAScript 的运算符和 C++、Java 等语言的差不多,具体内容不再赘述。这里只重点介绍一下关键字运算符。void、typeof、instanceof、new、delete 这些都是关键字运算符。
void 运算符比较特殊,它放在一个表达式前,舍弃表达式的值,返回 undefined。
typeof 前面讲过了,对于原始值,返回原始类型;对于引用值,返回 object。这导致你无法准确判断一个对象的引用类型,所以 ECMAScript 引入了 instanceof 运算符。
instanceof 用来测试一个对象的实际类型,你需要显式指定要测试的类型。例如:
var str = new String ("hello world"); console.log (str instanceof String); // 输出:true
new 运算符用来创建一个对象,前面用了很多次了,不再赘述。 delete 运算符比较特别,在 QML 中,一般它只能删除一个对象内由你定义的属性,而框架定义的那些核心属性,多数是你不能删除的。我们在 ECMAScript 中调用 delete,多数时候是解除对对象的引用,以免老有人引用某个对象而导致它逍遥法外。
七、使用 console
console 提供了输出日志信息、断言、计时器、计数器、性能分析等功能,这里只介绍前三个我们经常用到的功能。
1. 输出日志信息
console对象提供了多个打印调试信息的方法:
- console.log();
- console.debug();
- console.info();console.warn();
- console.error();
2. 断言
console.assert() 提供断言功能,它接受一个表达式,当表达式的值为 false 时会输出调试信息,打印 QML 所在行。例如:console.assert (false)
。
如果你传递了额外的参数给 console.assert(),它会在控制台输出这些信息。示例:
var years = 0; for (; years < 18; years++){ console.log("I\'m minor"); continue; console.log ("You shoult not see me"}; } console.assert(years < 18, years);
上面的断言语句,将会输出下列信息:
18 onCompleted (file:///F:/projects/qtquick/qmls/show_type.qml:187)
需要注意的是,在 QML 中,使用 console.assert(),断言失败,程序并不会终止运行。
3. 计时器
console 提供了计时器功能,方便我们测量某些代码的耗时情况。
console.time(tag) 启动定时器,字符串类型的 tag 是必需的。console.timeEnd(tag) 停止计时器,在控制台输出某个标签对应的耗时信息。tag 是必需的。 下面是简单的示例:
console.time("regexp"); var str = "We are dogs;\nYour dogs;\nWe want meat.\nPlease."; var lines = str.match(/^We.*/mg); console.log(lines.length); console.log(lines); console.timeEnd("regexp");
控制台输出如下:
regexp: 7ms
参考:
《Qt Quick 核心编程》第5章 ECMAScript初探