0%

JavaScript学习笔记(3)

《JavaScript高级程序设计》第三章,基本概念。


语法

区分大小写

ECMAScript中的一切都区分大小写,如变量名test和变量名Test表示两个不同变量。

严格模式

ECMAScript5引入了严格模式,它定义了一种不同的解析与执行模型,用以解决ECMAScript3中一些不确定或不安全行为的报错,使得调试错误变得更为直接。启用严格模式的方式为在作用域第一行加上'use strict';,作为编译指示(pragma)。

语句

ECMAScript中的语句以分号结尾,若省略则由解析器确定语句的结尾。不建议省略,因为分号可以避免很多错误、方便直接进行代码压缩、增进代码性能。

变量

使用var操作符定义的变量将成为定义该变量的作用域中的局部变量,省略var操作符可以定义全局变量,但是不建议这样使用。

数据类型

ECMAScript中有5种简单数据类型(Undefined、Null、Boolean、Number、String)和1种复杂数据类型(Object)。

typeof操作符

typeof是操作符而非函数(也因此可以选择不对其操作对象加括号),对一个值使用typeof操作符可能返回下列某个字符串:

  • “undefined”:当值未定义
  • “boolean”:当值为布尔值
  • “number”:当值为数值
  • “string”:当值为字符串
  • “object”:当值为对象或null
  • “function”:当值为函数

Undefined类型

Undefined类型只有一个值:undefined。声明了但未初始化的变量的值是undefined,未声明的变量不是;但若对二者使用typeof操作符均会返回”undefined”。

1
2
3
4
5
6
7
var declaredVar;

alert(declaredVar); // "undefined"
alert(nonDeclaredVar); // 产生Uncaught ReferenceError

alert(typeof declaredVar) // "undefined"
alert(typeof nonDeclaredVar) // "undefined"

Null类型

Null类型只有一个值:null。在逻辑上看null值指示一个空对象指针,对其使用typeof操作符会返回”object”。undefined值派生于null,因此null == undefined的值为true

Boolean类型

Boolean类型只有两个值:truefalse。这两个值与数字值无关,因此true不一定等于1,false不一定等于0。虽然Boolean类型字面值只有两个,但ECMAScript种所有类型的值都有与这两个Boolean值等价的值,若要转换可调用函数Boolean(),转换规则如下:

  • String:
    • 任何非空字符串->true
    • 空字符串->false
  • Number:
    • 任何非零数字包括无穷大->true
    • 0和NaN->false
  • Object:
    • 任何对象->true
    • null->false
  • Undefined:
    • undefined->false

Number类型

使用IEEE-754格式表示整数和浮点数,也因此会存在浮点数值计算的舍入误差问题(如0.1 + 0.2 != 0.3)。整数可表示为十进制、八进制(第一位为0)、十六进制(前两位为0x)。

数值范围

ECMAScript能够表示的最小/大数值保存于Number.MIN_VALUENumber.MAX_VALUE中。超出数值范围的数将转化成特殊的-InfinityInfinity,判断可用函数isFinite()

NaN

NaN用于表示一个本来要返回数值的操作数未返回数值的情况(这样就不会报错了),如任何数值除以0。NaN有两个特点:

  • 任何涉及NaN的操作都会返回NaN
  • NaN与任何值都不相等,包括NaN本身

判断一个参数是否”不是数值”可以用函数isNaN()

1
2
3
4
5
alert(isNaN(NaN));       // true
alert(isNaN(10)); // false
alert(isNaN("10")); // false,可转为数值10
alert(isNaN("blue")); // true,不可转为数值
alert(isNaN(true)); // false,可转为数值1

数值转换

有3个函数可以把非数值转换为数值:Number()parseInt()parseFloat()。前者可用于任何数据类型,后两者专门用于字符串。

Number()

规则略多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Boolean、数字都很简单

// Null
Number(null) // 0

