NPM 命令

NPM

npm 安装与升级

1
2
3
4
// 获取当前使用的 npm 的版本
npm -v
// 升级 npm
npm install npm -g

package 安装与卸载

本地安装将会把安装包安装在运行 npm 命令时所在目录的 ./node_modules 目录下;
全局安装则会将安装包安装在你的 node 所在目录;

1
2
3
4
npm install <package name>		// local
npm install <package name> -g // global
npm install <package name> --save-dev | -D// development
npm install <package name> --save | -S // production

通过命令 npm install <package name> —save 安装的 package 会被加到 package.json 文件中的 dependencies 部分,而通过命令 npm install <package name> —save-dev 安装的 package 会被加到 devDependencies 部分。
命令如果不加 —save 或 —save-dev 则 package 不会被添加到 package.json 文件中

卸载某个 package

1
npm uninstall <package name>

更新某个 package

1
npm update <package name>

安装最新版本的 package

1
npm install <package name>@latest

安装指定版本号的 package

1
npm install <package name>@[verion number]

查看信息

可以通过命令来查看已经安装的 packeage

1
2
3
4
npm list
npm list -g
# --depth 表示深度,深度为 0 时,不显示依赖模块
npm list --depth=0 -g

查看某个 package 是否安装或查看其版本号(若已存在)

1
npm list <package name>

查看某个 package 是否存在

1
npm ls

搜索某个模块

1
npm search <package name>

查看某个 package 的路径

1
npm root <package name>

切换安装源

1
2
3
4
# 淘宝镜像
npm config set registry https://registry.npm.taobao.org
# 官方源
npm config set registry https://registry.npmjs.org/

迭代器、for...in 和 for...of

如果一个对象实现了 Symbol.iterator 属性,则被认为是一个可迭代的对象。一些内建对象如 ArrayMapString 等都有自己的 Symbol.iterator 属性实现。对象上 Symbol.iterator 的方法负责返回用来迭代的值列表。

for...of 语句

for...of 循环访问一个可迭代对象,并调用对象上的 Symbol.iterator 属性。

1
2
3
4
5
let someArray = [1, 'string', false];

for (let entry of someArray) {
console.log(entry); // 1, 'string', false
}

for...offor...in

for...offor...in 都能遍历列表,但是它们遍历的值是不同的:for...in 返回被遍历对象上所有键的列表,而 for...of 则返回被遍历对象上数字属性键的值列表。

1
2
3
4
5
6
7
8
9
let list = [4, 5, 6];

for (let i in list) {
console.log(i); // 0, 1, 2
}

for (let i of list) {
console.log(i); // 4, 5, 6
}

另一点不同的是,for...in 被用作检查对象属性的一种方法;而 for...of 则主要关注可迭代对象的属性值

1
2
3
4
5
6
7
8
9
10
11
12
let aSet = new Set(['Cat', 'Dog', 'Tiger']);
console.log(aSet); // Set(3) { 'Cat', 'Dog', 'Tiger' }
aSet['species'] = 'mammals';
console.log(aSet); // Set(3) { 'Cat', 'Dog', 'Tiger', spespecies: 'mammals' }

for (let i in aSet) {
console.log(i); // species
}

for (let i of aSet) {
console.log(i); // Cat, Dog, Tiger
}

如果对象是不可迭代的,则会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function iterateOverByForOf(target: any) {
for (let i of target) {
console.log(i);
}
}

function iterateOverByForIn(target: any) {
for (let i in target) {
console.log(i);
}
}

const aObj = {
1: 'One',
'A': 2
};

iterateOverByForIn(aObj); // 1, 'A'
iterateOverByForOf(aObj); // target is not iterable

JavaScript 高级程序设计 - 引用类型

应用类型的值是应用类型的一个实例。在 ECMAScript 中,引用类型是一种数据结构,用于将数据和功能组织在一起,它也常被称为 ,但这种称呼并不妥当。尽管 ECMAScript 从技术上讲是一门面向对象的语言,但它不具备传统的面向对象语言所支持的类和接口等基本结构。引用类型有时候也被称为对象定义,因为它们描述的是一类对象所具有的属性和方法。

Object 类型

Object 是 ECMAScript 中使用最多的一个类型。虽然 Object 的实例不具备多少功能,但对于在应用程序中存储和传输数据而言,它们确实是非常理想的选择。

创建 Object 实例的两种方式 :

  • 使用 new 操作符

    1
    2
    var person = new Object();
    person.name = "CocoaLei";
  • 使用对象字面量

    1
    2
    3
    var person = {
    name = "CocoaLei"
    };

    对象字面量是对象定义的一种简写形式 ,目的在于简化创建包含大量属性的对象的过程。

    对象字面量也是向函数传递大量可选参数的首选方式。

一般来说,访问对象属性时使用的都是点语法,这也是很多面向对象语言中通用的语法。不过,在 JavaScript 中也可以使用方括号表示法来访问对象的属性。

1
2
3
4
5
6
7
var person = {
name = "CocoaLei"
};
console.log(person.name); // CocoaLei
console.log(person["name"]); // CocoaLei
var propertyName = "name";
console.log(person[propertyName]); // CocoaLei

通常,除非必须使用变量来访问属性,否则建议使用点语法。

Array 类型

Array 类型是出了 Object 之外 ECMAScript 中最常用的类型了。而且,ECMAScript 中的数组与其它语言中的数组有着相当大的区别。虽然 ECMAScript 数组与其它语言中的数组都是数据的有序列表,但与其它语言不通的是, ECMAScript 中的数组的每一项可以保存任何类型的数据。并且,ECMAScript 数组的大小是可以动态调整的,即可以随着数据的添加自动增长以容纳新增数据。

创建数组的两种方式 :

  • Array 构造函数

    1
    var colors = new Array();

    如果预先知道数组要保存的项目数量,也可以给构造函数传递该数量,而改数量会自动编程 length 的值。

    1
    var colors = new Array(255);
  • 使用数组字面量

    1
    var colors = ["red", "yellow", "blue"];

在读取和设置数组的值时,要使用方括号并提供相应值的基于 0 的数字索引。

数组最多可以包含 4294967295 个项,这几乎已经能够满足任何编程需求了。如果想添加的项数超过这个上限值,就会发生异常。而创建一个初始大小与这个上限值接近的数组,则可能会导致运行时间超长的脚本错误。

检测数组

如果假定只有一个全局执行环境,使用 instanceof 操作符就能检测值是不是数组。但是,实际上存在的全局环境不止一个,那么 Array 构造函数也不止一个。

