第 3 章(类型、值和变量)(3.6~ 3.10)

3.6 包装对象

Javascript 对象是一种复合值:它是属性或已命名值的集合。通过“.”符合来引用属性值。当属性值是一个函数的时候,称其为方法,通过 o.m() 来调用对象o中的方法。

同字符串一样,数字和布尔值也具有各自方法:通过 Number() 和 Boolean() 构造函数创建一个临时对象,这些方法的调用均是来自于这个临时对象。 null 和 undefined 没有包装对象;访问它们的属性会造成一个类型错误。

存取字符串、数字或布尔值的属性时创建的临时对象称作包装对象,它只是偶尔用来区分字符串值和字符串对象、数字和数值对象以及布尔值和布尔对象。

可通过 String(), Number() 或 Boolean() 构造函数来显式创建包装对象:

Javascript 会在必要时将包装对象转换成原始值,因此上段代码中的对象 S/N/B尝尝-----但不总是 ----表现的和值s/n/b 一样。“==”等于运算符将原始值和其包装对象视为相等,但“===”全等运算符将它们视为不等。通过typeof 运算符可以看到原始值和其包装对象的不同。

3.7 不可变的原始值和可变的对象引用

Javascritp 中的原始值(undefined / null / 布尔值 / 数字 / 字符串)与对象(包括数组和函数)有着根本区别。原始值是不可更改的:任何方法都无法更改(或“突变”)一个原始值。

如果比较两个单独的字符串,当且仅当它们的长度相等且每个索引的字符都相等时,Javascript 才认为它们相等。

对象和原始值不同,首先,它们是可变的 ------  它们的值是可修改的:

对象的比较并非值的比较:即使两个对象包含同样的属性一级相同的值,它们也是不相等的。各个索引元素完全相等的两个数组也不相等。

 

我们通常将对象称为引用类型,以此来和 javascript 基本类型区分开来。术语的叫法,对象值都是引用,对象的比较均是引用的比较:当且仅当它们引用同一个基对象时,它们才相等。

如上所示,将对象(或数组)赋值给一个变量,仅仅是赋值的引用值:对象本身并没有复制一次。如果想得到一个对象或数组的副本,则必须显式复制对象的每个属性或数组的每个元素。

如果我们想比较两个单独的对象或者数组,则必须比较它们的属性或元素

3.8 类型转换  

原始值到对象的转换也非常简单,原始值通过调用 String() / Number() / Boolean() 构造函数,转换为它们各自的包装对象。

null和undefined 属于例外,当将它们用在期望是一个对象的地方都会造成一个类型错误异常,而不会执行正常的转换。

3.8.1 转换和相等性

一个值转换成另一个值,并不意味着这两个值相等。比如,如果在期望使用布尔值的地方使用了 undefined,它会转换成 false,但这并不意味着 undefined == false.   JS 运算符和语句期望使用多样化的数据类型,并可以相互转换。if 语句将 undefined 转换为 false ,但 “==”运算符从不试图将其操作数转换为布尔值。

3.8.2 显式类型转换 

做显式转换最简单的方法就是使用 Boolean()  Number()  String()  Object() 函数,当不通过 New 运算符调用这些函数时,它们会作为类型转换函数转换类型。

除了 null 或 undefined 之外的任何值都具有 toString() 方法,这个方法的执行结果通常和 String() 方法的返回结果一样。

如果试图把 null 或 undefined 转换为对象,则会抛出一个类型错误,Object() 函数在这种情况下不会抛出异常:它仅简单地返回一个新创建的空对象。

Javascript 中的某些运算符会做隐式的类型转换,有时用于类型转换。“+”运算符的一个操作数是字符串,它会将另外一个操作数转换成字符串。一元“+”运算符将其操作数转换为数字。一元“!”运算符将其操作数转换成布尔值并取反。

JS中提供了专门的函数和方法用来做更加精确的数字到字符串和字符串到数字的转换。

Number类定义的 toString() 方法可以接收表示转换基数的可选参数,如果不指定此参数,转换规则将是基于十进制,同样,也可以将数字转换为其他进制数。

如果通过 Number() 转换函数传入一个字符串,它会试图将其转换为一个整数或浮点数直接量,这个方法只能基于十进制数进行转换,并且不能出现非法的尾随字符。使用parseInt() 和 parseFloat() 函数(它们是全局函数,从不属于任何类的方法)更加灵活。parseInt() 只解析整数,而 parseFloat() 可以解析整数和浮点数。

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

3.8.3 对象转换为原始值

对象到布尔值的转换非常简单:所有的对象(包括数组和函数)都转换为 true 。对于包装对象亦是如此: new Boolean(false) 是一个对象而不是原始值,它将转换为 true。

所有的对象继承了两个转换方法。第一个是 toString() ,它的作用是返回一个反映这个对象的字符串。

({x:1 , y:2}).toString()     // [object  object]

很多类定义了更多特定版本的 toString() 方法。例如,数组类的 toString() 方法将每个数组元素转换为一个字符串,并在元素之间添加逗号后合并成结果字符串。函数类的 toString() 方法返回这个函数的实现定义的表示方式。日期类定义的 toString() 方法返回了一个可读的日期和时间字符串。 RegExp 类定义的 toString() 方法将 RegExp 对象转换为表示正则表达式直接量的字符串。

另一个转换对象的函数是 valueOf() 。 这个方法的任务并未详细定义:如果存在任意原始值,它就默认将对象转换为表示它的原始值。

对象是复合值,而且大多数对象无法真正表示为一个原始值,因此默认的 valueOf() 方法简单的返回对象本身,而不是返回一个原始值。

数组,函数和正则表达式简单地继承了这个默认方法,调用这些类型的实例的 valueOf() 方法只是简单的返回对象本身。

