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 會員登入 會員註冊

這一支程式是從http://www.json.org/json.js網站來的。

json.js為array、object、boolean、number、string物件提供了toJSONString方法,可以把這些物件轉成JSON格式的字串。另外為string物件提供了parseJSON方法,讓使用者可以將JSON字串轉換成陣列或物件。

除了將JSON轉換成物件或陣列外,parseJSON方法還會做一些檢查,碰到不合法的JSON字串就會拋出例外。例如字串中如果有函數運算子,就會被檢查出來。parseJSON方法還可用傳入function reference的方式來加入filter,做一些額外的檢查。這些就是json.js全部的功能。使用json.js有一些方便的地方,除了方便做字串的轉換,最重要的還是檢查字串是否符合json的標準,減少受到xss攻擊的一些風險。

據說toJSONString跟parseJSON有可能成為ECMAScript第四版的標準,json.js是一個參考的實作。(參考http://www.json.org/json.js中的說明)

恩,不多說了,先來試試吧。我想試驗用xmlhttp取得js檔案,然後交給parseJSON函數處理。所以先用script標籤引入json.js,然後用幾個方法測試:

function xmlhttp() 
{
	try{return new ActiveXObject("Msxml2.XMLHTTP");} catch(e){}
	try{return new ActiveXObject("Microsoft.XMLHTTP");} catch(e){}
	try{return new XMLHttpRequest();} catch(e){}
	alert("XMLHttpRequest Object not existed!!");
	return null;
}
function useScript (name) {
	try {
		var loader = xmlhttp();
		loader.open('GET', name, false);
		loader.send(null);
		var str = loader.responseText.toString();
		return str.parseJSON();
	} catch (e) {
		alert(e);
	} finally {
		loader = null;
	}
}

function test1 (name) {
	try {
		var nav = useScript(name);
		var str = "";
		for (var i in nav) {
			str += i + " : " + nav[i] + "n";
		}
		if (str) {
			alert(str);
		}
	} catch (e) {
		alert(e);
	}
}

xmlhttp這個函數會根據用的是ie或是firefox/mozilla傳回一個xmlhttp/xmlhttprequest物件。useScript函數會根據傳入的參數用xmlhttp的方法來取得js檔案,並傳回用parseJSON轉換出來的物件。test1函數會把要處理的js檔名傳給useScript函數以獲得物件/陣列,然後用for in來probe他的成員。

這裡是一個例子:http://www.fillano.idv.tw/test49.html

兩支測試的js分別為:

{
	"inner": 3,
	"outer": "test"
}
(test49-1.js)

以及

{
	"inner": 3,
	"outer": function () {alert('test');}
}
(test49-2.js)

decorator在動態地賦予權責上相當好用,所以想試試看用javascript可不可以做出來類似的效果:)

嘗試的目標:可以用new運算子建立各物件的權責關係,然後用共同的的方法(各物件實作上有不一樣,但是有一致的傳入參數)很簡單就可以達成效果。

試作的功能:依照事件改變物件屬性。

接著就開始了:

var DownEventAttacher = function (obj) {
	this._obj = obj;
	this.setEvent = function (func, dom) {
		dom.onmousedown = func;
		if (typeof this._obj == "object") {
			this._obj.setEvent(func, dom);
		}
	}
}

var UpEventAttacher = function (obj) {
	this._obj = obj;
	this.setEvent = function (func, dom) {
		dom.onmouseup = func;
		if (typeof this._obj == "object") {
			this._obj.setEvent(func, dom);
		}
	}
}

var OutEventAttacher = function (obj) {
	this._obj = obj;
	this.setEvent = function (func, dom) {
		dom.onmouseout = func;
		if (typeof this._obj == "object") {
			this._obj.setEvent(func, dom);
		}
	}
}

var OverEventAttacher = function (obj) {
	this._obj = obj;
	this.setEvent = function (func, dom) {
		dom.onmouseover = func;
		if (typeof this._obj == "object") {
			this._obj.setEvent(func, dom);
		}
	}
}

關鍵在於,函數的constructor會把傳入的參數存起來(這會是一個new出來的物件),然後在呼叫方法時除了自己本身要處理的code以外,還呼叫了傳入物件的同一個方法。

接著設計要測試的效果。我想處理div的背景會容易看到效果,所以設計了一個函數:

function testf (e) {
	if (e) {
		//firefox或mozilla處理事件的方法
		switch (e.type) {
			case "mouseover":
				e.target.style.backgroundColor = "green";
				break;
			case "mouseout":
				e.target.style.backgroundColor = "yellow";
				break;
			case "mousedown":
				e.target.style.backgroundColor = "black";
				break;
			case "mouseup":
				e.target.style.backgroundColor = "green";
				break;
		}
	} else {
		//ie處理事件的方法,目前只試了ie7
		switch (event.type) {
			case "mouseover":
				event.srcElement.style.backgroundColor = "green";
				break;
			case "mouseout":
				event.srcElement.style.backgroundColor = "yellow";
				break;
			case "mousedown":
				event.srcElement.style.backgroundColor = "black";
				break;
			case "mouseup":
				event.srcElement.style.backgroundColor = "green";
				break;
		}
	}
}