为了解决这个问题,ECMAScript 5 新增了 Array.isArray() 方法。这个方法的母的是最终确定某个值到底是不是数组,而不管它在哪个全局执行环境中创建的。

1
Array.isArray(value);

转换方法

所有对象都具有 toLocalString()toString()valueOf() 方法。调用数组的 toString() 方法会返回数组中每个值得字符串形式拼接而成的一个以逗号分隔的字符串。调用 valueOf() 方法返回的还是数组。

数组继承的 toLocalString()toString()valueOf() 方法,在默认情况下都会以逗号分隔的字符串的形式返回数组项。而如果使用 join() 方法,则可以使用不同的分隔符来构建这个字符串。join() 方法只接受一个参数,即用作分隔符的字符串,然后返回包含所有数组项的字符串。

如果数组中的某一项的值是 nullundefined , 那么该值在 toLocalString()toString()valueOf() 方法返回的结果中以空字符串表示。

栈方法

ECMAScript 数组也提供了一种让数组的行为类似于其它数组结构的方法。具体来说,数组可以表现的像栈一样。

ECMAScript 专门为数组提供了push()pop() 方法,以便实现类似栈的行为。

push() 方法可以接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度。而 pop() 方法则从数组末尾移除最后一项,减少数组的 length 值,然后返回移除的项。

1
2
3
var sampleArr = new Array();
var arrLength = sampleArr.push("A","B"); // arrLength is 2 and sampleArr is ["A", "B"]
var removedItem = sampleArr.pop(); // removedItem is "B"

队列方法

栈数据结构的访问规则是后进先出,而队列数据结构的访问规则是先进先出。队列在列表的末端添加项,从列表的前端移除项。由于 push() 是向数组末端添加项的方法,因此要模拟队列只需一个从数组前端取得项的方法。实现这一操作的数组方法就是 shift() ,它能够移除数组中的第一个项并返回该项,同时将数组长度减 1 。结合使用 shift()push() 方法,就可以像使用队列一样使用数组。

1
2
3
var sampleArr = new Array();
sampleArr.push("A", "B");
var firstItem = sampleArr.shift(); // firstItem is "A"

ECMAScript 还未数组提供了一个 unshift() 方法,它能在数组前端添加任意个项并返回新数组的长度。

重排序方法

数组中已经存在两个可以直接用来重排序的方法 :reverse()sort()

数组的 reverse() 方法会反转数组的顺序 :

1
2
var originalArr = [3, 7, 1, -1, 22];
originalArr.reverse(); // 22, -1, 1, 7, 3

在默认情况下,sort() 方法按升序排列数组。为了实现排序,sort() 方法会调用数组中每项的 toString() 方法,然后比较得到的字符串,以确定如何排序。这种排序方式在很多情况下都不是最佳方案。不过,sort() 方法可以接收一个比较函数作为参数,以便我们指定哪个值位于哪个值的前面。

1
2
3
4
5
6
7
8
9
10
// 升序
function compare (value_1, value_2) {
if (value_1 < value_2) {
return -1;
} else if (value_1 > value_2) {
return 1;
} else {
return 0;
}
}

reverse()sort() 方法的返回值是经过排序后的数组。

对于数值类型或者其 valueOf() 方法会返回数值类型的对象类型,可以使用一个更简单的比较函数。这个函数只要用第二个值减第一个值即可。

1
2
3
function compare (value_1, value_2) {
return value_2 - value_1;
}

由于比较函数通过返回一个小于零、等于零或大于零的结果来影响排序,因此减法操作就可以适当地处理所有这些情况。

操作方法

ECMAScript 为操作已经包含在数组中的项提供了很多方法。

contact()

contact() 方法可以基于当前数组的所有项创建一个新数组。具体来说,这个方法会先创建当前数组的一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。在没有给 contact() 方法传递参数的情况下,它只是复制当前数组并返回副本。如果传递给 contact() 方法的是一或多个数组,则该方法会将这些数组中的每一项都添加到结果数组中。如果传递的不是数组,这些值就会被简单地添加到结果数组的末尾。

1
2
3
var sampleArr = nes Array();
sampleArr.push("A", "B");
var copyArr = sampleArr.contact("C"); // now sampleArr is ["A", "B", "C"]
slice()

slice() 方法能够基于当前数组中的一个或多个项创建一个新数组。slice() 方法可以接受一个或两个参数,即要返回项的起始和结束位置。在只有一个参数的情况下,slice() 方法返回从该参数指定位置开始到当前数组末尾的所有项。如果有两个参数,该方法返回起始和结束位置之间的项(不包括结束位置)。

1
2
3
var sampleArr = [1, 2, 3, 4, 5, 6, 7];
var resultArr = sampleArr.slice(3); // resultArr is [4, 5, 6, 7]
resultArr = sampleArr.slice(3,5); // resultArr is [4, 5]

如果 slice() 方法中有一个负数,则用数组长度加上该数来确定相应的位置。如果结束位置小于起始位置,则返回空数组。

splice()

splice() 方法主要用途是向数组的中部插入项,但使用这种方法的方式则有如下三种 :

  • 删除 :可以删除任意数量的项,只需指定要删除的第一项的位置和要删除的项数两个参数

    1
    2
    var sampleArr = [1, 2, 3, 4, 5, 6, 7];
    sampleArr.splice(5, 1); // sampleArr is [1, 2, 3, 4, 5, 6]
  • 插入 :可以向指定位置插入任意数量的项,只需提供起始位置、要删除的项数 (设为0) 和要插入的项

    1
    2
    var sampleArr = [1, 2, 3];
    sampleArr.splice(1, 0, 2.1, 2.2); // sampleArr is [1, 2, 2.1, 2.2, 3]
  • 替换 :可以向指定位置插入任意数量的项,同时删除任意数量的项

    1
    2
    var sampleArr = [1, 2, 3];
    sampleArr.splice(2, 1, 4); // sampleArr is [1, 2, 4]

splice() 方法始终都会返回一个数组,该数组包含从原始数组中删除的项 (如果没有删除任何项,则返回一个空数组) 。

位置方法

ECMAScript 5 为数组实例添加了两个位置方法 :indexOf()lastIndexOf() 。这两个方法都接收两个参数 :要查找的项和表示查找七点位置的索引。其中,indexOf() 从数组的开头开始向后查找,lastIndexOf() 则是从数组的末尾开始向前查找。

这两个方法都返回要查找的项在数组中的位置,或者在没有找到的情况下返回 -1 。在比较第一个参数与数组中的每一项时,会使用全等操作符。

