0%

JavaScript 权威指南 01:JavaScript 简介

本系列文章是《JavaScript 权威指南》第七版的读书笔记, 《JavaScript 权威指南》的作者是 David Flanagan,该书是 JavaScript 的经典书籍,被大家称为 犀牛书。该书的目标是全面、权威地讲解 JavaScript 语言,对 JavaScript 程序中可能用到的最重要的客户端 API 和服务器端 API 提供深入的介绍。

JavaScript 简介

JavaScript 是 Web 编程语言。绝大多数网站都使用 JavaScript,所有现代 Web 浏览器都包含 JavaScript 解释器,这让 JavaScript 成为有史以来部署最广泛的编程语言。而 Node.js 让浏览器之外的 JavaScript 编程成为可能,Node 的巨大成功意味着 JavaScript 如今也是软件开发者最常用的编程语言。

JavaScript 是一门高级、动态、解释型编程语言,非常适合面向对象和函数式编程风格。JavaScript 这个名字相当有误导性,其实它和 Java 没有任何关系。

JavaScript 是 Netscape 在 Web 诞生初期创造的,之后它将该语言提交给 ECMA 组织进行标准化。由于商标的问题,这门语言的标准版本使用 ECMAScript 这个名字。实践中大家仍称这门语言为 JavaScript。一般在讨论这门语言的标准及版本时使用 ECMAScript 或其缩写 ES

  • 2010 年以来,几乎所有浏览器都支持 ECMAScript 5 标准
  • ES6 发布于 2015 年,增加了重要的新特性。这些新特性把 JavaScript 从一门脚本语言转变为一门适合大规模软件工程的严肃、通用语言
  • 从 ES6 开始,ECMAScript 规范每年发布一次,语言的版本也以发布的年份来标识(例如 ES2016、ES2017 等)

JavaScript 发展过程中,也会纠正早期版本的缺陷,但是由于兼容性,某个特性即使问题严重,也不能将其删除。ES5 及其之后,可以选择切换到 JavaScript 严格模式,在这种模式下,某些早期的语法错误会得到纠正。在 ES6 及之后,使用新语言特性经常会隐式触发严格模式。在这些上下文中,不能使用老旧、有缺陷的特性。

核心 JavaScript 语言定义了最小限度的 API,可以操作数值、文本、数组、集合、映射等,但不包含任何输入和输出功能。输入和输出(以及更复杂的特性,如联网、存储和图形处理)是内嵌 JavaScript 的 宿主环境 的责任

  • 浏览器是 JavaScript 最早的宿主环境,也是 JavaScript 最常见的运行环境。浏览器环境允许 JavaScript 代码从用户的鼠标和键盘或者通过发送 HTTP 请求获取输入,也允许 JavaScript 代码通过 HTML 和 CSS 向用户显示输出
  • 2010 年之后,Node 的出现让 JavaScript 有了一种新的宿主环境。与限制 JavaScript 只能使用浏览器提供的 API 不同,Node 给与 JavaScript 完整的操作系统访问权限,这使得 JavaScript 程序可以读写文件、通过网络传送数据、处理 HTTP 请求等

探索 JavaScript

如果只是想要尝试少量 JavaScript 代码,可以打开浏览器的开发者工具,然后选择 Console 标签页,之后就可以在提示符后面输入代码。另外一种方式是下载并安装 Node,安装 Node 之后,直接输入 node 命令,既可以开始交互式的 JavaScript 会话。

1
2
3
4
5
6
7
# node
Welcome to Node.js v18.16.0.
Type ".help" for more information.
> console.log("hello javascript")
hello javascript
undefined
>

Hello, World

当要编写更长的代码时,这种以行为单位的交互环境就不合适了。此时就需要使用文本编辑器(或者 IDE)来编写 JavaScript 代码,然后将其保存到文件中(JavaScript 代码文件通常以 .js 扩展名结尾),再使用 Node 来运行这个 JavaScript 代码文件。

例如,创建一个名为 hello.js 的文件,内容如下:

1
2
// console 在终端窗口或者浏览器开发者工具的控制台中输出信息
console.log("Hello World!");

接下来使用 node 来执行该文件:

1
2
# node hello.js
Hello World!

如果想要在浏览器中运行该脚本文件,则需要通过 html 文件来引入该 js 文件:

1
<script src="hello.js"></script>

在浏览器中打开该 html 文件,就可以在浏览器开发者工具的控制台中看到 Hello World!

JavaScript 之旅

如下是一个更复杂的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// define variable
let x;

x = 0;
x


x = 1;
x = 0.01;
x = "hello world";
x = 'javascript';
x = true;
x = false;
x = null;
x = undefined;

// javascript object
let book = {
topic: "javascript",
edition: 7
};

book.topic;
book["edition"]

book.author= "Flanagan";
book.contents = {};

book.contents?.ch01?.sect1

// javascript array
let primes = [2, 3, 5, 7];
primes[0]
primes.length
primes[primes.length - 1]
primes[4] = 9;
primes[4] = 11;

let empty = [];
empty.length

