JavaScript检测原始值、引用值、属性


Posted in Javascript onJune 20, 2016

在 JavaScript 中,我们常常会看到这样的代码:变量与 null 的比较(这种用法很有问题),用来判断变量是否被赋予了一个合理的值。比如:

var Controller = {
process: function(items) {
if (items !== null) { // 不好的写法
items.sort();
items.forEach(function(item) {
// 执行一些逻辑
});
}
}
}

在这段代码中, process() 方法显然希望 items 是一个数组,因为我们看到 items 拥有 sort() 和 forEach() 。这段代码的意图非常明显:如果参数 items 不是一个组数,则停止接下来的操作。这种写法的问题在于,和 null 的比较并不能真正避免错误的发生。 items 的值可以是1,也可以是是字符串,甚至可以是任意对象。这些值都和 null 不相等,进而会导致 process() 方法一旦执行到 sort() 时就会出错。

仅仅和 null 比较并不能提供足够的信息来判断后续代码的执行是否真的安全。好在 JavaScript 为我们提供了很多种方法来检测变量的真实值。

检测原始值

在 JavaScript 中有5种原始类型(也称为简单数据类型): String 、 Number 、 Boolean 、 Undefined 和 Null 。如果你希望一个值是 String 、 Number 、 Boolean 或 Undefined ,最佳选择是使用 typeof 运算符,它会返回一个表示类型的字符串。

对于字符串, typeof 返回 "string" 。

对于数字, typeof 返回 "number" 。

对于布尔值, typeof 返回 "boolean" 。

对于undefined, typeof 返回 "undefined" 。

typeof 的基本语法是: typeof variable ,你还可以这样用: typeof(variable) ,尽管这是合法的 JavaScript 语法,这种用法让 typeof 看起来像一个函数而非运算符。鉴于此,我们更推荐无括号的写法。

使用 typeof 来检测这4种原始类型是非常安全的做法。来看下面这些例子。

// 检测"String"
if (typeof name === "string") {
anotherName = name.substring(3);
}
// 检测"Number"
if (typeof count === "number") {
updateCount(count);
}
// 检测"Boolean"
if (typeof found === "boolean" && found) {
message("Found!");
}
// 检测"Undefined"
if (typeof MyApp === "undefined") {
MyApp = {
// 其他代码
};
}

typeof 运算符的独特之处在于,将其用于一个未声明的变量也不会报错。未定义的变量和值为 undefined 的变量通过 typeof 都将返回 "undefined" 。

最后一个原始类型 null ,通过 typeof 将返回 "object" ,这看上去很怪异,被认为是标准规范的严重 bug,因此在编程时要 杜绝使用 typeof 来检测 null 的类型 。

console.log(typeof null); // "object"

简单地和 null 进行比较通常不会包含足够的信息以判断值的类型是否合法,所以 null 一般不应用于检测语句。

但有一个例外,如果所期望的值真的是 null ,则可以直接和 null 进行比较。例如:

// 如果你需要检测 null,则使用这种方法
var element = document.getElementById("my-div");
if (element !== null) {
element.className = "found";
}

如果 DOM 元素不存在,则通过 document.getElementById() 得到的值为 null 。这个方法要么返回一个节点,要么返回 null 。由于这时 null 是可预见的一种输出,则可以用恒等运算符 === 或非恒等运算符 !== 来检测返回结果。

typeof 运算符的返回值除了上述提到的 string 、 number 、 boolean 、 undefined 和 object 之外,还有 function 。从技术的角度来讲,函数在 JavaScript 中也是对象,不是一种数据类型。然而,函数也确实有一些特殊的属性,因此通过 typeof 运算符来区分函数和其他对象是有必要的。这一特性将在后面 检测函数 中用到。

检测引用值

在 JavaScript 中除了原始值之外的都是引用值(也称为对象),常用的引用类型有: Object 、 Array 、 Date 和 RegExp ,这些引用类型都是 JavaScript 的内置对象。 typeof 运算符在判断这些引用类型时全都返回 "object" 。

