阮一峰ES6入门学习笔记
阮一峰ES6入门学习笔记
let和const
let
只会在命令所在代码块内生效在
for
循环中使用let
,会在设置循环变量的地方建立一个父作用域(即for
代码块内依旧可以声明同名变量相比较
var
,let
不能在声明之前使用只要块级作用域中存在
let
,那么外部的同名变量就被屏蔽,这时候如果在声明前使用,会进入『暂时性死区』,将该变量看做不存在如果存在暂时性死区,那么无论是调用该变量还是使用
typeof
都会抛出ReferenceError
let
不允许同一变量的重复声明外层块级作用域的变量可以在内层块级作用域被访问
块级作用域可以替代匿名立即执行函数表达式(匿名IIFE)实现不污染外部变量的效果
1
2
3
4
5
6
7
8
9
10
11// IIFE 写法
(function () {
var tmp = ...;
...
}());
// 块级作用域写法
{
let tmp = ...;
...
}函数可以在块级作用域内声明,但只能在作用域内被调用(ES6),ES5之前的版本会污染外部同名函数
块级作用域必须包含括号
1
2
3
4
5
6
7// 第一种写法,报错
if (true) let x = 1;
// 第二种写法,不报错
if (true) {
let x = 1;
}const
的作用域与let
基本一致const
只能保证变量所指向的内存地址不被改变,因此如果const
指向了一个对象,那么对象中的内容依旧可以修改,但对象本身不能被覆盖/删除。如果真的需要冻结对象,应该使用
Object.freeze
方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
// 彻底冻结一个对象及其所有属性
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};const
和let
不能被delete
删除,因为两者均属于不可设置属性顶层对象在浏览器和Node中分别指
window
和global
,在ES5中顶层对象的属性和全局变量是等价的ES2020引入了
globalThis
作为顶层对象,这样就可以在任何环境下拿到全局this
变量的解构赋值
ES6允许对变量进行解构赋值,只要左右两边模式相同就可以一一对应,如果解构失败,失败的值等于
undefined
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [x, , y] = [1, 2, 3];
x // 1
y // 3
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []只要数据结构拥有
Iterator
接口,就都可以使用数组形式的解构赋值解构赋值允许指定默认值,但默认值只有当对应位置为
undefined
(严格等于)才能生效1
2
3
4
5let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'解构赋值的默认值是惰性求值的,只有在用到的时候才会被求值
解构赋值的默认值可以引用解构赋值的其他变量(但必须是已经声明的变量)
1
2
3
4let [x = 1, y = x] = []; // x=1; y=1
let [x = 1, y = x] = [2]; // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = []; // ReferenceError: y is not defined解构赋值也可以用于对象,但是变量必须与属性同名才能取到相应的值,否则为
undefined
对象解构可以方便的提取现有对象的方法:
1
2
3
4
5
6// 例一
let { log, sin, cos } = Math;
// 例二
const { log } = console;
log('hello') // hello如果变量名和属性名不一致,需要指定属性名
1
2
3
4
5
6
7let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'解构也可以用于嵌套的对象
1
2
3
4
5
6
7
8
9
10
11
12
13let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
// 这里的p是模式名,不是变量,不会被赋值
let { p: [x, { y }] } = obj;
// 如果要赋值,需要额外加一个p
let { p, p: [x, { y }] } = obj;
x // "Hello"
y // "World"对象的解构赋值可以取到继承的属性
1
2
3
4
5
6const obj1 = {};
const obj2 = { foo: 'bar' };
Object.setPrototypeOf(obj1, obj2);
const { foo } = obj1;
foo // "bar"对象的解构也可以指定默认值
字符串也可以被解构赋值(因为字符串属于可迭代对象)
只要等号右边的值不是对象/数组,就会先将其转换为对象,因此
undefined
和null
是不能被解构赋值的函数参数也可以被解构赋值
1
2
3
4
5function add([x, y]){
return x + y;
}
add([1, 2]); // 3函数参数也可以使用默认值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 情况1(为第一个参数中的x/y指定默认值)
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
// 情况2(为move的参数指定默认值,只要参数为undefined,就会触发默认值)
function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]用途
- 交换变量的值(无需新增变量)
- 从函数返回多个值
- 为函数调用增加参数名称
- 提取JSON中的数据
- 作为函数参数的默认值
- 遍历Map结构
- 导入模块中的指定方法(最常用)
字符串的扩展
字符串拥有遍历器接口,可以被循环遍历(支持大于
0xFFFF
的码点比如Emoji)模板字符串
使用反引号代替单双引号,可以嵌入模板字符串(可以当做普通字符串、也可以当做多行字符串、还可以插入变量)
1
2
3
4
5
6
7
8
9
10
11
12
13// 普通字符串
`In JavaScript '\n' is a line-feed.`
// 多行字符串
`In JavaScript this is
not legal.`
console.log(`string text line 1
string text line 2`);
// 字符串中嵌入变量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`如果需要过滤换行符号,可以加一个
trim()
1
2
3
4
5
6$('#list').html(`
<ul>
<li>first</li>
<li>second</li>
</ul>
`.trim());嵌入变量需要把变量名加入
${}
里面,括号里面可以放入任意的JavaScript表达式,甚至可以调用函数,但不能调用没声明的变量模板字符串可以嵌套使用
1
2
3
4
5
6
7
8const tmpl = addrs => `
<table>
${addrs.map(addr => `
<tr><td>${addr.first}</td></tr>
<tr><td>${addr.last}</td></tr>
`).join('')}
</table>
`;模板字符串可以作为函数被调用
1
2let func = (name) => `Hello ${name}!`;
func('Jack') // "Hello Jack!"
字符串的新增方法
includes()
:是否包含参数字符串startsWith()
:是否在原字符串的头部endsWith()
:是否在原字符串的尾部这三个方法都支持使用第二个参数表示开始搜索的位置,其中endsWith是针对前n个字符,而其他两个方法针对从n到结束
1
2
3
4
5let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // falserepeat()
表示将原字符串重复n次padStart()
和padEnd()
表示在头部或尾部使用指定字符串补全1
2
3
4
5'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'最常见的用途是为指定的数值补全指定位数,或是提示字符串格式
1
2
3
4
5
6'1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"ES2019
新增了trimStart()
和trimEnd()
两个方法,和trim()
一致,消除头部和尾部的空格,不会修改原始字符串
正则的扩展
正则表达式的初始化
1
2
3
4var regex = new RegExp('xyz', 'i');
var regex = /xyz/i;
var regex = new RegExp(/xyz/i);
new RegExp(/abc/ig, 'i') // ES6引入,ig被覆盖为i字符串的正则方法
match()
replace()
search()
split()
正则表达式的
u
修饰符(ES6引入),用于扩展到所有Unicode字符- 包含双Unicode码字符(如Emoji)
- 包含\u{0xFFFF}以上的Unicode码
- 包含同字但不同Unicode码的情况(如K可以使用
\u{004B}
或\u{212A}
表示)
1
2
3
4/^.$/u.test("😂")
true
/^.$/.test("😂")
false正则表达式的
y
修饰符(ES6引入),要求这一次匹配必须从上一次匹配的头部开始(粘连/sticky)ES6引入了正则表达式的
flags
属性,可以返回当前所设置的所有修饰符ES2018引入了
s
修饰符,使得点号可以匹配任意单个字符,可以使用dotAll
属性进行访问1
2
3
4
5
6
7const re = /foo.bar/s;
// 另一种写法
// const re = new RegExp('foo.bar', 's');
re.test('foo\nbar') // true
re.dotAll // true
re.flags // 's'先行断言和后行断言
1
2
3
4
5
6
7// 先行断言
/\d+(?=%)/.exec('100% of US presidents have been male') // ["100"]
/\d+(?!%)/.exec('that’s all 44 of them') // ["44"]
// 后行断言
/(?<=\$)\d+/.exec('Benjamin Franklin is on the $100 bill') // ["100"]
/(?<!\$)\d+/.exec('it’s is worth about €90') // ["90"]Unicode属性类(匹配某一类的所有Unicode字符)
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
26const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π') // true
const regex = /^\p{Decimal_Number}+$/u;
regex.test('𝟏𝟐𝟑𝟜𝟝𝟞𝟩𝟪𝟫𝟬𝟭𝟮𝟯𝟺𝟻𝟼') // true
const regex = /^\p{Number}+$/u;
regex.test('²³¹¼½¾') // true
regex.test('㉛㉜㉝') // true
regex.test('ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ') // true
// 匹配所有空格
\p{White_Space}
// 匹配各种文字的所有字母,等同于 Unicode 版的 \w
[\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]
// 匹配各种文字的所有非字母的字符,等同于 Unicode 版的 \W
[^\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]
// 匹配 Emoji
/\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F/gu
// 匹配所有的箭头字符
const regexArrows = /^\p{Block=Arrows}+$/u;
regexArrows.test('←↑→↓↔↕↖↗↘↙⇏⇐⇑⇒⇓⇔⇕⇖⇗⇘⇙⇧⇩') // true组匹配和具名组匹配
使用括号注明捕获组
1
2
3
4
5
6const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj[1]; // 1999
const month = matchObj[2]; // 12
const day = matchObj[3]; // 31ES2018引入具名组匹配,允许给匹配组命名
1
2
3
4
5
6const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31可以将具名组匹配和解构赋值相结合
1
2
3let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
one // foo
two // bar还可以与字符串替换结合
1
2'2015-01-02'.replace(re, '$<day>/$<month>/$<year>')
// '02/01/2015'
可以引用前面的某个普通组/具名组
1
2
3
4
5
6
7
8const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false
// 数字引用也是有效的
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>!\1$/;
RE_TWICE.test('abc!abc!abc') // true
RE_TWICE.test('abc!abc!ab') // false可以通过
exec()
返回后的indices
属性拿到每个匹配的开始和结束位置1
2
3
4
5const text = 'zabbcdef';
const re = /ab+(cd)/;
const result = re.exec(text);
result.indices // [ [ 1, 6 ], [ 4, 6 ] ]还可以通过
indices
属性的groups
属性拿到一个包含具名组信息和开始/结束位置的对象1
2
3
4
5const text = 'zabbcdef';
const re = /ab+(?<Z>cd)/;
const result = re.exec(text);
result.indices.groups // { Z: [ 4, 6 ] }ES2020引入了
String.prototype.matchAll()
方法,可以一次性取出所有匹配,但会一个遍历器1
2
3
4
5
6
7
8
9
10
11const string = 'test1test2test3';
// g 修饰符加不加都可以
const regex = /t(e)(st(\d?))/g;
for (const match of string.matchAll(regex)) {
console.log(match);
}
// ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"]
// ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"]
// ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"]遍历器转为数组的方式
1
2[...string.matchAll(regex)]
Array.from(string.matchAll(regex))
数值的扩展
ES6规范了二进制/八进制分别用
0b
和0o
表示使用
Number()
构造函数将不同进制的数字规范为十进制ES6在Number对象上新增了
Number.isFinite()
和Number.isNaN()
两个方法,相比较全局的同名方法,Number的这两个方法ES6将
parseInt()
和parseFloat()
移植到了Number对象中,使得语言逐步模块化1
2Number.parseInt === parseInt // true
Number.parseFloat === parseFloat // trueNumber.isInteger()
可以用来判断一个数值是否为整数,但是对于20.0
也会返回True(因为JavaScript内对浮点数和整数的存储方法相同)因为JavaScript内部使用64位双精度存储数字,因此该方法可能会存在误判:
1
2Number.parseInt === parseInt // true
Number.parseFloat === parseFloat // trueNumber.EPSILON
表示最小精度的数字,用于为浮点数计算设置一个误差范围1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
200.1 + 0.2
// 0.30000000000000004
0.1 + 0.2 - 0.3
// 5.551115123125783e-17
5.551115123125783e-17.toFixed(20)
// '0.00000000000000005551'
0.1 + 0.2 === 0.3 // false
function withinErrorMargin (left, right) {
return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);
}
0.1 + 0.2 === 0.3 // false
withinErrorMargin(0.1 + 0.2, 0.3) // true
1.1 + 1.3 === 2.4 // false
withinErrorMargin(1.1 + 1.3, 2.4) // trueNumber.isSafeInteger()
属性用于判断是否存在溢出情况Math.trunc()
方法用于去除小数部分,返回整数部分,是Math.ceil()
和Math.floor()
的结合1
2
3Math.trunc = Math.trunc || function(x) {
return x < 0 ? Math.ceil(x) : Math.floor(x);
};Math.sign()
可以用于判断一个数到底是正数、负数还是0Math.cbrt()
可以用于计算一个数的立方根Math.clz32()
可以用于计算一个32位无符号整数内存在多少个前导01
2
3
4
5Math.clz32(0) // 32
Math.clz32(1) // 31
Math.clz32(1000) // 22
Math.clz32(0b01000000000000000000000000000000) // 1
Math.clz32(0b00100000000000000000000000000000) // 2Math.log10()
可以返回10为底的x的对数Math.log2()
可以返回以2为底的x的对数ES2016引入了指数运算符(
**
),但是和其他语言不一样,它是右结合的1
2
3
4
5
6// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2
// 512
a **= 2;
// 相当于a = a * aES2020引入了一个新的数据类型
BigInt
来解决这个问题,可以存储任何位数的整数,在声明字面量时添加后缀n来声明,也可以通过BitInt()
构造方法将其他类型的值转换为BitInt
函数的扩展
ES6之后函数参数可以引入默认值:
1
2
3
4
5
6
7function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello使用参数默认值时,函数不能有同名参数
可以与解构赋值的默认值结合使用
1
2
3
4
5function foo({x, y = 5} = {}) {
console.log(x, y);
}
foo() // undefined 5区分默认值两种写法:
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// 写法一
function m1({x = 0, y = 0} = {}) {
return [x, y];
}
// 写法二
function m2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
// 函数没有参数的情况
m1() // [0, 0]
m2() // [0, 0]
// x 和 y 都有值的情况
m1({x: 3, y: 8}) // [3, 8]
m2({x: 3, y: 8}) // [3, 8]
// x 有值,y 无值的情况
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]
// x 和 y 都无值的情况
m1({}) // [0, 0];
m2({}) // [undefined, undefined]
m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]只有尾部的参数才能有默认值
函数的
length
参数指定的是期望传入的参数个数,不计包含默认值的参数(因为不期望能传入)ES6引入rest参数替代之前的arguments变量实现可变参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
// arguments变量的写法
function sortNumbers() {
// 使用Array.prototype.slice.call将参数转换为数组
return Array.prototype.slice.call(arguments).sort();
}
// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();函数的
name
属性可以返回函数名箭头函数的四个注意点:
this
指向定义时所在的对象,而不是使用时所在的对象- 不可以当做构造函数使用(不能使用new命令)
- 没有
arguments
对象,但是可以使用rest参数实现可变参数 - 没有
yield
命令,不能当做Generator
函数
不适合使用箭头函数的地方
- 定义对象的方法,这里的this会指向全局对象,因为对象不属于作用域
- this的值动态变化(例如响应事件时,因为事件的this一般是触发事件的对象,不是某个固定的对象)
尾调用优化(目前只有Safari支持,可以节省内存,直达内层函数的调用帧,但是要求严格模式开启)
尾递归优化(存储递归的结果,不会发生栈溢出,所有满足ES6规范的客户端都要支持)
递归函数的改写
- 对于需要状态参数的递归函数,可以用另一个caller将其包裹,保持API的友好
ES2019要求函数的
toString()
方法必须返回函数本身的所有代码(包括注释、空格)ES2019允许
catch
语句省略参数1
2
3
4
5try {
} catch { // 如果用不上err不用再写
}
关于this/apply/call/bind
this
默认指向最后调用它的那个对象
1
2
3
4
5
6
7
8
9
10
11
12var name = "windowsName";
function fn() {
var name = 'Cherry';
innerFunction();
function innerFunction() {
console.log(this.name); // windowsName
}
}
fn()
// 这时候fn()的this是window箭头函数中永远指向定义时候的this,不受调用干扰
1
2
3
4
5
6
7
8
9
10
11
12
13
14var name = "windowsName";
var fn = {
name: 'Cherry',
// innerFunction的this为fn
innerFunction: function() {
// 此处继承innerFunction的this
setTimeout(() => {
console.log(this.name)
})
}
}
fn.innerFunction()箭头函数本身没有this,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined
使用
that = this
来指定this1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21var name = "windowsName";
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
var that = this;
setTimeout( function() {
// 指定后that就变成了a而不是window
that.func1()
},100);
}
};
a.func2() // Cherry
apply/call/bind
使用这三个函数也可以来改变
this
的指向1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17func2: function () {
setTimeout( function () {
this.func1()
}.apply(a),100);
}
func2: function () {
setTimeout( function () {
this.func1()
}.call(a),100);
}
func2: function () {
setTimeout( function () {
this.func1()
}.bind(a)(),100);
}apply
调用一个函数,传入this值和数组包裹的参数列表call
调用一个函数,传入this值和单独的若干个参数bind
创建一个新的函数,传入this和预置参数,被调用的时候可以传入新的参数,附加在预置参数之后
数组的扩展
扩展运算符,可以理解为
rest
参数的逆运算,将一个数组转换为逗号分割的参数序列1
console.log(...[1, 2, 3]) // 1, 2, 3
扩展运算符可以代替
apply
方法: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// ES5 的写法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f.apply(null, args);
// ES6的写法
function f(x, y, z) {
// ...
}
let args = [0, 1, 2];
f(...args);
---
// ES5 的写法
Math.max.apply(null, [14, 3, 77])
// ES6 的写法
Math.max(...[14, 3, 77])
// 等同于
Math.max(14, 3, 77);
---
// ES5的 写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);
// ES6 的写法
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);扩展运算符还可以用来复制数组(深拷贝)
1
2
3
4
5const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;扩展运算符还可以合并数组(深拷贝)
1
2
3
4
5
6
7
8
9
10
11const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]扩展运算符还可以生成数组
1
2
3
4
5
6
7
8
9
10
11const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]
const [first, ...rest] = [];
first // undefined
rest // []
const [first, ...rest] = ["foo"];
first // "foo"
rest // []扩展运算符可以打散所有可迭代对象,比如ES6的字符串(可以实现按Unicode字符打散)
1
[..."😂😂"] // (2) ["😂", "😂"]
Array.from()
方法可以将可遍历对象转换为真正的数组,效果和扩展运算符类似,但是Array.from()
方法只要求有length
属性,扩展运算符要求满足Symbol.iterator
接口1
2
3
4
5
6
7
8
9
10
11
12let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']Array.of()
用于将一堆值转换为一个数组,为了弥补Array()
函数构造函数的不足1
2
3
4
5
6
7Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]Array.copyWithin()
方法可以原地覆盖成员,三个参数分别为替换开始位置,读取开始位置(可以为负数),结束读取位置(可以为负数)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// 将3号位复制到0号位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
// [4, 2, 3, 4, 5]
// -2相当于3号位,-1相当于4号位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)
// [4, 2, 3, 4, 5]
// 将3号位复制到0号位
[].copyWithin.call({length: 5, 3: 1}, 0, 3)
// {0: 1, 3: 1, length: 5}
// 将2号位到数组结束,复制到0号位
let i32a = new Int32Array([1, 2, 3, 4, 5]);
i32a.copyWithin(0, 2);
// Int32Array [3, 4, 5, 4, 5]
// 对于没有部署 TypedArray 的 copyWithin 方法的平台
// 需要采用下面的写法
[].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);
// Int32Array [4, 2, 3, 4, 5]find()
和findIndex()
,接收回调函数,直到找到一个返回值为True的成员/成员位置,如果没有则返回undefined1
2
3
4
5
6
7
8
9
10
11// 回调函数可以有三个参数,分别为当前值,当前位置,原完整数组
// 两个方法都可以接受第二个参数,用于绑定回调函数的this对象
[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9;
}) // 2
function f(v){
return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person); // 26这两个方法可以识别NaN,但
indexOf()
方法不行fill()
方法可以填充数组,有三个参数分别为填充的值、填充的起始位置、填充的结束位置,但是如果填充的类型是对象,那么所有对象都指向同一内存地址1
2
3
4
5
6
7
8
9let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
arr
// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
let arr = new Array(3).fill([]);
arr[0].push(5);
arr
// [[5], [5], [5]]数组实例的
keys()
values()
entries()
分别生成三个迭代器对象,用于迭代键、值、键值对,如果不使用for of
循环,可以手动调用遍历器对象的next()
方法进行遍历ES2016引入了
includes()
方法,相比较indexOf()
方法更直观,第一个参数是搜索值,第二个参数是搜索的起始位置,使用的是严格相等运算符进行判断,不会出现[NaN].indexOf(NaN)
返回-1的情况注意和Map&&Set的has方法区分开来
展平数组的若干方法
常规递归方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function flattenMd(arr){
var result=[]
function flatten(arr){
for (var i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
flatten(arr[i]);
}else{
result.push(arr[i]);
}
}
}
flatten(arr);
return result;
}
var arr=[1, [2, 3, [4, 5], 6], 7, 8]
console.log(flattenMd(arr));[ 1, 2, 3, 4, 5, 6, 7, 8 ];数组concat+递归
1
2
3
4flatten = function (arr) {
return arr.reduce((plane, toBeFlatten) => (plane.concat(Array.isArray(toBeFlatten) ? flatten(toBeFlatten) : toBeFlatten)), []);
}利用ES6的展开符号递归
1
2
3
4function deepFlatten(arr) {
flatten = (arr)=> [].concat(...arr);
return flatten(arr.map(x=>Array.isArray(x)? deepFlatten(x): x));
}数组的join和split(只适用于数字数组)
1
2
3
4
5function flattenMd(arr) {
return arr.join().split(',');
}
var arr=['1', [null, 3, [4, 5], {K:1}], undefined, 8]
console.log(flattenMd(arr));//[ '1', '', '3', '4', '5', '[object Object]', '', '8' ]ES6的
Array.prototype.flat()
方法1
2
3
4
5
6
7
8[1, 2, [3, [4, 5]]].flat()
// [1, 2, 3, [4, 5]]
[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]
[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]ES6的
Array.prototype.flatMap()
方法(结合了flat()
和map()
,但是只能展开一层数组,而且返回的还是一个嵌套数组)1
2
3// 相当于 [[[2]], [[4]], [[6]], [[8]]].flat()
[1, 2, 3, 4].flatMap(x => [[x * 2]])
// [[2], [4], [6], [8]]
对象的扩展
属性可以简写(使用大括号包围)
1
2
3
4
5
6
7
8
9
10
11function f(x, y) {
return {x, y};
}
// 等同于
function f(x, y) {
return {x: x, y: y};
}
f(1, 2) // Object {x: 1, y: 2}方法也可以简写(在Vue.js里面有用到)
1
2
3
4
5
6
7
8
9
10
11
12
13const o = {
method() {
return "Hello!";
}
};
// 等同于
const o = {
method: function() {
return "Hello!";
}
};JS模块也使用到了简写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21let ms = {};
function getItem (key) {
return key in ms ? ms[key] : null;
}
function setItem (key, value) {
ms[key] = value;
}
function clear () {
ms = {};
}
module.exports = { getItem, setItem, clear };
// 等同于
module.exports = {
getItem: getItem,
setItem: setItem,
clear: clear
};但是构造函数不能简写
JavaScript定义对象的属性有两种方法,但是如果用大括号定义对象只能用方法1
1
2
3
4
5// 方法一
obj.foo = true;
// 方法二
obj['a' + 'bc'] = 123;方法也可以拥有
name
属性,返回方法的名字,但是匿名函数返回anonymous
,bind
方法创造的函数,名字前会加一个bound
ES6引入了
super
关键字,指向原型对象,但是只能用在方法里1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 报错
const obj = {
foo: super.foo
}
// 报错
const obj = {
foo: () => super.foo
}
// 报错
const obj = {
foo: function () {
return super.foo
}
}super
关键字等同于Object.getPrototypeOf(this).foo
(属性)或Object.getPrototypeOf(this).foo.call(this)
(方法)ES2020引入链式判断运算符(和Swift类似),如果条件都不满足,返回
undefined
1
2const firstName = message?.body?.user?.firstName || 'default';
const fooValue = myForm.querySelector('input[name=foo]')?.value链式判断运算符可以用于调用一个可能不存在的对象,利用它如果不为真就不会求值的特性
ES2020引入了一个新的Null判断运算符
??
,对比||
,只有左侧的值为null
和undefined
才能返回右侧的值,而不像||
对false
和0
也生效
对象的各种方法
- 初始化方法
Object.assign()
合并两个对象,返回目标对象,同名对象后来者后覆盖,可以用于深拷贝Object.create()
创建一个新对象,可以用于继承
- 配置属性
Object.defineProperty()
给对象添加一个属性,并指定该属性的配置Object.defineProperties()
给对象添加多个属性,并分别指定它们的配置
- 获取属性
Object.getOwnPropertyDescriptor(obj, prop)
可以获取到方法中某个属性的描述对象Object.getOwnPropertyNames(obj)
可以获取到对象自身所有属性(不含Symbol
,但是包含不可枚举属性)的键名Object.getOwnPropertySymbols(obj)
可以获取到自身所有Symbol
属性的键名for...in
循环遍历对象自身的可枚举属性(不含Symbol
属性)Object.entries()
返回对象自身可枚举属性的键值对数组(二维数组),和for...in
返回内容相同Object.keys()
获取对象自身所有属性(不含Symbol
和不可枚举属性)Reflect.ownKeys(obj)
获取所有键名(包含Symbol
和不可枚举属性)Object.prototype.toString()
返回对象的字符串表示
- 配置状态
Object.preventExtensions()
阻止对象的任何扩展Object.freeze()
阻止对象被修改Object.seal()
阻止其他代码删除对象的属性(使用delete
关键字)Object.setPrototypeOf()
设置对象的原型
- 获取状态
Object.prototype.hasOwnProperty()
某个对象是否有非继承的指定属性Object.prototype.isPrototypeOf()
指定对象是否在本对象的原型链中Object.is()
判断两个值是否相同(内存/字面量相同)
Set和Map数据结构
Set结构有以下属性和方法
Set.prototype.size
返回Set实例的成员总数Set.prototype.add(value)
添加某个值,返回Set
本身Set.prototype.delete(value)
删除一个值,返回bool(删除是否成功)Set.prototype.has(value)
返回一个布尔值,表示该值是否为Set
的成员Set.prototype.clear()
清除所有成员,没有返回值- 四个遍历方法:
Set.prototype.keys()
返回键名遍历器Set.prototype.values()
返回键值的遍历器Set.prototype.entries()
返回键值对的遍历器Set.prototype.forEach()
使用回调函数遍历成员
Map
相对于Object
,键可以为任何类型,提供了值-值的对应,属性/方法和Set
基本一致
Promise
- 本质上是一个容器,存储着未来才会结束的事件
- 对象状态不受外界影响,只有自身能定义自身的状态,外部无法改变
- 一旦状态改变,就不会再改变
Promise
新建之后会立即执行,因此一般会使用一个函数将其包裹起来,不会直接new Promise
赋值给一个变量Promise
一般会在resolve()
或者reject()
之后继续执行下面的代码,将其放在本轮事件循环的结尾,留到最后执行,因此为了避免出现问题,一般会使用return resolve()
的方式Promise
的then
会接收来自前者的返回值,因此可以附加多个then
实现链式操作catch
其实是then(undefined, function)
的别名,用于专门捕获错误finally
可以用于处理善后,无论触发then
还是catch
都会被调用Promise.all()
用于将多个Promise实例包装成一个新的Promise实例,必须所有实例变为fulfilled
或者是其中任意一个变为rejected
才会调用Promise.all
方法后面的回调函数Promise.race()
和all()
类似,但是只要有一个实例率先改变状态,就会调用后面的回调函数,可以用于超时的实现(其中一个实例会在超时后reject
)Promise.allSettled()
需要等所有参数实例返回结果(无论是rejected
还是resolved
,才会调用后面的回调函数(ES2020引入)Promise.resolve()
、Promise.reject()
可以将现有对象转换为Promise
对象
Iterator&for…of
Iterator本质就是不断调用对象的
next()
方法一个数据结构只要有
Symbol.iterator
属性(本质上是一个遍历器生成函数),就可以被认为是可遍历的,在TypeScript中可以看得更直观1
2
3
4
5
6
7
8
9
10
11
12interface Iterable {
[Symbol.iterator]() : Iterator,
}
interface Iterator {
next(value?: any) : IterationResult,
}
interface IterationResult {
value: any,
done: boolean,
}原生具有
Iterator
接口的数据结构有Array
Map
Set
String
TypedArray
- 函数的
arguments
对象 NodeList
对象
遍历器对象还可以有
return()
方法,来保证迭代提前退出(break或者出错)的时候资源被释放
Generator
- 在函数名前带星号注明生成器,遇到
yield
就返回其后的值,直到遇到return
- 可以为一个生成器赋值
Symbol.iterator
属性使其具有Iterator
接口 next()
方法如果带一个参数,这个参数就会被当做上一个yield的返回值yield*
可以在一个函数内执行另一个生成器,混合两者结果输出- Generator可以实现异步/协程
async函数
async
函数相比较Generator
,自带执行器,会自动调用next
方法- 函数前加
async
声明该函数是async
函数,函数中使用await
跳出函数,返回Promise对象 async
的错误处理机制- 包含多个
await
的函数中,只要有一个出现错误,整个Promise对象就会被reject
,因此最好把await
命令放在try...catch
中
- 包含多个
Class
- Class的出现是为了简化原型链,将原型链包装成语法糖
- Class中所有方法都是不可枚举的(ES5的原型因为是Object,可以被枚举)
- Class构造器和原型一样,是
constructor()
,但Class不能被直接调用,必须使用new - 静态方法需要在前面显式加入
static
,此时this指向类而不是实例。静态属性同理 - 继承方法使用
extends
,访问父类使用super
- 实例属性可以放在class顶层,和其他语言类似
- 私有方法/属性在名称前加井号
- Class的继承必须显式调用父类构造器,而且要在调用父类构造器之后才能修改自身属性
Module
CommonJS使用
require()
语法,运行时加载所需的方法,再使用大括号进行解构ES6模块使用
import
语法,直接导入此前已经export
过的对象/方法ES6模块默认启动严格模式
模块使用
export
输出,必须输出接口(因为import
时会对接口进行解构)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 可以输出变量
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
// 可以输出函数
export function multiply(x, y) {
return x * y;
};
// 可以对输出的内容重命名
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};模块不能在块级作用域中被导出,否则会违背ES6模块设计(静态优化)
模块输入需要使用对象解构语法或者星号(星号不能导出默认方法),可以用as为模块重命名
export default
可以避免使用库的时候不知道导出的对象叫什么名字,这样import
时可以指定任意名字可以从其他模块
export
它们的接口:export { foo as myFoo } from 'my_module';
,对于功能复杂的模块,可以专门设置一个文件负责导出所有子模块import
不能按需加载,但是import()
可以,其本质是一个Promise对象Module
的加载<script>
可以带一个async
或者defer
属性,前者在JS加载完之后立刻执行(不管是否渲染完毕)阻塞页面渲染,后者等待页面渲染完毕再执行- 浏览器加载ES6模块需要在
<script>
里带一个type="module"
属性,此时加载默认为defer
模式,不会阻塞浏览器
ArrayBuffer
- ArrayBuffer最初设计目的是为了提升性能,允许开发者以数组下标的方式直接访问内存
- ArrayBuffer包含三部分:
- ArrayBuffer对象,提供了一批通用接口用于操作内存
- TypedArray视图,包含常用数据结构
- DataView视图,用于自定义各种复合类型,类似于结构体
- ArrayBuffer对象构造函数接受整数,代表字节数:
const buf = new ArrayBuffer(32);
构造32字节内存区域,每个字节的值都为0 - 使用ArrayBuffer需要先分配内存,再将内存赋予指定视图,然后才能操作视图