『JavaScript專題』之類型判斷(下)

前言

在上篇《JavaScript專題之類型判斷(上)》中犀概,我們抄襲 jQuery 寫了一個 type 函數(shù)啤月,可以檢測出常見的數(shù)據(jù)類型俩由,然而在開發(fā)中還有更加復雜的判斷,比如 plainObject倘零、空對象唱遭、Window 對象等,這一篇就讓我們接著抄襲 jQuery 去看一下這些類型的判斷呈驶。

plainObject

plainObject 來自于 jQuery拷泽,可以翻譯成純粹的對象,所謂"純粹的對象",就是該對象是通過 "{}" 或 "new Object" 創(chuàng)建的司致,該對象含有零個或者多個鍵值對拆吆。

之所以要判斷是不是 plainObject,是為了跟其他的 JavaScript對象如 null脂矫,數(shù)組枣耀,宿主對象(documents)等作區(qū)分,因為這些用 typeof 都會返回object羹唠。

jQuery提供了 isPlainObject 方法進行判斷奕枢,先讓我們看看使用的效果:

function Person(name) {
    this.name = name;
}

console.log($.isPlainObject({})) // true

console.log($.isPlainObject(new Object)) // true

console.log($.isPlainObject(Object.create(null))); // true

console.log($.isPlainObject(Object.assign({a: 1}, {b: 2}))); // true

console.log($.isPlainObject(new Person('yayu'))); // false

console.log($.isPlainObject(Object.create({}))); // false

由此我們可以看到娄昆,除了 {} 和 new Object 創(chuàng)建的之外佩微,jQuery 認為一個沒有原型的對象也是一個純粹的對象。

實際上隨著 jQuery 版本的提升萌焰,isPlainObject 的實現(xiàn)也在變化哺眯,我們今天講的是 3.0 版本下的 isPlainObject,我們直接看源碼:

// 上節(jié)中寫 type 函數(shù)時扒俯,用來存放 toString 映射結(jié)果的對象
var class2type = {};

// 相當于 Object.prototype.toString
var toString = class2type.toString;

// 相當于 Object.prototype.hasOwnProperty
var hasOwn = class2type.hasOwnProperty;

function isPlainObject(obj) {
    var proto, Ctor;

    // 排除掉明顯不是obj的以及一些宿主對象如Window
    if (!obj || toString.call(obj) !== "[object Object]") {
        return false;
    }

    /**
     * getPrototypeOf es5 方法奶卓,獲取 obj 的原型
     * 以 new Object 創(chuàng)建的對象為例的話
     * obj.__proto__ === Object.prototype
     */
    proto = Object.getPrototypeOf(obj);

    // 沒有原型的對象是純粹的,Object.create(null) 就在這里返回 true
    if (!proto) {
        return true;
    }

    /**
     * 以下判斷通過 new Object 方式創(chuàng)建的對象
     * 判斷 proto 是否有 constructor 屬性撼玄,如果有就讓 Ctor 的值為 proto.constructor
     * 如果是 Object 函數(shù)創(chuàng)建的對象夺姑,Ctor 在這里就等于 Object 構(gòu)造函數(shù)
     */
    Ctor = hasOwn.call(proto, "constructor") && proto.constructor;

    // 在這里判斷 Ctor 構(gòu)造函數(shù)是不是 Object 構(gòu)造函數(shù),用于區(qū)分自定義構(gòu)造函數(shù)和 Object 構(gòu)造函數(shù)
    return typeof Ctor === "function" && hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object);
}

注意:我們判斷 Ctor 構(gòu)造函數(shù)是不是 Object 構(gòu)造函數(shù)掌猛,用的是 hasOwn.toString.call(Ctor)盏浙,這個方法可不是 Object.prototype.toString,不信我們在函數(shù)里加上下面這兩句話:

console.log(hasOwn.toString.call(Ctor)); // function Object() { [native code] }
console.log(Object.prototype.toString.call(Ctor)); // [object Function]

