阅读 122

JS之数据类型检测详解

javaScript中数据类型检测方法

type of 用来检测数据类型的运算符,返回结果是字符串

  • 局限性
    • typeof null -->'Object'
    • 对象类型除了可执行对象/函数,可以检测出来是"function",其余都是"Object"
    • 检测原始值对象的对象类型值,结果是"Object"
    • 基于typeof检测一个未被声明的变量,结果是"undeifined",而不会报错[基于let在后面声明则会报错] —-暂时性死区
    • 原因:按照计算机底层存储的二进制值,来进行检测的,它认为'000'开始的都是对象,而null全是0
  • 优点:简单、性能好

instanceof 临时用来检测对象类型数据类型【本意是检测当前实例是否属于某个类】

  • (value) instanceof (Ctor) --> true/false

  • 优点:细分对象数据类型值【但是不能因为结果是true,就说它是标准普通对象】

  • 弊端:

    1. 不能检测原始值类型值【但是原始值对应的对象格式实例则可以检测】
    2. 因为原型链指向是可以改动的,所以检测的结果不一定准确【但是:ES6由class构造的类,不允许改变其prototype】
  • 原理:按照原型链检测,只要当前检测的构造函数,它的原型对象,出现在了实例的原型链上,即检测结果就是TRUE,如果找到Object.prototype都没有找到,则结果为false

  • 浏览器实现:f instanceof fn --> Fn**[Symbol.hasInstance]**(f)

    • 【新版本浏览器】所有的函数都有这个属性,因为它是挂在Function.prototype上的。且**Fn[Symbol.hasInstance]=function(){}**不能改变该属性【不能加到自己的构造函数上】;但基于ES6 **class Fn {static[Symbol.hasInstance](obj){}}** 可以将该属性加到自己的构造函数上。

      Fn[Symbol.hasInstance] = function () {}; 
      //普通写法的构造函数,它的Symbol.hasInstance属性无法被直接修改的,
      //但是ES6语法中可以!!
      class Fn {
          static[Symbol.hasInstance](obj) {
              console.log('OK');
              if (Array.isArray(obj)) return true;
              return false;
          }
      }
      let f = new Fn;
      let arr = [10, 20, 30];
      console.log(f instanceof Fn); //=>false
      console.log(arr instanceof Fn); //=>true
      复制代码
  • 重写instanceof (可以检测原始类型值,因为Object.getPrototypeOf(obj),会有一个装箱的操作)

const instance_of = function instance_of(obj, Ctor) {
    if (Ctor == null) throw new TypeError('Right-hand side of instanceof is not an object');
    let type = typeof Ctor;
    if (!/^(object|function)$/i.test(type)) throw new TypeError('Right-hand side of instanceof is not an object');
    if (!/^function$/i.test(type)) throw new TypeError('Right-hand side of instanceof is not callable');
    if (!Ctor.prototype) throw new TypeError('Function has non-object prototype in instanceof check');

    let proto = Object.getPrototypeOf(obj);
    while (proto) {
        if (proto === Ctor.prototype) return true;
        proto = Object.getPrototypeOf(proto);
    }
    return false;
};
console.log(instance_of([], Array)); //=>true
console.log(instance_of([], RegExp)); //=>false
console.log(instance_of(1, Number)); //=>true
console.log(instance_of(Symbol(), Symbol)); //=>true
console.log(instance_of([], () => {})); //报错 */
复制代码

constructor 也是基于原型链,可以检测原始值

  • constructor的修改比instanceof更容易

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

这是JS中唯一检测数据类型没有任何瑕疵的【最准确】,除了性能比typeof差一点。

