JS 类型判断 typeof PK {}.toString.call(obj);

阅读 836
收藏 35
2016-10-09
原文链接:www.talkingcoder.com

先看typeof 

<!doctype html>
<html lang="en">

    <head>
        <meta charset="UTF-8" />
        <script type="text/javascript" src="" ></script>
        <title>Document</title>
        <style type="text/css">

        </style>
        <script type="text/javascript">
            var a;
            console.log("1:" + typeof a);
            var b = null;
            console.log("2:" + typeof b);
            var c = undefined;
            console.log("3:" + typeof c);
            var d = new Object;
            console.log("4:" + typeof d);
            var e = function() {};
            console.log("5:" + typeof e);
            var f = {};
            console.log("6:" + typeof f);
            var g = '';
            console.log("7:" + typeof g);
            var h = [];
            console.log("8:" + typeof h);
            var i = true;
            console.log("9:" + typeof i);
            var j = 123;
            console.log("10:" + typeof j);
            var k = NaN;
            console.log("11:" + typeof k);
            var l = /^[-+]?\d+$/;
            console.log("12:" + typeof l);
        </script>

    </head>

    <body>

    </body>

</html>

按照上面的打印结果,总结出下面要注意的几点

  • typeof (引用类型) 除了函数, 都是 'object',比如 typeof /123/

  • typeof null 为'object'

  • typeof undefined 为 'undefined',通常, 如果使用两等号, null == undefined 为真.

  • 转换为数字的常见用法 "10"-0或+"10", 如果没有转换成功,返回NaN,由于NaN 的一个特性: NaN != NaN,故判断转换成功与否的常见做法: (这也是我参见 jQuery的源码发现的,jQuery源码读100遍都不为过)

      ("10x" - 0) == ("10x" - 0);
      // 结果为假!   


再看看constructor 和 instanceof

instanceof 用于判断一个变量是否某个对象的实例,或用于判断一个变量是否某个对象的实例;
constructor 用于判断一个变量的原型,constructor 属性返回对创建此对象的数组函数的引用。
Javascript中对象的prototype属性的解释是:返回对象类型原型的引用。
<!DOCTYPE html>
<html>

    <head>
        <meta charset="utf-8" />
        <title></title>
        <script type="text/javascript">
            console.log("----------------Number---------------");
            var A = 123;
            console.log(A instanceof Number); //false
            console.log(A.constructor == Number); //true
            console.log(A.constructor);
            console.log("----------------String---------------");
            var B = "javascript";
            console.log(B instanceof String); //false
            console.log(B.constructor == String); //true
            console.log(B.constructor);
            console.log("----------------Boolean---------------");
            var C = true;
            console.log(C instanceof Boolean); //false
            console.log(C.constructor == Boolean); //true
            console.log(C.constructor);
            console.log("----------------null---------------");
            var D = null;
            console.log(D instanceof Object); //false
            //console.log(D.constructor == null); //报错
            //console.log(D.constructor); //报错
            console.log("----------------undefined---------------");
            var E = undefined;
            //console.log(E instanceof undefined); // //报错
            //console.log(E.constructor == undefined); //报错
            //console.log(E.constructor); //报错
            console.log("----------------function---------------");
            var F = function() {};
            console.log(F instanceof Function);
            console.log(F.constructor == Function);
            console.log(F.constructor);
            console.log("----------------new function---------------");
            function SB() {};
            var G = new SB();
            console.log(G instanceof SB);
            console.log(G.constructor == SB);
            console.log(G.constructor);
            console.log("----------------new Object---------------");
            var H = new Object;
            console.log(H instanceof Object);
            console.log(H.constructor == Object);
            console.log(H.constructor);
            console.log("-----------------Array--------------");
            var I = [];
            console.log(I instanceof Array);
            console.log(I.constructor == Array);
            console.log(I.constructor);
            console.log("-----------------JSON--------------");
            var J = {
                "good": "js",
                "node": "very good"
            };
            console.log(J instanceof Object);
            console.log(J.constructor == Object);
            console.log(J.constructor);
        </script>
    </head>

    <body>

    </body>

</html>

再看看{}.toString.call(obj)

 <!doctype html>