1
2
3
var sampleArr = [1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1];
sampleArr.indexOf(4); // 3
sampleArr.lastIndexOf(4); // 7

迭代方法

ECMAScript 5 为数组定义了 5 个迭代方法,每个方法都接收两个参数 :要在每一项上运行的函数和运行该函数的作用域对象。传入这些方法中的函数会接收三个参数 :数组项的值,该项在数组中的位置和数组对象的本身。

  • every() : 对数组中的每一项运行给定函数,如果该函数对每一项都返回 true ,则返回 true
  • filter() :对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组
  • forEach() :对数组中的每一项运行给定函数,这个方法没有返回值
  • map() :对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组
  • some() :对数组中的每一项运行给定函数,如果该函数对任一项返回 true ,则返回 true

以上这些方法都不会修改数组中包含的值。

归并方法

ECMAScript 5 增加了两个归并数组的方法 :reduce()reduceRight() 。这两个方法都会迭代数组所有的项,然后构建一个最终返回的值。其中,reduce() 方法从数组的第一项开始,逐个遍历到最后。而 reduceRight() 则从数组的最后一项开始,向前遍历到第一项。

这两个方法都接收两个参数 :一个在每一项上调用的函数和作为归并基础的初始值。传给 reduce()reduceRight() 的函数接收 4 个参数 :前一个值、当前值、项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上,因此第一个参数是数组的第一项,第二个参数是数组的第二项。

求数组中所有值之和 :

1
2
3
4
var sampleArr = [1, 2, 3, 4, 5];
var sum = sampleArr.reduce(function(prev, cur, index, array) {
return prev + cur;
}); // sum is 15

Date 类型

ECMAScript 中的 Date 类型是在早期 Java 中的 java.util.Date 类基础上构建的。为此,Date 类型使用 UTC 1970 年 1 月 1 日午夜开始经过的毫秒数来保存日期。这使用这种数据存储格式的条件下,Date 类型保存的日期能够精确到 1970 年 1 月 1 日之前或之后的 285616 年。

在调用 Date 构造函数而不传递参数的情况下,新创建的对象自动获得当前的日期和时间。如果想根据特定的日期和时间创建日期对象,必须传入表示该日期的毫秒数。为了简化这一计算过程,ECMAScript 提供了两个方法 : Date.parse()Date.UTC()

其中,Date.parse() 方法接收一个表示日期的字符串参数,然后尝试根据这个字符串返回相应日期的毫秒数。ECMA 没有定义 Date.parse() 应该支持哪种日期格式,因此这个方法的行为因实现而异。

Date.UTC() 方法同样也返回表示日期的毫秒数,但它与 Date.parse() 在构建值时使用不同的信息。Date.UTC() 的参数分别是年份、基于 0 的月份、月中的哪一天、小时数、分钟、秒以及毫秒数。在这些参数中,只有前两个参数是必需的。

Date.now() 方法会返回表示调用这个方法时的日期和时间的毫秒数。

继承的方法

与其它引用类型一样,Date 类型也重写了 toLocalString()toString()valueOf() 方法 。Date 类型的 toLocalString() 方法会按照与浏览器设置的地区相适应的格式返回日期和时间。而 toString() 方法则通常返回带有时区信息的日期和时间。Date 类型的 valueOf() 方法,则根本不返回字符串,而是返回日期的毫秒表示,常用来比较日期值的大小。

1
2
3
4
5
var date_pre = new Date(2017, 0, 1);
var date_next = new Date(2017, 1, 1);
if (date_pre < date_next) {
consolo.log("");
}

日期格式化方法

Date 类型用于将日期格式化为字符串的方法 :

  • toDateString() :以特定于实现的格式显示星期几、月、日和年
  • toTimeString() :以特定于实现的格式显示时、分、秒和时区
  • toLocalDateString() :以特定于地区的格式显示星期几、月、日和年
  • toLocalTimeString() :以特定于实现的格式显示时、分、秒
  • toUTCString() :以特定于实现的格式显示完整的 UTC 日期

RegExp 类型

ECMAScript 通过 RegExp 类型来支持正则表达式 :

1
var expression = /pattern/flags;

其中的模式部分可以是任何简单或复杂的正则表达式,可以包含字符类、限定符、分组、向前查找以及反向引用。每个正则表达式都可以带有一或多个标志,用以表明正则表达式的行为。

正则表达式的匹配模式下支持下列 3 个标志 :

  • g :全局模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止
  • i :不区分大小写模式,即在确定匹配项时忽略模式与字符串的大小写
  • m :多行模式,即在到达一行文本末尾时还会继续查找下一行中是否在与模式匹配的项

模式中使用的所有元字符都必须转义。正则表达式中的元字符包括 :

1
( [ { \ ^ $ | ) ? * + . ] }

这些元字符在正则表达式中都有一或多种特殊用途,因此如果想要匹配字符串中包含的这些字符,就必须对它们进行转义。

使用 RegExp 构造函数构造正则表达式时,需要注意 :传递的两个参数都是字符串。由于 RegExp 的构造函数的模式参数是字符串,所以在某些情况下要对字符进行双重转义。所有元字符都必须双重转义,那些已经转义过的字符也是如此。

1
2
3
字面量模式			等价的字符串
/\[bc\]at/ “\\[bc\\]at”
/\w\\hello\\123/ "\\w\\\\heollo\\\\123"

使用正则表达式字面量和使用 RegExp 构造函数创建的正则表达式不一样。在 ECMAScript 3 中,正则表达式字面量始终会共享同一个 RegExp 实例,而使用构造函数创建的每一个新 RegExp 实例都是新实例。

ECMAScript 5 明确规定,使用正则表达式字面量必须像直接调用 RegExp 构造函数一样,每次赌创建新的 RegExp 实例。

RegExp 实例属性

RegExp 的每个实例都具有下列属性 :

  • global :布尔值,表示是否设置了 g 标志
  • ignoreCase :布尔值,表示是否设置了 i 标志
  • lastIndex :整数,表示开始搜索下一个匹配项的字符位置,从 0 算起
  • multiline :布尔值,表示是否设置了 m 标志
  • source :正则表达式的字符串表示,按照字面量模式而非传入构造函数中的字符串模式返回

RegExp 实例方法

