JavaScript數(shù)據(jù)類型檢測(cè)詳解及jQuery中實(shí)現(xiàn)數(shù)據(jù)類型檢測(cè)的封裝代碼分析

JavaScript數(shù)據(jù)類型檢測(cè)

JavaScript中創(chuàng)建一個(gè)值的兩種方案

1、字面量方式

let a = 1;
let obj = {};
...

2、構(gòu)造函數(shù)方式

不能 new Symbol 和 new BigInt,可以通過Object(Symbol()/BigInt()),其他基本類型(除null和undefined)也可以這樣做

let a = new Number(1)
let obj = new Object()
...
構(gòu)造函數(shù)方式創(chuàng)建示例

對(duì)于基本數(shù)據(jù)類型,兩種方式的結(jié)果不同

字面量方式得到的是基本數(shù)據(jù)類型(特殊的實(shí)例)

通過構(gòu)造函數(shù)方式的得到的是對(duì)象類型(這才是一個(gè)正規(guī)的實(shí)例,基于__proto__找到所屬類原型,valueOf 獲取原始值)

字面量方式下,也可以調(diào)用a.toFixed(2),是因?yàn)樗约簳?huì)先轉(zhuǎn)換為標(biāo)準(zhǔn)的實(shí)例new Number出來的結(jié)果,然后再去調(diào)用原型上的方法。但是1.toFixed(2)不行,(1).toFixed(2)可以

對(duì)于引用數(shù)據(jù)類型,兩種方式處理語法上的一些區(qū)別,沒有本質(zhì)的區(qū)別,獲取的都是對(duì)應(yīng)類的實(shí)例對(duì)象

JavaScript檢測(cè)數(shù)據(jù)類型的方法

1、typeof

typeof [value]

  • 好處:簡(jiǎn)單方便,大部分?jǐn)?shù)據(jù)類型都可以檢測(cè)

  • 缺點(diǎn):typeof null => "object"

    這是JS的設(shè)計(jì)缺陷:數(shù)據(jù)值都是按照二進(jìn)制存儲(chǔ)的。

    默認(rèn)1開頭的是整數(shù),010開頭的是浮點(diǎn)數(shù),100開頭的是字符串,110開頭的是布爾,000開頭的是對(duì)象,-2^30undefined,000000null 也說明typeof是按照二進(jìn)制存儲(chǔ)值進(jìn)行檢測(cè)的

typeof不能細(xì)分具體的對(duì)象數(shù)據(jù)類型值,所有的對(duì)象數(shù)據(jù)類型值,檢測(cè)出來的結(jié)果都是"object"

typeof檢測(cè)基于構(gòu)造函數(shù)創(chuàng)建出來的,基本數(shù)據(jù)類型的實(shí)例對(duì)象,結(jié)果也是"object"

console.log(typeof 1);            // => number
console.log(typeof 1.1);          // => number
console.log(typeof '1');          // => string
console.log(typeof true);         // => boolean
console.log(typeof Symbol());     // => symbol
console.log(typeof BigInt(1));    // => bigint
console.log(typeof function(){}); // => function
console.log(typeof [1, 2]);       // => object
console.log(typeof { a: 1 });     // => object

console.log(typeof new Number(1));    // => object
console.log(typeof new Number(1.1));  // => object
console.log(typeof new Boolean(true));// => object
console.log(typeof new Boolean(1));   // => object
console.log(typeof new String(1));    // => object
console.log(typeof new String('1'));  // => object
console.log(typeof new Object(1));    // => object
console.log(typeof new Object());     // => object
console.log(typeof Object(Symbol(1)));// => object
console.log(typeof Object(BigInt(1)));// => object
console.log(typeof new Function());   // => function
let Fn = function () {}
console.log(typeof Fn);               // => function
console.log(typeof new Fn());         // => object

2、instanceof

