写在最前

基于 Marijn Haverbeke 的 Eloquent JavaScript 编写

JavaScript 和 Java 没有任何关系,这个名字是为了蹭 Java 的热度

console.log('Hello, world!');

值,类型,操作符

数字,64 位,包括小数

算术,% 是取余

特殊的数:Infinity-InfinityNaN

字符串有三种方式:``, ””, '',使用 Unicode,特殊字符需要 \ 转义,第一个中可以使用 ${100 / 2} 等模板字面义

typeof 操作符输出类型,如 typeof 4.5

布尔值,注意 NaN != NaN

逻辑操作:$$||,短路

三元运算符:? :

空值:nullundefined,注意 null == undefined

自动类型转换,如 “” == false

如果在比较时不想要自动类型转换,则使用 ===!==,如 “” !== false

程序结构

语句有时可以不用分号隔开,但保险起见还是都用分号隔开

绑定,或变量:let a = 5;;还有另一种写法 var,是老式的写法,和 let 的区别在于作用域只限定于函数,而 let 支持块作用域。通常无脑使用 let

常量使用 const,注意是其指向的对象固定,即可以向一个 const 列表中添加元素

未赋初值的绑定的值是 undefined

绑定名称可以使用 $ 和 _

ifwhileforbreakswitch-case

命名推荐使用驼峰命名法,如 JavaScript

函数

定义函数有三种方法:

const square1 = function (x) {
return x * x;
}; // 注意有分号

// 函数声明,区别在于会自动提到最前面,即可以在这之前就调用 square2 函数
function square2(x) {
return x * x;
} // 无分号

const square3 = (x) => {
return x * x;
};
// 或
const square3_2 = (x) => x * x;

可以在函数中定义函数

参数是可选的,即可以传递过多或过少的参数

suqare(4, true, 'fdjoasdj'); // 合法

也可以设置参数默认值

闭包:把函数视为值,返回函数,如

function multiplier(factor) {
return (number) => number * factor;
}
let twice = multiplier(2);
twice(2);

数据结构:对象和阵列

定义列表:let listOfNumbers = [0, 1, 2, 3, 4];

属性,如 listOfNumbers.length

访问属性的方法有两种,还有一种是 listOfNumbers["length"],注意这个会计算方括号内的表达式

对象还有方法,如 listOfNumbers.push(5);

对于列表,尾部操作:push()pop(),头部操作:shift()unshift()

对象的定义类似于函数

let day1 = {
squirrel: false,
'daily events': ['work', 'running'], // 属性名可以用空格分割
};

可以后续添加属性:day1["finished"] = true;

delete 可以用于删除某个属性

Object.keys() 函数可以返回属性名的列表,用其可以遍历所有属性

Object.assign() 函数从其他对象复制所有属性到某个对象上

注意在对象的比较时只有两个绑定指向同一个实体时 == 才会返回 true,即 JavaScript 中没有深度比较

数组的遍历有简单的方法:

for (let entry of JOURNAL) {
...
}

反过来已知值获取索引有 .indexOf().lastIndexOf() 方法,还有切片方法:.slice()

对于字符串,有 .trim().padStart()repeat().join().split() 等方法终于有类似 Java 的地方了

可以使用剩余参数 ... 来接收任意个数的参数,如

function max(...numbers) {}

也可以使用这个记号展开列表:

let numbers = [7, 9, 0];
max(10, ...numbers, 6);

Math 对象提供了很多有用的数学函数,如 Math.random()

可以解析列表:

function phi([n1, n2]) {}

对象也适用:let {name} = {name: "Faraji", age: 23};

JSON 文件可用于序列化数据,与 JavaScript 的对象格式基本一致不然为什么叫 JavaScript Object Notation,但是名称必须用引号,如

{
"squirrel": false,
"events": ["work", "running"]
}

可以使用 JSON.stringify() 将数据转化为 JSON 字符串格式;使用 JSON.parse() 则转化 JSON 字符串为 JS 对象

高阶函数

传递函数为参数

filter()map()reduce() 等取代循环的方法

对象生命的秘密

封装,方法

原型:可以用 Object.getPrototypeOf() 获取某对象的原型,原型定义为属性 .prototype

所有对象的最终原型都是 Object.prototype

使用 Object.create() 创建一个特定的原型

可以直接把函数作为类,也可以显示编写类:

class Rabbit {
constructor(type) {
this.type = type;
}
speak(line) {
console.log(`The ${this.type} rabbit says '${line})'`);
}
}
let killerRabbit = new Rabbit('killer');

注意到类中可以有方法,但没有属性,必须后面手动设定,如

Rabbit.prototype.teeth = 'small';

原型属性可以被覆盖

JS 中还有映射类 Map,相当于字典,有方法 has()set()get()

多态:例如可以重载 toString

Rabbit.prototype.toString = function () {
return `a ${this.type} rabbit`;
};

属性名称实际上不是字符串,而是 Symbol

可迭代对象有一个 Symbol.iterator 方法,其有一个 next 方法,返回 value-done

一些似乎可以“直接”访问的属性其本质上也通过了函数调用,如 .size;定义方法为在方法名前加 getset

继承,如 class SymmetricMatrix extends Matrix

调用父类的方法则使用 super

instanceof 返回是否是某个类的实例,所有对象都是 Object 的实例

Bug 和错误

在程序或函数开头可以加上 “use strict” 进入严格模式,此处 JS 语法更严格

JavaScript 的强类型版本——TypeScript

编写测试

对于出错,可以返回一个特殊值,如 nullundefined-1

抛出异常:throw new Error("message");

捕捉异常:

try {
...
} catch(error) {
...
}

注意这会捕捉所有异常,如果想要捕捉特定类型的异常,可以

class InputError extends Error();
try {} catch (e) {if (e instanceof InputError) {} else {}}

正则表达式

两种写法:

let re1 = new RegExp('abc');
let re2 = /abc/;

调用:test("")exec()search()replace()

模块

模块,包

包管理器的 NPM

CommonJS 的引入模块方案:const ordinal = require("ordinal")

新版的方案:

import ordinal from "ordinal"; // 引入
export function formatData(data, format) {...} // 导出
export default ["Spring", "Winter"] // 不指定名字的默认导出

可能会使用某些 JS 方言编写,在发布时为了兼容性,会编译为较老版本的 JS

因为一个个小的模块不方便传输,所以通常会在发布时合并成较大的模块,叫做 bundling

为了节省空间,在发布时可能删除空格和注释,替换代码等,叫 minifier

异步编程

JavaScript 完全使用单线程

回调 callback 函数:当动作结束时调用函数,如 setTimeout(() => console.log("Tick"), 500);

JS 中是可能在某个时刻完成并产生某个值的异步 action,创建的办法是 Promise.resolve()

.then() 在 promise 产生一个值的时候调用回调函数

let fifteen = Promise.resolve(15);
fifteen.then((value) => console.log(`Got ${value}`));

对于异步中的异常,有 Promise.reject() 函数

对于 promise 集合,Promise.all() 返回一个等待所有 promises 被解决并产生值的 promise

可以像同步一样异步编程,只需要在函数之前加上 async,等待获得的值前加上 await,如

async function findInStorage(nest, name) {
let local = await storage(nest, name);
}

可以暂停和恢复的函数类似于异步,但是没有 promise,这种函数叫 generator,只有在调用 .next() 时才会产生下一个值,其在 function 后加上 *,使用 yield 返回数据,如 iterator 可以简化为

Group.prototype[Symbol.iterator] = function* () {
for (let i = 0; i, this.members.length; i++) yield this.members[i];
};

异步调度

JavaScript 和浏览器

html 中的标签:<script> </script>

浏览器中环境隔离

文档对象模型

DOM 类似于一棵树的结构

书上的一些遍历操作:.childNodesELEMENT_NODE

获取元素:.getElementsByTagName("a").getElementById("")

改变文档:

  • 删除 .remove()
  • 添加 .appendChild()
  • 替换 .replaceChild(新结点,被替换结点)
  • 插入 .insertBefore(新结点,插入结点)
  • 创建结点 .createTextNode()

可以使用 .getAttribute().setAttribute() 操作元素的属性

调整布局:.offsetWidthoffsetHeightclientWidthclientHeigtht

访问 style 属性修改样式

css

动画:requestAnimationFrame()

.querySelectorAll().querySelector().getElementByTagName() 等不同,其返回的对象不是活着的,即当改变文档时不会改变。且依旧不是一个真的数组,需要使用 Array.from()

处理事件

一个点击的例子:

<script>
windows.addEventListener('click', () => {
console.log('You knocked?');
});
</script>

第二个参数就是一个 event handler

事件对象可能有一些信息,如

<script>
button.addEventListener('mousedown', (event) => {
if (event.button == 0) console.log('Left button');
});
</script>

事件处理器按照从小到大的顺序发生,如果想要阻止传递,使用 event.stopPropagation()

很多事件已经定义了默认行为,可以使用 event.preventDefault() 显示阻止该事件的默认行为

键盘:keydownkeyupctrlKeymetaKey 属性等

对于指针,有 clientX 等属性

scroll 事件

focus 事件

load 事件

用 canvas 画图

有两种画图的方式:Scalable Vector Graphics(SVG)canvas,这里主要介绍 canvas

canvas 有两种支持的绘画风格:2dwebgl,后者通过 OpenGL 接口画三维图形,这里只讨论 2d

通过 getContext 方法创建上下文:

<p>Before canvas.</p>
<canvas width="120" height="60"></canvas>
<p>After canvas.</p>
<script>
let canvas = document.querySelector('canvas');
let context = canvas.getContext('2d');
context.fillStyle = 'red';
context.fillRect(10, 10, 100, 50);
</script>

path 是线的序列,回自动闭合,可以通过控制路径来画图形的轮廓,如下面是一个三角形:

<canvas></canvas>
<script>
let cx = document.querySelector('canvas').getContext('2d');
cx.beginPath();
cx.moveTo(50, 10);
cx.lineTo(10, 70);
cx.lineTo(90, 70);
cx.fill();
</script>

HTTP 和窗体

浏览器发送的 request 类似于:

GET /18_http.html HTTP/1.1
Host: eloquentjavascript.net
User-Agent: Your browser's name

服务器的 respond 类似于:

HTTP/1.1 200 OK
Content-Length: 65585
Content-Type: text/html
Last-Modified: Mon, 08 Jan 2018 10:29:45 GMT
<!doctype html>
... the rest of the document

一个使用 fetch 获取信息的例子:

fetch('example/data.txt').then((response) => {
console.log(response.status);
console.log(response.headers.get('Content-Type'));
});

浏览器一般有沙盒保护,不允许跨域请求

有以下场景的 form 类型:textpasswordcheckboxradio(多选域)、file(允许用户从计算机上选择文件)

<textarea> 用于多行文本域

<select> tag 配合 <option> 可供选择

当一个文本域被 focus 时,可以输入文本

disabled 显示灰色,表示无法被选中,可以直接使用,如

<button disabled>I'm out</button>

一个简单的 form:

<form action="example/submit.html">
Name: <input type="text" name="name" /><br />
Password: <input type="password" name="password" /><br />
<button type="submit">Log in</button>
</form>

change 事件只在域失去焦点时才会触发

一个从用户选择的文件中读取数据的例子:

<input type="file" multiple />
<script>
let input = document.querySelector('input');
input.addEventListener('change', () => {
for (let file of Array.from(input.files)) {
let reader = new FileReader();
reader.addEventListener('load', () => {
console.log(
'File',
file.name,
'starts with',
reader.result.slice(0, 20)
);
});
reader.readAsText(file);
}
});
</script>

localStorage 对象用于在客户端侧储存数据:

localStorage.setItem('username', 'old_driver_zero');
console.log(localStorage.getItem('username'));
localStorage.removeItem('username');

Node.js

除了浏览器以外,另一个可以运行 JavaScript 的环境

使用方法:node hello.js

注意 process.argv 的第一个参数是 node,第二个是文件名(*.js

使用 CommonJS 的模块系统,支持相对路径和绝对路径,默认为查找 node_modules/ 中的模块

npm 可用于安装包

注意依赖中的版本号如 ^2.3.0 表示支持至少为 2.3.0 且小于 3.0.0 的版本

与浏览器不同,Node.js 运行我们读写文件,如

const { readFile } = require('fs').promises;
readFile('file.txt', 'utf8').then((text) =>
console.log('The file contains:', text)
);

支持 http 模块:

这是一个客户端:

const { request } = require('http');
let requestStream = request(
{
hostname: 'eloquentjavascript.net',
path: '/20_node.html',
method: 'GET',
headers: { Accept: 'text/html' },
},
(response) => {
console.log('Server responded with status code', response.statusCode);
}
);
requestStream.end();