《红宝石》第3章“基本概念”【上】

目录

3.1 语法

3.1.1 区分大小写

3.1.2 标识符

3.1.3 注释

3.1.4 严格模式

3.1.5 语句

3.2 关键字和保留字

3.3 变量

3.4 数据类型

3.4.1  typeof 操作符

3.4.2  Undefined 类型

3.4.3  Null 类型

3.4.4  Boolean 类型

3.4.5  Number 类型

1、浮点数值

2、数值范围

3、NaN

4、数值转换

3.4.6  String 类型

1、字符字面量

2、字符串的特点

3、转换为字符串

3.4.7  Object 类型

3.5 操作符

3.5.1  一元操作符

1、递增和递减操作符

2、一元加和减操作符

3.5.2  位操作符

1、按位非(NOT)

2、按位与(AND)

3、按位或(OR)

4、按位异或(XOR)

5、左移

6、有符号的右移

7、无符号右移

3.5.3  布尔操作符

1、逻辑非

2、逻辑与

3、逻辑或

3.5.4  乘性操作符

1、乘法

2、除法

3、求模

3.5.5  加性操作符

1、加法

2、减法

3.5.6  关系操作符

3.5.7  相等操作符

3.5.8  条件操作符

3.5.9  赋值操作符

3.5.10  逗号操作符


本章内容 

  •   语法
  •   数据类型

下一章内容 : 《红宝石》第3章“基本概念”【下】

本文链接:《红宝石》第3章“基本概念”【下】_雨季mo浅忆的博客-CSDN博客 


3.1 语法

        ECMAScript 的语法 大量借鉴了 C 及其他类 C语言 ( 如 Java 和 Perl ) 的 语法。

        因此,熟悉这些语言的 开发人员 在接受 ECMAScript 更加 宽松 的 语法 时,一定会有一种轻松自在的感觉。

3.1.1 区分大小写

        要理解的 第一个 概念 就是 ECMAScript 中的一切 ( 变量、函数名 和 操作符 ) 都 

区分大小写 。这也就意味着,变量名 test 和 变量名 Test 分别表示两个不同的变量,

而 函数名 不能使用 typeof ,因为它是一个 关键字 ( 3.2 节介绍 关键字 ) ,

但 typeOf 则完全可以是一个有效的 函数名。

3.1.2 标识符

        所谓 标识符,就是指 变量、函数、属性的 名字,或者函数的 参数

标识符 可以是按照下列格式规则组合起来的 一 或 多个 字符 :

  • 1、第一个字符必须是一个 字母、下划线 ( _ ) 或一个美元符号 ( $ ) ;
  • 2、其他字符可以是 字母、下划线、美元符号 或 数字。

        标识符 中的字母也可以包含扩展的 ASCII 或 Unicode 字母字符 ( 如拉丁 À 和 Æ ) ,

但我们 不推荐 这样做。

        按照惯例,ECMAScript 标识符采用 驼峰大小写格式,也就是 第一个字母小写,

剩下的每个单词的首字母大写,例如 :

  • firstSecond
  • myCar
  • doSomethingImportant 

        虽然没有谁强制要求必须采用这种格式,但为了与 ECMAScript 内置的 函数 和 

对象 命名格式 保持一致,可以将其当作一种最佳实践。

  • 不能把关键字、保留字、true、false 和 null 用作 标识符。

3.1.3 注释

        ECMAScript 使用 C 风格的注释,包括 单行 注释块级 注释

    单行注释 以两个斜杠 // 开头,如下所示 :

// 单行注释

  块级注释 以一个斜杠和一个星号 ( /* ) 开头,以一个星号和一个斜杠 ( */ ) 结尾

/*
 * 这是一个多行
 * (块级)注释
 */

      虽然上面注释中的第二和第三行都以一个星号开头,但这不是必需的。之所以添加那

两个星号,纯粹是为了 提高注释的可读性 ( 这种格式在企业级应用中用得比较多 )

3.1.4 严格模式

        ECMAScript5 引入了 严格模式 ( strict mode ) 的概念。严格模式 是为 JavaScript

定义了一种不同的解析与执行模型。在严格模式下,ECMAScript3 中的一些不确定的行为

将得到处理,而且对某些不安全的操作也会抛出错误。要在整个脚本中启用严格模式,

可以 在顶部添加如下代码 :

"use strict";

    这行代码看起来像是字符串,而且也没有赋值给任何变量,但其实它是一个 

编译指示 ( pragma ) ,用于告诉支持的 JavaScript 引擎切换到严格模式。这是为

不破坏 ECMAScript3 语法而特意选定的语法。
        在函数内部的上方包含这条编译指示,也可以指定函数在严格模式下执行 :

  function doSomething () {
    "use strict";
    // 函数体
  }

    严格模式下,JavaScript 的执行结果会有很大不同,因此本书将会随时指出严格模式下

的区别。支持 严格模式 的 浏览器 包括 IE10+、Firefox 4+、Safari 5.1+、Opera 12+ 和  Chrome 。

3.1.5 语句

        ECMAScript 中的 语句以一个 分号 结尾 如果省略分号,则由 解析器 确定语句的

结尾, 如下例所示 :

var sum = a + b  // 即使没有分号也是有效的语句——不推荐
var diff = a - b;  // 有效的语句——推荐

     虽然语句结尾的分号不是必需的,但我们建议任何时候都 不要省略 它。因为加上这个分号可以避很多错误 ( 例如不完整的输入 ),开发人员也可以放心地通过 删除多余的空格来压缩 ECMAScript 代码 ( 代码行结尾处没有分号会导致压缩错误 ) 。另外,加上分号也会在某些情况下增进代码的性能,因为这样 解析器 就不必再花时间推测应该在哪里插入分号了
        可以使用 C 风格的语法把多条语句组合到一个 代码块 中,即 代码块

左花括号 ( { ) 开头,以 右花括号 ( } ) 结尾 :

    if (test) {
      test = false;
      alert(test);
    }

    虽然 条件控制语句 ( 如 if 语句 ) 只在执行多条语句的情况下才要求使用 代码块,但最佳

实践是始终在控制语句中使用代码块——即使代码块中只有一条语句,例如:

    if (test)
      alert(test);  // 有效但容易出错,不要使用 

    if (test) {     // 推荐使用 
      alert(test);
    }

在控制语句中使用 代码块 可以让编码意图更加清晰,而且也能降低修改代码时出错的几率。


3.2 关键字和保留字

        ECMA-262 描述了一组具有特定用途关键字 ,这些 关键字 可用于表示 控制语句的 开始 或 结束 ,或者用于执行 特定操作 等。按照规则,关键字也是语言保留的,不能用作 标识符。以下就是 ECMAScript 的全部 关键字 ( 带 * 号上标的是第 5 版新增的 关键字 ) :

break         do         instanceof    typeof 
case          else       new           var 
catch         finally    return        void 
continue      for        switch        while 
debugger*     function   this          with 
default       if         throw 
delete        in         try 

        ECMA-262 还描述了另外一组 不能 用作 标识符保留字 。尽管 保留字 在这门语言中还没有任何特定的用途,但它们有可能在 将来被用作 关键字 。以下是 ECMA-262 第 3 版定义的 全部 保留字 :

abstract       enum             int             short 
boolean        export           interface       static 
byte           extends          long            super 
char           final            native          synchronized 
class          float            package         throws 
const          goto             private         transient 
debugger       implements       protected       volatile 
double         import           public

        第 5 版把在非严格模式下运行时的 保留字 缩减为下列这些 :

class  enum    extends  super 
const  export  import 

    在严格模式下,第 5 版还对以下保留字施加了限制 :

implements  package    public 
interface   private    static 
let         protected  yield 

  注意,let 和 yield 是第 5 版新增的 保留字;其他 保留字 都是第 3 版定义的。为了最大程度地保证 兼容性,建议读者将第 3 版定义的保留字外加 let 和 yield 作为编程时的参考。
        在实现 ECMAScript3 的 JavaScript 引擎中使用 关键字 作 标识符 ,会导致 “ Identifier Expected ” 错误。而使用 保留字 作 标识符 可能会也可能不会导致相同的错误,具体取决于特定的引擎。
    第 5 版对使用 关键字 和 保留字 的规则进行了少许修改。关键字 和 保留字 虽然仍然不能作为标识符使,但现在可以用作对象的属性名。一般来说,最好都不要使用 关键字 和 保留字 作为 标识符 和 属性名 ,以便与将来的 ECMAScript 版本兼容。
    除了上面列出的 保留字 和 关键字 ,ECMA-262 第 5 版对 eval 和 arguments 还施加了限制。在严格模式下,这两个名字也不能作为 标识符 或 属性名 ,否则会抛出错误。 


3.3 变量

        ECMAScript 的变量是 松散类型 的,所谓 松散类型 就是可以用来保存任何类型的数据。换句话说,每个变量仅仅是一个用于保存值的 占位符 而已。定义变量时要使用 var 操作符 ( 注意 var 是一个 关键字 ) ,后跟 变量名 ( 即一个 标识符 ) ,如下所示 :

var message;

    这行代码定义了一个名为 message 的变量,该变量可以用来保存任何值 ( 像这样未经过初始化的变量 ,会保存一个特殊的值 —— undefined ,相关内容将在3.4 节讨论 ) 。ECMAScript 也支持直接初始化变量,因此在定义变量的同时就可以设置变量的值,如下所示 :

var message = "hi";

    在此,变量 message 中保存了一个 字符串值 "hi" 。像这样初始化变量并不会把它标记为字符串类型;初始化的过程就是给变量赋一个值那么简单。因此,可以在修改变量值的同时修改值的类型,如下所示 :

var message = "hi";
message = 100; // 有效,但不推荐 !

    在这个例子中,变量 message 一开始保存了一个字符串值 "hi" ,然后该值又被一个数字值 100取代。虽然我们 不建议修改变量所保存值的类型,但这种操作在 ECMAScript 中完全有效。
        有一点必须注意,即用 var 操作符定义的变量将成为定义该变量的作用域中的 局部变量 。也就是说,如果在函数中使用 var 定义一个变量,那么这个变量在函数退出后就会被销毁 ,例如 :

function test(){
  var message = "hi";  // 局部变量
}
test();
alert(message);    // 错误!

        这里,变量 message 是在函数中使用 var 定义的。当函数被调用时,就会创建该变量并为其赋值。在此之后,这个变量又会立即被销毁,因此例子中的下一行代码就会导致错误。不过,可以像下面这样省略 var 操作符,从而创建一个 全局变量

function test(){
  message = "hi";   // 全局变量
}
test();
alert(message);    // "hi"

    这个例子省略了 var 操作符,因而 message 就成了 全局变量 。这样,只要调用过一次 test( ) 函数,这个变量就有了定义,就可以在函数外部的任何地方被访问到。

        虽然省略 var 操作符可以定义 全局变量,但这也不是我们推荐的做法。因为在 局部作用域 中定义的 全局变量 很难维护,而且如果有意地忽略了 var 操作符,也会由于相应变量不会马上就有定义而导致不必要的混乱。给未经声明的变量赋值在严格模式下会导致抛出 ReferenceError 错误。

        可以使用一条语句定义多个变量,只要像下面这样把每个变量 ( 初始化 或 不初始化 均可 ) 用逗号分隔开即可 :

var message = "hi",
    found = false,
    age = 29;

    这个例子定义并初始化了3个变量。同样由于 ECMAScript 是松散类型的,因而使用不同类型初始化变量的操作可以放在一条语句中来完成。虽然代码里的换行和变量缩进不是必需的,但这样做可以 提高可读性
        在严格模式下,不能定义名为 eval 或 arguments 的变量,否则会导致语法错误。


