Cocos Creator_JavaScript权威指南(第六版)_第3章_类型、值和变量

JavaScript的数据类型分为两类:原始类型(primitive type)和对象类型(object typt)。
原始类型包括数字、字符串和布尔值。null(空)和undefined(未定义)是两个特殊的原始值,它们通常分别代表了各自特殊类型的唯一的成员。
除此之外的就是对象了。对象是属性的集合,每个属性都由“名/值对”(值可以是原始值,也可以是对象)构成。其中一个比较特殊的对象–全局对象。6
普通的JavaScript对象是“命名值”的无序集合。JavaScript同样定义了一种特殊对象–数组(Array),表示带编号的值的有序集合。JavaScript为数组定义了专用的语法,是数组拥有一些和普通对象不同的特有行为特性。7
JavaScript还定义了另一种特殊对象–函数。函数是具有与它相关联的可执行代码的对象,通过调用函数来运行可执行代码,并返回运算结果。和数组一样,函数的行为特征和其他对象都不一样。JavaScript为使用函数定义了专用语法。对于JavaScript函数来讲,最重要的是,他们都是真值,并且JavaScript可以将他们当做普通对象来对待。8
如果函数用来初始化(使用new运算符)一个新建的对象,我们称之为构造函数。每个构造函数定义了一类对象–由构造函数初始化的对象组成的集合。类可以看做是对象类型的子类型。除了数组类和函数类之外,JavaScript语言核心定义了其他三种有用的类。日期类定义了代表日期的对象。正则类定义了表达正则表达式(一种强大的模式匹配工具,10)的对象。错误类定义了那些表示JavaScript程序中运行时错误和语法错误的对象。可以通过定义自己的构造函数来定义需要的类。9
JavaScript解释器有自己的内存管理机制,可以自动对内存进行垃圾回收。这意味着程序可以按需创建对象,程序员则不必担心这些对象的销毁和内存回收。当不再有任何引用指向一个对象,解释器就会知道这个对象没用了,然后自动回收它所占用的内存资源。(GC机制)
JavaScript是一种面对对象的语言。不严格地讲,这意味着我们不用全局的定义函数去操作不同类型的值,数据类型本身可以定义方法来使用值。
a.sort(); //sort(a)的面对对象的版本
从技术上讲,只有JavaScript对象才能拥有方法。然而,数字、字符串和布尔值也可以拥有自己的方法。在JavaScript中,只有null和undefined是无法拥有方法的值。
JavaScript的类型可以分为原始类型和对象类型,也可分为拥有方法的类型和不能拥有方法的类型,同样可分为可变类型和不可变类型。可变类型的值是可以修改的。对象和数组属于可变类型:JavaScript程序可以更改对象属性值和数组元素的值。数字、布尔值、null和undefined属于不可变类型–比如,修改一个数值的内容本身就说不通。字符串可以看成有字符组成的数组,你可能会认为它是可变的。然而在JavaScript中,字符串是不可变的:可以访问字符串任意位置的文本,但JavaScript并为提供修改已知字符串的文本内容的方法。
JavaScript可以自由地进行数据类型转换。比如,如果在程序期望使用字符串的地方使用了数字,JavaScript会自动将数字转换为字符串。如果在期望使用布尔值的地方使用了非布尔值,JavaScript也会进行相应的转换。类型转换规则将在3.8中讲述。JavaScript中灵活的类型转换规则对“判断相等”的定义亦有影响。
JavaScript变量是无类型的,变量可以被赋予任何类型的值,同样一个变量也可以重新赋予不用类型的值。使用var关键字来声明变量。JavaScript采用语法作用域。不在任何函数内声明的变量称作全局变量,它在JavaScript程序中的任何地方都是可见的。在函数内声明的变量具有函数作用域,并且只有函数内可见。

3.1 数字
JavaScript不区分整数值和浮点数值。JavaScript中的所有数字均用浮点数值表示。JavaScript采用IEEE 754 标准定义的64位浮点格式表示数字,这意味着他能表示的最大值是±1.797693134862315710的308次方,最小值是±510的负324次方。
按照JavaScript中的数字格式,能够表示的整数范围是从-2的53次方~2的53次方,包含边界值。如果使用了超过此范围的整数,则无法保证低位数字的精度。然而需要注意的是,JavaScript中实际的操作(比如数组索引,以及第四章中的位操作符)则是基于32位整数。
当一个数字直接出现在JavaScript程序中,我们称之为数字直接量。JavaScript支持多种格式的数字直接量,注意,在任何数字直接量前添加负号(-)可以得到它们的负值。但负号是一元求反运算符,并不是数字直接量语法的组成部分。