RegExp 对象的主要方法是 exec() ,该方法是专门为捕获组而设计的。 exec() 接受一个参数,即要应用模式的字符串,然后返回包含第一个匹配项信息的数组或者在没有匹配项的情况下返回 null 。返回的数组虽然是 Array 的实例,但包含两个额外的属性 :indexinput 。其中 index 表示匹配项在字符串的位置,而 input 表示应用正则表达式的字符串。在数组中,第一项是与整个模式匹配的字符串,其它项是与模式中单的捕获组匹配的字符串。

对于 exec() 方法而言,即使在模式中设置了全局标志,它每次也只会返回一个匹配项。在不设置全局标志的情况下,在同一个字符串上多次调用 exec() 将始终返回第一个匹配项的信息。而在设置全局标志的情况下,每次调用 exec() 则都会在字符串中继续查找新匹配项。

正则表达式的第二个方法是 test() ,它接受一个字符串参数。在模式与该参数匹配的情况下返回 true ;否则返回 false 。在只想知道目标字符串与某个模式是否匹配,但不需要知道其文本内容的情况下,使用这个方法

JavaScript 高级程序设计 - 变量、作用域和内存问题

基本类型和引用类型

ECMAScript 变量可能包含两种不同数据类型的值 :基本类型值和引用类型值。基本类型值指的是简单的数据段,而引用数据类型指那些可能由多个值构成的对象。

在将一个值赋值给变量时,解析器必须确定这个值时基本数据类型还是引用类型值。基本类型的值是按值访问的,因为可以操作保存在变量中的实际的值。

引用类型的值时保存在内存中的对象,JavaScript 不允许直接访问内存中的位置,即不能直接操作对象的内存空间。在操作对象时,实际上是操作对象的引用而不是实际的对象(当复制保存着对象的某个变量时,操作的是对象的引用;但在为对象添加属性时,操作的是实际的对象)。

在很多其它语言中,字符串以对象的形式来表示,因此被认为是引用类型的。

动态的属性

对于引用类型的值,我们可以为其添加属性和方法,也可以改变和删除属性和方法 :

1
2
var aObject = new Object();
aObject.name = "ObjectName";

复制变量值

除了保存的方式不同之外,在从一个变量向另一个变量复制基本类型值和引用类型值时,也存在不同。

如果从一个变量向另一个变量复制基本类型值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。

当从一个变量向另一个变量复制引用类型值时,同样也会将存储在变量对象的值复制一份放到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。

传递参数

ECMAScript 中函数的参数都是按值传递的。基本类型值得传递如同基本类型变量的复制一样,而引用类型值得传递,则如同引用类型变量的复制一样。

在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数)。在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。

检测类型

基本类型检测的最佳工具是 typeof 操作符。但在检测引用类型的值时,这个操作符的用处不大,我们并不是想知道某个值是对象,而是想知道它是什么类型的对象。

使用 instanceof 操作符来确定实例是否是给定的引用类型 :

1
result = variable instanceof constructor

所有引用类型的值都是 Object 的实例。因此,在检测一个引用类型值和 Object 构造函数时,instanceof 操作符始终会返回 true 。当然,如果使用 instanceof 操作符检测基本类型的值,则该操作符始终会返回 false , 因为基本类型不是对象。

执行环境及作用域

执行环境是 JavaScript 中最为重要的一个概念。执行环境定义了变量或函数有权访问的其它数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。

全局执行环境是最外围的一个执行环境。根据 ECMAScript 实现所在的宿主环境不同,表示执行环境的对象也不一样。在 Web 浏览器中,全局执行环境被认为是 window 对象。

某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出时,才会被销毁)。

每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。

当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。活动对象在最开始时只包含一个变量,即 arguments 对象。作用域链中的下一个变量对象来自包含环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。

标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直到找到标识符为止(如果找不到标识符,通常会导致错误发生)。

延长作用域链

虽然执行环境的类型总共只有两种 :全局和局部,但是还有其它办法来延长作用域链。这是因为有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。

在两种情况下会发生这种现象 :

  • try-catch 语句的 catch
  • with 语句

这两个语句都会在作用域链的前端添加一个变量对象。对 with 语句来或,会将指定的对象添加到作用域链中。对于 catch 语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。

没有块级作用域

JavaScript 没有块级作用域经常会导致理解上的困惑。在其它类 C 语言中,由花括号封闭起来的代码块都有自己的作用域,因而支持根据条件来定义变量。

1
2
3
4
if (true) {
var color = "red";
}
console.log(color); // red

上面的例子中,在 if 语句中定义了变量 color ,它被添加到当前的执行环境,所以在 if 语句结束后还能够被访问。

在使用 for 语句时尤其要牢记这一差异 :由 for 语句创建的循环变量即使 for 循环执行结束后,也依旧会存在于循环外部的执行环境中。

声明变量

使用关键字 var 声明的变量会自动被添加到最接近的环境中。在函数内部,最接近的环境就是函数的局部环境;在 with 语句中,最接近的环境是环境函数。如果初始化变量时没有使用 var 声明,该变量会自动被添加到全局环境。

在编写 JavaScript 代码的过程中,不声明而直接初始化变量是一个常见的错误做法。

查询标识符

当在某个环境中为了读取或写入而引用一个标识符时,必须通过搜索来确定该标识符实际代表什么。搜索过程从作用域链的前端开始,向上逐级查询与给定名字匹配的标识符。如果在局部环境中找到了该标识符,搜索过程停止,变量就绪。如果在局部环境中没有找到该变量名,则继续沿作用域链向上搜索。搜索过程将一直追溯到全局环境的变量对象。如果在全局环境中也咩有找到这个标识符,则意味着该变量尚未声明。

变量查询也不是没有代价的。很明显,访问局部变量要比访问全局变量更快。

垃圾收集

JavaScript 具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。在 JavaScript 中,所需内存的分配和无用内存的回收完全实现了自动管理 :找出那些不再继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔,周期性的执行这一操作。

局部变量只在函数执行的过程中存在。在这个过程中,会为局部变量在栈或堆上分配相应的空间,以便存储它们的值。然后在函数中使用这些变量,直到函数执行结束。此时,局部变量就没有存在的必要了,因此可以释放它们的内存以供将来使用。在这种情况下,很容易判断变量是否还有存在的必要;但并非所有情况下都能这么容易得出结论。垃圾收集器必须跟踪哪个变量有用哪个变量没用,对于不再有用的变量打上标记,以备将来回收其占用的内存。

标记清除

JavaScript 中最常用的垃圾收集方式是标记清除。当变量进入环境时,就将这个变量标记为 “进入环境” 。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为 “离开环境” 。

标记变量的方式很多,它并不重要,关键在于才去什么策略。