// Undefined
Number(undefined) // NaN

// String
Number("123") // 123,只包含数字(包括正负号)则转换为十进制数值
Number("011") // 11,八进制的前导码会被忽略
Number("1.23") // 1.23
Number("0xf") // 15,十六进制的前导码不会被忽略
Number("") // 0
Number("123hello") // NaN,上述格式以外的均返回NaN

// Object
// 调用对象的valueOf()方法并依照以上规则转换
// 若转换结果为NaN,则调用对象的toString()方法再依照以上规则转换

parseInt()

处理方式:忽略字符串前面空格,直到找到第一个非空个字符。如果第一个字符不是数字字符或负号就返回NaN,再继续解析第二个字符、第三个字符等等,直到解析完所有字符或者遇到一个非数字字符。

1
2
3
4
5
6
7
8
9
10
11
parseInt("")             // NaN
parseInt("hello") // NaN
parseInt("123hello") // 123
parseInt("10.5") // 10
parseInt("70") // 70,十进制数
parseInt("070") // ES3下解析为八进制数56;ES5下解析为十进制数70
parseInt("0xf") // 15,十六进制数

// 为解决进制的解析问题,引入第二个参数:基数,即进制数
parseInt("AF", 16) // 175,十六进制
parseInt("AF") // NaN

parseFloat()

parseInt()类似,从第一个字符开始解析,直到解析完所有字符或者遇到一个无效的浮点数字字符。与parseInt()不同之处在于:字符串中第一个.有效,第二个.无效;只解析为十进制。

1
2
3
4
5
6
7
8
9
parseFloat("")           // NaN
parseFloat("hello") // NaN
parseFloat("123hello") // 123
parseFloat("10.5") // 10.5
parseFloat("10.5.89") // 10.5
parseFloat("3.125e7") // 31250000
parseFloat("70") // 70
parseFloat("070") // 70
parseFloat("0xf") // 0

String类型

特点

ECMAScript中的字符串是不可变的。如果要改变某个变量保存的字符串,首先要销毁原来的字符串,再用一个新字符串填入该变量。

转换为字符串

toString()

1
2
3
4
5
6
7
8
9
10
11
12
13
// null和undefined没有toString()方法

// 数值、布尔值、对象和字符串值都有toString()方法
var hello = true;
alert(hello.toString()); // "true"

// 可用参数:基数,即进制数
var num = 10;
num.toString() // "10"
num.toString(2) // "1010"
num.toString(8) // "12"
num.toString(10) // "10"
num.toString(16) // "a"

String()

规则:

  • 当值有toString()方法,则调用该方法并返回
  • 当值为null,返回”null”
  • 当值为undefined,返回”undefined”

其他方法

如果要把某个值转为字符串,可以令其加上空字符串(“”)。

Object类型

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

  • Constructor
  • hasOwnProperty(propertyName)
  • isPrototypeOf(object)
  • propertyIsEnumerable(propertyName)
  • toLocaleString()
  • toString()
  • valueOf()

操作符

一元操作符

包括(++)、(–)、(+)、(-),常规用法不赘述,注意点在于:

  • 应用于布尔值时,将false看成0、true看成1
  • 应用于字符串时,如果是有效数字,则用该数字,否则返回NaN
  • 应用于对象时,调用valueOf()方法得到值,如果结果是NaN则调用toString()方法得到值

位操作符

ECMAScript中所有数值都以IEEE-754 64位格式存储,但位操作符不直接操作64位的值,而是先将其转换为32位整数,执行操作,最后转换回64位。

  • 非(~)
  • 与(&)
  • 或(|)
  • 异或(^)
  • 左移(<<):符号位不动,空缺填0,不影响符号位
  • 有符号右移(>>):符号位不动,空缺填符号位的值,不影响符号位
  • 无符号右移(>>>):整体右移,空缺填0(负数会影响符号位)

