《JavaScript高级程序设计》笔记
学习方法:全部内容都要看,内容能理解换下一个,无法理解上b站看解说,找不到解说上Google看文章,依然无法理解,那么抱着疑问回到书中继续往下看吧
第一章 简介
1.2 JavaScript实现
- 完整的JavaScript实现包含:
- 核心(ECMAScript)
- 文档对象模型(DOM)
- 浏览器对象模型(BOM)
- ECMAScript提供语言核心功能;DOM提供与网页内容交互的方法和接口;BOM提供与浏览器交互的方法和接口
第二章 HTML中的JavaScript
2.1 Script元素
标签位置:为了避免加载JS文件导致页面空白,现代Web应用程序通常将所有JS引用放在
元素中的页面内容后面延迟脚本:defer属性脚本在执行时不会影响页面加载,脚本会被延迟到整个页面都解析完毕后再运行;延迟脚本不一定总会按顺序执行,因此最好只包含一个脚本
1
<script defer src="example1.js"></script>
异步脚本:async属性脚本同样不会影响页面加载,脚本下载完不管页面是否解析完直接运行;同样不一定会按顺序执行,异步脚本不建议修改DOM,可能会出现BUG,它和延迟都是外部脚本
1
2<script async src="example1.js"></script>
<script async src="example2.js"></script>
2.3 文档模式
最初的文档模式:
- 混杂模式(quirks mode)
- 标准模式(standards mode)
标准模式:
1
2<!-- HTML5 -->
<!DOCTYPE html>
第三章 语言基础
3.1 语法
- 区分大小写:ECMAScript中一切都区分大小写,变量test和变量Test是两个不同的变量
- ECMAScript标识符使用驼峰大小写形式:firstSecond
- 严格模式:在严格模式下,ECMAScript3的一些不规范写法在这种模式下会被处理,对于不安全的活动将抛出错误;顶部加上这一行启用严格模式:
1
;
3.3 变量
- 严格模式下,如果像这样给未声明的变量赋值,则会导致抛出ReferenceError
- 不使用var,const优先,let次之;使用const声明保持常量,不合理的赋值操作会报错,只在未来有修改再使用let
3.4 数据类型
- 简单数据类型:Undefined、Null、Boolean、Number、String、Symbol;复杂数据类型:Object
- typeof操作符,用来检测变量数据类型
- Undefined类型:当使用var或let声明了变量但没有初始化时,相当于给变量赋予了undefined
- Null类型:null值表示一个空对象指针;只要变量要保存对象,而当时又没有那个对象可保存,就要用null来填充该变量
- Boolean类型:其他类型的值转换为布尔值,可以调用特定的Boolean()转型函数
- Number类型:其他类型的值转换为数值,常用parseInt()函数;如果第一个字符不是数值字符、加号或减号返回NaN,第二个参数用来指定进制数
- String类型:字符串是不可变的;其他类型的值转换为字符串,使用函数toString()或String()
- Symbol类型:符号实例是唯一、不可变的;创建Symbol()实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性
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// 定义Symbol永远都不会一样
let a = Symbol('hello');
let b = Symbol('hello');
console.log(a === b); // false
console.log(a.description); // hello
// 如果声明过'hi',系统会拿过来用所以为true
let c = Symbol.for('hi');
let d = Symbol.for('hi');
console.log(c === d); // true
console.log(Symbol.keyFor(c)); // hi
// 同名查询成绩应用场景
let user1 = {
name: '幽蓝',
key: Symbol()
};
let user2 = {
name: '幽蓝',
key: Symbol()
};
let grade = {
[user1.key]: {css: 100, js: 89},
[user2.key]: {css: 57, js: 32}
};
console.log(grade); - Object类型
创建对象的方法:
1 | let o = new Object(); |
Object实例都有如下属性和方法:
- constructor: 用于创建当前对象的函数
- hasOwnProperty(propertyName): 用于判断当前对象实例(不是原型)上是否存在给定的属性
- isPrototypeOf(object): 用于判断当前对象是否为另一个对象的原型
- propertyIsEnumerable(propertyName): 用于判断给定的属性是否可以使用for-in语句枚举
- toLocaleString(): 返回对象的字符串表示,该字符串反映对象所在的本地化执行环境
- toString(): 返回对象的字符串表示
- valueOf(): 返回对象对应的字符串、数值或布尔值表示
3.5 操作符
ECMAScript7新增指数操作符
1
2
3console.log(3 ** 2); // 3*3 = 9
console.log(3 ** 3); // 3*3*3 = 27
console.log(16 ** 0.5); // 4字符串和字符串比较时,它们比较的是字符编码,所以看起来会很怪
1
console.log('23' < '3'); // true
数值和字符串比较时,字符串会先被转换为数值,这样能保证结果正确;如果不能转换成数值,就转换成NaN
任何操作数与NaN比较时,结果都会返回false
1
2
3
4console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
console.log(NaN > NaN); // false
console.log(NaN < NaN); // false相等(==)全等(===),相等比较时会转换数据类型,而全等不会转换数据类型直接比较
1
2console.log('55' == 55); // true,转换后相等
console.log('55' === 55); // false,不相等,因为数据类型不一样条件操作符
1
2
3
4
5variable = boolean_expression ? true_value : false_value;
// 如果100大于50,则将'答对了'赋值给faqNumber,否则将'答错了'赋值给faqNumber
let faqNumber = (100 > 50) ? '答对了':'答错了';
console.log(faqNumber); // 答对了
3.6 语句
for-in语句:用于枚举对象中的非符号键属性
1
2
3for (property in expression) {
...
}for-of语句:用于遍历可迭代对象的元素
1
2
3for (property of expression) {
...
}break和continue都可以与label语句一起使用;通常是在嵌套循环中
1
2
3
4
5
6
7
8
9
10
11let num = 0;
outermost:
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
if (i == 5 && j == 5) {
break outermost;
}
num++;
}
}
console.log(num); // 55
3.7 函数
- 函数参数:参数在内部是用一个数组来表示的,函数接收到的始终都是这个数组,而不关心数组中包含哪些函数;通过arguments对象来访问这个参数数组;命名的参数只提供便利,但不是必须的;arguments对象中的值与对应的命名参数的内存空间是独立的,但它们的值会同步
1
2
3
4
5
6
7
8
9
10function example(name, age) {
console.log('arguments', arguments);
console.log('name:', name, 'age:', age);
name = '幽蓝';
console.log(arguments[0]);
}
example('金涛', '20', 'myFriend');
// arguments: ['金涛', '20', 'myFriend']
// name: 金涛 age: 20
// 幽蓝
第四章 变量、作用域与内存
4.1 原始值与引用值
在操作对象时,实际上操作的是对象的引用而不是对象的本身
从一个变量到另一个变量复制基本类型值时,会创建这个值的副本;从一个变量到另一个变量复制引用类型值时,复制的值实际上是一个指针,它指向存储在堆内存中的对象,复制之后两个变量指向同一个对象
1
2
3
4
5
6
7
8
9let n1 = 1;
let n2 = n1;
n1 = 2;
console.log(n2); // 1
let o1 = {};
let o2 = o1;
o1.name = '幽蓝';
console.log(o2.name); // 幽蓝传递参数:参数只能按值传递,参数为对象时,在函数内部访问的是同一个对象
1
2
3
4
5
6
7
8function setName(o){
o.name = '幽蓝';
o = {};
o.name = '金涛';
}
let p = {};
setName(p);
console.log(p.name); // 幽蓝确定一个值是哪种基本类型可以使用typeof操作符,而确定一个值是哪种引用类型可以使用instanceof操作符
4.2 执行上下文与作用域
- 执行上下文(也称为作用域)分全局上下文、函数上下文和块级上下文;每个上下文都有一个关联的变量对象;上下文中的代码在执行的时候,会创建变量对象的一个作用域链;作用域链的最前端始终是正在执行上下文的变量对象,全局上下文的变量对象是作用域链的最后一个变量对象
- 标识符查找:从作用域链的最前端开始,向上逐级搜索,找到后搜索停止,没有找到则一直追溯到全局上下文的变量对象
4.3 垃圾回收
- 最常用的垃圾回收策略是标记清理,垃圾回收程序运行的时候,会标记内存中存储的所有变量,然后去掉上下文中的变量以及被上下文中的变量引用的变量标记,之后再被加上标记的变量就是待删除的,原因是这些变量无法被访问到了
- 优化内存占用:执行代码时只保存必要的数据,如果数据不再必要,那么把它设置为null,从而释放其引用——解除引用;解除引用的作用是让其脱离上下文,以便垃圾回收程序运行时将其回收
第五章 基本引用类型
5.1 Date
创建日期对象:月份从0开始(1月是0,2月是1…)
1
2let d1 = new Date();
let d2 = new Date(2022, 3, 20, 7, 30, 30); // 2022年4月20日7点30分30秒获取执行时日期和时间的毫秒数,用来分析代码
1
2
3
4
5
6
7
8
9// 起始时间
let start = Date.now();
// 调用函数
doSomething();
// 结束时间
let stop = Date.now();
result = stop - start;日期格式化方法:local表示以特定于地区的格式显示
1
2
3
4
5
6
7let d2 = new Date(2022, 3, 20, 7, 30, 30); // 2022年4月20日7点30分30秒
d2.toString(); // Wed Apr 20 2022 07:30:30 GMT+0800 (中国标准时间)
d2.toDateString(); // Wed Apr 20 2022
d2.toTimeString(); // 07:30:30 GMT+0800 (中国标准时间)
d2.toLocaleString(); // 2022/4/20 07:30:30
d2.toLocaleDateString(); // 2022/4/20
d2.toLocaleTimeString(); // 07:30:30
5.2 RegExp
pattern是正则表达式;flags是标记,用于控制正则表达式的行为。下面表示匹配模式的标记:
- g:全局模式,表示查找字符串的全部内容,而不是找到第一个匹配的内容就结束
- i:不区分大小写,表示在查找匹配时忽略pattern和字符串的大小写
- m:多行模式,表示查找到一行文本末尾时会继续查找
- y:粘附模式,表示只查找从lastIndex开始及之后的字符串
- u:Unicode模式,启用Unicode匹配
- s:dotAll模式,表示元字符.匹配任何字符(包括\n或\r)
1
2let exp1 = / pattern / flags;
let exp2 = new RegExp('pattern', 'flags');
实例方法:
- exec():返回包含第一个匹配信息的数组,数组第一个元素是匹配整个模式的字符串,其他元素是与表达式中的捕获组匹配的字符串;包含两个额外的属性:index和input
1
2
3
4
5
6
7
8let text = "I'm ulan, and this is newHome";
let pattern = /and( this( is)?)?/gi;
let matches = pattern.exec(text);
console.log(matches.index); // 10
console.log(matches.input); // I'm ulan, and this is newHome
console.log(matches[0]); // and this is
console.log(matches[1]); // this is
console.log(matches[2]); // is - test:输入的文本与模式匹配,则返回true,否则返回false
1
2
3
4let text = "I'm ulan, and this is newHome";
let pattern = /ulan/;
let matches = pattern.test(text);
console.log(matches); // true
- exec():返回包含第一个匹配信息的数组,数组第一个元素是匹配整个模式的字符串,其他元素是与表达式中的捕获组匹配的字符串;包含两个额外的属性:index和input
RegExp构造函数有部分属性,适用于作用域中的所有正则表达式,这些属性可以提取出与exec()和test()执行的操作相关的信息
5.3 原始值包装类型
三种特殊的引用类型:Boolean、Number、String
Number的方法
- toString():数值转字符串,参数可接收二、八进制…
- toFixed():数值转字符串,参数指定包含小数点位数
- isInteger():用来判断数值是否保存为整数
1
2
3
4
5
6let n1 = 10;
let n2 = 10.005;
console.log(n1.toString()); // "10"
console.log(n2.toFixed(2)); // "10.01"
console.log(Number.isInteger(n1)); // true
console.log(Number.isInteger(n2)); // false
String的方法
- length属性:表示字符串的长度
- charAt():查找索引位置返回字符,从索引0开始找,参数由整数指定位置
1
2
3let s1 = 'hello world';
s1.length // 11
s1.charAt(6) // 'w' - slice():提取子字符串,第一个参数表示开始搜索的位置,第二个参数表示结束搜索的位置,负数从末尾计算
- substr():提取子字符串,第一个参数表示开始搜索的位置,第二个参数是以length提取,按1、2、3…个字符计算
1
2
3
4
5let s2 = '我喜欢JS,同时也爱HTML';
s2.slice(3, 5) // 'JS'
s2.slice(-4, -1) // 'HTM'
s2.substr(3, 2) // 'JS'
s2.substr(-4, 4) // 'HTML' - indexOf():查找字符索引返回位置,从前往后找,没找到返回-1
- lastIndexOf():查找字符索引返回位置,从后往前找,没找到返回-1
- includes():判断字符串中是否包含另一个字符串,第二个参数表示指定位置向末尾搜索
1
2
3
4
5
6
7let s3 = 'abcdabcd';
s3.indexOf('a') // 0
s3.lastIndexOf('a') // 4
s3.indexOf('a', 1) // 4
s3.lastIndexOf('a', 1) // 0
s3.includes('ab') // true
s3.includes('df') // false - trim():创建字符串的副本,删除前后所有空格
1
2let s4 = ' hello world ';
let s5 = s4.trim(); // 'hello world' - repeat():字符串复制多少次,返回拼接后的副本,参数为复制次数
1
'hello '.repeat(3) + 'world' // 'hello hello hello world'
- toUpperCase()和toLowerCase():英文字符串转换大写和小写
1
2
3let s6 = 'hello world', s7 = 'HELLO WORLD';
s6.toUpperCase() // 'HELLO WORLD'
s7.toLowerCase() // 'hello world' - localeCompare():按字母表顺序比较两个字符串,返回比较结果;例如a在b前面,b在c前面
1
2
3
4let s8 = 'betty';
s8.localeCompare('alice') // 1
s8.localeCompare('betty') // 0
s8.localeCompare('claier') // -1
5.4 单例内置对象
- Global对象为一种兜底对象,不属于任何对象的属性和方法都是Global对象的方法,例如:isNaN()、parseInt()等;window对象是Global对象的代理,全局作用域中声明的变量和函数都变成了window的属性
- Math对象提供一些计算的属性和方法
- min()、max():确定一组数值中的最小值和最大值,可接收任意多个参数
- Math.ceil()、Math.floor()、Math.round()、 Math.fround():小数值舍入为整数
- random():返回随机数
1
2
3
4
5
6let arr = [1, 2, 3, 4, 5, 6, 7, 8];
console.log(Math.min(...arr)); // 1
console.log(Math.max(...arr)); // 8
let num = Math.floor(Math.random() * 10 + 1);
console.log(num); // 随机1~10
第六章 集合引用类型
6.1 Object
创建Object实例:使用Object构造函数;对象字面量
1
2
3
4
5
6
7
8
9
10// new操作符法
let o1 = new Object();
o1.name = '幽蓝';
o1.age = 9;
// 对象字面量表示法
let o2 = {
name: '幽蓝',
age: 9
};访问对象属性:点表示法;方括号表示法;建议使用点表示法
1
2
3
4
5
6
7
8
9// 点表示法
console.log(o.name);
// 方括号表示法
console.log(o['name']);
let n = 'name';
console.log(o[n]);
console.log(o['first name'])
6.2 Array
创建数组:使用Array构造函数;使用数组字面量表示法
1
2
3
4
5
6let a1 = new Array();
let a2 = new Array(20);
let a3 = new Array('red', 'blue', 'green');
let a4 = [];
let a5 = ['red', 'blue', 'green'];创建数组静态方法
- from():可以将类数组结构转换为数组实例
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// 字符串拆分单字符数组
console.log(Array.from('ulan')); // [ 'u', 'l', 'a', 'n' ]
// 数组浅复制
const a1 = [1, 2, 3, 4];
const a2 = Array.from(a1);
console.log(a1 === a2); // false
// 任何可迭代对象
const iter = {
* [Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
yield 4;
}
};
console.log(Array.from(iter)); // [ 1, 2, 3, 4 ]
// 集合和映射转换为新数组
const m = new Map() .set(1, 2)
.set(3, 4);
const s = new Set() .add(1)
.add(2)
.add(3)
.add(4);
console.log(Array.from(m)); // [[1, 2], [3, 4]]
console.log(Array.from(s)); // [1, 2, 3, 4]
// arguments对象可以被轻松地转换为数组
function f1() {
return Array.from(arguments);
}
console.log(f1(1, 2, 3, 4)); // [ 1, 2, 3, 4 ]
// 转换带有必要属性的自定义对象
const o1 = {
0: 1,
1: 2,
2: 3,
3: 4,
length: 4
};
console.log(Array.from(o1)); // [ 1, 2, 3, 4 ]
// 第二个参数可以映射函数,直接增强新数组的值
// 第三个参数指定映射函数this的值,重写this值在箭头函数中不适用
const a1 = [1, 2, 3, 4];
const a2 = Array.from(a1, x => x ** 2);
const a3 = Array.from(a1, function (x) {
return x ** this.e
}, {e: 2});
console.log(a2); // [ 1, 4, 9, 16 ]
console.log(a3); // [ 1, 4, 9, 16 ] - of():可以将一组参数转换为数组实例
1
2
3// arguments对象转换为数组
console.log(Array.of(1,2,3,4)); // [ 1, 2, 3, 4 ]
console.log(Array.of(undefined)); // [ undefined ]
- from():可以将类数组结构转换为数组实例
数组空位:一串空号来创建空位[,,,];尽量避免使用空位,有需要可用undefined代替
数组索引:利用length在末尾添加新项
1
2let a = ['a', 'b'];
a[a.length] = 'c';检测数组:Array.isArray()(解决了存在两个以上全局执行环境时instanceof检测结果出错的情况)
迭代器方法
- keys():返回数组索引
- values():返回数组元素
- entries():返回索引/值对
1
2
3const a = ['a', 'b', 'c'];
const aEntries = Array.from(a.entries());
console.log(aEntries); // [ [ 0, 'a' ], [ 1, 'b' ], [ 2, 'c' ] ]
复制和填充方法
- fill():填充数组,向已有的数组插入全部或部分相同的值
- copyWithin():批量复制,按指定范围浅复制数组中的内容,插入到指定索引开始位置
1
2
3
4
5
6
7
8
9
10
11
12// 用7填充,索引大于等于1,且索引小于3的元素
const z = [0, 0, 0, 0, 0];
z.fill(7, 1, 3);
console.log(z); // [ 0, 7, 7, 0, 0 ]
// 从i中复制索引0开始到索引3(不包括3)结束的内容
// 插入到索引4开始的位置
let i,
reset = () => i = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
i.copyWithin(4, 0, 3);
console.log(i); // [0, 1, 2, 3, 0, 1, 2, 7, 8, 9]
转换方法
- toString():返回一个逗号分隔的字符串
- valueOf():返回数组本身
- join():类似toString(),能把逗号换成其他
栈方法和队列方法
- push():添加一项到数组末尾
- pop():移除数组末尾一项
- shift():移除数组第一项
- unshift():添加一项到数组前端
排序方法
- reverse():数组元素反向排列
- sort():默认将数组元素转换成字符串,然后升序排列,可以接收一个比较函数作为参数
操作方法
- concat():添加项
1
2
3var a1 = ['red', 'green', 'blue'];
var a2 = a1.concat('yellow', ['black', 'brown']);
console.log(a2) // ["red", "green", "blue", "yellow", "black", "brown"] - slice():截取
1
2var a = ["red", "green", "blue", "yellow", "black", "brown"];
console.log(a.slice(1), a.slice(1, 4)) // ["green", "blue", "yellow", "black", "brown"] ["green", "blue", "yellow"] - splice():删除插入替换
1
2
3
4var a = ["red", "green", "blue", "yellow", "black", "brown"];
console.log(a.splice(2, 1), a); // 删除项; ["blue"] ["red", "green", "yellow", "black", "brown"]
console.log(a.splice(1, 0, 'yellow', 'orange'), a); // 插入项; [] ["red", "yellow", "orange", "green", "yellow", "black", "brown"]
console.log(a.splice(1, 1, 'red', 'purple'), a); // 替换项; ["yellow"] ["red", "red", "purple", "orange", "green", "yellow", "black", "brown"]
- concat():添加项
indexOf() lastIndexOf() 接收两个参数:要查找的项和(可选)查找起点位置的索引;indexOf()从前往后查找,lastIndexOf()从后往前查找;返回要查找的项的位置,没找到则返回-1
1
2
3
4
5var a = ["red", "purple", "orange", "green", "red", "yellow", "black", "brown"];
console.log(a.indexOf('red')); // 0
console.log(a.lastIndexOf('red')); // 4
console.log(a.indexOf('red', 1)); // 4
console.log(a.lastIndexOf('red', 1)); // 0迭代方法
- every():每一项函数都返回true,则这个方法返回true
- some():有一项函数返回true,则这个方法返回true
- filter():每一项都运行传入的函数,函数返回true的项,会组成新数组后返回
- map():每一项都运行传入的函数,返回函数调用的结果构成新数组
- forEach():每一项都运行传入的函数,没有返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let everyResult = numbers.every((item, index, array) => item > 2);
console.log(everyResult); // false
let someResult = numbers.some((item, index, array) => item > 2);
console.log(someResult); // true
// 返回一个所有数值都大于2的数组
let filterResult = numbers.filter((item, index, array) => item > 2);
console.log(filterResult); // [3, 4, 5, 4, 3]
// 每一项都乘以2
let mapResult = numbers.map((item, index, array) => item * 2);
console.log(mapResult); // [2, 4, 6, 8, 10, 8, 6, 4, 2]
// forEach()方法相当于使用for循环遍历数组
numbers.forEach((item, index, array) => {
// 执行某些操作
});
归并方法
1
2
3
4
5
6
7
8
9let values = [1, 2, 3, 4, 5];
let sum1 = values.reduce((prev, cur, index, array) => prev + cur);
console.log(sum1); // 15,过程1+2+3+4+5
let sum2 = values.reduceRight(function(prev, cur, index, array){
return prev + cur;
});
console.log(sum2); // 15,过程5+4+3+2+1
6.3 定型数组
ArrayBuffer():用于内存中分配特定数量的字节空间,创建就不能再调整大小
1
2const buf = new ArrayBuffer(16); // 在内存中分配16字节
console.log(buf.byteLength); // 16DataView:允许你读写ArrayBuffer的视图;DataView使用ElementType来实现JavaScript的Number类型到缓冲内二进制格式的转换;默认为大端字节序
1
2
3
4
5const buf = new ArrayBuffer(16); // 在内存中分配16字节
const dv = new DataView(buf, 0, 8);
console.log(dv.byteOffset); // 0,表示视图从缓冲起点开始
console.log(dv.byteLength); // 8,限制视图为前 8 个字节定型数组:另一种形式的ArrayBuffer视图;特定于一种ElementType且遵循系统原生的字节序;目的是提高WebGL等原生库交换二进制数据的效率;定型数组两个新方法
- set():复制值到指定的索引位置
1
2
3
4
5
6
7
8
9
10
11
12// 创建长度为8的int16数组
const container = new Int16Array(8);
// 把定型数组复制为前4个值
// 偏移量默认为索引0
container.set(Int8Array.of(1, 2, 3, 4));
console.log(container); // [1,2,3,4,0,0,0,0]
// 把普通数组复制为后4个值
// 偏移量4表示从索引4开始插入
container.set([5, 6, 7, 8], 4);
console.log(container); // [1,2,3,4,5,6,7,8] - subarray():复制值返回一个新定型数组;参数可选开始索引和结束索引
1
2
3
4
5const source = Int16Array.of(2, 4, 6, 8);
// 从索引1开始复制到索引3
const partialCopy = source.subarray(1, 3);
console.log(partialCopy); // [4, 6]
- set():复制值到指定的索引位置
6.4 Map
- 创建Map构造函数;建议键/值存放使用Map
- Map实例化方法和属性
- set():添加键/值对
- get():查询键,获取值
- has():判断Map中是否存在键
- size:获取键/值对数量
- delete():删除参数中的键/值对,参数是键
- clear():删除全部键/值对
1
2
3
4
5
6
7
8
9
10
11
12
13
14const m1 = new Map([
['name', '幽蓝'],
[true, 'true']
]);
m1.set('age', 9);
console.log(m1.get('name'), m1.get('age')); // 幽蓝 9
console.log(m1.has('age'), m1.has('sex')); // true false
console.log(m1.size); // 3
m1.delete('name');
console.log(m1); // { true => 'true', 'age' => 9 }
m1.clear();
console.log(m1, typeof m1); // {} object - entries():Map迭代器,能以插入顺序生成[key, value]形式的数组
1
2
3
4
5
6
7
8
9
10
11
12const m = new Map([
['name', '幽蓝'],
['age', 9],
[true, 'true']
]);
for (let pair of m.entries()) {
console.log(pair);
}
// [ 'name', '幽蓝' ]
// [ 'age', 9 ]
// [ true, 'true' ]
6.5 WeakMap
创建WeakMap构造函数;键只能是引用类型
1
2
3
4
5
6
7
8
9const k1 = {id: 1},
k2 = {id: 2},
k3 = {id: 3};
const wm1 = new WeakMap([
[k1, 'val1'],
[k2, 'val2'],
[k3, 'val3'],
]);
console.log(wm1.get(k1), wm1.get(k2), wm1.get(k3)); // val1 val2 val3WeakMap实例化方法:set()、get()、has()、delete()
应用场景:DOM节点是引用类型,WeakMap可以绑定成键,如果绑定的DOM节点被删除,垃圾回收程序可以立即释放其内存,而Map则不会释放内存,这也是WeakMap一项优势
6.6 Set
Set实例化方法和属性
- add():增加值,重复值会被替换
- has()、Set.size、delete()、clear()
- entries():两个元素的数组
1
2
3
4
5
6
7
8
9
10
11const s1 = new Set();
s1.add(1)
.add(2)
.add(2)
.add('幽蓝');
console.log(s1); // { 1, 2, '幽蓝' }
// 可以数组去重
const s2 = [1, 1, 2, 3, 3, 5, 6, 6];
const s3 = new Set(s2);
console.log(s3); // { 1, 2, 3, 5, 6 }
Set没有索引值,本身是无序的,但迭代器能以插入顺序生成集合内容;values()和keys()是相同的
6.7 WeakSet
创建WeakSet构造函数
1
2
3
4
5
6const k1 = {id: 1},
k2 = {id: 2},
k3 = {id: 3};
const ws = new WeakSet([k1, k2, k3]);
console.log(ws.has(k1), ws.has(k2), ws.has(k3)); // true ...WeakSet实例化方法和属性:add()、has()、delete()
总结:WeakMap使用较多,WeakSet使用较少,两者应用场景都在DOM节点上,能让垃圾回收程序立即释放其内存;而Map、Set则不会释放内存
1
2
3
4
5
6
7
8const ws1 = new WeakSet();
ws1.add([1, 2, 3]);
ws1.add({});
// 设置定时器看垃圾回收触发,去浏览器控制台看
setInterval(function (){
console.log(ws1);
}, 1000);
6.8 迭代与扩展操作
- 原生集合类型:Array、所有定型数组、Map、Set
第七章 迭代器与生成器
7.2 迭代器模式
- 迭代器模式:集合类型、类数组提供iterable接口;默认迭代器Symbol.iterator,调用之后会产生一个实现Iterator接口的对象
- 可迭代对象的原生语言特性
- for-of循环
- 数组解构
- 扩展操作符
- Array.from()
- 创建集合
- 创建映射
- Promise.all()接收由期约组成的可迭代对象
- Promise.race()接收由期约组成的可迭代对象
- yield*操作符,在生成器中使用
- 迭代器协议:next()在可迭代对象中遍历数据,成功调用返回IteratorResult对象,这个对象包含value和done属性,持续调用next(),直到value为undefined且done为true,后面始终重复
1
2
3
4
5
6
7
8
9
10let arr = [1,2,3];
let iter = arr[Symbol.iterator]();
for (let i = 0; i < 5; i++) {
console.log(iter.next());
}
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: undefined, done: true }
// { value: undefined, done: true }
7.3 生成器
- 生成器:函数名称前加一个型号(*)表示它是一个生成器,箭头函数不能定义生成器函数;yield关键字可以让生成器停止和开始执行,而给关键字加星号表示继续迭代
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
52function* func1(...nums) {
yield '--func1--';
yield* nums;
yield* func2(101, 102); // func2() 返回的函数参数也可以迭代
yield '--end--';
}
function* func2(...nums) {
yield '--func2--';
yield nums;
yield* nums;
yield* '对不起';
yield* func3('幽蓝', '9');
}
function* func3() {
yield '--func3--';
yield* arguments; // arguments也是可迭代对象
yield* funcNoStar(arguments); // 函数返回值是可迭代对象
}
function funcNoStar(arr) {
let res = ['--funcNoStar--'];
for (let a of arr) {
res.push(...a)
}
return res;
}
for (let i of func1(1, 2, 3)) {
console.log(i);
}
// --func1--
// 1
// 2
// 3
// --func2--
// [ 101, 102 ]
// 101
// 102
// 对
// 不
// 起
// --func3--
// 幽蓝
// 9
// --funcNoStar--
// 幽
// 蓝
// 9
// --end--
第八章 对象、类与面向对象编程
8.1 理解对象
两种属性:数据属性和访问属性。特性:描述属性的特征,是为了JavaScript引擎的规范定义,不能直接访问
1.1数据属性4个特性- [[Configurable]]:表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性
- [[Enumerable]]:表示属性是否可以通过for-in循环返回
- [[Writable]]:表示属性的值是否可以被修改
- [[Value]]:包含属性实际的值
1.2 访问属性4个特性
- [[Configurable]]:表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,以及是否可以把它改为数据属性
- [[Enumerable]]:表示属性是否可以通过for-in循环返回
- [[Get]]:获取函数,在读取属性时调用
- [[Set]]:设置函数,在写入属性时调用
定义及读取特性
- Object.defineProperty()
- Object.defineProperties()
- Object.getOwnPropertyDescriptor()
合并对象:Object.assign()
1
2
3
4
5
6let dest, src, result;
dest = {};
src = {id: '幽蓝'};
result = Object.assign(dest, src, {age: 9}, {age: 10});
console.log(dest === result, src !== result); // true true
console.log(dest, result); // { id: '幽蓝', age: 10 } { id: '幽蓝', age: 10 }对象标识及相等判定:Object.is(),改善全等(===)问题
1
2
3
4
5
6
7
8
9console.log(Object.is(true, 1),
Object.is({}, {}),
Object.is('2', 2)); // false false false
console.log(Object.is(+0, -0),
Object.is(+0, 0),
Object.is(-0, 0)); // false true false
console.log(Object.is(NaN, NaN)); // true对象解构:使用与对象匹配的结构来实现对象属性赋值
1
2
3
4
5
6
7let p = {
name: '幽蓝',
age: 9
};
let {name, age, likeColor = 'blue'} = p;
console.log(name, age, likeColor); // 幽蓝 9 blue
8.2 创建对象
工厂模式:虽然可以解决创建多个类似对象的问题,但没有解决对象标识问题
1
2
3
4
5
6
7
8
9
10
11
12function createPerson(name, age, job) {
let o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function () {
console.log(this.name);
};
return o;
}
let p1 = createPerson('幽蓝', 9, 'Software Engineer');
let p2 = createPerson('金涛', 20, 'coach');构造函数模式:构造函数应该以大写字母开头
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function Person (name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function (){
console.log(this.name);
};
}
let p1 = new Person('幽蓝', 9, 'Software Engineer');
let p2 = new Person('金涛', 20, 'coach');
p1.sayName(); // 幽蓝
p2.sayName(); // 金涛
// p1 p2 分别保存着Person的一个不同的实例,这两个对象都有一个constructor属性,该属性指向Person
console.log(p1.constructor); // function Person(name, age, job) {...
console.log(p1 instanceof Object); // true
console.log(p1 instanceof Person); // true2.1 这种方法会经历5个步骤
- 在内存中创建一个新对象
- 这个新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype属性
- 构造函数内部的this被赋值为这个新对象(即this指向新对象)
- 执行构造函数内部的代码(给新对象添加属性)
- 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象
2.2 构造函数的问题:每个方法会在每个实例上创建一遍
1
console.log(p1.sayName == p2.sayName); // false
原型模式:每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象(函数的原型对象),这个对象包含可以由该类型的所有实例共享的属性和方法
1
2
3
4
5
6
7
8
9
10
11
12function Person() {}
Person.prototype.name = '张三';
Person.prototype.age = 18;
Person.prototype.job = 'student';
Person.prototype.sayName = function () {
console.log(this.name);
};
let p1 = new Person();
let p2 = new Person();
console.log(p1.sayName === p2.sayName); // true3.1 理解原型
- 只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性(指向原型对象)
- 默认所有原型对象自动获得一个名为constructor的属性,指向与之关联的构造函数
- 调用构造函数创建新实例后,实例将有一个__proto__属性,指向构造函数的原型对象,指针叫[[Prototype]],默认原型指向Object
- 实例与构造函数之间没有直接关系
- 读取属性:搜索先从对象实例本身开始,如果没找到,搜索原型对象
- 使用isPrototype()来检测构造函数和实例之间是否有关系
- 使用hasOwnProperty()来检测属性存在于实例中还是原型中
- 原型和in操作符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// in操作符通过实例或原型访问到属性时返回true
console.log('name' in p1); // true
// 枚举属性
for (const prop in p1) {
console.log(prop); // name age job sayName
}
// Object.keys 返回一个所有元素为字符串的数组
p1.name = '幽蓝';
p1.age = 9;
p1.sayHello = 'hello';
console.log(Object.keys(p1)) // [ 'name', 'age', 'sayHello' ]
// Object.getOwnPropertyNames() 返回所有实例属性,无论是否可以枚举
console.log(Object.getOwnPropertyNames(Person.prototype)); // [ 'constructor', 'name', 'age', 'job', 'sayName' ] - 用对象字面量重写原型对象
1
2
3
4
5
6function Person() {}
Person.prototype = {
constructor: Person, // 这里重写了prototype,不再默认有constructor属性
name: '幽蓝',
age: 9,
};
8.3 继承
JavaScript中使用最多的继承模式:组合继承,融合原形链和构造函数的优点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21function SuperType(name) {
this.name = name;
this.color = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function (){
console.log(this.name);
}
function SubType(name, age){
SuperType.call(this, name); // 借用构造函数
this.age = age;
}
SubType.prototype = new SuperType(); // 原型链
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function (){
console.log(this.age);
}
let instance = new SubType('幽蓝', 9);
instance.sayName(); // 幽蓝
instance.sayAge(); // 9确定原型和实例的关系
1
2console.log(instance instanceof SuperType); // true
console.log(SuperType.prototype.isPrototypeOf(instance)); // true原型式继承、寄生式继承、寄生组合式继承
task8.4 类
class类名的首字母要大写;类不能声明提升;类是块级作用域
constructor关键字:定义块内部创建类的构造函数,用new调用类的构造函数操作
- 在内存中创建一个新对象
- 这个新对象内部的[[Prototype]]指针被赋值为构造函数的prototype属性
- 构造函数内部的this被赋值为这个新对象(即this指向新对象)
- 执行构造函数内部的代码(给新对象添加属性)
- 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象
类构造函数与构造函数区别:调用类构造函数必须用new操作符;调用普通构造函数不用new操作符,以全局this作为内部对象
原型方法与访问器:实例间共享方法,类定义语法把在类块中定义的方法作为原型方法
1
2
3
4
5
6
7
8
9
10
11
12class Person {
constructor() {
this.locate = () => console.log('instance');
}
locate() {
console.log('prototype');
}
}
let p = new Person();
p.locate(); // instance
Person.prototype.locate(); // prototype类继承:extends关键字,继承任何拥有[[Construct]]和原型的对象;super关键字引用它们的原型,super只能使用在派生类构造函数和静态方法
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
28class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log('hello,我是父类');
}
}
class Ulan extends Person {
constructor(name, age, job) {
super(name, age);
this.job = job;
}
sayHello() {
super.sayHello(); // 调用父类方法
console.log('hello,我是幽蓝');
}
}
let ul = new Ulan('幽蓝', 9, 'software engineer');
console.log(ul); // Ulan { name: '幽蓝', age: 9, job: 'software engineer' }
ul.sayHello();
// hello,我是父类
// hello,我是幽蓝静态方法
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
33class Person {
constructor() {}
sayHello() {
console.log('hello,我是父类');
}
static sayHello() {
console.log('hello,我是静态父类');
}
}
class Ulan extends Person {
constructor() {
super();
}
sayHello() {
// 方法相同,子类覆盖父类
console.log('hello,我是幽蓝');
}
static sayHello() {
super.sayHello();
console.log('hello,我是静态幽蓝');
}
}
let ul = new Ulan();
ul.sayHello(); // hello,我是幽蓝
Ulan.sayHello();
// hello,我是静态父类
// hello,我是静态幽蓝
第九章 代理与反射
9.1 代理基础
创建Proxy构造函数,接收两个参数:目标对象和处理程序对象
1
2
3
4
5
6
7
8
9
10
11
12
13const target = {
id: '幽蓝'
};
const handler = {};
const proxy = new Proxy(target, handler);
console.log(target.id, proxy.id); // 幽蓝 幽蓝
// 目标属性赋值会反映到两个对象上
target.id = 'ulan';
console.log(target.id, proxy.id); // ulan ulan
// 代理属性赋值会反映到两个对象上
proxy.id = 'cat';
console.log(target.id, proxy.id); // cat catget()捕获器:会在获取属性值的操作中被调用
- target:目标对象
- property:引用的目标对象上的字符串键属性
- receiver:代理对象或继承代理对象的对象
get捕获器不变式:如果target.property不可写且不可配置,处理程序返回的值必须与target.property匹配;如果target.property不可配置且[[Get]]特性为undefined,处理程序的返回值也必须是undefined
revocable():撤销代理对象与目标对象的关联
状态标记:反射方法返回称作”状态标记”的布尔值,表示要执行的操作是否成功
9.2 代理捕获器与反射方法
- set()捕获器:会在设置属性值的操作中被调用
- target:目标对象
- property:引用的目标对象上的字符串键属性
- value:要赋给属性的值
- receiver:接收最初赋值的对象
- set捕获器不变式:如果target.property不可写且不可配置,则不能修改目标属性的值;如果target.property不可配置且[[Set]]特性为undefined,则不能修改目标属性的值
- 其他捕获器
- has()捕获器会在in操作符中被调用
- defineProperty()捕获器会在Object.defineProperty()中被调用
- getOwnPropertyDescriptor()捕获器会在Object.getOwnPropertyDescriptor()中被调用
- deleteProperty()捕获器会在delete操作符中被调用
- ownKeys()捕获器会在Object.keys()及类似方法中被调用
- getPrototypeOf()捕获器会在Object.getPrototypeOf()中被调用
- setPrototypeOf()捕获器会在Object.setPrototypeOf()中被调用
- isExtensible()捕获器会在Object.isExtensible()中被调用
- preventExtensions()捕获器会在Object.preventExtensions()中被调用
- apply()捕获器会在调用函数时中被调用
- construct()捕获器会在new操作符中被调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const person = {name: '幽蓝', age: 9};
const personProxy = new Proxy(person, {
get(target, key) {
return target[key].toUpperCase(); // 获取name值并返回大写后的值
},
set(target, key, value) {
if (typeof value === 'string') {
target[key] = value.trim();
}
}
});
personProxy.name = 'ulan';
console.log(personProxy.name); // ULAN
personProxy.love = ' I love cat ';
console.log(personProxy.love); // I LOVE CAT
console.log(person.name, person.age); // ulan 9
9.3 代理模式
- 跟踪属性访问、隐藏属性、属性验证、函数与构造函数参数验证、数据绑定与可观察对象
第十章 函数
10.1 箭头函数
- 箭头函数实例化,建议参数不管有多少加(),花括号也加上{};箭头函数不适用场景:arguments、super、new.target和构造函数
1
2
3
4
5
6
7let sum = (a, b) => {
return a + b;
};
console.log(sum(5, 8)); // 13
let ints = [1, 2, 3];
console.log(ints.map((i) => { return i + 1; })); // [2,3,4]
10.3 理解参数
- 函数的参数在内部表现为一个数组,接收参数就是在接收数组;非箭头函数可在内部访问arguments对象,arguments对象是一个类数组对象,可用中括号语法访问其中的元素(第一个参数是arguments[0],第二个参数是arguments[1]),函数的参数只是为了方便才写出来的,并不是必须写出来的
1
2
3
4function a() {
console.log(arguments[0], arguments[1]); // hello world
}
a('hello', 'world');
10.5 默认参数值
参数后面用=可以为参数赋一个默认值
1
2
3
4
5
6function p(name = '张三', age = 18) {
return `个人信息: ${name} ${age}`;
}
console.log(p()); // 个人信息: 张三 18
console.log(p('李四')); // 个人信息: 李四 18
console.log(p('幽蓝', 9)); // 个人信息: 幽蓝 9暂时性死区:前面定义的参数不能引用后面定义的参数;参数也有自己的作用域,它们不能引用函数体的作用域
1
2
3
4
5
6
7
8
9
10
11function a(name = age, age = 9) {
return `名字:${name} ${age}`;
}
a(); // ReferenceError
// 作用域链先从自己身上找,找不到再向外找,直到全局作用域找不到返回错误
function b(name = '张三', age = getAge) {
let getAge = 18;
return `个人信息:${name},{age}`;
}
b(); // ReferenceError
10.6 参数扩展与收集
- 扩展操作符:对可迭代对象以一个参数传入,然后进行拆分,最后返回每个值在单独传入
1
2
3
4
5
6
7
8
9
10
11
12
13
14let arr = [1, 2, 3, 4, 10];
function getSum() {
let sum = 0;
for (let i = 0; i < arguments.length; ++i) {
sum += arguments[i];
}
return sum;
}
console.log(getSum(arr)); // 01,2,3,4,10
console.log(getSum(...arr)); // 20
console.log(getSum(1, ...arr, ...[4, 5])); // 30
console.log(getSum(...[2, 3])); // 5
10.9 函数内部
- 谁调用this,this就指向谁;箭头函数的this指向箭头函数的上下文
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function King() {
this.royaltyName = 'Henry';
// this 引用 King 的实例
setTimeout(() => console.log(this.royaltyName), 1000);
}
function Queen() {
this.royaltyName = 'Elizabeth';
// this 引用 window 对象
setTimeout(function () {
console.log(this.royaltyName);
}, 1000);
}
new King(); // Henry
new Queen(); // undefined
10.10 函数属性与方法
apply()和call()强大的地方并不是给函数传参,而是控制函数体内this值的能力;两个方法的功能一样,区别在怎么给调用的函数传参更方便
- apply():第一个参数是函数内this值,第二个参数是Array的实例,也可以是arguments对象
- call():第一个参数是函数内this值,第二个参数是将参数一个一个地列出来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function sum(num1, num2) {
return num1 + num2;
}
function a(num1, num2) {
return sum.apply(this, arguments);
}
function b(num1, num2) {
return sum.apply(this, [num1, num2]);
}
function c(num1, num2) {
return sum.call(this, num1, num2);
}
console.log(a(10, 10)); // 20
console.log(b(10, 10)); // 20
console.log(c(10, 10)); // 20
bind():创建一个新的函数实例,其this值会被绑定到传给bind()的对象上
10.12 递归
- arguments.callee指向正在执行的函数的指针,因此可以在函数内部递归调用
1
2
3
4
5
6
7
8
9
10
11function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
let a = factorial;
console.log(factorial(5)); // 120, 5*4*3*2
console.log(a(4)); // 24, 4*3*2
10.13 尾调用优化
- 尾调用优化的条件
- 代码在严格模式下执行
- 外部函数的返回值是对尾调用函数的调用
- 尾调用函数返回后不需要执行额外的逻辑
- 尾调用函数不是引用外部函数作用域中自由变量的闭包
10.14 闭包
闭包指的是引用另一个函数作用域中变量的函数
在调用一个函数时,会为这个函数调用创建一个执行上下文,并创建一个作用域链。然后用arguments和其他命名参数来初始化这个函数的活动对象。外部函数的活动对象是内部函数作用域链上的第二个对象。这个作用域链一直向外串起了所有包含函数的活动对象,直到全局执行上下文才终止
1
2
3
4
5
6
7
8
9
10
11// 最外面是作用域链上的第三个对象
function outer() { // outer函数是作用域链上的第二个对象
let name = '幽蓝';
return function () { // 匿名函数是作用域链上的第一个对象
console.log(name);
};
}
let inner = outer();
inner(); // 幽蓝
inner = null; // 解除对outer内部的匿名函数的引用,以便释放内存匿名函数中的this在非严格模式下等于window
1
2
3
4
5
6
7
8
9
10window.name = 'The Window';
let object = {
name: 'My Object',
getNameFunc() {
return function () {
return this.name;
};
}
};
// The Window
10.16 私有变量
任何定义在函数或块中的变量,都可以认为是私有变量
特权方法:能够访问函数私有变量(及私有函数)的公有方法,缺点是每个实例都会重新创建一遍新方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18function MyObject() {
// 私有变量和私有函数
let privateVariable = '幽蓝';
function privateFunction() {
console.log('_(:з」∠)_');
}
// 特权方法
this.publicMethod = function () {
console.log(privateVariable);
return privateFunction();
};
}
let o = new MyObject();
o.publicMethod(); // 幽蓝 _(:з」∠)_
o.privateFunction(); // TypeError: o.privateFunction is not a function at Object静态私有变量:通过使用私有作用域定义私有变量和函数来实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21(function () {
// 私有变量和私有函数
let privateVariable = '幽蓝';
function privateFunction() {
console.log('Σ(・□・;)');
}
// 不使用关键字声明的变量会创建在全局作用域中
MyObject = function () {
};
// 公有和特权方法
MyObject.prototype.publicMethod = function () {
console.log(privateVariable);
privateFunction();
};
})();
let o = new MyObject();
o.publicMethod(); // 幽蓝 Σ(・□・;)
o.privateFunction(); // TypeError: o.privateFunction is not a function at Object模块模式:创建一个要通过匿名函数返回的对象字面量,这个对象字面量中只包含可以公开访问的属性和方法
第十一章 期约与异步函数
11.1 异步编程
- 同步行为对应内存中顺序执行的处理器指令,每条指令都会严格按照它们出现的顺序来执行,而每条指令执行后也能立即获得存储在系统本地(如寄存器或系统内存)的信息
- 异步行为是为了优化因计算量大而时间长的操作,行为类似于系统中断,即当前进程外部的实体可以触发代码执行;早期支持定义回调函数来表明异步操作完成。串联多个异步操作是一个常见的问题,通常需要深度嵌套(俗称“回调地狱”)来解决
11.2 Promise期约
创建Promise构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// 男人和女人准备结婚,他们打算结婚前要个孩子
// 如果女人怀孕了,成功状态返回孩子他爹,失败状态返回老公
// 不论是否怀孕,最终都会结婚
const isPregnant = true;
const promise = new Promise((resolve, reject) => {
if (isPregnant) {
resolve(`孩子他爹`);
} else {
reject(`老公`);
}
});
promise
.then(name => {
console.log(`男人成为了${name}!`);
})
.catch(name => {
console.log(`男人成为了${name}!`);
})
.finally(() => {
console.log(`男人和女人最终结婚了`);
});Promise是一个有状态的对象,状态是私有的,不能直接通过js检测到,也不能被外部js修改,从而隔离外部同步代码;状态可能处于以下3种
- 待定(pending)
- 成功(fulfilled,也称resolve)
- 失败(reject)
Promise实例方法
- then():实现了Thenable接口,接收两个参数onResolved处理程序和onRejected处理程序
- catch():添加拒绝处理程序,接收一个参数onRejected处理程序
- finally():添加onFinally处理程序,解决或拒绝状态时都会执行
两个静态方法:Promise.all()和 Promise.race()
- Promise.all():接收多个Promise对象,全部对象状态成功,all才会成功,其中一个状态失败,all才会失败,最终返回一个数组
- Promise.race():接收多个Promise对象,第一个对象状态成功,race才会成功,第一个对象状态失败,race才会失败
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// 等待多少毫秒变化状态
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return 'p1';
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return 'p2';
});
const pAll = Promise.all([p1, p2]);
pAll.then(
data => {
console.log(data);
},
err => {
console.log(err);
});
// p1 完成了
// p2 完成了
// [ 'p1', 'p2' ]
const pRace = Promise.race([p1, p2]);
pRace.then(data => {
console.log(data);
}, err => {
console.log(err);
});
// p1 完成了
// p1
// p2 完成了
11.3 异步函数
- async关键字用于声明异步函数;await关键字可以暂停异步函数代码的执行,等待Promise解决
1
2
3
4
5
6
7
8
9
10
11const start = async () => {
console.log(1);
await setTimeout(() => {
console.log(2);
}, 1000);
console.log(3);
};
start();
// 1
// 3
// 2
第十二章 BOM
12.1 window对象
screenLeft和screenTop属性,表示窗口相对于屏幕左侧和顶部的位置
moveTo()接收要移动到的新位置的绝对坐标x和y;而moveBy()则接收相对当前位置在两个方向上移动的像素数
1
2
3
4
5
6
7
8
9
10
11// 把窗口移动到左上角
window.moveTo(0,0);
// 把窗口向下移动100像素
window.moveBy(0, 100);
// 把窗口移动到坐标位置(200, 300)
window.moveTo(200, 300);
// 把窗口向左移动50像素
window.moveBy(-50, 0);窗口大小
- outerWidth和outerHeight:浏览器窗口自身的大小
- innerWidth和innerHeight:浏览器窗口中页面视口的大小
- document.documentElement.clientWidth和document.documentElement.clientHeight:页面视口的宽度和高度
- resizeTo()接收新的宽度和高度值;resizeBy()接收宽度和高度要缩放多少
1
2
3
4
5
6
7
8// 缩放到100×100
window.resizeTo(100, 100);
// 缩放到200×150
window.resizeBy(100, 50);
// 缩放到300×300
window.resizeTo(300, 300);
视口位置
- scroll() 、scrollTo()表示要滚动到的坐标
- scrollBy() 表示要滚动的距离
1
2
3
4
5
6
7
8
9
10
11// 相对于当前视口向下滚动100像素
window.scrollBy(0, 100);
// 相对于当前视口向右滚动40像素
window.scrollBy(40, 0);
// 滚动到页面左上角
window.scrollTo(0, 0);
// 滚动到距离屏幕左边及顶边各100像素的位置
window.scrollTo(100, 100);
window.open():导航到指定URL,也可以用于打开新浏览器窗口接收4个参数:
- 要加载的URL
- 目标窗口
- 特性字符串
- 新窗口在浏览器历史记录中是否替代当前加载页面的布尔值
1
2// 与<a href="http://www.wrox.com" target="topFrame"/>相同
window.open("http://www.wrox.com/", "topFrame");
定时器
- setTimeout()指定在一定时间后执行某些代码;clearTimeout()可以取消超时任务,任务执行后再调用clearTimeout()没有效果
- setInterval()指定每隔一段时间执行某些代码;clearInterval()可以取消循环定时,如果不设置,定时任务会一直执行到页面卸载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21setTimeout(() => console.log('hi'), 1000);
setInterval(() => console.log('hello'), 2000);
// hi
// hello
// hello
// ...
let timeoutId = setTimeout(() => console.log('hi'), 1000);
clearTimeout(timeoutId);
let num = 0, max = 10;
let incrementNumber = function () {
num++;
if (num === max) {
clearInterval(intervalId);
console.log('终止');
}
};
let intervalId = setInterval(incrementNumber, 500);
// ...5秒后
// 终止
12.2 location对象
1 | // 给属性赋值命令浏览器进行页面跳转 |
12.3 navigator对象
1 | // 浏览器版本 |
12.5 history对象
1 | history.back(); // 后退一页 |
第十三章 客户端检测
13.2 用户代理检测
- 浏览器核心
- Firefox的渲染引擎是Gecko
- Safari的渲染引擎是WebKit
- Chrome的渲染引擎是Blink,使用V8作为JavaScript引擎
13.3 软件与硬件检测
- 浏览器元数据
- Geolocation API:查询宿主系统并尽可能精确地返回设备的位置信息
- Connection State:通过navigator.onLine属性来确定浏览器的联网状态
- NetworkInformation API:同上面一样,用来确定浏览器联网状态,通过navigator.connection属性使用
- navigator.hardwareConcurrency:返回浏览器支持的逻辑处理器核心数量
- navigator.deviceMemory:返回设备大致的系统内存大小
第十四章 DOM
nodeType属性用来区分不同类型的节点
- 值为1:元素节点
- 值为3:文本节点
- 值为9:Document节点
访问元素节点常用方法
- document.getElementById():通过id得到元素
- document.getElementsByTagName():通过标签名得到元素数组
- document.getElementsByClassName():通过类名得到元素数组
- document.querySelector():通过选择器得到元素
- document.querySelectorAll():通过选择器得到元素数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14<div id="box1">
<p>123</p>
<p class="spec">456</p>
<p>789</p>
</div>
<script>
let box1 = document.getElementById('box1');
let spec = document.getElementsByClassName('spec');
let p = box1.getElementsByTagName('p'); // [p, p.spec, p]
let theP = document.querySelector('#box1 .spec'); // <p class="spec">456</p>
let thePAll = document.querySelectorAll('#box1 p');
console.log(thePAll[2]); // <p>789</p>
</script>
节点关系
- parentNode:父节点
- children:子节点
- firstElementChild:第一个子节点
- lastElementChild:最后一个子节点
- previousElementSibling:上一个兄弟节点
- nextElementSibling:下一个兄弟节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<div id="box1">
<p>123</p>
<p id="spec">456</p>
<p>789</p>
</div>
<script>
let box1 = document.getElementById('box1');
let spec = document.getElementById('spec');
console.log(box1.children); // [p, p.spec, p]
console.log(box1.firstElementChild); // <p>123</p>
console.log(box1.lastElementChild); // <p>789</p>
console.log(spec.previousElementSibling); // <p>123</p>
console.log(spec.nextElementSibling); // <p>789</p>
</script>
改变元素节点中的内容
- innerHTML:以HTML语法设置节点中的内容
- innerText:以纯文本形式设置节点中的内容
1
2
3
4
5
6
7
8
9<div id="box1"></div>
<div id="box2"></div>
<script>
</script>
改变元素节点的CSS样式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<div id="box1">123</div>
<a href="www.baidu.com" id="link">
<img src="images/1.jpg" alt="不存在的图片" id="pic">
</a>
<script>
let box1 = document.getElementById('box1');
let pic = document.getElementById('pic');
let link = document.getElementById('link');
box1.style.bacgroudColor = 'red';
box1.style.bacgroudImage = 'url(images/2.jpg)';
box1.style.fontSize = '32px';
// 修改图片路径
pic.src = 'images/3.jpg';
// 修改跳转链接
link.href = 'diyulan.me';
</script>自定义属性
1
2
3
4
5
6
7
8
9<div id="box1"></div>
<script>
let box1 = document.getElementById('box1');
box1.setAttribute('data-n', 10); // <div id="box1" data-n="10"></div>
let getDIY = box1.getAttribute('data-n');
console.log(getDIY); // 10
</script>创建节点和移动节点
- document.createElement():创建tagname的HTML元素,并没有挂载到DOM树上
- appendChild():可以将createElement()挂载到父节点内,成为父节点的最后一个子节点
- insertBefore():可以将createElement()挂载到父节点内,第二个参数决定挂载位置
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<div id="box1">
<p>我是段落0</p>
<p id="move1">我是段落1</p>
<p id="move2">我是段落2</p>
</div>
<div id="box2">
<p>我是老油条</p>
</div>
<script>
let box1 = document.getElementById('box1');
let box2 = document.getElementById('box2');
let moveP1 = document.getElementById('move1');
let moveP2 = document.getElementById('move2');
let ps1 = box1.getElementsByTagName('p');
let ps2 = box2.getElementsByTagName('p');
// 创建节点
let createP1 = document.createElement('p');
let createP2 = document.createElement('p');
createP1.innerText = '我是新来的';
createP2.innerText = '我喜欢当第一';
// 挂载节点
box1.appendChild(createP1);
box1.insertBefore(createP2, ps1[0]);
// 移动节点
box2.appendChild(moveP1);
box2.insertBefore(moveP2, ps2[0]);
</script>
克隆节点:参数是一个布尔值,表示是否深克隆,如果为true则节点的所有后代节点都会克隆,如果为false则只克隆节点本身
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<div id="box1">
<ul>
<li>咖啡</li>
<li>牛奶</li>
<li>可乐</li>
</ul>
</div>
<div id="box2"></div>
<script>
let box1 = document.getElementById('box1');
let box2 = document.getElementById('box2');
// ul是一个类数组,就算只有一个ul标签也要写[0]
let theUl = box1.getElementsByTagName('ul')[0];
// 克隆节点,并添加到box2
let newUl = theUl.cloneNode(true);
box2.appendChild(newUl);
</script>删除节点:只能由父元素来删除子节点
1
2
3
4
5
6
7
8
9<div id="box1">
<p id="delete">123</p>
</div>
<script>
let box1 = document.getElementById('box1');
let delP = document.getElementById('delete');
box1.removeChild(delP);
</script>
第十五章 DOM扩展
15.1 Selectors API
- matches():接收一个CSS选择符参数,如果元素匹配返回true,否则返回false
15.3 HTML5
- 推荐id选择器负责JS,class选择器负责CSS
- classList.add():class选择器内添加值
- classList.remove():class选择器内删除值
1
2
3
4
5
6
7
8<div id="box1"></div>
<script>
</script>
第十六章 DOM2和DOM3
16.2 样式
- CSS属性名使用连字符表示法;JS中这些属性必须转换为驼峰大小写形式
CSS属性 | JS属性 |
---|---|
background-image | style.backgroundImage |
color | style.color |
display | style.display |
font-family | style.fontFamily |
第十七章 事件
17.1 事件流
- 事件冒泡:从最具体的元素(文档树中最深的节点)开始触发,然后向上传播至没有那么具体的元素(文档)
- 事件捕获:最不具体的节点应该最先收到事件,而最具体的节点应该最后收到事件
- DOM事件流分3个阶段:事件捕获、到达目标和事件冒泡
17.2 事件处理程序
事件处理程序:用户或浏览器执行的某种动作,比如单击、加载、鼠标悬停等事件,为响应事件而调用相关函数;事件处理程序的名字以”on”开头
DOM0事件处理程序,以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理
1
2
3
4
5
6
7
8
9
10<script>
// 绑定事件处理函数
let btn = document.getElementById("myBtn");
btn.onclick = function() {
console.log(this.id); // "myBtn"
};
// 移除事件处理函数
btn.onclick = null;
</script>DOM2事件处理程序
- addEventListener():接收3个参数:事件名、事件处理函数和一个布尔值,true表示在捕获阶段调用事件处理程序,false(默认值)表示在冒泡阶段调用事件处理程序
- removeEventListener():通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除;addEventListener()添加的匿名函数无法移除
1
2
3
4
5
6
7
8
9
10<script>
let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
console.log(this.id); // "myBtn"
}, false);
// 多个事件处理程序以添加顺序来触发
btn.addEventListener("click", () => {
console.log("Hello world!");
}, false);
</script>
IE事件处理程序,以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理
- attachEvent()和detachEvent():这两个方法接收两个同样的参数:事件处理程序的名字和事件处理函数
1
2
3
4
5
6<script>
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function() {
console.log(this === window); // true
});
</script>
- attachEvent()和detachEvent():这两个方法接收两个同样的参数:事件处理程序的名字和事件处理函数
17.3 事件对象
- 在DOM中发生事件时,所有相关信息都会被收集并存储在一个名为event的对象中。这个对象包含了一些基本信息,比如导致事件的元素、发生的事件类型,以及可能与特定事件相关的任何其他数据
- 属性方法
- bubbles:表示事件是否冒泡
- cancelable:表示是否可以取消事件的默认行为
- currentTarget:当前事件处理程序所在的元素
- defaultPrevented:true表示已经调用preventDefault()方法(DOM3Events中新增)
- detail:事件相关的其他信息
- eventPhase:表示调用事件处理程序的阶段:1代表捕获阶段,2代表到达目标,3代表冒泡阶段
- preventDefault():用于取消事件的默认行为。只有cancelable为true才可以调用这个方法
- stopImmediatePropagation():用于取消所有后续事件捕获或事件冒泡,并阻止调用任何后续事件处理程序(DOM3Events中新增)
- stopPropagation():用于取消所有后续事件捕获或事件冒泡。只有bubbles为true才可以调用这个方法
- target:事件目标
- trusted:true表示事件是由浏览器生成的。false表示事件是开发者通过JavaScript创建的(DOM3Events中新增)
- type:被触发的事件类型
- View:与事件相关的抽象视图。等于事件所发生的window对象
17.4 事件类型
- DOM3事件类型:用户界面事件、焦点事件、鼠标事件、滚轮事件、输入事件、键盘事件、合成事件
17.5 内存与性能
- 事件委托:可利用事件冒泡,只使用一个事件处理程序来管理一种类型的事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<ul id="myLinks">
<li id="goGithub">去GitHub社区</li>
<li id="doSomething">做点什么</li>
<li id="sayName">说名字</li>
</ul>
<script>
let list = document.getElementById("myLinks");
list.addEventListener("click", (event) => {
let target = event.target;
switch(target.id) {
case "goGithub":
location.href = "https://github.com";
break;
case "doSomething":
document.title = "我更改了文档的标题";
break;
case "sayName":
console.log("幽蓝");
break;
} });
</script>
17.6 模拟事件
- 创建模拟事件:document.createEvent()方法创建一个event对象,参数接收一个创建事件类型的字符串
- 模拟鼠标事件:initMouseEvent()方法,用于为新对象指定鼠标的特定信息
- 模拟键盘事件:initKeyboardEvent()方法,用于为新对象指定键盘的特定信息
- 模拟其他事件:initEvent()方法
第十八章 动画与Canvas图形
18.1 使用requestAnimationFrame
- requestAnimationFrame():告诉浏览器要执行动画了,浏览器可以通过最优方式确定重绘的时序
- cancelAnimationFrame():取消重绘任务
1
2
3
4
5
6<script>
let requestID = window.requestAnimationFrame(()=>{
console.log('Repaint!');
});
window.cancelAnimationFrame(requestID);
</script>
18.2 基本的画布功能
- canvas标签设置宽高,默认画布不可见
- getContext():参数”2d”,表示获取2D上下文对象
- toDataURL():导出<canvas>元素上的图像
- fillStyle:设置填充颜色
- strokeStyle:设置边框颜色
- fillRect:绘制一个填充的矩形
- strokeRect:绘制一个边框的矩形
- clearRect:设置透明像素擦除一个矩形区域
1
2
3
4
5
6
7
8
9
10
11
12<canvas id="drawing" width="500px" height="300px"></canvas>
<script>
let drawing = document.getElementById('drawing');
let ctx = drawing.getContext('2d');
ctx.fillStyle = '#95e1d3';
ctx.strokeStyle = '#b83b5e';
ctx.fillRect(10, 10, 50, 50);
ctx.strokeRect(30, 30, 50, 50);
// 擦除一个钜形区域
// ctx.clearRect(10, 10, 50, 50);
</script>
18.3 2D绘图上下文
- 绘制路径属性/方法
- beginPath():新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径
- closePath():闭合路径之后图形绘制命令又重新指向到上下文中
- stroke():通过线条来绘制图形轮廓
- fill():通过填充路径的内容区域生成实心的图形
- moveTo():从一个点到另一个点的移动过程
- lineTo():绘制一条从当前位置到指定x以及y位置的直线
- arc():画一个以(x,y)为圆心的以radius为半径的圆弧(圆)
1 | <canvas id="drawing" width="500px" height="300px"></canvas> |
- 绘制文本:fillText()和strokeText()
18.4 WebGL
- WebGL是画布的3D上下文;WebGL2.0上下文的名字叫”webgl2”,WebGL1.0上下文的名字叫”webgl1”
第十九章 表单脚本
19.1 表单基础
preventDefault():阻止表单提交
1
2
3
4
5
6
7<script>
let form = document.getElementById("myForm");
form.addEventListener("submit", (event) => {
// 阻止表单提交
event.preventDefault();
});
</script>表单字段:表单元素可以使用elements属性来访问,elements集合是一个有序列表,通过索引位置和name属性来访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<form id="form1" action="">
<button type="submit">点我提交</button>
<input type="text" value="请输入内容" name="textbox1">
<div></div>
</form>
<script>
let form = document.getElementById("form1");
// 取得表单中的第一个字段
let field1 = form.elements[0];
// 取得表单中名为"textbox1"的字段
let field2 = form.elements["textbox1"];
// 取得字段的数量
let fieldCount = form.elements.length;
console.log(field1); // <button type="submit">点我提交</button>
console.log(field2); // <input type="text" value="请输入内容" name="textbox1">
console.log(fieldCount); // 2,只计算表单功能的子标签,div标签不计算在内
</script>表单字段的公共属性和方法
- disabled:布尔值,表示表单字段是否禁用
- form:指针,指向表单字段所属的表单。这个属性是只读的
- name:字符串,这个字段的名字
- readOnly:布尔值,表示这个字段是否只读
- tabIndex:数值,表示这个字段在按Tab键时的切换顺序
- type:字符串,表示字段类型,如”checkbox”、”radio”等
- value:要提交给服务器的字段值。对文件输入字段来说,这个属性是只读的,仅包含计算机上某个文件的路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<form id="myForm" action="">
<button type="submit" name="submit-btn">点我提交</button>
</form>
<script>
// 第一次点击之后禁用提交按钮。通过监听submit事件来实现
// 只有提交按钮才能触发submit事件
let form = document.getElementById('myForm');
form.addEventListener('submit', (event)=>{
let target = event.target;
// 取得提交按钮
let btn = target.elements["submit-btn"];
// 禁用提交按钮
btn.disabled = true;
});
</script>
19.2 文本框编程
- 屏蔽字符,只允许输入数字
1
2
3
4
5
6
7
8
9
10
11
12
13
14<form id="myForm" action="">
请输入数字:<input type="text" value="" id="textbox">
</form>
<script>
let text = document.getElementById('textbox');
textbox.addEventListener("keypress", (event) => {
if (!/\d/.test(String.fromCharCode(event.charCode)) &&
event.charCode > 9 &&
!event.ctrlKey){
event.preventDefault();
}
});
</script>
第二十章 JavaScript API
- Atomics API:用于保护代码在多线程内存访问模式下不发生资源争用
- postMessage() API:支持从不同源跨文档发送消息,同时保证安全和遵循同源策略
- Encoding API:用于实现字符串与缓冲区之间的无缝转换(越来越常见的操作)
- File API:提供了发送、接收和读取大型二进制对象的可靠工具
- 媒体元素
- 拖放 API:支持方便地将元素标识为可拖动,并在操作系统完成放置时给出回应。可以利用它创建自定义可拖动元素和放置目标
- Notifications API:提供了一种浏览器中立的方式,以此向用户展示消通知弹层
- Streams API:支持以全新的方式读取、写入和处理数据
- Timing API:提供了一组度量数据进出浏览器时间的可靠工具
- Web Components API:为元素重用和封装技术向前迈进提供了有力支撑
- Web Cryptography API:让生成随机数、加密和签名消息成为一类特性
第二十一章 错误处理与调试
21.2 错误处理
- 错误类型
- InternalError类型的错误会在底层JavaScript引擎抛出异常时由浏览器抛出。例如,递归过多导致了栈溢出。这个类型并不是代码中通常要处理的错误,如果真发生了这种错误,很可能代码哪里弄错了或者有危险了
- EvalError类型的错误会在使用eval()函数发生异常时抛出
- RangeError错误会在数值越界时抛出
- ReferenceError会在找不到对象时发生
- SyntaxError经常在给eval()传入的字符串包含JavaScript语法错误时发生
- TypeError主要发生在变量不是预期类型,或者访问不存在的方法时
- URIError在使用encodeURI()或decodeURI()但传入了格式错误的URI时发生
- 大多数情况下,最好使用严格相等(===)和严格不相等(!==)操作符来避免类型转换
- 使用 try/catch 语句,可以通过更合适的方式对错误做出处理,避免浏览器处理
第二十二章 处理XML
- DOMParser类型是简单的对象,可以将XML字符串解析为DOM文档
- XMLSerializer类型执行相反操作,将DOM文档序列化为XML字符串
- DOM Level 3新增了针对XPath API的规范。该API可以让JavaScript针对DOM文档执行任何XPath查询并得到不同数据类型的结果
第二十三章 JSON
23.1 语法
- JSON作为替代XML的一个方案提出,解决XML冗余和啰唆问题,JSON不属于JavaScript,它们只是拥有相同的语法
- JSON支持3种类型的值
- 简单值:字符串、数值、布尔值和null,特殊值undefined不可以
- 对象:第一种复杂数据类型,对象表示有序键/值对。每个值可以是简单值,也可以是复杂类型
- 数组:第二种复杂数据类型,数组表示通过索引访问的有序列表。数组的值可以是任意类型
- JSON与JS区别:JSON字符串必须使用双引号;JSON没有变量、函数或对象实例的概念;JSON的所有记号都只表示结构化数据
23.2 解析与序列化
JSON的优势:能解析成可用的JS对象,JS开发者可以非常方便地使用JSON数据
JSON对象有两个方法:stringify()和parse()
- JSON.stringify():能把JavaScript序列化为JSON字符串;值为undefined的任何属性也会被跳过
- JSON.parse():能把JSON解析为原生JavaScript值;新对象接收解析的值与原对象不是同一个引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15let book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas",
"Matt Frisbie"
],
edition: 4,
year: 2017
};
let jsonText = JSON.stringify(book);
// {"title":"Professional JavaScript","authors":["Nicholas C. Zakas","Matt Frisbie"],"edition":4,"year":2017}
let bookCopy = JSON.parse(jsonText);
console.log(book === bookCopy); // false
过滤结果:JSON.stringify()第二个参数缩进想要的JSON字符串;如果第二参数是函数,还能修改值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16let jsonText = JSON.stringify(book, ['title', 'edition']);
// {"title":"Professional JavaScript","edition":4}
let jsonText1 = JSON.stringify(book, (key, value) => {
switch (key) {
case "authors":
return value.join(",");
case "year":
return 5000;
case "edition":
return undefined;
default:
return value;
}
});
// {"title":"Professional JavaScript","authors":"Nicholas C. Zakas,Matt Frisbie","year":5000}字符串索引:JSON.stringify()第三个参数控制缩进和空格
1
2
3
4
5
6
7
8
9
10
11
12// 每级缩进4个空格
let jsonText = JSON.stringify(book, null, 4);
console.log(jsonText);
// {
// "title": "Professional JavaScript",
// "authors": [
// "Nicholas C. Zakas",
// "Matt Frisbie"
// ],
// "edition": 4,
// "year": 2017
// }
第二十四章 网络请求与远程资源
24.1 XMLHttpRequest对象
创建XHR对象
let xhr = new XMLHttpRequest();
- open():启动一个请求以备发送;3个参数:请求类型(“get”、”post”等)、请求URL、请求是否异步的布尔值;不会真正发送请求
- send():发送请求;1个参数:发送的数据;不需要发送数据则必须传入null
XHR对象的属性
- responseText:作为响应体返回的文本
- responseXML:如果响应的内容类型是”text/xml”或”application/xml”,那就是包含响应数据的XML DOM文档
- status:响应的HTTP状态
- statusText:响应的HTTP状态描述
HTTP状态码
- 2xx,表示成功
- 304,表示资源未修改过,是从浏览器缓存中直接拿取的
readyState属性
- 0:未初始化
- 1:启动,已调用open()
- 2:发送,已调用send()
- 3:接收到部分响应数据
- 4:接收到全部响应数据
- readystatechange事件:readystate属性的值由一个值变成另一个值,都会触发readystatechange事件
- onreadystatechange事件处理程序不会收到event对象
1
2
3
4
5
6
7
8
9
10
11
12let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
console.log(xhr.responseText);
} else {
console.log("Request was unsuccessful: " + xhr.status);
}
}
};
xhr.open("get", "example.txt", true);
xhr.send(null);
XHR请求
- setRequestHeader():发送额外的请求头部;接收两个参数:头部字段的名称和值;必须在open()之后、send()之前调用setRequestHeader()
- getResponseHeader():从XHR对象获取响应头部;传入要获取头部的名称
- getAllResponseHeaders():返回字符串,通过解析头部字段的输出,可以知道服务器发送的所有头部
- GET请求:向服务器查询某些信息
- POST请求:向服务器发送应该保存的数据
24.3 跨源资源共享
- CORS:使用自定义的HTTP头部允许浏览器和服务器相互了解,以确实请求或响应应该成功还是失败
24.4 替代性跨源技术
图像Ping:只能发送GET请求;无法访问服务器的响应文本
1
2
3
4
5let img = new Image();
img.onload = img.onerror = function () {
console.log("Done!");
};
img.src = "http://www.example.com/test?name=Nicholas";JSONP:两部分组成回调函数和数据
1
2
3
4
5
6
7
8
9function handleResponse(response) {
console.log(`
You're at IP address ${response.ip}, which is in
${response.city}, ${response.region_name}`);
}
let script = document.createElement("script");
script.src = "http://freegeoip.net/json/?callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);Fetch API是作为对XHR对象的一种端到端的替代方案而提出的。这个API提供了优秀的基于期约的结构、更直观的接口,以及对Stream API的最好支持
Web Socket是与服务器的全双工、双向通信渠道。与其他方案不同,Web Socket不使用HTTP,而使用了自定义协议,目的是更快地发送小数据块。这需要专用的服务器,但速度优势明显
第二十五章 客户端存储
25.1 cookie
- 每个浏览器限制cookie的数量不同,超过上限浏览器会删除之前设置的cookie
- 每个域不超过20个cookie
- 每个域不超过81920字节
- 单个cookie不超过4096字节
- cookie的构成
- 名称:cookie名不区分大小写,但服务器软件可能会区分大小写
- 值:存储在cookie里的字符串值。这个值必须经过URL编码
- 域:cookie有效的域
- 路径:请求URL中包含这个路径才会把cookie发送到服务器
- 过期时间:表示何时删除cookie的时间戳;默认浏览器会话结束后删除所有cookie
- 安全标志:设置之后,只在使用SSL安全连接的情况下才会把cookie发送到服务器
- cookie的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 创建cookie
document.cookie = 'name=ulan';
// 名或值非英文,写入时需要encodeURIComponent()编码,读取时decodeURIComponent()解码
document.cookie = `username=${encodeURIComponent('幽蓝')}`;
// 过期时间,默认会话时间指关闭浏览器消失
// expires,值为Data类型,可以指定过期时间
// max-age,值为数字,表示当前时间多少秒后过期,单位是秒
document.cookie = `age=9; expires=${new Date(
'2099-01-01 00:00:00'
)}`;
document.cookie = 'job=softwareEngineer; max-age=5'; // 5秒后过期
document.cookie = `job=softwareEngineer; max-age=${24 * 3600 * 7}`; // 7天后过期
// 删除cookie
document.cookie = 'name=ulan; max-age=0';
25.2 Web Storage
- Web Storage解决通过客户端存储不需要频繁发送回服务器的数据时使用cookie的问题;localStorage永久存储机制,sessionStorage是跨会话的存储机制
- Storage的实例方法
- setItem(name, value):设置给定name的值
- getItem(name):取得给定name的值,不存在返回null
- removeItem(name):删除给定name的名/值对
- clear():删除所有值
- key(index):取得给定数值位置的名称
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 创建
localStorage.setItem('username', 'ulan');
localStorage.setItem('age', '19');
localStorage.setItem('job', 'softwareEngineer');
// 获取
console.log(localStorage.getItem('username')); // ulan
console.log(localStorage.getItem('friend')); // null
// 删除job
localStorage.removeItem('job');
console.log(localStorage);
// 全删除
localStorage.clear();
console.log(localStorage);
// sessionStorage使用基本一样
sessionStorage.setItem('friend', 'hello');
sessionStorage.getItem('friend');
sessionStorage.removeItem('friend');
sessionStorage.clear();
25.3 IndexedDB
- IndexedDB是类似于SQL数据库的结构化数据存储机制。不同的是,IndexedDB存储的是对象,而不是数据表。对象存储是通过定义键然后添加数据来创建的。游标用于查询对象存储中的特定数据,而索引可以针对特定属性实现更快的查询
第二十六章 模块
26.1 理解模块模式
- 按逻辑分块,各自封装,相互独立,每个块自行决定对外暴露什么,同时自行决定引入执行哪些外部代码
26.4 使用ES6模块
- 导出与导入
- demo.js文件
1
2
3
4export {age, fn, p};
const age = 9;
function fn() {}
const p = {name: '幽蓝'}; - 第一种导入方式,导入部分内容
1
2
3
4<script type="module">
import {age} from './demo.js';
console.log(age); // 9
</script> - 第二种导入方式,导入全部内容,按对象方式来访问
1
2
3
4
5<script type="module">
import * as obj from './demo.js';
console.log(obj.age); // 9
console.log(obj.p.name); // 幽蓝
</script>
第二十七章 工作者线程
27.1 工作者线程简介
- 工作者线程的价值所在:允许把主线程的工作转嫁给独立的实体,而不会改变现有的单线程模型
- JavaScript环境实际上是运行在托管操作系统中的虚拟环境。在浏览器中每打开一个页面,就会分配一个它自己的环境;使用工作者线程,浏览器可以在原始页面环境之外再分配一个完全独立的二级子环境。这个子环境不能与依赖单线程交互的API(如DOM)互操作,但可以与父环境并行执行代码
- 主要的工作者线程:专用工作者线程、共享工作者线程和服务工作者线程
27.2 专用工作者线程
worker实例化
- 比如下方html是父环境,多线程计算3次斐波那契数列,可能需要3秒;单线程计算3次斐波那契数列,可能需要9秒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<script>
const worker1 = new Worker('demo1.js');
const worker2 = new Worker('demo2.js');
const worker3 = new Worker('demo3.js');
worker1.onmessage = e => {
console.log(e.data);
}
worker2.onmessage = e => {
console.log(e.data);
}
worker3.onmessage = e => {
console.log(e.data);
}
// fb执行时间: 2616.596923828125 ms
// worker2
// fb执行时间: 2623.839111328125 ms
// worker1
// fb执行时间: 2625.576904296875 ms
// worker3
</script> - demo1.js
1
2
3
4
5
6
7
8
9
10
11
12// 斐波那契数列,当成是相当耗时的函数
function fb(n) {
if (n === 1 || n === 2) {
return 1;
}
return fb(n - 1) + fb(n - 2);
}
console.time('fb执行时间1');
const result = fb(43);
console.timeEnd('fb执行时间1');
self.postMessage('worker1'); - demo2.js
1
2
3
4
5
6
7
8
9
10
11
12// 斐波那契数列,当成是相当耗时的函数
function fb(n) {
if (n === 1 || n === 2) {
return 1;
}
return fb(n - 1) + fb(n - 2);
}
console.time('fb执行时间2');
const result = fb(43);
console.timeEnd('fb执行时间2');
self.postMessage('worker2'); - demo3.js
1
2
3
4
5
6
7
8
9
10
11
12// 斐波那契数列,当成是相当耗时的函数
function fb(n) {
if (n === 1 || n === 2) {
return 1;
}
return fb(n - 1) + fb(n - 2);
}
console.time('fb执行时间3');
const result = fb(43);
console.timeEnd('fb执行时间3');
self.postMessage('worker3');
- 比如下方html是父环境,多线程计算3次斐波那契数列,可能需要3秒;单线程计算3次斐波那契数列,可能需要9秒
Worker对象支持的事件处理程序属性
- onerror:在工作者线程中发生ErrorEvent类型的错误事件时会调用指定给该属性的处理程序
- onmessage:在工作者线程中发生MessageEvent类型的消息事件时会调用指定给该属性的处理程序
- onmessageerror:在工作者线程中发生MessageEvent类型的错误事件时会调用指定给该属性的处理程序
- postMessage():用于通过异步消息事件向工作者线程发送信息
- terminate():用于立即终止工作者线程。没有为工作者线程提供清理的机会,脚本会突然停止
27.5 小结
- 工作者线程可以运行异步JavaScript而不阻塞用户界面。这非常适合复杂计算和数据处理,特别是需要花较长时间因而会影响用户使用网页的处理任务。工作者线程有自己独立的环境,只能通过异步消息与外界通信
- 工作者线程可以是专用线程、共享线程。专用线程只能由一个页面使用,而共享线程则可以由同源的任意页面共享
- 服务工作者线程用于让网页模拟原生应用程序。服务工作者线程也是一种工作者线程,但它们更像是网络代理,而非独立的浏览器线程。可以把它们看成是高度定制化的网络缓存,它们也可以在PWA中支持推送通知
第二十八章 最佳实践
- 可维护性与可读性,不同团队有不同开发规范,入乡随俗
- 不要修改不属于你的对象。只有你自己创建的才是你的对象,包括自定义类型和对象字面量
- 优化代码,先能走在考虑怎么跑吧?