<html lang="en">

	<head>
		<meta charset="UTF-8" />
		<title>Document</title>
		<script type="text/javascript">
			console.log({}.toString.call(1));
			console.log({}.toString.call("11"));
			console.log({}.toString.call(/123/));
			console.log({}.toString.call({}));
			console.log({}.toString.call(function() {}));
			console.log({}.toString.call([]));
			console.log({}.toString.call(true));
			console.log({}.toString.call(new Date()));
			console.log({}.toString.call(new Error()));
			console.log({}.toString.call(null));
			console.log({}.toString.call(undefined));
			console.log(String(null));
			console.log(String(undefined));
		</script>
	</head>

	<body>

	</body>

</html>

使用jQuery中的方法$.type()

现在看看jQuery是怎么做的

// 先申明一个对象,目的是用来做映射
var class2type = {};
// 申明一个core_toString() 的方法,得到最原始的toString() 方法,因为在很多对象中,toStrintg() 已经被重写 
var core_toString() = class2type.toString;
// 这里为 toStrintg() 后的结果和类型名做一个映射,申明一个core_toString() 后的结果,而值就是类型名
jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
    class2type[ "[object " + name + "]" ] = name.toLowerCase();
});
<!doctype html>
<html lang="en">

	<head>
		<meta charset="UTF-8" />
		<script type="text/javascript" src="" ></script>
		<title>Document</title>
		<script type="text/javascript">
			console.log($.type(1));
			console.log($.type("11"));
			console.log($.type(/123/));
			console.log($.type({}));
			console.log($.type(function() {}));
			console.log($.type([]));
			console.log($.type(true));
			console.log($.type(new Date()));
			console.log($.type(new Error()));
			console.log($.type(null));
			console.log($.type(undefined));
			console.log(String(null));
			console.log(String(undefined));
		</script>
	</head>

	<body>

	</body>

</html>

上面的打印结果与

class2type[ "[object " + name + "]" ] = name.toLowerCase();

不谋而合!

这是jQuery.type 的核心方法

type: function( obj ) {
    if ( obj == null ) {
        return String( obj );
    }
    // Support: Safari <= 5.1 (functionish RegExp)
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[ core_toString.call(obj) ] || "object" :
        typeof obj;
},

注意,为什么把 null 或者 undefined 单独讨论呢,因为 在一些版本浏览器中

console.log(core_toString.call(null));
console.log(core_toString.call(undefined));

这是会报错的!

     如果是对象类型,另:由于 在一些低版本的浏览器中,typeof /123/ 会返回的是 "function" 而不是 "object",所以这里要判断是否是函数,要明白 这里的 typeof obj === function 不是为了函数讨论的,因为函数本身就可以通过typeof 来得到类型.

 typeof obj === "object" || typeof obj === "function" ?
        class2type[ core_toString.call(obj) ]

就直接返回class2type 中键值对的结果,,如果不是,那么一定就是基本类型, 通过 typeof 就可以啦.

class2type[ core_toString.call(obj) ] || "object" :
// 这是防止一些未知情况的,如果未取到,就返回object

但是 jQuery.type 有一个很大的缺陷

这是一个自定义类型

function Person() {
    this.name = 'pawn';
}
var p = new Person();
console.log($.type(p));
console.log({}.toString.call(p));

// 注意,这里会打印 [object Object],通过上面的方法,无法得到精确的自定义类型这也是 它的一个大缺陷了!

下面,我们通过构造函数的方式来获取精确类型

通过构造函数来获取类型

在理解这个方法之前,需要理解两个点

prorotype 原型属性

       我们知道,任何对象或者函数都直接或者间接的继承自Object 或者 Function, (其实最终Function 是继承自 Object 的,这属于原型链的知识了,见下图)。那么,任何一个对象都具有原型对象 __proto__ (这个对象只在chrome 和 firefox 暴露,但是在其他浏览器中也是存在的),这个原型对象就是这个对象的构造函数的原型属性(这里可能有点绕,直接上图).

​由于 任何函数都具有 原型属性prototype,并且这个原型属性具有一个默认属性 constructor,它是这个函数的引用,看下面的代码

  function Person(){
      this.name = 'pawn';
  }
  console.log(Person.prototype.constructor === Person);   //true