返回值:

  • “[object Number/String/Boolean/Null/Undefined/Symbol/Bigint/Object/Function/Array/RegExp/Date/Math/]”
  • 大部分内置类的原型上都有toString方法,一般都是转化为字符串了,但是**Object.prototype.toString**是检测数据类型的,返回值中包含自己所属的构造函数信息。[Symbol.toStringTag]
  1. 为啥要用call?

    ([]).toString() 调用的是Array.prototype上的toString,是转换为字符串

    ({}).toString() 调用的是Object.prototype上的toString,是检测数据类型的「方法中的this是谁,就是检测谁的类型」 -> 我们需要把Object.prototype.toString执行,而且还要改变其中的this

    -> ({}).toString.call(10) =>“[object Number]”

  2. 检测返回值遵循的规则

    • 一般都是返回当前实例所属的构造函数信息
    • 但是如果实例对象拥有 Symbol.toStringTag 属性,属性值是啥,最后返回的就是啥,例如:Math[Symbol.toStringTag]="Math"  => Object.prototype.toString.call(Math)  “[object Math]”
var Fn = function Fn(){};
var f = new Fn;
console.log(Object.prototype.toString.call(f));
//本来的结果是"[object Object]",如何改为"[object Fn]"
class Fn {
    // constructor() {
    //     this[Symbol.toStringTag] = 'Fn';
    // }
    [Symbol.toStringTag] = 'Fn';
}
let f = new Fn;
// console.log(Object.prototype.toString.call(Fn)); //“[object Function]”
console.log(Object.prototype.toString.call(f)); //“[object Fn]”
复制代码

jQuery中的数据类型检测方法

var getProto = Object.getPrototypeOf;
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); //Object.toString()  => "function Object() { [native code] }"

// 检测是否是一个函数
var isFunction = function isFunction(obj) {
    return typeof obj === "function" && typeof obj.nodeType !== "number" &&
        typeof obj.item !== "function";
};

// 检测是否是一个window对象
var isWindow = function isWindow(obj) {
    // window.window===window  window的独有属性
    return obj != null && obj === obj.window;
};

// 标准的检测数据类型的办法
/* var arr = ["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object", "Error", "Symbol"];
arr.forEach(function (name) {
    class2type["[object " + name + "]"] = name.toLowerCase();
});
var toType = function toType(obj) {
    if (obj == null) return obj + "";
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[toString.call(obj)] || "object" :
        typeof obj;
}; */
var toType = function toType(obj) {
    if (obj == null) return obj + "";
    var reg = /^\[object ([a-zA-Z0-9]+)\]$/i;
    return typeof obj === "object" || typeof obj === "function" ?
        reg.exec(toString.call(obj))[1].toLowerCase() :
        typeof obj;
};

// 检测是否为数组或者类数组
var isArrayLike = function isArrayLike(obj) {
    var length = !!obj && "length" in obj && obj.length,
        type = toType(obj);
    if (isFunction(obj) || isWindow(obj)) return false;
		//因为function和window都有自己的length所以要排除掉
    return type === "array" || length === 0 ||
        typeof length === "number" && length > 0 && (length - 1) in obj;
				//&& 符的优先级高于 ||  因此$$ 链接的当做一个整体
};

// 检测是否为纯粹的对象「直属类是Object || Object.create(null)」
var isPlainObject = function isPlainObject(obj) {
    var proto, Ctor;
    if (!obj || toString.call(obj) !== "[object Object]") return false;
    proto = getProto(obj);
    if (!proto) return true;
    Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
    return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
};

// 检测是否是空对象
var isEmptyObject = function isEmptyObject(obj) {
		//jQuery源码用的for in 循环 但是for in 循环有很多问题,无法获取Symbol类型属性,会获取原型上属性等
    var keys = Object.keys(obj);
    if (typeof Symbol !== "undefined") keys = keys.concat(Object.getOwnPropertySymbols(obj));
    return keys.length === 0;
};

// 检测是否是数字
var isNumeric = function isNumeric(obj) {
    var type = toType(obj);
    return (type === "number" || type === "string") && !isNaN(obj);
};
复制代码
文章分类
前端
文章标签