3 基本概念

本章内容

  • 语法
  • 数据类型
  • 流控制语句
  • 理解函数

3.1 语法

3.1.1 区分大小写

区分大小写

3.1.2 标识符

  • 第一个字符必须是一个字母、下划线(_)或1一个美元符号($);
  • 其他字符可以是字母、下划线、美元符号或数字。
    按照惯例,ECMAScript 标识符采用驼峰大小写格式。

3.1.3 注释

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

3.1.4 严格模式

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

3.1.5 语句

ECMAScript中的语句以一个分号结尾。

var sum = a + b
var diff = a - b;

虽然语句结尾的分号不是必需的,但我们建议任何时候都不要省略它。

最佳实践是始终在控制语句中使用代码块

if (test) {
  alert(test);
}

3.2 关键字和保留字

3.3 变量

var message;
var message = "hi";
var message = "hi";
message = 100;    //有效,但不推荐
function test () {
  var message = "hi"; // 局部变量
}
test();
alert(message); // 错误!

3.4 数据类型

ECMAScript 中有5种基本数据类型:UndefinedNullBooleanNumberString。还有一种复杂数据类型——Object

3.4.1 typeof 操作符

对于一个值使用typeof操作符可能返回下列某个字符串:

  • "undefined"——如果这个值未定义
  • "boolean"——如果这个值是布尔值
  • "string"——如果这个值是字符串
  • "number"——如果这个值是数值
  • “object”——如果这个值是对象或null
  • "function"——如果这个值是函数
var message = "some string";
alert(typeof message);  // "string"
alert(typeof(message));  // "string"
alert(typeof 95);  // "number"

注意,typeof是一个操作符而不是函数,因此例子中的圆括号不是必需的。

3.4.2 Undefined 类型

Undefined类型只有一个值,即特殊的 undefined

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

// 下面这个变量未声明
// var age

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

// var age

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

3.4.3 Null 类型

只有一个值null

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

如果定义的变量准备在将来用于保存对象,那么最好将该变量初始化为null而不是其他值。

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

实际上,undefined值是派生自null值的。

alert(null == undefined);  //true

没有必要把一个变量的值显式地设置为undefined
只要意在保存对象的变量还没有真正保持队形,就应该明确保存null值。

3.4.4 Boolean 类型

var found = true;
var lost = false;

TrueFalse都不是Boolean值,只是标识符。

ECMAScript中所有类型的值都有与这两个Boolean值等价的值,可以调用转型函数Boolean()

var message = "Hello world!";
var messageAsBoolean = Boolean(message);
数据类型 转换为true的值 转换为false的值
Boolean true false
String 任何非空字符串 空字符串
Number 任何非零数字值 0和NaN
Object 任何对象 null
Undefined n/a (不适用) undefined
var message = "Hello world";
if (message) {
  alert("Value is true");
}

3.4.5 Number 类型

var intNum = 55;

八进制字面值的第一位必须是0。八进制字面量在严格模式下是无效的。

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

十六进制字面值的前两位必须是 0x

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

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

1.浮点数值

var floatNum1 = 1.1;
var floatNum2 = 0.1;
var floatNum3 = .1;  //有效,但不推荐
var floatNum1 = 1.;  //小数点后面没有数字——解析为1
var floatNum2 = 10.0;  //整数——解析为10
var floatNum = 3.125e7;  //等于31250000

在默认情况下,ECMAScript会将那些小数点后面带有6个零以上的浮点数值转换为以e表示法表示的数值(0.0000003会被转换成3e-7)。
浮点数值的最高精度是17位小数,但在进行算术计算时其精确度远远不如整数。例如,0.1加0.2结果不是0.3,而是0.30000000000000004。

2.数值范围

计算结果得到一个超出JavaScript数值范围的值,那么这个数值将被自动转换成特殊的Infinity值。负数会被转换成-Infinity,正数会被转换成Infinity

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

3.NaN

这个数值用于表示一个本来要返回数值的操作数未返回数值的情况(这样就不会抛出错误了)。在ECMAScript中,任何数值除以0会返回NaN,不会影响其他代码的执行。
任何涉及NaN的操作都会返回NaN,这个特点在多步计算中有可能导致问题。
NaN与任何值都不想等,包括NaN本身。

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

4.数值转换

