笔记 - JavaScript 高级程序设计【第 03 章:基本概念】

第03章:基本概念

3.1. 语法

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

  3.1.1. 区分大小写

    ECMAScript 中的一切都区分大小写。

    扩展:在 HTML 中,代码一般不区分大小写(一般推荐小写),给带中划线的标签自定义属性存值时(如:data-name)也可以不区分大小写,但是在取值时,一定要使用小写,不然 JavaScript 会大概率取不到属性值或取到错误值。

  3.1.2. 标识符

    所谓标识符,就是指变量、函数、属性的名字、或者函数的参数。标识符可以是一或多个字符,第一个字符必须是一个字母、下划线“_”或一个美元符号“$”,其他字符可以是字母、下划线、美元符号或数字。

    标识符通常使用驼峰大小写格式,关键字、保留字、true、false 和 null 等,不能作为标识符。

  3.1.3. 注释

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

  3.1.4. 严格模式

    要在整个脚本中启用严格模式,可以在顶部(第一行)添加代码;也可以在函数内部上方包含这条编译指示,使指定函数在严格模式下执行。例如:

    'use strict';

    function fn() {
        'use strict';
        //函数体;
    }

    扩展:严格模式和标准模式下的代码结果可能会不一致,所以实际开发中,一般只在某些框架中会使用严格模式。

  3.1.5. 语句

    保守地在需要分号的地方加分号,好处多多:可以避免错误,方便排查;可以放心的压缩代码;可以使解析器不必花时间推测分号位置,从而增进代码性能。

    扩展:解析器在推测缺少分号的语句时,如果换行的上句和下句可以合并为一句,便合并;不能合并,便加分号解析;都不行,便报错。

    解析器在合并解析语句时也有例外,在解析 return、continue 及 break 时,如果代码在这三个关键字后面换行会直加分号解析,特别是 return,如果把返回值换行写,发生错误很难排查。

    还有一个例外就是自增运算符“++”或自减运算符“--”,一般的语句会向上合并,而解析器遇到这两个运算符时,会让它们和下面的语句合并。例如:

    a = 1
    b = 2
    a
    ++
    b
    alert(a); //1;
    alert(b); //3,结果是++b,而不是a++;

    针对解析器对没有分号的语句解析的方式,开发者有时会在自己代码开头加分号,这样即使碰到上个开发者的代码有分号缺失,也能保证自己的代码能正确执行。

    用来组合代码块的花括号“{}”,虽然有时可以省略(例如 if 语句后面仅有一条语句时),也尽量不要省略,从而增加代码可读性,降低出错几率,而且方便今后添加代码。

3.2. 关键字和保留字

  关键字可以用于表示控制语句的开始或结束,或者用于执行特定操作等。

  保留字没有特定用途,但它们有可能在将来用作关键字。在 ECMAScript 中的保留字,很多都是 Java 语言的关键字。

  关键字和保留字都不能用作标识符,虽然可以作为属性名,但是最好不要这样做,以便与将来的 ECMAScript 版本兼容。

3.3. 变量

  ECMAScript 的变量是松散类型的,可以用来保存任何类型的数据。定义变量时使用 var 操作符,后跟变量名(即一个标识符)。

  未经初始化的变量会保存一个特殊值——undefined。

  当一个变量初始化后,对该变量的值进行修改时,即使将值得类型更改也是可以的,但是强烈不推荐这样操作。

  可以使用一个 var 操作符在一条语句中定义多个变量,变量之间使用逗号隔开就可以了。

  在代码中任意位置省略 var 操作符定义的变量,都是全局变量。在局部作用域中定义的全局变量很难维护,而且如果有意忽略 var 操作符,也会由于相应变量不会马上就有定义而导致不必要的混乱。所以不推荐这样来定义全局变量。

  扩展:未使用 var 操作符定义的变量,成为了全局变量,这是一种导致的结果,而不是理应如此。

  这种全局变量的本质是 window 对象下的属性,所以可以使用 delete 操作符删除;而使用 var 操作符定义后的变量,无论是哪种作用域,是无法使用 delete 操作符删除的。

  另外,不使用 var 操作符定义的变量,无法被解释器提前。关于变量声明提前的概念在后面会提到。

