本系列通過閱讀 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…