3.1.1 整数直接量
在JavaScript程序中,用一个数字序列表示一个十进制整数。
除了十进制的整型直接量,JavaScript同样能识别十六进制值。所谓十六进制的直接量是指以“0x”或“0X”为前缀,十六进制值是0至9之间的数字和a(A)至f(F)之间的字母构成。
尽管ECMAScript标准不支持八进制直接量,但JavaScript的某些实现可以允许采用八进制形式表示整数。
由于某些JavaScript的实现支持八进制直接量,而有些不支持,因此最好不要使用以0为前缀的整型直接量,毕竟我们也无法得知当前JavaScript的实现是否支持八进制的解析。在ECMAScript 6的严格模式下,八进制直接量是明令禁止的。

3.1.2 浮点型直接量
6.02e23 //6.02 * 10的23次方
6.02E-32 //6.02 * 10的负23次方

3.1.3 JavaScript中的算术运算
JavaScript程序是使用语言本身提供的算术运算符来进行数字运算的。除了基本的运算符外,JavaScript还支持更加复杂的算术运算,这些复杂运算通过最为Math对象的属行定义的函数和常量来实现:
Math.pow(x,y) x的y次幂
第三部分中关于Math对象的介绍,那里列出了JavaScript所支持的所有数学函数。
PS:JavaScript中的算术运算在溢出(overflow)、下溢(underflow)或被零整除时不会报错。当数字运算结果超过了JavaScript所能表示的数字上限(溢出),结果为一个特殊的无穷大(infinity)值,在JavaScript中以Infinity表示。同样地,当负数的值超过了JavaScript所能表示的负数范围,结果为负无穷大,以—Infinity表示。无穷大值的行为特性和我们所期望的是一致的。
下溢是当运算结果无限接近于零并比JavaScript能表示的最小值还小的时候发生的一种情况。这种情况下,JavaScript将会返回0。当一个负数发生下溢时,JavaScript返回一个特殊的值“负零”。这个值几乎和正常的零完全一样,一般很少用到负零。
已知被零整除在JavaScript并不报错:它只是简单的返回无穷大。但有一个例外,零除以零是没有意义的,这种整除运算结果也是一个非数字值,用NaN表示。无穷大除以无穷大、给任意负数作开方运算或者算术运算符与不是数字或无法转换为数字的操作数一起使用时都将返回NaN。
JavaScript预定义了全局变量Infinity和NaN,在ECMAScript 3中,这两个值是可读/写的,并可修改。ECMAScript 5修正了这个错误,将他们定义为只读的。
JavaScript中的非数字值有一点特殊:他和任何值都不相等,包括自身。也就是说,没有办法通过x==NaN来判断变量x是否是NaN。相反,应当使用x!=NaN来判断,当且仅当x为NaN的时候,表达式的结果才为true。函数isNaN()的作用与此类似,如果参数是NaN或者是一个非数字值(比如字符串和对象),则返回true。JavaScript中有一个类似的函数isFinite(),在参数不是NaN、Infinity或-Infinity的时候返回true。
负零值同样有些特殊,它和正零值是相等的。这意味着这两个值几乎一模一样,除了作为除数之外.

3.1.4 二进制浮点数和四舍五入错误
在JavaScript的真实运行环境中,0.3-0.2 = 0.099 999 999 999 999 98。
二进制浮点数表示法并不能精确表示类似0.1这样简单的数字。
PS:在任意使用二进制浮点数的编程语言中都会有这个问题。这种计算结果可以胜任大多数的计算任务:这个问题也只有在比较两个值是否相等的时候才会出现。
JavaScript的未来版本或许会支持十进制数字类型以避免这些蛇人问题。

3.1.5 日期和是时间
JavaScript语言核心包括Date()构造函数,用来创建表示日期和时间的对象。日期对象不象数字那样:

        var then = new Date(2018,6,10);
        var later = new Date(2018,6,10,14,26,0);
        var now = new Date();
        console.log(now+"现在时间");
        var elapse = now - then;
        console.log(elapse+"相差时间");
        later.getFullYear();
        console.log(later.getFullYear());       
        now.getDay();
        console.log(now.getDay());

3.2 文本
字符串是一组有16位值组成的不可变的有序序列,每个字符通常来自于Unicode字符集。JavaScript通过字符串类型来表示文本。字符串的长度是其所含16位值的个数。JavaScript字符点的索引从零开始:第一个字符的位置是0,第二个字符的位置是1,以此类推,空字符串长度为0.