3.4 数据类型

        ECMAScript 中有 5 种 简单 数据类型 ( 也称为 基本数据类型 ) :Undefined、Null、Boolean、Number 和 String 。还有 1 种 复杂 数据类型 —— Object ,Object 本质上是由一组无序的 名值对 ( 键值对 )组成的。ECMAScript 不支持任何创建自定义类型的机制,而所有值最终者都将是上述 6 种数据类型之一。乍一看,好像只有 6 种数据类型不足以表示所有数据;但是,由于 ECMAScript 数据类型具有 动态性,因此的确没有再定义其他数据类型的必要了。

3.4.1  typeof 操作符

        鉴于 ECMAScript 是 松散类型 的 ,因此需要有一种手段来 检测 给定变量的 数据类型 —— typeof 就是负责提供这方面信息的 操作符 。对一个值使用 typeof 操作符 可能返回下列某个 字符串

  • 1、"undefined" —— 如果这个值 未定义 ;
  • 2、"boolean" —— 如果这个值是 布尔值 ;
  • 3、"string" —— 如果这个值是 字符串
  • 4、"number" —— 如果这个值是 数值
  • 5、"object" —— 如果这个值是 对象 或 null 
  • 6、"function" —— 如果这个值是 函数 ;

    下面是几个使用 typeof 操作符的例子 :

var message = "some string";
alert(typeof message);    // "string"
alert(typeof(message));   // "string"
alert(typeof 95);         // "number"

    这几个例子说明,typeof 操作符的操作数可可以是变量 ( message ) ,也可以是 数值字面量。注意。 typeof 是一个操作符而不是函数,因此例子中的圆括号尽管可以使用,但不是必需的。
        有些时候,typeof 操作符会返回一些令人迷惑但技术上却正确的值。比如,调用

 typeof null  会返回 "object" ,因为 特殊值 null 被认为是一个 空的对象引用 。Safari 5 及之前版本、Chrome 7 及之前版本在对正则表达式调用 typeof 操作符时会返返回 "function" ,而其他浏览器在这种情况下会返回 "object"。

        从技术角度讲,函数在 ECMAScript 中是对象,不是一种数据类型。然而,函数也确实有一些特殊的属性,因此通过 typeof 操作符来区分函数和其他对象是有必要的。

3.4.2  Undefined 类型

        Undefined 类型只有一个值,即特殊的 undefined 。在使用 var 声明变量 但 未 对其加以 初始化 时,这个变量的值就是 undefined,例如 :

var message;
alert(message == undefined);  // true

    这个例子只声明了变量 message ,但未对其进行初始化。比较这个变量与 undefined 字面量,结果表明它们是相等的。这个例子与下面的例子是等价的 :

var message = undefined;
alert(message == undefined);  // true

    这个例子使用 undefined 值显式初始化了变量 message。但我们没有必要这么做,因为未经初始化的值默认就会取得 undefined 值
          一般而言,不存在需要显式地把一个变量设置为 undefined 值的情况。字面值 undefined 的主要目的是用于 比较,而 ECMA-262 第 3 版之前的版本中并没有规定这个值。第 3 版引入这个值是为了 正式 区分 空对象指针 与 未经初始化的变量

        不过,包含 undefined 值的变量与尚未定义的变量还是不一样的。看看下面这个例子 :

var message;   // 这个变量声明之后默认取得了 undefined 值

// 下面这个变量并没有声明
// var age

alert(message);  // "undefined" 
alert(age):      // 产生错误 

    运行以上代码,第一个警告框会显示变量 message 的值,即 "undefined" 。而第二个警告框 —— 由于传递给 alert( ) 函数的是尚未声明的变量 age —— 则会导致一个错误。对于尚未声明过的变量,只能执行一项操作,即使用 typeof 操作符检测其数据类型 ( 对未经声明的变量调用 delete 不会导致错误,但这样做没什么实际意义,而且在严格模式下确实会导致错误 ) 。
        然而,令人困惑的是:对 未 初始化 的变量执行 typeof 操作符会返回 undefined 值,而对 未 声明 的变量 执行 typeof 操作符 同样也会返回 undefined 值。来看下面的例子 :

var message;   // 这个变量声明之后默认取得了 undefined 值

// 下面这个变量并没有声明
// var age

alert(typeof message);  // "undefined" 
alert(typeof age);      // "undefined" 

    结果表明,对 未初始化未声明 的 变量 执行 typeof 操作符都返回了 undefined 值;这个结果有其逻辑上的合理性。因为虽然这两种变量从技术角度看有本质区别,但实际上无论对哪种变量也不可能执行真正的操作。
          即便未初始化的变量会自动被赋予 undefined 值,但显式地初始化变量依然是明智的选择。如果能够做到这一点,那么当 typeof 操作符返回 "undefined" 值时,我们就知道被检测的 变量还没有被声明,而不是尚未初始化

3.4.3  Null 类型

        Null 类型是第二个只有一个值的数据类型,这个特殊的值是 null 。从逻辑角度来看,null 值表示一个 空对象 指针,而这也正是使用 typeof 操作符检测 null 值时会返回 "object" 的原因,如下面的例子所示 :

var car = null;
alert(typeof car); // "object“

        如果定义的变量准备在将来用于保存 对象,那么最好将该变量初始化为 null 而不是其他值。这一来,只要直接检查 null 值就可以知道相应的变量是否已经保存了一个对象的引用,如下面的例子所示 :

if (car != null) {
  // 对 car 对象执行某些操作
}

    实际上,undefined 值是派生自 null 值的,因此 ECMA-262 规定对它们的相等性测试要返回 true :

alert(null == undefined);  // true

    这里,位于 null 和 undefined 之间的相等操作符 ( == ) 总是返回 true ,不过要注意的是,这个操作符出于比较的目的会转换其 操作数 ( 本章后面将详细介绍相关内容 )
        尽管 null 和 undefined 有这样的关系,但它们的用途完全不同。如前所述,无论在什么情况都没有必要把一个变量的值显式地设置为 undefined ,可是同样的规则对 null 却不适用。换句话说,只要意在 保存对象的变量 还没有真正保存对象,就应该明确地让该 变量保存 null 值。这样做不仅可以体现 null 作为空对象指针的惯例,而且也有助于进一步

区分 null 和 undefined

3.4.4  Boolean 类型

        Boolean 类型是 ECMAScript 中使用得最多的一种类型,该类型只有两个字面值 :
true 和 false 。两个值与数字值不是一回事,因此 true 不一定等于 1,而 false 也不一定等于 0。以下是为变量赋 Boolean 类型值的例子 :

var found = true;
var lost = false;

    要注意的是,Boolean 类型的字面值 true 和 false 是 区分大小写。也就是说,

True 和 False ( 以及其他的混合大小写形式 ) 都不是 Boolean 值,只是 标识符 
        虽然 Boolean 类型的字面值只有两个,但 ECMAScript 中所有类型的值都有与这两个 Boolean 值等价的值。要将一个值转换为其对应的 Boolean 值,可以调用

转型函数 Boolean(),如下例所示 :

var message = "Hello world!";
var messageAsBoolean = Boolean(message);

    在这个例子中,字符串 message 被转换成了一个 Boolean 值,该值被保存在 messageAsBoolean 变量中。可以对任何数据类型的值调用 Boolean()函数 ,而且总会返回一个 Boolean 值。至于返回的这个值是 true 还是 false,取决于要转换值的数据类型及其

实际值。下表给出了各种数据类型及其对应的转换规则。

 数据类型     转换为true的值               转换为false的值
Boolean       true                       false
String        任何非空字符串               ""(空字符串)
Number        任何非零数字值(包括无穷大)   0和NaN
Object        任何对象                    null
Undefined     n/a                        undefined

n/a ( 或 N/A ) ,是 not applicable 的缩写,意思是 “不适用”。   

    这些转换规则对理解 流控制语句 ( 如 if 语句 ) 自动执行相应的 Boolean 转换非常重要,

请看下面的代码 :

var message = "Hello world!";
if (message) {
  alert("Value is true");
)

    运行这个示例,就会显示一个警告框,因为字符串 message 被自动转换成了对应的 Boolean 值 ( true ) 。由于存在这种 自动执行的 Boolean 转换,因此确切地知道在 流控制语句 中使用的是什么变量至关重要。错误地使用一个对象而不是一个 Boolean 值,就有可能彻底改变应用程序的流程。

3.4.5  Number 类型

        Number 类型应该是 ECMAScript 中最令人关注的数据类型了,这种类型使用 IEEE754 格式来表示 整数 浮点数值 ( 浮点数值 在某些语言中也被称为 双精度数值 ) 。为支持各种数值类型,ECMA-262 定义了不同的数值字面量格式。
    最基本的数值字面量格式是 十进制整数,十进制整数可以像下面这样直接在代码中输入:

var intNum = 55;  // 整数 

    除了以 十进制 表示外,整数还可以通过 八进制 ( 以 8 为 基数 ) 或 十六进制 ( 以 16 为基数 ) 的 字面值 来表示。其中,八进制字面值 的第一位必须是零( 0 ),然后是 八进制数字序列( 0 ~ 7 )。如果字面值中的数值超出了范围,那么前导零将被忽略,后面的数值将被当作 十进制 数值解析。请看下面的例子 :

var octalNum1 = 070;    // 八进制的 56 
var octalNum2 = 079;    // 无效的八进制数值 —— 解析为 79 
var octalNum3 = 08;     // 无效的八进制数值 —— 解析为 8 

    八进制字面量 在严格模式下是无效的,会导致支持该模式的 JavaScript 引擎抛出错误。
        十六进制字面值的前两位必须是 0x ,后跟任何 十六进制数字( 0 ~ 9 及 A ~ F )

其中,字母 A~F 可以大写,也可以小写。如下面的例子所示 :

var hexNum1 = 0xA;    // 十六进制的 10 
var hexNum2 = 0x1f;   // 十六进制的 31 

在进行算术计算时,所有以 八进制 和 十六进制 表示的数值最终都将被转换成 十进制数值。

    鉴于 JavaScript 中保存数值的方式,可以保存正零( +0 )和 负零( -0 )。

正零和负零被认为相等,但为了读者更好地理解上下文,这里特别做此说明。


1、浮点数值

        所谓 浮点数值,就是该数值中必须包含 一个小数点 ,并且 小数点 后面必须至少有一位数字。虽外 小数点 前面可以没有整数,但我们不推荐这种写法。以下是 浮点数值 的几个例子 :

var floatNum1 = 1.1;
var floatNum2 = 0.1;
var floatNum3 = .1:   // 有效,但不推荐

    由于 保存 浮点数值 需要的 内存空间 是保存 整数值 的 两倍,因此 ECMAScript 会不失时机地将浮点数佳转换为整数值。显然,如果小数点后面没有跟任何数字,那么这个数值就可以作为 整数值 来保存。同样地,如果 浮点数值 本身表示的就是一个整数 ( 如 1.0 ) ,那么该值也会被转换为整数,如下面的例子所示 :

var floatNuml = 1.;    // 小数点后面没有数字 —— 解析为 1 
var floatNum2 = 10.0;  // 整数 —— 解析为 10 

    对于那些 极大 或 极小 的 数值,可以用 e 表示法 ( 即科学计数法 ) 表示的 浮点数值 表示。用 e 表示法表示的数值等于 e 前面的数值乘以 10 的 指数次幂 。ECMAScript 中 e 表示法的格式也是如此,即前面是一个数值 ( 可以是整数也可以是浮点数 ) ,中间是一个 大写 或 小写 的 字母 E,后面是 10 的幂中的指数,该幂值将用来与前面的数相乘。下面是一个使用 e 表示法 表示数值的例子 :