垃圾收集器在运行的时候回给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。而在此之后再被加上标记的变量被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。

性能问题

垃圾收集器是周期性运行的,而且如果为变量分配的内存数量很可观,那么回收工作量也是相当大的。这种情况下,确定垃圾收集的时间间隔是一个非常重要的问题。

管理内存

使用具备垃圾收集机制的语言编写程序,开发人员一般不必操心内存管理问题。但是,JavaScript 在进行内存管理及垃圾收集时面临的最主要问题是分配给 Web 浏览器的可用内存数量通常比分配桌面应用程序的少。这样做的目的是防止运行 JavaScript 的网页耗尽全部系统内存而导致系统崩溃。内存限制不仅会影响给变量分配内存,同时还会影响调用栈以及在一个线程中能够同时执行的语句数量。

因此,确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式,就是为执行中的代码只保存必要的数据。一旦数据不再有用,最好通过将其设置为 null 来释放其引用。这一做法适用于大多数全局变量和全局对象的属性。

解除一个值的应用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。

JavaScript 高级程序设计 - 基础概念篇

JavaScript 简介

JavaScript 诞生于 1995 年,当时, 它的主要目的是处理以前由服务器端语言负责的一些输入验证操作。而今,JavaScript 已经从一个简单的输入验证器发展成为一门强大的编程语言:功能全面,能够处理复杂的计算和交互,拥有了闭包、匿名函数,甚至元编程等特性。

虽然 JavaScript 和 ECMAScript 通常被人们用来表达相同的含义,但 JavaScript 的含义却又要多得多。一个完整的 JavaScript 实例由 ECMAScript、DOM 和 BOM 组成。

ECMAScript

ECMAScript-262 定义的 ECMAScript 与 Web 浏览器没有依赖关系,它定义的只是这门语言的基础,而在此基础上可以构建更完善的脚本语言。常见的 Web 浏览器只是 ECMAScript 实现可能的宿主环境之一 : 宿主环境提供基本的 ECMAScript 实现,同时也会提供该语言的扩展,以便语言与环境之间对接交互。

文档对象模型 DOM

文档对象模型(DOM,Document Object Model)是针对 XML 但经过扩展用于 HTML 的应用程序编程接口。DOM 把整个页面映射为一个多层节点结构。即 HTML 或 XML 页面中的每个组成部分都是某种类型的节点,这些节点又包含着不同类型的数据。而开发人员通过 DOM 提供的 API 可以轻松自如的删除、添加或修改任何节点。

浏览器对象模型 BOM

浏览器对象模型使开发人员可以控制浏览器显示的页面以外的部分。从根本上讲,BOM 只处理浏览器窗口和框架,但是人们习惯上也把所有针对浏览器的 JavaScript· 扩展算作 BOM 的一部分。

在 HTML 中使用 JavaScript

向 HTML 页面中插入 JavaScript 的主要方法,就是使用 <script> 元素。这个元素由 Netscape 创造并在 Netscape Navigator 2 中首先实现。后来,这个元素被加入到正式的 HTML 规范中。HTML 4.01 为 <script> 定义了6个属性

  • async : 表示应该立即下载脚本,但不应妨碍页面中的其它操作。这是一个可选属性,只对外部脚本文件有效。
  • charset : 可选。表示通过 src 属性指定的代码的字符集。
  • defer : 可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效。
  • language : 已废弃。原来用于表示编写代码使用的脚本语言。
  • src :可选。表示包含要执行代码的外部文件。
  • type : 可选。可以看成是 language 的替代属性;表示编写代码使用的脚本语言的内容类型(MIME 类型)。虽然 text/javscripttext/ecmascript 都已经不被推荐使用,但人们一直以来使用的都还是 text/javascript 。实际上,服务器在传送 JavaScript 文件时使用的是 MIME 类型通常是 application/x-javascript , 但在 type 中设置这个值却可能导致脚本被忽略。考虑到约定俗成和最大限度的浏览器兼容性,目前 type 属性的默认值依旧还是 text/javascript

使用 <script> 元素的方式有两种 :直接在页面中嵌入 JavaScript 代码和包含外部 JavaScript 文件。

在使用 <script> 元素嵌入 JavaScript 代码时,只需为 <script> 指定 type 属性,然后直接把 JavaScript 代码放在元素内部即可。

1
2
3
<script>
// JavaScript Code
</script>

包含在 <script> 元素内部的 JavaScript 代码将被从上到下依次解释。在解释器对 <scirpt> 元素内部的所有代码求值完毕以前,页面中的其余内容都不会被浏览器加载或显示。

如果要通过 <script> 元素来包含外部 JavaScript 文件,那么 src 属性就是必须的。这个属性的值是一个指向外部 JavaScript 文件的链接。

1
<script type="text/javascript src="example.js"></script>

与解析嵌入式 JavaScript 代码一样,在解析外部 JavaScript 文件(包括下载该文件)时,页面的处理也会暂时停止。

无论如何包含代码,只要不存在 deferasync 属性,浏览器都会按照 <script> 元素在页面中出现的先后顺序对它们依次进行解析。

因此,在包含较多外部 JavaScript 文件的 HTML 页面中,将 <script> 标签放在 <head> 元素中,可能会造成严重的延迟。一般通过将全部 JavaScript 引用放在 <body> 元素中页面内容的后面。

在 HTML 中嵌入 JavaScript 代码虽然没有问题,但一般认为最好的做法还是尽可能使用外部文件来包含 JavaScript 代码。

基本概念

任何语言的核心都必然会描述这门语言最基本的工作原理,而描述的内容通常都要涉及这门语言的语法、操作符、数据类型、内置功能等用于构建复杂解决方案的基本概念。

语法

ECMAScript 的语法大量借鉴了 C 及其它类 C 语言的语法。

区分大小写

ECMAScript 中的一切都区分大小写(变量、函数名和操作符)。

标识符

所谓标识符,就是指变量、函数、属性的名字,或者函数的参数。标识符的组合规则如下:

  • 第一个字符必须是一个字母、下划线或一个美元符号;
  • 其它字符可以是字母、下划线、美元符号或数字。

按照惯例,ECMAScript 标识符采用驼峰大小写格式,也就是第一个字母小写,剩下的每个单词的首字母大写。

严格模式

ECMAScript 5 引入了严格模式的概念。严格模式是为 JavaScript 定义了一种不同的解析与执行模型。

1
"use strict";	// 这是一个编译指示,用于告诉支持的 JavaScript 引擎切换到严格模式

语句