布尔操作符

  • 非(!)
  • 与(&&)
    逻辑与是短路操作符。除了常规操作,还存在当一个操作数不是布尔值时,逻辑与的返回值不一定是布尔值的情况,规则如下。
    • object && _ => _
    • _ && object => object when _ is true
    • object1 && object2 => object2
    • return null/NaN/undefined if there exists null/NaN/undefined
  • 或(||)
    与逻辑与类似,规则如下。
    • object || _ => object
    • _ && object => object when _ is false
    • object1 || object2 => object1
    • return null/NaN/undefined if both are null/NaN/undefined

乘性操作符

  • 乘法
    • return NaN if there exists NaN
    • Infinity * 0 => NaN
    • Infinity * non-zero-val => ± Infinity
    • Infinity * Infinity => Infinity
  • 除法
    • return NaN if there exists NaN
    • 0 / 0 => NaN
    • Infinity / Infinity => NaN
  • 求模
    • Infinity % _ => NaN
    • _ % 0 => NaN

加性操作符

包括加法和减法,其中加法需要注意字符串拼接。

关系操作符

字符串的比较并不是按照字母表顺序,而是比较两个字符串中对应位置的每个字符的字符编码值。在编码表中,大写字母的字符编码全部小于小写字母的字符编码,因此如果要按照字母表顺序比较,必须转换为均小/大写。NaN和任何内容进行比较均为false。

1
2
3
4
5
6
"alphabet" < "Brick"     // false
"alphabet" < "brick" // true

"23" < "3" // true
"23" < 3 // false,数字和字符串比较时字符串要被转化为数字
"a" < 3 // false,此处"a"被转为NaN

相等操作符

常规的字符串、数值、布尔值的相等性很好判断,但是涉及到对象就比较复杂了。最早的ECMAScript会在执行相等操作符之前,将对象转换为相似的类型,但这种转换的合理性存在一定的争议。因此ECMAScript提出了两组操作符:相等和不相等——先转换再比较,全等和不全等——不转换直接比较。

相等和不相等

在判断相等之前,操作数会被进行强制转型,针对不同数据类型的转换规则如下:

  • Boolean ? _ => 将布尔值转化为数值
  • String ? Number => 将字符串转换为数值
  • Object ? non-object-type => 调用对象的valueOf()方法,将得到的基本类型值

比较时所遵循的规则如下:

  • null和undefined相等
  • 在比较相等性之前,不能将null和undefined转换成其他任何值
  • NaN和任何值都不相等,包括NaN本身
  • 两个对象判断相等的依据是比较它们是不是指向同一个对象

全等和不全等

全等操作符为三个等于号(===),它判断的依据是比较两个操作数在未经转换的情况下是否相等。

1
2
3
4
null === undefined       // false,因为它们是不同类型的值

"55" == 55 // true,字符串55会被转换为数值55
"55" === 55 // false,字符串55与数值55不相同

条件操作符

var num = boolean_expr ? true_val : false_val;

赋值操作符

逗号操作符

逗号操作符可用于声明多个变量。

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

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

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

语句

常规用法在此不赘述,记录几个不太熟的语句。

for语句

其中有一个特殊点在于,ECMAScript中不存在块级作用域,因此循坏内部定义的变量可以在外部被访问到。

1
2
3
4
5
var count = 10;
for(var i = 0; i < count; i++) {
alert(i);
}
alert(i); // 10

for-in语句

for-in语句是一种精准的迭代语句,可以用来枚举对象的属性。ECMAScript对象的属性没有顺序,因此如果使用for-in循环输出的内容顺序是不可预测的。在ECMAScript3中,如果要迭代的对象的变量值为null或undefined会产生报错,ECMAScript5中将其修正为不再报错,但是会不执行循环体。
for-in语句有点奇怪之处,举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var data = [{
"name": "a",
"age": 11
},{
"name": "b",
"age": 12
},{
"name": "c",
"age": 13
}];