console.log(typeof {}); // "object"
console.log(typeof []); // "object"
console.log(typeof new Date()); // "object"
console.log(typeof new RegExp()); // "object"

检测某个引用值类型的最好方法是使用 instanceof 运算符, instanceof 的基本语法是:

value instanceof constructor
// 检测日期
if (value instanceof Date) {
console.log(value.getFullYear);
}
// 检测 Error
if (value instanceof Error) {
throw value;
}
// 检测正则表达式
if (value instanceof RegExp) {
if (value.test(anotherValue)) {
console.log("Matches");
}
}

instanceof 的一个有意思的特性是它不仅检测构造这个对象的构造器,还检测原型链。原型链包含了很多信息,包括定义对象所采用的继承模式。比如,默认情况下,每个对象都继承自 Object ,因此每个对象的 value instanceof Object 都会返回 ture 。比如:

var now = new Date();
console.log(now instanceof Object); // ture
console.log(now instanceof Date); // ture
instanceof 运算符也可以检测自定义的类型,比如:
function Person(name){
this.name = name;
}
var me = new Person("Nicholas");
console.log(me instanceof Object); // ture
console.log(me instanceof Person); // ture

这段示例代码中创建了 Person 类型。变量 me 是 Person 的实例,因此 me instanceof Person 是 true 。上文也提到,所有的对象都被认为是 Object 的实例,因此 me instanceof Object 也是 ture 。

在 JavaScript 中检测 内置类型 和 自定义类型 时,最好的做法就是使用 instanceof 运算符,这也是唯一的方法。

但有一个严重的限制,假设两个浏览器帧(frame)里都有构造函数 Person ,帧A中的 Person 实例 frameAPersonInstance 传入到帧B中,则会有如下结果:

console.log(frameAPersonInstance instanceof frameAPerson) // ture
console.log(frameAPersonInstance instanceof frameBPerson) // false
尽管两个 Person 的定义是完全一样的,但在不同帧(frame)里,他们被认为是不同类型。有两个非常重要的内置类型也有这个问题: Array 和 Function ,所以检测它们一般不使用 instanceof 。

检测函数

从技术上讲,JavaScript 中的函数是引用类型,同样存在 Function 构造函数,每个函数都是其实例,比如:

function myFunc() {}
// 不好的写法
console.log(myFunc instanceof Function); // true

然而,这个方法亦不能跨帧(frame)使用,因为每个帧都有各自的 Function 构造函数,好在 typeof 运算符也是可以用于函数的,返回 "function" 。

function myFunc() {}
// 好的写法
console.log(typeof myFunc === "function"); // true

检测函数最好的方法是使用 typeof ,因为他可以跨帧(frame)使用。

用 typeof 来检测函数有一个限制。在 IE 8 和更早版本的 IE 浏览器中,使用 typeof 来检测 DOM 节点中的函数都返回 "object" 而不是 "function" 。比如:

// IE8 及更早版本的IE
console.log(typeof document.createElement); // "object"
console.log(typeof document.getElementById); // "object"
console.log(typeof document.getElementByTagName); // "object"

之所以出现这种怪异的现象是因为浏览器对 DOM 的实现有差异。简言之,这些早版本的 IE 并没有将 DOM 实现为内置的 JavaScript 方法,导致内置 typeof 运算符将这些函数识别为对象。因为 DOM 是有明确定义的,了解到对象成员如果存在则意味着它是一个方法,开发者往往通过 in 运算符来检测 DOM 的方法,比如:

// 检测 DOM 方法
if ("querySelectorAll" in document) {
var images = document.querySelectorAll("img");
}

这段代码检查 querySelectorAll 是否定义在 document 中,如果是,则使用这个方法。尽管不是最理想的方法,如果想在 IE 8 及更早浏览器中检测 DOM 方法是否存在,这是最安全的做法。在其他所有的情形中, typeof 运算符是检测 JavaScript 函数的最佳选择。

检测数组

JavaScript 中最古老的跨域问题之一就是在帧(frame)之间来回传递数组。开发者很快发现 instanceof Array 在此场景中不能返回正确的结果。正如上文提到的,每个帧都有各自的 Array 构造函数,因此一个帧中的实例在另外一个帧里不会被识别。