var floatNum = 3.125e7;   // 等于 31250000 

    在这个例子中,使用 e 表示法 表示的变量 floatNum 的形式虽然简洁,但它的实际值则是31250000 。在此,e 表示法的实际含义就是 “ 3.125 乘以 10 的 7次方”。
        也可以使用 e 表示法 表示极小的数值,如 0.00000000000000003 ,这个数值可以使用更简洁的 3e-17表示。在默认情况下,ECMAScript 会将那些小数点后面带有 6 个零以上的 浮点数值 转换为以 e 表示法 表示的数值 ( 例如,0.0000003 会被转换成 3e-7 ) 。
        浮点数值 的最高精度是 17 位小数,但在进行算术计算时其精确度远远不如整数。例如,0.1 加 0.2 的 结果 不是 0.3 ,而是 0.30000000000000004 。这个小小的舍入误差会导致无法测试特定的浮点数值。例如 :

if (a+b == 0.3) {           // 不要做这样的测试 !
  alert("You got 0.3.");
}

    在这个例子中,我们测试的是两个数的和是不是等于 0.3。如果这两个数是 0.05 和 0.25,或者是 0.15 和 0.15 都不会有问题。而如前所述,如果这两个数是 0.1 和 0.2,那么测试将无法通过。因此,永远不要测试某个特定的 浮点数值 
        关于 浮点数值 计算会产生 舍入误差 的问题,有一点需要明确 :这是使用基于 IEEE754 数值的 浮点计算 的 通病,ECMAScript 并非独此一家;其他使用相同数值格式的语言也存在这个问题。


2、数值范围

        由于 内存 的限制,ECMAScript 并不能保存世界上所有的数值。ECMAScript 能够表示的 最小数值 保存在 Number.MIN_VALUE 中 —— 在大多数浏览器中,这个值是 5e-324;

能够表示的 最大数值 保存在 Number.MAX_VALUE 中 —— 在大多数浏览器中,这个值是 1.7976931348623157e+308 。如果某次计算结果得到了一个超出 JavaScript 数值范围的值,那么这个数值将被自动转换成特殊的 Infinity 值。具体来说,如果这个数值是负数,则会被转换成 -Infinity ( 负无穷 ) ,如果这个数值是正数,则会被转换成 Infinity ( 正无穷 ) 。
        如上所述,如果某次计算返回了正或负的 Infinity 值,那么该值将无法继续参与下一次的计算,因为 Infinity 不是能够参与计算的数值。要想确定一个数值是不是有穷的 ( 换句话说,是不是位于最小和最大的数值之间 ) ,可以使用 isFinite() 函数。这个函数在 参数 位于最小与最大数值之间时会 返回 true,如下面的例子所示 :

var result = Number.MAX_VALUE + Number.MAX_VALUE;
alert(isFinite(result));    // false 

    尽管在计算中很少出现某些值超出表示范围的情况,但在执行极小或极大数值的计算时,检测监控这些值是可能的,也是必需的。
        访问 Number.NEGATIVE_INFINITY 和 Number.POSITIVE_INFINITY 也可以得到 负 和 正 Infinity 的值。可以想见,这两个属性中分别保存着 -Infinity 和 Infinity 。


3、NaN

        NaN ,即 非数值 ( Not a Number ) 是 一个特殊的数值,这个数值用于表示一个本来要返回数值的 操作数 未 返回数值 的 情况 ( 这样就不会抛出错误了 ) 。例如,在其他编程语言中,任何数值除以非数值都会导致错误,从而停止代码执行。但在 ECMAScript 中,任何数值除以非数值会返回 NaN ,因此不会影响其他代码的执行。
        NaN 本身有两个非同寻常的特点。首先,任何涉及 NaN 的操作 ( 例如 NaN/10 ) 都会返回 NaN,这个特点在多步计算中有可能导致问题。其次,NaN 与任何值都不相等,包括 NaN 本身。例如,下面的代码会返回 false :

alert(NaN == NaN);   // false 

    针对 NaN 的这两个特点,ECMAScript 定义了 isNaN() 函数 。这个函数接受一个参数,该参数可以是任何类型,而函数会帮我们 确定这个参数是否 “ 不是数值 ” 。isNaN() 在接收到一个值之后,会尝试将这个值转换为数值。某些不是数值的值会直接转换为数值,例如字符串 "10" 或 Boolean 值。而任何 不能被转换为数值的值都会导致这个函数返回 true 

请看下面的例子 :
 

alert(isNaN(NaN));    // true 
alert(isNaN(10));     // false (10是一个数值) 
alert(isNaN("10"));   // false (可以被转换成数值 10) 
alert(isNaN("blue")); // true  (不能转换成数值) 
alert(isNaN(true));   // false (可以被转换成数值 1)

    这个例子测试了 5 个不同的值。测试的第一个值是 NaN 本身,结果当然会返回 true 。

然后分别测试了 数值 10字符串"10",结果这两个测试都返回了 false ,因为前者本身就是数值,而 后者可以被换成数值。但是,字符串"blue"不能被转换成数值,因此函数返回了true。由于 Boolean 值 true 可以转换成数值 1,因此函数返回 false 。
        尽管有点儿不可思议,但 isNaN() 确实也适用于对象。在基于对象调用 isNaN() 函数 时,会首先调用对象的 valueOf() 方法,然后确定该方法返回的值是否可以转换为数值。如果不能,则基于这个返回值再调用 toString() 方法,再测试返回值。而这个过程也是 ECMAScript 中 内置函数 和 操作符 的一般执行流程,更详细的内容请参见 3.5 节。


4、数值转换

        有 3 个函数可以把 非数值转换为数值 : Number()、parseInt() 和 parseFloat()

第一个函数。 即 转型函数 Number() 可以用于任何数据类型,而另两个函数则专门用于

把 字符串转换成数值。这 3 个函数对于同样的输人会有返回不同的结果。
    Number() 函数 的转换规则如下。

  • 1、如果是 Boolean 值,true 和 false 将分别被转换为 1 和 0。
  • 2、如果是 数字值,只是简单的传人和返回。
  • 3、如果是 null 值,返回 0。
  • 4、如果是 undefined,返回 NaN。
  • 5、如果是 字符串,遵循下列规则 :

  (1)如果 字符串 中只包含 数字 ( 包括前面带 正号 或 负号 的情况 ),则将其转换为 十进制 数值,即 "1" 会变成 1 ,"123" 会变成 123 ,而 "011" 会变成 11 ( 注意 :前导的零被忽略了 );
  (2)如果 字符串 中包含有效的 浮点 格式,如 "1.1" ,则将其转换为对应的 浮点数值 ( 同样,也会忽略前导零);
  (3)如果 字符串 中包含有效的 十六进制 格式,例如 " 0xf " ,则将其转换为相同大小的 十进制 整数值;
  (4)如果 字符串 是 的 ( 不包含任何字符 ) ,则将其转换为 0
  (5)如果 字符串 中包含除上述格式之外的字符,则将其转换为 NaN 。

  • 6、如果是 对象 ,则调用对象的 valueOf() 方法 ,

          然后依照前面的规则转换返回的值。如果转换的结果是 NaN ,则调用对象的 toString() 方法 ,然后再次依照前面的规则转换返回的 字符串值 。
        根据这么多的规则使用 Number() 把各种 数据类型 转换为 数值 确实有点复杂。

下面还是给出几个具体的例子吧。

var num1 = Number("Hello world!");    // NaN
var num2 = Number("");                // 0
var num3 = Number("000011");          // 11
var num4 = Number(true);              // 1

    首先,字符串 "Hello world!" 会被转换为 NaN ,因为其中不包含任何有意义的数字值。

空字符串 会被转换为 0。字符串 "000011" 会被转换为 11,因为忽略了其前导的零。

最后,true 值被转换为 1。
        一元加 操作符 ( 3.5.1 节将介绍 ) 的操作与 Number() 函数相同 。
    由于 Number() 函数 在转换字符串时比较复杂而且不够合理,因此在 处理整数的时候更常用的是 parseInt() 函数parseInt() 函数 在转换字符串时,更多的是看其是否符合数值模式它会 忽略 字符串前面的 空格,直至找到第一个 非空格字符。如果第一个字符不是数字字符或者负号,parseInt() 就会返回 NaN ;也就是说,用

parseInt() 转换 空字符串 会返回  NaN ( Number() 对 空字符 返回 0 ) 。

如果第一个字符是数字字符,parseInt() 会继续解析第二个字符,直到解析完所有后续字符或者遇到了一个非数字字符。例如,"1234blue" 会被转换为 1234 ,因为 "blue" 会被完全忽略。类似地,"22.5" 会被转换为 22 ,因为小数点并不是有效的数字字符
        如果 字符串 中的第一个字符是数字字符,parseInt() 也能够识别出各种整数格式

( 即前面讨论的 十进制、八进制 和 十六进制数 ) 。也就是说,如果字符串以 "0x" 开头且后跟数字字符,就会将其当作一个十六进制整数;如果 字符串 以 "0" 开头且后跟数字字符,则会将其当作一个 八进制 数来解析。
        为了更好地理解 parseInt() 函数的转换规则,下面给出一些例子 :

var num1 = parseInt("1234blue");   // 1234 
var num2 = parseInt("");           // NaN 
var num3 = parseInt("0xA");        // 10 (十六进制数) 
var num4 = parseInt(22.5);         // 22 
var num5 = parseInt("070");        // 56 (八进制数) 
var numb = parseInt("70");         // 70 (十进制数) 
var num7 = parseInt("0xf");        // 15 (十六进制数) 

    在使用 parseInt() 解析像 八进制 字面量 的 字符串 时,ECMAScript3 和 5 存在分歧。

例如 :

// ECMAScript3 认为是 56 (八进制),ECMAScript5 认为是 70 (十进制) 
var num = parseInt("070");

    在 ECMAScript 3 JavaScript 引擎中,"070" 被当成 八进制 字面量 ,因此转换后的值是 十进制 的 56 。而在 ECMAScript 5 JavaScript 引擎中,parseInt() 已经不具有解析 八进制值 的能力,因此前导的零会被认为无效,从而将这个值当成 "70" ,结果就得到十进制的 70。在 ECMAScript5 中,即使是在非严模式下也会如此。
        为了消除在使用 parseInt() 函数时可能导致的上述困惑,可以为这个函数提供第二个参数 :转换时使用的基数 ( 即多少进制 ) 。如果知道要解析的值是 十六进制 格式的 字符串 ,那么指定基数 16 作为 第二个参数,可以保证得到正确的结果,例如 :

var num = parseInt("0XAF", 16);   // 175 

实际上,如果指定了 16 作为 第二个参数,字符串可以不带前面的 "0x" ,如下所示 :
 

var num1 = parseInt("AF", 16);   // 175 
var num2 = parseInt("AF");       // NaN

    这个例子中的第一个转换成功了,而第二个则失败了。差别在于第一个转换传人了基数,明确告诉 parseInt() 要解析一个 十六进制 格式的 字符串;而第二个转换发现第一个字符不是 数字字符,因此就自动终止了。
        指定基数会影响到转换的输出结果。例如:

var num1 = parseInt("10",2);    // 2 (按二进制解析) 
var num2 = parseInt("10",8);    // 8 (按八进制解析) 
var num3 = parseInt("10", 10);   // 10 (按十进制解析) 
var num4 = parseInt("10", 16);   // 16 (按十六进制解祈) 

    不指定基数意味着让 parseInt() 决定如何解析输入的 字符串 ,因此为了避免错误的解析,我们 建议无论在什么情况下都明确指定基数
        多数情况下,我们要 解析 的都是 十进制 数值,因此始终将 10 作为 第二个参数 是 非常必要的。
        与 parseInt() 函数 类似,parseFloat() 也是从第一个字符 ( 位置 0 ) 开始解析 每个字符。而且也是一直解析到字符串末尾,或者解析到遇见一个无效的浮点数字字符为止。也就是说,字符串 中的第一个小数点是有效的,而第二个小数点就是无效的了,因此它后面的字符串将被忽略。举例来说,

  “22.34.5" 将会被转换为 22.34。
        除了第一个小数点有效之外,parseFloat() 与 parseInt() 第二个区别在于它始终都会忽略前导的零。parseFloat() 可以识别前面讨论过的所有浮点数值格式,也包括 十进制 整数格式。但 十六进制 格式的 字符串 则始终会被转换成 0。由于 parseFloat() 只解析 十进制 值,因此它没有用第二个参数指定基数的用法。最后还要注意一点 :如果 字符串 包含的是一个可解析为整数的数 ( 没有小数点,或者小数点后都是零 ) ,parseFloat() 会返回整数。

以下是使用 parsefloat() 转换数值的几个典型示例。

var num1 = parseFloat("1234blue");   // 1234 (整数) 
var num2 = parseFloat("0xA");        // 0 
var num3 = parseFloat("22.5");       // 22.5 
var num4 = parseFloat("22.34.5");    // 22.34 
var num5 = parseFloat("0908.5");     // 908.5 
var num6 = parseFloat("3.125e7");    // 31250000 

3.4.6  String 类型

        String 类型用于表示 由零或多个 16位 Unicode 字符 组成的 字符序列,即 字符串
字符串 可以由 双引号 ( " ) 或 单引号 ( ' ) 表示 ,因此下面两种字符串的写法都是有效的 :

var firstName = "Nicholas";
var lastName = 'Zakas';

    与 PHP 中的 双引号 和 单引号 会影响对 字符串 的 解释方式不同,ECMAScript 中的这两种语法形式没有什么区别。用 双引号 表示的 字符串 和用 单引号 表示的 字符串 完全相同。不过,以 双引号 开头的 字符串也必须以双引号结尾,而以 单引号 开头的 字符串 也必须以单引号 结尾。例如 ,下面这种字符串表示法会导致语法错误 :

var firstName = 'Nicholas";  // 语法错误(左右引号必须匹配)

1、字符字面量

        String 数据类型 包含一些特殊的字符字面量,也叫 转义序列,用于表示非打印字符,或者具有其他用途的字符。这些字符字面量如下表所示 :

字面量                    含义
\n                       换行
\t                       制表
\b                       退格
\r                       回车
\f                       进纸
\\                       斜杠
\'           单引号( ' ),在用单引号表示的字符串中使用。例如:'He said,\'hey.\''
\"           双引号( " ),在用双引号表示的字符串中使用。例如:"He said,\"hey.\""
\xnn         以十六进制代码nn表示的一个字符(其中n为0~F)。例如,\x41表示"A"
\unnnn       以十六进制代码nnnn表示的一个Unicode字符(其中n为0~F)。例如,\u03a3 表示希腊字符 ∑ 

    这些字符字面量可以出现在字符串中的任意位置,而且也将被作为一个字符来解析,

如下面的例子所示 :

var text = "This is the letter sigma: \u03a3."; // \u03a3 表示希腊字符 ∑

    这个例子中的变量 text 有 28 个字符,其中 6 个字符长的转义序列表示 1 个字符。

任何字符串的长度都可以通过访问其 length 属性取得,例如 :

alert(text.length);    // 输出 28

    这个属性返回的字符数包括 16 位字符的数目。如果字符串中包含双字节字符,

那么 length 属性可能不会精确地返回字符串中的字符数目。


2、字符串的特点

        ECMAScript 中的 字符串 是不可变,也就是说,字符串一旦创建,它们的 值就不能改变。要改变某个变量保存的字符串,首先要销毁原来的字符串,然后再用另一个包含新值的字符串填充该变量,例如 :

var lang = "Java";
lang = lang + "Script";

    以上示例中的变量 lang 开始时包含字符串 "Java" 。而第二行代码把 lang 的值重新定义为 "Java” 与 "Script" 的 组合,即 "JavaScript" 。实现这个操作的过程如下 :首先创建一个能容纳 10 个字符的 新字符串 ,然后在这个字符串中填充 "Java" 和 "Script" ,最后一步是销毁原来的字符串 "Java" 和 字符串 "Script" ,因为这两个字符串已经没用了。这个过程是在后台发生的,而这也是在某些旧版本的浏览器 ( 例如版本低于1.0 的 Firefox、IE6 等 ) 中拼接字符串时速度很慢的原因所在。但这些浏览器后来的版本已经解决了这个低效率问题。


3、转换为字符串

        要把一个值转换为一个 字符串 有两种方式。第一种是使用几乎每个值都有的

toString() 方法 ( 第 5 章将讨论这个方法的特点 ) 。这个方法唯一要做的就是返回相应值的字符串表现。来看下面的例子 :

var age = 11;
var ageAsString = age.toString();      // 字符串 "11" 
var found = true;
var foundAsString = found.toString();  // 宇符串 "true" 

    数值、布尔值、对象 和 字符串值 ( 没错,每个字符串也都有一个 toString() 方法,该方法返回字符串的一个副本 )都有 toString()方法。但 null 和 undefined 值没有这个方法
        多数情况下,调用 toString() 方法不必传递参数。但是,在调用数值的 toString() 方法时,可以传递一个参数 :输出数值的基数。默认情况下, toString() 方法 以 十进制 格式返回数值的字符串表示。而通过传递基数,toString() 可以输出以 二进制、八进制、十六进制,乃至其他任意有效进制格式表示的字符串值。下面给出几个例子 :

var num =10;
alert(num.toString());     // "10" 
alert(num.toString(2));    // "1010" 
alert(num.toString(8));    // "12" 
alert(num.toString(10));   // "10" 
alert(num.toString(16));   // "a" 

    通过这个例子可以看出,通过指定基数,toString() 方法会改变输出的值。而数值 10 根据基数的不同,可以在输出时被转换为不同的数值格式。注意,默认的 ( 没有参数的 ) 输出值与指定基数 10 时的输出值相同。
        在不知道要转换的值是不是 null 或 undefined 的情况下,还可以使用转型函数 String(),这个函数能够将任何类型的值转换为字符串。String() 函数遵循下列转换规则 :

  • 1、如果值有 toString() 方法,则调用该方法 ( 没有参数 ) 并返回相应的结果;
  • 2、如果值是 null ,则返回 "null" ; 
  • 3、如果值是 undefined ,则返回 "undefined" 。

下面再看几个例子 :

var valuel = 10;
var value2 = true;
var value3 = null;
var value4;

alert(String(valuel));     // "10" 
alert(String(value2));     // "true" 
alert(String(value3));     // "null" 
alert(String(value4));     // "undefined"

    这里先后转换了 4 个值 :数值、布尔值、null 和 undefined 。数值 和 布尔值 的 转换结果与调用 toString()方法 得到的结果相同。因为 null 和 undefined 没有 toString()方法,所以 String() 函数 就返回了这两个值的 字面量 。
        要把某个值转换为 字符串 ,可以使用 加号 ( + ) 操作符 ( 3.5 节讨论 ) 把它与一个

字符串 ( " " ) 加在一起 

3.4.7  Object 类型

        ECMAScript 中的 对象 其实就是一组 数据 和 功能 的 集合

对象 可以通过执行 new 操作符 后跟要创建的对象类型的名称来 创建

而创建 Object 类型 的 实例 并为其添加 属性 和 (或) 方法,就可以 创建自定义对象

如下所示 :

var o = new Object();

    这个语法与 Java 中 创建对象 的语法相似;但在 ECMAScript 中,如果不给 构造函数 传递 参数 ,则可以 省略后面的那一对圆括号 。也就是说,在像前面这个示例一样不传递参数的情况下,完全可以省略那对 圆括号 ( 但这不是推荐的做法 )

var o = new Object;    // 有效,但不推荐省略圆括号!

    仅仅创建 Object 的 实例 并没有什么用处,但关键是要理解一个重要的思想 :

即在 ECMAScript 中,( 就像 Java 中的 java.lang.Object 对象一样 )Object 类型 是所有它的 实例 的 基础 。换句话说, Object 类型所具有的任何 属性 和 方法 也同样存在于更具体的对象中。
        Object 的每个 实例 都具有下列 属性 和 方法

  • 1、constructor :保存着用于创建当前对象的 函数

          对于前面的例子而言,构造函数( constructor )就是 Object()

  • 2、hasOwnProperty(propertyName) :

        用于检查给定的 属性 在当前 对象实例 中( 而不是在实例的原型中 )是否存在。

        其中,作为 参数 的 属性名 (propertyName) 必须以 字符串 形式 指定

( 例如 :o.hasOwnProperty("name") )。

  • 3、isPrototypeOf(object) :用于检查传入的 对象 是否是当前对象的 原型

        ( 第 5 章将讨论 原型 )。

  • 4、propertyIsEnumerable(propertyName) :

          用于检查给定的 属性 是否能够使用 for-in 语句 ( 本章后面将会讨论 ) 来枚举

        与 hasOwnProperty() 方法 一样,作为 参数 的 属性名 必须以 字符串 形式 指定。

  • 5、toLocaleString() :返回对象的字符串表示,该字符串与执行环境的地区对应。 
  • 6、toString() :返回 对象 的 字符串 表示。
  • 7、valueOf() :返回 对象 的 字符串、数值 或 布尔值 表示。

        通常与 tostring()方法 的 返回值 相同。
    由于在 ECMAScript 中 Object 是所有对象的基础,因此 所有对象 都具有这些基本的

属性 和 方法 。第 5 章 和 第 6 章将详细介绍 Object 与其他对象的关系。


        从技术角度讲,ECMA-262 中对象的行为不一定适用于 JavaScript 中的其他对象。

浏览器环境中的对象,比如 BOM 和 DOM 中的 对象,都属于 宿主对象 ,因为它们是由

宿主 实现提供和定义的。ECMA-262 不负责定义 宿主对象 ,因此 宿主对象 可能会也可能不会 继承 Object 


3.5 操作符

        ECMA-262 描述了一组用于 操作 数据值 的 操作符 ,包括 算术操作符 ( 如 加号 + 和 减号 - ) 、位操作符、关系操作符 和 相等操作符。ECMAScript 操作符的与众不同之处在于,它们能够适用于很多值,例如 字符串、数字值、布尔值,甚至对象。不过,在应用于 对象 时,相应的 操作符 通常都会调用 对象 的 valueOf() 和 (或) toString() 方法,以便取得可以操作的值。

3.5.1  一元操作符

        只能操作 一个值 的 操作符 叫做 一元操作符 

一元操作符 是 ECMAScript 中最简单的操作符。


1、递增和递减操作符

        递增 和 递减 操作符 直接借鉴自 C ,而且各有两个版本:前置型 和 后置型

顾名思义,前置型应该位于要操作的变量之前,而后置型则应该位于要操作的变量之后。
因此,在使用 前置递增操作符 给一个数值加 1 时,要把两个加号 ( ++ ) 放在这个数值变量

前面,如下所示 :

var age = 29;
++age;

    在这个例子中,前置递增操作符 把 age 的值变成了 30 ( 为 29 加上了1 ) 。
实际上,执行这个前置递增操作与执行以下操作的效果相同 :

var age = 29;
age = age + 1;

    执行 前置递减操作 的方法也类似,结果会从一个数值中减去 1。使用 前置递减操作符 时,要把 两个 减号 ( -- ) 放在相应变量的前面,如下所示 :

