Javascript 中的資料型別判斷

shawncheung發表於2017-09-24

本系列通過閱讀 underscore 原始碼與實戰進而體驗函數語言程式設計的思想, 而非通過冗長的文字教程, 細讀精度
約 1500 行的 underscore 有利於寫出耦合度低, 符合函數語言程式設計思想的程式碼, 並且可以學到 call 與 apply 執行效率的不同進而進行程式碼效能優化的技巧等.

歡迎大家 star 或者 watch 本系列, 您的關注是作者的最大動力, 讓我們一起持續進步.
本系列倉庫: github.com/zhangxiang9…

Typeof

我們都使用 typeof 是用來判斷資料型別的命令, 在常規的場景中足以應付資料型別判斷的需求:

var obj = {
   name: 'zhangxiang'
};

function foo() {
    console.log('this is a function');
}

var arr = [1,2,3];

console.log(typeof 1);  // number
console.log(typeof '1');  //string
console.log(typeof true);  //boolean
console.log(typeof null); //object
console.log(typeof undefined); //undefined
console.log(typeof obj); //object
console.log(typeof foo);  //function
console.log(typeof arr);   //object複製程式碼

可以看到, typeof 命令可以判斷所有 javascript 中的基本資料型別(Null, Undefined, Boolean, String, Number), 雖然 null 使用 typeof 返回的是 object 字串, 但是無礙
它的基本使用, 但是在一些複雜的場景比如 object 與 null, array 與 object, function 與 object 等等的型別區分, typeof 就會顯得心有餘力不足了.
所以一般來說, typeof 會使用在比較簡單的場景, 比如你幾乎可以確定資料是哪一類資料然後稍微加以區分的時候.舉個簡單的例子來說明情況:

function unique(array){
  var hash = {};
  var result = [], key;
  array.forEach(function(item, index){
    key = item;
    if(typeof item === 'string') {
      key = '_' + item;
    }
    if(!hash[key]) {
      result.push(item);
    } else {
      hash[key] = true;
    }
  });
  return result;
}複製程式碼

instanceof

instanceof 其實適合用於判斷自定義的類例項物件, 而不是用來判斷原生的資料型別, 舉個例子:

// a.html
<script>
  var a = [1,2,3];
</script>複製程式碼
//main.html
<iframe src="a.html"></iframe>

<script>
  var frame = window.frame[0];
  var a = frame.a;
  console.log(a instanceof Array);  // false
  console.log(a.contructor === Array);  //false
  console.log(a instanceof frame.Array); // true
</script>複製程式碼

是什麼原因導致上面的結果呢? 其實 iframe 之間不會共享原型鏈, 因為他們有獨立的執行環境, 所以 frame a 中的陣列 a 不會是本執行環境的例項物件. 通過特性嗅探同樣不靠譜, 像通過 contructor
sort, slice 等等的特有的陣列(或者其他資料型別)方法或屬性, 萬一物件中也有 sort, slice 屬性, 就會發生誤判. 所以最靠譜的方法是使用 Object.prototype.toString 方法.

Object.prototype.toString

使用 Object.prototype.toString 方法, 可以獲取到變數的準確的型別.

function foo(){};

Object.prototype.toString.call(1);  '[object Number]'
Object.prototype.toString.call('1'); '[object String]'
Object.prototype.toString.call(NaN); '[object Number]'
Object.prototype.toString.call(foo);  '[object Function]'
Object.prototype.toString.call([1,2,3]); '[object Array]'
Object.prototype.toString.call(undefined); '[object Undefined]'
Object.prototype.toString.call(null); '[object Null]'
Object.prototype.toString.call(true); '[object Boolean]'
....複製程式碼

Object.prototype.toString 的原理是當呼叫的時候, 就取值內部的 [[Class]] 屬性值, 然後拼接成 '[object ' + [[Class]] + ']' 這樣的字串並返回. 然後我們使用 call 方法來獲取任何值的資料型別.

有用的資料型別判斷函式

isArray polyfill

isArray 是陣列型別內建的資料型別判斷函式, 但是會有相容性問題, 所以模擬 underscore 中的寫法如下:

isArray = Array.isArray || function(array){
  return Object.prototype.toString.call(array) === '[object Array]';
}複製程式碼

isNaN polyfill

判斷一個數是不是 NaN 不能單純地使用 === 這樣來判斷, 因為 NaN 不與任何數相等, 包括自身, 所以:

isNaN: function(value){
  return isNumber(value) && isNaN(value);
}複製程式碼

這裡的 isNumber 就是用上面所說的 Object.prototype.toString 進行判斷的, 然後使用 isNaN 來判斷值, 至於為什麼需要在判斷 isNaN 之前需要判斷是不是 Number 型別, 這是因為 NaN 本身
也是數字型別(Object.prototype.toString 可知), 在 ES6 的 isNaN 中只有值為數字型別使用 NaN 才會返回 true, 這是為了模擬 ES6 的 isNaN.

判斷是否是 DOM 元素

在實際專案裡面, 有時或許我們需要判斷是否是 DOM 元素物件, 那麼在判斷的時候利用的是 DOM 物件特有的 nodeType 屬性:

isElement: function(obj){
  return !!(obj && obj.nodeType === 1);
}複製程式碼

判斷是否是物件

isObject: function(obj){
  var type = typeof obj;
  return type === 'function' || typeof === 'object' && obj !== null;
}複製程式碼

這裡的物件是狹義的, 是通常所指的 key-value 型的集合, 或者是 function 函式並且不為 null.

判斷是否是 arguments 物件 polyfill

判斷一個物件是不是 arguments 物件可以通過 Object.prototype.toString 來判斷, 但是低版本的瀏覽器不支援, 他們返回的是 [object Object], 所以需要相容:

isArguments: function(obj){
  return Object.prototype.toString.call(obj) === '[object Arguments]' || (obj != null && Object.hasOwnProperty.call(obj, 'callee'));
}複製程式碼

相容做法原理是通過物件的 hasOwnProperty 方法來判斷物件是否擁有 callee 屬性從而判斷是不是 arguments 物件.

如果覺得有收穫, 請到 github 給作者一個 star 表示支援吧, 謝謝大家.
本系列倉庫: github.com/zhangxiang9…

相關文章