Qt Quick應用開發介紹 13 (JavaScript)

Chapter13 Annexure: JavaScript Language Overview 附錄: JavaScript語言概覽

Js語言總覽; 提供一個Qt支持的所有語言特性的概覽; 通過本文了解Js語言的基本特性; 特別是當你開始學習一個相關的技術, 如QML時, 你可以在這獲得幫助;

這篇文章是對 JavaScript Language Overview http://qt-project.org/wiki/JavaScript 的輕微改動版本; 內容經過Qt4.8和QtQuick1.1測試; 另外, 本文提供一個QtQuick的應用作爲樣例; 路徑: http://download.qt-project.org/learning/developerguides/qtquickappdevintro/qt_quick_app_dev_intro_src.zip   js_basics;


13.1 Introduction 介紹

Js是一個minimalistic(簡單/弱)動態類型的Scripting(腳本)語言; 它是真正object-oriented(面向對象的), 雖然它缺乏對class的支持; 雖然經常作爲客戶端的web開發工具, Js本身是一種語言; 原來是被Netscape開發, 現在Js標準化爲 ECMAScript-262 (3rd and 5th edition) http://www.ecma-international.org/publications/standards/Ecma-262.htm, 這個語言被廣泛使用而且以各種名稱circulate流通; JScript是微軟的derivative衍生語言; JavaScript是由Netscape挑選的原始名稱, 在Netscape 2的時候開始使用; Adobe的ActionScript在v3發佈之前也是基於ECMAScript-262的;

Qt從Qt4.3開始支持JavaScript引擎, 和ECMAScript-262兼容; 這個引擎稱爲 QtScript,  原本是一個獨立的實現; 從Qt4.5開始, QtScript已經基於屬於WebKit https://www.webkit.org/ 的JavaScriptCore了; QtQuick中intense大量地使用了QtScript;


13.2 The Type System 類型系統

Js支持以下的基礎類型: boolean; number; string; object; function;

新的變量可以使用 var statement聲明語句引入當前的scope;

1
2
3
var flag = false // a boolean
var x = 1., y = 2 // numbers can be integers and reals
var s1 = ’abc’; // a string

要獲取變量的類型, 可以使用 typeof 關鍵字; typeof返回一個string, 值爲類型的名字;

1
2
3
4
var x = 0; typeof // ’number’
typeof { x: 1 } // ’object’
typeof typeof { x : 1 } // ’string’
typeof JSON.parse(’{"a": 7}’) // ’object’

Js中所有的東西都表現得像一個對象;

1
2
1.3333333.toFixed(2) // ’1.33’
7..toString() // ’7’

http://www.w3schools.com/ ]

Note 在Js中expression表達式7.toString()不能被正確interpreted解析; 7. 被解析成一個數字, 然後產生語法錯誤;

primitive原始類型boolean, number, string在需要的時候會被隱式地轉換成對象; 爲了這個目的, 全局global對象提供了特殊的構造方法, 可以手動調用:

1
2
3
typeof 1. // ’number’
typeof new Number(1.) // ’object’
typeof new String(’Hi!’) // ’object’

function方法是特殊的對象; 它們和對象的區別只是他們可以被調用, 像構造那樣被調用; 方法可以動態地加上屬性:

1
2
3
4
function f(x) { return f.A x * x }
f.A = 2.7
function Buffer() {}
Buffer.MAX_LINE_LENGTH = 4096

通常這些屬性作爲全局常量使用, 因此以大寫表示; 

對象自己可以被表示爲使用一個數組或對象literal字面量; 數組沒有separate分開的類型, 但是specialized專門的對象會使用數組索引作爲屬性:

1
2
3
4
5
6
var o = { name: ’Werner’, age: 84 } // allocate simple object
print(o.name, o[age])
// both notations are valid, but [] notation allows generated strings
var a = [’a’, ’b’, 7, 11.]
// an array, equivalent to {’0’: ’a’, ’1’: ’b’, ’2’: 7, ’3’: 11.}
typeof o, a // ’object’, ’object’



13.3 Expressions 表達式

