鉴定JavaScript中的数据类型
众所周知,JavaScript是一门弱类型的语言,但是这并不代表JavaScript中没有数据类型。JavaScript中常见的数据类型有string、number、object等等,通常我们使用typeof操作符来判断一个变量值的数据类型;但是由于许多问题的存在,往往出现一些出人意料的坑,或者我们无法得到具体的令人满意的答案,所以我们需要自己实现一些函数,用于鉴定各种数据类型,并且得到的结果要符合我们的常识,underscore就实现了一系列这样的函数。
1.数组(Array)的鉴定
如果我们使用typeof操作符鉴定一个数组,我们得到的结果将不会是字符串"array",而是"object",这使得我们无法精确的判断一个对象是否是数组。

有人觉得使用instanceof操作符可以解决这个问题,因为[] instanceof Array === true,但是这个操作符也并不是那么的靠谱。因为instanceof操作符默认假定只有一个全局执行环境,当我们的网页中包含多个框架时,就会有多个全局执行环境,可能会导致'[] instanceof Array'的结果返回false。
JavaScript中实现了一个内置函数Array.isArray用于判断某个对象是否是数组,但是由于Array.isArray兼容性(IE 9+/FireFox 4+/Safari 5+/Opera 10.5+/Chrome)存在问题,所以我们不得不考虑到在低级浏览器中的后备方案。
那么如何实现这个后备方案呢?我们知道在任何值上调用Object原生的toString方法,都会返回一个[object NativeConstructorName]格式的字符串。