ECMAScript 中的语句以一个分号结尾;如果省略分号,则由解析器确定语句的末尾。虽然语句末尾的分号不是必须的,但建议任何时候都不要省略它。

关键字和保留字

ECMAScript 描述了一组具有特定用途的关键字,这些关键字可用于表示控制语句的开始或结束,或者用于执行特定操作,它们不能用作标识符。 ECMAScript 还描述了一组将来可能被用作关键字的保留字。

变量

ECMAScript 的变量是松散类型的,所谓松散类型就死可以用来保存任何类型的数据。即每个变量仅仅是一个用于保存值得占位符。未被初始化的变量会保存一个特殊的值 :undefined 。ECMAScript 也支持直接初始化变量。

修改变量的值得同时也可以同时修改值得类型,虽然这样做是可行而且有效的,但是并不推荐。

如果定义变量时省略了 var 操作符,变量将会被定义为全局变量。给未经声明的变量赋值在严格模式下会导致抛出 ReferenceError 错误。

数据类型

ECMAScript 中有 5 中简单数据类型 :Undefined、Null、Boolean、Number 和 String。还有一种复杂数据类型 - Object。ECMAScript 不支持任何创建自定义类型的机制,所有值最终都将是上述 6 种数据类型之一。

typeof 操作符

ECMAScript 是松散类型的,因此需要有一种手段来监测给定变量的数据类型,使用 typeof 可以完成这个目的。

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

Undefined 类型

如果使用 var 声明变量但未对其加以初始化时,这个变量的值就是 undefined

值得注意的是,对未初始化的变量执行 typeof 操作符会返回 undefined 值,而对未声明的变量执行 typeof 同样也会返回 undefined 值。这两种变量虽然从技术角度上看有本质区别,但实际上无论对哪种变量也不可能执行真正的操作。

即便未初始化的变量会被自动赋予 undefined 值,但显式地初始化变量依然是明智的选择。如果能够做到这一点,那么当 typeof 返回 undefined 值时,我们就知道被检测的变量还没有被生命,而不是尚未初始化。

Null 类型

从逻辑角度来看,null 值表示一个空对象指针,而这也正是使用 typeof 操作符检测 null 值时会返回 “object” 的原因。

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

实际上,undefined 是派生自 null 值得,因此规定它们的相等性测试返回 true

Boolean 类型

ECMAScript 中的 Boolean 类型只有两个字面值 :truefalse 。这两个值与数字值不是一回事,因此 true 不一定等于 1,而 false 也不一定等于 0 。

虽然 Boolean 类型的字面值只有两个,但 ECMAScript 中所有类型的值都有与这两个 Boolean 值等价的值。使用转型函数 Boolean() 可以将一个值转换为其对应的 Boolean 值。

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

Number 类型

ECMAScript 中的 Number 类型使用 IEEE754 格式来表示整数和浮点数值。为了支持各种数值类型,ECMA 定义了不同的数值字面量格式。

1
2
3
var intNum = 55;	// 整数
var octalNum = 070; // 八进制整数 56
var hexNum = 0xA; // 十六进制整数 10

需要注意的是,八进制字面量在严格模式下是无效的,会导致支持的 JavaScript 引擎抛出错误。

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

浮点数值

保存浮点数值需要的内存空间是保存整数值的两倍,ECMAScript 会不失时机的将浮点数值转换为整数值。

对于那些极大或极小的数值,可以用科学表示法表示的浮点数值表示。

1
var floatNum = 3.14e7;	// 等价于 3.14 * 10^7

浮点数值的最高精度是17位小数,但在进行算数运算时其精确度远远不如整数。例如,0.1 加 0.2 的结果不是 0.3, 而是 0.30000000000000004 。这个舍入误差会导致无法测试特定的浮点数值。

数值范围

由于内存的限制,ECMAScript 并不能保存所有的数值。它能够表示的最小数值为 Number.MIN_VALUE ,在大多数浏览器中,这个值是 5e-234 。能够表示的最大数值为 Number.MAX_VALUE ,在大多数浏览器中,这个值是 1.976931348623157e+308 。如果某次计算的结果得到了一个 超出 JavaScript 数值范围的值,那么这个数值将被自动转换为特殊的 Infinity 值。如果某次计算返回了正或负的 Infinity 值,那么该值将无法继续参与下一次的计算,因为 Infinity 不是能够参与计算的数值。

可以使用 isFinite() 函数确定一个数值是不是无穷的。

NaN

NaN,即非数值,它是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数未返回数值的情况。比如在 ECMAScript 中,0 除以 0 会返回 NaN,正数除以 0 会返回 Infinity ,负数除以 0 返回 -Infinity

NaN 本省有两个非同寻常的特点。

首先,任何涉及 NaN 的操作都会返回 NaN,这个特点在多步计算中可能导致问题。

其次,NaN 与任何值都不相等,包括 NaN 本身。

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

数值转换

有三个函数可以将非数值转换为数值 :Number() , paresInt() , parseFloat()Number() 可以用于任何数据类型,而 parseInt() , parseFloat() 则用于把字符串转换成数值。

Number() 函数的转换规则如下 :

  • truefalse 将分别转换为 1 和 0
  • null 返回 0
  • undefined 返回 NaN
  • 如果是字符串,则
    • 如果只包含数字,返回十进制数值
    • 如果为十六进制格式,返回相同大小的十进制数值
    • 如果是空字符串,则返回 0
    • 如果包含除上述格式之外的字符,返回 NaN

由于 Number() 函数在转换字符串时比较复杂而且不够合理,因此处理整数的时候更常用的是 parseInt() 函数。它会忽略字符串前面的空格,直到找到第一个非空格字符。如果第一个字符不是数字字符或者负号,则返回 NaN。如果第一个字符是数字字符,parseInt() 会继续解析第二个字符,直到解析玩所有后续字符或者遇到了一个非数字字符。

需要注意的是,”1.1” 这样的字符串会被 parseInt() 转换为 1 ,因为小数点并不是有效的数字字符。

还可以为 parseInt() 函数指定第二个参数 :转换时使用的基数。

1
var num = parseInt("0xAF", 16);	// 以十六进制转换字符串	

parseInt() 不同的是,parseFloat() 只解析十进制值。

String 类型

String 类型用于表示由零活多个 16 位 Unicode 字符组成的字符序列,即字符串。字符串可以由双引号或单引号表示。

字符字面量

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

字面量 含义
\n 换行
\t 制表
\b 空格
\r 回车
\\ 斜杠
\' 单引号
\" 双引号
\xnn 以十六进制代码表示的一个字符
\unnn 以十六进制代码表示的一个 Unicode 字符
\f 进纸

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

