程式碼高𠅙

2012/09/14

你所想像不到的 Javascript

/**
 * 這是一篇我在 N 年前所寫的 Javascript 教材,近來隨著 Node.js 及 Mobile web 
 * 的熱潮興起,Javascript 又再度走紅,因此再把它重新發佈,希望能對用到的人
 * 有所幫助。
 */
 
這不是一篇教你如何在網頁中應用 JavaScript 的教材。這是一篇讓你明瞭看似簡單的 JavaScript,其實其核心語法功能強大。我將在這篇文章中說明 JavaScript 物件導向的特性,並說明如何透過這些特性,達到傳統程式語言 (C++/Java) 所難以完成的功能。
 
Written by Edward Hsieh/謝鎮澤
 
January 04, 2002 -- 1st Edition
January 04, 2009 -- 2nd Edition
 
Object and JavaScript
 
一般談到 Object-Oriented,皆會提到物件導向語言必須具有的三個特性,即封裝、繼承及多型。這三種特性中,若缺少了繼承的特性、則稱為 Object-Based。
 
Javascript 與其說是物件導向語言,倒不如說它是以物件為基礎的程式語言,但它極為動態的程式設計風格,卻能模擬出物件導向的特性。這種作法與大家所熟悉的,以類別 (class) 為基礎的靜態物件導向語言大不相同。
 
在 Javascript 中有五種基礎的物件型態,包含 undefined, object, boolean, number, 以及 string。以下的範例可供驗證這五種基礎型態:
 
alert (typeof(X));
alert (typeof(null));
alert (typeof(true));
alert (typeof(123));
alert (typeof('abc'));
 
除了上述五種基礎類型,使用者還可自訂型態,而這些自訂的型態,都是「以物件為基礎」的。
 
Create Objects in JavaScript
 
在 Javascript 中建立物件的方法相當簡單。直接看個例子:
 
var main = new Object;     // 建立新物件
main.x = 123; // 設定物件成員變數(屬性)之一
main["y"] = "XYZ"; // 設定物件成員變數(屬性)之二
alert(main["x"]); // 取得物件屬性並輸出
alert(main.y);    
 
可以看到在 JavaScript 中,main.x 與 main["x"] 這兩種語法是通用的。其實在其他語言中,這兩種表示法的語意並不相同。我稍後再作說明。
 
List All Members in an Object
 
這是 JavaScript 的必殺技,使用 JavaScript 的人務必要學會這個技巧。底下函式可以傳回一個物件的所有成員的字串表達式,包括物件中的屬性及方法。在物件導向程式設計中,這種技術叫 reflection。
 
function listMember(main) {
   var s = "";
   for( key in main ) // 使用 in 運算子列舉所有成員
   s += key + ": " + main[key] + "\n";
   return s;
}
 
範例碼中的 key 會對應到物件中的屬性名稱,如 "x" 或 "y",而 main[key] 則對應到屬性值。
 
說這項技巧是必殺技的原因是,你可以透過這項技巧,將物件封裝的黑箱打開來,看看裡面藏有什麼東西。我常用這項技巧來看看 IE 與 Mozilla 的 DOM 物件模型有何不同。試試看下面呼叫範例,就可以知道這項技巧的強大了:
 
var ary = [123, "abc"];
alert (listMember(ary));
alert (listMember(document.location));
 
Construct Object with Initial Value
 
要在建立物件的同時指定物件初始值,必須先透過 function 建立一個「原型物件」(或稱為 constructor),再透過 new 運算子建立新物件。例如以下程式碼會建立一個二維陣列的原型,再產生一個新的二維物件。
 
function Array2DVar(x,y) {     // 定義二維陣列原型
   this.length = x;
   this.x = x;        // x 維度長度
   this.y = y;        // y 維度長度
   for(var i = 0; i < this.length; i++)  // 初始各元素值為 null
   this[i] = new Array(y);    // this 代表物件本身
}
 
