-
- typeof类型检测
作用:用于判断一个一个表达式,(对象或者原始值),返回一个字符串。
var a;
var b=null;
var c=true;
var d=1;
var e='s';
var f=[1,2];
var g={name:'kk'};
var h=function(){};
var i=new h();
console.log(typeof(a));
console.log(typeof(b));
console.log(typeof(c));
console.log(typeof(d));
console.log(typeof(e));
console.log(typeof(f));
console.log(typeof(g));
console.log(typeof(h));
console.log(typeof(i));
结果
typeof返回结果
可以发现
typeof返回的内容有undefinded,boolen,number,string,object,function. null返回的是object而undefined还是undefined。数组返回的也是object,typeof不区分对象是由谁创建的,包括内置对象 Instanceof
instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
使用规则:
object instanceof constructor
要求前面是个对象,后面是一个构造函数。而且返回的是布尔型的,不是true就是false。
常用使用:
由于typeof只能判断类型,所以,数组和对象返回的都是object,这时就需要使用instanceof来判断是否是
用法:o instanceof c,如果o继承自c.propotype(可以不是直接继承),则返回true。如果o不是对象的话则返回false
var a;
var b=null;
var c=true;
var d=1;
var e='s';
var f=[1,2];
var g={name:'kk'};
var h=function(){};
var i=new h();
var j=/\s/;
console.log(a instanceof Object);
console.log(b instanceof Object);
console.log(c instanceof Boolean);
console.log(c instanceof Object);
console.log(d instanceof Object);
console.log(e instanceof Object);
console.log(f instanceof Object);
console.log(f instanceof Array);
console.log(g instanceof Object);
console.log(h instanceof Object);
console.log(i instanceof h);
console.log(j instanceof RegExp);
结果
instanceof
可以发现
instanceof是检测对象用的,可以检测对象的特定类型,只要左边不是对象就返回false,如 var a=true; a instanceof Boolean==>false, function的instanceof是object
instanceof/constructor
->检测某一个实例是否属于某一个类
->我们使用instanceof/constructor可以检测数组和正则了
console.log([] instanceof Array);//->true
console.log(/^$/ instanceof RegExp);//->true
console.log([] instanceof Object);//->true
console.log([].constructor === Array);//->true
console.log([].constructor === Object);//->false 我们的constructor可以避免instanceof检测的时候,用Object也是true的问题
console.log({}.constructor === Object);
局限性:
1)用instanceof检测的时候,只要当前的这个类在实例的原型链上(可以通过原型链__proto__找到它),检测出来的结果都是true
var oDiv = document.getElementById("div1");
//HTMLDivElement->HTMLElement->Element->Node->EventTarget->Object
console.log(oDiv instanceof HTMLDivElement);//->true
console.log(oDiv instanceof Node);//->true
console.log(oDiv instanceof Object);//->true
2)基本数据类型的值是不能用instanceof来检测的
->console.log(1 instanceof Number);//->false
数组创建的两种方式(对象、正则、函数...)
->对于引用数据类型来说,我们两种方式创建出来的都是所属类的实例,而且都是对象数据类型的值,是没有区别的
var ary = [];
var ary = new Array;
->对于基本数据类型来说,虽然不管哪一种方式创建出来的都是所属类的一个实例(在类的原型上定义的方法都可以使用),但是字面量方式创建出来的是基本数据类型,而实例方式创建出来的是对象数据类型
var num1 = 1;
var num2 = new Number("1");
console.log(typeof num1,typeof num2);//->"number" "object"
3)在类的原型继承中,instanceof检测出来的结果其实是不准确的
function Fn() {}
var f = new Fn();
console.log(f instanceof Array);//->false f不是一个数组,它就是一个普通的实例(普通的对象)
->虽然我们的Fn继承了Array,但是f没有length和数字索引哪些东西,所以f应该不是数组才对,但是用instanceof检测的结果却是true,因为f虽然不是数组,但是在f的原型链上可以找到Array
function Fn() {
}
Fn.prototype = new Array;//->Fn子类继承了Array这个父类中的属性和方法
var f = new Fn;
console.log(f instanceof Array);//->true
3.Object.propotype.toString.call(obj).slice(8,-1)
类属型法。
很多对象重写了toSting,需要调用Object对象上的,返回形式[object class].
function classof(o){
if(o===null) return 'Null';
if(o===undefined) return 'Undefined';
return Object.prototype.toString.call(o).slice(8,-1);
}
var a;
var b=null;
var c=false;
var d=1;
var e='s';
var f=[1,2];
var g={name:'kk'};
var h=function(){};
var i=new h();
var j=/\s/;
console.log(classof(a));
console.log(classof(b));
console.log(classof(c));
console.log(classof(d));
console.log(classof(e));
console.log(classof(f));
console.log(classof(g));
console.log(classof(h));
console.log(classof(i));
console.log(classof(j));
结果
类属性法
可以发现
能够识别内置对象但不能识别自定义对象的类别。 返回Null,Undefined,Boolean,Number,String,Array,Object,自定义对象。
4.constructor法
原理:返回构造函数的名称。
Function.prototype.getName=function(){
if('name' in this) return this.name;
return this.name=this.toString().match(/function\s*([^(]*)\(/);
}
function typeAndValue(x){
if(x===null) return 'Null';
if(x===undefined) return 'Undefined';
return x.constructor.getName();
}
var a;
var b=null;
var c=false;
var d=1;
var e='s';
var f=[1,2];
var g={name:'kk'};
var h=function(){};
var i=new h();
var j=/\s/;
console.log(typeAndValue(a));
console.log(typeAndValue(b));
console.log(typeAndValue(c));
console.log(typeAndValue(d));
console.log(typeAndValue(e));
console.log(typeAndValue(f));
console.log(typeAndValue(g));
console.log(typeAndValue(h));
console.log(typeAndValue(i));
console.log(typeAndValue(j));
结果
constructor法
可以看出
能够返回自定义对象与内置对象的名称 null与undefined没有constructor需要自己处理。 有的对象没有constructor需要特殊处理。
原型链上没有constructor的情况
Function.prototype.getName=function(){
if('name' in this) return this.name;
return this.name=this.toString().match(/function\s*([^(]*)\(/);
}
function typeAndValue(x){
if(x===null) return 'Null';
if(x===undefined) return 'Undefined';
return x.constructor.getName();
}
function Range(from,to){
this.from=from;
this.to=to;
}
Range.prototype={
toString:function(){
return '('+this.from+','+this.to+')'
}
}
var r=new Range(3,4);
console.log(typeAndValue(r))
console.log(Object.prototype.toString(r).slice(8,-1));
结果
都为object
几种方法的综合
function type(o){
var t,c,n
if(o===null) return 'Null';//处理Null
if(o!==o) return 'NaN';//处理NaN特殊
if((t=typeof(o))!=='object') return t;//处理string,boolean,number,function,undefined。
if((c=classof(o))!=='Object') return c//处理内置对象Date
if(o.constructor && typeof(o.constructor)==='function'&&(n=o.constructor.getName())) {return n;}//处理自定义对象
return 'object';
}
function classof(o){
return Object.prototype.toString.call(o).slice(8,-1);
}
Function.prototype.getName=function(){
if('name' in this) return this.name;
return this.name=this.toString().match(/function\s*([^(]*)\(/)[1];
}
var a;
var b=null;
var c=false;
var d=1;
var e='s';
var f=[1,2];
var g={name:'kk'};
var h=function(){};
var i=new h();
var j=/\s/;
console.log(type(a));
console.log(type(b));
console.log(type(c));
console.log(type(d));
console.log(type(e));
console.log(type(f));
console.log(type(g));
console.log(type(h));
console.log(type(i));
console.log(type(j));
结果
综合
可以发现
- 以typeof处理基本类型(undefined,boolean,number,string,function)第一个字母是小写的。
- 以类属性法处理内置对象Object.prototype.toString.call(obj).slice(8,-1)(Date,RegExp),第一个字母大写的
以constructor法处理自定义类别和普通的Object(Object,h),注意Object是首字母大写
回顾 JS 的数据类型
我们先来回顾一下最新的 ECMAScript 标准的8种数据类型:
7种原始类型(或称作基本类型):Boolean、Null、Undefined、Number、BigInt、String、Symbol 和 Object。
基本类型(基本数值、基本数据类型)是一种既非对象也无方法的数据。在 JavaScript 中,共有7种基本类型:string,number,bigint,boolean,null,undefined,symbol (ECMAScript 2016新增)。
多数情况下,基本类型直接代表了最底层的语言实现。
所有基本类型的值都是不可改变的。但需要注意的是,基本类型本身和一个赋值为基本类型的变量的区别。变量会被赋予一个新值,而原值不能像数组、对象以及函数那样被改变。
何谓不可改变
var bar = "baz";
bar[0] = "a";
console.log(bar[0]); // "b"
console.log(bar);// "baz"
基本类型的包装对象
除了 null 和 undefined 外,所有基本类型都有其对应的包装对象:
- String 为字符串基本类型。
- Number 为数值基本类型。
- BigInt 为大整数基本类型。
- Boolean 为布尔基本类型。
- Symbol 为字面量基本类型。
这个包裹对象的 valueOf() 方法返回基本类型值。
var s_prim = "foo";
var s_obj = new String(s_prim);
console.log(typeof s_prim);//"string"
console.log(typeof s_obj);//"object"
console.log(typeof s_obj.valueOf());//"string"
typeof 可能的返回值表
类型 | 结果 |
Undefined | “undefined” |
Null | “object” (历史遗留问题) |
Boolean | “boolean” |
Number | “number” |
BigInt | “bigint” |
String | “string” |
Symbol (ECMAScript 2015 新增) | “symbol” |
宿主对象(由 JS 环境提供) | 取决于具体实现 |
Function 对象 (按照 ECMA-262 规范实现 [[Call]]) | “function” |
其他任何对象 | “object” |
下表总结了 typeof 可能的返回值:
分析 typeof 的误区
我之前在分析 typeof 函数和对象的时候,是根据这种方式来分析的:
以前面的 String 为例:
var s_obj = new String(s_prim);
console.log(typeof String); // "function"
// 使用 typeof 调用函数的时候,是把函数当做函数对象来调用的,又因为所有的函数对象都是由 Function 构造出来的,所以:String.__Proto__ === Function.prototype,所以结果返回了 "function"console.log(typeof s_obj); // "object"// 1. 因为 s_obj 是一个对象,它是由 String 构造出来的,所以:S_obj.__proto__ === String.prototype;// 2. 又因为所有的构造函数都是由 Object 派生出来的,所以:String.prototype.__proto__ === Object.prototype;// 3. 所以 s_obj 返回 "object";
因为原型链是这样的。但是问题点出在,typeof 是提前把函数的原型链终止了吗?
// 因为Function.prototype.__proto__ === Object.prototype;
这么看来,如果不提前终止原型链,那么 typeof 的结果应该返回 “object” 才对啊。
一小段 JS 历史
带着这个问题,我们来看一点 JavaScript 的历史。
大家都知道“typeof null”的bug,它是在JavaScript的第一版就存在的。在这个版本中,值以32位的单位存储,包括一个小型类型标记(1-3位)和值的实际数据。类型标记存储在单元的较低位上。一共有5种类型:
- 000: object,表示这个数据是一个对象的引用。
- 1: int,表示这个数据是一个31位的有符号整型。
- 010: double,表示这个数据是一个双精度浮点数的引用。
- 100: string,表示这个数据是一个字符串的引用。
- 110: boolean,表示这个数据是一个布尔值。
两个值比较特殊:
undefined (JSVAL_VOID) 的值是 −2^30 整型(一个超出整型范围的数)。
null (JSVAL_NULL) 是机器码空指针。或者是:一个对象类型标记加上一个为零的引用。
在 javascript 的最初版本中,使用的 32 位系统,为了性能考虑使用低位存储了变量的类型信息:
- 000:对象
- 1:整数
- 010:浮点数
- 100:字符串
- 110:布尔
有 2 个值比较特殊:
- undefined:用 - (−2^30)整型(一个超出整型范围的数)表示。
- null:对应机器码的NULL指针,一般是全零。
所以,typeof 在判断 null 的时候就出现问题了,由于null的所有机器码均为0,因此直接被当做了对象来看待;
现在应该很清楚为什么 typeof 认为 null 是一个对象了:它检查了类型标记和类型标记表示的对象。
mozilla 的 typeof 的源码
回顾完第一版的历史,我们再来看一下 1998 年 mozilla 的 typeof 的源码:
JS_PUBLIC_API(JSType)
JS_TypeOfValue(JSContext *cx, jsval v){
JSType type = JSTYPE_VOID;
JSObject *obj;
JSObjectOps *ops;
JSClass *clasp;
CHECK_REQUEST(cx);
// 检查 v 是否是 undefined
if (JSVAL_IS_VOID(v)) {
type = JSTYPE_VOID;
}
// 检查 v 是否有 object 的类型标记
else if (JSVAL_IS_OBJECT(v)) {
obj = JSVAL_TO_OBJECT(v);
// 如果 obj 是可调用的,或者是内部的 [[Class]] 属性标记了它是一个函数,就返回 function
if (obj &&
(ops = obj->map->ops,
ops == &js_ObjectOps
? (clasp = OBJ_GET_CLASS(cx, obj),
clasp->call || clasp == &js_FunctionClass)
: ops->call != 0)) {
type = JSTYPE_FUNCTION;
} else {
type = JSTYPE_OBJECT;
}
} else if (JSVAL_IS_NUMBER(v)) {
type = JSTYPE_NUMBER;
} else if (JSVAL_IS_STRING(v)) {
type = JSTYPE_STRING;
} else if (JSVAL_IS_BOOLEAN(v)) {
type = JSTYPE_BOOLEAN;
}
return type;
}
ES6 是如何解释的
我们再来看一下ES6的typeof 是如何对待函数和对象类型的:
如果一个对象(Object)没有实现[[Call]]内部方法,那么它就返回object
如果一个对象(Object)实现了[[Call]]内部方法,那么它就返回function
不愧是标准,简单直接。
[[Call]] 是什么
执行与此对象关联的代码。通过函数调用表达式调用。内部方法的参数是一个 this 值和一个包含调用表达式传递给函数的参数的列表。实现此内部方法的对象是可调用的。
这是翻译的原话,简单点说,一个对象如果支持了内部的[[Call]]方法,那么它就可以被调用,就变成了函数,所以叫做函数对象。
相应地,如果一个函数对象支持了内部的[[Construct]]方法,那么它就可以使用new或super来调用,这时我们就可以把这个函数对象称为:构造函数。
typeof 原理就大致分析到这里
这两个方法都可以用来判断变量类型
区别:前者是判断这个变量是什么类型,后者是判断某一个实例是不是某种类型,返回的是布尔值
(1)typeof
typeof返回一个数据类型的字符串,而且都是小写的字符串,返回值有'number','boolean','string','function','object','undefined'这几个
typeof 100; //'number'
typeof (1==1); //'boolean'
typeof 'onepixel'; //'string'
typeof {} ; //'object'
typeof onepixel; // 'undefined'
typeof parseInt; // 'function'
typeof [];//'object'
typeof new Date(); //'object'
缺陷:
1.不能判断变量具体的数据类型比如数组、正则、日期、对象,因为都会返回object,不过可以判断function,如果检测对象是正则表达式的时候,在Safari和Chrome中使用typeof的时候会错误的返回"function",其他的浏览器返回的是object.
2.判断null的时候返回的是一个object,这是js的一个缺陷,所以null也成为 "薛定谔的对象";判断NaN的时候返回是number
(2)instanceof
可以用来检测这个变量是否为某种类型,返回的是布尔值,并且可以判断这个变量是否为某个函数的实例,它检测的是对象的原型
instanceof 可以判断一个引用是否属于某构造函数;另外,还可以在继承关系中用来判断一个实例是否属于它的父类型。
instanceof的判断逻辑是: 从当前引用的proto一层一层顺着原型链往上找,能否找到对应的prototype。找到了就返回true
[] instanceof Array; //true
{} instanceof Object;//true
new Date() instanceof Date;//true
function Person(){};
[] instanceof Object; //true
new Date() instanceof Object;//tru
new Person instanceof Object;//true
var array = new Array()
array instanceof Array //true
共同点:基本数据类型都可以判断
不同点:instanceof可以判断这个变量是否为某个函数的实例,而typeof不能
用法:typeof经常用来检测一个变量是不是最基本的数据类型,instanceof简单说就是判断一个引用类型的变量具体是不是某种类型的对象。
联系:typeof和instanceof的目的都是检测变量的类型,两个区别在于typeof只能用于检测基本数据类型,instanceof可以检测基本数据类型,也可以检测某些引用数据类型,因为instanceof只能通过true或者false来判断,不能直接看出来是什么类型,所以需要另外一种直观的方法:
解决方案
因为js中的一切都是对象,任何都不例外,对所有值类型应用 Object.prototype.toString.call() 方法结果如下:
console.log(Object.prototype.toString.call(123)) //[object Number]
console.log(Object.prototype.toString.call('123')) //[object String]
console.log(Object.prototype.toString.call(undefined)) //[object Undefined]
console.log(Object.prototype.toString.call(true)) //[object Boolean]
console.log(Object.prototype.toString.call({})) //[object Object]
console.log(Object.prototype.toString.call([])) //[object Array]
console.log(Object.prototype.toString.call(function(){})) //[object Function]
判断是否为函数
function isFunction(value) {
return Object.prototype.toString.call(value) === '[object Function]';
}
判断是否为数组:
function isArray(value) {
return Object.prototype.toString.call(value) === '[object Array]';
}
利用这个特性,可以写一个比typeof instanceof更准确的判断方法:
var type = function (o) {
var s = Object.propertype.toString.call(o)
return s.match(/object(.∗?)object(.∗?)/)[1].toLowerCase()
}
type({}); // "object"
type([]); // "array"
type(5); // "number"
type(null); // "null"
type(); // "undefined"
type(/abcd/); // "regex"
type(new Date()); // "date"复制代码
其他方法总结:
数组:
var a = [1, 2, 3]
var b = {}
Array.isArray(a) // true
Array.isArray(b) // false
面试题中经常会考js数据类型检测,js中常用的四种方法判断数据类型
1.typeof
console.log(typeof "");
console.log(typeof 1);
console.log(typeof true);
console.log(typeof null);
console.log(typeof undefined);
console.log(typeof []);
console.log(typeof function(){});
console.log(typeof {});
看看控制台输出什么
可以看到,typeof对于基本数据类型判断是没有问题的,但是遇到引用数据类型(如:Array)是不起作用的。
2.instanceof
console.log("1" instanceof String);
console.log(1 instanceof Number);
console.log(true instanceof Boolean);
// console.log(null instanceof Null);
// console.log(undefined instanceof Undefined);
console.log([] instanceof Array);
console.log(function(){} instanceof Function);
console.log({} instanceof Object);
暂且不考虑null和undefined(这两个比较特殊),看看控制台输出什么
可以看到前三个都是以对象字面量创建的基本数据类型,但是却不是所属类的实例,这个就有点怪了。后面三个是引用数据类型,可以得到正确的结果。如果我们通过new关键字去创建基本数据类型,你会发现,这时就会输出true,如下:
接下再来说说为什么null和undefined为什么比较特殊,实际上按理来说,null的所属类就是Null,undefined就是Undefined,但事实并非如此:控制台输出如下结果:
浏览器压根不认识这两货,直接报错。在第一个例子你可能已经发现了,typeof null的结果是object,typeof undefined的结果是undefined
尤其是null,其实这是js设计的一个败笔,早期准备更改null的类型为null,由于当时已经有大量网站使用了null,如果更改,将导致很多网站的逻辑出现漏洞问题,就没有更改过来,于是一直遗留到现在。作为学习者,我们只需要记住就好。
3.constructor
console.log(("1").constructor === String);
console.log((1).constructor === Number);
console.log((true).constructor === Boolean);
//console.log((null).constructor === Null);
//console.log((undefined).constructor === Undefined);
console.log(([]).constructor === Array);
console.log((function() {}).constructor === Function);
console.log(({}).constructor === Object);
(这里依然抛开null和undefined)乍一看,constructor似乎完全可以应对基本数据类型和引用数据类型,都能检测出数据类型,事实上并不是如此,来看看为什么:
function Fn(){};
Fn.prototype=new Array();
var f=new Fn();
console.log(f.constructor===Fn);
console.log(f.constructor===Array);
首先声明了一个构造函数,并且把他的原型指向了Array的原型,所以这种情况下,constructor也显得力不从心了。
看到这里,是不是觉得绝望了。没关系,终极解决办法就是第四种办法,看过jQuery源码的人都知道,jQuery实际上就是采用这个方法进行数据类型检测的。
4.Object.prototype.toString.call()
var a = Object.prototype.toString;
console.log(a.call("aaa"));
console.log(a.call(1));
console.log(a.call(true));
console.log(a.call(null));
console.log(a.call(undefined));
console.log(a.call([]));
console.log(a.call(function() {}));
console.log(a.call({}));
可以看到,所有的数据类型,这个办法都可以判断出来。那就有人质疑了,假如我把他的原型改动一下呢?如你所愿,我们看一下:
可以看到,依然可以得到正确的结果。