美文网首页
《编写可维护的JavaScript》读书笔记之编程实践-避免“空

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

作者: 游学者灬墨槿 | 来源:发表于2019-01-07 14:04 被阅读3次

    避免“空比较”

    在 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
    
    1. 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
    
    1. typeof 检测函数的限制:在 IE 8 和更早版本的 IE 浏览器中,使用 typeof 来检测 DOM 节点(比如 document.getElementById())中的函数都返回 "object" 而不是 "function"。之所以出现这种现象是因为浏览器对 DOM 的实现有差异。简言之,早期的 IE 并没有将 DOM 实现为内置的 JavaScript 方法,导致内置 typeof 运算符将这些函数识别为对象。
    2. 在 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()。

    相关文章

      网友评论

          本文标题:《编写可维护的JavaScript》读书笔记之编程实践-避免“空

          本文链接:https://www.haomeiwen.com/subject/marurqtx.html