發(fā)現(xiàn)返回的值并不一樣荔茬,這是因為 hasOwn.toString 調(diào)用的其實是 Function.prototype.toString废膘,畢竟 hasOwnProperty 可是一個函數(shù)!

而且 Function 對象覆蓋了從 Object 繼承來的 Object.prototype.toString 方法慕蔚。函數(shù)的 toString 方法會返回一個表示函數(shù)源代碼的字符串丐黄。具體來說,包括 function關(guān)鍵字孔飒,形參列表灌闺,大括號,以及函數(shù)體中的內(nèi)容坏瞄。

EmptyObject

jQuery提供了 isEmptyObject 方法來判斷是否是空對象桂对,代碼簡單,我們直接看源碼:

function isEmptyObject( obj ) {

        var name;

        for ( name in obj ) {
            return false;
        }

        return true;
}

其實所謂的 isEmptyObject 就是判斷是否有屬性惦积,for 循環(huán)一旦執(zhí)行接校,就說明有屬性,有屬性就會返回 false。

但是根據(jù)這個源碼我們可以看出isEmptyObject實際上判斷的并不僅僅是空對象蛛勉。

舉個栗子:

console.log(isEmptyObject({})); // true
console.log(isEmptyObject([])); // true
console.log(isEmptyObject(null)); // true
console.log(isEmptyObject(undefined)); // true
console.log(isEmptyObject(1)); // true
console.log(isEmptyObject('')); // true
console.log(isEmptyObject(true)); // true

以上都會返回 true鹿寻。

但是既然 jQuery 是這樣寫,可能是因為考慮到實際開發(fā)中 isEmptyObject 用來判斷 {} 和 {a: 1} 是足夠的吧诽凌。如果真的是只判斷 {}毡熏,完全可以結(jié)合上篇寫的 type 函數(shù)篩選掉不適合的情況。

Window對象

Window 對象作為客戶端 JavaScript 的全局對象侣诵,它有一個 window 屬性指向自身痢法,這點在《JavaScript深入之變量對象》中講到過。我們可以利用這個特性判斷是否是 Window 對象杜顺。

function isWindow( obj ) {
    return obj != null && obj === obj.window;
}

isArrayLike

isArrayLike财搁,看名字可能會讓我們覺得這是判斷類數(shù)組對象的,其實不僅僅是這樣躬络,jQuery 實現(xiàn)的 isArrayLike尖奔,數(shù)組和類數(shù)組都會返回 true。

因為源碼比較簡單穷当,我們直接看源碼:

function isArrayLike(obj) {

    // obj 必須有 length屬性
    var length = !!obj && "length" in obj && obj.length;
    var typeRes = type(obj);

    // 排除掉函數(shù)和 Window 對象
    if (typeRes === "function" || isWindow(obj)) {
        return false;
    }

    return typeRes === "array" || length === 0 ||
        typeof length === "number" && length > 0 && (length - 1) in obj;
}

重點分析 return 這一行提茁,使用了或語句,只要一個為 true馁菜,結(jié)果就返回 true茴扁。

所以如果 isArrayLike 返回true,至少要滿足三個條件之一:

  1. 是數(shù)組
  2. 長度為 0
  3. lengths 屬性是大于 0 的數(shù)組汪疮,并且obj[length - 1]必須存在

第一個就不說了峭火,看第二個,為什么長度為 0 就可以直接判斷為 true 呢铲咨?

那我們寫個對象:

var obj = {a: 1, b: 2, length: 0}

isArrayLike 函數(shù)就會返回 true躲胳,那這個合理嗎?

回答合不合理之前纤勒,我們先看一個例子:

function a(){
    console.log(isArrayLike(arguments))
}
a();

如果我們?nèi)サ鬺ength === 0 這個判斷坯苹,就會打印 false,然而我們都知道 arguments 是一個類數(shù)組對象摇天,這里是應該返回 true 的粹湃。

所以是不是為了放過空的 arguments 時也放過了一些存在爭議的對象呢?