3.4. 数据类型

  ECMAScript 中有 5 种简单数据类型(又称原始数据类型或基本数据类型):Undefined未定义)、Null)、Boolean布尔值)、Number数字)和 String字符串)。

  还有一种复杂数据类型,就是 Object对象),它本质上是由一组无序的名值对(键值对)组成的。

  扩展:基本数据类型不具备属性和方法,只有对象类型才具备。但实际使用中,有时会对基本数据类型进行方法调用,这是因为 JavaScript 内部会先将其为对象后,再去执行相应的属性或方法,而后中间的临时变量会被销毁。例如:

    var a = 'abcd';
    a.len = 4;
    alert(a.len); //undefined,基本数据类型不具备属性;
    a.toUpperCase();
    alert(a); //abcd,值未保存,被销毁,所以没有改变;
    a = a.toUpperCase();
    alert(a); //ABCD,变量a已被重新赋值;
  3.4.1. typeof 操作符

    使用 typeof 可以检测给定变量的数据类型,对一个值使用 typeof 会返回这个值得数据类型对应的字符串:

      ● "undefined",未定义;

      ● "boolean",布尔值;

      ● "string",字符串;

      ● "number",数值;

      ● "object",对象或 null;

      ● "function",函数。

    typeof 不是函数,但是使用中需要检测的代码是计算表达式时,最好加上括号。例如:

    var a = true;
    var b = true;
    alert(typeof a === b); //false,先检测了a的类型后再和b比较;
    alert(typeof (a === b)); //boolean;

    由于 null 被认为是一个空的对象引用,所以使用 typeof 检测返回的是 "object" 。

    Safari 5 及以下版本和 Chrome 7 及以下版本,检测正则表达式会返回 "function" ,其他浏览器返回 "object" 。

    函数从技术角度讲也属于对象,不是数据类型,但是函数确实有一些特殊属性,所以通过 typeof 操作符来区分函数和其他对象是有必要的。

  3.4.2. Undefined 类型

    Undefined 类型只有一个值,即 undefined。在使用 var 声明变量但未对其进行初始化时,此变量的值便为 undefined。

    由于未经初始化的值默认为 undefined,所以使用 undefined 来显式初始化变量是多余的。

  3.4.3. Null 类型

    Null 类型是第二个只有一个值的类型,这个特殊值便是 null。它表示一个空指针对象,所以使用 typeof 操作符检测会返回 "object"。

    如果定义的变量准备在将来用于保存一个对象时,那么最好将该变量初始化为 null,这样不仅可以体现 null 作为空对象的惯例,而且可以进一步与 undefined 进行区分。

    扩展:undefined 与 null 的相同与不同

      相同点:

      1. 都只有个一个值;

      2. 在参与判断时,都为假,都返回 false;

      3. 都不具有方法。

      不同点:

      1. null 是一个关键字,undefined 不是关键字;

      2. null 表示了一个对象,而 undefined 表示的是一个数据类型;

      3. 使用 typeof 操作符检测的返回值不同;

      4. 使用 null 可以初始化一个变量为一个空对象,而使用 undefined 初始化一个变量是没有什么意义的;

      5. 将这两种值分别转换为数字时,undefined 转换后会变成 NaN,而 null 转换后会变成 0

  3.4.4. Boolean 类型

    Boolean 类型有两个值,分别为 true 和 false,这两个值与数字值不是一回事,所以 true 不一定等于 1,而 false 也不一定等于 0。

    在 JavaScript 中,所有的数据类型都可以使用 Boolean() 函数转换为 Boolean 类型,用于判断真假。

    除了 false 本身,还有 6 种数据在转换后值为假,也就是 false,它们分别是:undefined、null、0、-0、NaN、空字符串("")。

  3.4.5. Number 类型

    1. 浮点数值

      ECMAScript 会自动将小数点后没有数字或浮点数本身表示的就是一个整数(如 1.0)的数字,自动转换为整数来保存,以降低内存占用。

      与其他使用 IEEE754 数值格式的语言一样,ECMAScript 也有浮点数计算的舍入误差问题。例如 0.1 + 0.2 的结果不是 0.3,而是0.30000000000000004;

    2. 数值范围

      如果某次计算的结果超过了 ECMAScript 所能保存的数值范围,那么这个结果会被自动转换为特殊的 Infinity(正无穷),如果这个数值是负数,则会保存为 -Infinity(负无穷)。

    3. NaN

      NaN 即非数值(Not a Number),是一个特殊的数值;它用来表示一个本来要返回数值的操作数未返回数值的情况(以此来避免抛出错误)。

      NaN 本身有两个特点:首先,任何涉及 NaN 的操作都会返回 NaN;其次,NaN 与任何值都不相等,包括 NaN 本身。

      针对 NaN 的特点,ECMAScript 定义了 isNaN() 函数。该函数接受一个可以是任何类型的参数。isNaN() 会尝试将参数转换为数值,能转换的返回 false,不能转换的返回 true。

    4. 数值转换

      有 3 个函数可以把非数值转换为数值:Number()、parseInt() 和 parseFloat()。第一个可以用于任何类型,后两个主要用于字符串的转换。

      Number() 函数的转换规则:

        ● 如果是布尔值,true 转换为 1,false 转换为 0;

        ● 如果是数字值,只是简单传入和返回;

        ● 如果是 null 值,返回 0;

        ● 如果是 undefined,返回 NaN。

        ● 如果是字符串,遵循以下规则:

          ◎ 如果字符串中只有数字(包括前面带正负的),则将其转换为十进制数值,转换后的结果会忽略前导的 0;

          ◎ 如果字符串中包含有效的十六进制格式,则将其转换为相同大小的十进制整数值;

          ◎ 如果为空字符串,则转换为 0;

          ◎ 如果以上三种情况都不是,则转换为 NaN。

        ● 如果是对象,则调用对象的 valueOf() 方法,然后依照前面的规则转换。如果转换的结果为 NaN,则调用对象的 toString() 方法,然后再次依照前面的规则转换。

      由于 Number() 函数转换规则复杂且不够合理,因此在处理整数时,更常用的是 parseInt() 函数。

      parseInt() 函数会忽略字符串前面的空格,直至找到第一个非空格字符。如果第一个字符不是数字或负号,则直接返回 NaN;如果是数字,则继续逐个解析,直到解析完后续字符或遇到一个非数字(小数点也是非数字哦)。

      parseInt() 函数可以接收第二个参数:转换时使用的基数(即多少进制);实际开发中一般都按十进制解析,所以始将 10 作为第二个参数是非常必要的。

      parseFloat() 函数的解析方式与 parseInt() 函数类似,但是 parseFloat() 函数解析时,字符串中的第一个小数点符号是有效的,而第二个小数点无效,连并后面的字符串仍然会被忽略。

      parseFloat() 函数只解析十进制值,所以会始终忽略前导的 0,而且它没有第二参数指定基数。

      如果字符串只能解析为一个整数时(没小数点或小数点后都是 0),那么 parseFloat() 函数会返回整数。

  3.4.6. String 类型

    String 类型用于表示由零或多个 16 位 Unicode 字符组成的字符序列,即字符串。

    1. 字符字面量

      String 数据类型包含一些特殊的字符字面量,也叫转义序列,都使用“/”转移符开头。常见的如:\n 换行,\t 制表符,\\ 斜杠等等(详细的可以查字符表)。

    2. 字符串的特点

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

    var str = 'Java';
    str = str + 'Script'; //JavaScript;

      扩展:实际开发中,可以根据这个特点,使用加号“+”拼接空字符串的方法,使一个值转换为字符串。

    3. 转换为字符串

      要把一个值转换为字符串有两种方法,第一种是几乎每个值都有的 toString() 方法,第二种是转型函数 String()。

      一般调用 toString() 不用传递参数,只要在调用数值的 toString() 方法时,可以传递一个参数来设定按什么进制来转换目标数值。

      由于 null 和 undefined 是没有方法的,所以在不知道要转换的值是不是这两种值时,可以使用 String() 函数。

      String() 函数转换规则很简单,如果值有 toString() 方法就调用(无参)并返回相应结果,如果值是 null 则返回 "null",如果值是 undefined 则返回 "undefined"。

  3.4.7. Object 类型

    ECMAScript 中的对象其实就是一组数据和功能的集合。对象可以通过执行 new 操作符后面跟要创建的对象类型的名称来创建。

    Object 类型是所有它的实例的基础(这与 Java 中的 java.lang.Object 对象一样),它所具有的任何属性和方法也同样存在于更具体的对象中。这些属性和方法,包括:

      ● constructor :称为构造函数,保存着用于创建当前对象的函数;

      ● hasOwnProperty(propertyName) :用于检查给定的属性在当前对象实例中是否存在。作为参数的属性名 propertyName 必须为字符串形式;

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

      ● propertyIsEnumerable(propertyName) :用于检查给定的属性是否能够使用 for-in 语句来枚举,参数必须为字符串类型;

      ● toLocaleString() :返回对象的字符串表示,该字符串与执行环境的地区对应。例如输出时间对象时,不同的区域设置和语言设置,执行的输出结果不同;

      ● toString() :返回对象的字符串表示;

      ● valueOf() :返回对象的字符串、数值或布尔值表示。通常与 toString() 方法的返回值相同。

    扩展:方法是对象的特殊属性,而方法本身也是一个特殊的对象。

    JavaScript 中的对象大致可以分为三类:

      1. 内部对象:ECMAScript 本身自带的对象,共有 17 个,这些对象又分为三种:

        ● 错误对象:有6个,是用来表示错误信息的对象;

        ● 常用对象:有8个,除了数字对象、布尔值对象、字符串对象和对象类型本身外,还包括数组对象、时间对象、函数对象、正则表达式对象;

        ● 内置对象:有3个,包括 Math 对象、Global 对象和 Json 对象;

      2. 宿主对象:JavaScript 的运行环境对象,例如 window 对象和 document 对象;

      3. 自定义对象:用户创建的对象。

    任何数据类型都可以使用 Object() 函数转换为对象类型,函数接受一个参数,该参数可以是任何类型;转换后的对象包含一个属性称为原始值,用来存放传入参数的原始数据,部分转换规则如下:

      1. Boolean 类型和 Number 类型转换为对象后,原始值对应的值就是传入的数据对应的值;

      2. String 类型转换为对象后,除了原始值,还会包含一个 length 属性,表示字符串的个数;另外还包含每个字符对应的下标属性以及该属性(下标)对应的值(字符);

      3. Undefined 类型和 Null 类型转换后,都会得到一个空的对象。

    基本数据类型可以转换成对象,对象也可以转换成基本数据类型,部分转换规则如下:

      1. 转换为 Boolean 类型时,无论这个对象是什么样的,都会得到 true,例如:

    var a = Boolean(new Object(false));
    alert(a); //true;

      2. 转换为 String 类型时,首先会调用对象的 toString() 方法,如果无法返回字符串,便调用对象的 valueOf() 方法;

      3. 转换为 Number 类型时,首先会调用对象的 valueOf() 方法,如果无法返回结果,便调用对象的 toString() 方法。

    在 JavaScript 中访问对象的属性有两种方法,一种是使用“.”,另一种是使用“[]”,第二种方式中的表达式的结果必须为 String 类型;例如:

    var student = {};
    student.name = 'lucy';
    student.age = 19;
    alert(student.name); //lucy;
    alert(student['age']); //19;