有3个函数可以把非数值转换为数值:Number()parseInt()parseFloat()
Number()函数的转换规则如下。

  • 如果是Boolean值,truefalse将被转换为10
  • 如果是数字值,只是简单的传入和返回。
  • 如果是null值,返回0
  • 如果是undefined,返回NaN
  • 如果是字符串,遵循下列规则:
    如果是字符串中只包含数字,则将其转换为十进制数值;(忽略前导零)
    如果字符串中包含有效的浮点格式,则将其转换为对应的浮点数值;(忽略前导0)
    如果字符串中包含有效的十六进制格式,则将其转换为相同大小的十进制整数值;
    如果字符串是空的,则将其转换为0
    如果字符串中包含除上述格式之外的字符,则将其转换为NaN
  • 如果是对象,则调用对象的valueOf()方法,然后依照前面的规则转换返回的值。如果转换的结果是NaN,则调用对象的toString(),然后再次依照前面的规则转换返回的字符串值。
var num1 = Number("Hello world!");  //NaN
var num2 = Number("");  //0
var num3 = Number("000011");  //11
var num4 = Number(true);  //1

一元操作符的操作与Number()函数相同

parseInt()函数在转换字符串时,更多的是看其是否符合数值模式。它会忽略字符串前面的空格,直至找到第一个非空格字符。如果第一个字符不是数字字符或者负号,会返回NaN;如果第一个字符是数字字符,会继续解析,知道解析完所有后需字符或遇到一个非数字字符。

var num1 = parseInt("1234blue1213");  //1234
var num2 = parseInt("");  //NaN
var num3 = parseInt("0xA");  //10 (十六进制数)
var num4 = parseInt(22.5);  //22
var num = parseInt("0xAF", 16);  //175

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

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.12e7");  //31250000

3.4.6 String 类型

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

1.字符字面量

String数据类型包含一些特殊的字符字面量,也叫转义序列。

2.字符串的特点

要改变某个变量保存的字符串,首先要销毁原来的字符串,然后再用另一个包含新值的字符串填充该变量。

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

3.转换为字符串

把一个值转换为一个字符串有两种方式。第一种是使用几乎每个值都有的toString()方法。这个方法唯一要做的就是返回相应值得字符串表现。

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

数值、布尔值、对象和字符串值都有toString()方法。但nullundefined值没有这个方法。
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(10));  //"a"

在不知道要转换的值是不是nullundefined的情况下,还可以使用转型函数String(),这个函数能将任何类型的值转换为字符串。

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

3.4.7 Object 类型

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

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

Object的每个实例都具有下列属性和方法。

  • Constructor:保存着用于创建当前对象的函数。对于前面的例子而言,构造函数就是Object()
  • hasOwnProperty(propertyName):用于检查给定的属性在当前对象实例中是否存在。其中,作为参数的属性名必须以字符串形式指定。
  • isPrototypeOf(object):用于检查传入的对象是否是另一个对象的原型。
  • propertyIsEnumerable(propertyName):用于检查给定的属性是否能够使用for-in语句来枚举。作为参数的属性名必须以字符串形式指定。
  • toLocaleString():返回对象的字符串表示,该字符串与执行环境的地区对应。
  • toString():返回对象的字符串表示。
  • valueOf():返回对象的字符串、数值或布尔值表示。通常与toString()方法的返回值相同。

由于在ECMAScript中Object是所有对象的基础,因此所有对象都具有这些基本的属性和方法。

3.5 操作符

ECMAScript 操作符的与众不同之处在于,它们能够适用于很多值,例如字符串、数字值、布尔值,甚至对象。在应用于对象时,相应的操作符通常都会调用对象的valueOf()和(或)toString()方法,以便取的可以操作的值。

3.5.1 一元操作符

1.递增和递减操作符

var age = 29;
++age;
var age = 29;
--age;

执行前置递增和递减操作时,变量的值都是在语句被求值以前改变的。

var age = 29;
var anotherAge = --age + 2;
alert(age);  //输出28
alert(anotherAge);  //输出30

后置型递增和递减操作是在包含它们的语句被求值之后才执行的。

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