var age = 29;
--age;

  这样,age 变量的值就减少为 28 ( 从 29 中减去了 1 ) 。
        执行 前置递增 和 递减 操作时,变量的值都是在语句被求值以前改变的。

( 在计算机科学领域,这种情况通常被称作 副效应 。) 请看下面这个例子。

var age = 29;
var anotherAge = --age + 2;

alert(age);         // 输出 28 
alert(anotherAge);  // 输出 30

    这个例子中变量 anotherAge 的 初始值 等于变量 age 的值 前置递减 之后加 2。

由于先执行了减法操作,age 的值变成了 28,所以再加上 2 的结果就是 30。
        由于 前置递增 和 递减 操作 与执行语句的优先级相等,因此整个语句会从左至右被求值。再看一个例子 :

var num1 = 2; 
var num2 = 20;
var num3 = --num1 + num2;     // 等于 21 
var num4 = num1 + num2;       // 等于 21 

    在这里,num3 之所以等于 21 是因为 num1 先减去了 1 才与 num2 相加。
而变量 num4 也等于 21 是因为相应的加法操作使用了 num1 减去 1 之后的值。
        后置型 递增 和 递减 操作符 的语法不变 ( 仍然分别是 ++ 和 -- ) ,只不过要放在变量的后面而不是前面。后置 递增 和 递减 与 前置 递增 和 递减 有一个非常重要的区别,即 递增 和 递减 操作是在包含它们的语句被求值之后才执行的。这个区别在某些情况下不是什么问题,例如 :

var age = 29;
age++;

    把 递增 操作符 放在变量后面并不会改变语句的结果,因为 递增 是这条语句的唯一操作。但是,当语句中还包含其他操作时,上述区别就会非常明显了。请看下面的例子 :

var num1 = 2;
var num2 = 20;
var num3 = num1-- + num2;     // 等于 22 
var num4 = num1 + num2;       // 等于 21 

    这里仅仅将 前置 递减 改成了 后置 递减,就立即可以看到差别。在前面使用 前置递减 的例子中,num3 和 num4 最后都等于 21。而在这个例子中,num3 等于 22,num4 等于 21。差别的根源在于,这里在计算 num3 时使用了 num1 的原始值 ( 2 ) 完成了加法计算,而 num4 则使用了递减后的值 ( 1 ) 。
        所有这 4 个 操作符 对 任何值 都适用,也就是它们不仅适用于 整数 ,还可以用于 字符串、布尔值、浮点数值 和 对象。在应用于不同的值时,递增 和 递减 操作符 遵循下列规则。

    1、在应用于一个 包含有效数字字符的字符串时,先将其转换为数字值,再执行 加减 1 的    操作。字符串 变量变成 数值 变量。
    2、在应用于一个 不包含有效数字字符的字符串时,将变量的值设置为 NaN ( 第 4 章将详    细讨论 ) 。 字符串 变量变成 数值 变量。
    3、在应用于 布尔值 false 时,先将其转换为 0 再执行 加减 1 的 操作。

  布尔值 变量 变成 数值 变量。

    4、在应用于 布尔值 true 时,先将其转换为 1 再执行 加减 1 的操作。

  布尔值 变量 变成 数值 变量。

    5、在应用于 浮点 数值 时,执行 加减 1 的操作。
    6、在应用于 对象 时,先调用对象的 valueof() 方法 ( 第 5 章将详细讨论 ) 以取得一个可    供操作的值。然后对该值应用前述规则。如果结果是 NaN ,则在调用 toString()方法 后再    应用前述规则。对象 变量 变成 数值 变量 。

以下示例展示了上面的一些规则 :

var sl = "2"; 
var s2 = "z"; 
var b = false; 
var f = 1.1; 
var o = {
  valueOf: function() {
    return -1;
  }
};

s1++;     // 值变成数值 3 
s2++;     // 值变成 NaN 
b++;      // 值变成数值 1 
f--;      // 值变成 0.10000000000000009 ( 由于浮点舍入错误所致 ) 
o--;      // 值变成数值 -2 

2、一元加和减操作符

        绝大多数开发人员对 一元加 和 减 操作符 都不会陌生,而且这两个 ECMAScript 操作符的作用与数学上讲的完全一样。一元加 操作符以一个加号 ( + ) 表示,放在数值前面,对数值不会产生任何影响如下面的例子所示 :

var num = 25;
num = +num;     // 仍然是 25 

    不过,在对非数值应用 一元加 操作符时,该操作符会像 Number() 转型函数 一样对这个值执行转换。换句话说,布尔值 false 和 true 将被转换为 0 和 1 ,字符串值 会被按照一组特殊的规则进行解析,而 对象 是先调用它们的 valueOf() 和 (或) toString() 方法 ,再转换得到的值。
        下面的例子展示了对不同数据类型应用一元加操作符的结果 :

var s1 ="01"; 
var s2 = "1.1"; 
var s3 = "z"; 
var b = false; 
var f = 1.1; 
var o = {
  valueOf: function() {
    return -1;
  }
};

s1 = +s1;     // 值变成数值 1 
s2 = +s2;     // 值变成数值 1.1 
s3 = +s3;     // 值变成 NaN 
b = +b;       // 值变成数值 0 
f = +f;       // 值未变,仍然是 1.1 
o = +o;       // 值变成数值 -1 

    一元减 操作符 主要用于表示 负数 ,例如将 1 转换成 -1 。

下面的例子演示了这个简单的转换过程 :

var num = 25;
num = -num;     // 变成了 -25

    在将 一元减 操作符 应用于数值时,该值会变成 负数 ( 如上面的例子所示 ) 。

而当应用于 非数值 时,一元减 操作符 遵循与 一元加 操作符 相同的规则,最后再将得到的数值转换为 负数 ,如下面的例子所示 :

var s1 ="01"; 
var s2 = "1.1"; 
var s3 = "z"; 
var b = false; 
var f = 1.1; 
var o = {
  valueOf: function() {
    return -1;
  }
};

s1 = -s1;     // 值变成数值 -1 
s2 = -s2;     // 值变成数值 -1.1 
s3 = -s3;     // 值变成 NaN 
b = -b;       // 值变成数值 0 
f = -f;       // 值未变,仍然是 -1.1 
o = -o;       // 值变成数值 1 

    一元加 和 减 操作符 主要用于基本的算术运算,也可以像前面示例所展示的一样用于 转换数据类型。

3.5.2  位操作符

        位操作符 用于在最基本的层次上,即按 内存 中表示 数值 的 位 来操作 数值 。ECMAScript 中的所有数值都以 IEEE-754 64 位格式存储,但 位操作符 并不直接操作 64 位的值。而是先将 64 位的值转换成 32 位的 整数,然后执行操作,最后再将结果转换回 64 位。对于开发人员来说,由于 64 位 存储格式是透明的,因此整个过程就像是只存在 32 位的整数一样。
        对于有符号的整数,32 位中的前 31 位用于表示整数的值。第 32 位用于表示 数值 的 符号 :0 表示 正数 ,1 表示 负数 。这个 表示符号 的 位 叫做 符号位 ,符号位 的 值 决定了其他位数值的格式。其中,正数以纯二进制格式存储,31 位中的每一位都表示 2 的 幂 。

第一位 ( 叫做位 0 ) 表示 2° ,第二位表示 2¹ ,以此类推。没有用到的位以 0 填充,即忽略不计。例如,数值 18 的 二进制 表示是 00000000000000000000000000010010 ,或者更简洁的 10010 。这是 5 个 有效位 ,这 5 位本身就决定了实际的值 ( 如图 3-1 所示) 。

         负数 同样以 二进制码 存储,但使用的格式是 二进制补码。计算一个数值的 二进制补码,需要经过下列 3 个 步骤 :

    (1) 求这个数值 绝对值 的 二进制码 ( 例如,要求 -18 的 二进制补码,先求 18 的 二进制码 ) ;

    (2) 求二进制反码,即将 0 替换为 1 ,将 1 替换为 0 ;
    (3) 得到的二进制反码加 1 。

要根据这 3 个步骤求得 -18 的 二进制码,首先就要求得 18 的 二进制码,即 :

    0000 0000 0000 0000 0000 0000 0001 0010

    然后,求其 二进制 反码,即 0 和 1 互换 :
    1111 1111 1111 1111 1111 1111 1110 1101

    最后,二进制 反码 加 1 :
    1111 1111 1111 1111 1111 1111 1110 1101
                                          1
------------------------------------------------
    1111 1111 1111 1111 1111 1111 1110 1110

    这样,就求得了 -18 的 二进制 表示,即 11111111111111111111111111101110。

要注意的是,在处理有符号整数时,是不能访问 位 31 的 。
        ECMAScript 会尽力向我们隐藏所有这些信息。换句话说,在以 二进制 字符串 形式输出一个负数时,我们看到的只是这个 负数 绝对值 的 二进制码前面加上了一个 负号。如下面的例子所示 :

var num = -18;
alert(num.toString(2));     // "-10010" 

    要把数值 -18 转换成 二进制 字符串 时,得到的结果是 "-10010" 。

这说明转换过程理解了 二进制 补码 并将其以更合乎逻辑的形式展示了出来。
        默认情况下,ECMAScript 中的所有整数都是有符号整数。不过,当然也存在无符号整数。对于无符号整数来说,第 32 位不再表示符号,因为无符号整数只能是正数。而且,无符号整数的值可以更大,因为多出的一位不再表示符号,可以用来表示数值。
        在 ECMAScript 中,当对数值应用 位操作符 时,后台会发生如下转换过程 :

64 位的数值被转换成 32 位数值,然后执行 位操作,最后再将 32 位的结果转换回 64 位数值。这样,表面上看起来就好像是在操作 32 位数值,就跟在其他语言中以类似方式执行二进制操作一样。但这个转换过程也导致了一个严重的 副效应 ,即在对 特殊 的 NaN 和 Infinity 值应用 位操作 时,这两个值都会被当成 0 来处理。
        如果对 非数值 应用 位操作符,会先使用 Number() 函数将该值转换为一个数值 ( 自动完成 ) ,然后再应用位操作。得到的结果将是一个数值。


1、按位非(NOT)

        按位非 操作符 由 一个 波浪线 ( ~ ) 表示 ,执行 按位非 的 结果 就是 返回 数值 的     

反码 。按位非 是 ECMAScript 操作符 中少数几个与 二进制 计算有关的 操作符 之一。

下面看一个例子 :

var numl = 25;       // 二进制 00000000000000000000000000011001
var num2 = ~num1;    // 二进制 11111111111111111111111111100110

alert(num2);         // -26 

        这里,对 25 执行 按位非 操作,结果得到了 -26 。这也验证了 按位非 操作的本质 :

操作数 的 负值 减 1 。因此,下面的代码也能得到相同的结果 :

var num1 = 25;
var num2 = -num1 - 1;
alert(num2);             // "-26" 

    虽然以上代码也能返回同样的结果,但由于 按位非 是在数值表示的最底层执行操作,

因此速度更快 。


2、按位与(AND)

        按位与 操作符 由一个 和号 字符 ( & ) 表示,它有两个操作符数。

从本质上讲,按位与 操作 就是将两个数值的每一位对齐,然后根据下表中的规则,

对相同位置上的两个数执行 AND 操作 :

第一个数值的位   第二个数值的位   结 果 
     1              1          1
     1              0          0
     0              1          0
     0              0          0   

    简而言之,按位与 操作 只在 两个数值 的 对应位 都是 1 时才返回 1 ,任何一位是 0 ,
结果都是 0 。下面看一个对 25 和 3 执行 按位与 操作的例子 :

var result = 25 & 3;
alert(result);      // 1