JavaScript采用UTF-16编码的Unicode字符集,JavaScript字符串是由一组无符号的16位值组成的序列。最常用的Unicode字符都是通过16位内码表示,并代表字符串中的单个字符,那些不能表示为16为的Unicode字符则遵循UTF -16编码规则–用两个16位值组成一个序列表示。这意味着一个长度为2的JavaScript字符串有可能表示一个Unicode字符:

      var p = "π";
      var e = "e";
      console.log(p.length);
      console.log(e.length);

JavaScript定义的各式字符串操作方法均作用于16为值,而非字符,且不会对代理相,同样JavaScript不会,同样JavaScoro不会对字符串做标准化的加工,甚至不能保证字符串是合法的UTF-16各式。

3.2.1 字符串直接量
在JavaScript程序中的字符串直接量,是由单引号或双引号括起来的字符序列。由单引号定界的字符串何总可以包含双引号,由双引号定界的字符串中也可以包含单引号。
在ECMAScript 3中,字符串直接量必须写在一行中,而在ECMAScript 5中,字符串直接量可以拆分成数行,每行必须以反斜线(\)结束,反斜线和行结束符都不算是字符串直接量的内容。
需要注意的是,当使用单引号来定界字符串时,需要格外小心英文中的缩写和所有格写法,因为撇号和单引号是同一个字符,所以必须使用反斜线(\)来转义所有的撇号。
在客户端JavaScript程序设计中,JavaScript代码和HTML代码会互相夹杂,因此,当两者混杂在一起的时候,最好在JavaScript中使用双引号表示字符串,而在HTML事件处理程序属性中则使用单引号表示字符串。

3.2.2 转义字符
下表列出了JavaScript中的转义字符以及它们所代表的含义。其中有两个是通用的,通过十六进制数表示Latin-1或Unicode中的任意字码。例如,\xA9表示版权符号,版权符号的Latin-1编码是十六进制数A9。同样,\u表示由4个十六进制数指定的任意Unicode字符,比如,\u03c0表示字符π。

\o NUL字符(\u0000)
\b 退格符(\u0008)
\t 水平制表符(\u0009)
\n 换行符(\u000A)
\v 垂直制表符(\u000B)
\f 换页符(\u000C)
\r 回车符(\u000D)
\" 双引号(\u0022)
\' 单引号(\u0027)
\\ 反斜线(\u005C)
\xXX 有两位十六进制数XX指定的Latin-1字符(\)
\uXXXX 有4为十六进制数XXXX指定的Unicode字符(\)

如果“\”字符位于没有在上表中列出的字符前,则忽略“\”(当然,JavaScript语言将来的版本可能定义新的转义符)。比如,“#和“#”等价。最后,上文提到过,在ECMAScript 5 中,允许在一个多行字符直接量里的每行结束处使用反斜线。

3.2.3 字符串的使用
JavaScript的内置功能之一就是字符串连接。

msg = "Hello,"+"World";
greeting = "Welcome to my blog,"+"name";

使用字符串的length属性。可以得到字符串s的长度:

s.length

除了length属性,字符串还提供许多可以调用的方法:

s.charAt(0) 第一个字符串
s.charAt(s.length-1) 最后一个字符串
s.substring(1,5) 第2至5个字符
s.slice(1,5) 同上
s.slice(-3) 最后三个字符
s.indexOf("o") 字符o首次出现的位置
s.lastIndexOf("o") 字符o最后一次出现的位置
s.indexOf("o",1)在位置1及之后首次出现字符o的位置
s.split(",") 分割成子字符串
s.replace("h","H") 敏感文字替换
s.toUpperCase() 大写

PS:在JavaScript中字符串是固定不变的,类似replace()和toUpperCase()的方法都返回新字符串,原字符串本身并没有发生改变。
在ECNAScrips 5中,字符串可以当做只读数组,除了使用charAt()方法,也可以使用方括号来访问字符串中的单个字符(16位值):
基于Mozilla的Web浏览器(比如Firefox)很久之前就支持这种方式的字符串索引,多数现代浏览器(IE除外)也紧跟Mozilla的脚步,在ECMAScript 5成型之前就支持了这一特性。

3.2.4模式匹配
JavaScript定义了RegExp()构造函数,用来创建表示文本匹配模式的对象。这些模式称为“正则表达式”,JavaScript采用Perl中的正则表达式语法。String和RegExp对象均定义了利用正则表达式进行模式匹配和查找与替换的函数。
尽管RegExp并不是语言中的基本数据类型,但是他们依然具有直接量写法,可以直接在JavaScript程序中使用。在两条斜线之间的文本构成了一个正则表达式直接量。第二条斜线之后也可以跟随一个或多个字母,用来修饰匹配模式的含义,例如:

/^HTML/ //匹配以HEML开始的字符串
/[1-9][0-9]*/ //匹配一个非零数字,后面是任意个数字
/\bjavascript\b/i //匹配单词“Javascript”,忽略大小写

RegExp对象定义了很多有用的方法,字符串同样具有可以接受RegExp参数的方法,例如:

       var text  = "testing:1,2,3"; //文本示例
       var pattern = /\d+/g //匹配所有
       pattern.test(test) //匹配成功
       text.search(pattern) // 9首次匹配成功的位置
       tesx.match(pattern)//["1","2","3"]:所有匹配组成的数组
       test.replace(pattern,"#"); //"testing:#,#,#"
       tesx.split(/\D+/);//["","1","2","3"]:用非数字字符截取字符串

8

Array[3] ["1", "2", "3"]
length:3
__proto__:Array[0] []
0:"1"
1:"2"
2:"3"

testing:#,#,#

Array[4] ["", "1", "2", "3"]
length:4
__proto__:Array[0] []
0:""
1:"1"
2:"2"
3:"3"

3.3 布尔值
JavaScript程序中的比较语句的结果通常都是布尔值,例如:
a==4
布尔值通常用于JavaScript中的控制结构中。
任意JavaScript的值都可以转换为布尔值。下面这些值会被转换成false:undefined null 0 -0 NaN “”//空字符串
所有其他值,包括所有对象(数组)都会转换成true。false和上面6个可以转换成false的值有时称做“假值”,其他值称做“真值”。JavaScript期望使用一个布尔值的时候,假值会被当成false,真值会被当成true。
布尔值包含toString()方法,因此可以使用这个方法将字符串转换为“true”或“false”,但它并不包含其他有用的方法。除了这个不重要的API,还有三个中重要的布尔运算符。 与或非。

3.4 null和undefined
null是JavaScript语句的关键字,它表示一个特殊值,常用来描述“空值”。对null执行typeof预算,结果返回字符串“object”,也就是说,可以将null认为是一个特殊的对象值,含义是“非对象”。但实际上,通常认为null是它自有类型的唯一一个成员,它可以表示数字、字符串和对象是“无值”的。大多数编程语言和JavaScript一样含有null:你可能对null或nil很眼熟。
JavaScript还有第二个值来表示值的空缺。用未定义的值表示跟深层次的“空值”。它是变量的一种取值,表明变量没有初始化,如果要查询对象属性或数组元素的值时返回undefined则说明这个属性或元素不存在。如果函数没有返回任何值,则返回undefined。引用没有提供实参的函数形参的值也会只得到undefined。undefined是预定义的全局变量(它和null不一样,他不是关键字),它的值就是“未定义”。表明这个值是这个类型的唯一成员。
尽管null和undefined是不同的,但它们都表示“值的空缺”,两者往往可以互换。判断相等运算符“”认为两者是相等的(要使用严格相等运算符“=”来区分它们)。

        var x = null;
        var y = undefined;
        if (x==y) {
            console.log("x=y");           
        }
        if (x===y) {
            console.log("x===y");          
        }
        else {
            console.log("x!==y");           
        }

在希望值是布尔类型的地方他们的值都是假值,和false类似。null和undefined都不包含任何属性和方法。实际上,使用“.”和“[]”来存取这两个值的成员或方法都会产生一个类型错误。

3.5 全局对象
前几节讨论了JavaScript的原始类型和原始值。对象类型–对象、数组和函数–在本书中均会有独立章节来讲述。但有一类非常重要的对象,我们不得不现在就把他们讲清楚–全局对象。
全局对象(global object)在JavaScript中有着重要的用途:全局对象的属性是全局定义的符号,JavaScript程序可以直接使用。当JavaScript解释器启动时(或者任何Web浏览器加载新页面的时候),它将创建一个新的全局对象,并给它一组定义的初始属性:
全局属性,比如undefined、Infinity和NaN
全局函数,比如isNaN()、parseInt()(3.8.2)和eval()(4.1.2)
构造函数,比如Date()、RegExp()、String()、Object()和Array()(3.8.2)
全局对象,比如Math和JSON(6.9)
全局对象的初始属性并不是保留字,但他们应该当做保留字来对待。
在代码的最顶级–不在任何函数内的JavaScript代码–可以使用JavaScript关键字this来引用全局对值。如果代码声明了一个全局变量,这个全局变量就是全局对象的一个属性。