var a2dv = new Array2DVar(10, 10);   // 建立新的 10*10 的二維陣列
a2dv[1][3] = "ABC";       // 設定二維陣列元素值
a2dv[2][6] = "XYZ";
a2dv[9][9] = 1000;
 
alert( a2dv[1][3]);  // 取得二維陣列元素值,並顯示出來
alert( a2dv[2][6]);
alert( a2dv[9][9]);
 
Initial Array Object
 
在 JavaScript 中陣列也是物件 (其實近代多數語言中陣列也都是物件,只有像 C 或 Assembly 這類古老的語言才不把陣列看成物件),因此也可以用 constructor 的語法來建構。當然 JavaScript 還提供了 [] 語法,以更方便建構陣列,範例如下:
 
a = new Array("abc", "xyz", 1000); // constructor 語法,或
a = ["abc", "xyz", 1000]; // 陣列標準語法
 
陣列的元素可以是簡單的資料、其他物件,或是函數。舉個例子來在陣列裡面放函式:
 
b = [ // 使用函式作為陣列元素
   function () { alert("這個好玩!") },
   function () { alert("再按一次離開!") },
   function () { alert("再來一次!") },
   function () { alert("最後一次!") }
];
 
for (var i = 0; i < b.length ; i++)
   b[i]();
 
最後一個 for 迴圈是個有趣的應用。由於 b 陣列中現在存放的所有元素都是函式,因此我們可以對 b 的每個元素進行呼叫。
 
Object as Association Array
 
關連陣列 (Assocation Array) 又稱作 Map 或 Dictionary,是一種物件容器,其中可以放置許多的 key-value pair,以存取子物件。在 JavaScript 中,物件本身就可以作為關連陣列。以關連陣列的方式初始化物件的範例如下:
 
obj1 = {"a" : "Athens" , "b" : "Belgrade", "c" : "Cairo"};
alert(obj1["a"]); // 顯示 Athens
 
obj2 = {
   name: "Edward",
   showName: function() { alert(this.name); }  // 使用函式作為物件屬性
}
 
obj2.showName();  // 顯示 Edward
obj2.age = 23; // 屬性可以動態加入
 
其中 obj1 儲存了三個子元素,其鍵 (key) 為 "a", "b" 與 "c",而值 (value) 為 "Athens", "Belgrade" 與 "Cairo"。obj2 中 showName 鍵所對應的值為 function,因此 obj2.showName() 即為函式呼叫。
 
Object as Return Value
 
雖然 Javascript 的函式只能傳回一個變數,但你卻可以將傳回值設定為物件,達到傳回 1 個以上變數值的效果:
 
function a () {
   return [32, 17];
}
 
b = a();
alert( b ); // 或
alert(a());
 
function pixel () { return {"x": 32, "y":17}; }
 
point = pixel (); alert (point.x + "n" + point.y); // 或 alert (pixel().x + "n" + pixel().y);
 
Delegation Function Object
 
函式也是物件,只是其中包含的是程式的邏輯。這項特性可拿來作為委任式的程式設計,亦即使用委任函式當作另一函式的參數:
 
function doloop(begin, end, func) { // 這個函式是個 iterator
   for (var i = begin; i < end; i++) {
       func(i);
   }
}
 
function func1(i) { // 印出 ** n **
   document.writeln("** " + i + " **");
}
 
doloop(1, 10, func1); // 印出 1o 行 ** n **
 
doloop(20, 29, function(i) { // 印出 10 行 ## n ##
   document.writeln("## " + i + " ## ");
});
 
Object = Properties + Behaviors
 
古有明訓:程式 = 資料結構 + 演算法。而物件是建構程式的基本單位,自然的具有相同的性質。物件除了有屬性 (property),也可具有操作 (behavior),也就是函式。
 
假如我們要使用一維陣列來模擬二維陣列,那麼就無法使用 ary[x][y] 這種表示法來設定或取得陣列成員。不過我可以定義一個 set 方法來設定成員變數,而以 get 方法來取得成員變數值。原型函式定義如下:
 
