《编写可维护的 JavaScript》编程实践 - 第 8 章 避免“空比较”

编程实践 - 第 8 章 避免“空比较”

《编写可维护的 JavaScript》—— Nicholas C. Zakas

在 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 比较并不能提供足够的信息来判断后续代码的执行是否真的安全。

1. 检测原始值

在 JavaScript 中有 5 种原始类型:字符串、数字、布尔值、undefinednull

可以用 typeof 验证前 4 种,用 === 验证 null

typeof 'a';   //=> "string"
typeof 1;     //=> "number"
typeof true;  //=> "boolean"
typeof undefined;     //=> "undefined"

// 可用于未声明的变量
typeof undeclaredVar; //=> "undefined"

typeof null;  //=> "object"

简单地和 null 比较通常不会包含足够的信息以判断值的类型是否合法。
如果所期望的值是 null,则可以直接和 null 进行比较。

2. 检测引用值

引用值也称作对象(object),在 JavaScript 中除了原始值之外的值都是引用。

内置的引用类型: ObjectArrayDateErrorRegExp
对引用类型使用 typeof 运算符返回的都是 "object"

typeof {};    //=> "object"
typeof [];    //=> "object"
typeof new Date();      //=> "object"
typeof new RegExp();    //=> "object"

// 这看上去很怪异,被认为是标准规范的严重 bug
typeof null;  //=> "object"

通过 instanceof 运算符检测某个引用值的类型,基本语法:

value instanceof constructor

示例:

// 检测日期
if (value instanceof Date) {
  console.log(value.getFullYear());
}

// 检测正则表达式
if (value instanceof RegExp) {
  if (value.test(anotherValue)) {
    console.log("Matches");
  }
}

// 检测 Error
if (value instanceof Error) {
  throw value;
}

instanceof 不仅检测构造函数还检测原型链。
所有对象都继承自 Object,因此 引用类型值 instanceof Object; //=> true

instanceof 运算符也可以检测自定义的类型,比如:

function Person(name) {
  this.name = name;
}

var me = new Person("Nicholas");

console.log(me instanceof Object);  //=> true
console.log(me instanceof Person);  //=> true

instanceof 不能跨上下文。
比如,一个页面里有个 <iframe>,父级中的数组对象不能用子级的 Array 构造函数检测。

2.1. 检测函数

JavaScript 中的函数是引用类型,是 Function 构造函数的实例,但 instanceof 不能跨上下文检测。

检测函数最好的方法时使用 typeof,它可以跨上下文使用。

function myFunc() {}

typeof myFunc === "function"; //=> true

在 IE 8- 中,使用 typeof 检测 DOM 节点中的函数都返回 "object" 而不是 "function"。比如:

console.log(typeof document.getElementById); //=> "object"
console.log(typeof document.createElement); //=> "object"
console.log(typeof document.getElementsByTagName); //=> "object"

IE 8- 没有将 DOM 函数实现为 JavaScript 方法,导致 typeof 运算符将这些函数识别为对象,通常使用 in 运算符来检测 DOM 的方法。比如

if ("querySelectorAll" in document) {
  images = document.querySelectorAll("img");
}

2.2. 检测数组

JavaScript 中最古老的跨域问题之一是在 <iframe> 之间来回传递数组,
而使用 instanceof Array 不总是返回正确的结果,因为每个 <iframe> 都有各自的 Array 构造函数,相互之间不能识别。

Douglas Crockford 首先推荐使用“鸭式辩型”(duck typing)来检测其 sort() 存在与否,以判断是否为数组对象。

function isArray(value) {
  return typeof value.sort === "function";
}

这种检测方法依赖一个事实,即数组是唯一包含 sort() 方法的对象。

最终,Juriy Zaytsev(也称为 Kangax)给出了一种更优雅的解决方案,如下

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

Kangax 发现调用某个值的内置 toString() 方法在所有浏览器中都会返回标准的字符串结果,对于数组来说,返回 "[object Array]"
这个解决方案很快流行起来,并被大多数 JavaScript 类库所采纳。

这种方法在识别内置对象时往往十分有用,但对于自定义对象请不要使用这种方法。

ECMAScript 5 将 Array.isArray() 正式引入 JavaScript,唯一的目的是准确的检测一个值是否为数组。
同 Kangax 的函数一样,Array.isArray() 也可以检测跨 <iframe> 传递的值。很多类库都做了相应的兼容处理,如下

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()

3. 检测属性

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

// 不好的写法:不能检测为假值(0、""、false)的属性
if (object[propertyName]) {
  // ...
}

// 不好的写法:不能检测属性值为 null 时会出错
if (object[prototypeName] != null) {
  // ...
}

// 不好的写法
if (object[prototypeName] != 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.prototype,所以任何对象都可以使用。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值