3.6 包装对象
JavaScript对象是一种复合值:它是属性或已命名值的集合。通过“.”符号来引用属性值。当属性值是一个函数的时候,称其为方法。通过o.m()来调用对象o中的方法。
我们看到字符串也同样具有属性和方法:

var s = “hello world!”;//一个字符串
var word = s.substring(s.indexOf(" ")+1,s.length); //使用字符串的属性

字符串既然不是对象,为什么它会有属性呢?只要引用了字符串S的属性,JavaScript就会将字符串通过调用new String(S)的方式转换成对象,这个对象继承了字符串的方法,并被用来处理属性的引用。一旦属性引用结束,这个新创建对象就会销毁(其实在实际上并不一定创建或销毁这个练市对象,然然整个过程看起来是这样)。
同字符串一样,数字和布尔值也具有各自的方法:通过Nqumber()和Boolean构造函数创建一个临时对象,这些方法的调用均是来自与这个临时对象。null和undefined没有包装对象:访问它们的属性会造成一个类型错误。

        var s = "test";
        s.len = 4;
        var t = s.len;
        console.log(t);

当运行上述代码是,t的值是undefined。第二行代码创建一个临时字符串对象,并给其len属性赋值为4,随即销毁这个对象。第三行通过原始的(没有被修改过)字符串值创建一个新字符串对象,尝试读取去len属性,这个属性自然不存在,表达式求值结果为undefined。这段代码说明了在读取字符串、数字和布尔值的属性值(或方法)的时候,表现的像对象一样。但如果你试图给其属性赋值,则会忽略这个操作:修改只是发生在临时对象身上,而这个临时对象并未继续保留下来。
存取字符串、数字或布尔值的属性时创建的临时对象称做包装对象,它只是偶尔用来区分字符串值和字符串对象、数字和数值对象以及布尔值和布尔对象。通常,包装对象只是被看做是一种实现细节,而不用特别关注。由于字符串、数字和布尔值的属性都是只读的并且不能给它们定义新属性,因此你需要明白它们是有别于对象的。
需要注意的是,可通过String(),Numbe()或Boolean()构造函数来显式创建包装对象:

        var s = "test",n=1,b=true;
        var S = new String(s);
        var N = new Number(n);
        var B = new Boolean(b);
        S.len = 4;
        var t = S.len;
        console.log(S);
        console.log(t);

JavaScript会在必要时将包装对象转换成原始值,因此上段代码中的对象S、N和B常常–但不总是–表现的和值s、n和b一样。“”等于运算符将原始值和其包装对象视为相等,但“=”全等运算符将他们视为不等。通过typeof运算符可以看到原始值和其包装对象的不同。
Cocos Creator_JavaScript权威指南(第六版)_第3章_类型、值和变量_第1张图片

3.7 不可变的原始值和可变的对象引用
JavaScript中的原始值(undefined、null、布尔值、数字和字符串)与对象(数组和函数)有着根本区别。原始值是不可更改的:任何方法都无法更改一个原始值。

        var s = "hello";
        console.log(s.toUpperCase());//HELLO
        console.log(s);//hello

原始值的比较是值的比较:只有在他们的值相等时他们才相等。这对数字、布尔值、null和undefined来说,并没有其他办法来比较他们。同样,对于字符串来说则并不明显:如果比较两个单独的字符串,当且仅当它们的长度相等且每个索引的字符都相等时,JavaScript才认为它们相等。
对象和原始值不同,首先,他们是可变的–它们的值是可修改的:

        var o = {x:1};
        o.x =2;
        o.y =3;
        console.log(o);
        var arr = [1,2,3];
        arr[0]=0;
        arr[4]=5;
        console.log(arr);

Cocos Creator_JavaScript权威指南(第六版)_第3章_类型、值和变量_第2张图片
对象的比较并非值的比较:即使两个对象包含同样的属性及相同的值,它们也是不相等的。各个索引元素完全相等的两个数组也不相等。

        var o = {x:1},p ={x:1};
        console.log(o===p);//false
        console.log(o==p);//false
        console.log(o.x===p.x);//true

我们通常将对象称为引用类型,为此来和JavaScript的基本类型区分开来。依照术语的叫法,对象值都是引用,对象的比较均是引用的比较:当且仅当它们引用同一个基对象是,它们才相等。
将对象(或数组)赋值给一个变量,仅仅是赋值的引用值:对象本身并没有复制一次。如果你想得到一个对象或数组的副本,则必须显示复制对象的每个属性或数组的每个元素。