function Array2D(x,y){      // 以一維陣列模擬二維陣列的原型物件
   this.length = x * y;      // 陣列總長
   this.x = x;         // x 維度長度
   this.y = y;         // y 維度長度
   for(var i = 0; i < this.length; i++)  // 初始各元素值為 null
       this[i] = null;
   this.get = function(x,y){      // 成員函式:取得陣列第 [x,y]個元素值
      return this[x*this.x + y];
   }
    
   this.set = function(x,y,value){  // 成員函式:設定陣列第 [x,y] 個元素值
   this[x*this.x + y] = value;
}
}
 
我們接著來使用它:
 
var a2d = new Array2D(10, 10);    // 建立新的「二維」陣列
 
a2d.set(1, 3, "ABC");   // 設定「二維」陣列元素值
a2d.set(2, 6, "XYZ");
a2d.set(9, 9, 1000);
 
alert( a2d.get(1,3) );  // 取得「二維」陣列元素值,並顯示出來
alert( a2d.get(2,6) );
alert( a2d.get(9,9) );   
 
Member Function Outside of Constructor
 
我們也可以將物件成員函式寫於原型物件之外。以下的 Array2D 物件與上一個範例中的 Array2D 物件有相同的作用,只不過這次是寫在原型物件之外。
 
function Array2D(x,y){      // 以一維陣列模擬二維陣列的原型物件
   this.length = x * y;      // 陣列總長
   this.x = x;         // x 維度長度
   this.y = y;         // y 維度長度
   for(var i = 0; i < this.length; i++)  // 初始各元素值為 null
       this[i] = null;
   this.get = Array2DGet; // 用這種方式把成員函式掛進來
   this.set = Array2DSet;
}
 
function Array2DGet(x,y){      // 成員函式:取得陣列第 [x,y] 個元素值
return this[x*this.x + y];
}
 
function Array2DSet(x,y,value){    // 成員函式:設定陣列第 [x,y] 個元素值
this[x*this.x + y] = value;
}
 
Dynamic Object Function
 
這裡說明如何為一個已定義物件,動態的加上其他操作的方法。
 
如果一物件已定義完成,而您也使用它來建立了新的物件,這時候您想為原型物件增加新的操作 (而不修改原型物件的原始碼),讓所有該物件的複本都能使用該操作,該如何達成呢?方法是使用物件的 prototype 屬性。以下這個例子,為 Array 這類 Object 在執行期加入一個 max 方法,以取得陣列元素之最大值 (修改自微軟 jscript.chm之範例):
 
function array_max(){      // 定義求取 Array 最大值之函式
   var i, max = this[0];
   for (i = 1; i < this.length; i++){
       if (max < this[i])
           max = this[i];
   }
   return max;
}
Array.prototype.max = array_max;   // 在 Array 原型中加入 max 函式
 
上面的程式碼,首先建立一個 array_max 方法,以求取陣列之最大元素。接著將這個方法設定給 Array 原型物件。
 
var x = new Array(1, 2, 3, 4, 5, 6);  // 透過 Array 建構子建立一陣列
                                 // 想求取 x 中某一元素之最大值
var y = x.max( );       // 取得 x 之最大元素
 
Dynamic Mix In
 
假如物件 source 有 mathod1, method2 兩個函式;而另一物件 target 有 methodA 及 methodB 兩個函式。現在我想把 source 的所有特性 (feature) 匯入到 target 中,我們可以撰寫一個 mixin 函式如下,來達到這裡所說的功能:
 
function mixin(target, source) {
   if( typeof source == "object")
       for( value in source )
           target[value] = source[value];
}
 
這個 mixin 函式可以動態的為 target 加上另一物件的所有操作。這種 Mix in 的功能可是 C++/Java 的 static type 語言所望塵莫及的。完整的應用範例如下:
 