使用的時候,只要一連串地把需要加入權責的事件對應的再constructor加進來就可以。例如以下的例子是在一個div block上達成效果,div的id是test,背景顏色是yellow:

var test = document.getElementById("test");
var attacher = new OverEventAttacher(
    new OutEventAttacher(
        new DownEventAttacher(
            new UpEventAttacher()
)));
attacher.setEvent(testf, test);

這樣在滑鼠移入、移出、click都會有改變背景顏色的效果。

如果不需要在mousedown/mouseup事件讓div block改變背景顏色,只要調整一下:

var test = document.getElementById("test");
var attacher = new OverEventAttacher(
    new OutEventAttacher()
);
attacher.setEvent(testf, test);

這樣就只有在滑鼠移入移出會改變背景顏色。

有圖有真相:http://www.fillano.idv.tw/test48.html

今天看到新聞,google發表新的ajax api,叫做feed api,可以從其他網站讀取rss、atom資料。看起來不錯又容易上手,所以來試試看。

網址:http://code.google.com/apis/ajaxfeeds/

要使用這個api一定要去google申請,他會為申請使用的url產生一組key,必需要用這個key才能把他的api引用進來,之後在這個url及次目錄中均可使用。

申請完成以後,他會在網頁上顯示你申請網址產生的key,以及一個簡單的sample。只要利用這個sample code 就可以應用主要的功能了。以下是google產生的sample code:

先用申請的key來引用google ajax feed api:

<script type="text/javascript" src="http://www.google.com/jsapi?key=ABQIAAAA-ocbLlp5OH3I3TK-WDpoFhRoleihMAzPXqmwBMBn1VmZtQsreRRopkPGhtnsPBpu_epnuTbhzWq_Sw"></script>
(注意:以上範例裡的"<"、">"都是全形的,不能直接用)

接著是主要的程式(直接從google網站來,幾乎沒改):

    google.load("feeds", "1");
 
    function initialize() {
      var feed = new google.feeds.Feed("http://blog.fillano.idv.tw/rss.php?blogId=1&profile=rss20");
      feed.load(function(result) {
        if (!result.error) {
          var container = document.getElementById("feed");
          for (var i = 0; i < result.feed.entries.length; i++) {
            var entry = result.feed.entries[i];
            var div = document.createElement("div");
            div.appendChild(document.createTextNode(entry.title));
            container.appendChild(div);
          }
        }
      });
    }
    google.setOnLoadCallback(initialize);
(注意:以上範例裡的"<"、">"都是全形的,不能直接用)

網頁中有一個id為feed的div,結果就會在這裡顯示出來。但是目前只顯示出title而已,所以還需要再改造一下才能用。從sample code的程式看起來,東西都會放在entries陣列裡面,每個元素事一個物件,但是在sample code裡面除了title我不知道還有什麼property。所以先寫一個小程式來probe一下,先把for迴圈整個註解掉,改成以下的程式:

for (i in result.feed.entries[0]) {
    alert(i + ": " + result.feed.entries[0][i]);
}

這樣可以找出,透過lifetype的rss2.0,取回的物件包含幾個properties:title, link, author, publishedDate, contentSnippet, content, categories。

接著修改一下程式,讓他顯示更多資訊:

    google.load("feeds", "1");

    function initialize() {
      var feed = new google.feeds.Feed("http://blog.fillano.idv.tw/rss.php?blogId=1&profile=rss20");
      feed.load(function(result) {
        if (!result.error) {
          var container = document.getElementById("feed");
          for (var i = 0; i < result.feed.entries.length; i++) {
            var entry = result.feed.entries[i];
            var div = document.createElement("div");
            var title = document.createElement("p");
            var link = document.createElement("a");
            link.href = entry.link;
            link.appendChild(document.createTextNode(entry.title));
            link.target = "_blank";
            title.appendChild(link);
            title.appendChild(document.createTextNode(" | "+entry.categories+" | "+entry.author+" | "
+entry.publishedDate));
            hr = document.createElement("hr");
            hr.width = "100%";
            hr.size = "1";
            snippet = document.createElement("p");
            snippet.appendChild(document.createTextNode(entry.contentSnippet));
            div.appendChild(title);
            div.appendChild(hr);
            div.appendChild(snippet);
            container.appendChild(div);
          }
        }
      });
    }
    google.setOnLoadCallback(initialize);
(注意:以上範例裡的"<"、">"都是全形的,不能直接用)

最後利用css再加一點修飾就可以看了。

這是測試的結果:http://www.fillano.idv.tw/test46.html