表達式的語法更多地依照C語法(就像C++或Java); 主要的區別是, 語句和表達式之間沒有sharp distinction明顯差別; 基本上所有東西都有值(evaluate to something); 方法聲明和compounds複合詞可以在運行時(on-the-fly)包含進來:

1
2
3
4
function f() {} // evaluates ’undefined’
function f() {} + 1 // evaluates to 1, because ’undefined’ is casted to 0
(function() {}) // evaluates to a function object
(function() { return 0; })() // evaluates to 0

表達式可以被分號或換行符分隔開;


13.4 Branching 分支語句

條件分支依照C語法:

1
2
3
4
if (<expression>)
    <statement1>
else // optional
    <statement2> // optional

switch語句和C語言的fall through語義完全一樣:

1
2
3
4
5
6
7
8
9
10
11
switch(<expression>) {
    case <expression>:
        <statement-list-1>
        break;
    case <expression>:
        <statement-list-2>
        break;
    ...
    default:
        <statement-list-n>
}


13.5 Repetitions and Iterators 重複和迭代

重複的動作可以使用 do, while, for循環來解釋:

1
2
3
4
5
6
7
...
do <statement> while (<expression>)
...
while (<expression>) <statement>
...
for (<init-expression>;<test-expression>;<step-expression>) <statement>
...

對於迭代的對象, Js提供了一個特殊的 for-in 語句:

1
for (<expression>; in <object>;) <statement>

給出的表達式需要有一個合適的左值來分配值; 最簡單的情況下, 就是一個變量聲明;

1
2
3
4
5
6
7
var a = [1,2,3,4]
for (var in a)
    print(i, a[i] a[i])
// ’0’, 1
// ’1’, 4
// ’2’, 9
// ’3’, 16

這裏的變量 i 被consecutively連續地賦值給數組的所有的key; 下個example中, 左值表達式是動態生成的:

1
2
3
var o = {a0: 11, a1: 7, a2: 5}
var k = []
for(k[k.length] in o);

o 的keys被拷貝到了 k; 循環語句自身的內容是空的; 每個o的成員, 名字都被分配給k的成員;


13.6 Labeled Loops, Break and Continue 循環標籤

Js中, 循環語句可以打標籤; break和continue語句可以break或continue當前的循環; 使用標籤名字可以做到從一個內部的循環break一個外部的循環:

1
2
3
4
5
6
label_x:
for (var x = 0; x < 11; ++x) {
    for (var y = 0; y < 11; ++y) {
        if ((x + y) % 7 == 0) break label_x;
    }
}


13.7 Objects and Functions 對象和方法

對象創建可以用literal字面意義上的對象或new操作符;

下面的example中, 一個point座標可以用對象文字來表示:

1
var p = { x: 0.1, y: 0.2 }

對象是屬性的整個動態集合; 新的屬性會在第一次分配的時候被引入; 它們可以使用delete操作符刪除; 要查詢一個對象是否包含某個值, 使用 in 操作符;

1
2
3
4
5
’z’ in // false
p.z = 0.3 // introduce new property ’z’
’z’ in // true
delete p.z // remove ’z’ from p
p.z // undefined

屬性值可以是任何類型--包括function類型; Js中的method方法就是function屬性; 當一個function被method notation(方法符號)調用, 它就獲得一個對象的引用來作爲隱式的參數調用--this;

1
2
3
4
5
p.move = function(x, y) {
    this.x = x
    this.y = y
}
p.move(1, 1) // invoke a method

使用 call 方法, Js允許任何function被當作任何對象的方法調用; 然而, 只有少數的情況下你會想要使用 call 方法;

1
2
p2 = { x: 0, y: 0 }
p.move.call(p2, 1, 1)


13.8 Prototype-based Inheritance 基於原型的繼承結構

創建對象的第二種方式是使用new關鍵字+構造function;

1
2
3
var p = new Object
p.x = 0.1
p.y = 0.2

