對於Javascript所有型別在Closure中表現的測試
Mon, 15 Dec 2008 22:47:56 +0800今天有一篇迴響講到我在「搞清楚lexical scope與closure」舉的例子有問題,我檢查了一下,的確有問題,不過在測試時發現好玩的現象,乾脆對所有型別做一些測試。
所作的測試包含使用Literal產生物件、使用Constructor產生物件、針對陣列元素、針對Date物件時間修改等做測試。程式如下:
var alterObj = function(v) { v.a = "test altered."; }; var alterArr = function(v) { v[0] = "test altered"; } var alterDat = function(v) { v.setTime(v.getTime()+86400000); } var closureObj = function(v) { return function() { return v; } }; var testObjectLiteral = function(){ var str = {}; alert(typeof str); str.a = "test02"; alert(str); alert(str.a); alterObj(str); alert(str); alert(str.a); var f = closureObj(str); str = null; alert(f()); alert(f().a); }; var testFunctionLiteral = function(){ var str = function(){}; alert(typeof str); str.a = "test02"; alert(str); alert(str.a); alterObj(str); alert(str); alert(str.a); var f = closureObj(str); str = null; alert(f()); alert(f().a); }; var testArrayLiteral = function(){ var str = ["test01"]; alert(typeof str); str.a = "test02"; alert(str); alert(str.a); alterObj(str); alert(str); alert(str.a); var f = closureObj(str); str = null; alert(f()); alert(f().a); }; var testArrayLiteralMember = function(){ var str = ["test3"]; alert(typeof str); str[0] = "test02"; alert(str); alert(str[0]); alterArr(str); alert(str); alert(str[0]); var f = closureObj(str); str = null; alert(f()); alert(f()[0]); } var testStringLiteral = function(){ var str = "test01"; alert(typeof str); str.a = "test02"; alert(str); alert(str.a); alterObj(str); alert(str); alert(str.a); var f = closureObj(str); str = null; alert(f()); alert(f().a); }; var testNumberLiteral = function(){ var str = 3; alert(typeof str); str.a = "test02"; alert(str); alert(str.a); alterObj(str); alert(str); alert(str.a); var f = closureObj(str); str = null; alert(f()); alert(f().a); }; var testRegExpLiteral = function(){ var str = /[a-z]/gi; alert(typeof str); str.a = "test02"; alert(str); alert(str.a); alterObj(str); alert(str); alert(str.a); var f = closureObj(str); str = null; alert(f()); alert(f().a); }; var testBooleanLiteral = function(){ var str = false; alert(typeof str); str.a = "test02"; alert(str); alert(str.a); alterObj(str); alert(str); alert(str.a); var f = closureObj(str); str = null; alert(f()); alert(f().a); }; var testObjectObject = function(){ var str = new Object(); alert(typeof str); str.a = "test02"; alert(str); alert(str.a); alterObj(str); alert(str); alert(str.a); var f = closureObj(str); str = null; alert(f()); alert(f().a); }; var testFunctionObject = function(){ var str = new Function(""); alert(typeof str); str.a = "test02"; alert(str); alert(str.a); alterObj(str); alert(str); alert(str.a); var f = closureObj(str); str = null; alert(f()); alert(f().a); }; var testArrayObject = function(){ var str = new Array("test01"); alert(typeof str); str.a = "test02"; alert(str); alert(str.a); alterObj(str); alert(str); alert(str.a); var f = closureObj(str); str = null; alert(f()); alert(f().a); }; var testArrayObjectMember = function(){ var str = new Array("test01"); alert(typeof str); str[0] = "test02"; alert(str); alert(str[0]); alterArr(str); alert(str); alert(str[0]); var f = closureObj(str); str = null; alert(f()); alert(f()[0]); } var testStringObject = function(){ var str = new String("test01"); alert(typeof str); str.a = "test02"; alert(str); alert(str.a); alterObj(str); alert(str); alert(str.a); var f = closureObj(str); str = null; alert(f()); alert(f().a); }; var testBooleanObject = function(){ var str = new Boolean("test string object"); alert(typeof str); str.a = "test02"; alert(str); alert(str.a); alterObj(str); alert(str); alert(str.a); var f = closureObj(str); str = null; alert(f()); alert(f().a); }; var testNumberObject = function(){ var str = new Number(3); alert(typeof str); str.a = "test02"; alert(str); alert(str.a); alterObj(str); alert(str); alert(str.a); var f = closureObj(str); str = null; alert(f()); alert(f().a); }; var testDateObject = function(){ var str = new Date(); alert(typeof str); str.a = "test02"; alert(str); alert(str.a); alterObj(str); alert(str); alert(str.a); var f = closureObj(str); str = null; alert(f()); alert(f().a); }; var testDateObjectAlter = function(){ var str = new Date(); alert(typeof str); str.a = "test02"; alert(str); alert(str.a); alterObj(str); alterDat(str); alert(str) alert(str.a); var f = closureObj(str); str = null; alert(f()); alert(f().a); }; var testRegExpObject = function(){ var str = new RegExp(); alert(typeof str); str.a = "test02"; alert(str); alert(str.a); alterObj(str); alert(str); alert(str.a); var f = closureObj(str); str = null; alert(f()); alert(f().a); }; //testObjectLiteral(); //testFunctionLiteral(); //testArrayLiteral(); //testArrayLiteralMember(); //testStringLiteral(); //testNumberLiteral(); //testRegExpLiteral(); //testBooleanLiteral(); //testObjectObject(); //testFunctionObject(); //testArrayObject(); //testArrayObjectMember(); //testStringObject(); //testBooleanObject(); //testNumberObject(); //testDateObject(); //testDateObjectAlter(); //testRegExpObject();
alterObj函數用來將傳入物件的a屬性做修改,alterArr函數用來將傳入陣列的元素做修改,alterDat函數則用來修改傳入Date物件的日期。closureObj函數則用傳入參數來產生一個closure,並且返回一個函數來將local變數攜出,並且可以展示傳入參數被「綁住」在裡面的狀況。前面三個函數可以展現,傳入的參數基本上都是一個物件的Reference。這個結論對於closureObj函數也是一樣的。
Date以及Array可以透過內建方法來改變自己,所以另外透過testDateObjectAlter函數以及testArrayObjectMember、testArrayLiteralMember這兩個函數來測試,其他的物件基本上都是透過額外給他一個a屬性來做測試,基本上都可以表現出closure的效果。
比較特別的是String、Number與Boolean Literal,產生的變數無法再assign一個額外的a屬性,但是透過String、Number與Boolean Constructor卻可以。
下面一個是測試結果的表(我是在Firefox做的測試,IE跑出來的東西可能會不一樣,因為toString()的實作可能不一致。另外測試Date顯示的日期會因為執行的日期/時間而改變(初始時會抓current time)。):
測試函數 | typeof結果 | 呼叫改變str變數的函數 | 改變前的str | 改變前的str.a | 改變後的str | 改變後的str.a | Closure回傳的str | closure回傳的str.a |
testObjectLiteral(); | object | alterObj() | [object Object] | test02 | [object Object] | test altered | [object Object] | test altered |
testFunctionLiteral(); | function | alterObj() | function(){} | test02 | function(){} | test altered | function(){} | test altered |
testArrayLiteral(); | object | alterObj() | test01 | test02 | test01 | test altered | test01 | test altered |
testArrayLiteralMember(); | object | alterArr() | test02 | test02 | test altered | test altered | test altered | test altered |
testStringLiteral(); | string | alterObj() | test01 | undefined | test01 | undefined | test01 | undefined |
testNumberLiteral(); | number | alterObj() | 3 | undefined | 3 | undefined | 3 | undefined |
testRegExpLiteral(); | object | alterObj() | /[a-z]/gi | test02 | /[a-z]/gi | test altered | /[a-z]/gi | test altered |
testBooleanLiteral(); | boolean | alterObj() | false | undefined | false | undefined | false | undefined |
testObjectObject(); | object | alterObj() | [object Object] | test02 | [object Object] | test altered | [object Object] | test altered |
testFunctionObject(); | function | alterObj() | function anonymous(){} | test02 | function anonymous(){} | test altered | function anonymous(){} | test altered |
testArrayObject(); | object | alterObj() | test01 | test02 | test01 | test altered | test01 | test altered |
testArrayObjectMember(); | object | alterArr() | test02 | test02 | test altered | test altered | test altered | test altered |
testStringObject(); | object | alterObj() | test01 | test02 | test01 | test altered | test01 | test altered |
testBooleanObject(); | object | alterObj() | true | test02 | true | test altered | true | test altered |
testNumberObject(); | object | alterObj() | 3 | test02 | 3 | test altered | 3 | test altered |
testDateObject(); | object | alterObj() | Tue Dec 16 2008 00:00:24:04 GMT +800 | test02 | Tue Dec 16 2008 00:00:24:04 GMT +800 | test altered | Tue Dec 16 2008 00:00:24:04 GMT +800 | test altered |
testDateObjectAlter(); | object | alterDat() | Tue Dec 16 2008 00:00:24:04 GMT +800 | test02 | Tue Dec 17 2008 00:00:24:04 GMT +800 | test altered | Tue Dec 17 2008 00:00:24:04 GMT +800 | test altered |
testRegExpObject(); | object | alterObj() | /(?:)/ | test02 | /(?:)/ | test altered | /(?:)/ | test altered |
最後的一點小小感想:我前面文章的補充還是不太對,closure對所有型別都有效果。另外要說......ECMA-262 Edition 3的設計真的不太一致......