在应用于不同的值时,递增和递减操作符遵循下列规则。

  • 在应用与一个包含有效数字字符的字符串时,先将其转换为数字值,在执行加减1的操作。字符串变量变成数值变量。
  • 在应用于一个不包含有效数字字符的字符串时,将变量的值设置为NaN。字符串变量变成数值变量。
  • 在应用于布尔值false时,先将其转换为0再执行加减1的操作。布尔值变量变成数值变量。
  • 在应用于布尔值true时,先将其转换为1在执行加减1的操作。布尔值变量变成数值变量。
  • 在应用于浮点数值时,执行加减1的操作。
  • 在应用于对象时,先调用对象的valueOf()方法以取得一个可供操作的值。然后对该值应用前述规则。如果结果是NaN,则在调用toString()方法后再应用前述规则。对象变量变成数值变量。
var s1 = "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.一元加和减操作符

一元操作符以一个加号表示,放在数值前面,对数值不会产生任何影响。

var num = 25;
num = +num;  //25

不过,在对非数值应用一元加操作符时,该操作符会像Number()转型函数一样对这个值执行转换。

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

一元减操作符主要用于表示负数。

var num = 25;
num = -num;  //-25

应用于飞数值时,一元减操作符遵循与一元加操作符相同的规则,最后再将得到的数值转换为负数。

3.5.2 位操作符

在 ECMAScript中,当对数值应用位操作符时,后台会发生如下转换过程:64位的数值被转换成32位数值,然后执行位操作,最后再将32位的结果转换回64位数值。这个转换过程导致了一个严重的副效应,即在对特殊的NaNInfinity值应用位操作时,这两个值都会被当成0来处理。
如果对非数值应用位操作符,会先使用Number()函数将该值转换为一个数值,然后再应用位操作。得到的结果将是一个数值。

1.按位非 (NOT)

var num1 = 25;  //二进制
var num2 = ~num1;  //二进制 
alert(num2);  //-26

这也验证了按位非操作的本质:操作数的负值减1。

2.按位与(AND)

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

第一个数值的位 第二个数值的位 结果
1 1 1
1 0 0
0 1 0
0 0 0
var result = 25 & 3;
alert(result);  //  1

3.按位或 (OR)

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

第一个数值的位 第二个数值的位 结果
1 1 1
1 0 1
0 1 1
0 0 0
var result = 25 | 3;
alert(result);  //27

4.按位异或 (XOR)

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

第一个数值的位 第二个数值的位 结果
1 1 0
1 0 1
0 1 1
0 0 0
var result = 25 ^ 3;
alert (result);  //26

5.左移

左移操作符由两个小于号(<<)表示,这个操作符会将数值的所有位向左移动指定的位数。

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

左移操作会以0来填充这些空位,以便得到的结果是一个完整的32位二进制数。
注意,左移不会影响操作数的符号位。换句话说,如果将-2向左移动5位,结果将是-64,而非64。

6.有符号的右移

有符号的右移操作符由两个大于号(>>)表示,这个操作符会将数值向右移动,但保留符号位。

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

7.无符号右移

无符号右移操作符由3个大于号(>>>)表示。这个操作符会将数值的所有32位都向右移动。对正数来说,无符号右移结果与有符号右移相同。

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

对负数来说,无符号右移是以0来填充空位,不会保留符号位的值。

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

3.5.3 布尔操作符

布尔操作符一共有3个:非(NOT)、与(AND)和或(OR)。

1.逻辑非

逻辑非操作符由一个叹号(!)表示,可以应用于ECMAScript中的任何值。逻辑非操作符首先会将它的操作数转换为一个布尔值,然后再对其求反。逻辑非操作符遵循下列规则:

  • 如果操作数是一个对象,返回fasle
  • 如果操作数是一个空字符串,返回true
  • 如果操作数是一个非空字符串,返回false
  • 如果操作数是数值0,返回true
  • 如果操作数是任意非0数值(包括Infinity),返回false
  • 如果操作数是null,返回true;
  • 如果操作数是NaN,返回true
  • 如果操作数是undefined,返回true
alert(!false);  //true
alert(!"blue");  //false
alert(!0);  //true
alert(!NaN);  //true
alert(!"");   //true
alert(!12345);  //false

逻辑非操作符也可以用于将一个值转换为与其对应的布尔值。而同时使用两个逻辑非操作符,实际上就会模拟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

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

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

逻辑与操作术语短路操作,即如果第一个操作数能够决定结果,那么就不会再对第二个操作数求值。如果第一个操作数是false, 则无论第二个操作数是什么值,结果都不会是true了。

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

3.逻辑或