3.8 类型转换
JavaScript中的取值类型非常灵活,我们已经从布尔值看到了只一点:当JavaScript期望使用一个布尔值的时候,你可以提供任意类型值,JavaScript将根据需要自行转换类型。一些值(真值)转换为true,其他值(假值)转换为false。这在其他类型中同样适用:如果JavaScript期望使用一个字符串,它把给定的值将转换为字符串。如果JavaScript期望使用一个数字,它把给定的值将转换为数字(如果转换结果无意义的话将返回NaN)。
61 简要说明了在JavaScript中如何进行类型转化。
数字表示的字符串可以直接转换为数字,也允许在开始和结尾处带有空格。但在开始和结尾处的任意非空格字符都不会被当成数字直接量的一部分,进而造成字符串转换为数字的结果为NaN.
Cocos Creator_JavaScript权威指南(第六版)_第3章_类型、值和变量_第3张图片
原始值到对象的转换也非常简单,原始值通过调用String()、Number()或Boolean()构造函数,转换为它们各自的包装对象。
null和undefined属于例外,当将他们用在期望是一个对象的地方都会造成一个类型错误(TypeError)异常,而不会执行正常的转换。

3.8.1 转换和相等性
由于JavaScript可以做灵活的类型转换,因此其“”相等运算符也随相等的含义灵活多变。
”等于运算符在判断两个值是否相等时做了哪些类型转换,并同样介绍了“=”恒等运算符在判断相等时并为做任何类型转换。
PS:一个值转换为另一个值并不意味着两个值相等。比如,如果在期望使用布尔值的地方使用了undefined,它将会转换为false,但这并不表明undefined
false。JavaScript运算符和语句期望使用多样化的数据类型,并可以相互转换。if语句将undefined转换为false,但“==”运算符从不试图将其操作数转换为布尔值。

3.8.2 显示类型转换
尽管JavaScript可以自动做许多类型转换,但有时仍需要做显示转换,或者为了使代码变得清晰易读而做显式转换。
做显式类型转换最简单的方法就是使用(…)函数。当不通过new运算符调用这些函数时,它们会作为类型转换函数并按照表61所描述的规则做类型转换:

        console.log(Number("3"));
        console.log(String(false));
        console.log(Boolean([0]));
        console.log(Object(3));

需要注意的是,除了null或undefined之外的任何值都具有toString()方法,这个方法的执行结果通常和String()方法的返回结果一致。同样需要注意的是,如果试图把null或undefined转换为对象,则会抛出一个类型错误。Object()函数在这种情况下不会抛出异常:它仅简单地返回一个新创建的空对象。
JavaScript中的某些运算符会做隐式的类型转换,有时用于类型转换。如果“+”运算符第一个操作数是字符串,它将会把另外一个操作数转换为字符串。一元“+”运算符将其操作数转换为数字。同样,一元“!”运算符将其操作数转换为布尔值并取反。

        console.log("2"+"5dfa"); //25dfa
        console.log(+"126"); //126
        console.log(!!"40"); // true**

在计算机程序中数字的解析和格式化是非常普通的工作,JavaScript中提供了专门的函数和方法用来做更加精确的数字到字符串(number - to - string)和字符串到数字(string–to–number)的转换。
Number类定义的ToString()方法可以接收表示准换基数的可选参数,如果不指定次参数,转换规则将是基于十进制。同样,亦可以将数字转换为其他进制数(范围在2~36(26个字母)之间)。八进制为0x或0X格式。
当处理财务或科学数据的时候,在做数字到字符串的转换过程中,你期望自己控制输出中小数点位置和有效数字位数,或者决定是否需要指数计数法。Number类为这种数字到字符串的类型转换场景定义了三个方法。toFixed()根据小数点后的指定位数将数字转换为字符串,它从不使用指数计数法。toExponential()使用指数记数法将数字转换为指数形式的字符串,其中小数点前只有一位,小数点后的位数则由参数指定(也就是说有效数字位数比指定的位数要多一位),toPrecision()根据指定的有效数字位数将数字转换成字符串。如果有效数字的位数少于数字整数部分的位数,则转换成指数形式。我们注意到,所有三个方法都会适当地进行四舍五入或填充0。
如果通过Number()转换函数传入一个字符串,它会试图将其转换为一个整数或浮点数直接量,这个方法只能基于十进制数进行转换,并且不能出现非法的尾随字符。parseInt()(只解析整数)函数和parseFloat()(则可以解析整数和浮点数)函数(他们是全局函数,不从属于任何类的方法)更加灵活。如果字符串前缀是“0X”或者“0x”,parseInt()将其解析为十六进制数,两者都会跳过任意数量的前导空格,尽可能解析更多数值字符,并忽略后面的内容。如果第一个非空格字符是非法的数字直接量,将最终返回NaN:

parseInt(“3.14 sds”) = 3
parseFloat(“3.14 met”) = 3.14
parseFloat(“0.1”)=0.1
parseInt("0XFF") = 255
parseInt("-0xFF") = -255
parseInt(".1") = NaN
parseFloat("S72.47") = NaN

parseInt()可以接收第二个可选参数,这个参数指定数字转换的基数,合法的取值范围是2~36,例如:

parseInt("11",2); 3 = 1*2+1
parseInt("ff",16) 255 = 15*16+15
parseInt("zz",36) 1295 = 35*36 +35
parseInt("077",8) 63 = 7*8 +7
parseInt("077",10) 77 = 7*10+7

3.8.3 对象转换为原始值
对象到布尔值的转换非常简单:所有对象(包括数组和函数)都转换为true。对于包装对象亦是如此:new Boolean(false)是一个对象而不是原始值,它将转换为true。
对象与字符串之间的转换是通过调用待转换对象的一个方法来完成。一个麻烦的事实是,JavaScript对象有两个不同的方法来执行转换,并且接下来要讨论的一些特殊场景更加复杂。值得注意的是,这里提到的字符串和数字的转换规则只使用于本地对象。宿主对象(有浏览器定义的对象)根据各自的算法可以转换成字符串和数字。
console.log(({x:1,y:2}).toString());
很多类定义了更多特定版本的toString()方法。
另一种转换对象的函数是valueOf()。这个方法的任务并为详细定义:如果存在任意原始值,他就默认将对象转换为表示它的原始值。对象是复合值,而且大多数对象无法真正表示为一个原始值,因此默认的valueOf()方法简单地返回对象本身,而不是返回一个原始值。数组、函数和正则表达式简单地继承了这个默认方法,调用这些类型的实例的valueOf()方法只是简单返回对象本身。日期类定义的valueOf()方法会返回它的一个内部表示。
通过使用toString()和valueOf()方法,就可以做到对象与字符串之间的转换了。但需要注意的是,在某些特殊的场景中,JavaScript执行了完全不同的对象到原始值转换。
日期类是JavaScript语言核心中唯一的预先定义类型,它定义了有意义的向字符串和数字类型的转换。
减号运算符把它的两个操作数都转换为数字。

        console.log(typeof(now + 1));//字符串
        console.log(typeof(now - 1));//数字
        console.log(now.toString());//true 隐式的和显示的字符串转换
        console.log(now == now.toString());//true “>”将日期转换为数字

3.9 变量声明
Python、Ruby和JavaScript就是典型的动态类语言。动态类型的语言编程时,永远不用给任何变量指定数据类型,该语言会在第一次赋值给变量时,在内部将数据类型记录下来。静态类型语言与其恰恰相反,它的数据类型是在编译期间检查的,也就是说在写程序时要声明所有变量的数据类型,C/C++是静态类型语言的典型代表,其他语言还有C#、JAVA等。
重复的声明和遗漏的声明
使用var语句重复声明变量是合法且无害的。如果重复声明带有初始化器,那么这就和一条简单的赋值语句没什么两样。
如果你试图读取一个没有声明的变量和值,JavaScript会报错。总而言之,你应当始终使用var来声明变量。

3.10 变量作用域
一个变量的作用域(scope)是程序源代码中定义这个变量的区域。全局变量拥有全局作用域,在JavaScript代码中的任何地方都是有定义的。然而在函数内声明的变量只在函数体内有定义。他们是局部变量,作用域是局部性的。函数参数也是局部变量,它们只在函数体内有定义。
在函数体内,局部变量的优先级高于同名的全局变量。如果在函数内声明的一个局部变量或者函数参数中带有的变量和全局变量重名,那么全局变量就被局部变量所遮盖。
尽管在全局作用域编写代码时可以不写var语句,但声明局部变量时则必须使用var语句。
函数定义是可以嵌套的。由于每个函数都有它自己的作用域,因此会出现几个局部作用域嵌套的情况