檢測(cè)某個(gè)實(shí)例是否屬于這個(gè)類?;趇nstanceof可以細(xì)分一下不同類型的對(duì)象,也可以檢測(cè)出基于構(gòu)造函數(shù)方式創(chuàng)建出來的基本類型對(duì)象值

  • 原理:instanceof基于構(gòu)造函數(shù)[Symbol.hasInstance](實(shí)例)。檢測(cè)當(dāng)前構(gòu)造函數(shù)的原型prototype是否出現(xiàn)在,當(dāng)前實(shí)例所處的原型鏈上,如果能出現(xiàn),結(jié)果就是true,否則就是false

  • 缺陷:在JS中的原型鏈?zhǔn)强梢愿膭?dòng)的,所以結(jié)果不準(zhǔn)確。所有實(shí)例的原型鏈最后都指向Object.prototype,所有 “實(shí)例 instanceof Object” 的結(jié)果都是true

    字面量方式創(chuàng)造的基本數(shù)據(jù)類型值是無法給予instanceof檢測(cè)的,瀏覽器默認(rèn)并不會(huì)把它轉(zhuǎn)換為new的方式,所以它本身不是對(duì)象,不存在__proto__

let a = 1;
// 原因:基本數(shù)據(jù)類型不是對(duì)象,不存在__proto__
console.log(a instanceof Number); // => false
// -----------------------------------------------------------------
let b = [1, 2];
console.log(b instanceof Array);  // => true
console.log(b instanceof Object); // => true
// -----------------------------------------------------------------
let c = {
  0: 1,
  1: 2,
  length: 2
}
console.log(c instanceof Array);  // => false
console.log(c instanceof Object); // => true
// -----------------------------------------------------------------
let d = {
  a: 1,
  b: 2
}
console.log(d instanceof Array);  // => false
console.log(d instanceof Object); // => true
// -----------------------------------------------------------------
function Fn () {}
let fn = new Fn;
console.log(fn instanceof Array);  // => false
// 原型鏈可以改動(dòng),所以結(jié)果也不準(zhǔn)確
Fn.prototype = Array.prototype;
let fn1 = new Fn;
// fn1是函數(shù)而不是數(shù)組,所以原型鏈改動(dòng)后,結(jié)果就不準(zhǔn)確了
console.log(fn1 instanceof Array); // => true

3、constructor

檢查直屬類是誰

注:constructor是可以肆意被修改,所以也不準(zhǔn)確

let a = 1;
// 瀏覽器會(huì)默認(rèn)把 a 轉(zhuǎn)換為 new Number,所以也可以用
console.log(a.constructor === Number);  // => true
a = new Number(1);
console.log(a.constructor === Number);  // => true
// -----------------------------------------------------------------
let b = [1, 2];
console.log(b.constructor === Array);   // => true
console.log(b.constructor === Object);  // => false
// -----------------------------------------------------------------
let c = {
  0: 1,
  1: 2,
  length: 2
}
console.log(c.constructor === Array);   // => false
console.log(c.constructor === Object);  // => true

4、Object.prototype.toString.call

Object.prototype.toString.call([value])

這是一個(gè)萬全之策。 (除了代碼略長(zhǎng))

  • 原理:大部分內(nèi)置類的原型是都有toString(),但是一般都是轉(zhuǎn)換為字符串,只有Object.prototype上的toString()并不是轉(zhuǎn)換為字符串,而是返回當(dāng)前實(shí)例對(duì)象所屬類的信息的'[object 所屬構(gòu)造函數(shù)的信息]'

所屬構(gòu)造函數(shù)的信息是根據(jù)Symbol.toStringTag這個(gè)屬性獲取的,有這個(gè)屬性基于這個(gè)獲取,沒有的瀏覽器自己計(jì)算。

// 用call是改變this指向,使用Object原型上的toString方法(鴨子類型)[原型上方法的借用]
// 也可以這樣寫:({}).toString.call([value])
let a = 1;
console.log(Object.prototype.toString.call(a)); // => [object Number]
a = new Number(1);
console.log(Object.prototype.toString.call(a)); // => [object Number]
// -----------------------------------------------------------------
let b = [1, 2];
console.log(Object.prototype.toString.call(b)); // => [object Array]
// -----------------------------------------------------------------
let c = {
  0: 1,
  1: 2,
  length: 2
}
console.log(Object.prototype.toString.call(c)); // => [object Object]
// -----------------------------------------------------------------
let d = {
  a: 1,
  b: 2
}
console.log(Object.prototype.toString.call(d)); // => [object Object]
// -----------------------------------------------------------------
let Fn = function () {}
console.log(Object.prototype.toString.call(Fn)); // => [object Function]
let fn = new Fn;
console.log(Object.prototype.toString.call(fn)); // => [object Object]