3.5. 操作符

  所谓操作符就是用于操作数据值的符号,包括算数操作符、位操作符、关系操作符和相等操作符。

  扩展:学习和理解操作符之前,先要了解表达式:在 JavaScript 中,参与到运算中的各种“短语”,都叫作表达式;也可以理解为,除了操作符,剩下的都是表达式。

  表达式可以分为 6 种类型:

    1. 原始表达式:包括变量、常量、直接量和关键字;其中常量在命名时,字母一般全为大写,例如:PI = 3.1415926;直接量,指的是直接使用的数据值;

    2. 初始化表达式:例如在创建一般对象或数组对象时,不是使用 new 操作符,而是使用直接量的方式来创建这两种对象的表达式,就是初始化表达式;

    3. 函数定义表达式:指的是常规的定义函数的表达式,例如:function functionName(argsList) {functionBody;};

    4. 函数调用表达式:用来调用已定义的函数的表达式,例如:functionName(args);

    5. 属性访问表达式:通过“.”或者“[]”来访问对象属性的表达式;

    6. 对象创建表达式:通过 new 操作符创建一个对象的表达式。

  3.5.1. 一元操作符

    只能操作一个值的操作符就叫做一元操作符,是最简单的操作符。

    1. 递增和递减操作符

      递增(以“++”表示)和递减(以“--”表示)操作符都分为前置型和后置型。这两种操作变量的区别通俗的解释是:前置型,变量先求值并赋值,后调用;后置型,变量先调用,后求值并赋值。例如:

    var a = 29;
    var b = --a + 2;
    alert(a); //28,此时a已完成自减;
    alert(b); //30,b与完成自减的a相加;
    var c = 49;
    var d = c++;
    alert(d); //49,c先将值赋值给d,然后才自增;
    alert(c); //50,c完成自增;

      书中的例子表示 ++a 与 a = a + 1 的效果相同,这里有个前提是后者的 a 的值不能是 String 类型;不然“+”号会成为连接符,将后面的 1 转换为字符串后再与前面的 a 中的字符串进行拼接。可以试一下:

    var a = '123';
    a = a + 1;
    alert(a); //1231;

      执行前置递增和递减操作时,变量的值都是在语句被求值以前改变的,这种情况在计算机科学领域通常称作副效应。这里有个栗子,有一些扩展知识:

    var a = 1;
    var b = a +++ a; //先运算a++,此时a为1,这时a的值暂时被保存为一个临时变量,参与运算后,完成了++的操作,此时a为2,临时变量里的1加上自增后的2,得出3,最后赋值给了b;一元操作符优先级高,所以a +++ a其实是a++ +a;
    alert(a); //2;
    alert(b); //3;

      另外,浮点数执行递减操作时,结果可能会受到浮点数舍入错误的影响,例如 a = 1.1,那么 a-- 的结果为 0.10000000000000009。

      扩展:运算符优先级、结合性和运算顺序:

      先了解运算符优先级,在 JavaScript 中,运算符优先级决定了表达式中运算执行的先后顺序,优先级高的运算符最先被执行。

      一元操作符的优先级很高,而赋值运算符的等级几乎是最低的,在实际开发和面试时,了解常用的运算符优先级是必要的。例如:

    var a = 3;
    alert(++a == 3); //知道了优先级后,就能看出最后的结果应该是false,而不是2;

      再来看结合性,结合性决定了拥有相同优先级的运算符的执行顺序;结合性又分为左结合(从左到右计算)和右关联(从右到左计算)。

      在 ES 5.1 标准中,只有一元操作符、三目运算符及赋值运算符是右关联,其余都是左结合。掌握了关联性可以在实际开发中,更方便阅读和编写代码。例如:

    x = a ? b : c ? d : e ? f : g; //看起来犯晕,知道了关联性就能知道这行代码等价于下面的代码;
    x = a ? b : (c ? d : (e ? f : g));

      最后说说运算顺序,在 JavaScript 中,优先级和结合性都只是决定表达式的运算顺序,但如果表达式中包含子表达式,那么表达式与子表达式之间全都遵循从左向右的运算顺序。

      再来一个栗子:

    var c = 1;
    var d = c++ + ++c; //开始c为1,暂存,再经历后++为2,为2的c直接前++,为3,1再和为3的c相加,得4;
    alert(d); //妥妥的4;

      下面是运算符优先级和结合性的参照表(引自:mozilla.org):