JS 中对象到字符串的转换经过了如下步骤:

① 如果对象具有 toString() 方法,则调用这个方法,如果它返回一个原始值,那么 JS 将这个值转换为字符串(如果本身不是字符串的话),并返回这个字符串结果。

②如果对象没有 toString() 方法,或者这个方法并不返回一个原始值,那么 JS 会调用 valueOf() 方法。如果存在这个方法,则 JS 调用它。如果返回值是原始值,JS 将这个值转换成字符串(如果本身不是字符串的话)并返回这个字符串结果。

③否则,JS 无法从 toString() 方法或 valueOf() 方法获得一个原始值,因此这时它将抛出一个类型错误的异常。

在对象到数字的转换过程中,JS 做了同样的事情,只是它会首先尝试使用 valueOf() 方法:

①如果对象具有 valueOf() 方法,后者返回一个原始值,则 JS 将这个原始值转换为数字(如果需要)并返回这个数字。

②如果这个对象具有 toString() 方法,后者返回一个原始值,则JS将其转换并返回。(JS将这个字符串转换为数字类型,并返回这个数字)

③否则,JS 抛出一个类型错误的异常。

日期类是JS 语言核心中唯一的预先定义类型,它定义了有意义的向字符串和数字类型的转换。对于非日期的对象来说,对象到原始值的转换基本上是对象到数字的转换(首先调用 valueOf() 方法),日期对象则使用对象到字符串的转换模式。这里说的转换是通过 valueOf() 和 toString() 返回的原始值将被直接使用,而不会被强制转换为数字或字符串。

3.9 变量声明  

使用 var 关键字来声明变量

var i; 

var sum;

也可以通过一个 var 关键字来声明多个变量: var  i,sum;

而且可以将变量和初始赋值和变量声明合写在一起:

var message = "Hello";

var i = 0, j  = 0, k = 0;

如果未在var 声明语句中给变量指定初始值,那么虽然声明了这个变量,但在给它存入一个值之前,它的初始值就是 undefined。

3.10 变量作用域

一个变量的作用域是程序源代码中定义这个变量的区域。全局变量拥有全局作用域,在JS代码中任何地方都是有定义的。然而在函数内声明的变量只有在函数体内有定义。它们是局部变量,作用域是局部性的。函数参数也是局部变量,它们只在函数体内有定义。

在函数体内,局部变量的优先级高于同名的全局变量。

如果在函数内声明一个局部变量或者函数参数中带有的变量和全局变量同名,那么全局变量就会被局部变量遮盖。

全局作用域可以不写 var 。但声明局部变量时必须使用 var 语句。

函数定义是可以嵌套的,由于每个函数都有它自己的作用域,因此会出现几个局部作用域嵌套的情况。

3.10.1 函数作用域和声明提前

JS 中没有块级作用域。JS 取而代之地使用了函数作用域:变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。

 如下示例:在不同位置定义了。i、j、k,它们都在同一个作用域内 ---- 这3个变量在函数体内均是有定义的。

JS 的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的。

变量在声明之前甚至可用,JS 的这个特性被非正式称为 声明提前,即JS 函数里声明的所有变量,都被“提前”至函数体的顶部。

由于函数作用域的特性,局部变量在整个函数体始终是有定义的,也就是说,在函数体内局部变量遮盖了同名的全局变量。尽管如此,只有在程序执行到 var 语句的时候,局部变量才会被真正赋值。

上述过程等价于:将函数内的变量声明“提前”至函数体顶部,同时变量初始化留在原来的位置。

由于JS 没有块级作用域,因此一些程序员特意将变量声明放在函数体顶部,而不是将声明靠近放在使用变量之处。这种做法使得源代码非常清晰的反映了真是的变量作用域。

3.10.2 作为属性的变量  

当声明一个JS 全局变量时,实际上是定义了全局对象的一个属性。当使用var 声明一个变量时,创建的这个属性是不可配置的,也就是说这个变量无法通过 delete运算符删除。

JS 全局变量是全局对象的属性。这是在 ECMAScript 规范中强制规定的。对于局部变量没有此规定。局部变量当做跟函数调用相关的某个对象的属性。ES3规范称该对象为“调用对象”,ES5 称该对象为“声明上下文对象”。JS 可以允许使用 this 关键字来引用全局对象,却没有办法可以引用局部变量中存放的对象。

3.10.3 作用域链  

JS 是基于词法作用域的语言:通过阅读包含变量定义在内的树行源码就能知道变量的作用域。全局变量在程序中始终都是有定义的。局部变量在声明它的函数体内以及其所嵌套的函数内始终是有定义的。

如果将一个局部变量看作是自定义实现的对象的属性的话,那么可以换个角度来解读变量作用域。每一段JS 代码(全局代码或函数)都有一个与之关联的作用于链。这个作用域链是一个对象列表或者链表,这组对象定义了这段代码“作用域中”的变量。

1.在JS 的最顶层代码中(也就是不包含在任何函数定义内代码),作用域链由一个全局对象组成

2.在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象

3.在一个嵌套的函数体内,作用域链上至少有三个对象

当定义一个函数时,它实际上保存一个作用域链。当调用这个函数时,它创建一个新的对象来存储它的局部变量,并将这个对象添加至保存的那个作用域链上,同时创建一个新的更长的表示函数调用作用域的“链”。

对于嵌套函数来讲,每次调用外部函数时,内部函数又会重新定义一遍。因为每次调用外部函数的时候,作用域链都是不同的,内部函数在每次定义的时候都有微妙的差别-----在每次调用外部函数时,内部函数的代码都是相同的,而且关联这段代码的作用域链也不相同。

原文地址:https://www.cnblogs.com/cimuly/p/7301357.html