for(var key in data) {
alert(key); // 输出为0、1、2
alert(data[key]); // 输出为其中的对象
alert("text: " + data[key].name + "value: " + data[key].age);
}

原因在于for-in语句的设计初衷是用于遍历对象中的属性的,用它来遍历数组很容易错用。详情参考:
Why is using for-in with array iteration a bad idea?
Loop through an array in JavaScript

label语句

在代码中添加标签,可被break或continue语句引用。

1
2
3
4
5
6
7
8
9
10
11
12
// 其中的outermost即为标签
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,退出了内部循环和外部循环
1
2
3
4
5
6
7
8
9
10
11
var num = 0;
outermost:
for(var i = 0; i < 10; i++) {
for(var j - 0; j < 10; j++) {
if(i == 5 && j == 5) {
continue outermost;
}
num++;
}
}
alert(num); // 95,退出了内部循环,未退出外部循环

with语句

with语句的作用是将代码的作用域设置到一个特定的对象中,以简化多次编写同一个对象的工作。但是大量使用with语句会导致性能下降和调试困难,因此不建议使用。严格模式下使用with语句会被视为语法错误。

1
2
3
4
5
6
7
8
9
10
11
// 化简前
var qs = location.search.substring(1);
var hostName = location.hostname;
var url = location.href;

// 化简后
with(location) {
var qs = search.substring(1);
var hostName = hostname;
var url = href;
}

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

switch语句

与其他语言的switch语句的不同之处在于:

  • 可以在switch语句中使用任何数据类型,而不是只能使用数值
  • 每个case的值不一定是常量,可以是变量、表达式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    switch("hello world") {
    case "hello" + "world":
    alert("Greeting sent");
    break;
    case "bye":
    alert("Farewell sent");
    break;
    default:
    alert("None sent");
    }

    var num = 25;
    switch(true) {
    case num < 0:
    alert("negative");
    break;
    case num >= 0 && num <= 10:
    ...
    }
    注意:switch语句在比较值时使用全等操作符,因此不会发生类型转换,如”10”不等于10。

函数

ECMAScript函数在定义时不必指定是否返回值,未指定返回值的函数返回的是undefined。

理解参数

ECMAScript函数不介意传递进来多少个参数,也不在乎传进来的参数是什么类型。原因在于ECMAScript中的参数在内部是用一个数组来表示的,函数接收到的是这个数组,而不关心其中的内容。在函数体内可以通过arguments对象来访问这个参数数组(但arguments只是与数组类似,而非Array的实例),如arguments[0]、arguments[1]等,也可以通过其length属性获取参数个数。
函数可以在不包含命名参数的情况下,使用arguments访问真实传入的参数。

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

arguments对象可以与命名参数一起使用,它的值也永远与对应命名参数的值保持同步。虽然是保持同步的,但是读取这两个值时并不会访问同一块内存空间,它们的内存空间是独立的。没有传递值的命名参数将自动被赋予为undefined。

1
2
3
4
5
6
7
8
9
10
function doAdd(num1, num2) {
if (arguments.length == 1) {
arguments[0] = 0; // 保持同步
num2 = 2;
} else if (arguments.length == 2) {
alert(arguments[0] + num2); // 一起使用
}
}

doAdd(1); // 此时num1=1,num2=undefined

严格模式下对arguments对象做出了一定限制:对没有传递值的命名参数的赋值无效,仍然为undefined;重写arguments的值会导致语法错误。
ECMAScript中的所有参数传递的都是值,不可能通过引用传递参数。

没有重载

一般语言的重载是为一个函数编写两个定义,只要两个定义的签名(接收参数的类型和数量)不同即可。而ECMAScript函数没有签名,所以不能做到真正的重载,但是可以通过对arguments.length不同值设置不同反应来模拟重载。如果在ECMAScript中定义两个名字相同的函数,则该名字只属于后定义的函数。