zoukankan      html  css  js  c++  java
  • js 数据类型

    JavaScript语言规定了7种语言类型

    1. Undefined;
    2. Null;
    3. Boolean;
    4. String;
    5. Number;
    6. Symbol;
    7. Object。

    Undefined、Null

    我们的第一个问题,为什么有的编程规范要求用void 0代替undefined?现在我们就分别来看一下。

    Undefined 类型表示未定义,它的类型只有一个值,就是 undefined。任何变量在赋值前是 Undefined 类型、值为 undefined,一般我们可以用全局变量undefined(就是名为undefined的这个变量)来表达这个值,或者 void 运算来把任一一个表达式变成 undefined 值。

    但是呢,因为JavaScript的代码undefined是一个变量,而并非是一个关键字,这是JavaScript语言公认的设计失误之一,所以,我们为了避免无意中被篡改,我建议使用 void 0 来获取undefined值。

    Undefined跟 null 有一定的表意差别,null表示的是:“定义了但是为空”。所以,在实际编程时,我们一般不会把变量赋值为 undefined,这样可以保证所有值为 undefined 的变量,都是从未赋值的自然状态。

    Null 类型也只有一个值,就是 null,它的语义表示空值,与 undefined 不同,null 是 JavaScript 关键字,所以在任何代码中,你都可以放心用 null 关键字来获取 null 值。

    Boolean

    Boolean 类型有两个值, true 和 false,它用于表示逻辑意义上的真和假,同样有关键字 true 和 false 来表示两个值。

    String

    我们来看看字符串是否有最大长度。

    String 用于表示文本数据。String 有最大长度是 2^53 - 1,这在一般开发中都是够用的,但是有趣的是,这个所谓最大长度,并不完全是你理解中的字符数。

    因为String 的意义并非“字符串”,而是字符串的 UTF16 编码,我们字符串的操作 charAt、charCodeAt、length 等方法针对的都是 UTF16 编码。所以,字符串的最大长度,实际上是受字符串的编码长度影响的。

    Note:现行的字符集国际标准,字符是以 Unicode 的方式表示的,每一个 Unicode 的码点表示一个字符,理论上,Unicode 的范围是无限的。UTF是Unicode的编码方式,规定了码点在计算机中的表示方法,常见的有 UTF16 和 UTF8。 Unicode 的码点通常用 U+??? 来表示,其中 ??? 是十六进制的码点值。 0-65536(U+0000 - U+FFFF)的码点被称为基本字符区域(BMP)。

    JavaScript 中的字符串是永远无法变更的,一旦字符串构造出来,无法用任何方式改变字符串的内容,所以字符串具有值类型的特征。

    JavaScript 字符串把每个 UTF16 单元当作一个字符来处理,所以处理非BMP(超出 U+0000 - U+FFFF 范围)的字符时,你应该格外小心。

    JavaScript 这个设计继承自 Java,最新标准中是这样解释的,这样设计是为了“性能和尽可能实现起来简单”。因为现实中很少用到 BMP 之外的字符。

    Number

    下面,我们来说说Number类型。Number类型表示我们通常意义上的“数字”。这个数字大致对应数学中的有理数,当然,在计算机中,我们有一定的精度限制。

    JavaScript中的Number类型有 18437736874454810627(即2^64-2^53+3) 个值。

    JavaScript 中的 Number 类型基本符合 IEEE 754-2008 规定的双精度浮点数规则,但是JavaScript为了表达几个额外的语言场景(比如不让除以0出错,而引入了无穷大的概念),规定了几个例外情况:

    • NaN,占用了 9007199254740990,这原本是符合IEEE规则的数字;
    • Infinity,无穷大;
    • -Infinity,负无穷大。

    另外,值得注意的是,JavaScript中有 +0 和 -0,在加法类运算中它们没有区别,但是除法的场合则需要特别留意区分,“忘记检测除以-0,而得到负无穷大”的情况经常会导致错误,而区分 +0 和 -0 的方式,正是检测 1/x 是 Infinity 还是 -Infinity。

    根据双精度浮点数的定义,Number类型中有效的整数范围是-0x1fffffffffffff至0x1fffffffffffff,所以Number无法精确表示此范围外的整数。

    同样根据浮点数的定义,非整数的Number类型无法用 ==(===也不行) 来比较,一段著名的代码,这也正是我们第三题的问题,为什么在JavaScript中,0.1+0.2不能=0.3:

      console.log( 0.1 + 0.2 == 0.3);
    

    这里输出的结果是false,说明两边不相等的,这是浮点运算的特点,也是很多同学疑惑的来源,浮点数运算的精度问题导致等式左右的结果并不是严格相等,而是相差了个微小的值。

    所以实际上,这里错误的不是结论,而是比较的方法,正确的比较方法是使用JavaScript提供的最小精度值:

      console.log( Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON);
    

    检查等式左右两边差的绝对值是否小于最小精度,才是正确的比较浮点数的方法。这段代码结果就是 true 了。

    Symbol

    Symbol 是 ES6 中引入的新类型,它是一切非字符串的对象key的集合,在ES6规范中,整个对象系统被用Symbol 重塑。

    这里我们只介绍Symbol类型本身:它有哪些部分,它表示什么意思,以及如何创建Symbol类型。

    Symbol 可以具有字符串类型的描述,但是即使描述相同,Symbol也不相等。

    我们创建 Symbol 的方式是使用全局的 Symbol 函数。例如:

        var mySymbol = Symbol("my symbol");
    

    一些标准中提到的 Symbol,可以在全局的 Symbol 函数的属性中找到。例如,我们可以使用 Symbol.iterator 来自定义 for…of 在对象上的行为:

        var o = new Object
    
        o[Symbol.iterator] = function() {
            var v = 0
            return {
                next: function() {
                    return { value: v++, done: v > 10 }
                }
            }        
        };
    
        for(var v of o) 
            console.log(v); // 0 1 2 3 ... 9
    

    代码中我们定义了iterator之后,用for(var v of o)就可以调用这个函数,然后我们可以根据函数的行为,产生一个for…of的行为。

    这里我们给对象o添加了 Symbol.iterator 属性,并且按照迭代器的要求定义了一个0到10的迭代器,之后我们就可以在for of中愉快地使用这个o对象啦。

    这些标准中被称为“众所周知”的 Symbol,也构成了语言的一类接口形式。它们允许编写与语言结合更紧密的 API。

    类型转换

    StringToNumber

    字符串到数字的类型转换,存在一个语法结构,类型转换支持十进制、二进制、八进制和十六进制,比如:

    • 30;
    • 0b111;
    • 0o13;
    • 0xFF。

    此外,JavaScript支持的字符串语法还包括正负号科学计数法,可以使用大写或者小写的e来表示:

    • 1e3;
    • -1e-2。

    需要注意的是,parseInt 和 parseFloat 并不使用这个转换,所以支持的语法跟这里不尽相同。

    在不传入第二个参数的情况下,parseInt只支持16进制前缀“0x”,而且会忽略非数字字符,也不支持科学计数法。

    在一些古老的浏览器环境中,parseInt还支持0开头的数字作为8进制前缀,这是很多错误的来源。所以在任何环境下,都建议传入parseInt的第二个参数,而parseFloat则直接把原字符串作为十进制来解析,它不会引入任何的其他进制。

    多数情况下,Number 是比 parseInt 和 parseFloat 更好的选择。

    NumberToString

    在较小的范围内,数字到字符串的转换是完全符合你直觉的十进制表示。当Number绝对值较大或者较小时,字符串表示则是使用科学计数法表示的。这个算法细节繁多,我们从感性的角度认识,它其实就是保证了产生的字符串不会过长。

    具体的算法,你可以去参考JavaScript的语言标准。由于这个部分内容,我觉得在日常开发中很少用到,所以这里我就不去详细地讲解了。

    装箱转换

    每一种基本类型Number、String、Boolean、Symbol在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象,它是类型转换中一种相当重要的种类。

    前文提到,全局的 Symbol 函数无法使用 new 来调用,但我们仍可以利用装箱机制来得到一个 Symbol 对象,我们可以利用一个函数的call方法来强迫产生装箱。

    我们定义一个函数,函数里面只有return this,然后我们调用函数的call方法到一个Symbol类型的值上,这样就会产生一个symbolObject。

    我们可以用console.log看一下这个东西的type of,它的值是object,我们使用symbolObject instanceof 可以看到,它是Symbol这个类的实例,我们找它的constructor也是等于Symbol的,所以我们无论从哪个角度看,它都是Symbol装箱过的对象:

        var symbolObject = (function(){ return this; }).call(Symbol("a"));
    
        console.log(typeof symbolObject); //object
        console.log(symbolObject instanceof Symbol); //true
        console.log(symbolObject.constructor == Symbol); //true
    

    装箱机制会频繁产生临时对象,在一些对性能要求较高的场景下,我们应该尽量避免对基本类型做装箱转换。

    使用内置的 Object 函数,我们可以在JavaScript代码中显式调用装箱能力。

        var symbolObject = Object(Symbol("a"));
    
        console.log(typeof symbolObject); //object
        console.log(symbolObject instanceof Symbol); //true
        console.log(symbolObject.constructor == Symbol); //true
    

    每一类装箱对象皆有私有的 Class 属性,这些属性可以用 Object.prototype.toString 获取:

        var symbolObject = Object(Symbol("a"));
    
        console.log(Object.prototype.toString.call(symbolObject)); //[object Symbol]
    

    在 JavaScript 中,没有任何方法可以更改私有的 Class 属性,因此Object.prototype.toString 是可以准确识别对象对应的基本类型的方法,它比 instanceof 更加准确。

    但需要注意的是,call本身会产生装箱操作,所以需要配合 typeof 来区分基本类型还是对象类型。

    拆箱转换

    在JavaScript标准中,规定了 ToPrimitive 函数,它是对象类型到基本类型的转换(即,拆箱转换)。

    对象到 String 和 Number 的转换都遵循“先拆箱再转换”的规则。通过拆箱转换,把对象变成基本类型,再从基本类型转换为对应的 String 或者 Number。

    拆箱转换会尝试调用 valueOf 和 toString 来获得拆箱后的基本类型。如果 valueOf 和 toString 都不存在,或者没有返回基本类型,则会产生类型错误 TypeError。

        var o = {
            valueOf : () => {console.log("valueOf"); return {}},
            toString : () => {console.log("toString"); return {}}
        }
    
        o * 2
        // valueOf
        // toString
        // TypeError
    

    我们定义了一个对象o,o有valueOf和toString两个方法,这两个方法都返回一个对象,然后我们进行o*2这个运算的时候,你会看见先执行了valueOf,接下来是toString,最后抛出了一个TypeError,这就说明了这个拆箱转换失败了。

    到 String 的拆箱转换会优先调用 toString。我们把刚才的运算从o*2换成 String(o),那么你会看到调用顺序就变了。

        var o = {
            valueOf : () => {console.log("valueOf"); return {}},
            toString : () => {console.log("toString"); return {}}
        }
    
       String(o)
        // toString
        // valueOf
        // TypeError
    

    在 ES6 之后,还允许对象通过显式指定 @@toPrimitive Symbol 来覆盖原有的行为。

        var o = {
            valueOf : () => {console.log("valueOf"); return {}},
            toString : () => {console.log("toString"); return {}}
        }
    
        o[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"}
    
    
        console.log(o + "")
        // toPrimitive
        // hello
  • 相关阅读:
    小白的基金理财课
    Spring Security 入门原理及实战
    spring-data-rest的魔力 10分钟实现增删改查
    redis单点、redis主从、redis哨兵sentinel,redis集群cluster配置搭建与使用
    Netty开发redis客户端,Netty发送redis命令,netty解析redis消息
    使用Netty实现HTTP服务器
    Netty实现心跳机制
    SpringMVC是怎么工作的,SpringMVC的工作原理
    Netty 学习系列
    Mybatis 源码学习系列
  • 原文地址:https://www.cnblogs.com/x-yy/p/11011328.html
Copyright © 2011-2022 走看看