本文首發於個人我的博客 : cherryblog.site/node
js 中的類型檢測也是很重要的一部分,因此說這篇文章咱們就來說一下怎麼對 JavaScript 中的基本數據類型進行檢測。其實這也是在讀 Zepto 源碼中學習到的,因此閱讀源碼對咱們的提高仍是頗有幫助的。本文基於參考了前輩們的文章以後我的理解此文寫的有不當的地方,請各位大佬指正。web
其實常規方法主要有四種segmentfault
typeof
instanceof
Object.prototype.toString
construcor
其實這四種方式歸根結底就是兩種思路:數組
再看 Zepto 以前看了 慕課網一個老師的視頻,一共一個小時左右,開了快進估計也就 45 分鐘左右。只是講了 Zepto 的架構和設計,沒有詳細的將每個方法,初看以前能夠看一下,對 Zepto 有一個大概的印象。瀏覽器
其實這部分真的是老生常談的問題,可是每一次聽其餘人都有新的收穫。真的是不想寫這部分,可是自我感受總體思路比較清晰,因此推薦你們閱讀一下。架構
Zepto 整個的設計思想實際上是基於 js 的原型鏈。關於原型鏈,這個老師講的比較清晰,須要記住三句話:app
__proto__
指向這個函數的 prototype。__proto__
中找。 每個函數在建立以後都會擁有一個名爲 prototype 的屬性,這個屬性指向函數的原型對象。經過Function.prototype.bind方法構造出來的函數是個例外,它沒有prototype屬性。函數
var fn = function() {}
console.log( fn.prototype );複製代碼
經過下面這幅圖咱們能夠看出建立一個函數時,都會有一個 prototype
屬性指向它的原型。而 fn.prototype
中有一個 constructor
屬性指向 fn 函數。
學習
__proto__
(隱式原型)JavaScript 中任意對象都有一個內置屬性 __proto__
,隱式原型指向建立這個對象的函數(constructor)的 prototype。測試
Object.prototype
這個對象是個例外,它的 __proto__
值爲 null
console.log( typeof Array ); // 'function'
console.log( Array.prototype );複製代碼
數組構造函數 Array
也是一個函數,而且在 Array 的原型中除了指向 Array 的 constructor 以外還有其餘的內置對象。
__proto__
的指向上面應該都不難理解,主要是 __proto__
的指向,這個問題是比較難理解的,咱們來看剛剛的定義,__proto__
指向建立這個對象的函數的顯式原型。建立函數一共有三種方式:
var person1 = {
name: 'cyl',
sex: 'male'
};複製代碼
字面量的方式是一種爲了開發人員更方便建立對象的一個語法糖,本質就是
var o = new Object();
o.xx = xx;
o.yy=yy;複製代碼
因此說使用字面量方式建立的函對象的 __proto__
屬性是指向 Object.prototype
的。
new
關鍵字調用的函數,只要是經過 new
關鍵字調用的函數都是構造函數。由構造函數構造的對象,其 __prototype__
指向其構造函數的 prototype 屬性指向的對象。var arr = new Array()複製代碼
好比 arr
是一個實例化的數組,那麼 arr 的 __proto__
屬性就指向 Array 的 prototype 屬性。
Object.create
構造的var person1 = {
name: 'cyl',
sex: 'male'
};
var person2 = Object.create(person1);複製代碼
Object.create
的內部實際上是這樣的:
function object(o){
function F(){}
F.prototype = o;
return new F()
}複製代碼
也能夠當作是經過 new
建立的。因此說咱們就能夠一目瞭然,person2 的 __proto__
是指向 person1 的。(注意,是直接指向 person1,而不是 person1.prototype
)。
prototype
和 __proto__
的做用在瞭解了什麼是顯示原型 prototype
和隱式原型 __proto__
以後,咱們也知道了怎麼去找隱式原型,那麼它們有什麼做用呢?
__proto__
依次查找。這裏咱們要注意了,當咱們訪問 obj 這個對象中的 x 屬性時,若是在 obj 中找不到,那麼就會沿着 __proto__
依次查找。
劃重點,是在 __proto__
中依次查找
__proto__
既然咱們知道了繼承其實是繼承對象 __proto__
上的屬性,那咱們就能夠改寫咱們的 __proto__
屬性。
var arr = [1,2,3];
arr.__proto__.addClass = function () {
console.log(123);
}
arr.push(4);
arr.addClass(); // 123複製代碼
修改了以後,arr
不只有內置的 concat
、push
等功能,還多了一個 addClass
功能。
也能夠徹底改寫 __proto__
屬性,那麼其原先的全部的功能都沒有了,以下圖所示。
是時候祭上這張圖了:
typeof
typeof
是解釋器內部實現,根據 ECMA-262 規定的幾種類型的值來返回類型名稱。
可是 typeof
的應用場景很是有限,基本上只能判斷出來使用字面量方式賦值的基本數據類型,例如:
var a = 123;
console.log(typeof(a)); // number
var b = "string";
console.log(typeof(b)); // string
var c = true;
console.log(typeof(c)); // boolean
var d;
console.log(typeof(d)); // undefined
var e = null;
console.log(typeof(e)); // object
var f = [1,2,3];
console.log(typeof(f)); // object
var g = {};
console.log(typeof(g)); // object
var fun = function () {};
console.log(typeof(fun)); // function
var A = new Number(123);
console.log(typeof(A)); // object
console.log(A instanceof Number); // true
var B = new String("123");
console.log(typeof(B)); // object
console.log(B instanceof String); // true複製代碼
由以上例子能夠看出,typeof
測試的結果並非特別的準確,而且只能檢測使用字面量命名的基本數據類型(除了 null
)。因此咱們通常不使用 typeof 進行數據檢測。
instanceof
在上面的例子中,咱們已經使用了 typeof
進行數據檢測。instance
是「例子,實例」的意思,因此 instanceof
意思是用於判斷變量是不是某一個對象的實例。
instanceof
原理如下部分是根據 JavaScript instanceof 運算符深刻剖析 理解。
instanceof
的原理能夠認爲是以下:
function instance_of(L, R) { //L 表示左表達式,R 表示右表達式
var O = R.prototype; // 取 R 的顯示原型
L = L.__proto__; // 取 L 的隱式原型
while (true) {
if (L === null)
return false;
if (O === L) // 這裏重點:當 O 嚴格等於 L 時,返回 true
return true;
L = L.__proto__;
}
}複製代碼
再結合咱們在最開始介紹的前置知識的這張圖來看幾個例子幫助咱們更好的理解 instanceof
的原理:
Object instanceof Object;
// 爲了方便表述,首先區分左側表達式和右側表達式
ObjectL = Object, ObjectR = Object;
// 下面根據規範逐步推演
O = ObjectR.prototype = Object.prototype
L = ObjectL.__proto__ = Function.prototype
// 第一次判斷
O != L
// 循環查找 L 是否還有 __proto__
L = Function.prototype.__proto__ = Object.prototype
// 第二次判斷
O == L
// 返回 true複製代碼
例2:
Function instanceof Function
// 爲了方便表述,首先區分左側表達式和右側表達式
FunctionL = Function, FunctionR = Function;
// 下面根據規範逐步推演
O = FunctionR.prototype = Function.prototype
L = FunctionL.__proto__ = Function.prototype
// 第一次判斷
O == L
// 返回 true複製代碼
例3:
Foo instanceof Foo
// 爲了方便表述,首先區分左側表達式和右側表達式
FooL = Foo, FooR = Foo;
// 下面根據規範逐步推演
O = FooR.prototype = Foo.prototype
L = FooL.__proto__ = Function.prototype
// 第一次判斷
O != L
// 循環再次查找 L 是否還有 __proto__
L = Function.prototype.__proto__ = Object.prototype
// 第二次判斷
O != L
// 再次循環查找 L 是否還有 __proto__
L = Object.prototype.__proto__ = null
// 第三次判斷
L == null
// 返回 false複製代碼
其實,instanceof 的重點也就是左邊對象的隱式原型等於右邊構造函數的顯示原型,是否是聽着很熟悉呢,這就是在 new 操做中的關鍵一步(new 操做是賦值),這樣就能夠判斷指定的對象是不是某個構造函數的實例,使 L = L.proto(繼續沿原型鏈向上尋找),一直循環判斷左邊對象的隱式原型等於右邊構造函數的顯示原型,直到L.__proto__
爲 null(L 已經循環到 object.prototype) 或者 true
instanceof
侷限性instanceof
的侷限性應該也就是不能檢測基本數據類型了吧,其餘的貌似沒什麼。經過對 instanceof
的原理進行分析,咱們能夠得知,只要左邊的對象的對象可以經過原型鏈 __proto__
是指向右邊的構造函數就能夠~
instanceof
右邊必須是對象或構造函數,不然會拋出 TypeError 的錯誤。
Object.prototype.toString
全部的數據類型均可以用 Object.prototype.toString
來檢測,並且很是的精準。
如下內容參考 談談Object.prototype.toString
咱們先來看一下 Object.prototype.toString
是怎麼進行類型檢測的
var a = 123;
console.log(Object.prototype.toString.call(a)); // [object Number]
var b = "string";
console.log(Object.prototype.toString.call(b)); // [object String]
var c = [];
console.log(Object.prototype.toString.call(c)); // [object Array]
var d = {};
console.log(Object.prototype.toString.call(d)); // [object Object]
var e = true;
console.log(Object.prototype.toString.call(e)); // [object Boolean]
var f = null;
console.log(Object.prototype.toString.call(f)); // [object Null]
var g;
console.log(Object.prototype.toString.call(g)); // [object Undefined]
var h = function () {};
console.log(Object.prototype.toString.call(h)); // [object Function]
var A = new Number();
console.log(Object.prototype.toString.call(A)); // [object Number]複製代碼
因此說,Object.prototype.toString
仍是可以比較準確的檢測出對應的類型的。
Object.prototype.toString
的實現過程在 ECMAScript 5中,Object.prototype.toString()
被調用時,會進行以下步驟:
this
是 undefined
,返回 [object Undefined]
;this
是 null
, 返回 [object Null]
;Object
爲以 this
做爲參數調用 ToObject
的結果;class
爲 Object
的內部屬性 [[Class]]
的值;[object", class, 以及"]
拼接而成的字符串。[[Class]]
本規範的每種內置對象都定義了 [[Class]] 內部屬性的值。宿主對象的 [[Class]] 內部屬性的值能夠是除了 "Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", "String" 的任何字符串。[[Class]] 內部屬性的值用於內部區分對象的種類。注,本規範中除了經過 Object.prototype.toString ( 見 15.2.4.2) 沒有提供任何手段使程序訪問此值。
在 JavaScript 代碼裏,惟一能夠訪問該屬性的方法就是經過 Object.prototype.toString
,一般方法以下:
Object.prototype.toString.call/apply(value)複製代碼
在 ES6 請參見 談談 Object.prototype.toString
construtor
construtor
其實也是用了原型鏈的知識。
constructor 屬性返回對建立此對象的數組函數的引用。
var a = 123;
console.log( a.constructor == Number); // true
var b = "string";
console.log( b.constructor == String); // true
var c = [];
console.log( c.constructor == Array); // true
var d = {};
console.log( d.constructor == Object); // true
var e = true;
console.log( e.constructor == Boolean); // true
var f = null;
console.log( f.constructor == Null); // TypeError: Cannot read property 'constructor' of null
var g;
console.log( g.constructor == Undefined); // Uncaught TypeError: Cannot read property 'constructor' of
undefined
var h = function () {};
console.log( h.constructor == Function); // true
var A = new Number();
console.log( A.constructor == Number); // true
var A = new Number();
console.log( A.constructor == Object); // false複製代碼
經過上述的實例,咱們能夠看到,不管是經過字面量或者構造函數建立的基本類型,均可以檢測出。而且也能夠檢測出 Array
、Object
、Function
引用類型,可是不能檢測出 Null
和 Undefined
在 Zepto 中主要是用 Object.prototype.toString
來作數據類型的判斷的
如今讓咱們來看一下 Zepto 中是怎麼檢測這些數據類型的:
var class2type = {},
toString = class2type.toString
// 在代碼中部,執行了
// $.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
// class2type[ "[object " + name + "]" ] = name.toLowerCase()
// })
// 用來給 class2type 對象賦值
//
// a ? b : c || d
//type 用來判斷類型
function type(obj) {
return obj == null ? String(obj) :
class2type[toString.call(obj)] || "object"
}複製代碼
這裏你可能會有疑問,爲何使用 toString
而不是 Object.prototype.toString
那是由於若是將基本數據類型,好比 string、number、boolean等類型的值使用 toString 的方法時,是直接將基本數據類型轉換爲 string 類型,可是若是對 object 類型使用 toString 方法,則是會調用其原型上的 toString 方法,也就是 Object.prototype.toString
。因此 Zepto 在開頭的地方就定義了 class2type 爲一個 object 類型。
若是 obj 的類型爲 null 或者 undefined 直接返回,若是該對象的 Object.prototype.toString
將返回的結果做爲 class2type 的 key 取值。Object.prototype.toString 對不一樣的數據類型會返回形如 [object Boolean] 的結果。
若是都不是以上狀況,默認返回 object 類型。
// 判斷是不是函數
function isFunction(value) { return type(value) == "function" }複製代碼
// 判斷是不是 window對象(注意,w爲小寫)指當前的瀏覽器窗口,window對象的window屬性指向自身。
// 即 window.window === window
function isWindow(obj) { return obj != null && obj == obj.window }複製代碼
// 判斷是不是 document 對象
// window.document.nodeType == 9 數字表示爲9,常量表示爲 DOCUMENT_NODE
function isDocument(obj) { return obj != null && obj.nodeType == obj.DOCUMENT_NODE }複製代碼
// 判斷是不是 object
function isObject(obj) { return type(obj) == "object" }複製代碼
function isPlainObject(obj) {
return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
}複製代碼
// 判斷是不是arr
isArray = Array.isArray || function(object){ return object instanceof Array };複製代碼
// 判斷是不是數組或者對象數組
// !!的做用是把一個其餘類型的變量轉成的bool類型。
// !!obj 直接過濾掉了false,null,undefined,''等值
function likeArray(obj) {
var length = !!obj && 'length' in obj && obj.length,
// 獲取obj的數據類型
type = $.type(obj);
// 不能是function類型,不能是window
// 若是是array則直接返回true
// 或者當length的數據類型是number,而且其取值範圍是0到(length - 1)這裏是經過判斷length - 1 是否爲obj的屬性
return 'function' != type && !isWindow(obj) && (
'array' == type || length === 0 ||
(typeof length == 'number' && length > 0 && (length - 1) in obj)
)
}複製代碼
// 空對象
$.isEmptyObject = function(obj) {
var name
for (name in obj) return false
return true
}複製代碼
//數字
$.isNumeric = function(val) {
var num = Number(val), type = typeof val;
return val != null && type != 'boolean' &&
(type != 'string' || val.length) &&
!isNaN(num) && isFinite(num) || false
}複製代碼