可见,对 25 和 3 执行 按位与 操作的结果是 1 。为什么呢 ?请看其底层操作 :

 25 = 0000 0000 0000 0000 0000 0000 0001 1001
  3 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
AND = 0000 0000 0000 0000 0000 0000 0000 0001

    原来,25 和 3 的 二进制码 对应位 上只有一位 同时是 1 ,而其他位的结果自然都是 0 ,因此 最终结果等于 1 。


3、按位或(OR)

        按位或 操作符 由 一个 竖线 符号 ( | ) 表示 ,同样也有两个 操作数。

按位或 操作 遵循下面这个真值表。

第一个数值的位   第二个数值的位   结 果 
     1              1          1
     1              0          1
     0              1          1
     0              0          0

    由此可见,按位或 操作 在有一个位是 1 的情况下就返回 1 ,而只有在 两个位 都是 0 的 情况下才返回 0 。如果在前面 按位与 的 例子中对 25 和 3 执行 按位或 操作,则代码如下所示 :

var result = 25 | 3;
alert(result);         // 27

25 与 3 按位或 的 结果是 27 :

25 = 0000 0000 0000 0000 0000 0000 0001 1001
3  = 0000 0000 0000 0000 0000 0000 0000 0011
--------------------------------------------
OR = 0000 0000 0000 0000 0000 0000 0001 1011

    可以看到,有四位包含 1 ,因此可以把每个 1 直接放到结果中。

二进制码 11011 等于 十进制 值 27。


4、按位异或(XOR)

        按位异或 操作符 由 一个 插入 符号 ( ^ ) 表示 ,也有两个操作数。以下是 按位异或 的 真值表 。

第一个数值的位   第二个数值的位   结 果
     1              1          0
     1              0          1
     0              1          1
     0              0          0

    按位异或 与 按位或 的不同之处在于,这个操作在两个数值对应位上只有一个 1 时才返回1,如果对应的两位都是 1 或 都是 0,则返回 0。
        对 25 和 3 执行 按位异或 操作 的代码如下所示 :

var result = 25 ^ 3;
alert(result);          // 26 

    25 与 3 按位异或 的 结果 是 26 ,其 底层操作 如下所示 :

 25 = 0000 0000 0000 0000 0000 0000 0001 1001
 3  = 0000 0000 0000 0000 0000 0000 0000 0011
----------------- -------------- ------------
XOR = 0000 0000 0000 0000 0000 0000 0001 1010

    这两个数值都包含 4 个 1 ,但 第一位 上则都是 1 ,因此结果的 第一位 变成了 0 。

而其他位上的 1 在另一个数值中都没有对应的 1 ,可以直接放到结果中。二进制码 11010

等于 十进制值 26 ( 注意这个结果 比 执行 按位或 时 小 1 ) 。


5、左移

        左移 操作符 由 两个 小于号 ( << ) 表示 ,这个 操作符 会将 数值 的所有位 向 左 移动 指定的 位数 。例如,如果将数值 2 ( 二进制码 为 10 ) 向 左 移动 5 位 ,结果 就是 64 ( 二进制 码 为 1000000 ) ,代码如下所示 :

var oldValue = 2;              // 等于 二进制 的 10 
var newValue = oldValue << 5;  // 等于 二进制 的 1000000,十进制 的 64 

    注意,在向 左 移位后,原数值的右侧多出了 5 个空位。左移 操作 会以 0 来填充这些空位,以便得到的结果是一个完整的 32 位 二进制数 ( 见图 3-2 ) 。

    注意,左移不会影响操作数的符号位。换句话说,如果将 -2 向左移动 5 位 ,结果将是 -64 ,而非 64 。


6、有符号的右移

        有符号 的 右移 操作符 由 两个 大于号 ( >> ) 表示 ,这个 操作符 会将 数值 向 右 移动,但 保留 符号位 ( 即 正负号标记 ) 。有 符号 的 右移 操作 与 左移 操作 恰好 相反,即 如果将 64 向 右 移动 5 位 ,结果将变回 2 :

var oldValue = 64;             // 等于二进制的 1000000 
var newValue = oldValue >> 5;  // 等于二进制的 10 ,即十进制的2 

    同样,在移位过程中,原数值中也会出现空位。只不过这次的空位出现在原数值的左侧、符号位的右侧 ( 见图 3-3 ) 。而此时 ECMAScript 会用 符号位 的 值 来填充所有空位,以便得到一个完整的值。


7、无符号右移

        无符号 右移 操作符 由 3 个 大于号 ( >>> ) 表示 ,这个 操作符 会将 数值 的 所有 32 位都向 右 移动。对 正数 来说,无符号 右移的 结果与有符号 右移 相同。仍以前面有 符号 右移 的 代码 为例,如果将 64 无符号 右移 5 位 ,结果仍然还是 2 :

var oldValue = 64;                 // 等于二进制的 1000000 
var newValue = oldValue >>> 5;     // 等于二进制的 10 ,即十进制的 2 

    但是对 负数 来说,情况就不一样了。首先,无符号 右移 是以 0 来填充空位,而不是像有符号 右移 那样以 符号位 的 值 来填充 空位。所以,对 正数 的 无符号 右移 与 有符号 右移 结果相同,但对 负数 的 结果 就不一样了。其次,无符号 右移 操作符 会把 负数 的 二进制码 当成 正数 的 二进制码 。而且,由于 负数 以其 绝对值 的 二进制补码 形式 表示 ,因此 就会导致 无符号 右移 后的 结果 非常之 大,如下面的例子所示 :

var oldValue = -64;                // 等于二进制的 11111111111111111111111111000000 
var newValue = oldValue >>> 5;     // 等于十进制的 134217726 

    这里,当对 -64 执行 无符号 右移 5 位的操作后,得到的结果是 134217726。之所以结果如此之大。是因为 -64 的 二进制码 为 11111111111111111111111111000000,而且 无符号 右移 操作 会把这个 二进制码 当成 正数 的 二进制码 ,换算成 十进制 就是 4294967232 。如果把这个值 右移 5 位 ,结果就变成了 00000111111111111111111111111110,即 十进制 的 134217726。

3.5.3  布尔操作符

        在一门编程语言中,布尔操作符 的 重要性 堪比 相等操作符 。如果没有 测试 两个值 关系 的 能力,那么诸如 if...else 和 循环 之类的 语句 就不会有用武之地了。布尔操作符 一共 有 3 个 :非(NOT)、与(AND)和 或(OR)。


1、逻辑非

        逻辑非 操作符 由 一个 叹号( ! )表示,可以应用于 ECMAScript 中的任何值。

无论这个值是什么数据类型,这个 操作符 都会返回一个 布尔值 。逻辑非 操作符 首先 会 将 它的 操作数 转换为一个 布尔值 ,然后再对其 求。也就是说,逻辑非 操作符 遵循 下列 规则 :

  • 1、如果 操作数 是一个 对象 ,返回 false ;
  • 2、如果 操作数 是一个 空字符串 ,返回 true ;
  • 3、如果 操作数 是一个 非空字符串 ,返回 false ;
  • 4、如果 操作数 是 数值 0,返回 true ;
  • 5、如果 操作数 是 任意非 0 数值 ( 包括 Infinity ) ,返回 false ;
  • 6、如果 操作数 是 null ,返回 true ;
  • 7、如果 操作数 是 NaN ,返回 true ;
  • 8、如果 操作数 是 undefined ,返回 true。

下面几个例子展示了应用上述规则的结果 :

alert(!false);      // true 
alert(!"blue");     // false 
alert(!0);          // true 
alert(!NaN);        // true 
alert(!"");         // true 
alert(!12345);      // false 

    逻辑非 操作符 也可以用于将一个值转换为与其对应的 布尔值 。而 同时使用两个 逻辑非 操作符 ,实际上就会 模拟 Boolean() 转型函数 的 行为。其中,第一个 逻辑非 操作 会 基于无论什么操作数返回一个 布尔值 ,而 第二个 逻辑非 操作 则对该 布尔值 求反 ,于是 就得到了这个值真正对应的 布尔值 。当然,最终结果与对这个值使用 Boolean() 函数 相同,如下面的例子所示 :

alert(!!"blue");     // true 
alert(!!0);          // false 
alert(!!NaN);        // false 
alert(!!"");         // false 
alert(!!12345)       // true 