字符串的特点

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

转换为字符串

几乎每个值都有 toString() 方法,它将返回相应值得字符串表现(nullundefined 没有这个方法 )。多数情况下,调用 toString() 方法不必传递参数,但是在调用数值的 toString() 方法时,可以传递一个表示输出数值基数的参数。

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

  • 如果值有 toString() 方法,则调用该方法
  • 如果值是 null ,则返回 “null”
  • 如果值是 “undefined” ,则返回 “undefined”

Object 类型

ECMAScript 中的对象其实就是一组数据和功能的集合。对象可以通过执行 new 操作符后跟要创建的对象类型的名称来创建。而创建 Object 类型的实力并为其添加属性和方法,就可以创建自定义对象。

在 ECMAScript 中,Object 类型是所有它的实例的基础。

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

  • constructor : 保存着用于创建当前对象的函数。
  • hasOwnProperty(propertyName) : 用于检查给定的属性在当前的对象实例中是否存在。
  • isPrototypeOf(object) : 用于检查传入的对象是否是传入的对象的原型。
  • propertyIsEnumerable(propertyName) : 用于检查给定的属性是否能够使用 for-in 语句来枚举。
  • toLocalString() : 返回与执行环境的地区对应的字符串表示。
  • toString() : 返回对象的字符串表示。
  • valueOf() : 返回对象的字符串、数值或布尔值表示。

操作符

一元操作符

递增和递减操作符

前置递增/递减操作符,变量的值都是在包含它的语句被求值之前改变的,而后置递增/递减操作符的则是在包含它的语句被求值之后改变变量的值。

而且递增/递减操作符不仅适用于整数,还可以用于字符串、布尔值、浮点数职和对象 :

  • 在应用于一个包含有效数字字符的字符串时,先将其转换为数字值,再执行加减 1 的操作。字符串变量变成数值变量。
  • 在应用于一个不包含有效数字字符的字符串时,将变量的值设置为 NaN。字符串变量变成数值变量。
  • 在应用于布尔值时,先将其转换为 1 或 0,再执行加减 1 的操作。布尔值变量变成数值变量。
  • 在应用于对象时,先调用对象的 valueOf() 方法以取得一个可供操作的值。然后对该值应用前述规则。如果结果是 NaN,则在调用 toString() 方法后再应用前述规则。对象变量变成数值变量。
加和减操作符

+ 放在数值之前,对数值不会产生影响。不过,对非数值应用 + 时,它会像 Number() 转型函数一样对这个值进行转换。

- 主要用于表示负数。对非数值应用时,先转换为数值,再转换为负数。

位操作符

位操作符用于在最基本的层次上,即按内存中表示数值的位来操作数值。ECMAScript 中的所有数值都以 IEEE-754 64 位格式存储,但位操作符并不直接操作 64 位的值,而是先将 64 位的值转换成 32 位的整数,然后执行操作,最后再讲结果转换回 64 位。

对于有符号的整数,32 位中的前 32 位用来表示整数的值,第 32 位用于表示数值的符号 :0 表示正数,1 表示负数。这个表示符号的位叫做符号位,符号位的值决定了其它位数值的格式。其中,正数以纯二进制格式存储,32 位中的每一位都表示 2 的幂,没有用到的位以 0 填充,即忽略不计。负数同样以二进制码存储,但使用的格式是二进制补码。

要注意的是,在处理有符号整数时,是不能访问位 31 的。

对数值进行位操作时,NaN 和 Infinity 会被当做 0 来处理。

默认情况下,ECMAScript 中的所有整数都是有符号整数。不过,当然也存在无符号整数,对于无符号整数来说,第 32 位不再表示符号,因为无符号整数只能是整数。

按位非

按位非操作符由 ~ 表示,执行按位非的结果就是返回数值的反码。即操作数的负值减 1.

按位与

按位与操作符由 & 表示,它有两个操作符数。从本质上来讲,按位与操作就是将两个数值的每一位对齐,然后对相同位置上的两个数执行 AND 操作。

按位或

按位或操作符由 | 表示,同样有两个操作数。从本质上来讲,按位或操作就是将两个数值的每一位对齐,然后对相同位置上的两个数执行 OR 操作。

按位异或

按位异或操作符由 ^ 表示,同样有两个操作数。从本质上来讲,按位异或操作就是将两个数值的每一位对齐,然后对相同位置上的两个数执行 XOR 操作。

左移

左移操作符由 << 表示,它会将数值的所有位向左移动指定的位数,出现的空位用 0 进行填充。

注意,左移不会影响操作数的符号位。

有符号右移

右移操作符由 >> 表示,它会将数值的所有位向右移动指定的位数,但保留符号位,原数值中出现的空位以符号位的值来填充所有空位。

无符号右移

无符号左移由 <<< 表示,它会将数值的所有 32 位都向右移动。对正数来说,无符号右移的结果与有符号右移相同。但对负数来说,其结果就不一样了,而且无符号右移会将负数的二进制码当成整数的二进制码,因此导致无符号右移后的结果非常之大。

布尔操作符

逻辑非

逻辑非操作符由 ! 表示,可以应用于 ECMAScript 中的任何值。无论这个值是什么数据类型,这个操作符都会返回一个布尔值。逻辑非操作符首先会将它的操作数转换为一个布尔值,然后再对其求反。

逻辑非操作符也可以用于讲一个值转换为与其对应的布尔值。而同时使用两个逻辑非操作符,实际上就会模拟 Boolean() 转型函数的行为。

逻辑与

逻辑与操作符由 && 表示,有两个操作数。逻辑与操作可以应用于任何类型的操作数,而不仅仅是布尔值。在有一个操作数不是布尔值的情况下,逻辑与操作就不一定返回布尔值 :

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

逻辑与操作属于短路操作,即如果第一个操作数能够决定结果,那么就不会对第二个操作数求值。

注意,不能在逻辑与操作中使用未定义的值。

逻辑或

逻辑或操作由 || 表示,有两个操作数。与逻辑与操作相似,如果有一个操作数不是布尔值,逻辑或也不一定返回布尔值 :

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

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

乘性操作符

ECMAScript 定义了 3 个乘性操作符 :乘法、除法和求模,在操作数为非数值的情况下会执行自动的类型转换。

