js函數有一個非凡的特性,即將其源代碼重現為字元串的能力。 反射獲取函數源代碼的功能很強大,使用函數對象的toString方法有嚴重的局限性。toString方法的局限性ECMAScript標準對函數對象的toString方法的返回結果(即該字元串)並沒有任何要求。這意味著不同的js引擎將產生不同的 ...
js函數有一個非凡的特性,即將其源代碼重現為字元串的能力。
(function(x){ return x+1 }).toString();//"function (x){ return x+1}"
反射獲取函數源代碼的功能很強大,使用函數對象的toString方法有嚴重的局限性。
toString方法的局限性
ECMAScript標準對函數對象的toString方法的返回結果(即該字元串)並沒有任何要求。這意味著不同的js引擎將產生不同的字元串,甚至產生的字元串與該函數並不相關。
如果函數是使用純js實現的,那麼js引擎會試圖提供該函數的源代碼的真實表示。
一個失敗的例子
(function(x){ return x+1 }).bind(16).toString();//"function () { [native code] }"
失敗原因:
使用了由宿主環境的內置庫提供的函數。
-
由於許多宿主環境中,bind函數是由其他編程語言實現的(通常是c++)。宿主環境提供的是一個編譯後的函數,在此環境下該函數沒有js的源代碼供顯示。
-
由於標準允許瀏覽器引擎改變toString方法的輸出,很容易使編寫的程式一個js系統中正確運行,在其他js系統中卻無法正確運行。程式對函數的源代碼字元串的具體細節很敏感,即使js的實現有一點細微的變化都可能破壞程式。
-
由toString方法生成的源代碼並不展示閉包中保存的與內部變數引用相關的值
(function(x){ return function(y){ return x+y; } })(42).toString();//"function (y){ return x+y; }"
註意:儘管函數實際上是一個綁定x為42的閉包,但結果字元串仍然包含一個引用x的變數。
從某種意義上說,js的toString方法的這些局限使其用來提取函數源代碼並不是特別有用和值得信賴。通常應該避免使用它。對提取函數源代碼相當複雜的使用應當採用精心製作的js解釋器和處理庫。將js函數看作是一個不該違背的抽象是最穩妥的。
提示
-
當調用函數的toString方法時,並沒有要求js引擎能夠精確地獲取到函數的源代碼
-
由於在不同的引擎下調用toString方法的結果可能不同,所以絕不要信賴函數源代碼的詳細細節
-
toString方法的執行結果並不會暴露存儲在閉包中的局部變數
-
通常情況下,應該避免使用函數對象的toString方法
附錄一:toString方法
不同數據類型調用toString方法的結果。
toString方法是Object原型對象中的一個方法,所以繼承自這個類的對象都會繼承這個方法,並可以對toString方法進行覆蓋。
js標準庫中的5種簡單數據類型:Undefined,Null,Boolean,Number和String。還有一種複雜的數據類型Object,Object的本質是一組無序的名值對組成。
簡單數據類型
//數字 (Undefined).toString();//"error" (Null).toString();//error (true).toString();//"true" (1).toString();//"1" ('111').toString();//"111"
可以看出其中除了Undefined和Null類型外,為什麼其它幾個基本類型可以運行呢。
這在我們之前的文章《[Effective JavaScript 筆記] 第4條:原始類型優於封閉對象》中講到,當簡單數據類型調用toString方法會首先把原始類型轉換成包裝對象。
此時對應的包裝對象為
-
數字為Number對象
-
布爾值為Boolean對象
-
字元串為String對象
這些對象也都是繼承自Object對象的,並重寫了各自的toString方法。
但Undefined類型和Null類型都只有一個值undefined,null,並沒有對應的封裝對象。
雖然typeof null的值是"object",但並沒用。
引用類型
//Object對象 ({a:10,b:20}).toString();//"[object Object]"//Date對象 (new Date).toString();//"Tue Jun 07 2016 15:37:15 GMT+0800 (中國標準時間)"//RegExp對象 (/^sss$/g).toString();//"/^sss$/g"//Function對象function aa(){return "bb"} aa.toString();//"function aa(){return "bb"}"//window對象window.toString();//"[object Window]"//Math對象Math.toString();//"[object Math]"
看到上面的toString方法,Object,window,Math是使用Object原型方法。其它對象都使用了自身覆蓋的toString方法。
typeof操作符
對以上所有類型使用typeof操作符時會得到以下的結果
typeof 1;//"number"typeof '1';//"string"typeof true;//"boolean"typeof undefined;//"undefined"typeof (function a(){});//"function"typeof null;//"object"typeof {};//"object"typeof (new Date);//"object"typeof [];//"object"typeof window;//"object"typeof Math;//"object"typeof (/sdfsf/g);//"object"
可以看出,想使用單單的typeof操作符來對類型進行判斷幾乎是不可能的。
有人可能會說對於返回object字元串,可以使用構造函數來判斷類型即instanceOf方法。
({}) instanceof Object;//true (new Date) instanceof Date;//true ([]) instanceof Array;//true (/sdfsf/g) instanceof RegExp;//true
然後null類型只要
var a=null; a===null;//true;
好像可以實現下麵這樣的類型判斷代碼了
function getType(obj){ if(typeof obj !== 'object'){ return typeof obj; }else{ if(obj===null){ return 'null'; } if(obj===window){ return 'window'; } if(obj===Math){ return 'Math' } if((obj) instanceof Date){ return 'date'; } if((obj) instanceof Array){ return 'array'; } if((obj) instanceof RegExp){ return 'regexp'; } if((obj) instanceof Object){ return 'object'; } } }
上面代碼是否可以運行測試一下,並沒有問題
getType(1);//"number" getType(true);//"boolean" getType('1');//"string" getType(undefined);//"undefined" getType(function(){});//"function" getType(/sf/);//"regexp" getType(null);//"null" getType(window);//"window" getType({});//"object" getType([]);//"array"
但這裡要註意的一個問題就是,這個代碼里的對於object類型的檢測一定要放到最後面。
如下所示,所有對象都是繼承自Object,所以instanceof檢測所有對象是否為Object類型的實例返回都是true
([]) instanceof Array;//true ([]) instanceof Object;//true
看到以上代碼是不是覺得太複雜麻煩了,有沒有一種更簡單的方法來對類型進行判斷呢?答案當然是有,下麵來看toString方法的運用。
toString應用
如上面所說,繼承自Object的對象都有toString方法,但每個對象實現了各自的toString方法,導致無法用toString方法進行類型判斷。這裡可以利用之前講到過的call或apply方法來調用Object.prototype.toString方法。
function getType(obj){ var toString=Object.prototype.toString; return toString.call(obj); }
測試一下各類型會得到如下結果
getType(1);//"[object Number]" getType(true);//"[object Boolean]" getType('1');//"[object String]" getType(undefined);//"[object Undefined]" getType(function(){});//"[object Function]" getType(/sf/);//"[object RegExp]" getType(null);//"[object Null]" getType(window);//"[object Window]" getType({});//"[object Object]" getType([]);//"[object Array]" getType(Math);//"[object Math]"
所有類型都可以區分出來,是不是很簡單呀?這個是不是就可以萬事大吉了呢,錯,還有個特殊的值沒有處理NaN.
getType(NaN);//"[object Number]"
NaN並不是一個Number類型的數,它是表達不是一個數字的值,這裡對這個值也要進行處理。可以關註之前文章《
》里關於NaN的內容。處理代碼如下
function isReallyNaN(x){ return x!==x; }
完整的版本
function getType(obj){ if(obj!==obj)return "NaN"; var toString=Object.prototype.toString; return toString.call(obj); }
getType(NaN);//"NaN"
備忘:
這裡需要去瞭解一下,js解釋器的知識。
相關的鏈接有:
javascript設計模式之解釋器模式詳解
javascript設計模式 - 解釋器模式(interpreter)
Chrome V8