2、逻辑与

        逻辑与 操作符 由 两个 和号( &&表示,有两个 操作数,如下面的例子所示 :

var result = true && false;

    逻辑与 的 真值表 如下 :

第一个操作数     第二个操作数      结果 
   true           true         true 
   true           false        false 
   false          true         false 
   false          false        false 

    逻辑与 操作 可以应用于 任何类型 的 操作数 ,而不仅仅是 布尔值 。在有一个 操作数 不是 布尔值 的情况下,逻辑与 操作 就不一定 返回 布尔值 ;此时,它遵循下列规则 :

  • 1、如果第一个 操作数 是 对象 ,则返回第二个 操作数;
  • 2、如果第二个 操作数 是 对象 ,

        则只有在第一个操作数的求值结果为 true 的情况下才会返回该对象;

  • 3、如果两个 操作数 都是 对象 ,则返回第二个 操作数;
  • 4、如果第一个 操作数 是 null ,则返回 null ;
  • 5、如果第一个 操作数 是 NaN ,则返回 NaN ;
  • 6、如果第一个 操作数 是 undefined ,则返回 undefined。

    逻辑与 操作 属于 短路操作 ,即如果第一个操作数能够决定结果,那么就不会再对第二个操作数求值。对于 逻辑与 操作 而言,如果第一个操作数是 false,则无论第二个 操作数 是什么值,结果都不再可能是 true 了。来看下面的例子 :

var found = true;
var result = (found && someUndefinedVariable);  // 这里会发生错误 
alert(result);     // 这一行不会执行

    在上面的代码中,当执行 逻辑与 操作 时会发生错误,因为变量 someUndefinedVariable 没有声明。由于变量 found 的值是 true ,所以 逻辑与 操作符 会继续对变量 someUndefinedVariable 求值。但 someUndefinedVariable 尚未定义,因此就会导致错误。这说明不能在 逻辑与 操作 中 使用 未定义 的 值。如果像下面这个例中一样,将 found 的值设置为 false,就不会发生错误了 :

var found = false;
var result = (found && someUndefinedVariable); // 不会发生错误
alert(result);        // 会执行 ("false")

    在这个例子中,警告框 会 显示 出来。无论变量 someUndefinedVariable 有没有定义,也永远不会对它求值,因为第一个 操作数 的值是 false 。而这也就意味着 逻辑与 操作 的 结果 必定是 false,根本用不着再对 && 右侧 的 操作数 求值了。在使用 逻辑与 操作符 时要始终铭记它是一个 短路操作符


3、逻辑或

        逻辑或 操作符 由 两个 竖线 符号 ( || )表示 ,有 两个 操作数 ,如下面的例子所示:

var result = true || false;

    逻辑或的真值表如下 :

第一个操作数        第二个操作数        结果
   true              true           true
   true              false          true
   false             true           true
   false             false          false

    与 逻辑与 操作 相似,如果有一个 操作数 不是 布尔值 ,逻辑或 也不一定返回 布尔值 ;此时,它遵循下列规则 :

  • 1、如果第一个 操作数 是 对象 ,则返回第一个 操作数 ;
  • 2、如果第一个 操作数 的求值结果为 false,则返回第二个 操作数 ;
  • 3、如果两个 操作数 都是 对象,则返回第一个 操作数 ;
  • 4、如果两个 操作数 都是 null,则返回 null ;
  • 5、如果两个 操作数 都是 NaN,则返回 NaN ;
  • 6、如果两个 操作数 都是 undefined,则返回 undefined。

        与 逻辑与 操作符 相似,逻辑或 操作符 也是 短路操作符。也就是说,如果第一个操作数的求值结果为 true,就不会对第二个操作数求值了。下面看一个例子 :

var found = true;
var result = (found || someUndefinedVariable);  // 不会发生错误 
alert(result};         // 会执行 ("true")

    这个例子跟前面的例子一样,变量 someUndefinedVariable 也没有定义。但是,由于变量 found 的值是t rue,而变量 someUndefinedVariable 永远不会被求值,因此结果就会输出 "true" 。如果像下面这个例子一样,把 found 的值改为 false,就会导致错误 :

var found = false;
var result = (found || someUndefinedVariable);  // 这里会发生错误 
alert(result);         // 这一行不会执行

    我们可以利用 逻辑或 的这一行为来避免为变量赋 null 或 undefined 值。例如 :

var myObject = preferredObject || backupObject;

    在这个例子中,变量 myObject 将被赋予等号后面两个值中的一个。变量 preferredobject 中包含优先赋给变量 myObject 的值,变量 backupObject 负责在 preferredObject 中不包含有效值 的情况下提供 后备值。如果 preferredObject 的值不是 null ,那么它的值将被赋给 myObject ;如果是 null ,则将 backupObject 的值赋给 myObject 。ECMAScript 程序的赋值语句经常会使用这种模式,本书也将采用这种模式。

3.5.4  乘性操作符

        ECMAScript 定义了 3 个 乘性 操作符 :乘法、除法 和 求模

这些操作符与 Java、C 或者 Perl 中的相应 操作符 用途类似,只不过在操作数为非数值的情况下会执行 自动 的 类型转换。如果参与 乘性计算 的某个 操作数 不是数值,后台会先使用  Number() 转型函数 将其转换为数值。也就是说,空字符串 将被当作 0 ,布尓值 true 将被当作 1 。


1、乘法

        乘法 操作符 由 一个 星号 ( * )表示 ,用于 计算 两个数值 的 乘积 。

其语法类似于 C,如下面的例子所示 :

var result = 34 * 56;

    在处理 特殊值 的情况下,乘法 操作符 遵循 下列特殊的 规则 :

  • 1、如果 操作数 都是 数值 ,执行常规的 乘法 计算,
  • 即两个 正数 或 两个 负数 相乘的结果还是 正数,而如果只有一个 操作数 有符号,那么结果就是 负数 。如果 乘积 超过了 ECMAScript 数值的表示范围,则返回 Infinity 或 -Infinity;
  • 2、如果 有一个 操作数 是 NaN ,则结果是 NaN ;
  • 3、如果 是 Infinity 与 0 相乘,则结果是 NaN ;
  • 4、如果 是 Infinity 与 非 0 数值 相乘,则结果是 Infinity 或 -Infinity,取决于 有符号 操作数 的 符号;
  • 5、如果 是 Infinity 与 Infinity 相乘,则结果是 Infinity ;
  • 6、如果 有一个 操作数 不是 数值 ,则在后台调用 Number() 将其转换为 数值 ,然后再应用上面的规则。

2、除法

        除法 操作符 由 一个 斜线 符号 ( / )表示 ,执行第二个 操作数 除 第一个 操作数 的 计算,如下面的例子所示 :

var result = 66 /11;

    与 乘法 操作符 类似,除法 操作符 对特殊的值也有特殊的处理规则。这些规则如下 :
  1、如果 操作数 都是 数值 ,执行常规的 除法 计算,

即 两个 正数 或 两个 负数 相除的结果还是 正数 ,而如果只有一个 操作数 有 符号,那么结果就是 负数。如果 商 超过了 ECMAScript 数值的表示范围,则返回 Infinity 或 -Infinity ;
  2、如果 有一个 操作数 是 NaN ,则结果是 NaN ;
  3、如果 是 Infinity 被 Infinity 除 ,则结果是 NaN ;

  4、如果 是 零 被 零 除 ,则 结果 是 NaN ;
  5、如果 是 非零 的 有限数被 零 除 ,则结果是 Infinity 或 -Infinity ,取决于 有符号 操作数 的符号 ;

  6、如果 是 Infinity 被任何 非零数值 除 ,则结果是 Infinity 或 -Infinity ,取决于 有符号 操作数的 符号 ;

  7、如果 有一个 操作数 不是数值,则在后台调用 Number() 将其转换为 数值,然后再应用上面的规则。


3、求模

        求模(余数)操作符 由 一个 百分号( % )表示,用法如下 :

var result = 26 % 5;     // 等于 1 

    与另外两个 乘性 操作符 类似,求模 操作符 会遵循下列特殊规则来处理特殊的值 :

  1、如果 操作数 都是 数值,执行常规的 除法 计算,返回除得的 余数 ;

  2、如果 被除数 是 无穷大值 而 除数 是 有限大 的 数值 ,则结果是 NaN ;

  3、如果 被除数 是 有限大 的 数值 而 除数 是 零 ,则结果是 NaN ;

  4、如果 是 Infinity 被 Infinity 除 ,则 结果 是 NaN ;
  5、如果 被除数 是 有限大 的 数值 而 除数 是 无穷大 的 数值 ,则 结果 是 被除数 ;

  6、如果 被除数 是 零 ,则 结果 是 零 ;

  7、如果 有一个 操作数 不是 数值 ,则在后台调用 Number() 将其转换为 数值 ,然后再应用上面的规则。

3.5.5  加性操作符

        加法 和 减法 这两个 加性 操作符 应该说是编程语言中 最简单 的 算术 操作符 了 。

但是在 ECMAScript 中 两个 操作符 却都有一系列的 特殊行为。与 乘性 操作符 类似,

加性 操作符 也会在 后台转换 不同的 数据类型 。然而,对于 加性 操作符 而言,相应的 转换规则 还稍微有点复杂。


1、加法

        加法 操作符 ( + )的 用法 如下所示 :

var result = 1 + 2;

    如果 两个 操作符 都是 数值 ,执行常规的 加法 计算 ,然后根据下列规则返回结果 :

  • 1、如果 有一个 操作数 是 NaN ,则 结果 是 NaN ;
  • 2、如果 是 Infinity 加 Infinity ,则 结果 是 Infinity ;
  • 3、如果 是 -Infinity 加 -Infinity ,则 结果 是 -Infinity ;
  • 4、如果 是 Infinity 加 -Infinity ,则 结果 是 NaN ;
  • 5、如果 是 +0 加 +0 ,则 结果 是 +0 ;
  • 6、如果 是 -0 加 -0 ,则 结果 是 -0 ;
  • 7、如果 是 +0 加 -0 ,则 结果 是 +0 。

不过,如果有一个 操作数 是 字符串 ,那么就要应用如下规则 :

  • 1、如果 两个 操作数 都是 字符串 ,则将第二个操作数与第一个操作数 拼接 起来
  • 2、如果 只有一个 操作数 是 字符串 ,则将另一个操作数转换为字符串,然后再将两个字符串 拼接 起来 。

    如果 有一个 操作数 是 对象 、数值 或 布尔值 ,则 调用 它们的 toString() 方法 取得 相应的 字符串值 。然后再应用前面关于 字符串 的 规则 。对于 undefined 和 null ,则分别调用 String()函数 并取得 字符串 "undefined" 和 "null" 。
        下面来举几个例子 :

var result1 = 5 + 5;       // 两个数值相加 
alert(result1);            // 10
var result2 = 5 + "5";     // 一个数值和一个宇符串相加 
alert(result2);            // "55" 

    以上代码演示了 加法 操作符 在 两种模式下的差别。第一行代码演示了正常的情况,即 5+5 等于 10 ( 数值 ) 。但是,如果将一个 操作数 改为 字符串 "5" ,结果 就变成了 "55" ( 字符串值 ) ,因为第一个 操作数 也被转换成了 "5" 。
        忽视 加法 操作中的 数据类型 是 ECMAScript 编程中最常见的一个错误。

再来看一个例子 :

var num1 = 5; 
var num2 = 10;
var message = "The sum of 5 and 10 is " + num1 + num2;
alert(message);         // "The sum of 5 and 10 is 510" 

    在这个例子中,变量 message 的值是执行两个 加法 操作 之后的 结果。有人可能以为最后得到的 字符串 是 "The sum of 5 and 10 is15",但实际的结果却是 "The sum of 5 and 10 is 510"。之所以会这样,是因为每个 加法 操作 是 独立 执行 的。第一个 加法操作 将一个 字符串 和 一个数值 ( 5 ) 拼接了起来,结果是一个 字符串 。而第二个 加法操作 又用这个 字符串 去加另一个 数值 ( 10 ) ,当然也会得到一个 字符串 。如果想先对 数值 执行 算术计算,然后再将结果与 字符串 拼接起来,应该像下面这样使用圆括号 :

var num1 = 5; 
var num2 = 10;
var message = "The sum of 5 and 10 is " + (num1 + num2);
alert(message);             // "The sum of 5 and 10 is 15" 

    在这个例子中,一对 圆括号 把 两个数值变量 括在了一起,这样就会告诉 解析器 先 计算其 结果,然后再将 结果 与 字符串 拼接 起来。因此,就得到了结果 "The sum of 5 and 10 is 15"。


2、减法

        减法 操作符( - )是 另一个 极为 常用 的 操作符 ,其 用法 如下所示 :

var result = 2 - 1;

    与 加法 操作符 类似,ECMAScript 中的 减法 操作符 在处理各种数据类型转换时,同样需要遵循一些 特殊规则 ,如下所示 :

  • 1、如果 两个 操作符 都是 数值 ,则执行常规的 算术 减法 操作 并返回 结果;
  • 2、如果 有一个 操作数 是 NaN ,则 结果 是 NaN ;
  • 3、如果 是 Infinity 减 Infinity ,则 结果 是 NaN ;
  • 4、如果 是 -Infinity 减 -Infinity ,则 结果 是 NaN ;
  • 5、如果 是 Infinity 减 -Infinity ,则 结果 是 Infinity ;
  • 6、如果 是 -Infinity 减 Infinity ,则 结果 是 -Infinity ;
  • 7、如果 是 +0 减 +0 ,则 结果 是 +0 ;
  • 8、如果 是 -0 减 +0 ,则 结果 是 -0 ;
  • 9、如果 是 -0 减 -0 ,则 结果 是 +0 ;

        10、如果 有一个 操作数 是 字符串、布尔值、null 或 undefined,则先在后台调用 Number()函数 将其转换为 数值 ,然后再根据前面的规则执行 减法 计算。如果转换的结果是 NaN ,则 减法 的结果就是 NaN ;
        11、如果 有一个 操作数 是 对象 ,则调用对象的 valueOf()方法 以取得表示该对象的数值。如果得到的值是 NaN ,则 减法 的结果就是 NaN。如果对象没有 valueOf()方法,则调用其 toString()方法 并将得到的 字符串 转换为 数值 。

    下面几个例子展示了上面的规则 :

var result1 = 5 - true;     // 4,因为 true 被转换成了 1 
var result2 = NaN - 1;      // NaN 
var result3 = 5 - 3;        // 2 
var result4 = 5 - "";       // 5,因为 "" 被转换成了 0 
var result5 = 5 - "2";      // 3,因为 "2" 被转换成了 2 
var result6 = 5 - null;     // 5,因为 null 被转换成了 0

3.5.6  关系操作符

        小于 ( < ) 、大于 ( > ) 、小于等于 ( <= ) 和 大于等于 ( >= ) 这几个 关系 操作符 用于 对 两个值 进行 比较 ,比较 的 规则 与 我们 在数学课上所学的一样。这几个 操作符 都 返回 一个 布尔值 ,如下面的例子所示 :

var result1 = 5 > 3;  // true 
var result2 = 5 < 3;  // false 

    与 ECMAScript 中的其他 操作符 一样,当 关系 操作符 的 操作数 使用了 非数值 时,也要进行 数据转换 或 完成某些奇怪的操作。以下就是相应的规则。
  1、如果 两个 操作数 都是 数值,则执行 数值比较。
  2、如果 两个 操作数 都是 字符串,则比较两个 字符串 对应的 字符 编码值。
  3、如果 一个 操作数 是 数值,则将 另一个 操作数 转换为一个 数值,然后执行 数值比较。
  4、如果 一个 操作数 是 对象,则调用这个对象的 valueOf()方法 ,用得到的结果按照前面的规则执行比较。如果对象没有 valueOf() 方法,则调用 toString()方法,并用得到的结果根据前面的规则执行比较。

  5、如果 一个 操作数 是 布尔值,则先将其转换为 数值,然后再执行比较。
        在使用 关系 操作符 比较 两个 字符串 时,会执行一种奇怪的操作。很多人都会认为,在比较 字符串 值时,小于 的意思是 “在字母表中的位置靠前”,而 大于 则意味着 “在字母表中的位置靠后”,但实际上完全不是那么回事。在比较字符串时,实际比较的是 两个 字符串 中 对应 位置 的 每个 字符 的 字符 编码值 。经这么一番比较之后,再返回一个 布尔值。由于 大写 字母 的 字符编码 全部 小于 小写 字母 的 字符编码 ,因此我们就会看到如下所示的奇怪现象 :

var result = "Brick" < "alphabet";   // true

    在这个例子中,字符串 "Brick" 被认为 小于 字符串 "alphabet" 。原因是 字母 B 的 字符 编码 为 66,而 字母 a 的 字符 编码 是 97。如果要真正按字母表顺序比较字符串,就必须把两个 操作数 转换为 相同 的 大小写 形式 ( 全部大写 或 全部小写 ),然后再执行比较,如下所示 :

var result= "Brick".toLowerCase() < "alphabet".toLowerCase();  // false

    通过将 两个 操作数 都 转换 为 小写 形式,就可以得出 "alphabet" 按 字母表 顺序 排在 "Brick" 之前的 正确判断了。
        另一种奇怪的现象发生在 比较两个 数字 字符串 的情况下,比如下面这个例子 :

var result = "23" < "3"  // true 

    确实,当比较 字符串 "23" 是否 小于 "3" 时,结果居然是 true。这是因为两个 操作数 都是 字符串 。而 字符串 比较的是 字符 编码 ( "2" 的 字符 编码是 50,而 "3" 的 字符 编码 是 51 ) 。 不过,如果像下面例子中一样,将一个 操作数 改为 数值,比较的结果就正常了 :

var result = "23" < 3;  // false 

    此时,字符串 "23" 会被 转换 成 数值 23,然后再与 3 进行 比较,因此就会得到合理的结果。在比较 数值 和 字符串 时,字符串 都会被 转换 成 数值,然后再以 数值 方式与另一个数值 比较。当然,这个规则对前面的例子是适用的。可是,如果那个 字符串 不能被转换成一个合理的数值呢 ?比如 :

var result = "a" < 3;  // false,因为"a"被转换成了 NaN 

    由于字母 "a" 不能转换成合理的 数值,因此就被转换成了 NaN。根据规则,任何 操作数 与 NaN 进行 关系 比较,结果都是 false。于是,就出现了下面这个有意思的现象 :

var result1 = NaN < 3;   // false 
var result2 = NaN >= 3;  // false 

    按照常理,如果一个值不小于另一个值,则一定是大于或等于那个值。然而,在与 NaN 进行比较时,这两个比较操作的结果都返回了 false。

3.5.7  相等操作符

        确定 两个 变量 是否 相等 是 编程中的一个非常重要的操作。在比较 字符串、数值 和 布尔值 的 相等性 时,问题还比较简单。但在涉及到 对象 的 比较 时,问题就变得复杂了。最早的 ECMAScript 中的 相等 和 不等 操作符 会在执行比较之前,先将 对象 转换成相似的类型。后来,有人提出了这种 转换 到底 是否 合理 的 质疑。最后,ECMAScript 的解决方案就是提供 两组 操作符 :相等 和 不相等 —— 先转换再比较,全等 和 不全等 —— 仅比较而不转换。


1、相等和不相等

        ECMAScript 中的 相等 操作符 由 两个 等于号 ( == ) 表示,如果 两个 操作数 相等 ,则 返回 true 。而 不相等 操作符 由 叹号 后跟 等于号 ( != ) 表示,如果 两个 操作数 不相等,则返回 true 。这 两个 操作符 都会 先转换 操作数 ( 通常称为 强制 转型 ) ,然后再 比较 它们 的 相等性 。
        在转换不同的 数据类型 时,相等 和 不相等 操作符 遵循 下列 基本规则 :
1、如果 有一个 操作数 是 布尔值 ,则在比较 相等性 之前先将其转换为 数值 —— false 转换为 0,而 true 转换为 1;

2、如果 一个 操作数 是 字符串 ,另一个 操作数 是 数值 ,在比较相等性之前先将 字符串 转换 为 数值;

3、如果 一个 操作数 是 对象 ,另一个 操作数 不是,则调用对象的 valueOf() 方法,用得到的基本类型值按照前面的规则进行比较;
        这两个 操作符 在进行 比较 时则要 遵循 下列 规则。

1、null 和 undefined 是相等的。
2、要比较 相等性 之前,不能将 null 和 undefined 转换成其他任何值。
3、如果 有一个 操作数 是 NaN,则 相等 操作符 返回 false ,而 不相等 操作符 返回 true。重要提示:即使 两个 操作数 都是 NaN ,相等 操作符 也 返回 false;因为按照规则,NaN 不等于 NaN ;

4、如果 两个 操作数 都是 对象,则比较它们是不是同一个对象。如果两个 操作数 都指向 同一个对象,则 相等 操作符 返回 true;否则,返回 false。

    下表列出了一些特殊情况及比较结果:

      表达式                值
null == undefined         true
"NaN" == NaN              false
5 == NaN                  false
NaN == NaN                false
NaN != NaN                true
false == 0                true
true == 1                 true
true == 2                 false
undefined == 0            false
null == 0                 false
"5" == 5                  true

2、全等和不全等

        除了在比较之前 不转换 操作数 之外,全等 和 不全等 操作符 与 相等 和 不相等 操作符 没有什么区别。全等 操作符 由 3 个 等于号 ( === ) 表示 ,它只在 两个 操作数 未经 转换 就相等 的情况下 返回 true ,如下面的例子所示 :

var result1 = ("55" == 55);     // true,因为 转换后 相等 
var result2 = ("55" === 55);    // false,因为 不同的 数据类型 不相等 

    在这个例子中,第一个比较使用的是 相等 操作符 比较 字符串 "55" 和 数值 55,结果 返回了 true。如前所述,这是因为 字符串 "55" 先被 转换 成了 数值 55,然后再与另一个 数值 55进行 比较。第二个比较使用了 全等 操作符 以不转换数值的方式比较同样的 字符串 和 值 。在 不转换 的情况下,字符串 当然 不等于 数值,因此结果就是 false。
        不全等 操作符 由 一个 叹号 后跟 两个 等于号 ( !== ) 表示 ,它在 两个 操作数 未经 转换 就 不相等 的情况下返回 true。例如 :

var result1 = ("55" != 55);      // false,因为 转换后 相等 
var result2 = ("55" !== 55);     // true,因为 不同的 数据类型 不相等

     在这个例子中,第一个比较使用了 不相等操作符 ,而该 操作符 会将 字符串 "55" 转换 成55,结果就第二个 操作数 ( 也是 55 ) 相等了。而由于这两个 操作数 被认为 相等,因此就 返回了 false。第二个比较使用了 不全等 操作符。假如我们这样想:字符串 55 与数值 55 不相同吗?,那么答案一定是:是的( true )。
        记住 :null == undefined 会返回 true,因为它们是类似的;

但 null === undefined 会返回 false,因为它们是不同类型的值。

        由于 相等 和 不相等 操作符 存在 类型转换 问题,而为了保持代码中 数据类型 的 完整性 ,我们 推荐 使用 全等( === ) 和 不全等( !== ) 操作符

3.5.8  条件操作符

        条件 操作符 ( 三目运算符 ) 应该算是 ECMAScript 中最灵活的一种 操作符 了,而且它 遵循 与 Java 中的 条件 操作符 相同 的 语法形式,如下面的例子所示 :

variable = boolean_expression ? true_value : false_value;

    本质上,这行代码的 含义 就是 基于 对 boolean_expression 求值 的 结果,决定 给 变量variable 赋什么值。如果求值结果为 true,则给 变量 variable 赋 true_value 值;如果求值结果为 false,则给 变量 variable 赋 false_value 值。再看一个例子 :

var max = (num1 > num2) ? num1 : num2;

    在这个例子中,max 中将会保存一个 最大 的 值 。这个 表达式 的 意思 是 :

如果 num1 大于 num2 ( 关系表达式 返回 true ) ,则将 num1 的 值 赋给 max;

如果 num1 小于 或 等于 num2 ( 关系表达式 返回 false ) ,则将 num2 的值赋给 max。

3.5.9  赋值操作符

  简单 的 赋值 操作符 由 等于号 ( = ) 表示 ,其 作用 就是把 右侧 的 值 赋给 左侧 的 变量,如下面的例子所示 :

var num = 10;

    如果在 等于号 ( = ) 前面 再添加 乘性 操作符、加性操作符 或 位操作符,就可以完成 复合 赋值 操作。这种 复合 赋值 操作 相当于是对下面常规表达式的 简写 形式 :

var num = 10;
num = num + 10;
// 其中的第二行代码可以用一个复合赋值来代替 :
var num = 10; 
num += 10;

    每个 主要 算术 操作符 ( 以及 个别 的 其他 操作符 ) 都有 对应 的 复合 赋值 操作符 。

这些 操作符 如下所示 :

  • 1、乘 / 赋值 ( *= ) ;
  • 2、除 / 赋值 ( /=  ) ;
  • 3、模 / 赋值 ( %= ) ;
  • 4、加 / 赋值 ( += ) ;
  • 5、减 / 赋值 ( -= ) ;
  • 6、左移 / 赋值 ( <<= ) ;
  • 7、有符号 右移 / 赋值 ( >>= ) ;
  • 8、无符号 右移 / 赋值 ( >>>= ) 。

设计这些 操作符 的 主要目的 就是 简化 赋值 操作。使用它们不会带来任何性能的提升。

3.5.10  逗号操作符

        使用 逗号 操作符 可以在 一条语句中 执行 多个操作 ,如下面的例子所示 :

var num1 = 1, num2 = 2, num3 = 3;

    逗号 操作符 多用于 声明 多个变量;但除此之外,逗号 操作符 还可以用于 赋值。在用于赋值时,逗号 操作符 总会 返回 表达式中的 最后一项 ,如下面的例子所示 :

var num = (5,1,4,8,0);     // num 的值为 0

    由于 0 是 表达式中 的 最后一项,因此 num 的值就是 0 。虽然逗号的这种 使用方式 并不常见,但这个 例子 可以帮我们 理解 逗号 的这种行为。


下一章内容 : 《红宝石》第3章“基本概念”【下】

本文链接:《红宝石》第3章“基本概念”【下】_雨季mo浅忆的博客-CSDN博客

猜你喜欢

转载自blog.csdn.net/weixin_58099903/article/details/129449012