This page looks plain and unstyled because you're using a non-standard compliant browser. To see it in its best form, please visit upgrade to a browser that supports web standards. It's free and painless.

Fillano's Learning Notes 會員登入 會員註冊

原文出處:Javascript Closures,作者是Richard Cornford。

這篇文章從Javascript非常核心的概念:Identifier resolution、scope and scope chain以及execution context講起,言簡意賅地把ECMA-262標準中這些核心的觀念解釋了一遍,接著提到什麼是closure(在函數中定義函數),closure可以做什麼、何時會意外做出closure,以及記憶體洩漏(memory leak)的議題。

了解closure,可以利用他的特性來解決問題(例如做出private function),也可以避免因為意外使用而使Javascript因為Garbage Collection無法作用而導致浪費大量記憶體,或是記憶體泄露的問題。

原文出處:Classical Inheritance in JavaScript,作者是Douglas Crockford。

他的結論是,基於Javascript語言的高度彈性,它的繼承與傳統的類別式繼承不同,所以必須用不同的思維來使用這個語言。主要的方向是避免深度的繼承,而用彈性的方法解決類別式繼承必須透過子類別才能解決的問題。例如物件的函數需要調整,簡單地透過assign一個新的函數給它就可以了。

作者在文中提到五種繼承的pattern:Classical Inheritance, Multiple Inheritance, Parasitic Inheritance, Class Augmentation, Object Augmentation。並且透過在Function物件增加幾個簡單的函數,就可以實作以上幾種繼承模式。這些方法很有用,舉例來說,我在google feed api裡面就有看到用了他的方法。

最近在嘗試用observer來自定網頁程式的內部事件/訊息機制,為了方便,打算讓每個函數物件在產生的時候有一個屬性是唯一的key。這樣在observable中加入/移除observer時可以用這個key檔做索引,比較方便。結果在用建構子的時候發生了問題....

在Javascript中,要做出繼承的效果,根據"Core Javascript Guide"裡面"Details of the Object Model"這一節的介紹,是用以下的方法:

1
2
3
4
5
6
7
8
function Parent () {
	code....;
}
function Child () {
	code....;
}
Child.prototype = new Parent;
var a = new Child();

我原先想要做的Observer像這樣:

1
2
3
4
5
6
7
8
function Observer () {
	this.stat = null;
	this.uid = (new Date()).getTime().toString() 
		+ (Math.random()*100000000).toString();
	this.update = function (stat) {
		this.stat = stat;
	}
}

這樣在用var a = new Observer();時並不會出問題,constructor會在產生a時給a.uid一個不容易重複的key。但是如果要繼承,就會出問題:

1
2
3
4
5
6
7
8
function EventListener () {
	this.update = function (stat) {
		this.stat = stat;
		alert("eventlistener"+stat);
	}
}
EventListener.prototype = new Observer;
var a = new EventListener();

關鍵在於,uid在EventListener.prototype = new Observer時候就產生了!所以每個EventListener的instance都有一個固定的uid。要怎麼解決這個問題呢?最初的想法,是在constructor傳入uid作為參數,但是要每次new的時候都打這一串產生uid的方法,感覺很麻煩。所以,我想到簡單的解法是再加一個工廠函數來產生對應的instance:

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
function Observer (uid) {
	this.stat = null;
	this.uid = uid;
	this.update = function (stat) {
		this.stat = stat;
	}
}
function ObserverFactory (objname) {
	try {
		return eval("new " + objname + "('" 
			+ (new Date()).getTime().toString() 
			+ (Math.random()*100000000).toString() + "')");
	} catch (e) {
		alert(e);
	}
}
function EventListener (uid) {
	this.uid = uid;
	this.update = function (stat) {
		this.stat = stat;
		alert("eventlistener"+stat);
	}
}
EventListener.prototype = new Observer;
var a = ObserverFactory("EventListener");

這樣是可以解決問題,但是真的需要這麼複雜嗎?回頭翻了一下reference,每個物件都有一個constructor屬性,指向自己的constructor。檢查一下a.constructor,會發現透過prototype的方式做出繼承效果,instance物件的constructor都會指向最上層的constructor。例如EventListener繼承Observer,而PhaseListener繼承EventListener,那PhaseListener的instance,constructor屬性就會指向Observer()!所以只要在本身的function definition呼叫this.constructor()就會呼叫到上層的constructor。程式修改一下就便得很簡潔了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Observer () {
	this.stat = null;
	this.uid = (new Date()).getTime().toString() 
		+ (Math.random()*100000000).toString();
	this.update = function (stat) {
		this.stat = stat;
	}
}
function EventListener () {
	this.constructor();
	this.update = function (stat) {
		this.stat = stat;
		alert("eventlistener"+stat);
	}
}
EventListener.prototype = new Observer;
var a = new EventListener();