优先级

运算类型

关联性

运算符

20

圆括号

n/a

( … )

19

成员访问

从左到右

… . …

需计算的成员访问

从左到右

… [ … ]

new(带参数列表)

n/a

new … ( … )

函数调用

从左到右

… ( … )

18

new(无参数列表)

从右到左

new …

17

后置递增(运算符在后)

n/a

… ++

后置递减(运算符在后)

n/a

… --

16

逻辑非

从右到左

! …

按位非

从右到左

~ …

一元加法

从右到左

+ …

一元减法

从右到左

- …

前置递增

从右到左

++ …

前置递减

从右到左

-- …

typeof

从右到左

typeof …

void

从右到左

void …

delete

从右到左

delete …

15

从右到左

… ** …

14

乘法

从左到右

… * …

除法

从左到右

… / …

取模

从左到右

… % …

13

加法

从左到右

… + …

减法

从左到右

… - …

12

按位左移

从左到右

… << …

按位右移

从左到右

… >> …

无符号右移

从左到右

… >>> …

11

小于

从左到右

… < …

小于等于

从左到右

… <= …

大于

从左到右

… > …

大于等于

从左到右

… >= …

in

从左到右

… in …

instanceof

从左到右

… instanceof …

10

等号

从左到右

… == …

非等号

从左到右

… != …

全等号

从左到右

… === …

非全等号

从左到右

… !== …

9

按位与

从左到右

… & …

8

按位异或

从左到右

… ^ …

7

按位或

从左到右

… | …

6

逻辑与

从左到右

… && …

5

逻辑或

从左到右

… || …

4

条件运算符

从右到左

… ? … : …

3

赋值

从右到左

… = …

… += …

… -= …

… *= …

… /= …

… %= …

… <<= …

… >>= …

… >>>= …

… &= …

… ^= …

… |= …

2

yield

从右到左

yield …

yield*

从右到左

yield* …

1

展开运算符

n/a

... …

0

逗号

从左到右

