zoukankan      html  css  js  c++  java
  • 打造属于自己的underscore系列 ( 二 )

    在上一篇 打造属于自己的underscore系列 ( 一 )的文章中,我们介绍了underscore 的一些设计思想和理念,并对框架的结构进行了详细的介绍,这一节的源码打造,我们会深入javascript的数据类型,并会对underscore对各种数据类型的判定方法进行分析。

    二, 数据类型诊断

    2.1. isArray - 判断数组

    判断一个对象是否为数组的方法常用的有:

    • ES5 方法: Array.isArray(obj)
    • instanceof: obj instanceof Array
    • Object.prototype.toString.call(obj) === "[object Array]"

    前面的两个方法或多或少存在缺陷,低版本浏览器不支持ES5 Array.isArray()的新方法,而instanceof 判定规则在跨iframe 中也存在问题。比如,一个页面(父页面)有一个框架,框架中引用了一个页面(子页面),在子页面中声明了一个array,并将其赋值给父页面的一个变量,这时判断该变量时使用 instanceOf便不准确了。因此最正确的方法是使用Object.prototype.toString.call(obj)来判断数组。

    // 判断数组
      _.isArray = function (obj) {
        return Array.isArray(obj) || toString.call(obj) === '[object Array]'
      }
    
    2.2. isObject - 判断对象

    如果object是一个对象,返回true。需要注意的是JavaScript数组和函数是对象,字符串和数字不是。

    typeof 可以用来判断数据类型属于Object,同时,Function 类型的数据同样属于对象。而null 虽然是对象,但是需要排除

    // 判断对象
      _.isObject = function (obj) {
        var type = typeof obj;
        return type === 'function' || type === 'object' && !!obj
      }
    
    2.3 深入Object.prototype.toString.call()

    我们知道在js中,一切都是对象,而Object原型对象上都有一个 toString()方法,toString() 方法调用会返回"[object type]", 其中type 是对象的类型,在ES6以前,js内置对象类型 主要有'Arguments', 'Function', 'String', 'Boolean', 'Number', 'Date', 'RegExp', 'Error', ES6之后增加了诸如'Symbol', 'Map', 'WeakMap', 'Set', 'WeakSet' 的数据类型。那既然toString 的方法可以用来判断对象的具体类型,为什么还需要通过Object.prototype.toString.call(obj) 的方式来调用呢?

    原来toString()虽然作为Object原型上的方法,但是Array ,Function等类型作为Object的实例,都重写了toString方法。因此直接调用对象的toString()方法并不会返回数据类型,而是返回重写后的结果。我们可以举几个例子

    var a = function(){console.log(2)}
    a.toString() // 'function(){console.log(2)}'
    
    var b = [2,5,6];
    b.toString() // "2,5,6"
    
    var f = new Date()
    f.toString() // "Thu Jan 10 2019 14:33:08 GMT+0800 (中国标准时间)"
    
    var p = /d/g
    p.toString() // "/d/g"
    
    var h = new Error('33')
    h.toString() // "Error: 33"
    
    var i = Symbol(3);
    i.toString() // "Symbol(3)"
    ···
    
    

    因此 Arguments', 'Function', 'String', 'Boolean', 'Number', 'Date', 'RegExp', 'Error', 'Symbol', 'Map', 'WeakMap', 'Set', 'WeakSet'对象类型的判定方法,我们可以统一用Object.prototype.toString.call(obj) 来实现

    // 对象类型判断方法
      _.each(['Arguments', 'Function', 'String', 'Boolean', 'Number', 'Date', 'RegExp', 'Error', 'Symbol', 'Map', 'WeakMap', 'Set', 'WeakSet'], function (name) {
        _['is' + name] = function (obj) {
          return toString.call(obj) === '[object ' + name + ']';
        };
      });
    
    2.4 isFinite - 判断是否是一个有限的数字

    javascript原生提供了一个isFinite() 的函数来判断number(或者可转成number 的值) 是否为无穷大。注意,判断条件包括可转化为number 类型的值,也就是针对 true,false的布尔值,以及null的特殊值,可以通过隐式转换为数字,inFinite(true)返回的是true, 因此我们使用isFinite来判断一个纯的有限数字并不妥当。并且为了避免Symbol类型做类型转换时报错,我们需要先排除Symbol的数据类型。

    // 判断数字是否为有限值
      _.isFinite = function(obj) {
        return !_.isSymbol(obj) && isFinite(obj) && !isNaN(parseFloat(obj));
      }
    
    2.5 _.keys - 对象所拥有的可枚举属性的集合,不包括原型链

    往下介绍之前,先介绍一下一个重要的方法:_.keys(), _.keys()是用来枚举 对象中可枚举属性,并以数组的形式返回。我们知道,要遍历对象的属性可以通过 for in 来遍历,ES5中也有新增Object.keys方法,Object.getOwnProperty的方法同样能获取对象的属性,那三者的区别在哪里呢?

    • for in 遍历对象自身和原型上的可枚举属性
    • Object.keys 是ES5新增的方法, 他可以遍历对象自身的可枚举属性,但不包括原型链上的属性
    • Object.getOwnProperty() 遍历对象自身可枚举,不可枚举属性,不包括原型链上的属性
    • 在不支持ES5的条件下,只要在for in 基础上,可以通过 obj.hasOwnProperty(属性) 便可以来排除原型链上的属性方法。

    认清出这几点后,keys方法的设计就很简单了

    // 遍历对象自身可枚举属性
      _.keys = function(obj) {
        if(!_.isObject(obj)) return []; // 非对象则返回空数组
        if(Object.keys) return Object.keys(obj); // 支持ES5方法,则使用Object.keys()
        var keys = []
        for(var i in obj) {            // 不支持,通过for in 遍历并排除原型链上的属性
          if(obj.hasOwnProperty(i)) keys.push(i)
        }
        return keys
      }
    

    在underscore源码中,我们看到了这样的一段兼容性代码。 对于IE9以下而言,forin 遍历对象存在着某种程度的缺陷,我们知道,诸如 valueof,toString这些定义在Ojbect原型上的方法是不可枚举的,而当我们重写这些方法后,我们访问的是这些可枚举的自定义方法。而对于IE9 以下而言。即使重写了不可枚举的方法后,依然无法在可枚举属性中遍历。所以我们需要做对低版本的进行兼容。

    _.keys = function(obj) {
        ···
        if (hasEnumBug) collectNonEnumProps(obj, keys); // 收集不可枚举属性
        return keys
    }
    
    var hasEnumBug = !{
        toString: null
      }.propertyIsEnumerable('toString'); // 重写toString 方法,并判断是否是可枚举的。
      var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
        'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'
      ]; //  枚举所有Object原型上不可枚举的属性方法。
    
    var collectNonEnumProps = function (obj, keys) {
        var nonEnumIdx = nonEnumerableProps.length;
        var constructor = obj.constructor;
        var proto = _.isFunction(constructor) && constructor.prototype || ObjProto;
    
        // Constructor is a special case.
        var prop = 'constructor';
        if (has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);
    
        while (nonEnumIdx--) {    // 核心: 举例,判断对象的toString 方法是否和 对象.constructor.protopye.toString 的内存地址是否相同,不相同,则判定重写了方法。
            prop = nonEnumerableProps[nonEnumIdx];
            if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
                keys.push(prop);
            }
        }
    };
    
    2.6 _.allKeys - 对象所拥有的可枚举属性的集合,包括原型链

    allkeys方法和keys 方法唯一的不同在于,allkeys遍历的集合包括了原型链上的属性和方法,因此我们可以沿用keys的方法实现,并删除是否为自身属性的判断即可。

    // 遍历对象上可枚举属性和方法,包括原型链
    _.allkeys = function(obj) {
        if(!_.isObject(obj)) return []; // 非对象则返回空数组
        if(Object.keys) return Object.keys(obj); // 支持ES5方法,则使用Object.keys()
        var keys = []
        return keys
      }
    
    2.7 _.isEmpty

    如果object 不包含任何值(没有可枚举的属性),返回true。 对于字符串和类数组(array-like)对象,如果length属性为 0,那么_.isEmpty检查返回true。

    //判断对象是否有可枚举属性,字符串,类数组属性length 是否为0
    
      _.isEmpty = function(obj) {
        if(_.isArray(obj) || _.isString(obj) || _.isArguments(obj)) return obj.length === 0
        return _.keys(obj).length === 0;
      }
    
    2.8 _.isNaN - 判断obj是否为NaN

    javascript 原生提供了一个isNaN() 的函数,该函数用于检查其参数是否为非数字值。一般情况下,isNaN() 函数用于检测 parseFloat() 和 parseInt() 的结果,以判断它们表示的是否是合法的数字。而underscore 的isNaN 方法判断的唯一标准是NaN 其他情况都会返回false,因此 _.isNaN 方法的实现如下:

    // 判断obj 是否为NaN
      _.isNaN = function(obj) {
        return _.isNumber(obj) && isNaN(obj)  // 必须是数字,且为NaN
      }
    
    2.9 _.isNull - 判断obj 是否为Null
    // 判断obj 是否为null
      _.isNull = function (obj) {
        return obj === null
      }
    
    2.10 _.isUndefined - 判断obj 是否为undefined
    // 
    _.isUndefined = function(obj) {
        return obj === void 0
    }
    

    为什么undefined 我们通过void 0 来判断, 而不是直接和 "undefined"比较呢?

    在ES5之前,undefined 是可以被重写的,在ES5之后修复了这个问题,但是即使修复了全局环境下重写的问题,在局部环境下,依然可以被重写

    (function() {
      var undefined;
      undefined = 1
      console.log(undefined) // 1
    }())
    

    而void 无论什么值,返回的都是undefined

    void function test() {
      console.log('boo!');
      // expected output: "boo!"
    }();
    
    try {
      test();
    }
    catch(e) {
      console.log(e);
      // expected output: ReferenceError: test is not defined
    }
    
    
    2.11 _.isElement 判断dom元素
    // 判断obj 为一个DOM元素
      _.isElement = function(obj) {
        return !!(obj && obj.nodeType === 1);
      }
    





    > 本文为博主原创文章,转载请注明出处 https://juejin.im/post/5c382aede51d4543805e89d2
  • 相关阅读:
    Visual C# 2008+SQL Server 2005 数据库与网络开发14.1.2 WPF的组成
    Visual C# 2008+SQL Server 2005 数据库与网络开发13.1.3 简单记事本程序菜单设计
    Visual C# 2008+SQL Server 2005 数据库与网络开发13.1.1 菜单创建
    Feathers TextInput使KeyboardEvent失效
    UILabel 多行显示
    突破flash player的睡眠模式
    突破flash player睡眠模式 后续
    缩放UIImage
    IT菜鸟报到!
    用VMware装了Ubuntu后,安装VMware Tools
  • 原文地址:https://www.cnblogs.com/kidflash/p/10254773.html
Copyright © 2011-2022 走看看