关于如何在 JavaScript 中检测数组类型已经有狠多研究了,最终 Kangax 给出了一种优雅的解决方案:

function isArray(value) {
return Object.prototype.toString.call(value) === "[object Array]";
}

Kangax 发现调用某个值的内置 toString() 方法在所有浏览器中都会返回标准的字符串结果。对于数组来说,返回的字符串为 "[object Array]" ,也不用考虑数组实例实在哪个帧(frame)中被构造出来的。这种方法在识别内置对象时往往十分有用,但对于自定义对象请不要用这种方法。

ECMAScript5 将 Array.isArray() 正式引入 JavaScript。唯一的目的就是准确地检测一个值是否为数组。同 Kangax 的函数一样, Array.isArray() 也可以检测跨帧(frame)传递的值,因此很多 JavaScript 类库目前都类似地实现了这个方法。

function isArray(value) {
if (typeof Array.isArray === "function") {
return Array.isArray(value);
} else {
return Object.prototype.toString.call(value) === "[object Array]";
}
}

IE 9+、FireFox 4+、Safari 5+、Opera 10.5+、Chrome 都实现了 Array.isArray() 方法。

检测属性

另外一种用到 null (以及 undefined )的场景是当检测一个属性是否在对象中存在时,比如:

// 不好的写法:检测假值
if (object[propertyName]) {
// 一些代码
}
// 不好的写法:和null相比较
if (object[propertyName] != null) {
// 一些代码
}
// 不好的写法:和undefined相比较
if (object[propertyName] != undefined) {
// 一些代码
}

上面这段代码里的每个判断,实际上是通过给定的名字来检查属性的值,而并非判断给定的名字所指的属性是否存在。在第一个判断中,当属性值为假值时结果会出错,比如: 0 、 ""(空字符串) 、 false 、 null 和 undefined ,毕竟这些都是属性的合法值。

判断属性是否存在的最好的方法是使用 in 运算符。 in 运算符仅仅会简单地判断属性是否存在,而不去读属性的值,如果实例对象的属性存在、或者继承自对象的原型, in 运算符都会返回 true 。比如:

var object = {
count: 0,
related: null
};
// 好的写法
if ("count" in object) {
// 这里的代码会执行
}
// 不好的写法:检测假值
if (object["count"]) {
// 这里的代码不会执行
}
// 好的写法
if ("related" in object) {
// 这里的代码会执行
}
// 不好的写法,检测是否为
if (object["related"] != null) {
// 这里的代码不会执行
}

如果你只想检查实例对象的某个属性是否存在,则使用 hasOwnProperty() 方法。所有继承自 Object 的 JavaScript 对象都有这个方法,如果实例中存在这个属性则返回 true (如果这个属性只存在于原型里,则返回 false )。需要注意的是,在 IE 8 以及更早版本的 IE 中,DOM 对象并非继承自 Object,因此也不包含这个方法。也就是说,你在调用 DOM 对象的 hasOwnProperty() 方法之前应当先检测其是否存在。

// 对于所有非 DOM 对象来说,这是好的写法
if (object.hasOwnProperty("related")) {
// 执行这里的代码会
}
// 如果你不确定是否为 DOM 对象,则这样来写
if ("hasOwnProperty" in object && object.hasOwnProperty("related")) {
// 执行这里的代码会
}

因为存在 IE 8 以及更早版本的 IE 的情形,在判断实例对象的属性是否存在时,我更倾向于使用 in 运算符,只有在需要判断实例属性时才会用到 hasOwnProperty() 。

不管你什么时候需要检测属性的存在性,请使用 in 运算符或者 hasOwnProperty() 。这样做可以避免很多 bug。

