1. 程式人生 > >underscore.js原始碼解析之型別判斷

underscore.js原始碼解析之型別判斷

1. 引言

  underscore.js是一個1500行左右的Javascript函式式工具庫,裡面提供了很多實用的、耦合度極低的函式,用來方便的操作Javascript中的陣列、物件和函式,它支援函式式和麵向物件鏈式的程式設計風格,還提供了一個精巧的模板引擎。理解underscore.js的原始碼和思想,不管是新手,還是工作了一段時間的人,都會上升一個巨大的臺階。雖然我不搞前端,但是在一個星期的閱讀分析過程中,仍然受益匪淺,決定把一些自認為很有意義的部分記錄下來。

2. 型別判斷

  在underscore.js裡面,有大量的is字首的函式:isEmpty isElement isArray

isObject isArguments isFunction isString isNumber isDate isRegExp isError isFinite isNaN isBoolean isNull isUndefined

搞定這些函式,可以對js的型別有一個非常深刻的理解,還可以學到很多的型別判斷實用技巧。

先從軟柿子開始捏:
isUndefined

_.isUndefined = function(obj) {
  return obj === void 0;
};

這裡出現了第一個技巧,void 0。void永遠只會返回undefined,用void 0表示undefined比直接使用undefined更加準確,因為undefined並不是關鍵字,可以用作識別符號:

<script>
window.onload = function() {
  var undefined = 'a';
  var b = undefined;
  console.log(b);//輸出為 a
};
</script>

isNull

 _.isNull = function(obj) {
  return obj === null;//必須要使用嚴格相等,因為null == undefined返回true
};

isBoolean

_.isBoolean = function(obj) {
  return obj === true || obj === false
|| toString.call(obj) === '[object Boolean]'; };

toString.call()是判斷型別的最精確方式,這裡先判斷為true還是false是為了提高效率。

isArguments isFunction isString isNumber isDate isRegExp isError

_.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) {
  _['is' + name] = function(obj) {
    return toString.call(obj) === '[object ' + name + ']';
  };
});

toString.call()是判斷型別的最精確方式最通用的方式,這裡動態構造了幾個underscore的is函式。

isNaN

_.isNaN = function(obj) {
  return _.isNumber(obj) && obj !== +obj;//NaN !== NaN
};

又出現一個技巧+,作用是將後面的變數轉換成數字,如果無法轉換則為NaN。肯定還記得坑爹的+[] 吧。

isFinite

_.isFinite = function(obj) {
  return isFinite(obj) && !isNaN(parseFloat(obj));//先呼叫原生的isFinite,實際上isFinite(NaN)返回的就是false,後面的進一步判斷應該是一些相容措施。
};

isObject

_.isObject = function(obj) {
  var type = typeof obj;
  return type === 'function' || type === 'object' && !!obj;
};

typeof的判斷比較粗糙,object代表很多種型別,null也是object,這裡需要排除為null的情況,使用了一個技巧 !!,將其轉換成對應的布林型別。

isArray

_.isArray = nativeIsArray || function(obj) {
  return toString.call(obj) === '[object Array]';//toString.call簡單粗暴
};

||邏輯或經常用來做polyfill,靜態語言的邏輯或只能返回true or false,而js的邏輯或可以返回表示式的值,經常用來消除if else

isElement

_.isElement = function(obj) {
  return !!(obj && obj.nodeType === 1);//元素結點type為1
};

isEmpty

_.isEmpty = function(obj) {
  if (obj == null) return true;
  //常見的類陣列有Array,String,Arguments
  if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) 
      return obj.length === 0;
  return _.keys(obj).length === 0;//如果是{}型別,取其屬性名的陣列
};

3. DuckTyping

DuckTyping是動態語言實現多型的一種強有力的方式,只要多個物件有相同的屬性名和函式名,就認為這幾個物件是一個種類:

//屬性訪問器
var property = function(key) {
 return function(obj) {
    return obj == null ? void 0 : obj[key];
  };
};
//length屬性訪問器
var getLength = property('length');
//類陣列判斷,只要有length屬性就當成是類陣列,在這個函式裡String, Array, Arguments都被認為是一個collection
var isArrayLike = function(collection) {
  var length = getLength(collection);
  return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};

4. 總結

  1. 使用void 0 表示undefined.
  2. +將變數轉換成數字型別,無法轉換則為NaN.
  3. !!將變數轉換成對應的布林型別.
  4. 使用||來消除不必要的if else.
  5. toString.call是通用的型別判斷方法.
  6. DuckTyping.