… , …

     2. 一元加和减操作符 

      一元加操作符以一个加号“+”表示,放在数值前,对数值不会产生任何影响,即使这个数值是负数;而放在非数值前,一元加操作符会像 Number() 转型函数一样对这个值执行转换。其他的好理解,但要注意负数的情况:

    var a = -10;
    var b = +a;
    alert(b); //-10;

      一元减操作符以一个减号“-”表示,主要用于表示负值;应用于数值时,该值变成负数;应用于非数值时,遵循一元加操作符相同的规则,最后将得到的数值转换为负数。

      扩展:在 JavaScript 中,虽然 0 有 +0 和 -0 两种表示方式,但由于它们的意义相同(使用“===”全等比较结果也为 true),故都显示为 0。

  3.5.2. 位操作符

    位操作符用于在最基本的层次上,即按内存中表示数值的位来操作数值。这节主要涉及计算机底层存储数值的原理,例如补码和反码的知识。实际开发中,除了开发特殊项目,几乎很少用到位操作符。

    1. 按位非(NOT)

      按位非操作符由一个波浪线“~”表示,执行按位非的结果就是返回数值的反码。其本质就是操作数的负值减 1。虽然直接对操作值的取负值后进行 -1 操作也能返回同样结果,但是由于按位非是在底层操作,因此速度更快。

    2. 按位与(AND)

      按位与操作符由一个和号字符“&”表示,它有两个操作数。按位与操作本质上就是将两个数值底层的 32 数值的每一位对齐,如果两个数值的对应位都是 1 便返回1,如果不是则返回 0,最后将返回后的结果转换为数值。例如:

      对 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

    3. 按位或(OR)

      按位或操作符由一个竖线符号“|”表示,同样也有两个操作数。它与按位与操作符本质上一样,但是规律相反,如果两个数值对应的位数有一个是 1,便返回 1,如果对应位数都为 0,才会返回 0。

    4. 按位异或(XOR)

      按位异或操作符由一个插入符号“^”表示,也有两个操作数。它与按位或不同的是,对应位上只有一个 1 才返回 1,如果对应的两位都是 1 或都是 0,则返回 0。也就是说对应位数值不同便返回 1,相同则返回 0。

    5. 左移

      左移操作符由两个小于号“<<”表示,它会将操作数值的二进制码全部向左移动指定位数,移位后多出的空位用 0 填充,以便得到的结果是一个完整的 32 位二进制数,此过程不会影响符号位,最后返回 10 进制结果。

    6. 有符号右移

      有符号的右移操作符由两个大于号“>>”表示,它与左移恰好相反,不同之处在于,原数值中的空位是以符号位的值来填充。

    7. 无符号右移

      无符号右移操作符由三个大于号“>>>”表示,它在操作正数时,结果与有符号右移相同,但在操作负数时,无符号右移后的左边空位是以 0 来填充;所以无符号右移操作数值的结果都是正数。

  3.5.3. 布尔操作符

    1. 逻辑非

      逻辑非操作符由一个叹号“!”表示,可以应用于 ECMAScript 中的任何值。无论这个值是什么类型,这个操作符都会返回一个布尔值。它首先将操作数转换为一个对应的布尔值后,再对其求反。转换规则如下(操作数——返回值):

        ● 对象——false;

        ● 空字符串——true;

        ● 非空字符串——false;

        ● 数值 0——true;

        ● 任意非 0 数值(包括 Infinity)——false;

        ● null——true;

        ● NaN——true;

        ● undefined——true。

      使用两个逻辑非的效果和 Boolean() 转型函数的结果是一样的,相当于先将数值转换为布尔值,取反,再取反,就还是刚转换后的布尔值;这是一种将操作数转换为布尔类型的便捷方法。

    2. 逻辑与

      逻辑与操作符由两个和号“&&”表示,有两个操作数。

      逻辑与操作只有在两个操作数都为“真”(操作数或求值结果为 true)的情况下,结果才会为 true;如果第一个操作数为 true,会继续判断第二个操作数,如果第一个操作数为 false, 便不再向后判断。

      逻辑与操作可以应用于任何类型的操作数,如果有一个操作数不是布尔值,它的返回值规则如下:

        ● 如果第一个操作数是对象,则返回第二个操作数;因为所有对象转换为布尔值,即使是空对象,也为 true;

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

        ● 如果两个操作数都是对象,则返回第二个操作数;

        ● 如果有一个操作数是 null,则返回 null;

        ● 如果有一个操作数是 NaN,则返回 NaN;

        ● 如果有一个操作数是 undefined,则返回 undefined。

      逻辑与运算符的返回值,总是第一个结果为 false 的表达式或值;

      逻辑与操作符是一个短路操作符,即如果第一个操作数(或其求值结果)是 false,那么无论第二个操作数是什么值,结果都不再可能为 true。另外,在逻辑与操作中,不能使用未经声明的值,否则会出现编译错误。

      扩展:逻辑与的特性,可以概括为“并且”一词,它不单单可以用来作为判断语句,还可以直接在一句代码里完成判断和执行的操作,这种写法又称“短路写法”。例如:

    function foo() {
        var a = confirm('选择:');
        alert('foo执行了。');
        return a;
    }

    function bar() {
        alert('bar执行了。');
    }

    foo() && bar(); //使用逻辑与操作做为判断条件,来控制是否运行后面的函数;

    3. 逻辑或

      逻辑或操作符由两个竖线符号“||”表示,有两个操作数。它的判断逻辑看起来与逻辑与正好相反。

      逻辑或操作只有在两个操作数都为“假”(操作数或求值结果为 false)的情况下,结果才会为 false;如果第一个操作数为 false,会继续判断第二个操作数,如果第一个操作数为 true,便不再向后判断。

      逻辑或操作和逻辑与操作类似,它的返回值规则如下:

        ● 如果第一个操作数是对象,则返回第一个操作数;

        ● 如果第一个操作数的求值结果为 false,则返回第二个操作数;

        ● 如果两个操作数都是对象,则返回第一个操作数;

        ● 如果两个操作数都是 null,则返回 null;

        ● 如果两个操作数都是 NaN,则返回 NaN;

        ● 如果两个操作数都是 undefined,则返回 undefined。

      逻辑非运算符的返回值,总是第一个结果为 true 的表达式或值;

      逻辑或操作符也是一个短路操作符,即如果第一个操作数(或其求值结果)是 true,则不对后面的操作数求值。同样的,逻辑或操作中也不能使用未经声明的值

      逻辑或的特性可以概括为“或者”一词,利用这种特性,可以避免为变量赋 null 或 undefined 值。扩展:这在实际开发中,常用于设定变量的默认值,这也是一种短路写法。例如:

    function foo(args) {
        var a = args || '初始值'; //如果没有参数传入,那么a会被赋值为第二个操作数;
        return '字符串是:' + a;
    }

    扩展:逻辑与和逻辑或一起使用的效果,就是简化版的 if-else 语句,而在 JavaScript 中,有专门代替这种写法的运算,就是三目运算(条件运算)。例如:

    a && b || c; //如果a结果为true,便运行b,如果a结果为false,便运行c;
    a ? b : c; //等价于上面的表达式;

    另外,逻辑非是一元操作符,要比逻辑与和逻辑或运算优先级高,而逻辑与又比逻辑或优先级高,三目运算的优先级比前三者都低。

  3.5.4. 乘性操作符

    ECMAScript 定义了 3 个乘性操作符:乘法、除法和求模。这 3 种操作符在操作数为非数值的情况下会执行自动的类型转换,使操作数转换为数值后再进行运算。无法转换为数值的操作数会被转换为 NaN

    1. 乘法

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

    2. 除法

      除法操作符由一个斜线符号“/”表示,执行第二个操作数除第一个操作数的计算。

    3. 求模

      求模(又称取余、取模)操作符由一个百分号“%”表示,用于取两个整数操作数中,第二个操作数第一个操作数后的余数;如果第一个操作数能被第二个操作数整除,则取模结果为 0。

      扩展:求模运算在实际开发中的作用很多,常见的可以用来做队列循环,比如轮播图中的循环显示。另外,虽然求模也可以对浮点数使用,但是由于精确度误差问题,一般不推荐。

  3.5.5. 加性操作符

    加性操作符也会在后台转换不同的数据类型。

    1. 加法

      加法操作符由一个加号“+”表示。运算规则和对操作数的转换规则如下:

        ● 如果两个操作数都是数值,便执行常规加法计算,返回结果略;

        ● 如果有一个操作数是字符串,则应用如下规则:

          ◎ 如果两个操作数都是字符串,便将两者拼接;

          ◎ 如果只有一个操作数是字符串,便将另一个操作数转换为字符串后再将两者拼接;可以理解为加法操作符优先进行字符串的处理;

        ● 如果有一个操作数是对象、数值或布尔值,则调用它们的 toString() 方法取得相应字符串值,然后应用前面的字符串拼接规则;对于 undefined 和 null,则分别调用 String() 函数取得字符串。

      在拼接字符串时要注意,如果要表达式中包含一个加法运算,并希望拼接这个表达式得出的结果,要先把加法运算表达式用括号括起来。例如:

    var a = 10;
    var b = 20;
    var c = '结果是' + a + b;
    var d = '结果是' + (a + b);
    alert(c); //结果是1020,从运算顺序来讲,首先字符串和a拼接为一个字符串,这个字符串再和b拼接,最终导致a和b都被拼接为了字符串;
    alert(d); //结果是30;

      使用为操作数加一个空字符串的方法,可以快速将操作数转换为字符串类型。

    2. 减法

      减法操作符由一个减号“-”表示。

        ● 如果两个操作数都是数值,便执行常规减法计算,返回结果略;

        ● 如果有一个操作数是字符串、布尔值、null 或 undefined,则调用 Number() 函数将其转换为数值后进行减法运算;

        ● 如果有一个操作数是对象,则调用对象的 valueOf() 方法以取得表示该对象的数值;如果对象没有 valueOf() 方法,则调用其 toString() 方法并将得到的字符串转换为数值。

  3.5.6. 关系操作符

    关系操作符包括小于“<”、大于“>”、小于等于“<=”和大于等于“>=”四种,它们用于对两个值进行比较,并返回一个布尔值;比较和类型转换规则如下:

      ● 如果两个操作数都是数值,则执行数值比较;

      ● 如果两个操作数都是字符串,则比较两个字符串对应的字符串编码值;

      ● 如果一个操作数是数值,则将另一个操作数转换为一个数值,然后进行数值比较;

      ● 如果一个操作数是对象,则调用其 valueOf() 方法,用得到的结果按照前面的规则比较;如果对象没有 valueOf() 方法,则调用 toString() 方法求结果;

      ● 如果一个操作数是布尔值,则先将其转换为数值,再进行比较。

    注意字符串的比较是对字符串编码值进行比较,由于大写字母的编码值比小写字母的编码值要小,所以会出现一些奇怪现象,例如:

    var a = 'B' < 'a';
    var b = '23' < '3';
    alert(a); //true,字符串B的字符编码为66,而字符串a的字符编码为97;
    alert(b); //true,字符串2的字符编码为50,而字符串3的字符编码为51;

    避免大小写字母字符串比较的奇怪结果可以使用 toLowerCase() 方法将大写转换为小写后再比较;而数字字符串的比较可以使用 Number() 方法将其中一个转换为数字即可。

    JavaScript 中的特殊数值 NaN,特殊到和所有数值都不相等(包括自身),它和任何操作数比较返回的结果都是 false。

  3.5.7. 相等操作符

    ECMAScript 提供两组相等操作符:相等和不相等,特点是先转换再比较全等和不全等,特点是仅比较而不转换

    1. 相等和不相等

      相等操作符由两个等于号“==”表示,如果两个操作数相等,则返回 true。而不相等操作符由叹号后跟等于号“!=”表示,如果两个操作数不相等,则返回 true。

      这两个操作符都会先转换操作数(称为“强制转型”),再比较操作数的相等性。强制转型的规则如下(比较类型是基于数值类型的):

        ● 如果一个操作数是布尔值,则将 false 转换为 0,true 转换为 1 后再与另一个操作数比较;

        ● 如果一个操作数是字符串,另一个是数值,则将字符串转换为数值再比较;

        ● 如果一个操作数是对象,另一个不是,则调用对象的 valueOf() 方法,用得到的基本类型值按前面的规则比较;

      扩展:相等和不相等操作符在比较时最终是基于数值(Number)类型的,要记住这点,下面有个面试题,可以有效帮助记忆:

    var bol = [] == ![];
    alert(bol); //true!发生了什么?
    //先看右边,因为逻辑非是一元操作符,运算优先级最高,先计算![]。空数组是对象,任何对象转换为布尔值都为true,而!true则为false,按照强转规则,false转换为了数值0,那么此时是比较[]==0;
    //再看左边,空数组为对象,先用valueOf方法,返回了自身,无效;那么再调用toString方法得到空字符串,空字符串转换为数值为0,最终比较的是0==0,所以结果是true……NND……

      这两个操作符在比较时还遵循以下规则(敲黑板,重点):

        ● null 和 undefined 是相等的

        ● 比较相等性之前,不能将 null 和 undefined 转换成其他任何值(通俗理解,它们只和对方或自己相等);

        ● 如果有一个操作数是 NaN,则相等操作符返回 false,不相等操作符返回 true;

        ● 如果两个操作数都是对象,则比较他们是不是同一对象;对象之间比较的是对象的引用位置是否为同一位置

    2. 全等和不全等

      除了在比较之前不强制转换操作数的类型之外,全等和不全等与相等和不相等没有区别。

      全等操作符由三个等于号“===”表示,它只在两个操作数未经转换就相等的情况下返回 true;

      全等操作符比较的规则,是先比较两个操作数类型是否相同,如果类型相同则继续遵循相等和不相等操作符的比较规则继续进行值的比较,如果类型不同则不再进行值的比较而直接返回 false。

      在实际开发中,为了保持代码中数据类型的完整性,一般使用全等和不全等(避免诡异的类型转换让你疯,比如前面那个面试题呵呵)

  3.5.8. 条件操作符

    条件操作符又称为三目运算符,语法形式如下:

    variable = boolean_expression ? true_value : false_value;

    其含义是基于 boolean_expression 的求值结果,决定给变量 variable 赋什么值,如果求值结果为 true,则赋 true_value 的值,结果为 false,则赋 false_value的值。

    扩展:有一道常见的面试题,就是巧妙的使用条件操作符来解题的,题目是:有 a,b,c 三个数值,要求使用一行代码找出其中最大的数值 max;解题如下:

    max = a > b ? (a > c ? a : c) : (b > c ? b : c);
    //或者
    max = c > (a > b ? a : b) ? c : (a > b ? a : b);
  3.5.9. 赋值操作符

    简单的赋值操作符由等于号“=”表示,其作用就是把右侧的值赋给左侧的变量;初学编程时阅读代码经常会觉得是左侧的变量等于右侧的值,要尽快改变这种习惯。

    在赋值操作符等于号前面添加乘性操作符、加性操作符或位操作符,就可以完成复合赋值操作,其含义是在变量原值的基础上进行添加操作符的对应运算后,将得到的值再赋给这个变量。例如:

    var num = 10;
    num += 10; //等价于num = num + 10;

    每个主要算术操作符(及个别其他操作符)都有对应的复合赋值操作符。包括:*=(乘/赋值)、/=(除/赋值)、%=(模/赋值)、+=(加/赋值)、-=(减/赋值)、<<=(左移/赋值)等等。

    复合赋值操作符除了简化赋值操作外,没有任何性能的提升。

  3.5.10. 逗号操作符

    使用逗号操作符可以在一条语句中执行多个操作,一般用于使用一个 var 操作符声明多个变量。

