JavaScript数据类型判断
1. typeof
使用typeof判断数据类型
let obj = {
name: 'Davie'
};
function foo() {
console.log('this is a function');
}
let arr = [1, 2, 3];
let arrObject = [{index : 1},{index : 2},{index : 3}]; // 对象数组
let s = Symbol();
console.log(typeof 1); // number
console.log(typeof '1'); //string
console.log(typeof NaN); // number
console.log(typeof true); //boolean
console.log(typeof s); //Symbol
console.log(typeof undefined); //undefined
console.log(typeof null); //object
console.log(typeof foo); //function
console.log(typeof obj); //object
console.log(typeof arr); //object
console.log(typeof arrObject); //object
使用typeof可以检测大部分的基本类型。但无法检测出来null,会认为是对象。
NaN也会认为是number。
可以检测出函数,但是对象数组和对象无法区分。
2. instanceof
instanceof可以用来判断对象是否是某个类的实例。
简单说就是左边对象的原型链(通过.__proto__
访问其原型对象,再访问其原型对象的原型对象,…)上是否有一点(原型对象)和右边对象的原型对象相等(通过.prototype
访问),如果相等则返回true。比如Function和Object,由于Function.__proto__.proto__ === Object.prototype
,所以Function instanceof Object
为true。同时也因为Object.__proto__ === Function.prototype
,所以Object instanceof Function
为true。下面为简易的instanceof的代码实现示例:
function _instanceof(L,R){
let l = L.__proto__
let r = R.prototype
while(true){
if(l === null){
return false
}
if(l === r){
return true
}
l = l.__proto__
}
}
下面为一些比较例子:
function Person() {
}
// 实例对象才有__proto__属性,构造函数才有prototype属性(当然其也有__proto__属性),而这两个属性指向的都是一个原型对象
console.log(Person.prototype);
console.log(Person.__proto__ === Function.prototype); // true
var p1 = new Person();
console.log(p1);
console.log(p1.__proto__ === Person.prototype); // true
var obj = {};
console.log(obj.__proto__ === Object.prototype); // true
var arr = [1, 2, 3];
var date = new Date();
var fn= function () {
alert(123);
};
console.log(arr instanceof Array); // true
console.log(date instanceof Date); // true
console.log(fn instanceof Function); // true
console.log(Function instanceof Object) // true
console.log(Object instanceof Function) // true
console.log(Function);
console.log(Function.__proto__);
console.log(Function.__proto__.__proto__);
console.log(Function.prototype);
console.log(Function.prototype.__proto__);
console.log(Function.__proto__ === Function.prototype); // true
console.log(Function.__proto__ === Object.prototype); // false
console.log(Function.__proto__.__proto__ === Object.prototype); // true
console.log(Object.__proto__ === Function.prototype); // true
console.log(Object.__proto__);
console.log(Object.__proto__ === Object.prototype); // false
console.log(Object.__proto__.__proto__);
再来看另外一种情况:
在 test.html中定义了数组a:
// test.html
<script>
var a = [1,2,3];
</script>
然后通过iframe引入到main.html页面:
// main.html
<iframe src="test.html"></iframe>
<script>
var iframe = window.frames[0];
var arr = iframe.a;
console.log(arr);
console.log(iframe.a);
console.log(iframe.a instanceof Array); // false
console.log(iframe.a instanceof iframe.Array); // true , 要考虑所属的执行环境或环境对象
console.log(arr instanceof Array); // false
console.log(arr.constructor === Array); //false
console.log(arr instanceof frame.Array); // true
</script>
注意:以上例子的测试需要关闭Chrome同源跨域安全策略才能测试成功,不然的话就要本地配置服务器站点(保证父子窗口同源)才能测试。其中在windows上需要先关闭所有的Chrome浏览器窗口后,再通过cmd输入以下命令打开Chrome浏览器测试:
"C:\Program Files\Google\Chrome\Application\chrome.exe" --disable-web-security --user-data-dir=~/chromeTemp
具体可参考:
blocked-a-frame-of-origin-null-from-accessing-a-cross-origin-frame-chrome
disable-same-origin-policy-in-chrome
测试结果显示:
在main.html页面通过iframe获取到test页面的数组检测,发现 a 不是 Array的实例对象,这是什么原因呢?
其实 iframe 之间不会共享原型链, 因为他们有独立的执行环境, 所以 frame test 中的数组 a 不会是本执行环境的实例对象。
3. constructor
使用构造函数判断类型:
var bool = true;
var num = 123;
var str = "Davie"
var arr = [1,2,3]
var obj = {name:"Davie"}
var fun = function(){}
var sy = Symbol("Davie")
function Person(){}
function Student(){
Person.call(this)
}
var stu = new Student()
console.log(bool.constructor === Boolean);// true
console.log(num.constructor === Number);// true
console.log(str.constructor === String);// true
console.log(arr.constructor === Array);// true
console.log(obj.constructor === Object);// true
console.log(fun.constructor === Function);// true
console.log(sy.constructor === Symbol);// true
console.log(stu.constructor === Student);// true
console.log(stu.constructor === Person);// false
- undefined和null没有contructor属性,所以constructor不能判断undefined和null
- 使用constructor判断类型是不安全的,因为contructor的指向是可以改变的
arr.constructor = Object
console.log(arr.constructor === Object);// true
4. 特性嗅探
或者一些特有的方法,比如数组特有的sort,slice等:
console.log(typeof arr.sort === 'function')
但是这种方式也不是特别牢靠,因为很难保证其他对象里面没有这些方法。
5. 万能方法
在任何值上调用 Object 原生的 toString() 方法,都会返回一个 [object NativeConstructorName] 格式的字符串。
需要注意的是,它不能检测非原生构造函数的构造函数名。
function foo(){};
var div = document.createElement('div')
Object.prototype.toString.call(1); '[object Number]'
Object.prototype.toString.call(NaN); '[object Number]'
Object.prototype.toString.call('1'); '[object String]'
Object.prototype.toString.call(true); '[object Boolean]'
Object.prototype.toString.call(undefined); '[object Undefined]'
Object.prototype.toString.call(null); '[object Null]'
Object.prototype.toString.call(Symbol());'[object Symbol]'
Object.prototype.toString.call(foo); '[object Function]'
Object.prototype.toString.call([1,2,3]); '[object Array]'
Object.prototype.toString.call({}); '[object Object]'
Object.prototype.toString.call(/\d+/); "[object RegExp]"
Object.prototype.toString.call(div) ; '[object HTMLDivElement]'
Object.prototype.toString.call((function() {
return arguments;
})());
"[object Arguments]"
Object.prototype.toString.call(new Error()); // => "[object Error]"
Object.prototype.toString.call(new Date()); // => "[object Date]"
从上面的例子可以看到,Object.prototype.toString 方法能有效弥补typeof不能很好区分数组、对象和函数的短板。
每个类在内部都有一个 [[Class]] 属性,这个属性中就指定了上述字符串中的构造函数名。
Object.prototype.toString 的原理是当调用的时候, 就取值内部的 [[Class]] 属性值, 然后拼接成 '[object ’ + [[Class]] + ‘]’ 这样的字符串并返回。然后我们使用 call 方法来获取任何值的数据类型。
6. 检测函数
Array.isArray()
用于确定传递的值是否是一个Array。如果对象是Array,则返回true,否则为false。
Array.isArray([1, 2, 3]);
判断是否是 DOM 元素
在实际项目里面, 有时或许我们需要判断是否是 DOM 元素对象, 那么在判断的时候利用的是 DOM 对象特有的 nodeType 属性:
isElement: function(obj){
return !!(obj && obj.nodeType === 1);
}
判断是否是对象
isObject: function(obj){
var type = typeof obj;
return type === 'function' || typeof === 'object' && obj !== null;
}
这里的对象是狭义的, 是通常所指的 key-value 型的集合, 或者是 function 函数并且不为 null.
判断是否是 arguments 对象
判断一个对象是不是 arguments 对象可以通过 Object.prototype.toString 来判断, 但是低版本的浏览器不支持, 他们返回的是 [object Object], 所以需要兼容:
isArguments: function(obj){
return Object.prototype.toString.call(obj) === '[object Arguments]' || (obj != null && Object.hasOwnProperty.call(obj, 'callee'));
}
兼容做法原理是通过对象的 hasOwnProperty 方法来判断对象是否拥有 callee 属性从而判断是不是 arguments 对象.
isNaN()和Number.isNaN
isNaN函数可以检测某个值是否是NaN:
isNaN(NaN); // true
但是:
isNaN(undefined); // true
isNaN({}); // true
isNaN([]); // false
只要传入的参数不是数字,都会返回true,但是数组会返回false,所以仍然无法很好进行区分。
ES6为了修正这个BUG,引入了Number.isNaN()
Number.isNaN(NaN); // true
Number.isNaN('A String'); // false
Number.isNaN(undefined); // false
Number.isNaN({}); // false
Number.isNaN(1); // false
Number.isNaN([]); // false
没有ES6的情况下,可以采用以下方式在Number类型上添加isNaN函数:
if (!Number.isNaN) {
Number.isNaN = function(n) {
return (
typeof n === "number" &&
window.isNaN( n )
);
};
}