嗯嗯,沒弄清楚的原因,是自己在還沒熟悉ECMA-262之前做了一些假設,實際上javascript的建構子運作跟我們習慣的c、java等是不太一樣的,要執行父代的建構子,必須明確執行。另外Javascript的assign非常有彈性,隨時assign隨時參考的物件、屬性、函數等就改了。在最後的程式碼的EventListener裡面呼叫this.constructor(),就會執行function Observer()裡面的程式,在上面的例子裡面是使this.stat=null,然後將this.uid設為一個不易重複的字串,然後assign一個匿名函數給this.update。呼叫完this.constructor()以後,會再重新assign另一個在function EventListener中定義的匿名函數給this.update。運作的模式大概就是像這樣。如果不呼叫this.constructor(),在之前EventListener.prototype = new Observer時其實就呼叫一次了,但是這會使所有EventListener的instance的uid固定不變,再呼叫一次this.constructor()才會正確設定好uid。這樣看起來,需要的話,最好在函數(EventListener)一開始就呼叫this.constructor(),如果在定義一些屬性或函數之後再呼叫this.constructor(),就有可能把之前定義的東西覆蓋過去了(如果名稱相同的話)。換句話說,就是把覆載的屬性/函數用父代的覆蓋過去了,這樣覆載就不會產生效果了。

為了簡化問題,另外做了一個實驗:

1
2
3
4
5
6
7
8
9
function base () {
	alert("type of base");
	this.type = "base";
}
function derived () {
	alert("type of derived");
	this.type = "derived";
}
derived.prototype = new base;

只執行以上的程式,就會跳出"type of base"訊息對話框。如果再加上一行:

1
var a = new derived();

會依序跳出"type of base"以及"type of derived"兩個對話框,很明顯地,base()只在derived.prototype=new base()時執行過一次,如果只是單純地assign一些屬性或函數,這樣做是沒問題的,但是如果是希望一些屬性在new的時候動態被父代的constructor決定,就行不通了。但是透過prototype的動作,a的constructor屬性已經被assign成base(),所以只要在derived()裡面呼叫this.constructor(),就可以達到動態使用父代的constructor來決定屬性值的效果。

同事跟我介绍的。這是host在Yahoo!! Developer Network上的計畫,是一個checklist base的評分工具,他會根據內建的check list來測試網站的效能(都是performance tuning的調整項目),做出綜合評分。

YSlow計畫網站:http://developer.yahoo.com/yslow/

這個工具是firefox的add-on,所以只能在firefox上面執行。一些測試的實作則仰賴另外一個著名的firefox除錯工具:firebug。所以必須安裝啟動firebug以後,才能執行。

他有幾個視景,第一個是performance:

yslow testing

在這個畫面可以看到他的check list以及評分結果。

另外一個視景是stats:

yslow testing

這個畫面會顯示一些狀態的資訊。主要是需要多少request以及需要傳輸資料的大小等等。

還有一個視景是components:

yslow testing

這個畫面可以看到網頁裡面用到的元件資訊,包含網址、大小、編碼方式、傳輸時間等等。

YSlow功能精簡,不過他提出的check list以及測試結果,當作網站performance tuning的參考還蠻適合的,有許多performance issues很多人大概也不太熟悉吧。

恩,純動腦筋,好玩用的。之前做過產生直線與多邊形的方法,但是畫圓用的方法比較不同,所以試一下。

function circle (x,y,r) {
	var points = [];
	for (var i = 0; i> (-Math.round(r/Math.sqrt(2))-1); i--) {
		j = Math.sqrt(Math.pow(r,2) - Math.pow(i,2));
		if (Math.ceil(j)-j > j-Math.floor(j)) {
			j = Math.floor(j);
		} else {
			j = Math.ceil(j);
		}
		points.push({"x":i+x,"y":j+y});
		points.push({"x":i+x,"y":y-j});
		points.push({"x":x-i,"y":j+y});
		points.push({"x":x-i,"y":y-j});
		points.push({"x":j+x,"y":i+y});
		points.push({"x":j+x,"y":y-i});
		points.push({"x":x-j,"y":i+y});
		points.push({"x":x-j,"y":y-i});
	}
	return points;
}

上面這個函數會根據座標與半徑,傳回圓周的點集合。再用下面這個函數來畫:

function plot (x, y, target) {
	var div=document.createElement("div");
	div.style.position = "absolute";
	div.style.height = "1px";
	div.style.width = "1px";
	div.style.left = x + "px";
	div.style.top = y + "px";
	div.style.fontSize = "1px";
	div.innerHTML = " ";
	div.style.padding = "0px";
	div.style.margin = "0px";
	div.style.background = "black";
	div.style.clip = "rect(0,1,1,0)";
	target.appendChild(div);
}

最後測試一下:

var a = circle(120, 120, 100);
alert(a.length);
for (var m=0; m<a.length; m++) {
	plot(a[m].x,a[m].y,document.body);
}

這樣就會畫出一個以座標(120,120)為圓心,半徑為100的圓。像下面這張圖:

test107

繪圖原理?簡單地說,圓周上每一個點(x,y)會符合x^2+y^2=r^2的條件。過來的問題是如何組合。我是從最下面一點開始,每次x值減一來求出y值,因為斜率剛好,所以線會連在一起,弧線的長度不超過圓周的八分之一(因為超過的話,線就不會連在一起),最後再往八個方向做mirror,這樣就構成整個圓了。(不過因為計算的關係,也許陣列裡面會有少許重複的點)