第三個條件:length 是數(shù)字泉坐,并且 length > 0 且最后一個元素存在为鳄。

為什么僅僅要求最后一個元素存在呢?

讓我們先想下數(shù)組是不是可以這樣寫:

var arr = [,,3]

當我們寫一個對應的類數(shù)組對象就是:

var arrLike = {
    2: 3,
    length: 3
}

也就是說當我們在數(shù)組中用逗號直接跳過的時候腕让,我們認為該元素是不存在的孤钦,類數(shù)組對象中也就不用寫這個元素歧斟,但是最后一個元素是一定要寫的,要不然 length 的長度就不會是最后一個元素的 key 值加 1偏形。比如數(shù)組可以這樣寫

var arr = [1,,];
console.log(arr.length) // 2

但是類數(shù)組對象就只能寫成:

var arrLike = {
    0: 1,
    length: 1
}

所以符合條件的類數(shù)組對象是一定存在最后一個元素的静袖!

這就是滿足 isArrayLike 的三個條件,其實除了 jQuery 之外俊扭,很多庫都有對 isArrayLike 的實現(xiàn)队橙,比如 underscore:

var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;

var isArrayLike = function(collection) {
    var length = getLength(collection);
    return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};

isElement

isElement 判斷是不是 DOM 元素。

isElement = function(obj) {
    return !!(obj && obj.nodeType === 1);
};

結(jié)語

這一篇我們介紹了 jQuery 的 isPlainObject萨惑、isEmptyObject捐康、isWindow、isArrayLike庸蔼、以及 underscore 的 isElement 實現(xiàn)解总。我們可以看到,即使是 jQuery 這樣優(yōu)秀的庫朱嘴,一些方法的實現(xiàn)也并不是非常完美和嚴密的倾鲫,但是最后為什么這么做粗合,其實也是一種權(quán)衡萍嬉,權(quán)衡所失與所得,正如玉伯在《從 JavaScript 數(shù)組去重談性能優(yōu)化》中講到:

所有這些點隙疚,都必須腳踏實地在具體應用場景下去分析壤追、去選擇,要讓場景說話供屉。

作者:冴羽
github:https://github.com/mqyqingfeng/Blog
掘金主頁:https://juejin.im/user/58e4b9b261ff4b006b3227f4
segmentfault主頁:https://segmentfault.com/u/yayu/articles

Vicky丶Amor 經(jīng)授權(quán)轉(zhuǎn)載行冰,版權(quán)歸原作者所有。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伶丐,一起剝皮案震驚了整個濱河市悼做,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哗魂,老刑警劉巖肛走,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異录别,居然都是意外死亡朽色,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門组题,熙熙樓的掌柜王于貴愁眉苦臉地迎上來葫男,“玉大人,你說我怎么就攤上這事崔列∩液郑” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長盈咳。 經(jīng)常有香客問我趣效,道長,這世上最難降的妖魔是什么猪贪? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任跷敬,我火速辦了婚禮,結(jié)果婚禮上热押,老公的妹妹穿的比我還像新娘西傀。我一直安慰自己,他們只是感情好桶癣,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布拥褂。 她就那樣靜靜地躺著,像睡著了一般牙寞。 火紅的嫁衣襯著肌膚如雪饺鹃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天间雀,我揣著相機與錄音悔详,去河邊找鬼。 笑死惹挟,一個胖子當著我的面吹牛茄螃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播连锯,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼归苍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了运怖?” 一聲冷哼從身側(cè)響起拼弃,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎摇展,沒想到半個月后吻氧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡吗购,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年医男,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捻勉。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡镀梭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出踱启,到底是詐尸還是另有隱情报账,我是刑警寧澤研底,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站透罢,受9級特大地震影響榜晦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜羽圃,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一乾胶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧朽寞,春花似錦识窿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至肘迎,卻和暖如春甥温,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背妓布。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工姻蚓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人秋茫。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓史简,卻偏偏與公主長得像,于是被迫代替她去往敵國和親肛著。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內(nèi)容