JavaScript数据类型检测详解及jQuery中实现数据类型检测的封装代码分析

JavaScript数据类型检测

JavaScript中创建一个值的两种方案

1、字面量方式

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

2、构造函数方式

不能 new Symbol 和 new BigInt,可以通过Object(Symbol()/BigInt()),其他基本类型(除null和undefined)也可以这样做

let a = new Number(1)
let obj = new Object()
...
构造函数方式创建示例

对于基本数据类型,两种方式的结果不同

字面量方式得到的是基本数据类型(特殊的实例)

通过构造函数方式的得到的是对象类型(这才是一个正规的实例,基于__proto__找到所属类原型,valueOf 获取原始值)

字面量方式下,也可以调用a.toFixed(2),是因为它自己会先转换为标准的实例new Number出来的结果,然后再去调用原型上的方法。但是1.toFixed(2)不行,(1).toFixed(2)可以

对于引用数据类型,两种方式处理语法上的一些区别,没有本质的区别,获取的都是对应类的实例对象

JavaScript检测数据类型的方法

1、typeof

typeof [value]

  • 好处:简单方便,大部分数据类型都可以检测

  • 缺点:typeof null => "object"

    这是JS的设计缺陷:数据值都是按照二进制存储的。

    默认1开头的是整数,010开头的是浮点数,100开头的是字符串,110开头的是布尔,000开头的是对象,-2^30undefined,000000null 也说明typeof是按照二进制存储值进行检测的

typeof不能细分具体的对象数据类型值,所有的对象数据类型值,检测出来的结果都是"object"

typeof检测基于构造函数创建出来的,基本数据类型的实例对象,结果也是"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

检测某个实例是否属于这个类。基于instanceof可以细分一下不同类型的对象,也可以检测出基于构造函数方式创建出来的基本类型对象值

  • 原理:instanceof基于构造函数[Symbol.hasInstance](实例)。检测当前构造函数的原型prototype是否出现在,当前实例所处的原型链上,如果能出现,结果就是true,否则就是false

  • 缺陷:在JS中的原型链是可以改动的,所以结果不准确。所有实例的原型链最后都指向Object.prototype,所有 “实例 instanceof Object” 的结果都是true

    字面量方式创造的基本数据类型值是无法给予instanceof检测的,浏览器默认并不会把它转换为new的方式,所以它本身不是对象,不存在__proto__

let a = 1;
// 原因:基本数据类型不是对象,不存在__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
// 原型链可以改动,所以结果也不准确
Fn.prototype = Array.prototype;
let fn1 = new Fn;
// fn1是函数而不是数组,所以原型链改动后,结果就不准确了
console.log(fn1 instanceof Array); // => true

3、constructor

检查直属类是谁

注:constructor是可以肆意被修改,所以也不准确

let a = 1;
// 浏览器会默认把 a 转换为 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])

这是一个万全之策。 (除了代码略长)

  • 原理:大部分内置类的原型是都有toString(),但是一般都是转换为字符串,只有Object.prototype上的toString()并不是转换为字符串,而是返回当前实例对象所属类的信息的'[object 所属构造函数的信息]'

所属构造函数的信息是根据Symbol.toStringTag这个属性获取的,有这个属性基于这个获取,没有的浏览器自己计算。

// 用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]

可以简写为下面这种方式:

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]

关于Symbol.toStringTag这个属性

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

5、数组检测的其他方式

Array.isArray

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

其他

// 基于正则
// 这样写代码太长
console.log(/array/i.test(Object.prototype.toString.call([]))); // => true

jQuery源码中的数据检测代码分析及部分重写

这些方法可以直接引入到项目中使用。

var class2type = {};
var toString = class2type.toString;     // Object.prototype.toString 检测数据类型
var hasOwn = class2type.hasOwnProperty; // Object.prototype.hasOwnProperty 检测是否是私有属性
var fnToString = hasOwn.toString;       // Function.prototype.toString 把函数转换为字符串
var ObjectFunctionString = fnToString.call(Object);
// => "function Object() { [native code] }"
var getProto = Object.getPrototypeOf;   // 获取当前对象的原型链__proto__

