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 x //
’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’ |
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 i 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 p //
false p.z
= 0.3 //
introduce new property ’z’ ’z’ in p //
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 n 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