August 01, 2008

以 C 語言實做 Javascript 的 prototype 特性

自從撰文 [以 C 語言實做 Functional Language 的 Currying] 與 Thinker 的指教後,又認真思索「以 C 語言模擬其他程式語言的關鍵機制」的議題,何也?在筆者淺薄的認知中,語言只是一種手段,語法不過是彰顯某些動機與概念,而本文則試著由 C 語言「模仿」Javascript 的 prototype 特性,雖不過是東施效顰,但不妨可視為一個切入點,對 Javascript 這個兼具 prototype inheritance 與 functional programming 特性的動態語言。

JavaScript 這個具有十多年歷史的程式語言,雖然普遍的認知是,語言本身跟 Java 無關,但兩者發展的背景卻值得玩味。過去任職於 Netscape 的 Brendan Eich (現為 Mozilla Corporation 的 CTO),為訂於 1995 年發佈的 Netscape Navigator 2.0 設計了嶄新的語言,即 LiveScript,目標為同時能運作於客戶端 (如 Navigator 這樣的網頁瀏覽器) 與伺服器端 (Netscape 主打自家的解決方案 LiveWare)。這樣的概念與打著 "The Network is The Computer" 旗幟的 Sun Microsystems 不謀而合,於是,雙方進行了合作,這樣的因素使得 LiveScript 被更名為 Javascript,考量到同一年發佈的 Java,可在市場概念上作強化。經過多次改版的 Javascript,最後由 Netscape 交付予 ECMA (European Computer Manufacturers Association) 制定為新標準,也就是 ECMA-262,也被規範為 ECMAscript、與實現廠商無關的腳本程式語言的語法和語意,稍後整合為 ISO/IEC-16262。

Javascript 程式語言最令初學者費解的兩項特性就是 Currying 與 prototype,前者來自 functional programming,後者來自物件導向的一個分枝:prototype-based,與傳統程序語言如 C 或 Pascal 有很大的出入。簡單來說,在 prototype-based 的思維中,任何物件都是 "instance",但卻沒有傳統 class 的概念,這些 "instance" 由特定的 prototype 去複製 (clone) 而生。godfat 給了一個簡要的範例,探討 ECMAscript 中操作 prototype 的方式,以下引述作參考: [出處]
    // 產生一個 function object, 會輸出「我是 ooo」
    function say() { print('I am ' + this) }
    
    // 產生一個 function object, 拿這當 Duck 的 prototype
    function Duck() { }
    
    // 讓 Duck prototype 產生一個成員,也就是讓 say 變成他的 method
    Duck.prototype.say = say;
    
    // 定義 toString 讓 say 使用
    Duck.prototype.toString = function(){ return 'Duck' }
    
    // 假設現在有一個讓某東西說話的 function
    function say_hello(who){ who.say() }
    
    // 於是我們可以這樣呼叫 say_hello
    say_hello(new Duck)
    
在如此的語意中,"new Duck" 意味著 ECMAscript 得先去尋找 Duck 的 prototype,然後 clone 一份該 prototype 後傳回物件的 instance,所以 function say_hello(who) 的 "who" 被帶入一份 Duck 的複製,也因此,執行 say 則會輸出:
    I am Duck
    
而回到 Javascript 的物件導向設計,其 prototype inheritance 的特性落實於 this, new, prototype 等關鍵字的使用,再巧妙地摻入 lexical scope (execution context 和 scope chain)、匿名函數、function object 等 functional programming 特性,組合表現出物件導向的多個面向。在 [石頭閒語] 有若干篇值得一讀的好文,與此主題相關者可參見: 鋪陳許久,本文終於能切入主題了,是的,掌握 prototype-base 的思維,一句話來說就是:
    「"instance" 由特定的 prototype 去 clone 而生」
筆者預想的情境是,環境中存有若干個矩形 (Rect) 物件,對 prototype 作操作,給定如 getter() 這類的 method 動作予矩形物件,並觀察其執行的表現。下列的程式碼,將展現 C 語言實做 prototype 的手法: [js-prototype.c]
#include <stdio.h>
#include <stdlib.h>

struct _rect {
	int  x, y;
	int (**prototype)(struct _rect *);
};
typedef struct _rect Rect;

typedef int (*Rect_method)(Rect *); 	/**< method enumeration */
 
enum { GET_X, GET_Y, END_OF_NAME_TABLE }; 	/**< name table */

Rect_method *rect_prototype; 	/**< method as an array of prototypes */

/* constructor & destructor */
Rect *rect_new(int x, int y)
{
	Rect *p = (Rect *) malloc(sizeof(Rect));
	p->x = x, p->y = y;
	p->prototype = rect_prototype;
	return p;
}

void rect_delete(Rect *p) { free(p); }

/* methods: getter */
int rect_get_x(Rect *p) { return p->x; }
int rect_get_y(Rect *p) { return p->y; }

/* register prototype */
void rect_init_prototype(void)
{
	rect_prototype = (Rect_method *) malloc((sizeof(Rect_method)) *
	                                        END_OF_NAME_TABLE);
	rect_prototype[GET_X] = rect_get_x;
	rect_prototype[GET_Y] = rect_get_y;
}
/* deregister prototype */
void rect_delete_prototype(void) { free(rect_prototype); }

static void test_suite(void)
{
	Rect *r1 = rect_new(1, 2);
	Rect *r2 = rect_new(3, 4);

	printf("r1 = (%d, %d), r2 = (%d, %d)\n",
			r1->prototype[GET_X](r1), r1->prototype[GET_Y](r1),
			r2->prototype[GET_X](r2), r2->prototype[GET_Y](r2));

	rect_delete(r1);
	rect_delete(r2);
}

int main(void)
{
	rect_init_prototype();
	test_suite();
	rect_delete_prototype();
	return 0;
}
試著編譯並執行:
$ gcc -o js-prototype{,.c} && ./js-prototype
r1 = (1, 2), r2 = (3, 4)
顯然,我們的 prototype 操作已發揮功能,當然,這僅是一個模擬的手法,但可從中窺見 Javascript 在此機制的特性。
由 jserv 發表於 August 1, 2008 01:35 PM
迴響

呵,這讓我想到一年半前,曾經在 ptt CSSE 板看過 semop 示範一些由 C 實作動態 OO 的機制。
不過一來我 C 不熟,二來這段時間我自己本身的改變也滿大的。總之大概看過去之後,
多少就比較能想像由 C 實作一個完整的 OO 系統要怎麼做。或許也像是在設計一個新語言吧?

那篇文章在:
http://www.ptt.cc/bbs/CSSE/M.1167950648.A.894.html

或:
● 1461 1/05 semop R: [討論] 念完資工之後...
┌─────────────────────────────────────┐
│ 文章代碼(AID): #15dOCuYK (CSSE) [ptt.cc] Re: [討論] 念完資工之後... │
│ 這一篇文章值 663 銀 │
└─────────────────────────────────────┘

godfat 真常 發表於 August 1, 2008 08:14 PM

Hi Jserv

I am finding a way to parse and run "javascript" by python.

Only python can run on google app engine. so I try to write a python app to parse and run "javascript".

Hugo Darwin 發表於 August 19, 2008 05:47 PM

Hi Jserv,
both mozilla and firefox use a javascript interpreter
'spidermonkey'. You can study and give us some tutorial.

Hugo Darwin 發表於 August 20, 2008 12:07 PM
發表迴響









記住我的資訊?