Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript类型判断 #8

Open
xunan007 opened this issue Feb 23, 2018 · 0 comments
Open

JavaScript类型判断 #8

xunan007 opened this issue Feb 23, 2018 · 0 comments

Comments

@xunan007
Copy link
Owner

xunan007 commented Feb 23, 2018

本文主要讲解如何准确判断JavaScript中出现的各种类型和对象。(基本类型、Object类、Window对象、纯对象plainObject、类数组)其中部分参考了jQuery的函数实现。

typeof

JavaScript定义的数据类型有UndefinedNullBooleanNumberStringObjectSymbol(ES6新增)。

其中typeof对大部分的数据类型都能够准确识别,如下:

typeof undefined // "undefined"
typeof null // "object"
typeof true // "boolean"
typeof 1 // "number"
typeof "s" // "string"
typeof {} // "object"
typeof function a() {} // "function"
typeof Symbol('2') // "symbol"

其中返回的字符串首字母都是小写的。

对于typeof null === 'object'来说,这其实是一个bug

在JavaScript中,Object下还有很多细分的类型,比如说DateRegExpErrorArrayFunction

typeof除了能够准确的判断出Function之外,对于其他细分类型均返回object

Object.prototype.toString()

toString方法被调用的时候,下面的步骤会被执行:

  • 如果this值是undefined,就返回[object Undefined]
  • 如果this的值是null,就返回[object Null]
  • O成为ToObject(this)的结果
  • class成为O的内部属性[[Class]]的值
  • 最后返回由"[object "class"]"三个部分组成的字符串

该方法至少可以识别14种类型。

// 以下是11种:
var number = 1;          // [object Number]
var string = '123';      // [object String]
var boolean = true;      // [object Boolean]
var und = undefined;     // [object Undefined]
var nul = null;          // [object Null]
var obj = {a: 1}         // [object Object]
var array = [1, 2, 3];   // [object Array]
var date = new Date();   // [object Date]
var error = new Error(); // [object Error]
var reg = /a/g;          // [object RegExp]
var func = function a(){}; // [object Function]

function checkType() {
    for (var i = 0; i < arguments.length; i++) {
        console.log(Object.prototype.toString.call(arguments[i]))
    }
}

checkType(number, string, boolean, und, nul, obj, array, date, error, reg, func)

// 还有不常见的Math、JSON
console.log(Object.prototype.toString.call(Math)); // [object Math]
console.log(Object.prototype.toString.call(JSON)); // [object JSON]

// 还有一个arguments
function a() {
    console.log(Object.prototype.toString.call(arguments)); // [object Arguments]
}
a();

type API

结合上面我们可以写一个type函数,其中基本类型值走typeof,引用类型值走toString

var class2type = {};

// 生成class2type映射
"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) {
    class2type["[object " + item + "]"] = item.toLowerCase();
})

function type(obj) {
    // 一箭双雕
    if (obj == null) {
        return obj + "";
    }
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[Object.prototype.toString.call(obj)] || "object" :
        typeof obj;
}

通过toLowerCase()小写化和typeof的结果是小写一致。

注意IE6中toString()会把UndefinedNull都识别为[object Object],所以加了一个判断,直接调用+来隐式toString()-> "null"

这里之所以class2type[Object.prototype.toString.call(obj)] || "object"是考虑到ES6新增的SymbolMapSet在集合class2type中没有,直接把他们识别成object

这个type其实就是jQuery中的type

isFunction

之后可以直接封装:

function isFunction(obj) {
    return type(obj) === "function";
}

数组

var isArray = Array.isArray || function( obj ) {
    return type(obj) === "array";
}

jQuery3.0中已经完全使用Array.isArray()

plainObject

plainObject翻译为中文即为纯对象,所谓的纯对象,就是该对象是通过{}new Object()创建的。

判断是否为“纯对象”,是为了和其他对象区分开比如说null、数组以及宿主对象(所有的DOMBOM都是数组对象)等。

jQuery中有提供了该方法的实现,除了规定该对象是通过{}new Object()创建的,且对象含有零个或者多个键值对外,一个没有原型(__proto__)的对象也是一个纯对象。

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

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

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

jQuery3.0版本的plainObject实现如下:

var toString = Object.prototype.toString;

var hasOwn = Object.prototype.hasOwnProperty;

