Brief
有時我們需要根據入參的數據類型來決定調用哪個函數實現,就是說所謂的函數重載(function overloading)。因為JS沒有內置函數重載的特性,正好給機會我們思考和實現一套這樣的機制。
使用方式:
function foo(){ return dispatch(this, arguments) } foo["object,number"] = function(o, n){console.log(o.toString() + ":" + n)} foo["string"] = function(s){console.log(s)} foo["array"] = function(a){console.log(a[0])}
機制實現:
;(function(e/*xports*/){ e.dispatch = function(thisValue, args){ var rSignature = getSignature(args) for (var p in args.callee){ if (rSignature.test(p)){ return args.callee[p].apply(thisValue, args) } } } function getSignature(args){ var arg, types = [] for (var i = 0, len = args.length; i < len; ++i){ arg = args[i] types.push(type(arg)) } var rTypes = "^\\s*" + types.join("\\s*,\\s*") + "\\s*$" return RegExp(rTypes) } function type(val){ // TODO } }(window))
那現在問題就落在type函數的實現上了!
關于獲取變量的數據類型有typeof、Object.prototype.toString.call和obj.constructor.name三種方式,下面我們一起來了解一下!
typeof Operator
語法: typeof val
內部邏輯:
function typeof(val){ var ret = Type(val) if (ret === "Reference" && IsUnresolvableReference(val)) return "undefined" ret = GetValue(val) ret = Type(ret) return ret }
Type(val)抽象操作的邏輯:
Undefined -> "undefined"
Null -> "object"
Boolean -> "boolean"
Number -> "number"
String -> "string"
Objecjt,若對象為native object并且沒有[[Call]]內置方法,則返回"object"
若對象為native object或host object且有[[Call]]內置方法,則返回"function"
若對象為host object并且沒有[[Call]]內置方法,則返回除"undefined"、"boolean"、"number"和"string"外的數據類型字符串。
native object,就是Math、{foo:1}、[]、new Object()和RegExp等JS規范中定義的對象,其中Math、RegExp等程序運行時立即被初始化的對象被稱為built-in object。
host object,就是宿主環境提供的對象,如瀏覽器的window和nodejs的global。
從上述Type(val)抽象操作的邏輯得知:
1. typeof能清晰區分Boolean、Number、String和Function的數據類型;
2. 對于未聲明和變量值為Undefined的變量無法區分,但對未聲明的變量執行typeof操作不會報異常;
3. typeof對Null、數組和對象是無能的。
針對2、3點我們可以求助于 Object.prototype.toString.call(val) 。
Object.prototype.toString.call(val)
Object.prototype.toString.call(val)或({}).toString.call(val)均是獲取val的內置屬性[[Class]]屬性值,并經過加工后返回。
內部邏輯:
function Object.prototype.toString(){ if (this === undefined) return "[object Undefined]" if (this === null) return "[object Null]" var o = ToObject(this) var clazz = o.[[Class]] return "[object " + clazz + "]" }
注意:1. 由于內部硬編碼null返回"[object Null]",因此雖然null本應不屬于Object類型,但JS中我們依然將其當作Object來使用(歷史+避免破壞已有庫的兼容性,導致后來無法修正該錯誤了);
2. 即使入參為primitive value,但內部還是會對其進行裝箱操作(通過ToObject抽象操作)。
那現在我們就需要了解一下[[Class]]內部屬性了。
內部屬性[[Class]]
在構造對象時會根據對象的類型設置[[Class]]的值,而其值類型為字符串。對于native object而言,其值范圍是:Arguments
Array、Boolean、Date、Error、Function、JSON、Math、Number、Object、RegExp、String。對于host object而言,則用HTMLElement、HTMLDocument等了。
注意:[[Class]]是用于內部區分不同類型的對象。也就是僅支持JS語言規范和宿主環境提供的對象類型而已,而自定義的對象類型是無法存儲在[[Class]]中。
function Foo(){} var foo = new Foo() console.log(({}).toString.call(foo)) // 顯示[object Object]
于是我們需要求助于constructor.name屬性了。
obj.constructor.name
function Foo(){} var foo = new Foo() console.log(foo.constructor.name) // 顯示Foo
那如果采用匿名函數表達式的方式定義構造函數呢?只能說直接沒轍,要不在構造函數上添加個函數屬性來保存(如Foo.className="Foo"),要不自己構建一個類工廠搞定。
Implementaion of type function
綜上所述得到如下實現:
/* * 獲取對象的數據類型 * @method type * @param {Any} object - 獲取數據類型的對象 * @param {Function} [getClass] - 用戶自定義獲取數據類型的方法 * @returns {String} 數據類型名稱 */ function type(o/*bject*/, g/*etClass*/){ var t = typeof o if ("object" !== t) return t.replace(/^[a-z]/, function(l){return l.toUpperCase()}) var rType = /\s*\[\s*object\s*([0-9a-z]+)\s*\]\s*/i t = ({}).toString.call(o) t = t.match(rType)[1] if ("Object" !== t) return t t = o.constructor.name if (!t && arguments.callee(g) === "Function"){ t = g(o) } t = t || "Object" return t }
Consolusion
尊重原創,轉載請注明來自:http://www.cnblogs.com/fsjohnhuang/p/5156912.html^_^肥子John
Thanks
http://segmentfault.com/q/1010000000669230
http://es5.github.io/#x15.2.4.2
文章列表