3.10.1 函数作用域和声明提前
在一些类似c语言的编程语言中,花括号内的每一段代码都具有各自的作用域,而且变量在声明它们的代码段之外是不可见的,我们称为块级作用域,而JavaScript中没有块级作用域。JavaScript取而代之地使用了函数作用域:变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。
在如下所示的代码中,在不同位置定义了变量i、j和k,它们都在同一作用域内–这三个变量在函数体内均是有定义的。
JavaScript的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的。有意思的是,这意味着变量在声明之前甚至已经可用。JavaScript的这个特性被非正式地称为声明提前(hoisting),即JavaScript函数里声明的所有变量(但不涉及赋值)都被“提前”至函数体的顶部,如下代码所示:

      test:function () {
      console.log(scope); //输出“undefined”,而不是“global”
      var scope = "local";//变量在这里赋初始值,但变量本身在函数体内任何地方均是有定义的
      console.log(scope); //输出“local”       
    },

第一行之所以不会输出local是因为代码还没有执行到var语句声明局部变量的地方。其实不然,由于函数作用域的特性,局部变量在整个函数体始终是有定义的,也就是说,在函数体内局部变量遮盖了同名全局变量。尽管如此,只有在程序执行到var语句的时候,局部变量才会被真正赋值。因此,上述过程等价于:将函数内的变量声明“提前”至函数体顶部,同时变量初始化留在原来的位置:

 var s;
 console.log(s); //undefined
 s = “y”;
 console.log(s); //y 

在具有块级作用域的编程语言中,在狭小的作用域里让变量声明和使用变量的代码尽可能靠近彼此,通常来讲,这是一个良好的编程习惯。由于JavaScript没有块级作用域,因此一般将变量声明放在函数体顶部,而不是将声明靠近放在使用变量之处。这种做法使得他们的源代码非常清晰地反映了真实的变量作用域。

3.10.2 作为属性的变量
当声明一个JavaScript全局变量是,实际上是定义了全局对象的一个属性(3.5)。当使用var声明一个变量时,创建的这个属性是不可配置的(6.7),也就是说这个变量无法通过delete运算符删除。如果没有使用严格模式并给一个为声明的变量赋值的话,JavaScript会自动创建一个全局变量。以这种方式创建的变量是全局对象的正常的可配值属性,并可以删除他们:

        var x ={x1 :0,x2:2};      
        console.log(x);      
        delete x.x2,
        console.log(x);

JavaScript全局变量是全局对象的属性,这是在ECMAScript规范中强制规定的。对于局部变量则没有如此规定,但我们可以想象得到,局部变量当做跟函数调用相关的某个对象的属性。ECMAScript 3规范该对象为“调用对象”,5规范称为“声明上下对象”。JavaScript可以允许使用this关键字来引用全局对象,却没有方法可以引用局部变量中存放的对象。这种存放局部变量的对象的特有性质,是一种对我们不见的内部实现。然而,这些局部变量对象存在的观念是非常重要的。

3.10.3 作用域链
JavaScript是基于语法作用域的语言:通过阅读包含变量定义在内的数行源码就能知道变量的作用域。全局变量在程序中始终都是有定义的。局部变量在声明它的函数体内以及其所嵌套的函数内始终是有定义的。
如果将一个局部变量看做是自定义实现的对象的属性的话,那么可以换个角度来解读变量作用域。每一段JavaScript代码(全局代码或函数)都有一个与之关联的作用域链(scope chain)。这个作用域链是一个对象列表或者链表,这组对象定义了这段代码“作用域中”的变量。当JavaScript需要查找变量x的值的时候(这个过程称做“变量解析”),它会从链中的第一个对象开始查找,如果这个对象有一个名为x的属性,则会直接使用这个属性的值,如果第一个对象中不存在名为x的属相,则会继续查找下一个对象,以此类推。如果作用域链上没有任何一个对象含有属性x,那么就认为这段代码的作用域链上不存在x,并最终抛出一个引用错误(ReferenceError)异常。
在JavaScript的最顶层代码中(也就是不包含在任何函数定义内的代码),作用域链由一个全局对象组成。在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。在一个嵌套的函数体内,作用域链上至少有三个对象。理解对象链的创建规则是非常重要的。当定义一个函数是,它实际上保存一个作用域链。当调用这个函数时,它创建一个新的对象来存储它的局部变量,并将这个对象添加至保存的那个作用域链上,同时创建一个新的更长的表示函数调用作用域的“链”。对于嵌套函数来讲,事情变得更加有趣,每次调用外部函数时,内部函数又会重新定义一遍。因为每次调用外部函数的时候,作用域链都是不同的。内部函数在每次定义的时候都有微妙的差别–在每次调用外部函数时,内部函数的代码都是相同的,而且关联这段代码的作用域链也不相同。
作用域链的概念对于理解with语句(5.7.1),闭包(8.6)的概念是至关重要的。

你可能感兴趣的:(Cocos Creator_JavaScript权威指南(第六版)_第3章_类型、值和变量)