function listMember(main) {
   var s = "";
   for( key in main ) // 使用 in 運算子列舉所有成員
       s += key + ": " + main[key] + "\n";
   return s;
}
 
function main(){              // main 之建構子
   this.name = "main";
   this.methodA = function() {};
   this.methodB = function() {};
}
var dynamic = {
   method1: function() {},
   method2: function() {}
}
function mixin(target, source) {
   if( typeof source == "object")
       for( value in source )
           target[value] = source[value];
}
obj = new main();
mixin(obj, dynamic);   // 匯入 dynamic 物件之所有功能
alert(listMember(obj));
 
Singleton Object
 
用 associate array 的語法來建立 singleton, 用 function contructor 的語法來建立可多重複製的原型物件
 
Inheritance Through Functions
 
function superClass() {
 this.supertest = superTest; //attach method superTest
}
 
function subClass() {
 this.inheritFrom = superClass;
 this.inheritFrom();
 this.subtest = subTest; //attach method subTest
}
 
function superTest() {
 return "superTest";
}
  
function subTest() {
 return "subTest";
}
 
 
var newClass = new subClass();
 
alert(newClass.subtest()); // yields "subTest"
alert(newClass.supertest()); // yields "superTest"

一則關於思考的小故事--心裡掛鳥籠

一則關於思考的小故事,出自《換個思考,換種人生》第 3 章「精通思考流程」,找個地方思考:
偉大發明家兼德科公司創辦人凱特林,擁有 140 多項專利,及近 30 多所大學的榮譽博士頭銜,我曾經聽他談起專門闢室思考的事情,他比喻那是在心裡掛鳥籠。
這種說法似乎很怪異,然而當你聽過他以 100 美元跟人打睹的事,就會比較有概念了。
有一回凱特林向朋友說,他有辦法讓對方在來年買一隻鳥當寵物。那位朋友心想,誰也無法「逼」他做這種事,就和他打賭了。
不久之後,凱特林送了一個瑞士手工精製的昂貴鳥龍給這位朋友。朋友把鳥籠帶回家,覺得鳥籠實在美極了,於是掛在飯廰裡。可是他發現每當客人來訪時,總有人問:「你的鳥什麼時候死的?」
「我從沒養過鳥。」他會這麼說,然後把整件事解釋一遍。重複幾次之後,他終於去買了一隻鸚哥,再把輸掉的 100 美元付給凱特林。凱特林後來說:「如果你把鳥籠掛在心裡,終究會放點什麼東西進去的。」
正如凱特林的鳥籠能引來一隻鳥,特定的思考地點也能引來好的想法。如果你來到思考的地點希望能產生好的想法,那麼終究會想出一些好點子。
這則故事也讓我回想起個人兒時的生活經歷:小學一二年級前,我並不是特別喜歡讀書。有一天父親竟然買了一架簡易式的折疊式書桌,說是給我當禮物。當時我對那書桌的設計感到好奇,也覺得它的樣式精巧。每天吃完晚飯後,非得「享受」一下坐在它上面的「樂趣」不可。就這樣,也養成我至今為止尚稱喜歡閱讀的習慣。

如果這樣的人也能成功的扭轉她的生命

