《编写可维护的JavaScript》读书笔记之编程实践-避免“空比较”

本文为转载,原文链接:clvsit的博客

  • 避免“空比较"

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

var Controller = {
    process : function(items) {
        // 不好的写法
        if(items !== null) {
            items.sort();
            items.forEach(function(item) {
                // 执行一些逻辑
            });
        }
    }
};
  • 意图:如果参数 items 不是一个数组,则停止接下来的操作。
  • 问题:和 null 的比较并不能真正避免错误的发生。
  • 原因:items 的值可以是 1,也可以是字符串,甚至可以是任意对象。这些值都和 null 不想等,进而会导致 process() 方法一旦执行到 sort() 时就会出错。
  • 结论:仅仅和 null 比较并不能提供足够的信息来判断后续代码的执行是否真的安全。
  • 检测原始值

    如果你希望一个值是字符串、数字、布尔值或 undefined,最佳选择是使用 typeof 运算符。typeof 运算符会返回一个表示值的类型的字符串。

    • 字符串:typeof 返回 “string”。
    • 数字:typeof 返回 “number”。
    • 布尔值:typeof 返回 “boolean”。
    • undefined:typeof 返回 “undefined”。

    typeof 语法:

    typeof variable
    /*
     * 尽管这是合法的 JavaScript 语法,这种用法让 typeof
     * 看起来像一个函数而非运算符。因此,更推荐无括号的写法
     */
    typeof(variable)
    

    检测 4 种原始值的做法:

    // 检测字符串
    if(typeof name === "string") {
        anotherName = name.substring(3);
    }
    
    // 检测数字
    if(typeof count === "number") {
        unpdateCount(count);
    }
    
    // 检测布尔值
    if(typeof found === "boolean" && found) {
        message("Found!");
    }
    
    // 检测 undefined
    if(typeof MyApp === "undefined") {
        MyApp = {
            // 其他的代码
        };
    }
    

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

    关于 null:

    • 概述:原始值 null 一般不应用于检测,因为简单地和 null 比较通常不会包含足够的信息以判断值的类型是否合法。但有个例子,如果所期望的值真的是 null,则可以直接和 null 进行比较。这时应当使用 === 或者 !== 来和 null 进行比较。
    • 示例
    // 如果你需要检测 null,则使用这种方法
    var element = document.getElementById("my-div");
    if(element !== null) {
        element.className = "found";
    }
    /*
     * 如果 DOM 元素不存在,则获得的节点的值为 null。这个方法要么返回一个节点,要么返回 null。
     * 由于这时 null 是可预见的一种输出,则可以使用 !== 来检测返回结果。
    
  • 注意:运行 typeof null 则返回 “object”,这是一种低效的判断 null 的方法,甚至被认为是标准规范的严重 bug,因此在编程时杜绝使用 typeof 来检测 null 的类型。如果你需要检测 null,则直接使用恒等运算符(===)或非恒等运算符(!==)。
  • 检测引用值

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

    常见的内置引用类型:
    • Object
    • Array
    • Date
    • RegExp
    • Error

    对引用值的检测:

    • typeof 运算符在判断这些引用类型以及 null 时都会返回 “object”,因此 typeof 不适合检测引用值。
    var arr = [1, 2],
    	obj = {name: "clvsit"},
    	date = Date(),
    	reg = RegExp(),
    	err = Error();
    console.log(typeof arr === "object"); // true
    console.log(typeof obj === "object"); // true
    console.log(typeof date === "object"); // false
    console.log(typeof reg === "object"); // true
    console.log(typeof err === "object"); // true
    

    从上述示例可以发现,typeof date === “object” 返回 false,为什么会这样?

    console.log(typeof date); // 'string'
    console.log(date.toString()); // "Sat Jan 05 2019 15:06:28 GMT+0800 (中国标准时间)"
    console.log(date); // "Sat Jan 05 2019 15:06:28 GMT+0800 (中国标准时间)"
    

    实践可知,在使用 Date 对象时会自动调用 Date 对象的 toString() 方法,因此 typeof date 得到的结果为 ‘string’。

    • 检测某个引用值的类型的最好方法是使用 instanceof 运算符。

    instanceof:

    • 语法
    value instanceof constructor
    
    • 示例
    // 检测日期
    if(value instanceof Date) {
        console.log(value.getFullYear());
    }
    

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

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

  • 注意
    1. instanceof 不仅检测构造这个对象的构造器,还检测原型链。而原型链包含了很多信息,包括定义对象所采用的继承模式。比如,默认情况下,每个对象都继承来自 Object,因此每个对象的 value instanceof Object 都会返回 true(使用 value instanceof Object 来判断对象是否属于某个特定类型的做法并非最佳)。
    var now = new Date();
    

    console.log(now instanceof Object); // true
    console.log(now instanceof Date); // 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

    存在的限制:
    • 情景:假设一个浏览器帧(frameA) 里的一个对象被传入到另一个浏览器帧(frameB)中,两个帧里都定义了构造函数 Person。如果来自帧 A 的对象是 帧 A 的 Person 的实例,则如下规则成立:
    // true
    frameAPersonInstance instanceof frameAPerson;
    

    // false
    frameAPersonInstance instanceof frameBPerson;

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

    检测函数

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

    function myFunc() {}
    

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

  • 注意
    1. instanceof 不能跨帧(frame)使用,因为每个帧都有各自的 Function 构造函数。
    2. typeof 运算符也是可以用于函数的,返回 “function”。检测函数最好的方法是使用 typeof,因为它可以跨帧使用。
    function myFunc() {}
    

    // 好的写法
    console.log(typeof myFunc === “function”); // true

  • typeof 检测函数的限制:在 IE 8 和更早版本的 IE 浏览器中,使用 typeof 来检测 DOM 节点(比如 document.getElementById())中的函数都返回 “object” 而不是 “function”。之所以出现这种现象是因为浏览器对 DOM 的实现有差异。简言之,早期的 IE 并没有将 DOM 实现为内置的 JavaScript 方法,导致内置 typeof 运算符将这些函数识别为对象。
  • 在 IE 8 及早期版本,开发者往往通过 in 运算符来检测 DOM 的方法。
  • // 检测 DOM 方法
    if("querySelectorAll" in document) {
        images = document.querySelectorAll("img");
    }
    
    1. 尽管使用 in 检测 DOM 方法不是最理想的方法,但如果想在 IE 8 及更早浏览器中检测 DOM 方法是否存在,这是最安全的做法。在其他所有情形中,typeof 运算符是检测 JavaScript 函数的最佳选择。

    检测数组

    JavaScript 最古老的跨域问题之一就是在帧之间来回传递数组。因为每个帧都有各自的 Array 构造函数,因此一个帧中的实例在另外一个帧里面不会被识别。

    • 检测方法
    1. 鸭式辨型(duck typing)
    // 采用鸭式辨型的方法检测数组
    function isArray(value) {
        return typeof value.sort === "function";
    }
    /*
     * 缺陷:数组是唯一包含 sort() 方法的对象。
     * 如果传入 isArray() 的参数是一个包含 sort() 
     * 方法的对象,也会返回 true。
     */
    var obj = {
        sort: function () {
    
        // ...
    }
    

    };
    isArray(obj) // true;

    1. Kangax 方案
    function isArray(value) {
        return Object.prototype.toString.call(value) === "[object Array]";
    }
    
    • 解释
      调用某个值的内置 toString() 方法在所有浏览器中都会返回标准的字符串结果。对于数组来说,返回的字符串为 “[object Array]”,也不用考虑数组实例实在哪个帧中被构造出来。
    • 缺陷
      对于自定义对象使用这种方法会存在问题,比如内置 JSON 对象将返回 “[object JSON]”。
    • 后续
      ECMAScript5 将 Array.isArray() 正式引入 JavaScript。唯一的目的就是准确地检测一个值是否为数组。Array.isArray() 可以检测跨帧传递的值。同样很多 JavaScript 类库都实现了这个方法。
    function isArray(value) {
        if(typeof Array.isArray === "function") {
            return Array.isArray(value);
        } else {
            return Object.prototype.toString.call(value) === "[object Array]";
        }
    }
    

    检测属性

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

    // 不好的写法:检测假值
    if(object[propertyName]) {
        // 一些代码
    }
    

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

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

    • 解释
      上面这段代码里的每个判断,实际上是通过给定的名字来检查属性的值,而非判断给定的名字所指的属性是否存在。因为当属性值为假值(false value)时结果会出错,比如 0、""、false、null 和 undefined。毕竟,这些都是属性的合法值。比如,如果属性记录了一个数字,则这个值可以是零,这样的话,上段代码中的第一个判断就会导致错误。以此类推,如果属性值为 null 或者 undefined 时,三个判断都会导致错误。

    • 合适的方式
      判断属性是否存在的最好方法是使用 in 运算符。因为 in运算符仅仅会简单地判断属性是否存在,而不会去读属性的值,这样就可以避免解释部分提到的问题。如果实例对象的属性存在、或者继承自对象的原型,in 运算符都会返回 true。

    var object = {
        count : 0,
        related : null
    }
    

    // 好的写法
    if(“count” in object) {
    // 这里的代码会执行
    }

    // 不好的写法
    if(object[“count”]) {
    // 这里的代码不会执行
    }

    // 好的写法
    if(“related” in object) {
    // 这里的代码会执行
    }

    // 不好的写法:检测是否为 null
    if(object[“related”] !== null) {
    // 这里代码不会执行
    }

    hasOwnProperty():

    • 概述
      如果只想检查实例对象的某个属性是否存在,可以使用 hasOwnProperty() 方法,所有继承自 Object 的 JavaScript 对象都有这个方法。如果实例中存在这个属性则返回 true(如果这个属性只存在于原型里,则返回 false)。
    • 注意
      在 IE 8 及早期版本,DOM 对象并非继承自 Object,因此也不包含这个方法。也就是说,你在调用 DOM 对象的 hasOwnProperty() 方法之前应当先检测其是否存在(假如你已经知道对象不是 DOM,则可以省略这一步)。
    // 对于所有非 DOM 对象来说,这是好的写法
    if(object.hasOwnProperty("related")) {
        // 执行这里的代码
    }
    

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

    • 总结
      因为存在 IE 8 以及更早版本 IE 的情形,在判断实例对象的属性是否存在时,推荐使用 in 运算符。只有在需要判断实例属性时才会用到 hasOwnProperty()。
            </div>
    					<link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-df60374684.css" rel="stylesheet">
                </div>
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值