逻辑或操作符由两个竖线符号(||)表示,由两个操作数。

var result = true || false;

逻辑或的真值表如下:

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

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

  • 如果第一个操作数是对象,则返回第一个操作数;
  • 如果第一个操作数的求值结果为false,则返回第二个操作数;
  • 如果两个操作数都是对象,则返回第一个操作数;
  • 如果两个操作数都是null,则返回null
  • 如果两个操作数都是NaN,则返回NaN
  • 如果两个操作数都是undefined,则返回undefined

逻辑或操作符也是短路操作符。也就是说,如果第一个操作数的求值结果为true,就不会对第二个操作数求值了。

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

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

var myObject = preferredObject || backupObject;

3.5.4 乘性操作符

ECMAScript定义了3个乘性操作符:乘法、除法和求模。如果参与乘法计算的某个操作数不是数值,后台会先使用Number()转型函数将其转换为数值。也就是说,空字符串将被当作0,布尔值true将被当作1。

1.乘法

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

var result = 34 * 56;

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

  • 如果操作数都是数值,执行常规的乘法计算。如果乘积超过了ECMAScript数值的表示范围,则返回Infinity-Infinity
  • 如果有一个操作数是NaN,则结果是NaN
  • 如果是Infinity与0相乘,则结果是NaN
  • 如果是Infinity与非0数值相乘,则结果是Infinity-Infinity,取决于有符号操作数的符号;
  • 如果有一个操作数不是数值,则在后台调用Number()将其转换为数值,然后再应用上面的规则。

2.除法

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

var result = 66 / 11;

规则如下:

  • 如果操作数都是数值,执行常规的除法计算。如果商超过了ECMAScript数值的表示范围,则返回Infinity-Infinity
  • 如果有一个操作数是NaN,则结果是NaN;
  • 如果是InfinityInfinity除,则结果是NaN
  • 如果是零被零出,则结果是NaN
  • 如果是非零的有限数被零除,则结果是Infinity-Infinity
  • 如果是Infinity被任何非零数值除,则结果是Infinity-Infinity
  • 如果有一个操作数不是数值,则在后台调用Number()将其转换为数值,然后在应用上面的规则。

3.求模

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

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

求模操作符会遵循下列特殊规则来处理特殊的值:

  • 如果操作数都是数值,执行常规的除法计算,返回除得的余数;
  • 如果被除数是无穷大值而除数是有限大的数值,则结果是NaN
  • 如果被除数是有限大的数值而除数是零,则结果是NaN
  • 如果是InfinityInfinity除,则结果是被除数
  • 如果被除数是有限大的数值而除数是无穷大的数值,则结果是被除数;
  • 如果被除数是零,则结果是零;
  • 如果有一个操作数不是数值,则在后台调用Number()将其转化为数值,然后在应用上面的规则。

3.5.5 加性操作符

在ECMAScript中,这两个操作符有一系列的特殊行为。

1.加法

var result = 1 + 2;

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

  • 如果有一个操作数是NaN,则结果是NaN
  • 如果是InfinityInfinity,则结果是Infinity
  • 如果是-Infinity-Infinity,则结果是-Infinity
  • 如果是+0加+0,则结果是+0;
  • 如果是-0加-0,则结果是-0;
  • 如果是+0加-0,则结果是+0;

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

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

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

var result1 = 5 + 5;  //连个数值相加
alert(result1);  //10
var result2 = 5 + "5";  //一个数值和一个字符串相加
alert(result2);  //"55"
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"
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"

2.减法

var result = 2 - 1;

同样需要遵循一些特殊规则,如下所示:

  • 如果两个操作符都是数值,则执行常规的算术减法操作并返回结果;
  • 如果有一个操作数是NaN,则结果是NaN
  • 如果是InfinityInfinity,则结果是NaN
  • 如果是-Infinity-Infinity,则结果是NaN
  • 如果是Infinity-Infinity,则结果是Infinity
  • 如果是-InfinityInfinity,则结果是-Infinity
  • 如果是+0减+0,则结果是+0;
  • 如果是+0减-0,则结果是-0;
  • 如果是-0减-0,则结果是+0;
  • 如果有一个操作数是字符串、布尔值、nullundefined,则先在后台调用Number()函数将其转换为数值,然后再根据前面的规则执行减法计算。如果转换的结果是NaN,则减法的结果就是NaN
  • 如果有一个操作符是对象,则调用对象的valueOf()方法以取得表示该对象的数值。如果得到的值是NaN,则减法的结果就是NaN。如果对象没有valueOf()方法,则调用其toString()方法并将得到的字符串转换为数值。