乘法

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

  • 如果乘积结果超过了 ECMAScript 的数值表示范围,则返回 Infinity-Infinity
  • 如果有一个操作数是 NaN ,则结果是 NaN
  • Infinity 与 0 相乘,结果是 NaN
  • Infinity 与非 0 数值相乘,结果是 Infinity-Infinity ,取决于有符号操作数的符号
  • 如果 InfinityInfinity 相乘,则结果是 Infinity
  • 如果有一个操作数不是数值,则使用 Number() 将其转换为数值
除法

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

  • 如果商超过了 ECMAScript 的数值表示范围,则返回 Infinity-Infinity
  • 如果有一个操作数是 NaN ,则结果是 NaN
  • InfinityInfinity 除,结果是 NaN
  • 如果 0 被 0 除,结果是 NaN
  • 如果非零的有限数被零除,则结果是 Infinity-Infinity ,取决于有符号的操作数
  • 如果 Infinity 被任何非 0 数值除,则结果是 Infinity-Infinity ,取决于有符号的操作数
  • 如果有一个操作数不是数值,则使用 Number() 将其转换为数值
求模

求模操作符由 % 表示。

  • 如果被除数是无穷大值而出除数是有限大的数值,则结果是 NaN
  • 如果被除数是有限大的数值而除数是 0 ,则结果是 NaN
  • 如果是 InfinityInfinity 除,则结果是 NaN
  • 如果被除数是有限大的数值而除数是无穷大的数值,则结果是被除数
  • 如果被除数是 0 ,则结果是0
  • 如果有一个操作数不是数值,则使用 Number() 将其转换为数值

加性操作符

加法

加法操作符由 + 表示。

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

  • 如果一个操作数是 NaN ,则结果是 NaN
  • 如果是 InfinityInfinity ,则结果是 Infinity
  • 如果是 -Infinity-Infinity ,则结果是 -Infinity
  • 如果是Infinity-Infinity ,则结果是 NaN

如果有一个操作数是字符串 :

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

如果有一个操作数是对象、数值或布尔值,则调用它们的 toString() 方法取得相应的字符串值,然后再应用前述规则。

对于 undefinednull ,则分别调用 String() 函数并取得字符串 “undefined” 和 “null” 。

减法

减法操作符由 - 表示。

  • 如果有一个操作数是 NaN ,则结果是 NaN
  • 如果是 InfinityInfinity ,则结果是 NaN
  • 如果是 -Infinity-Infinity ,则结果是 NaN
  • 如果是 Infinity-Infinity ,则结果是 Infinity
  • 如果是 -InfinityInfinity ,则结果是 Infinity
  • 如果有一个操作数是字符串、布尔值、nullundefined ,则先调用 Number() 函数将其转换为数值,然后在按照前述规则执行减法计算。如果转换的结果是 NaN ,则减法的结果就是 NaN
  • 如果有一个操作数是对象,则调用对象的 valueOf() 方法以取得表示该对象的数值。如果得到的是 NaN ,则减法的结果就是 NaN 。如果对象没有 valueOf() 方法,则调用其 toString() 方法并将得到的字符串转换为数值

关系操作符

与 ECMAScript 中其它操作符一样,当关系操作符的操作数使用了非数值时,也要进行数据转换或完成某些奇怪的操作 :

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

在比较两个字符串时,实际比较的是两个字符串中对应位置的每个字符的字符编码值,因此一般需要将两个操作数转换为相同的大小写形式,然后再执行比较。

相等操作符

相等和不相等

ECMAScript 中的相等操作符由 == 表示,如果两个操作数相等,则返回 true 。而不相等操作符由 != 表示,如果两个操作数不相等,则返回 true 。这两个操作符都会先转换操作数,然后在比较它们的相等性。

在转换不同的数据时,需遵守以下规则 :

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

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

  • nullundefined 时相等的
  • 比较相等性之前,不能将 nullundefined 转换为其它任何值
  • 如果有一个操作数是 NaN ,则相等操作符返回 false ,而不相等操作符返回 true
  • 如果两个数都是对象,则比较它们是否是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回 true ;否则, 返回 false

特殊情况 :

1
2
3
4
5
6
7
8
9
10
11
null == undefined 	// true
true == 1 // true
true == 2 // false
"NaN" == NaN // false
5 == NaN // false
undefined == 0 // false
NaN == NaN // false
null == 0 // false
NaN != NaN // true
"5" == 5 // true
false == 0 // true
全等和不全等

除了在比较之前不能转换为操作数之外,全等和不全等操作符与相等和不相等操作符没有什么区别。全等操作符由 === 表示,它只在两个操作数未经转换就想等的情况下返回 true

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

条件操作符

1
variable = boolean_expression ? true_value : false_value;	

基于对 boolean_expression 的求值结果,决定给变量 variable 赋什么值。

语句

if 语句、do-while 语句、while 语句、for 语句、for-in 语句与其它语言并没有不同,在此不做介绍。

label 语句

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

breakcontinue 语句

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

with 语句

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

1
with (expression) statment;

定义 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 语句改写

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

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

由于大量使用 with 语句会导致性能下架,同时也会给调试代码造成困难,因此在开发大型应用程序时,不建议使用 with 语句。

swithc 语句

ECMAScript 中的 switch 有两个特点 :

  • 可以在 switch 中使用任何数据类型
  • 每个 case 的值不一定是常量,可以是变量,甚至是表达式

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

函数

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

1
2
3
function functionName (arg_0, arg_1, ...) {
statments
}

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

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

严格模式下 :

  • 不能把函数或参数命名为 eval 或 arguments

  • 不能出现两个命名参数同名的情况

参数

ECMAScript 函数的参数不介意传递进来多少个参数,也不在乎传进来的参数是什么数据类型。ECMAScript 中函数的参数在内部是用一个数组来表示的,函数接收到的始终都是这个数组,而不关心数组中包含哪些参数。在函数体内,可以通过 arguments 对象来访问这个参数数组,从而获得传递给函数的每一个参数。

命名的参数只提供便利,但并不是必需的。而且在 ECMAScript 中,解析器不会验证命名参数。

没有传递值得命名参数将自动被赋予 undefined 值。

严格模式对如何使用 arguments 对象做出了一些限制 :无法对命名参数进行赋值;重写 arguments 的值会导致语法错误。

arguments 本质上并不是一个数组,只是与数组类似,你可以使用方括号语法来访问它的每一个元素。

没有重载

ECMAScript 中函数的参数有包含零或多个值得数组来表示,所以没有函数签名。而没有函数签名,真正的重载是不可能做到的。如果在 ECMAScript 中定义了同名函数,则该名字只属于后定义的函数。