【读书笔记】:《编写可维护的JavaScript》第08章 避免“空比较”

第08章 避免“空比较”

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

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

这段代码中,process()方法显然希望items是一个数组,但是仅仅和null比较并不能提供足够的信息来判断后续代码的执行是否真正安全,上面的代码items可以使用数字 也可以是字符串,这些值都和null不相等。好在JavaScript为我们提供了多种方法来检测变量的真实性。

8.1 检测原始值

在JavaScript中有5中原始类型:字符串、数字、布尔值、null、undefined。如果希望一个值是字符串、数字、布尔值、null、undefined,最佳选择是使用 typeof 运算符。typeof运算符会返回一个表示类型的字符串。

  • 字符串 string
  • 数字 number
  • 布尔值 boolean
  • undefined undefined

typeof的基本语法:

var str = "ddd";
typeof str; // 推荐使用这种形式
typeof(sre); // 这种用法让 typeof 看起来像一个函数而非运算符

使用typeof来检测这4中原始类型时非常安全的做法。来看一下下面的例子:

// 检测字符串
if (typeof name === "string") {
    anotherName = name.substring(3);
}

// 检测数字
if (typeof count === "number") {
    updateCount(count);
}

// 检测布尔值
if (typeof found === "boolean" && found){
    message("found!");
}

// 检测undefined
if (typeof MyApp === "undefined"){
    MyApp = {

    };
}

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

最后一个原始值,null,一般不应用于检测语句。正如上文提到的,简单地和null比较通常不会包含足够的信息判断值得类型是否合法。
但有一个例外,如果所期望的值真的是null,则可以直接和null进行比较。这时应当使用===或!==来和null进行比较:

var element = document.getElementById("myDiv");
if (element !== null) {
    element.className = "found";
}

如果DOM元素不存,则document.getElementById()得到的值为null。这时null就是一个可预见的输出,可以用!==来检测返回结果。

运行 typeof null 返回的是“object”,这是一个低效率的判断null方法。如果需要检测null,请直接使用===和!==。

8.2 检测引用值

引用值也称为对象(*object)。在JavaScript中除了原始值外都是引用值。
其中JavaScript内置的引用类型有:Object、Array、Date、Error。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

例如:

// 检测日期
var value1 = new Date();
if (value1 instanceof Date) {
    console.log(value1.getFullYear());
}

// 检测正则表达式
var value2 = new RegExp();
if (value2 instanceof RegExp) {
    console.log(value2);
}

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

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

var now = new Date();
console.log(now instanceof Date);   // true
console.log(now instanceof Object); // true

因为这个原因,使用 value instanceof Object 来判断对象是否属于某个特定类型的做法并非最佳。

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

function Person(name) {
    "use strict";
    this.name = name;
}

var me = new Person("happyking");

console.log(me instanceof Object); // true 任何对象都是Object的实例
console.log(me instanceof Person); // true 因为me是Person的实例

在JavaScript中检测自定义类型时,最好的做法就是使用 instanceof 运算符,这也是唯一的方法。同样对于内置JavaScript类型也是如此。但是,有一个严重的限制。

假设一个浏览器帧(frame A)里的一个对象被传入到另一个帧(frame B)中。两个帧里都定义了构造函数Person。如果来自帧A的对象是帧A的Person实例,则如下规则成立:

frameAPersonInstance instanceof frameAPerson; // true
frameAPersonInstance instanceof frameBPerson; // false

因为每个帧(frame)都拥有Person的一份拷贝,它被认为是该帧(frame)中的Person的拷贝实例,尽管两个定义可能是完全一样的。
这个问题不仅出现在自定义类型上,其他两个非常重要的内置类型也有这个问题:函数和数组、对于这两个类型来说,一般用不着使用instanceof。

8.2.1 检测函数

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

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

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

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

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

8.2.2 检测数组

ECMAScript5将Array.isArray()正式引入JavaScript。该函数可以准确的检测一个值是否为数组。Array.isArray()可以检测跨帧(frame)传递的值。

var arr = [1,2,3,4];
var arr32 = 22;
console.log(Array.isArray(arr)); // true
console.log(Array.isArray(arr32)); // false

8.3 检测属性

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

// 不好的写法 检测假值
if(object[propertyName]){
    // ...
}

// 不好的写法 和null作比较
if(object[propertyName] != null){
    // ... 
}

// 不好的写法 和undefined作比较
if(object[propertyName] != undefined){
    // ...
}

上面这段代码里的每一个判断,实际上都是通过给定的名字来检测属性的值,而非判断给定的名字所指的属性是否存在。因为当属性的值为假(false)时结果会出错。比如0、""(空字符串)、false、null、undefined。毕竟这些都是属性的合法值。比如属性记录了一个数字,则这个值可以为0,这样的话,上段代码的第一个判断就会出错。

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

var object = {
    count: 0,
    relate: null
};

// 好的写法
if ("count" in object) {
    console.log(object.count);
}

// 不好的写法 检查假值
if (object["count"]) {
    console.log(object.count);
}

// 好的写法
if ("relate" in object) {
    console.log(object.relate);
}

// 不好的写法 检测是否为null
if (object["relate"] !== null) {
    console.log(object.relate);
}

如果你只想检测实例对象的某个属性是否存在,则使用hasOwnProperty()方法,所有继承自Object的JavaScript对象都有这个方法,如果实例中存在这个属性则返回true(如果这个属性只存在于原型里,则返回false)。

var object = {
    count: 0,
    relate: null
};

// 好的写法
if (object.hasOwnProperty("count")) {
    console.log(object.count);
}

// 好的写法
if (object.hasOwnProperty("relate")) {
    console.log(object.relate);
}

需要注意的是,在IE8以及更早的版本的IE中,DOM对象并非继承自Object,因此也不包含这个方法。也就是说,你在调用DOM对象的hasOwnProperty()方法之前应当先检查其是否存在(假如你已经知道对象不是DOM,则可以省略这一步)。

// 对于所有非DOM对象来说,这时好的写法
if (object.hasOwnProperty("related")) {
    // 执行代码
}

// 如果你不确定是否为DOM对象,则这样来写
if ("hasOwnProperty" in Object && object.hasOwnProperty("related")) {
    // 执行代码
}

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值