在mozilla網站上面看arguments的資料時看到一些匿名函數遞迴的方法....

匿名函數常用在一些Javascript Framework (Lib) 上,或是直接assign一個函數參考給物件變數時(可以減少或避免使用變數來避開變數名稱衝突的問題)。

雖然不知道自己什麼時候會用到匿名函數遞迴......但是匿名函數要遞迴的時候,不能透過this來參考,只能透過arguments.callee。以下是一個例子(階乘,從mozilla網站抄來的)

var t = (function (x) {
    if (x <= 1) {//'<'符號是全形的,不然顯示不出來....
        return 1;
    } else {
        return x * arguments.callee(x-1);
    }
})(5);
alert(t);

這個函數是用遞迴的方式算出階乘,使用立刻執行的匿名函數。(函數前後的括號會把函數從定義改為敘述,後面的括號輸入參數傳給函數執行)我自己做了一個測試網頁,有興趣可以試試看效果。

另外舉一個以前做過的例子,計算給定元素的座標(相對於body):

var p = (function (o) {
    var currPos = {"x": o.offsetLeft, "y": o.offsetTop};
    var workPos = {"x": 0, "y": 0};
    if (o.offsetParent.tagName != "BODY" && o.offsetParent.tagName != "body") {
        workPos = arguments.callee(o.offsetParent);
        currPos.x += workPos.x;
        currPos.y += workPos.y;
    } else {
        return currPos;
    }
})(obj);

這個例子會算出傳入元素(obj)相對於body的座標,也就是在網頁上的座標。我同樣做了一個測試網頁,有興趣可以試試看效果(在IE跟Firefox中結果不同喔)。

不過argument.callee除了用在匿名函數的遞迴中,其他地方好像都不太用得到了?

我自己是在改良網頁Isometric引擎時碰到這樣的需求(動態載入地圖),最早是用連到不同網頁避開這個問題,最近看到別人嘗試的一些方法,所以自己再試試看。

這些方法有:

  1. 利用document.createElement("script")來產生script node,並加到Node Tree中。
  2. 利用Ajax取得.js檔案內容,並透過eval函數來執行內容。

經過嘗試以後,大概有了一些心得。

以下是對第一個方法的測試

主要的script:

var nav = null;

function addScript(name) {
	var obj = document.createElement("script");
	obj.src = name;
	var target = document.getElementsByTagName("HEAD")[0];
	target.appendChild(obj);
}

function test (name) {
	try {
		addScript(name);
		setTimeout("alert(nav.getInner())", 100);
	} catch (e) {
		alert(e);
	}
}

要載入的test1.js檔案:

var nav = {
"inner": 0,
"getInner": function () { return this.inner; }
};
(這樣不是合法的JSON,不過還是可以執行)

測試的網頁內容:

<input type="button" value="test" onclick="test('test1.js')">
(上面是用全形的<>符號,避免顯示成按鈕)

有興趣可以測試一下:第一個測試網頁連結

這個方法有幾個問題:

  1. 在firefox改變不會立刻生效,所以我用setTimeout來延緩測試
  2. 需要對node做適當的管理,否則會佔用不必要的系統資源

接下來使用ajax + eval的方法。

主要的script:

var nav=null;

if (window.execScript == null) {
    try {
        window.execScript = function(script) {
            eval(script,window);
        }
    } catch (e) { alert(e); }
}

function useScript (name) {
	try {
		var loader = xmlhttp();
		loader.open('GET', name, false);
		loader.send(null);
		window.execScript(loader.responseText.toString());
	} catch (e) {
		alert(e);
	}
}

function test1 (name) {
	try {
		useScript(name);
		alert(nav.getInner());
	} catch (e) {
		alert(e);
	}
}

function xmlhttp() 
{
	try{return new ActiveXObject("Msxml2.XMLHTTP");} catch(e){}
	try{return new ActiveXObject("Microsoft.XMLHTTP");} catch(e){}
	try{return new XMLHttpRequest();} catch(e){}
	alert("XMLHttpRequest Object not existed!!");
	return null;
}

要載入的test2.js檔案:

nav = {
"inner": 3,
"getInner": function () { return this.inner; }
};

測試的網頁內容:

<input type="button" value="test" onclick="test1('test2.js')">
(上面是用全形的<>符號,避免顯示成按鈕)

有興趣可以測試一下:第二個測試網頁連結

有幾個部份需要說明一下:

  1. 在ie上,eval沒辦法取用到global variables,所以使用window.execScript來代替,並且為firefox的window物件加上這個函數,讓兩個瀏覽器可以用同樣的方法操作。execScript的第二個參數可以省略,因為預設就是JavaScript,我這裡就偷懶不寫了。
  2. eval所執行的程式,會參考到執行eval時程式的上下文,所以特別給定第二個參數為window物件,讓他可以取用global variable。