3.6. 语句

  ECMA-262 规定了一组流程控制语句。语句通常使用一个或多个关键字来完成给定任务。

  扩展:会促使某件事发生的命令,称之为语句。学习语句,需要先了解表达式,以及表达式的作用和副作用的概念;作用指的是表达式要实现的功能或效果,而副作用指的是表达式为了实现功能或效果而对操作数进行的增、删、改的操作。

  这里的副作用要理解为:除了主要作用外的额外作用,它并不多余,不要理解为“不好的作用”。

  3.6.1. if 语句

    判断条件可以是任意表达式,其结果不一定是布尔值,但 ECMAScript 会自动将其结果转换为布尔值。

    前文也提到过,即使 if 或 else 后面只有一行代码,也要用花括号括起来,这样可以避免很多问题。

    扩展:在没有花括号分割语句的情况下,else 语句不受缩进的影响,而是和前面离自己最近的 if 语句进行关联。例如:

    var a = 1, b = 2;
    if (a === 1)
        if (b === 3)
            alert(123); 
    else 
        alert(456); //还是弹了456,证明这个else是和条件为b===3的if语句关联;

    如果有多个判断条件也可以将 else 和 if 连写做成 else if 语句。

  3.6.2. do-while 语句

    do-while 语句是一种后测试循环语句,在对条件表达式(出口条件)求值之前,循环体内的代码至少会被执行一次。

  3.6.3. while 语句

    while 语句属于前测试循环语句,在循环体内的代码被执行之前,就会对出口条件求值;所以,循环体内的代码有可能永远不会执行。

  3.6.4. for 语句

    for 语句也是一种前测试循环语句。

    for 循环语句的功能也可以用 while 循环语句实现,使用 while 循环做不到的,使用 for 循环也是做不到的;for 循环只是把与循环有关的代码集中在了一个位置。

    在 for 循环的变量初始化表达式中可以不使用 var 操作符,这个变量的初始化可以在 for 循环外部执行。例如:

    var count = 10;
    var i;
    for (i = 0; i < count; i++) {
        alert(i);
    }

    由于 ECMAScript 中没有块级作用域,所以循环内部定义的变量可以在外部访问到。

    for 语句中的初始化表达式、控制表达式和循环后表达式都是可选的。将这三个表达式全部省略的话,就会创建一个无限循环。虽然这些表达式可以省略,但是用于分割它们的分号是不能省略的

  3.6.5. for-in 语句

    for-in 语句是一种精准的迭代语句,可以用来枚举对象的属性。

    for-in 语句每次执行循环时,都会将循环对象中存在的一个属性名赋值给初始化表达式所定义的变量。这个过程一直持续到对象中的所有属性都被枚举一遍为止。

    由于对象的属性没有顺序,所以通过 for-in 循环输出的属性名顺序不可预测,先后次序因浏览器而异。

  3.6.6. label 语句

    使用 label 语句可以在代码中添加标签,以便在后面的代码中使用;一般是加给 for 等循环语句并且由中断语句 break 和 continue 引用的。

    扩展:实际开发中并不推荐使用,而且其功能可以使用其他方式很好的替代。

  3.6.7. break 和 continue 语句

    break 和 continue 语句用于在循环中精确地控制代码的执行。其中,break 语句会立刻退出当前循环,强制继续执行当前循环后面的语句;而 continue 语句是立刻退出当前的本轮循环,退出后循环会从循环的顶部继续执行。

    虽然实际开发中不常用,但是还是举一个这两个语句与 label 语句联合使用的例子:

    var num = 0;
    outermost: //这个标签包含的是下面整个双重循环;
        for (var i = 0; i < 10; i++) {
            for (var j = 0; j < 10; j++) {
                if (i === 5 && j === 5) {
                    break outermost; //跳出整个双重循环;
                }
                num++;
            }
        }
    alert(num); //55;
  3.6.8. with 语句

    with 语句的作用是将代码的作用域设置到一个特定的对象中。使用它主要是用来简化多次编写同一个对象的工作,例如:

    var qs = location.search.substring(1);
    var hostName = location.hostname;
    var url = location.href;

    //上面的变量都是由相应的location对象的属性赋值的,可以使用下面的with语句简化定义;
    with (location) {
        var qs = search.substring(1);
        var hostName = hostName;
        var url = href;
    }

    上面例子中,使用 with 语句关联了 location 对象,在代码块内部,每个变量首先被认为是一个局部变量,如果在局部环境中找不到该变量的定义,就会查询 location 对象中是否有同名属性,如果有则使用该属性的值作为变量的值。

    在严格模式下,不允许使用 with 语句。而且使用大量的 with 语句会导致性能下降,所以在实际开发中几乎不使用这种语句。

  3.6.9. switch 语句

    switch 语句与 if 语句的关系最为密切,从根本上讲,switch 语句是为了避免编写多个连续的 if-else 语句。

    理解 switch 语句:

      1. 如果条件中的表达式得出的值等于其中一个 case 所设定的值,则执行这个 case 后面的语句;

      2. 如果这个 case 后面的语句末有 break 关键字,则跳出整个 switch 语句,如果没有 break 关键字则继续判断下一个 case;

      3. 最后的 default 关键字用于表达式得出的值不匹配任何一个 case 设定值时执行的机动代码。

    ECMAScript 中的 switch 语句的特色是:

      1. 条件判断中可以使用任何数据类型(很多语言中只能使用数值);

      2. 每个 case 的值不一定是常量,可以是变量,甚至是表达式。例如:

    switch ('hello world') {
        case 'hello' + ' world':
            alert('yes!'); //只要条件语句的值和case的值全等于比较结果为true,便会执行这个case对应的代码,理解这点就可以灵活使用switch语句了;
            break;
        case 'hello':
            alert('no!');
            break;
        default:
            alert('???');
    }

    上面的例子中的条件可以直接写成 true,然后 case 的值可以写成返回值为布尔值的表达式;每个 case 按照顺序被求值,直到找到匹配的值或者遇到 default 语句为止。

    switch 语句在比较时使用的是全等操作符,因此不会进行类型转换。