var result1 = 5 - true;  //4
var result2 = NaN - 1;  //NaN
var result3 = 5 - 3;  //2
var result4 = 5 - "";  //5
var result5 = 5 - "2";  //3
var result6 = 5 - null;  //5

3.5.6 关系操作符

小于(<)、大于(>)、小于等于(<=)和大于等于(>=)这几个关系操作符用于对两个值进行比较,这几个操作符都返回一个布尔值。

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

当关系操作符的操作数使用了非数值时,也要进行数据转换或完成某些奇怪的操作。

  • 如果连个操作数都是数值,则执行数值比较。
  • 如果两个操作数都是字符串,则比较两个字符串对应的字符编码值。
  • 如果一个操作数是数值,则将一个操作数转换为一个数值,然偶执行数值比较。
  • 如果一个操作数是对象,则调用这个对象的valueOf()方法,用得到的结果按照前面的规则执行比较。如果对象没有valueOf()方法,则调用toString方法,并用得到的结果根据前面的规则执行比较。
  • 如果一个操作数是布尔值,则先将其转换为数值,然后在执行比较。

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

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

要真正按照字母表顺序比较字符串,就必须把两个操作数转换为相同的大小写形式,然后在执行比较,如下所示:

var result = "Brick.toLowerCase()" < "alphabet".toLowerCase();  //false
var result = "23" < "3";  //true
var result = "23" < 3;  //false
var result = "a" < 3;  //false,因为“a”被转换成了NaN
var result1 = NaN < 3;  //false
var result2 = NaN >= 3;  //false

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

3.5.7 相等操作符

在比较字符串、数值和布尔值的的相等性时,问题还比较简单。但在涉及到对象的比较时,问题就变得复杂了。最后,ECMAScript的解决方案就是提供两组操作符:相等和不相等——先转换在比较,全等和不全等——仅比较而不转换。

1.相等和不相等

会先转换操作数,然后再比较它们的相等性。在转换不同的数据类型时,相等和不相等操作符遵循下列基本规则:

  • 如果有一个操作数是布尔值,则在比较相等性之前先将其转换为数值——false转换为0,而true转换为1;
  • 如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值;
  • 如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法,用得到的基本类型值按照前面的规则进行比较;

这两个操作符在进行比较时则要遵循下列规则。

  • nullundefined是相等的。
  • 要比较相等性之前,不能将nullundefined转换成其他任何值。
  • 如果有一个操作数是NaN,则相等操作符返回false,而不相等操作符返回true。重要提示:即使两个操作数都是NaN,相等操作符也返回false;按照规则,NaN不等于NaN
  • 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回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.全等和不全等

只在两个操作数未经转换就相等的情况下返回true

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

3.5.8 条件操作符

variable = boolean_expression ? true_value : false_value;
var max = (num1 > num2) ? num1: num2;

3.5.9 赋值操作符

var num = 10;
var num = 10;
num += 10;

每个主要算术操作符(以及个别的其他操作符)都有对应的复合赋值操作符。这些操作符如下所示:

  • 乘/赋值 (*=);
  • 除/赋值 (/=);
  • 模/赋值 (%=);
  • 加/赋值 (+=);
  • 减/赋值 (-=);
  • 左移/赋值 (<<=);
  • 有符号右移/赋值 (>>=);
  • 无符号右移/赋值 (>>>=)。

3.5.10 逗号操作符

使用逗号操作符可以在一条语句中执行多个操作。

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

逗号操作符还可以用于赋值。在用于赋值时,逗号操作符总会返回表达式中的最后一项。

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

由于0是表达式中的最后一项,因此num的值就是0。

3.6 语句

从本质上看,语句定义了ECMAScript中的主要语法,语句通常使用一或多个关键字来完成给定任务。

3.6.1 if 语句

if (condition) statement1 else statement2
if (i > 25)
  alert("Greater than 25.");  //单行语句
esle {
  alert("Less than or equal to 25.");  //代码块中的语句
}

不过,业界普遍推崇的最佳实践是始终使用代码块,即使要执行的只有一行代码。
另外,也可以把整个if语句写在一行代码中:

if (condition1) statement1 else if (condition2) statement2 else statement3

但我们推荐的做法则是像下面这样:

if (i > 25) {
  alert("Greater than 25.");
} else if (i < 0) {
  alert("Less than 0.");
} else {
  alert("Between 0 and 25, inclusive.");
}

3.6.2 do-while 语句

do-while语句是一种后测试循环语句,即只有在循环体中的代码执行之后,才会测试出口条件。

do {
  statement
} while (expression);
var i = 0;
do {
  i += 2;
} while (i < 10);
alert(i);

3.6.3 while 语句

while语句属于前测试循环语句,也就是说,在循环体内的代码被执行之前,就会对出口条件求值。

while(expression) statement
var i = 0;
while (i < 10) {
  i +=2;
}

3.6.4 for语句

for语句也是一种前测试循环语句,但它具有在执行循环之前初始化变量和定义循环后要执行的代码的能力。

for (initialization; expression; post-loop-expression) statement
var count = 10;
for (var i = 0; i < count; i++) {
  alert(i);
}

这个for循环语句与下面的while语句的功能相同:

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

变量初始化可以在外部执行,由于ECMAScript中不存在块级作用域,因此在循环内部定义的变量也可以在外部访问到。
此外,for语句中的初始化表达式、控制表达式和循环后表达式都是可选的。将这两个表达式全部省略,就会创建一个无限循环。

for (; ;) {  //无限循环
  doSomething();
}

而只给出控制表达式实际上就把for循环转换成了while循环。

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

3.6.5 for-in 语句

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

for (property in expression) statement
for (var propName in window) {
  document.write(propName);
}

ECMAScript对象的属性没有顺序。因此,通过for-in循环输出的属性名的顺序是不可预测的。
但是,如果表示要迭代的对象的变量值为nullundefinedfor-in语句会抛出错误。ECMAScript5更正了这一行为;对这种情况不再抛出错误,而只是不执行循环体。建议在使用for-in循环之前,先检测确认该对象的值不是nullundefined

3.6.6 label语句

使用label语句可以在代码中添加标签,以便将来使用。

label: statement
start: for(var i=0; i < count; i++) {
  alert(i);
}

这个例子中定义的start标签可以在将来由breakcontinue语句引用。加标签的语句一般都要与for语句等循环语句配合使用。

3.6.7 break和continue语句

breakcontinue语句用于在循环中精确控制代码的执行。其中,break语句会立即退出循环,强制继续执行循环后面的语句。而continue语句虽然也是立即退出循环,但退出循环后会从循环的顶部继续执行。

var num = 0;
for (var i = 1; i < 10; i++) {
  if(i % 5 == 0) {
    break;
  }
  num++;
}
alert(num);  //4
var num = 0;
for (var i = 1; i < 10; i++) {
  if(i % 5 == 0) {
    continue;
  }
  num++;
}
alert(num);  //8

breakcontinue语句都可以与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

添加这个标签的结果将导致break语句不仅会退出内部的for语句,而且也会退出外部的for语句。

3.6.8 with语句

with语句的作用是将代码的作用域设置到一个特定的对象中。

with (expression) statement;

定义with语句的目的主要是为了简化多次编写同一个对象的工作。

var qs = location.search.substring(1);
var hostName = location.hostname;
var url = location.href; 
with(location) {
  var qs = search.substring(1);
  var hostName = host.name;
  var url = href;
}

严格模式下不允许使用with语句,否则将视为语法错误。
不建议使用with语句。

3.6.9 switch语句

ECMAScript中switch语句的语法与其他基于C的语言非常接近。

switch (expression) {
  case value: statement
    break;
  case value: statement
    break;
  case value: statement
    break;
  case value: statement
    break;
    default: statement
}

假如确实需要混合几种情形,不要忘了在代码中添加注释,说明你是有意省略了break关键字。

switch(i) {
  case 25:
    /*合并两种情形*/
  case 35:
    alert("25 or 35");
    break;
  case 45:
    alert("45");
    break;
  default:
    alert("other");
}

可以在switch语句中使用任何数据类型,无论是字符串,还是对象都没有问题。。每个case的值不一定是常量,可以是变量,甚至是表达式。

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

3.7 函数

ECMAScript中的函数使用function关键字来声明,后跟一组参数以及函数体。

