zoukankan      html  css  js  c++  java
  • 【译】Javascript中的数据类型

        这篇文章通过四种方式获取Javascript中的数据类型:通过隐藏的内置[[Class]]属性;通过typeof运算符;通过instanceof运算符;通过函数Array.isArray().我们也会看看原型对象的构造函数,可能会有意想不到的数据类型结果。

        [这篇文章是我在adobe发布的文章,我发布在这里只是为了存档。]

    1. 知识储备

        在开始我们的话题之前,我们不得不复习一些所需的知识点 

        1.1 原始值和对象

        Javascript中的数据,要么是原始值,要么是对象。

        原始值。下面是原始值:

    • undefined
    • null
    • Booleans
    • Numbers
    • Strings

        原始值是不变的,你不能给它们添加属性:

    > var str = "abc";
    > str.foo = 123;  // try to add property "foo”   
    123 
    > str.foo  // no change
    undefined    

        原始值是通过数值进行比较的,如果它们有相同的内容就认为相等。

    > ‘abc’ === ‘abc’
    true

        对象。所有非原始值都是对象。对象是可变的:     

    > var obj = {};
    > obj.foo = 123;  // try to add property “foo” 
    123
    > obj.foo  // property "foo" has been added 
    123  

        对象是通过引用进行比较的。每一个对象都有各自的特性,两个对象只有是同一个对象,才会被认为是相等的。

    > {} === {}
    false
    > var obj = {};
    > obj === obj
    true

        对象封装类型。基本数据类型boolean、number和string都有各自对应的Boolean Number String对象封装类型。与原始值不同,后者都是对象的实例。它们的封装形式是:

    > typeof new String("abc")
    'object'
    > typeof "abc"
    'string'
    > new String("abc") === "abc"
    false

        对象封装类型很少被直接使用,但是它们的原型对象定义了原始值的方法。如:String.prototype是封装类型String的原型对象。它所有的方法对string也可用,原始值同样拥有String.prototype.indexOf,并不是不同的方法使用相同的名称,相同的方法是:

    > String.prototype.indexOf === "".indexOf
    true

        1.2 内部属性

        Javascript中内部属性不能直接访问,但是它影响程序的运行。内部属性的名称以大写字母开始,并且写在双层方括号中。如:[[Extensible]]是一个布尔型的标志,决定对象是否可以扩展属性。它的值可以通过Object.isEntensible()间接的获取它的值,Object.preventExtensions()可以设置它的值为false。一旦变成false之后,就无法再变成true了。

        1.3 术语:原型和原型对象

        在Javascript中,原型拥有多重含义:

        1. 一方面,是原型对象之间的关系。每一个对象都有一个隐藏属性[[Porototype]],指向它的原型对象或者是null。对象的原型是一个延续,如果一个属性无法在对象中找到,可以追溯到它的原型上查找。多个对象可以有相同的对象。

        2. 另一方面,如果一个类型是由构造函数Foo实现的,那么这个构造函数有一个原型对象Foo.prototype,保存类型的原型对象。

        为了很好的区分,我们写了关于原型(1)和原型对象(2)的例子。三种方法帮助我们处理原型:

    • Object.getPrototypeOf(obj),返回obj的原型:
    > Object.getPrototypeOf({}) === Object.prototype
    true
    • Object.create(proto),新建一个原型是proto的空对象。
    > Object.create(Object.prototype)
    {}

            Object.create()可以做的更多,但是超出了这篇文章的范围。

    • proto.isPrototypeOf(obj),如果proto是obj的原型(或者是obj原型的原型),返回true
    > Object.prototype.isPrototypeOf({})
     true

        1.4 constructor属性 

        实现一个构造函数Foo,它的原型Foo.prototype拥有一个属性Foo.prototype.constructor指向构造函数Foo。每个函数都有自动设置这个属性。    

    > function Foo() { }
    > Foo.prototype.constructor === Foo
    true
    > RegExp.prototype.constructor === RegExp
    true

        所有构造函数的实例,继承原型对象上的所有属性,我们可以确定一个实例的构造函数。

    > new Foo().constructor
    [Function: Foo]
    > /abc/.constructor
    [Function: RegExp] 

        2. 数据的类型

        我们可以通过四种方式获取数据类型:

    • [[Class]]是一个内部属性字符串,用来给对象分类
    • typeof是一个运算符,用来区分对象和原始值
    • instance of是一个运算符,用来分类对象
    • Array.isArray()是一个区分数值和数组的函数

        2.1 [[Class]]

        [[Class]]是一个内部属性,它的值有: 

        "Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", RegExp", “String"

        Javascript只能通过toString()方法(Object.prototype.toString())获取。这个方法是通用的,返回:

    • "[object Undefined]" 如果值是undefined,
    • "[object Null]" 如果值是null,
    • "[object " + obj.[[Class]] + "]" 如果是一个对象obj.
    • 原始值转化为对象可以按照上面的规则处理

      如:

    > Object.prototype.toString.call(undefined)
    '[object Undefined]'
    > Object.prototype.toString.call(Math)
    '[object Math]'
    > Object.prototype.toString.call({})
    '[object Object]’
    

        因此,下面的函数返回[[Class]]的值是x 

    function getClass(x) {
        var str = Object.prototype.toString.call(x);
        return /^[object (.*)]$/.exec(str)[1];
    
    }

        下面是一些应用: 

    > getClass(null)
    'Null'
    
    > getClass({})
    'Object'
    
    > getClass([])
    'Array'
    
    > getClass(JSON)
    'JSON'
    
    > (function () { return getClass(arguments) }())
    'Arguments'
    
    > function Foo() {}
    > getClass(new Foo())
    ‘Object'

        2.2 typeof

        typeof对原始值进行分类,可以帮助我们区分原始值和对象。

            typeof value

        下面是一些值和结果的对照关系:

     

    结果

    undefined

    “undeifined"

    null

    “object"

    Boolean

    “boolean"

    Number

    “number"

    String

    “string"

    Function

    “function"

    其他值

    “object"

        typeof null返回object是一个bug,但是不能修复,因为它会破坏现在存在的代码。注意,function也是一个对象,但是typeof做了区分,Array也是一个对象。

        2.3 instanceof

        instanceof检测值是不是一个构造函数的实例:

            value instanceof Type

        这个操作符看起来像Type.prototype,检测原型链是否有value。如果我们自己实现的话,就像下面这个样子(会有一些错误,比如说null):

    function myInstanceof(value, Type) {
            return Type.prototype.isPrototypeOf(value);
    }

        原始值使用instanceof都会返回false

    > "" instanceof String
    false
    > "" instanceof Object
    false

        2.4 Array.isArray()

        Array.isArray()这个方法存在是因为浏览器的一个特殊的问题:每一个frame都有一个自己的运行环境。如:现在存在frame A和frame B(每一都有自己的document)。通过frame A 传递参数给frame B,frame B中不能通过instanceof判断,传递的参数是不是array。因为frame A中的Array和frame B中Array(array是它的一个实例)并不是同一个。 

    <html>
    <head>
        <script>
            // test() is called from the iframe
            function test(arr) {
                var iframeWin = frames[0];
                console.log(arr instanceof Array); // false
                console.log(arr instanceof iframeWin.Array); // true
                console.log(Array.isArray(arr)); // true
            }
        </script>
    </head>
    <body>
        <iframe></iframe>
        <script>
            // Fill the iframe
            var iframeWin = frames[0];
            iframeWin.document.write(
                '<script>window.parent.test([])</'+'script>');
        </script>
    </body>
    </html>

        因此,ES5中Array.isArray()使用[[Class]]来检查一个值是不是数组。它的意图是使JSON.stringify()更安全,instanceof的问题存在于各个类型中

        3. 内置的原型对象

        原型对象的内置类型是奇怪的值:他们都是原始的值,而不是实例。这就导致分类很诡异。为了摸清这个诡异的现象,我们需要深入理解分类。

        3.1 Object.prototype

        Object.prototype看起来更像是一个空对象:不存在任何可以枚举的属性(它的所有方法都是不可枚举的)。

    > Object.prototype
    {}
    > Object.keys(Object.prototype)
    []

        Object.prototype是一个对象,但是不是Object函数的实例。一方面,通过typeof和[[Class]]得到它是一个对象。 

    > getClass(Object.prototype)
    'Object'
    > typeof Object.prototype
    ‘object'

        另一方面,instanceof不认为它是Object的实例。    

    > Object.prototype instanceof Object
    false

        为了让上面的结果变成true,Object.prototype必须在它的原型链上,这样就会原型链上形成一个死循环。这就是为什么Object.prototype没有prototype属性了,它是唯一一个内置对象。

     > Object.getPrototypeOf(Object.prototype)
    null

        这是所有内置对象的一个悖论:它算是实例类型但不是instanceof。

         [[Class]], typeof and instanceof 在其他对象上是适用的:

    > getClass({})
    'Object'
    > typeof {}
    'object'
    > {} instanceof Object
    true

        3.2 Function.prototype

        Function.prototype是它的函数本身,接受任何的参数都返回undefined:

    > Function.prototype("a", "b", 1, 2)
    undefined

        Function.prototype是一个函数,但是不是Function的实例: 一方面, typeof Function.prototype的结果是一个函数:    

    > typeof Function.prototype
    'function' 

        通过内部属性[[Class]]结果也一样:

    > getClass(Function.prototype)
    'Function'

        另一方面, instanceof表明Function.prototype不是Function实例。

    > Function.prototype instanceof Function
    false

        这就是为什么Function.prototype没有存在它的原型链上. 相反, 它的原型是Object.prototype:     

    > Object.getPrototypeOf(Function.prototype) === Object.prototype
    true  

        其他函数没有什么特别的:

    > typeof function () {}
    'function'
    > getClass(function () {})
    'Function'
    > function () {} instanceof Function
    true

        任何场景下,Function还是Function

    > typeof Function
    'function'
    > getClass(Function)
    'Function'
    > Function instanceof Function
    true    

        3.3 Array.prototype

        Array.prototype是一个空数组:它的长度是0.

    > Array.prototype
    []
    > Array.prototype.length
    0

        [[Class]]也认为它是array:

    > getClass(Array.prototype)
     ‘Array'

        Array.isArray()也是这样的,因为它是基于[[Class]]实现的:

    > Array.isArray(Array.prototype)
     true

        自然而然,instanceof不是这样的:

    > Array.prototype instanceof Array
    false

        在这个章节我们不会提醒y原型对象不是他们构造函数的实例。

        3.4 RegExp.prototype

        RegExp.prototype是一个匹配任何东西的正则表达式:

    > RegExp.prototype.test("abc")
    true
    > RegExp.prototype.test("")
    true

        RegExp.prototype也可以使用String.prototype.match通过[[Class]],检测参数是不是一个正则表达式. 检测结果如下: 

    > getClass(/abc/)
    'RegExp'
    > getClass(RegExp.prototype)
    'RegExp'

        空的正则表达式. RegExp.prototype和“空正则表达式”相等。 可以通过一下两种方式实现: 

    new RegExp("")  // constructor 构造函数
     /(?:)/          // literal 字面量

        如果你想动态的生成一个正则表达式,只能通过构造函数才能创建。通过字面量的方式创建一个空的正则表达式,如: // 是不能直接使用的。应该是用(?:)空的非捕捉分组来实现空的正则表达式。    

    > new RegExp("").exec("abc")
    [ '', index: 0, input: 'abc' ]
    > /(?:)/.exec("abc")
    [ '', index: 0, input: 'abc' ]
    

        比较发现,空的分组不仅可以完成匹配,并且可以捕捉分组一中:

    > /()/.exec("abc")
    [ '',  // index 0
      '',  // index 1
      index: 0,
      input: 'abc’ ]

        有意思的是,空正则表达式不管是构造函数形式的还是RegExp.prototype形式的,它们最终的展现结果都是字面量: 

    > new RegExp("")
    /(?:)/
    > RegExp.prototype
    /(?:)/

        3.5 Date.prototype

    Date.prototype也是date类型:
    > getClass(new Date())
    'Date'
    > getClass(Date.prototype)
    'Date'  

        日期是数字. 在ES5.1中的描述是这样的:    

    A Date object contains a Number indicating a particular instant in time to within a millisecond. Such a Number is called a time value. A time value may also be NaN, indicating that the Date object does not represent a specific instant of time.
    
     Time is measured in ECMAScript in milliseconds since 01 January, 1970 UTC.

        两种方式可以获取日期的时间戳,一种是通过调用valueof()方法,一种是调用Number函数:

    > var d = new Date(); // now
    
    > d.valueOf()
    1347035199049
    > Number(d)
    1347035199049

        Date.prototype的时间戳是NaN:

    > Date.prototype.valueOf()
    NaN
    > Number(Date.prototype)
    NaN 

        Date.prototype是一个非法的日期, 好像是同过NAN创建的一样:     

    > Date.prototype
    Invalid Date
    > new Date(NaN)
    Invalid Date

        3.6 Number.prototype

        Number.prototype的值和new Number(0)是一样的: 

    > Number.prototype.valueOf()
    0
    
    转换成数字的话,返回基本数据类型:
    
    > +Number.prototype
    0
    
    比较:
    
    > +new Number(0)
    
    0
    

      

      3.7 String.prototype

      String.prototype和new String("")的值是一样的:

    > String.prototype.valueOf()
    ''
    
    转化为字符串的话,返回基本数据类型:
    
    > "" + String.prototype
    ''
    
    比较:
    
    > "" + new String("")
    ''

        3.8 Boolean.prototype

        Boolean.prototype和new Boolean(false)的值是一样的:

    > Boolean.prototype.valueOf()
    false

        布尔对象可以转化为布尔值,但是所有的结果都是true,因为对象转换成布尔值都是true。     

    > !!Boolean.prototype
    true
    > !!new Boolean(false)
    true
    > !!new Boolean(true)
    true

        这个对象转化成数字或者字符串不同:如果对象封装了原始值,那么转换结果就是封装的原始值。

        译者注:比如我使用Object实例化一个数字,我会这么操作:

    > new Object(1);
    Number {[[PrimitiveValue]]: 1}
    //这就是上面所有的,被封装过的原始值

        4. 推荐

        本节给出了很多建议,怎么能最好的区分Javascript中的数据类型。

        4.1 把原型对象作为原始类型的成员

        一个原型对象总是一个原始类型的成员吗?不,这仅仅适用于内置的类型。一般而言,原型对象的行为很神奇,最好是把它们作为模拟类:它们包含所有实例共享的属性(通常方法)。

        4.2 使用哪个分类机制

        当决定如何最好的使用Javascript中分类机制,必须区分正常的代码和不同frame的代码。

        普通代码:在普通代码中,使用typeof或者instanceof,而不是[[Class]]和Array.isArray()。你必须清楚的知道typeof的特殊结果:null的结果是object,有两个非原始值的分类:function和object。如判断一个函数是不是一个对象可以通过下面的方式:

    function isObject(v) {
        return (typeof v === "object" && v !== null)
    
            || typeof v === "function";
    
    }

        尝试:

    > isObject({})
    true
    > isObject([])
    true
    > isObject("")
    false
    > isObject(undefined)
    false

       代码跨frame传递:如果接收其他frame传递的值,那么使用instanceof就不再可用了,必须考虑使用[[Class]]或者是Array.isArray()。另外一个选择就是获得构造函数名,但是这个也不是很靠谱:不是所有的对象都有构造函数,也不是所有的构造函数都有名称。下面是如何获得构造函数的名称: 

    function getConstructorName(obj) {
        if (obj.constructor && obj.constructor.name) {
            return obj.constructor.name;
        } else {
            return "";
        }
    }

        另外需要指出的是,函数的name属性(obj.constructor)不是一个标准,如:IE浏览器就不支持。

        尝试:

    > getConstructorName({})
    'Object'
    > getConstructorName([])
    'Array'
    > getConstructorName(/abc/)
    'RegExp'
    > function Foo() {}
    > getConstructorName(new Foo())
    'Foo'

        如果对原始值使用getConstructorName方法的话,它的值是该类型对应的构造函数:

     > getConstructorName("")
    'String'

        那是因为原始值获取了原型对象上的constructor属性:   

     > "".constructor === String.prototype.constructor
    true

        5. 下一步读些什么

        通过这篇文章知道Javascript中,怎么对数据进行分类。不幸的是,为了能够正确的执行,需要了解一些详细的知识。作为两个主要的分类是有缺陷的:typeof null是object,instanceof不能跨frame。文章也介绍了解决缺陷的建议。

        下一步,需要进一步了解Javascript的继承,下面的四篇博客可以作为入门:

     

    原文地址:http://www.2ality.com/2013/01/categorizing-values.html

  • 相关阅读:
    Markdown文档示例
    Python网络编程(一)
    JS之客户端检测
    MySQL 多表查询
    MySQL 单表查询
    MySQL 入门
    Python内置方法大全
    010 盒模型
    009 CSS选择器
    008 常用样式
  • 原文地址:https://www.cnblogs.com/xiaoheimiaoer/p/4575002.html
Copyright © 2011-2022 走看看