let points = [
{x: 0, y: 0},
{x: 1, y: 1}
];
let data = {
trial1: [[1, 2], [3, 4]],
trial2: [[2, 3], [4, 5]]
};
  • JavaScript 以 // 开始注释内容,// 后面的文字都是注释内容
  • 变量使用 let 关键字声明,变量是一个代表值的名字
  • 使用 = 为变量赋值
  • JavaScript 支持数值(整数或实数)、字符串(双引号或单引号所引用的字符序列)、布尔值、null(特殊值,意思为没有值)、undefined(特殊值,表示变量已被声明但未初始化)
  • JavaScript 支持对象,对象是一个 名/值 对的集合,或者一个字符串到值的映射。支持 ?. 条件式访问属性
  • JavaScript 支持数组(以数值作为索引的列表)
  • 中括号中罗列出数组元素 以及在 大括号中将对象属性名映射为属性值 的语法被称为 初始化表达式

表达式在 JavaScript 中就是一个短语,可以求值产生一个值。JavaScript 构造表达式最常见的方式是使用操作符。变量名也是表达式。表达式只用于计算值,什么也不做,即不以任何方式改变程序的状态

JavaScript 语句则像一个完整的句子,语句没有值,但是却会改变状态,例如变量声明语句和赋值语句。

函数是一个有名字、有参数的 JavaScript 代码块,只要定义一次就可以反复调用。函数也是值,可以赋给变量。如下是一个示例:

1
2
3
4
5
6
7
8
9
function plus1(x) {
return x + 1;
}

let square = function(x) {
return x * x;
};

console.log(square(plus1(3)));

ES6 及之后,有一种定义函数的简写方式。这种简洁的语法使用 => 来分隔参数列表和函数体,因此以这种方式定义的函数被称为箭头函数。箭头函数常用于把一个未命名的函数作为参数传递给另一个函数:

1
2
3
const plus1 = x => x + 1;
const square = x => x * x;
console.log(square(plus1(3)));

在通过对象使用函数时,我们称其为方法。所有的 JavaScript 对象(包括数组)都可以有方法,我们可以将函数赋值给对象的属性,从而为对象添加方法。此时 this 关键字引用的是方法所在的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let a = [];
a.push(1, 2, 3);
a.reverse();

let points = [
{x: 0, y:0},
{x: 1, y:1},
];
points.dist = function() {
let p1 = this[0];
let p2 = this[1];
let a = p2.x - p1.x;
let b = p2.y - p1.y;

return Math.sqrt(a * a + b * b);
};

console.log(a);
console.log(points.dist());

接下的实例则演示了 JavaScript 中常用的控制结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
function abs(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}

console.log(abs(-10) == abs(10))

function sum(array) {
let sum = 0;

for (let x of array) {
sum += x;
}

return sum;
}
console.log(sum([1, 2, 3]));

function factorial(n) {
let product = 1;
while (n > 1) {
product *= n;
n--;
}

return product;
}

console.log(factorial(4));

function factorial2(n) {
let i, product = 1;
for (i = 2; i <= n; i++) {
product *= i;
}

return product;
}
console.log(factorial2(4));

JavaScript 支持面向对象的编程风格,但与 经典的 面向对象编程语言非常不一样。如下是一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}

distance() {
return Math.sqrt(this.x * this.x +
this.y * this.y);
}
}

let p = new Point(1, 1);
console.log(p.distance());
  • 按照惯例,类名需要首字母大写
  • 构造函数用于初始化新实例,this 代表要初始化的对象,构造函数不需要 return 语句

字符频率柱形图

如下是一个更复杂的 JavaScript 程序,使用了一些 JavaScript 高级特性,通过这个程序可以感受真正的 JavaScript 长什么样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class DefaultMap extends Map {
constructor(defaultValue) {
super();
this.defaultValue = defaultValue;
}

get(key) {
if (this.has(key)) {
return super.get(key);
} else {
return this.defaultValue;
}
}
}

class Histogram {
constructor() {
this.letterCounts = new DefaultMap(0);
this.totalLetters = 0;
}

add(text) {
text = text.replace(/\s/g, "").toUpperCase();
for (let c of text) {
let count = this.letterCounts.get(c);
this.letterCounts.set(c, count + 1);
this.totalLetters++;
}
}

toString() {
let entries= [...this.letterCounts];
entries.sort((a, b) => {
if (a[1] == b[1]) {
return a[0] < b[0] ? -1 : 1;
} else {
return b[1] - a[1];
}
})

for (let entry of entries) {
entry[1] = entry[1] / this.totalLetters * 100;
}

let lines = entries.map(
([l, n]) => `${l}: ${'#'.repeat(Math.round(n))} ${n.toFixed(2)}%`
);

return lines.join("\n");
}
}

async function histogramStdin() {
process.stdin.setEncoding("utf-8");
let histogram = new Histogram();
for await (let chunk of process.stdin) {
histogram.add(chunk);
}
return histogram;
}

histogramStdin().then(histogram => {console.log(histogram.toString()); });

该程序可以统计所输入zi符串中每个字符的频率,并以柱形图的形式展示:

1
2
3
4
5
# node diagram.js
test
T: ################################################## 50.00%
E: ######################### 25.00%
S: ######################### 25.00%