new操作符會allocate分配一個新的對象, 調用已有的constructor構造函數來初始化對象; 這個例子中, ctor是Object, 但是它可以是任何其他的function; ctor function由新創建的對象作爲隱式的this參數來傳遞; Js中沒有class, 但是ctor  function的繼承結構操作起來就像對象工廠; 一般ctor function以大寫字母開頭, 這樣可以和平常的fucntion區分開;  下面的example展示瞭如何使用ctor function來創建point座標:

1
2
3
4
5
function Point(x, y) {
    this.x = x
    this.y = y
}
var p = new Point(1, 2)

Js中每個function可以當作一個ctor和new操作符來組合使用; 爲了支持繼承機制, 每個function有一個默認屬性叫做prototype 

1
2
3
4
5
6
7
8
9
function Point(x, y) {
    this.x = x
    this.y = y
}
Point.prototype = new Object // can be omitted here
Point.prototype.translate = function(dx, dy) {
    this.x += dx
    this.y += dy
}

[prototype是對象原型?]

首先聲明一個新的function稱爲Point, 可以初始化一個point; 之後創建自己的prototype對象, 不會在這裏是redundant多餘的; function的prototype已經默認是一個空的Object; 分配給prototype的屬性將在所有的point中被共享; 這個例子中, 我們定義了translate function可以將point平移一段距離;

現在可以使用Point ctor來實例化point;

1
2
3
4
5
var p0 = new Point(1, 1)
var p1 = new Point(0, 1)
p1.translate(1, -1)
p0 === p1 // false
p0.translate === p1.translate // true

p0和p1對象攜帶了自己的 x和 y屬性, 但是它們是共享 translate方法; 無論何時一個對象的屬性值是通過名字來索取的, underlying潛在的Js引擎首先尋找object自己, 然後如果它不包含那個名字, 它就fall back回退到object的prototype; 每個object攜帶一份ctor的prototype的拷貝來達到這個目的;

如果一個object確實含有每個屬性, 或者它繼承了某個屬性, 它就可以使用 Object.hasOwnProperty() 方法來詢問:

1
2
p0.hasOwnProperty("x"// true
p0.hasOwnProperty("translate"// false

目前我們只定義了一個單獨的ctor, 還沒有涉及object的繼承機制; 我們現在會引入兩個額外的ctor來展示如何chain連接prototype以及thereby從而在object之間構建更多複雜的關係:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function Point(x, y) {
    this.x = x
    this.y = y
}
Point.prototype = {
    move: function(x, y) {
        this.x = x
        this.y = y
    },
    translate: function(dx, dy) {
        this.x += dx
        this.y += dy
    },
    area: function() { return 0; }
}
function Ellipsis(x, y, a, b) {
    Point.call(this, x, y)
    this.a = a
    this.b = b
}
Ellipsis.prototype = new Point
Ellipsis.prototype.area = function() { return Math.PI this.a * this.b; }
function Circle(x, y, r) {
    Ellipsis.call(this, x, y, r, r)
}
Circle.prototype = new Ellipsis

這裏有3個ctor分別創建point, ellipsis, circle; 對於每個ctor, 我們設置了一個prototype; 當一個新的object使用new操作符來創建的時候, object被給予一個ctor的prototype的內部拷貝; prototype的內部引用會在resolving解析屬性名字的時候使用, 名字並不是直接存儲在object中的; 所以prototype的屬性是在以某個ctor創建的object之間被重用的; e.g. 我們創建一個circle然後調用它的move方法:

1
2
var circle = new Circle(0, 0, 1)
circle.move(1, 1)

Js引擎首先查找circle對象, 查看它是否有 move屬性; 由於它找不到, 它會去circle的prototype尋求; 在構造過程中, circle的object的內部prototype引用被設置到了 Circle.prototype; 它是使用Ellipsis的ctor創建的, 但是也不包含move屬性; 因此, name resolution名字解析隨着prototype的prototype繼續, 找到創建它的Point的ctor; 這次name resolution成功找到Point的ctor包含了move屬性; 內部的prototype引用通常指向一個object的prototype chain;

要查詢prototype chain的信息, Js提供了 instanceof 操作符:

1
2
3
4
5
circle instanceof Circle // true
circle instanceof Ellipsis // true
circle instanceof Point // true
circle instanceof Object // true
circle instanceof Array // false, is not an Array

因爲屬性是它們第一次被分配的時候引入的, prototype chain中的屬性在新分配的時候被覆蓋; Object.hasOwnProperty 方法和 in 操作符給出了研究是否一個屬性被存儲起來的方式; 

1
2
3
circle.hasOwnProperty("x"// true, assigned by the Point constructor
circle.hasOwnProperty("area"// false
"area" in circle // true

可以看到, in 操作符使用prototype chain解析名字, Object.hasOwnProperty則只查詢當前對象; 

在多數Js引擎裏, 內部prototype引用稱爲 __proto__ 而且可以從外部訪問; 下一個example中, 我們會使用 __proto__ 引用來探索prototype chain; 不應該在其他context(上下文)中使用這個屬性因爲它是non-standard非標準的; 首先我們定義一個function, 通過iterate迭代它的成員來inspect查看一個對象;

1
function inspect(o) { for (var in o) if (o.hasOwnProperty(n)) print(n, "=", o[n]); }

inspect function 會將所有存儲在object中的成員打印出來, 我們將function應用在circle object和它的prototype上, 獲得下面的輸出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
js> inspect(circle)
x = 1
y = 1
a = 1
b = 1
js> inspect(circle.__proto__)
x = undefined
y = undefined
a = undefined
b = undefined
js> inspect(circle.__proto__.__proto__)
x = undefined
y = undefined
area = function () { return Math.PI this.a * this.b; }
js> inspect(circle.__proto__.__proto__.__proto__)
move = function (x, y) {
    this.x = x
    this.y = y;
}
translate = function (dx, dy) {
    this.x += dx
    this.y += dy;
}
area = function () { return 0; }
js> inspect(circle.__proto__.__proto__.__proto__.__proto__)
js>

可以看到, move方法是存儲在 circle.__proto__.__proto__.__proto__.__proto__ 中的; 有許多重複的未定義成員, 但prototype object仍然可以在instance之中共享; 


13.9 Scopes, Closures and Encapsulation 範圍, 閉包和封裝

在Js中, 開始執行時的範圍是global scope(全局); 預定義的global function比如 Math 或 String 是global object的屬性; global object就像是scope chain的root, 而且是第一個被創建的object; 另外對於global object的標準屬性( ECMAScript Reference http://qt-project.org/doc/qt-4.8/ecmascript.html), QtQuick提供了一個 Qt global object http://qt-project.org/doc/qt-4.8/qdeclarativeglobalobject.html 以及一些附加的屬性; 

通常, global object可以從global scope使用 this 關鍵字顯式地引用; 但 this的值在目前的QtQuick的大多數context上下文中是未定義的; refer to "QML JavaScript Restrictions"--Integrating JavaScript http://qt-project.org/doc/qt-4.8/qdeclarativejavascript.html 

更多的scope是根據是否function被調用而創建的; scope像其他的object會在不再需要的時候被destroy銷燬; 當一個function被定義, enclosing scope保持和function的定義一樣, 作爲invocation scope的parent scope使用; 新的scope是根據function invocation(函數調用者)來創建, 通常refer to as稱爲 activation object; function中的scope定義通常被稱爲 lexical scope;

下面的example展示如何使用 lexical scope 來隱藏私有成員:

1
2
3
4
5
6
function Point(x, y) {
    this.getX = function() { return x; }
    this.setX = function(x2) { x = x2; }
    this.getY = function() { return y; }
    this.setY = function(y2) { y = y2; }
}

當Point的ctor被調用, 會創建get和set方法; 新生成的scope, 基於Point ctor的調用者, 攜帶着x和y成員; getter和setter引用這個scope, 因而它也保留了和新創建的object的生存期; 有趣的是, 除了通過set和get方法, 沒有其他的方式可以獲取x和y; 在這種方式下Js支持了 data encapsulation--數據封裝;

closure閉包的概念--一個funcion引用了封閉的scope, 保留了function的生存期; 低層次的編程語言如C語言不支持closure, 因爲局部的scope是通過stack frame(堆棧結構)創建的, 因此在function return的時候需要被destroy;


13.10 Namespaces 名字空間

function在Js中扮演了pivotal中樞角色; 它們可以作爲簡單的function, method, ctor, 原來封裝私有屬性; 另外function也可以作爲匿名namespcae;

1
2
3
4
5
6
7
(function() {
    // my code
    var smth = new Smth // safe
    other = [1,2,3] // bad, goes into global scope
    Array = function() {} // forbidden
}) ()
var smthElse = {} // bad, goes into global scope

一個匿名function可以在on-the-fly運行時被定義; 特別是global初始化代碼, 通常以這種方式wrapped包裝來防止污染global scope; 因爲global object可以在Js中作爲另一個object來修改, 以這種方式wrapping code包裝代碼可以減少不經意地覆蓋global變量的可能性; 爲了確保做到這點, 所有的變量需要duly充分地使用 var 語句來引入;

命名的名字空間也可以使用function創建; 比如如果我們想要寫一個utility庫來做繪畫, 可以這麼寫:

1
2
3
4
5
6
7
8
9
function PaintUtil() {
    PaintUtil.Point = function(x, y) {
        this.move = function(x2, y2) { x = x2; y = y2 }
        this.getX = function() { return x; }
        this.getY = function() { return y; }
    }
// Ellipsis, Circle, other painting utility methods
}
PaintUtil()

一旦這個小小的庫模塊被執行, 它會提供單個的 PaintUtil object, 使得utility function可以訪問; Point可以使用PaintUtil提供的ctor來實例化:

1
var p = new PaintUtil.Point(0.1, 0.2)

可重用的Js模塊應該只會以一個可分辨的名字來引入單個global object;


13.11 Common Methods 普通方法

Js允許一個object的默認行爲被 valueoOf() 和 toString() 方法改變; valueOf會返回一個基本類型的值; 它用來比較object(在排序的時候), 以及評估comprising組成object和基本類型的表達式; toString()可以把object轉化爲string; 在Js中, object的比較是關於equality, 而不是大於或小於; 

equality的comparison比較總是比較object的引用; 而對大小的比較則是先把object轉化爲基本類型的value再做比較; 首先調用 valueOf(), 然後如果它沒有返回一個基本類型, 則調用 toString()代替;

對於Point類, 我們可以定義以下的方法:

1
2
3
4
5
6
Point.prototype.valueOf = function () {
    return Math.sqrt(this.getX() * this.getX() + this.getY() * this.getY());
}
Point.prototype.toString = function () {
    return this.getX().toString() + "," this.getY().toString();
}


13.12 Exceptions

Js提供了一個exception handling異常處理機制, 和其他高層次語言一樣; exception使用 throw 語句拋出; 任何值都可以在一個exception中使用:

1
throw <expression>;

拋出一個exception的時候, Js會將當前的scope進行unwind(退棧/棧展開), 直到它到達一個try-catch的scope:

1
2
3
4
5
6
7
8
9
try {
    <statement-list>
}
catch (<name for exception object>) {
    // handle exception
}
finally {
    // always go through here
}

exception object的名字僅僅是局部地定義在catch scope中的; exception可以被 re-thrown 重新拋出;


13.13 Resources

web鏈接:

“The JavaScript Reference”Mozilla Developer Network https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference 

“JavaScript. The core.” by Dmitry A. Soshnikov”http://dmitrysoshnikov.com/ecmascript/javascript-the-core/ 

“Changes to JavaScript: EcmaScript 5 by Mark Miller”video from Google Tech Talk, May 18, 2009

“Standard ECMA-262”official standard PDF http://www.ecma-international.org/publications/standards/Ecma-262.htm 

推薦書籍:

“JavaScript: The Good Parts” by Douglas Crockford10 * “Part I - Core JavaScript” in“JavaScript: The Definitive Guide” by David Flanagan11

---13---

---YCR---   

refer to http://qt-project.org/wiki/developer-guides


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章