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

  • 相关阅读:
    leetcode Convert Sorted List to Binary Search Tree
    leetcode Convert Sorted Array to Binary Search Tree
    leetcode Binary Tree Level Order Traversal II
    leetcode Construct Binary Tree from Preorder and Inorder Traversal
    leetcode[105] Construct Binary Tree from Inorder and Postorder Traversal
    证明中序遍历O(n)
    leetcode Maximum Depth of Binary Tree
    限制 button 在 3 秒内不可重复点击
    HTML 和 CSS 画三角形和画多边行基本原理及实践
    在线前端 JS 或 HTML 或 CSS 编写 Demo 处 JSbin 与 jsFiddle 比较
  • 原文地址:https://www.cnblogs.com/xiaoheimiaoer/p/4575002.html
Copyright © 2011-2022 走看看