那么我们可以通过这种方式来判断。当Array.isArray()可靠时,我们使用这个函数,当其不可靠时,我们使用Object原生的toString方法:
// Is a given value an array?
// Delegates to ECMA5's native Array.isArray
// nativeIsArray = Array.isArray
// toString = Object.prototype.toString
_.isArray = nativeIsArray || function(obj) {
return toString.call(obj) === '[object Array]';
};
上方代码就是underscore的实现。 在之后的实现方案中,我们可以看到许多类型的鉴定都是通过判断Object.prototype.toString返回的字符串来实现的。
2.对象(Object)的鉴定
我们知道在JavaScript中,不只有var object = {}或者var object = new Object()这种对象,还有许多内置的对象,比如Error、Function、Array、Number、Date、RegExp、Math等等。
通过测试,这些内置对象在使用typeof操作符鉴定其类型时,都会返回“object”,但是有一个例外,就是Function对象:
typeof new Date()
//"object"
typeof new RegExp()
//"object"
typeof new Boolean();
//"object"
typeof new Array();
//"object"
typeof new Object();
//"object"
typeof new Error();
//"object"
typeof Math
//"object"
typeof function(){};
//"function"
在我们看来,函数理所应当应该是一个对象,因为函数包含许多的属性并且函数具有prototype原型,比如function.length表示函数定义时具有的形参的个数,函数原型中还包含apply、call、bind等常用方法,所以我们应该把函数看做一个对象。
所以我们在判定一个变量值是否是对象时,我们可以通过typeof variable === "object"来实现,另外还需要考虑typeof variable === "function"时的情况。当然我们不希望我们判断的参数是一个空对象(null或者undefined),所以应当排除这种情况:
// Is a given variable an object?
//判断传入的参数是否是一个非空对象。
_.isObject = function(obj) {
var type = typeof obj;
return type === 'function' || type === 'object' && !!obj;
//!!双感叹号的作用等同于Boolean函数。
//之所以需要判断!!obj的值,是因为需要确保传入参数是非空对象。
//!!null===false
};
以上代码就是underscore中的具体实现。
3.内置对象(Function、Date、RegExp等)的鉴定
之前有提到过,使用typeof操作符鉴定内置对象时,除了Function之外都会返回字符串"object"。当我们需要具体鉴定内置对象时,显然typeof操作符不再适合。
因为Array也是内置对象,所以我们可以借鉴之前鉴定Array时所使用的方法——Object.prototype.toString。
我们看underscore是如何实现的:
// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError, isMap, isWeakMap, isSet, isWeakSet.
//添加一系列的类型判断函数:比如isArguments、isFunction、isString等等。
//具体的方法是通过判断Object.prototype.toString方法来辨别其类型。
_.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error', 'Symbol', 'Map', 'WeakMap', 'Set', 'WeakSet'], function(name) {
_['is' + name] = function(obj) {
//相当于是:
//return Object.prototype.toString.call(obj) === `[object ${name}]`;
return toString.call(obj) === '[object ' + name + ']';
};
});
underscore把所有的内置对象的名称字符串放入一个数组当中,然后使用已经定义的_.each方法遍历元素进行操作。针对每一个名称,为_变量添加一个鉴定方法,鉴定方法的实现原理是一样的,就是通过对比Object.prototype.toString返回的字符串与内置对象的名称来判断。
可以看出ES6新增的Set、WeakSet、Symbol、WeakMap、Map等内置对象同样使用该方法进行鉴定。
4.几个特殊值的鉴定
4.1 NaN的鉴定
我们知道,NaN是一个非常特殊的值,为什么特殊呢?因为其含义为非数值(Not a Number),但是typeof NaN === "number",因为NaN是JavaScript中唯一一个与任何值都不相等(包括自身)的值:
alert(NaN === NaN);
//false
针对这些特殊情况,JavaScript定义了一个鉴定函数——isNaN。isNaN在接收到一个参数之后,会先尝试将其转换为数值,如果转换失败,则返回true。
比如:
alert(isNaN('123a'));
//true
alert(isNaN(undefined));
//true
这不符合实际,我们需要鉴定确切的NaN,而不是广义上的非数值就是NaN。所以我们首先要确保传入的参数是number类型,这样就缩小了参数的范围,所有的number之外的类型都会被返回false。确保参数是number类型之后,我们再使用isNaN鉴定就行了。
源码:
// Is the given value `NaN`?
_.isNaN = function(obj) {
return _.isNumber(obj) && isNaN(obj);
};
其实在ES6中引入了Number.isNaN方法用于鉴定NaN,它与全局函数isNaN的区别在于Number.isNaN在鉴定之前不会对参数进行强制转换,这就确保了鉴定的结果满足严格的定义。
其实我个人认为,针对其特殊之处可以用另外一个方法实现鉴定。因为NaN是JavaScript中唯一一个自身不等于自身的值,那么我们通过判断变量是否等于自身来鉴定变量值是否为NaN。
实现方案:
function _.isNaN(value){
return value !== value;
}
我在浏览器控制台中简单的尝试了一下,结果是可行的。不知道为什么underscore中没有采用这种方案,如果有同学知道欢迎给我指出:email:zhongdeming428@163.com。
4.2 Null以及Undefined的鉴定
虽然null == undefined,但是null !== undefined,所以我们通过直接比较就行了。
源码:
_.isNull = function(obj) {
return obj === null;
};
// Is a given variable undefined?
_.isUndefined = function(obj) {
return obj === void 0;
};
之所以采用void 0 代替undefined,是因为undefined不是一个保留字或者关键字,这代表着在编程时,我们可以对undefined进行赋值!即是在新版的ES中,undefined已经成为了一个Read-Only属性,但是在局部作用域中,undefined还是可以被赋值:
function v(){
let undefined = 'test';
return undefined;
}
v();
//"test"
所以为了防止被用户重新定义undefined,我们需要找到一个可靠的替代品,因为void操作符后接任何参数都会返回undefined,所以这就成为了一个可靠的替代品。
5.结语
JavaScript中的数据类型,是一个永远也说不完道不尽的话题,因为一不小心我们就会写下一个“定时炸弹”,不定时的扔出一些bug,这也是为什么TypeScript大受欢迎的原因。
这也警示了我在接触变量时要非常小心,做好单元测试,才能防止出现一些不该出现的错误。
最后,学习underscore源码,让我学习到了一些平常难以学习到的知识,学到了一些坑应该如何去处理。接下来继续学习,总结经验!
获取更多underscore源码解读:GitHub
鉴定JavaScript中的数据类型的更多相关文章
- JavaScript 中的数据类型
Javascript中的数据类型有以下几种情况: 基本类型:string,number,boolean 特殊类型:undefined,null 引用类型:Object,Function,Date,Ar ...
- javaScript中的数据类型
一.综述 javaScript中的数据类型分为两类: 简单类型:Boolean,Number,String 引用类型:Object 其他:undefined代表变量没有初始化,null代表引用类型为空 ...
- Javascript中的数据类型之旅
虽然Javascript是弱类型语言,但是,它也有自己的几种数据类型,分别是:Number.String.Boolean.Object.Udefined.Null.其中,Object属于复杂数据类型, ...
- 【译】Javascript中的数据类型
这篇文章通过四种方式获取Javascript中的数据类型:通过隐藏的内置[[Class]]属性:通过typeof运算符:通过instanceof运算符:通过函数Array.isArray().我们也会 ...
- javascript 中检测数据类型的方法
typeof 检测数据类型 javascript 中检测数据类型有好几种,其中最简单的一种是 typeof 方式.typeof 方法返回的结果是一个字符串.typeof 的用法如下: typeof v ...
- JavaScript中基本数据类型之间的转换
在JavaScript中共有六种数据类型,其中有五种是基本数据类型,还有一种则是引用数据类型.五种基本数据类型分别是:Number 数值类型.String 字符串类型.Boolean 布尔类型, nu ...
- JavaScript中基本数据类型和引用数据类型的区别(栈——堆)
JavaScript中基本数据类型和引用数据类型的区别 1.基本数据类型和引用数据类型 ECMAScript包括两个不同类型的值:基本数据类型和引用数据类型. 基本数据类型指的是简单的数据段,引用数据 ...
- 面试说:聊聊JavaScript中的数据类型
前言 请讲下 JavaScript 中的数据类型? 前端面试中,估计大家都被这么问过. 答:Javascript 中的数据类型包括原始类型和引用类型.其中原始类型包括 null.undefined.b ...
- 简单回忆一下JavaScript中的数据类型
说到JavaScript,大家都应该知道,它是一门脚本语言,也是一门弱类型语言,也是一门解析型的语言,同时也是一门动态类型的语言. 很好,至于JavaScript中数据类型.其分为基本数据类型和复杂数 ...
随机推荐
- 九度oj题目1019:简单计算器
题目1019:简单计算器 时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:6346 解决:2334 题目描述: 读入一个只包含 +, -, *, / 的非负整数计算表达式,计算该表达 ...
- 搭建简单FTP
搭建简单FTP 环境 CentOS 7 安装 yum install vsftpd 修改配置文件, 在/etc/vsftpd/vsftpd.conf中添加allow_writeable_chroot= ...
- Java入门系列-16-继承
这一篇文章教给新手学会使用继承,及理解继承的概念.掌握访问修饰符.掌握 final 关键字的用法. 继承 为什么要使用继承 首先我们先看一下这两个类: public class Teacher { p ...
- [Visual studio code 常见问题解决] ——中文乱码、
中文乱码问题解决 描述:刚开始安装VScode要打开含中文的文件,都会出现乱码,这时只要点击右下方的“UTF-8”改成“GB2312”即可.但每次都改是很麻烦的,所以要使得VScode默认支持GB23 ...
- c#中的程序集
程序集:一些相关类的包,比如三层中Model层都是一些数据库表的实体类.我们所用到的类都是位于各个程序集中,若需要调用某个类,就必须引用其所在的程序集. 访问级别:程序集中的类有四种访问级别,int ...
- RabbitMQ - 任务队列
这次我们试着实现这样一个小程序: 嗯,就是任务队列(task queue).不是将任务集中在一堆并一直等到所有任务一并完成为止,而是将每一个任务封装为一个消息,并将其发送到队列,后台的workers就 ...
- 本地如何将svn和git管理的代码做关联
svn和git都是广为流传的代码版本管理工具,实际项目中往往会将两者结合使用,那么如何将本地的一份代码和两者做有机的关联呢! 前提假设:项目已经在开发阶段中,此时变更了svn代码库的地址:或者是组里来 ...
- VUE的两种跳转push和replace对比区别
router.push(location) 在vue.js中想要跳转到不同的 URL,需要使用 router.push 方法. 这个方法会向 history 栈添加一个新的记录,当用户点击浏览器后退按 ...
- axios拦截器请求头携带token
转—— https://github.com/superman66/vue-axios-github/blob/master/src/http.js
- 1003. 猜数游戏 (Standard IO)
题目描述 有一个“就是它”的猜数游戏,步骤如下:请你对任意输入的一个三位数x,在这三位数后重复一遍,得到一个六位数,467-->467467.把这个数连续除以7.11.13,输出最后的商. 输入 ...