发现,这两个东西其实一个东西

但是,在某些情况下,需要这么写

  function Person(){
      this.name = 'pawn';
  }
  Person.protype = {
      XX: ... ,
      xx: ... ,
      ...
  }

这么做,就会覆盖原本的 protype 方法,那么construcor 就不存在了,这是,必须要显示的申明这个对象,

construction: Person, 这句话非常重要,作用是修正this指向

  Person.protype = {
      construction: Person,   //这句话的作用是修正this指向
      XX: ... ,
      xx: ... ,
      ...
  }

在jQuery的中,就是这么做的,

  jQuery.fn = jQuery.prototype = {
    constructor: jQuery,
    init: function( selector, context, rootjQuery ) {
        var match, elem;

关于 jQuery对象封装的方式 也是非常值得研究

注意,这里已经不是熟悉 [object Object],而是 已经重写了.

也就是,如果调用一个函数的toString() 方法.那么就会打印这个函数的函数体.

如何通过构造函数来获得变量的类型?

判断是否是基本类型

   var getType = function(obj){
       if(obj == null){
          return String(obj);
       }
       if(typeof obj === 'object' || typeof obj === 'fucntion'){
           ...
       }else{
           // 如果不是引用类型,那么就是基本类型
           return typeof obj
       }
   }

如果是对象或者函数类型

   function Person(){
       this.name = 'pawn';
   }
   var p = new Person();
   console.log(p.constructor);   //返回function Person(){...}

现在要做的事 : 如何将Person 提取出来呢?
毋庸置疑,字符串切割那一套肯定可以办到,但是太 low 啦!
这里,我使用正则将Person提取出来

   var regex = /function\s(.+?)\(/
   function Person(){
    this.name = 'pawn';
   }
   var p = new Person();
   var c = p.constructor
   var regex = /function\s(.+?)\(/;
   console.log('|' + regex.exec(c)[1] + '|');

 

其实,除了上面的正则,每个函数还有一个name属性,返回函数名,但是ie8 是不支持的.

因此上面的代码可以写为:

var getType = function(obj){
    if(obj == null){
        return String(obj);
    }
    if(typeof obj === 'object' || typeof obj === 'function'){ 
        var constructor = obj.constructor;
        if(constructor && constructor.name){
            return constructor.name;
        }
        var regex = /function\s(.+?)\(/;
        return regex.exec(c)[1];
    }else{
        // 如果不是引用类型,那么就是基本;类型
        return typeof obj;
    }
};

但是上面的代码太丑啦,将其简化

简化

var getType = function(obj){
    if(obj == null){
        return String(obj);
    }
    if(typeof obj === 'object' || typeof obj === 'function'){ 
        return obj.constructor && obj.constructor.name.toLowerCase() || 
          /function\s(.+?)\(/.exec(obj.constructor)[1].toLowerCase();
    }else{
        // 如果不是引用类型,那么就是基本类型
        return typeof obj;
    }
};

还是比较麻烦,继续简化

var getType = function(obj){
    if(obj == null){
       return String(obj);
    }
    return typeof obj === 'object' || typeof obj === 'function' ?
      obj.constructor && obj.constructor.name && obj.constructor.name.toLowerCase() ||
          /function\s(.+?)\(/.exec(obj.constructor)[1].toLowerCase():
      typeof obj;
};

好了,已经全部弄完了,写个代码测试一下:

function Person(){
    this.name = 'pawn';
}
var p = new Person();

console.log(getType(p));
console.log(getType(1));
console.log(getType("a"));
console.log(getType(false));
console.log(getType(/123/));
console.log(getType({}));
console.log(getType(function(){}));
console.log(getType(new Date()));
console.log(getType(new Error()));
console.log(getType( null));
console.log(getType( undefined));

1.有时会看到Object.prototype.toString.call()

2.toString()是一个怎样的方法,他定义在哪里?


3.call.apply.bind可以吗?


4.为神马要去call呢?用 Object.prototype.toString.call(obj) 而不用 obj.toString() 呢?

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <script type="text/javascript">
            function A(){
                this.say=function(){
                    console.log("我是1");
                }
            }
            function B(){
                this.say=function(){
                    console.log("我是2");
                }
            }
            var a=new A();
            var b=new B();
            a.say.call(b);    //我是1
        </script>
    </head>
    <body>
    </body>
</html>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <script type="text/javascript">
            function A(){
                this.name='SB';
                this.say=function(){
                    console.log("我是1");
                }
            }
            function B(){
                A.call(this);   //B继承A,重写say方法
                this.say=function(){
                    console.log("我是2");
                }
            }
            var a=new A();
            var b=new B();
            console.log(b.name);  //SB
            b.say();         //我是2
            a.say.call(b);    //我是1
        </script>
    </head>
    <body>
    </body>
</html>

就是怕你重写了toString,所以才要用object 最原始的他toString,所以才去call。

5.Object.prototype.toString方法的原理是什么?

参考链接:www.jb51.net/article/799…

在JavaScript中,想要判断某个对象值属于哪种内置类型,最靠谱的做法就是通过Object.prototype.toString方法.

12var arr = [];console.log(Object.prototype.toString.call(arr)) //"[object Array]"

本文要讲的就是,toString方法是如何做到这一点的,原理是什么.

ECMAScript 3

在ES3中,Object.prototype.toString方法的规范如下:

115.2.4.2 Object.prototype.toString()

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

1. 获取this对象的[[Class]]属性的值.

2. 计算出三个字符串"[object ", 第一步的操作结果Result(1), 以及 "]"连接后的新字符串.

3. 返回第二步的操作结果Result(2).

[[Class]]是一个内部属性,所有的对象(原生对象和宿主对象)都拥有该属性.在规范中,[[Class]]是这么定义的

[[Class]]一个字符串值,表明了该对象的类型.


然后给了一段解释:

所有内置对象的[[Class]]属性的值是由本规范定义的.所有宿主对象的[[Class]]属性的值可以是任意值,甚至可以是内置对象使用过的[[Class]]属性的值.[[Class]]属性的值可以用来判断一个原生对象属于哪种内置类型.需要注意的是,除了通过Object.prototype.toString方法之外,本规范没有提供任何其他方式来让程序访问该属性的值(查看 15.2.4.2).

也就是说,把Object.prototype.toString方法返回的字符串,去掉前面固定的"[object "和后面固定的"]",就是内部属性[[class]]的值,也就达到了判断对象类型的目的.jQuery中的工具方法$.type(),就是干这个的.

在ES3中,规范文档并没有总结出[[class]]内部属性一共有几种,不过我们可以自己统计一下,原生对象的[[class]]内部属性的值一共有10种.分别是:"Array", "Boolean", "Date", "Error", "Function", "Math", "Number", "Object", "RegExp", "String".

ECMAScript 5

在ES5.1中,除了规范写的更详细一些以外,Object.prototype.toString方法和[[class]]内部属性的定义上也有一些变化,Object.prototype.toString方法的规范如下:

15.2.4.2 Object.prototype.toString ( )

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

如果this的值为undefined,则返回"[object Undefined]".

如果this的值为null,则返回"[object Null]".

让O成为调用ToObject(this)的结果.

让class成为O的内部属性[[Class]]的值.

返回三个字符串"[object ", class, 以及 "]"连接后的新字符串.

可以看出,比ES3多了1,2,3步.第1,2步属于新规则,比较特殊,因为"Undefined"和"Null"并不属于[[class]]属性的值,需要注意的是,这里和严格模式无关(大部分函数在严格模式下,this的值才会保持undefined或null,非严格模式下会自动成为全局对象).第3步并不算是新规则,因为在ES3的引擎中,也都会在这一步将三种原始值类型转换成对应的包装对象,只是规范中没写出来.ES5中,[[Class]]属性的解释更加详细:

所有内置对象的[[Class]]属性的值是由本规范定义的.所有宿主对象的[[Class]]属性的值可以是除了"Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", "String"之外的的任何字符串.[[Class]]内部属性是引擎内部用来判断一个对象属于哪种类型的值的.需要注意的是,除了通过Object.prototype.toString方法之外,本规范没有提供任何其他方式来让程序访问该属性的值(查看 15.2.4.2).

和ES3对比一下,第一个差别就是[[class]]内部属性的值多了两种,成了12种,一种是arguments对象的[[class]]成了"Arguments",而不是以前的"Object",还有就是多个了全局对象JSON,它的[[class]]值为"JSON".第二个差别就是,宿主对象的[[class]]内部属性的值,不能和这12种值冲突,不过在支持ES3的浏览器中,貌似也没有发现哪些宿主对象故意使用那10个值.

ECMAScript 6

ES6目前还只是工作草案,但能够肯定的是,[[class]]内部属性没有了,取而代之的是另外一个内部属性[[NativeBrand]].[[NativeBrand]]属性是这么定义的:

 

内部属性属性值描述
[[NativeBrand]]枚举NativeBrand的一个成员.该属性的值对应一个标志值(tag value),可以用来区分原生对象的类型.

 

[[NativeBrand]]属性的解释:

[[NativeBrand]]内部属性用来识别某个原生对象是否为符合本规范的某一种特定类型的对象.[[NativeBrand]]内部属性的值为下面这些枚举类型的值中的一个:NativeFunction, NativeArray, StringWrapper, BooleanWrapper, NumberWrapper, NativeMath, NativeDate, NativeRegExp, NativeError, NativeJSON, NativeArguments, NativePrivateName.[[NativeBrand]]内部属性仅用来区分区分特定类型的ECMAScript原生对象.只有在表10中明确指出的对象类型才有[[NativeBrand]]内部属性.

表10 — [[NativeBrand]]内部属性的值

 

属性值对应类型
NativeFunctionFunction objects
NativeArrayArray objects
StringWrapperString objects
BooleanWrapperBoolean objects
NumberWrapperNumber objects
NativeMathThe Math object
NativeDateDate objects
NativeRegExpRegExp objects
NativeErrorError objects
NativeJSONThe JSON object
NativeArgumentsArguments objects
NativePrivateNamePrivate Name objects

 

可见,和[[class]]不同的是,并不是每个对象都拥有[[NativeBrand]].同时,Object.prototype.toString方法的规范也改成了下面这样:

15.2.4.2 Object.prototype.toString ( )

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

如果this的值为undefined,则返回"[object Undefined]".

如果this的值为null,则返回"[object Null]".

让O成为调用ToObject(this)的结果.

如果O有[[NativeBrand]]内部属性,让tag成为表29中对应的值.

否则

让hasTag成为调用O的[[HasProperty]]内部方法后的结果,参数为@@toStringTag.

如果hasTag为false,则让tag为"Object".

否则,

让tag成为调用O的[[Get]]内部方法后的结果,参数为@@toStringTag.

如果tag是一个abrupt completion,则让tag成为NormalCompletion("???").

让tag成为tag.[[value]].

如果Type(tag)不是字符串,则让tag成为"???".

如果tag的值为"Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp",或

者"String"中的任一个,则让tag成为字符串"~"和tag当前的值连接后的结果.

返回三个字符串"[object ", tag, and "]"连接后的新字符串.

表29 — [[NativeBrand]] 标志值

[[NativeBrand]]值标志值
NativeFunction"Function"
NativeArray"Array"
StringWrapper"String"
BooleanWrapper"Boolean"
NumberWrapper"Number"
NativeMath"Math"
NativeDate"Date"
NativeRegExp"RegExp"
NativeError"Error"
NativeJSON"JSON"
NativeArguments"Arguments"

 

 

可以看到,在规范上有了很大的变化,不过对于普通用户来说,貌似感觉不到.

也许你发现了,ES6里的新类型Map,Set等,都没有在表29中.它们在执行toString方法的时候返回的是什么?

console.log(Object.prototype.toString.call(Map())) //"[object Map]"

console.log(Object.prototype.toString.call(Set())) //"[object Set]"

其中的字符串"Map"是怎么来的呢:

15.14.5.13 Map.prototype.@@toStringTag

@@toStringTag 属性的初始值为字符串"Map".

由于ES6的规范还在制定中,各种相关规定都有可能改变,所以如果想了解更多细节.看看下面这两个链接,现在只需要知道的是:[[class]]没了,使用了更复杂的机制.

以上所述是小编给大家分享的JavaScript中Object.prototype.toString方法的原理,希望对大家有所帮助!


评论