可以簡(jiǎn)寫為下面這種方式:

let type = {};
let toString = type.toString; // => Object.prototype.toString
console.log(toString.call(1));              // => [object Number]
console.log(toString.call(new Number(1)));  // => [object Number]
console.log(toString.call('1'));            // => [object String]
console.log(toString.call(true));           // => [object Boolean]
console.log(toString.call(null));           // => [object Null]
console.log(toString.call(undefined));      // => [object Undefined]
console.log(toString.call([1, 2]));         // => [object Array]
console.log(toString.call(/^\d+$/));        // => [object RegExp]
console.log(toString.call({}));             // => [object Object]
console.log(toString.call(function () {})); // => [object Function]

關(guān)于Symbol.toStringTag這個(gè)屬性

function Fn () {}
Fn.prototype[Symbol.toStringTag] = 'Fn';
let fn = new Fn;
console.log(Object.prototype.toString.call(fn)); // => [object Fn]

5、數(shù)組檢測(cè)的其他方式

Array.isArray

let a = [];
console.log(Array.isArray([])); // => true

其他

// 基于正則
// 這樣寫代碼太長(zhǎng)
console.log(/array/i.test(Object.prototype.toString.call([]))); // => true

jQuery源碼中的數(shù)據(jù)檢測(cè)代碼分析及部分重寫

這些方法可以直接引入到項(xiàng)目中使用。

var class2type = {};
var toString = class2type.toString;     // Object.prototype.toString 檢測(cè)數(shù)據(jù)類型
var hasOwn = class2type.hasOwnProperty; // Object.prototype.hasOwnProperty 檢測(cè)是否是私有屬性
var fnToString = hasOwn.toString;       // Function.prototype.toString 把函數(shù)轉(zhuǎn)換為字符串
var ObjectFunctionString = fnToString.call(Object);
// => "function Object() { [native code] }"
var getProto = Object.getPrototypeOf;   // 獲取當(dāng)前對(duì)象的原型鏈__proto__

// 檢測(cè)是否為函數(shù)
var isFunction = function isFunction(obj) {
  // typeof obj.nodeType !== "number":防止在部分瀏覽器中,檢測(cè)<object>元素對(duì)象結(jié)果也是"function",但是它的nodeType是1,處理瀏覽器兼容問題
  // 元素節(jié)點(diǎn)(DOM對(duì)象)具備nodeType,值是1
  // 所有的DOM節(jié)點(diǎn)具備nodeType。元素:1,文本:3,注釋:8,document:9
  return typeof obj === "function" && typeof obj.nodeType !== "number";
};

// 檢測(cè)是否為window對(duì)象
var isWindow = function isWindow(obj) {
  // window對(duì)象有個(gè)特點(diǎn):window上有個(gè)屬性是window
  // window.window === window (符合這個(gè)條件的就是window對(duì)象)
  return obj != null && obj === obj.window;
};

// 檢測(cè)數(shù)據(jù)類型的方法
var toType = function toType(obj) {
  if (obj == null) {
    // 傳遞的null或者undefined或者null,直接返回字符串'undefined'或者'null'
    return obj + "";
  }
  // 基于字面量方式創(chuàng)造的基本數(shù)據(jù)類型,直接基于typeof檢測(cè)類型即可(性能會(huì)好一些)(function也可以走typeof)
  // 剩余的基于Object.prototyp.toString.call的方式來檢測(cè):格式 "[object xxx]"->對(duì)應(yīng)到class2type映射表直接拿到對(duì)應(yīng)數(shù)據(jù)類型字符串
  return typeof obj === "object" || typeof obj === "function" ? class2type[toString.call(obj)] || "object" : typeof obj;
}

/**
jQuery.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),
function (_i, name) {
  class2type["[object " + name + "]"] = name.toLowerCase();
});
*/
// 建立數(shù)據(jù)類型檢測(cè)映射表(jq源碼是上面這樣實(shí)現(xiàn)的)
// class2type: { [object Array]: "array", [object Boolean]: "boolean", ... }
var mapType = ["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object", "Error", "Symbol", "BigInt"];
mapType.forEach(function (name) {
  class2type["[object " + name + "]"] = name.toLowerCase();
});