// 检测是否为函数
var isFunction = function isFunction(obj) {
  // typeof obj.nodeType !== "number":防止在部分浏览器中,检测<object>元素对象结果也是"function",但是它的nodeType是1,处理浏览器兼容问题
  // 元素节点(DOM对象)具备nodeType,值是1
  // 所有的DOM节点具备nodeType。元素:1,文本:3,注释:8,document:9
  return typeof obj === "function" && typeof obj.nodeType !== "number";
};

// 检测是否为window对象
var isWindow = function isWindow(obj) {
  // window对象有个特点:window上有个属性是window
  // window.window === window (符合这个条件的就是window对象)
  return obj != null && obj === obj.window;
};

// 检测数据类型的方法
var toType = function toType(obj) {
  if (obj == null) {
    // 传递的null或者undefined或者null,直接返回字符串'undefined'或者'null'
    return obj + "";
  }
  // 基于字面量方式创造的基本数据类型,直接基于typeof检测类型即可(性能会好一些)(function也可以走typeof)
  // 剩余的基于Object.prototyp.toString.call的方式来检测:格式 "[object xxx]"->对应到class2type映射表直接拿到对应数据类型字符串
  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();
});
*/
// 建立数据类型检测映射表(jq源码是上面这样实现的)
// 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();
});

// 检测是否为数组或者类数组
var isArrayLike = function isArrayLike(obj) {
  // length存储的是对象的length属性值或者是false
  // type存储的是检测的数据类型
  var length = !!obj && "length" in obj && obj.length, type = toType(obj);
  // 一定不是数组或类数组。window.length = 0 && Function.prototype.length = 0,需要排除掉
  if (isFunction(obj) || isWindow(obj)) { return false; }
  // type === "array":是数组
  // length === 0:是空的类数组
  // typeof length === "number" && length > 0 && (length - 1) in obj:如果有length属性并且是一个数字且不是空的类数组,并且索引是按序增长的(最大索引在对象中)
  return type === "array" || length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj;
}

// 检测是否为纯粹的对象,如{}
// 实例对象不是纯粹的对象
var isPlainObject = function isPlainObject (obj) {
  var proto, Ctor;
  // 如果不存在 或者 基于toString检测的结果不是"[object Object]",一定不是纯对象
  if (!obj || toString.call(obj) !== "[object Object]") {
    return false;
  }
  // 获取当前值的原型链(直属类的原型链)
  proto = getProto(obj);
  // Object.create(null):这样创造的对象没有__proto__
  if (!proto) return true;
  // Ctor存储原型对象上的constructor这个属性,没有这个属性就是false
  Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
  // 条件成立说明原型上的构造函数是Object:当前对象就是Object的一个实例,并且obj.__proto__ === Object.prototype
  return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
}

// 检测是否为空对象
var isEmptyObject = function isEmptyObject (obj) {
  /**
   * jQ实现的方式(但这种for...in...的方式不太好):
   *  var name;
      for (name in obj) { return false; }
      return true;
   */
  // obj是undefined或者null,排除掉
  if (obj == null) { return false; }
  // 基于typeof检测不是object,不是对象类型
  if (typeof obj !== "object") { return false }
  // 是一个对象(纯粹但这或者特殊对象)
  var keys = Object.keys(obj); // Object.keys()[IE6,7,8不兼容] -> 拿到的私有的属性,symbol拿不到
  if (hasOwn.call(Object, 'getOwnPropertySymbols')) { // getOwnPropertySymbols兼容性差,看看Object有没有这个属性
    // 兼容这个属性的情况下,再去拼接
    keys = keys.concat(Object.getOwnPropertySymbols(obj));
  }
  return keys === 0;
}

// 检测是否为数字
var isNumeric = function isNumeric (obj) {
  var type = jQuery.type(obj);
  // 如果不是数字或者字符串,一定不是数字
  // 如果是字符串,需要处理:用之前的值与浮点型进行数学计算,如果结果是NaN,那就不是数字
  // 排除16进制数字
  // return (type === "number" || type === "string") && !isNaN(obj - parseFloat(obj)); // => 【jq实现的方式】
  return (type === "number" || type === "string") && !isNaN(+obj);
}

// 数组或类数组循环遍历
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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容