function functionName(arg0, arg1, ..., argN) {
  statements
}
function sayHi(name, message) {
  alert("hello " + name + "," + message);
}

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

function sum(num1, num2) {
  return num1 + num2;
}

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

推荐的做法是要么让函数始终都返回一个值,要么永远都不要返回值。

严格模式对函数有一些限制:

  • 不能把函数命名为evalarguments
  • 不能把参数命名为evalarguments;
  • 不能出现两个命名参数同名的情况。

如果发生以上情况,就会导致语法错误,代码无法执行。

3.7.1 理解函数

在函数体内可以通过arguments对象来访问这个参数数组,从而获取传递给函数的每一个参数。
arguments对象只是与数组类似,因为可以使用方括号语法访问它的每一个元素,使用length属性来确定传递进来多少个参数。

function sayHi() {
  alert("hello " + arguments[0] + "," + arguments[1]);
}

这个重写后的函数中不包含命名的参数。这个事实说明:命名的参数只提供便利,但不是必需的。

通过访问arguments对象的length属性可以获知有多少个参数传递给了函数。

function howManyArgs() {
  alert(arguments.length);
}

howManyArgs("string", 45);  //2
howManyArgs();  //0
howManyArgs(12);  //1

开发人员可以利用这一点让函数能够接收任意个参数并分别实现适当的功能。

function doAdd() {
  if(arguments.length == 1) {
    alert(arguments[0] + 10);
  } else if (arguments.length == 2) {
    alert(arguments[0] + arguments[1]);
  }
}

doAdd(10);  //20
doAdd(30, 20);  //50

另一个与参数相关的重要方面,就是arguments对象可以与命名参数一起使用。

function doAdd(num1, num2) {
  if(arguments.length ==1) {
    alert(num1 + 10);
  } else if (arguments.length == 2) {
    alert(arguments[0] + num2);
  }
}

关于arguments的行为,还有一点比较有意思。那就是它的值永远与对应命名参数的值保持同步。

function doAdd(num1, num2) {
  arguments[1] = 10;
  alert(arguments[0] + num2);
}

每次执行这个函数都会重写第二个参数,将第二个参数的值修改为10.因为arguments对象中的值会自动反映到对应的命名参数。它们的内存空间是独立的,但它们的值会同步。但这种影响是单向的:修改命名参数不会改变arguments中对应的值。如果只传入了一个参数,那么为arguments[1]设置的值不会反应到命名参数中。这是因为arguments对象的长度是由传入的参数个数决定的,不是由定义函数时的命名参数的个数决定的。
关于参数还要记住最后一点:没有传递值的命名参数将自动被赋予undefined值。
严格模式中,像前面例子中那样的赋值会变得无效,即使把arguments[1]设置为10,num2的值仍然还是undefined。其次,重写arguments的值会导致语法错误。

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

3.7.2 没有重载

ECMAScript函数不能像传统意义上那样实现重载。
如果在ECMAScript中定义了两个名字相同的函数,则该名字只属于后定义的函数。

function addSomeNumber(num) {
  return num + 100;
}

function addSomeNumber(num) {
  return num + 200;
}
var result = addSomeNumber(100);  //300 

3.8 小结

以下简要总结了ECMAScript中基本的要素。

  • ECMAScript中的基本数据类型包括UndefinedNullBooelanNumberString
  • 与其他语言不同,ECMAScript没有为整数和浮点数值分别定义不同的数据类型,Number类型可用于表示所有数值。
  • Object类型,该类型是这门语言中所有对象的基础类型。
  • 严格模式为这门语言中容易出错的地方施加了限制。
  • ECMAScript提供了很多与C及其其他类C语言中相同的基本操作符,包括算数操作符、布尔操作符、关系操作符、相等操作符及赋值操作符等。
  • 从其他语言借鉴了很多流控制语句,例如if语句、for语句和switch语句等。

ECMAScript中的函数与其他语言中的函数有诸多不同之处。

  • 无须指定函数的返回值。
  • 实际上,未指定返回值的函数返回的是一个特殊的undefined值。
  • ECMAScript中也没有函数签名的概念,因为其函数参数是以一个包含零或多个值的数组的形式传递的。
  • 可以向ECMAScript函数传递任意数量的参数,并且可以通过arguments对象来访问这些参数。
  • 由于不存在函数签名的特性,ECMAScript函数不能重载。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容