// 檢測(cè)是否為數(shù)組或者類數(shù)組
var isArrayLike = function isArrayLike(obj) {
  // length存儲(chǔ)的是對(duì)象的length屬性值或者是false
  // type存儲(chǔ)的是檢測(cè)的數(shù)據(jù)類型
  var length = !!obj && "length" in obj && obj.length, type = toType(obj);
  // 一定不是數(shù)組或類數(shù)組。window.length = 0 && Function.prototype.length = 0,需要排除掉
  if (isFunction(obj) || isWindow(obj)) { return false; }
  // type === "array":是數(shù)組
  // length === 0:是空的類數(shù)組
  // typeof length === "number" && length > 0 && (length - 1) in obj:如果有l(wèi)ength屬性并且是一個(gè)數(shù)字且不是空的類數(shù)組,并且索引是按序增長(zhǎng)的(最大索引在對(duì)象中)
  return type === "array" || length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj;
}

// 檢測(cè)是否為純粹的對(duì)象,如{}
// 實(shí)例對(duì)象不是純粹的對(duì)象
var isPlainObject = function isPlainObject (obj) {
  var proto, Ctor;
  // 如果不存在 或者 基于toString檢測(cè)的結(jié)果不是"[object Object]",一定不是純對(duì)象
  if (!obj || toString.call(obj) !== "[object Object]") {
    return false;
  }
  // 獲取當(dāng)前值的原型鏈(直屬類的原型鏈)
  proto = getProto(obj);
  // Object.create(null):這樣創(chuàng)造的對(duì)象沒有__proto__
  if (!proto) return true;
  // Ctor存儲(chǔ)原型對(duì)象上的constructor這個(gè)屬性,沒有這個(gè)屬性就是false
  Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
  // 條件成立說明原型上的構(gòu)造函數(shù)是Object:當(dāng)前對(duì)象就是Object的一個(gè)實(shí)例,并且obj.__proto__ === Object.prototype
  return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
}

// 檢測(cè)是否為空對(duì)象
var isEmptyObject = function isEmptyObject (obj) {
  /**
   * jQ實(shí)現(xiàn)的方式(但這種for...in...的方式不太好):
   *  var name;
      for (name in obj) { return false; }
      return true;
   */
  // obj是undefined或者null,排除掉
  if (obj == null) { return false; }
  // 基于typeof檢測(cè)不是object,不是對(duì)象類型
  if (typeof obj !== "object") { return false }
  // 是一個(gè)對(duì)象(純粹但這或者特殊對(duì)象)
  var keys = Object.keys(obj); // Object.keys()[IE6,7,8不兼容] -> 拿到的私有的屬性,symbol拿不到
  if (hasOwn.call(Object, 'getOwnPropertySymbols')) { // getOwnPropertySymbols兼容性差,看看Object有沒有這個(gè)屬性
    // 兼容這個(gè)屬性的情況下,再去拼接
    keys = keys.concat(Object.getOwnPropertySymbols(obj));
  }
  return keys === 0;
}

// 檢測(cè)是否為數(shù)字
var isNumeric = function isNumeric (obj) {
  var type = jQuery.type(obj);
  // 如果不是數(shù)字或者字符串,一定不是數(shù)字
  // 如果是字符串,需要處理:用之前的值與浮點(diǎn)型進(jìn)行數(shù)學(xué)計(jì)算,如果結(jié)果是NaN,那就不是數(shù)字
  // 排除16進(jìn)制數(shù)字
  // return (type === "number" || type === "string") && !isNaN(obj - parseFloat(obj)); // => 【jq實(shí)現(xiàn)的方式】
  return (type === "number" || type === "string") && !isNaN(+obj);
}

// 數(shù)組或類數(shù)組循環(huán)遍歷
var each = function each (obj, callback) {
  var length, i = 0;
  if (isArrayLike(obj)) {
    length = obj.length;
    for (; i < length; i++) {
      if (callback.call(obj[i], i, obj[i]) === false) {
        break;
      }
    }
  } else {
    var keys = Object.keys(obj);
    // 如果瀏覽器兼容Symbol,也要處理Symbol屬性的遍歷
    typeof Symbol !== "undefined" ? keys = keys.concat(Object.getOwnPropertySymbols(obj)) : null;
    for (; i < keys.length; i++) {
        var key = keys[i];
        if (callback.call(obj[key], key, obj[key]) === false) {
            break;
        }
    }
  }
  return obj;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容