在我生活周遭,免不了有些因為生活過得並不很好,而怪罪生活環境的朋友。雖然我認為他們應該停止抱怨,做些更有利於生命的事,但是總是少了一個強而 有力的案例,來說明每個人的確可以因為信念及努力,成功的改變他們的人生,直到我讀到《換個思考,換種人生》,「技巧五:釋放策略思考的力量」,「策略持 家」一節:
萊恩於 1900 年代中期住在俄亥俄州的迪凡斯。她一直沒有學開車,自從有了孩子以後,也從沒出外工作過。在她那個年代,身為母親就必需留在家裡。這本來也沒有什麼問題,只不過她家裡很缺錢,而且她有 10 個小孩!
她的丈夫凱利在一家機械廠工作,收入十分微薄。更糟的是,凱利是個酒鬼,每星期拿回家的錢,1/3 都被他喝掉了。
任何人在街上遇見萊恩,恐怕都看不出她會運用策略思考,但她的確是。她必需撫養 10 個小孩,打理家務,還要另外賺到足夠的錢養活全家。
在那個時間,製造廠商經常贊助徵稿競賽…〈略過部分〉
萊恩的文字能力不錯,婚前在地方報社工作又鍛鍊了不少,因此她想了一個順理成章的計劃。既然她不能外出賺錢 (即使有人願意照顧 10 個小孩,你能想像這筆褓姆費有多可觀嗎?) 那就參加競賽來賺取金錢與家用品。
想要照顧一家 12 口,洗這麼多人的衣服,還要同時寫數百首詩。廣告歌曲和宣傳短文,需要高度的策略。萊恩做到了。
首先,她苦心建立了一些關係網,用來蒐集參賽表格和購物證明,諸如盒蓋、罐頭標籤等等。其次,她必須在做家事的時候寫東西。每年她都買一本活頁筆記薄,把點子記錄下來,並追蹤一切參賽後的相關資訊。
接下來的挑戰,是找出時間寫作。她處理家務時,總是會把筆記本翻開就近放著,但是她發現燙衣服是最佳時機。
萊恩的策略思考並不限於書寫的準備工作,她也運用策略挑選書寫內容。每一次徵文比賽,她都慎選字眼。她的女兒泰莉回想萊恩準備競賽的情形,說道:
「她 總是會說競賽不只是收集盒蓋和表現聰明才智,還要考慮格式 (有些競賽會要求使用特定字眼,有時若是使用產品想關字眼可以加分)、產品對象 (針對家庭、年輕人或小孩) 和評審。比起贊助商和產品,參賽者更應該重視作為評審的廣告代理商。每個廣告商都有他們的偏好,可能是韻文或散文,可能是幽默或中規中矩的文章。」
萊恩打聽出每家廣告商的喜好,這個策略幫了大忙。在幾年當中,她為這個大家庭贏了幾台洗衣機和烘乾機,數十件大大小小的電器用品、兩輛新車 (車子賣了)、數面筆小額獎金,和兩筆大額獎金,分別是 5000 元與 3400 元。
她把第一筆大額獎金做為頭期款,買下一棟房子,一家 12 口終於搬出 2 房的出租屋。第二筆獎金則用來償還丈夫偷偷申請的二胎房貸 (她一直到償還日前一個月才發現)。
當情況不允許失敗,策略思考就是你最好的選擇。萊恩本來可以偶爾寫首詩,投稿到地方報社去,但她需要做點事來幫忙家人的生計。「像我爸爸這樣的丈夫和父親,永遠不可能改變,」她的女兒說:「我們家唯一的希望就是看媽媽怎麼改變,怎麼撫養一群快樂健康的孩子。」
萊恩成功了。她不僅讓家人活下來,還幫助他們成功。她擁有 7 個小孩大學畢業,1 個拿到博士學位,還有 1 個法學院畢業。
這則故事給了我很深的感觸。如果在那樣環境下的女人都能因為策略思考而成功,那麼我相信大都數人成功的條件都比她優越的多。我把這樣的故事告訴一個酒鬼朋友,想聽聽看他有何看法。
他說:嗯!能討到這樣的老婆就好了。
嗯,這……也算是策略思考的第一步吧。

2011/10/14

Google+ Bookmarklet



Google+  <== 這是 Bookmarklet 連結
使用方式:
  1.  將上面這個 Bookmarklet 連結,拉到瀏覽器的網址列,或加入書籤。
  2. 在 網址列 或 書籤 中點選剛加入的 Google+ Bookmarklet,就能將目前瀏覽的網頁,分享到 Google+ 上,以下是分享頁面抓圖。
  3.  按下 Share 鈕即可分享。