以上所述是小编给大家介绍的JavaScript检测原始值、引用值、属性,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
JavaScript中String和StringBuffer的速度之争
Apr 01 Javascript
javascript实现tabs选项卡切换效果(扩展版)
Mar 19 Javascript
Jquery图片滚动与幻灯片的实例代码
Apr 08 Javascript
Jquery EasyUI的添加,修改,删除,查询等基本操作介绍
Oct 11 Javascript
自己封装的javascript事件队列函数版
Jun 12 Javascript
JavaScript删除数组元素的方法
Mar 20 Javascript
jquery实现初次打开有动画效果的网页TAB切换代码
Sep 06 Javascript
详解vee-validate的使用个人小结
Jun 07 Javascript
jQuery实现 RadioButton做必选校验功能
Jun 15 jQuery
Angular2 自定义validators的实现方法
Jul 05 Javascript
vue-router路由与页面间导航实例解析
Nov 07 Javascript
JS实现扫码枪扫描二维码功能
Jan 03 Javascript
Js 获取当前函数参数对象的实现代码
Jun 20 #Javascript
JS实现鼠标框选效果完整实例
Jun 20 #Javascript
javascript事件冒泡简单示例
Jun 20 #Javascript
JS实现的打字机效果完整实例
Jun 20 #Javascript
jQuery实现的网格线绘制方法
Jun 20 #Javascript
jQuery实现右下角可缩放大小的层完整实例
Jun 20 #Javascript
需灵活掌握的Bootstrap预定义排版类 你精通吗?
Jun 20 #Javascript
You might like
第十一节--重载
2006/11/16 PHP
phpExcel中文帮助手册之常用功能指南
2014/08/18 PHP
php表单文件iframe异步上传实例讲解
2017/07/26 PHP
html数组字符串拼接的最快方法
2009/09/16 Javascript
JQuery EasyUI 对话框的使用方法
2010/10/24 Javascript
JavaScript字符串对象charAt方法入门实例(用于取得指定位置的字符)
2014/10/17 Javascript
jQuery实现带滚动导航效果的全屏滚动相册实例
2015/06/19 Javascript
JavaScript ES5标准中新增的Array方法
2016/06/28 Javascript
js实现非常棒的弹出div
2016/10/06 Javascript
微信小程序 小程序制作及动画(animation样式)详解
2017/01/06 Javascript
微信小程序 动态绑定事件并实现事件修改样式
2017/04/13 Javascript
Javascript es7中比较实用的两个方法示例
2017/07/21 Javascript
利用vue+elementUI实现部分引入组件的方法详解
2017/11/22 Javascript
解决vue 绑定对象内点击事件失效问题
2018/09/05 Javascript
详解写好JS条件语句的5条守则
2019/02/28 Javascript
TensorFlow.js 微信小程序插件开始支持模型缓存的方法
2020/02/21 Javascript
JavaScript console的使用方法实例分析
2020/04/28 Javascript
基于Vue2实现移动端图片上传、压缩、拖拽排序、拖拽删除功能
2021/01/05 Vue.js
[02:33]DOTA2亚洲邀请赛趣味视频之吐真话筒
2018/03/31 DOTA
[59:35]DOTA2-DPC中国联赛定级赛 Aster vs DLG BO3第一场 1月8日
2021/03/11 DOTA
python实现电子词典
2020/04/23 Python
python使用wmi模块获取windows下硬盘信息的方法
2015/05/15 Python
简单介绍Python的Django框架加载模版的方式
2015/07/20 Python
Python实现基于PIL和tesseract的验证码识别功能示例
2018/07/11 Python
anaconda如何查看并管理python环境
2019/07/05 Python
python-web根据元素属性进行定位的方法
2019/12/13 Python
利用Tensorboard绘制网络识别准确率和loss曲线实例
2020/02/15 Python
Python面向对象特殊属性及方法解析
2020/09/16 Python
CSS3实现彩色进度条动画的示例
2020/10/29 HTML / CSS
勤奋学习演讲稿
2014/05/10 职场文书
税务干部个人整改措施思想汇报
2014/10/10 职场文书
2015年纪委工作总结
2015/05/13 职场文书
2019年大学推荐信
2019/06/24 职场文书
利用Python判断整数是否是回文数的3种方法总结
2021/07/07 Python
B站评分公认最好看的动漫,你的名字评分9.9,第六备受喜欢
2022/03/18 日漫
使用Nginx的访问日志统计PV与UV
2022/05/06 Servers