3.7. 函数

  函数对任何语言来说都是一个核心的概念。通过函数可以封装任意多条语句,而且可以在任何地方、任何时候通过函数名来调用执行。ECMAScript 中的函数使用 function 关键字来声明,后跟一组参数以及函数体。

  任何函数在任何时候都可以通过 return 语句后跟要返回的值来返回值。

  函数执行完 return 语句之后停止并立刻退出,位于 return 之后(换行、使用分号分隔或一些特定的关键字开头的语句,如 var 关键字)的任何代码都永远不会执行。

  一个函数中可以包含多个 return 语句,不同的 return 语句要在不同的分支语句末尾。

  return 语句可以不带任何返回值,这种情况下,函数在停止执行后将返回 undefined 值;这种用法一般用在需要提前停止函数执行而又不需要返回值的情况下。

  3.7.1. 理解参数

    ECMAScript 函数的参数与大多数其他语言中函数的参数有所不同:函数传递参数的个数和参数的数据类型是不受限制的;例如定义的函数只接收两个参数,但调用这个函数时可以传递一个、三个或者不传递函数,都没有问题。

    ECMAScript 函数的参数之所以灵活,是因为参数在内部是用一个数组来表示的,函数接收到的始终都是这个数组,而参数是以这个数组的元素形式传入的;在函数体内通过 arguments 对象访问参数数组,便可以获取每个参数。

    arguments 对象只是与数组类似,因为可以使用方括号语法访问它的每个元素,使用 length 属性确定参数的个数;但它并不是 Array 对象的实例

    命名的参数只提供便利,但不是必需的,因为使用 arguments 的元素下标同样可以达到使用参数的目的。

    在命名参数方面,其他语言可能需要事先创建一个函数签名,而将来的调用必须与该签名一致;而在 ECMAScript 中则不需要,解析器不会验证命名参数。这是 ECMAScript 的函数没有重载的根本原因。

    通过 arguments 的 length 属性和使用 typeof 关键字判断参数的类型等方式,可以变相的实现 ECMAScript 中函数的重载。

    arguments 对象可以和命名参数一起使用,命名参数的顺序对应 arguments 元素的下标;命名参数的值和对应的 arguments 元素的值永远保持同步。例如:

    function doAdd(num1, num2) {
        arguments[1] = 10; //无论num2传入的值是多少,经过这一步都会被修改为10;
        alert(arguments[0] + num2);
    }

    doAdd(1, 99); //弹出11;

    通过修改 arguments 中元素的值,对应命名参数的值也相应的改变了(同步了),但读取这两个值并不是访问相同的内存空间,它们的内存空间是相互独立的。

    如果只传入了一个参数,那么修改 arguments[1] 的值是不会反应到参数中的,因为 arguments 对象的长度是由传入的参数个数决定的,而不是定义函数时的命名参数个数决定的。

    调用函数时,传入的参数小于定义函数时的命名参数个数时,多余的命名参数将自动被赋予 undefined 值,这点跟定义了变量但又没有初始化一样。

    ECMAScript 中的所有参数传递的都是值,不可能通过引用传递参数。

  3.7.2. 没有重载

    所谓重载,指的是可以为一个函数编写两个定义,只要这两个定义的签名(接受参数的类型和数量)不同即可。

    ECMAScript 函数不能像传统意义上那样实现重载,原因前面已提到,正是因为没有函数签名;而通过检查传入函数中参数的类型和数量并作出不同反应,便可变相实现重载。

    如果在 ECMAScript 中定义了两个名字相同的函数,则该名字只属于后定义的函数(这点个人感觉类似 CSS 中的“就近原则”)。

3.8. 小结

  ● 五种基本类型:Undefined、Null、Boolean、Number 和 String。

  ● 没有单独为整数和浮点数分数据类型,Number 类型两种都能表示。

  ● 一种复杂数据类型:Object,它是所有对象的基础。

  ● 严格模式为易错代码作了限制。

  ● 包含类 C 语言中相同的基本操作符,如:算数操作符、布尔操作符、关系操作符、相等操作符及赋值操作符等。

  ● 从其他语言中借鉴了很多流控制语句,如:if、for 和 switch 等语句。

  ● 函数返回值是非必须的,因为函数可以在任何时候返回任何值。

  ● 未指定返回值的函数返回的是 undefined 值。

  ● 函数没有签名,因为函数参数是以一个包含零或多个值的“类数组”形式传递。

  ● 函数传参数量不受限制,并可以通过 arguments 对象访问这些参数。

  ● 由于没有函数签名,所以函数无法实现重载。

(完)

转载于:https://www.cnblogs.com/battlehawk/p/8287073.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值