function isPlainObject(obj) {
    var proto, Ctor;

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

    /**
     * getPrototypeOf es5 方法,获取 obj 的原型
     * 以 new Object 创建的对象为例的话
     * obj.__proto__ === Object.prototype
     */
    proto = Object.getPrototypeOf(obj);

    // 没有原型的对象是纯粹的,Object.create(null) 就在这里返回 true
    if (!proto) {
        return true;
    }

    /**
     * 以下判断通过 new Object 方式创建的对象
     * 判断 proto 是否有 constructor 属性,如果有就让 Ctor 的值为 proto.constructor
     * 如果是 Object 函数创建的对象,Ctor 在这里就等于 Object 构造函数
     */
    Ctor = hasOwn.call(proto, "constructor") && proto.constructor;

    // 在这里判断 Ctor 构造函数是不是 Object 构造函数,用于区分自定义构造函数和 Object 构造函数
    return typeof Ctor === "function" && hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object);
}

注意最后这一句非常的重要:

hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object)

hasOwn.toString调用的其实是Function.prototype.toString()而不是Object.prototype.toString(),因为hasOwnProperty是一个函数,它的原型是Function,于是Function.prototype.toString覆盖了Object.prototype.toString

Function.prototype.toString()会把整个函数体转换成一个字符串。如果该函数是内置函数的话,会返回一个表示函数源代码的字符串。比如说:

Function.prototype.toString(Object) === function Object() { [native code] }

所以如果此时对象不是由内置构造函数生成的对象,这个hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object)false

function Person(name) {
    this.name = name;
}
var person = new Person("Devin");
plainObject(person) === false; // true
// 其实就是`hasOwn.toString.call(Ctor) === "function Person(name) { this.name = name; }"

Window对象

Window对象有一个特性:Window.window指向自身。

Window.window === Window; //true

类数组对象

常见的类数组有函数的argumentsNodeList对象。

判断

对于类数组对象,只要该对象中存在length属性并且length为非负整数且在有限范围之内即可判断为类数组对象。

JavaScript权威指南中提供了方法:

function isArrayLike(o) {
    if (o && // o is not null, undefined, etc
        // o is an object
        typeof o === "object" &&
        // o.length is a finite number
        isFinite(o.length) &&
        // o.length is non-negative
        o.length >= 0 &&
        // o.length is an integer
        o.length === Math.floor(o.length) &&
        // o.length < 2^32
        o.length < 4294967296) //数组的上限值
      	return true;
    else 
      	return false;
}

以上的判断无论是真的数组对象或是类数组对象都会返回true,那我们如何区分到底是真的数组对象还是类数组对象?

其实只需要先判断是否为数组对象即可。

function utilArray(o) {
    if (Array.isArray(o)) {
        return 'array';
    }
    if (isArrayLike(o)) {
        return 'arrayLike';
    } else {
        return 'neither array nor arrayLike';
    }
}

类数组对象的特征

类数组对象并不关心除了数字索引和length以外的东西。

比如说:

var a = {"1": "a", "2": "b", "4": "c", "abc": "abc", length: 5};
Array.prototype.join.call(a, "+"); // +a+b++c

其中,'0''3'没有直接省略为两个undefined,同样的abc被忽略为undefined

如果length多出实际的位数会补undefined(空位也补充undefined),少位则截断后面的数组成员。

var a = {"1": "a", "2": "b", "4": "c", "abc": "abc", length: 6};
Array.from(a); // [undefined, "a", "b", undefined, "c", undefined]

var a = {"1": "a", "2": "b", "4": "c", "abc": "abc", length: 5};
Array.from(a); // [undefined, "a", "b", undefined, "c"]

var a = {"1": "a", "2": "b", "4": "c", "abc": "abc", length: 4};
Array.from(a); // [undefined, "a", "b", undefined]

类数组对象的转换

Array.from

该方法从一个类似数组或可迭代对象中创建一个新的数组实例。

Array.from('foo');
// ["f", "o", "o"]

Array.prototype.slice

该方法返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象。

var a = {"0":"a", "1":"b", "2":"c", length: 3};
Array.prototype.slice.call(a, 0); // ["a", "b", "c"]

ES6扩展运算符

var a = "hello";
[...a]; //["h", "e", "l", "l", "o"]

参考链接:

mqyqingfeng/Blog#28

mqyqingfeng/Blog#30

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant