Thinking in Technique
tangrui.cn: en
剛剛忍不住手癢,下載了一個(gè)Firefox 2.0 Beta 1(可能過(guò)幾天這個(gè)鏈接就換了)。寫(xiě)了一段JS,看來(lái)果然在JS1.7當中,Iterator是非常好用的。
代碼如下
================================
<html>
<head>
<script language=javascript>
// add function for JavaScript Object
Object.prototype.newfunc = function(){};
function init() {
var a = [1,2,3,4];
var it;
var s;
// old method in JavaScript 1.5
s = “”;
for (it in a) {
s += it + ” ==> ” + a[it] + “<BR>”;
}
document.getElementById(”old_in”).innerHTML = s;
// new one in JavaScript 1.7
s = “”;
try {
it = Iterator(a, true);
while (true) {
var key = it.next();
s += key + ” ==> ” + a[key] + “<BR>”;
}
} catch (err if err == StopIteration) {
//alert(”End of record.\n”);
} catch (err) {
//alert(”Unknown error: ” + err.description + “\n”);
}
document.getElementById(”new_in”).innerHTML = s;
}
</script>
</head>
<body onload=”init()”>
<div id=old_in></div>
<hr>
<div id=new_in></div>
<hr>
</body>
</html>
================================
運行結果如下:
0 ==> 1
1 ==> 2
2 ==> 3
3 ==> 4
newfunc ==> function () { }
可見(jiàn),使用Iterator是可以避免在for之中錯誤的使用一些未知的新添加的方法的。
有一點(diǎn)遺憾的就是,Iterator的結束是很丑陋的,不知道有沒(méi)有什么好的方法。還需要繼續研究。
Firefox 2.0 beta 1推出了,肯定很多人已經(jīng)關(guān)注這個(gè)事情了,我則比較(更加)關(guān)心的是JavaScript的增強。JavaScript升級到1.7了(FF1.5支持JavaScript1.5,IE6sp1支持不完全的JavaScript1.5)
在JavaScript 1.7里面有了一些新功能的支持,比如Generator和Iterator,let語(yǔ)法的支持,非結構化賦值(destructuring assignment)。
令我最關(guān)心和期待的就是Iterator的支持,據說(shuō)只要自定義next方法和__iterator__屬性就可以根據自己的需要定義返回的值了。Iterator的出現最大的好處就是可以放心的在一些內嵌的對象當中添加方法了。
以前這樣的代碼是很危險的:
var o = new Object();
for (var i in o){}
因為很可能某個(gè)第三方的庫文件在Object當中添加了一些方法,(比如著(zhù)名的prototype),這樣,在不同的第三方庫混用的時(shí)候,就很可能出現問(wèn)題。
現在有了自定義的Iterator,那么我覺(jué)得是完全可以避免的,只要為Object寫(xiě)好合適的iterator就可以了。具體的代碼我還沒(méi)有嘗試。
還有一個(gè)問(wèn)題,就是不知道IE7支持JavaScript到什么版本,而且,IE6和Firefox1.5.*都還是JavaScript 1.5 并且很多人是不會(huì )升級的。
所以,JavaScript1.7也許在未來(lái)的一段時(shí)間內,還是一個(gè)美好的(但是不能實(shí)現的)愿景吧。
如我前面的文章說(shuō)的,對于JavaScript,一個(gè)類(lèi),就是一個(gè)function,他的類(lèi)方法(也就是static的)都是作為這個(gè)function的一部分,而實(shí)例方法,都是在prototype上面的。
function ClassA() {
}
ClassA.staticMethod = function () {
}
ClassA.prototype.instanceMethod = function () {
}
在我這個(gè)實(shí)現當中,一個(gè)類(lèi)的繼承是拷貝父類(lèi)的所有類(lèi)方法,這樣子類(lèi)就有了父類(lèi)的靜態(tài)方法。
然后讓子類(lèi)的prototype.prototype指向父類(lèi)的prototype.
然后可以根據自己的需要,重寫(xiě)一些方法。
function ClassB() {
}
ClassB.staticMethod = ClassA.staticMethod;
ClassB.prototype.prototype = ClassA.prototype;
ClassB.prototype.instanceMethod = function () {
// method 2
}
對于子類(lèi),使用一個(gè)prototype的鏈,來(lái)實(shí)現方法的實(shí)例方法的繼承。之所以選擇這種實(shí)現方法,是因為子類(lèi)是要重寫(xiě)其中的某些方法的。而prototype又是一個(gè)reference,所以直接的寫(xiě)作ClassB.prototype = ClassA.prototype,會(huì )在重寫(xiě)ClassB的實(shí)例方法的同時(shí),破壞ClassA的實(shí)例方法。而修改后的方法則會(huì )屏蔽父類(lèi)的。
尋找方法的順序是,instanceA.prototype.method -> ClassA.prototype.
此時(shí)對于類(lèi)方法的繼承,已經(jīng)實(shí)現,現在需要實(shí)現在子類(lèi)中,調用父類(lèi)的方法。
對于Java,這樣的使用是很平常的
public void method() {
super.method();
}
在JavsScript中,為了實(shí)現此類(lèi)功能,所以必須保留一個(gè)parent的reference,指向ParentClass.prototype.
ClassB.prototype.parent = ClassA.prototype.
那么在instanceB里面調用this.parent.method.call(this);就可以使用父類(lèi)的方法了。使用call調用,是為了把自己的數據傳到父類(lèi)。更漂亮的解決方法,我還沒(méi)有想到。
所以完成的代碼是
function ClassA() {
}
ClassA.prototype.method1 = function () {
}
ClassA.staticMethod = function () {
}
function ClassB(){
}
ClassB.staticMethod = ClassA.staticMethod;
ClassB.prototype.prototype = ClassB.prototype.parent = ClassA.prototype;
這個(gè)我抽象出來(lái)一個(gè)extend方法,
var LCore = function () {
}
LCore.extend = function (destination, source) {
// copy all functons
for (var prop in source) {
if (prop == “prototype”) {
continue;
}
destination.prototype[prop] = source[prop];
}
// make a reference for parent and reference prototype
destination.prototype.prototype = destination.prototype.parent = source.prototype;
return destination;
}
停了一段時(shí)間,今天說(shuō)說(shuō)JavaScript的面向對象
js不是一個(gè)天生的oo的語(yǔ)言。但是他的函數是可以擔負面向對象的概念的。并且js也內置了很多的對象,比如String,Date等等。
js有一個(gè)關(guān)鍵字new,可以認為是實(shí)例化一個(gè)function的對象,把function作為一個(gè)class。
比如
function MyObj () {
}
var myObj = new MyObj();
這個(gè)時(shí)候,myObj就是MyObj的一個(gè)實(shí)例。
有兩點(diǎn)應該具體的說(shuō)一下
1,JavaScript是一個(gè)prototype的語(yǔ)言
2,this在JavaScript里面的應用
首先我們說(shuō)一下prototype,prototype是一個(gè)function的屬性,也可以認為是一個(gè)類(lèi)的屬性。他用來(lái)記錄這個(gè)類(lèi)有那些方法。
比如我們要給MyObj定義一個(gè)sayHello的方法,我們可以這樣寫(xiě)
MyObj.prototype.sayHello = function () {
alert(”Hello”);
}
這個(gè)時(shí)候,我們就可以調用myObj.sayHello()執行這個(gè)方法。
而this則表示這個(gè)類(lèi)本身(嚴格意義上說(shuō),應該是這個(gè)函數本身),比如
MyObj.prototype.sayBye = function () {
alert(”bye” + this.name);
}
那么這里的this,就是實(shí)例變量本身。myObj.sayBye()。函數里面的this就是表示myObj。那么我們就可以實(shí)現帶參數的構造函數。
function MyObj2(name) {
this.name = name;
}
var myO2 = new MyObj2(”tom”);
這樣,我們就可以實(shí)現一般情況下使用的面向對象了。
需 要主意的是,JavaScript沒(méi)有屬性的共有私有機制,我們可以通過(guò)myO2.name訪(fǎng)問(wèn)他的name屬性。而且js也不是強行規定的,我們要訪(fǎng)問(wèn) myO2.age也是可以的,但是myO2并沒(méi)有age的屬性(或者說(shuō)重來(lái)沒(méi)有給age賦值)那么得到的就是undefined。
通過(guò)這個(gè),我們就可以用Object來(lái)實(shí)現平常使用的Map(java語(yǔ)言,對于python應該是字典類(lèi)型)。
var m = new Object();
m[”name”] = “tom”;
m[”age”] = 20;
alert(m[”name”]);
注1:對于alert,JavaScript調用的是這個(gè)object的toString函數,如果沒(méi)有,則會(huì )顯示 ie:[object] ff:[object, Object]
注2:在js當中,new Object和{}是一樣的,我們可以寫(xiě) var m = {};
這也是js中簡(jiǎn)單的map的實(shí)現。不好的地方是遍歷不方便,因為沒(méi)有地方記錄有那些key
for (var i in m) 這種遍歷方法,會(huì )把所有的對于object進(jìn)行擴展的方法都顯示出來(lái)。
對Object的擴展,是通過(guò)對Object的prototype添加方法實(shí)現的。
比如js的String方法是沒(méi)有trim的,我們可以自己做一個(gè)實(shí)現然后通過(guò)擴展String來(lái)提供這個(gè)方法
String.prototype.trim = function () {}
var a = ” 123 “.trim();
上面提到了一下this,之所以說(shuō)一下this,是因為他并不完全等同于C++或者Java里面的this變量。
this在js中表示緊貼著(zhù)調用地點(diǎn)的,非prototype擴展的方法。
比如上面提到的
MyObj.prototype.sayBye = function () {
alert(”Bye” + this.name);
}
這個(gè)里面的this,緊貼的非prototype的函數是MyObj(再次注意,js中class是通過(guò)函數實(shí)現的),所以this.name就是實(shí)例變量。
但是在這種情況
MyObj.prototype.doSomething = function () {
todo(function () {
alert(this.name);
});
}
這個(gè)時(shí)候,this表示的是這個(gè)匿名函數
function () {
alert(this.name)
}
那么這里就會(huì )出現錯誤,如果想要這樣使用,應該使用輔助變量。
MyObj.prototype.doSomething = function () {
var me = this; //把自己的reference賦值給變量me
todo (function () {
alert(me.name); //通過(guò)me來(lái)訪(fǎng)問(wèn)myObj實(shí)例
});
}
而且所有的實(shí)例變量必須通過(guò)this來(lái)進(jìn)行訪(fǎng)問(wèn),直接寫(xiě)變量名稱(chēng)會(huì )認為是全局的變量。
上例中的alert(this.name)是正確的,而alert(name)是錯誤的。
還有最后說(shuō)一下靜態(tài)方法。我們可以用js來(lái)模擬靜態(tài)方法(或者說(shuō)是類(lèi)方法)
就是直接把方法寫(xiě)在函數上
MyObj.staticMethod = function () {
}
這種方法都是實(shí)例無(wú)關(guān)的,調用的時(shí)候,通過(guò)MyObj.staticMethod()來(lái)調用。其實(shí)這也是一個(gè)變通的方法。
下一次我再說(shuō)說(shuō)類(lèi)的繼承。
大家有什么想要聽(tīng)的,告訴我,如果我會(huì ),那么我一定分享出來(lái)。
今天說(shuō)一下函數參數。
函數參數,可以理解為函數的參數,還有就是函數作為參數(還是挺詭意)。
首先說(shuō)一下函數的參數。
這個(gè)問(wèn)題看上去沒(méi)有什么可說(shuō)的。因為作為拍代碼的,都肯定知道這個(gè)東西。
function method1(a, b) {
return a + b;
}
很簡(jiǎn)單,a,b就是函數method1的參數。一般情況下,這樣足夠了。
但是在JavaScript當中,函數的調用,其中的參數個(gè)數是不強制的。(作為腳本語(yǔ)言,當然類(lèi)型也沒(méi)有強制)。
這里很象某些語(yǔ)言的不定參數列表(由于我沒(méi)有過(guò)多的研究,所以不便多說(shuō))。
比如上面的方法
var c = method1(1,2); //正確
var d = method(1); //也正確
此時(shí)c的值是3,d的值是NaN。
那么我們如何來(lái)保證參數的個(gè)數呢?
JavaScript當中,每一個(gè)方法里面,有一個(gè)內欠的變量,arguments。這是一個(gè)數組變量,記錄著(zhù)傳入這個(gè)方法一共有幾個(gè)參數。
很象java里面main函數的 args數組
static public void main(String args[]) {…}
我們可以使用arguments.length來(lái)判斷一共有幾個(gè)參數
function method2() {
if (arguments.length == 2) {
return arguments[0] + arguments[1];
} else {
return 0;
}
}
這樣我們就可以保證參數的個(gè)數了。
接下來(lái),我們說(shuō)一下函數作為參數。
在昨天的例子當中,我使用了很多這樣的例子。比如
ServerHandler.getName(myID, fucntion (name) {
alert(”Hello:” + name);
});
這里面,getName方法的第二個(gè)參數就是一個(gè)方法,
function (name) {…}
在JavaScript 當中,任何的東西都是Object,一個(gè)函數的類(lèi)型是Function,他是Object的一個(gè)子類(lèi)(姑且這么說(shuō),JavaScript不是一個(gè)純粹的面 向對象的語(yǔ)言,所以說(shuō)子類(lèi)有一些牽強)。所有的Object都可以作為參數被傳入函數。那么一個(gè)函數作為參數就不足為奇了。
暫停一下,我們現來(lái)看一下這個(gè)例子。
function method3() {
alert(”Hello”);
}
var m = method1;
var v = method1();
上面的兩個(gè)變量,m,v,他們的區別就是,v是method1運算的結果,在這里是undefined,因為method3在alert以后什么都沒(méi)有返回。
而m,這句付值語(yǔ)句,是把method3的函數體,付值給了m(有興趣的話(huà),可以使用alert(m)來(lái)看一下結果)。如果現在寫(xiě)下如下的語(yǔ)句
m();
那么就會(huì )調用這個(gè)函數,然后alert一個(gè)Hello。
好,我們回來(lái),上面我想說(shuō)明的就是,當一個(gè)函數作為參數傳入到另外的一個(gè)函數當中,我們是可以使用一個(gè)參數變量來(lái)接受這個(gè)函數的。并且在需要的時(shí)候,我們可以繼續的分發(fā),或者調用。
比如
function method4(m) {
m();
}
method4(method3); //注意,這里面的method3沒(méi)有后面的括號(),他表示是這個(gè)函數體作為參數而不是運行結果作為參數。
在進(jìn)入到method4里面之后,通過(guò)參數變量m,可以調用method3。
利 用函數作為參數,在JavaScript中使用的最普遍的就是callback方法了。當需要執行一個(gè)函數,傳入一個(gè)callback function用來(lái)處理結果。這種方法我們在很多地方已經(jīng)使用的非常多了?,F在隨著(zhù)AJAX的流行,callback方法也越來(lái)的重要。如我昨天所說(shuō), 很多的和server通訊的方法都是無(wú)法預料何時(shí)能夠結束的。那么傳入一個(gè)callback function就很方便了。
這里,我一直有這樣的一個(gè)難點(diǎn),始終沒(méi)有很好的解決辦法。
就是一般情況,我們給出callback function,都是在無(wú)法控制返回時(shí)間的情況下采用的,那么此時(shí),大多數的情況就是這個(gè)方法會(huì )使用另外的線(xiàn)程來(lái)執行,而程序會(huì )繼續向下走。
使用返回值的函數調用,是會(huì )在這個(gè)函數調用的時(shí)候,線(xiàn)程阻塞,直到返回結果。
我現在遇到的情況就是,很多的時(shí)候,我無(wú)法返回值(比如AJAX),而使用callback function也挺麻煩。所以一直沒(méi)有好的方法。
雖然我昨天說(shuō)的信號量的方法可以變通的解決這個(gè)問(wèn)題。但是在復雜的情況下(比如我現在正在開(kāi)發(fā)的一套Web UI Framework)里面也是挺麻煩的。希望大家如果有好的方法可以告訴我。
最后,還要提及的一點(diǎn)就是,在函數調用的時(shí)候,還有一種方法就是call(或者apply)調用。
function method5(msg) {
alert(msg);
}
method5.call(x, “Hello”);
這個(gè)x就是某個(gè)Object的變量(從面向對象的角度可以說(shuō)是一個(gè)實(shí)例(instance))。很類(lèi)似于python的self。
這個(gè)使用方法在一般情況下意義不大。但是在某種面向對象實(shí)現的時(shí)候是非常有用的。我會(huì )在面向對象的文章中再次說(shuō)明的。
從今天開(kāi)始,我會(huì )不定期的寫(xiě)一些關(guān)于JavaScript的東西,包括語(yǔ)言,應用等方面。組成JavaScript系列。
如果沒(méi)有特殊的說(shuō)明,這里假定JavaScript的執行環(huán)境是在瀏覽器(browser)當中的。
今天開(kāi)始第一次,討論一下同步和異步。
曾經(jīng)查詢(xún)過(guò)一些JavaScript的信息,發(fā)現google出來(lái)的結果都是詢(xún)問(wèn)JavaScript如何能夠實(shí)現異步的代碼。
而我,很不幸,查詢(xún)的卻是如何讓JavaScript實(shí)現異步調用的同步(是不是挺起來(lái)很詭異)。
首先說(shuō)一下JavaScript當中的異步方法。
其實(shí)這個(gè)問(wèn)題是大家經(jīng)常要碰到的。而且這個(gè)實(shí)現也很簡(jiǎn)單。我就不多說(shuō)了。
給兩段代碼
我們應該注意到setTimeout和setInterval都是window的方法。
我們可以直接使用,但是規范的還是調用window.setTimeout window.setInterval,之所以提及這個(gè),我會(huì )在以后的JavaScript系列中繼續講解。
現在該說(shuō)一下我遇到的問(wèn)題了。
我現在使用dwr作為AJAX的server端引擎,在調用dwr方法的時(shí)候,需要提供一個(gè)回調方法(callback function)來(lái)接受server的返回結果。
而這個(gè)回調方法是不會(huì )被阻塞的。此時(shí)browser回啟動(dòng)另外的現成處理。
這個(gè)很好理解,因為dwr的這個(gè)方法執行的時(shí)間是無(wú)法預料的,如果此時(shí)調用被阻塞,而server又花相當長(cháng)的時(shí)間進(jìn)行處理。那么瀏覽器就會(huì )死在這里。從用戶(hù)體驗的角度是根本無(wú)法接受的。
這里的例子代碼是
…
ServerHandler.getString(”Weiming”, function (str) { //”Weiming”是傳回server的參數
alert(str);
}); // ServerHandler是dwr提供的server方法的interface,具體使用請參見(jiàn)dwr網(wǎng)站。
alert(1);
在執行的過(guò)程中,會(huì )先執行alert(1),然后在一個(gè)無(wú)法預料的時(shí)間后執行alert(str)。
如果一次簡(jiǎn)單的比如hello world的調用是不會(huì )出問(wèn)題的。
但是如果我要執行的一系列的dwr function是有前后順序的,比如后面執行的需要前面的返回結果,簡(jiǎn)單的代碼書(shū)寫(xiě)順序是無(wú)法保證執行順序的。
var myID = null;
ServerHandler.getID(function (id) {
myID = id; //無(wú)法預料何時(shí)會(huì )執行這句話(huà)
});
ServerHandler.getUserWithID(myID, function (name) {
/*
此時(shí)myID還沒(méi)有值,因為上面的 myID = id這段代碼是需要一個(gè)時(shí)間段之后才會(huì )執行的
*/
alert(”hello:” + name);
});
比如這樣的代碼就會(huì )出錯。那么如何解決呢?
最簡(jiǎn)單的實(shí)現方法就是callback function的嵌套。
…
ServerHandler.getID(function (id) {
ServerHandler.getUserWithID(id, function (name) {
alert(”hello:” + name);
}
});
這樣我們就可以保證多個(gè)dwr方法調用的順序了。這樣貌似解決了問(wèn)題。但是并不完美。
原因是當我們把JavaScript和Browser作為一個(gè)操作的平臺和邏輯業(yè)務(wù)的平臺(AJAX的應用程序,后面的JavaScript系列中會(huì )有提及),而不是一個(gè)簡(jiǎn)單的展示平臺的時(shí)候。這樣的回調函數嵌套就很難控制了。
這也就是我最開(kāi)始指出的需要同步異步調用的一個(gè)方法。
最終我在公司的解決方案是這樣的。
寫(xiě)一個(gè)信號量的類(lèi)(JavaScript的面向對象會(huì )稍后講解),當我需要執行一個(gè)方法的時(shí)候,我就申請一部分信號量。
把需要被執行的方法放進(jìn)信號量的隊列進(jìn)行等待。等前面等待的方法(如果存在)執行后在執行。
信號量將作為一個(gè)參數被傳入執行的方法,這樣這個(gè)方法可以決定釋放這個(gè)信號量還是繼續分發(fā)。
比如
var s = new Semaphore();
var myID = null;
s.p(function (e) { //把方法放入信號量隊列
ServerHandler.getID(function (id) {
myID = id;
s.v(); //釋放信號量
}
});
s.p(function (e) { //將第二個(gè)方法放到信號量隊列,只有當前面的s.v()執行之后,這個(gè)方法才會(huì )執行。
ServerHandler.getName(myID, function (name) { //此時(shí),可以保證myID一定有值
alert(”Hello:” + name);
s.v();
})
})
這里只是對信號量這個(gè)方法進(jìn)行了簡(jiǎn)單的闡述。
信號量還支持創(chuàng )建自信號量,如果創(chuàng )建了子信號量,那么父信號量必須等帶所有的孩子都歸還了信號量之后才可以執行他里面的代碼。
由于代碼的版權是公司的,所以很抱歉,現在無(wú)法給出相應的完整的信號量的實(shí)現。
如果下一端我有時(shí)間的話(huà),我會(huì )給出一個(gè)我實(shí)現的版本的。
聯(lián)系客服