视频教程:https://www.bilibili.com/video/BV1uK411H7on
目录
ES6
ES6基本概念
ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的一项脚本语言的标准化规范,而JavaScript就是这一标准的一种实现。
ES6实际上是一个泛指,指ES2015及后续的版本。
let
ES6中新增的用于声明变量的关键字。
let具有三个特性:
① let声明的变量只在所处于的块级有效
if (true) {
let a = 10;
var b = 20;
}
console.log(a) // undefined
console.log(b) // 20
注意:使用let关键字声明的变量才具有块级作用域,使用var声明的变量不具备块级作用域特性。
② let声明的变量不存在变量提升
console.log(a) // undefined
var a = 10;
----
console.log(b) // ERROR: Uncaught ReferenceError: b is not defined
let b = 10;
③ 暂时性死区
var tmp = 1;
if (true) {
tmp = 2;
let tmp;
}
// 运行后报错:Cannot access 'tmp' before initialization
例题一
let arr = [];
for (let i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
}
}
arr[0]();
arr[1]();
此时,运行结果是:0,1
此题的关键点在于每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的,函数执行时输出的是自己上一级(循环产生的块级作用域)作用域下的i值.
如果把代码中的两处let换成var,输出结果是:2, 2
const
ES6中新增的用于声明变量的关键字,其用来声明常量,常量就是值(内存地址)不能变化的量。
const具有三个特性:
① const声明的变量只在所处于的块级有效(具有块级作用域)
if (true) {
const a = 10;
}
console.log(a) // undefined
② const声明的变量不存在变量提升
console.log(a) // undefined
const a = 10;
③ 声明变量时必须赋值
const PI; // 报错:Missing initializer in const declaratio
④ 常量赋值后,值不能修改,但是可以修改复杂数据类型的内部元素
const a = 3;
a = 100; // 报错:Assignment to constant variable.(赋值给常量变量)
const arr = [1, 2];
arr[0] = 'a';
arr[1] = 'b';
console.log(arr); // ['a', 'b']
arr = ['a', 'b'] // 报错:Assignment to constant variable.
总结:let、const、var
var | let | const |
---|---|---|
函数级作用域 | 块级作用域 | 块级作用域 |
变量提升 | 不存在变量提升 | 不存在变量提升 |
值可以更改 | 值可以更改 | 值不可更改 |
解构赋值
按照一定的模式,从数组或对象中提取值,将提取出来的值赋给另外的变量
数组解构
let [a, b, c] = [1, 2, 3];
a // 1
b // 2
c // 3
如果解构不成功,变量的值为undefined:
let [foo, bar] = [1];
foo // 1
bar // undefined
------
let [xq] = [1, 2];
xq // 1
对象解构
let person = { name: 'zizi', age: 20 };
let { name, age } = person;
name // 'zizi'
age // 20
注意:解构赋值的新变量名需要和对象的属性名一致,如不一致,需要更改赋值语法。
let person = { name: 'zizi', age: 20 };
let { name: myName, age: myAge } = person;
console.log(myName) // 'zizi'
console.log(myAge) // 20
箭头函数
箭头函数是ES6中新增的定义函数的方法。
基本语法:() => {}
分情况讨论:
① 当函数体中只有一句代码,且要将这句代码的结果作为返回值,此时可以省略大括号
const sum = (num1, num2) => num1 + num2;
var a = sum(10, 20) // a = 30
② 如果函数的形参只有一个,可以省略小括号
const fn = v => { console.log(v) }
③ 结合①和②,可以省略小括号和大括号
const fn = v => v;
注意:
- 不能用箭头函数来作为定义对象类的构造函数
- 不能在箭头函数内部使用
arguments
来查看传入的形参
箭头函数的this
箭头函数不绑定this关键字,箭头函数中的this,指向的是函数定义位置的上下文this。
const obj = { name: '张三'}
function fn () {
console.log(this);
// 返回匿名函数
// return () => {
// console.log(this)
// }
// 返回箭头函数
return () => {
console.log(this)
}
}
const resFn = fn.call(obj);
resFn();
上述代码,当fn函数返回的是匿名函数时,首先fn内的this被call方法改为指向obj,而resFn变量接收到了一个匿名函数,再通过resFn()
调用时,因为是window调用的resFn,因此匿名函数内的this指向window
。
当fn函数返回的是箭头函数时,因为箭头函数中的this是其定义位置的this,所以箭头函数this指向obj。
注意:
当我们使用ES5语法进行类的定义时(构造函数),如果使用箭头函数来定义方法,有以下两种情况,其this指向不同:
- 在构造函数外部使用**原型对象(共享方法)**定义方法时,如使用箭头函数来定义,则方法内部的this永远指向顶层对象(window),除非使用call等方法来强行转换指向。
- 在构造函数内部使用**原型对象(共享方法)**定义方法时,如使用箭头函数来定义,则方法内部的this永远指向被定义时所在的对象,而不是使用时所在的对象。
*****例:
function Test(val) {
this.val = val;
console.log(this) // this指向 被创建时的待创建对象(即那个执行完后就被创建了的对象)
Test.prototype.t1 = () => {
console.log('t1:', this)
}
}
Test.prototype.t2 = () => {
console.log('t2:', this)
}
// 实例化对象
var a1 = new Test('aaa');
var b1 = new Test('bbb');
a1.t1() // this指向Test{val: 'bb'}
a1.t2() // this指向Window{}
b1.t1() // this指向Test{val: 'bb'}
b1.t2() // this指向Window{}
通过注释可以了解到上文提到了两种定义方式的情况,此外,关于箭头函数this指向问题,有一个更深入的探讨(即代码中a1.t1()
方法却指向了b1的问题):
首先我们要知道,t1()
方法内部的this应该等同于其定义时上下文的this,而在a1
和b1
这些对象被创建时,其上下文的this是指向当前被定义的对象的(即分别指向a1
和b1
,打印结果就是Test { val: 'aaa' }
),此时我们明确了t1
的this指向。
又因为t1()、t2()
方法是共享方法,其内部this指向是不会因为被调用对象的改变而改变的,t1()
方法是在构造函数内部定义的,因此每次new Test()
时,都会对t1
方法进行一次定义,所以t1()
方法内部的this指向就决定于被定义时的对象。
在例子中,首先创建了a1对象,此时t1
的this指向a1
对象,再创建了b1对象,此时t1
的this指向了b1
对象,因此在a1.t1()
时,打印的this指向了b1
。
函数剩余参数 rest
相关:见上文apply方法
剩余参数 允许我们将一个不定数量的参数表示为一个数组
语法:function xx (xx, xx, ...args) {}
此处的args可以为其他命名。
function sum (first, ..args) {
console.log(first); // 10
console.log(args); // [20, 30]
}
sum(10, 20, 30)
剩余参数与解构赋值
两者可以配合使用:
此处将剩余参数作为解构赋值数组的最后一个参数,用于接受不定数量的数组的元素。
let students = ['wangwu', 'zhangsan', 'lisi'];
let [s1, ...s2] = students;
console.log(s1); // 'wangwu'
console.log(s2); // ['zhangsan', 'lisi']
函数默认参数
ES6中新增了可以为函数的形参设置默认值的方法。
//ES6 允许给函数参数赋值初始值
//1. 形参初始值 具有默认值的参数, 一般位置要靠后(潜规则)
function add(a, b, c=10) {
return a + b + c;
}
let result = add(1,2);
console.log(result); // result = 13
默认参数与解构赋值
//2. 与解构赋值结合
// 在想函数传递参数时,直接解析数组/对象并分发给各形参,亦可设置形参默认值
function connect({host="127.0.0.1", username,password, port}){
console.log(host)
console.log(username)
console.log(password)
console.log(port)
}
connect({
host: 'atguigu.com',
username: 'root',
password: 'root',
port: 3306
})
内置对象扩展
Number 数值扩展
Number.EPSILON
Number.EPSILON
是ES6新增的数值属性,其是JavaScript能够表示的最小精度。
EPSILON值接近于2.2204460492503130808472633361816E-16。
它可以用于浮点数计算和相等判断中:
错误判断:
var a = 0.1 + 0.2 // 0.30000000000000004
a === 0.3 // false
正确判断:
Math.abs(a - 0.3) < Number.EPSILON // true
进制
ES6提供了二进制和八进制数的新写法,分别用前缀0b
and 0o
表示。
let b = 0b1010; // 10
let o = 0o777; // 511
let d = 100; // 100
let x = 0xff; // 255
Number.isFinite() & isNaN()
这两个方法在ES6之前都作为一个window下的全局方法来提供,在ES6中,将这两个方法也添加到了Number数值对象中,这样更符合使用逻辑。
Number.isFinite()
用来检查一个数值是否有有限的;
console.log(Number.isFinite(100)); // true
console.log(Number.isFinite(100/0)); // false
console.log(Number.isFinite(Infinity)); // false
Number.isNaN()
用来检查一个数值是否是NaN;
console.log(Number.isNan(100)); // false
console.log(Number.isNaN(NaN)); // true
Number.parseInt() & parseFloat()
这两个方法在ES6之前都作为一个window下的全局方法来提供,在ES6中,将这两个方法也添加到了Number数值对象中,这样更符合使用逻辑。
Number.parseInt(str)
用来解析字符串为整数;Number.parseFloat()
用来解析字符串为浮点数;
console.log(Number.parseInt('5211314love')); // 5211314
console.log(Number.parseFloat('3.1415926神奇')); // 3.1415926
Number.isInteger
该方法用于判断一个数是否是整数。
console.log(Number.isInteger(5)); // true
console.log(Number.isInteger(2.5)); // false
Math.trunc
该方法用于去除一个数的小数部分,返回整数部分。
console.log(Math.trunc(3.5)); // 3
Math.sign
该方法用于判断一个数到底为正数 负数 还是零,如果是正数返回1,负数返回-1,零则返回0。
console.log(Math.sign(100)); // 1
console.log(Math.sign(0)); // 0
console.log(Math.sign(-20000)); // -1
ES7 指数操作符 **
在ES7中引入了指数操作符**
,用来实现幂运算,其功能与Math.pow(number, x)
一致。
console.log(Math.pow(2, 10)) // 1024
console.log(2 ** 10) // 1024
Array的扩展方法
扩展运算符(展开语法)
扩展运算符...xx
有多个使用方法/作用。
① 扩展运算符可以将数组或者对象转为用逗号分隔的参数序列。
let ary = [1, 2, 3];
...ary // 1, 2, 3
console.log(...ary); // 1 2 3
// 上下语句等价
console.log(1, 2, 3) // 1 2 3
这里的log打印结果是1 2 3 ,没有逗号分隔,因为...arg
将数组arr的方括号移除,然后运行console.log(...arg)
,此时1,2,3是作为三个参数传入的console.log。
② 扩展运算符可以应用于合并数组
// 方法一
let ary1 = [1, 2, 3];
let ary2 = [3, 4, 5];
let ary3 = [...ary1, ...ary2]; // ary3 = [1,2,3,3,4,5]
// 方法二
ary1.push(...ary2); // ary1 = [1,2,3,3,4,5],该语句返回值是添加完后数组的元素个数
③ 将类数组或可遍历对象转换为真正的数组
let oDivs = document.getElementsByTagName('div');
oDivs = [...oDivs];
Array.from()方法
这个方法可以将类数组或可遍历对象转换为真正的数组,和上文的扩展运算符功能之一类似。
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
方法还可以接受第二个参数,作用类似于map方法,对每个元素进行处理,将处理后的值放入返回的数组。
let arrayLike = {
"0": 1,
"1": 2,
"length": 2
}
let newAry = Array.from(aryLike, item => item *2)
// newAry = [2, 4]
上文也可以改写为以下,实现相同功能(原始arrayLike需为数组):
let arrayLike = [1,2];
let newAry = arrayLike.map(x => x * 2)
// newAry = [2, 4]
find()方法
用于找出第一个符合条件的数组成员,如果没有找到则返回undefined。
let ary = [{
id: 1,
name: '张三'
}, {
id: 2,
name: '李四'
}];
let target = ary.find((item, index) => item.id == 2);
// target = {id: 2, name: '李四'}
findIndex()方法
用于找出第一个符合条件的数组成员的位置索引值,如果没有找到则返回 -1
。
let ary = [1, 5, 10, 15];
let index = ary.findIndex((value, index) => value > 9);
console.log(index); // 2
ES7 includes()方法
用于判断数组是否包含给定的值,返回布尔值。
该方法与es5之前就有的arr.indexOf(xxx)
类似,只不过indexOf仅返回索引(无则-1),不返回布尔值。
[1,2,3].includes(2) // true
[1,2,3].includes(4) // false
ES10:Array.prototype.flat & flatMap
flat方法将多维数组转换为低维数组,其接受一个数字参数,用于设定降低的维数,默认为1。
const arr1 = [1,2,3,4,[5,6]];
const arr2 = [1,2,3,4,[5,6,[7,8,9]]];
console.log(arr1.flat()) // [1, 2, 3, 4, 5, 6]
console.log(arr2.flat(2)) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
flatMap方法即map方法与flat方法的结合,可在对数组元素进行遍历+操作时,将其返回的元素(数组)降维。
const arr = [1,2,3,4];
const result1 = arr.map(item => [item * 10]);
const result2 = arr.flatMap(item => [item * 10]);
console.log(result1); // [[10], [20], [30], [40]]
console.log(result2); // [10, 20, 30, 40]
String的扩展方法
模板字符串
模板字符串用于格式化字符串输出。
用反引号括起来的就是模板字符串
// 基本使用方法
let name = 'qq';
let x = `hello, my name is ${name}`;
// x = 'hello, my name is qq'
// 模板字符串中还可以换行
let result = {
name: 'zhangsan',
age: 20, sex: '男'
}
let html = ` <div>
<span>${result.name}</span>
<span>${result.age}</span>
<span>${result.sex}</span>
</div> `;
// 在模板字符串中还可以调用函数
const fn = () => 'this is fn function';
let x = `调用函数 ${fn()}`;
console.log(x); // '调用函数 this is fn function'
startsWith() & endsWith()
str.startsWith(subStr)
:判断str字符串的头部是否是subStr子串,返回布尔值
str.endsWith(subStr)
:判断str字符串的尾部是否是subStr子串,返回布尔值
let str = 'Hello world!';
str.startsWith('Hello') // true
str.endsWith('!') // true
repeat() 方法
该方法可以将原字符串重复n次,并返回这个新字符串。
'x'.repeat(3) // 'xxx'
'hello'.repeat(2) // 'hellohello'
ES10: trimStart & trimEnd
这两个方法与ES5的trim
类似,前者去除字符串前面的空白字符,后者去除字符串后面的空白字符。
let str = ' zzz ';
str.trimStart() // 'zzz ';
str.trimEnd() // ' zzz';
ES11:String.prototype.matchAll()
该方法用来得到正则的批量匹配的结果。
let str = `
<ul>
<li>
<a>肖生克的救赎</a>
<p>上映日期: 1994-09-10</p>
</li>
<li>
<a>阿甘正传</a>
<p>上映日期: 1994-07-06</p>
</li>
</ul>
`;
// 正则
const reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/sg;
const result = str.matchAll(reg);
// 返回的是可迭代对象,可用扩展运算符展开
console.log(...result);
// ["<li>↵ <a>肖生克的救赎</a>↵ <p>上映日期: 1994-09-10</p>", "肖生克的救赎", "上映日期: 1994-09-10", index: 7, input: "↵<ul>↵ <li>↵ <a>肖生克的救赎</a>↵ <p>上映日期: 1994-09-10<…>阿甘正传</a>↵ <p>上映日期: 1994-07-06</p>↵ </li>↵</ul>↵", groups: undefined]
// ["<li>↵ <a>阿甘正传</a>↵ <p>上映日期: 1994-07-06</p>", "阿甘正传", "上映日期: 1994-07-06", index: 62, input: "↵<ul>↵ <li>↵ <a>肖生克的救赎</a>↵ <p>上映日期: 1994-09-10<…>阿甘正传</a>↵ <p>上映日期: 1994-07-06</p>↵ </li>↵</ul>↵", groups: undefined]
Object对象 扩展
Object.is
该方法用于比较两个值是否严格相等,于===
基本一致。
console.log(Object.is(120, 120));// true
console.log(Object.is(NaN, NaN));// true
console.log(NaN === NaN);// false
Object.assign
Object.assign(obj1, obj2)
该方法用于将两个对象合并,可将obj2对象的所有可枚举属性复制给obj1对象。
const config1 = {
host: 'localhost',
port: 3306,
name: 'root',
pass: 'root',
test: 'test'
};
const config2 = {
host: 'http://atguigu.com',
port: 33060,
name: 'atguigu.com',
pass: 'iloveyou',
test2: 'test2'
}
console.log(Object.assign(config1, config2));
// 打印结果:
host: "http://atguigu.com"
name: "atguigu.com"
pass: "iloveyou"
port: 33060
test: "test"
test2: "test2"
__proto__: Object
对象定义的简化写法
ES6允许在大括号中直接写入变量和函数作为对象的属性和方法:
let name = '尚硅谷';
let change = function(){
console.log('我们可以改变你!!');
}
const school = {
name,
change,
improve(){
console.log("我们可以提高你的技能");
}
}
console.log(school);
setPrototypeOf、 getPrototypeOf
这两个方法可以用于获取和设置对象的原型对象,即对象实例.__proto__ & 对象.prototype
,但是不建议使用这个方法,直接对象.prototype = xxx
修改即可。
const school = {
name: '尚硅谷'
}
const cities = {
xiaoqu: ['北京','上海','深圳']
}
Object.setPrototypeOf(school, cities);
console.log(Object.getPrototypeOf(school));
ES8:Object.values & entries & getOwnPropertyDescriptors
- Object.values()方法:返回一个给定对象的所有可枚举属性值的数组;
- Object.entries()方法:返回一个给定对象自身可遍历属性 [key,value] 的数组,随后可利用返回结果快速创建Map对象;
- Object.getOwnPropertyDescriptors()该方法:返回指定对象所有自身属性的描述对象(数据描述符);
//声明对象
const school = {
name:"尚硅谷",
cities:['北京','上海','深圳'],
xueke: ['前端','Java','大数据','运维']
};
//获取对象所有的键
console.log(Object.keys(school)); // ["name", "cities", "xueke"]
//获取对象所有的值
console.log(Object.values(school)); // ["尚硅谷", Array(3), Array(4)]
//entries
console.log(Object.entries(school)); // [Array(2), Array(2), Array(2)]
// 0: (2) ["name", "尚硅谷"]
// 1: (2) ["cities", Array(3)]
// 2: (2) ["xueke", Array(4)]
//利用entries返回的结果创建 Map 对象
const m1 = new Map(Object.entries(school)); // 0: {"name" => "尚硅谷"}
// 1: {"cities" => Array(3)}
// 2: {"xueke" => Array(4)}
console.log(m1.get('cities')); // ["北京", "上海", "深圳"]
//对象属性的描述对象
console.log(Object.getOwnPropertyDescriptors(school));
// cities: {value: Array(3), writable: true, enumerable: true, configurable: true}
// name: {value: "尚硅谷", writable: true, enumerable: true, configurable: true}
// xueke: {value: Array(4), writable: true, enumerable: true, configurable: true}
ES10:Object.fromEntries
该方法和ES8提出的Object.entries
方法相反,用于将二维数组或Map对象转换为对象。
const result = Object.fromEntries([
['name','尚硅谷'],
['xueke', 'Java,大数据,前端,云计算']
]);
console.log(result) // {name: "尚硅谷", xueke: "Java,大数据,前端,云计算"}
ES9:对象的 Rest参数 & spread扩展运算符
在ES6中引入了Rest剩余参数和扩展运算符,不过是针对数组和函数的,并不适用于对象Object,因此在ES9中为对象Object也适配的Rest和扩展运算符。
//rest 参数
function connect({host, port, ...user}){
console.log(user); // username + password + type均被接收到
}
connect({
host: '127.0.0.1',
port: 3306,
username: 'root',
password: 'root',
type: 'master'
});
//对象合并
const skillOne = {
q: '天音波'
}
const skillTwo = {
w: '金钟罩'
}
const skillThree = {
e: '天雷破'
}
const skillFour = {
r: '猛龙摆尾'
}
const mangseng = {...skillOne, ...skillTwo, ...skillThree, ...skillFour};
console.log(mangseng) // {q: "天音波", w: "金钟罩", e: "天雷破", r: "猛龙摆尾"}
Set集合 数据结构
ES6提供了一种新的数据解构Set
,它类似于数组,但是成员的值都是唯一的,不可重复。(就是python中的set)
此外,Set也实现了iterator接口,所以我们可以使用扩展运算符
和for...of
进行遍历。
Set本身是一个构造函数,用来生成Set数据解构。
const s = new Set();
const s = new Set([1,2,3,4,5,6,6])
Set的实例方法:
- add(value):添加某个值,返回 Set 结构本身
- delete(value):删除某个值,返回一个布尔值,表示删除是否成功
- has(value):返回一个布尔值,表示该值是否为 Set 的成员
- clear():清除所有成员,没有返回值
const s = new Set();
s.add(1).add(2).add(3); // 向 set 结构中添加值 return Set(2) {1, 2, 3}
s.delete(2) // 删除 set 结构中的2值 return true
s.has(1) // 表示 set 结构中是否有1这个值 返回布尔值 return true
s.clear() // 清除 set 结构中的所有值 return undefined 此时s = Set(0) {}
Set的遍历
Set结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行指定的操作,没有返回值。
该方法的使用与数组forEach一致,通过回调函数执行操作,回调函数可接受三个参数,分别是当前元素值、由于set没有键所以index和value值一致、原Set内容,此外forEach接受第二个参数(可选),用于指定回调函数执行时的this指向。
s.forEach((val, index, set) => {console.log(val, index, setInterval)})
// 打印结果
1 1 Set(3) {1, 2, 3}
2 2 Set(3) {1, 2, 3}
3 3 Set(3) {1, 2, 3}
此外,也可以使用for ... of ..
来遍历集合Set。
var s = new Set([1,2,3,4,5,6,8,9])
for (let val of s){
console.log(val); // 1,2,3,...,9
}
Map 数据结构
ES6提供了Map数据结构,它类似于对象Object,也是键值对的集合,特殊之处在于,其键
的范围不限于字符串,其他各类型的值(包括对象)均可当作Map的键。
此外,Map也实现了iterator接口,所以我们可以使用扩展运算符
和for...of
进行遍历。
Map的属性和方法
m.size
属性,表示该map的元素个数m.set('key', value)
方法,为map添加新元素,并返回新mapm.get(key)
方法,获取指定键的键值m.has(key)
方法,检测map中是否包含某个键,返回布尔值m.clear()
方法,清空Map内的所有元素,返回undefined
Symbol
symbol是一种基本数据类型(Boolean、Number、String、undefined、null、bigint、symbol),它的作用就是可以生成一个全局唯一的值。
基本使用
//创建Symbol
let s = Symbol();
// console.log(s, typeof s);
let s2 = Symbol('尚硅谷');
let s3 = Symbol('尚硅谷');
// s2 === s3 false
//Symbol.for 创建
let s4 = Symbol.for('尚硅谷');
let s5 = Symbol.for('尚硅谷');
// s4 === s5 true
特点
- Symbol的值是唯一的,可以用来解决命名冲突的问题;(在对象中作为属性名,避免属性名冲突)
- 替代代码中多次使用的字符串(例如:abc),多次使用的字符串在代码中不易维护,而这时候定义一个对象的属性(属性名用Symbol格式),值为abc,就可以作为全局变量来使用了
- Symbol值不能和其他数据进行运算
- 用Symbol在对象中定义的对象属性,不能被for…in循环遍历得到,但是可以用Reflect.ownKeys来获取对象的所有键名
内置值
除了可以定义自己使用的Symbol值以外,还有11个内置的symbol值,指向语言内部使用的方法,这些方法可以称为魔术方法,它们会在特定的场景下自动执行。
内置值在使用时,都是作为某个对象类型的属性去使用的。
ES10:Symbol.prototype.description
该属性可以得到Symbol的描述字符串。
let s = Symbol('尚硅谷');
console.log(s.description); // '尚硅谷'
ES9:正则表达式扩展
命名捕获
使用?<name.*>
来捕获指定字符并赋值给变量name。
let str = '<a href="http://www.atguigu.com">尚硅谷</a>';
//分组命名
const reg = /<a href="(?<url>.*)">(?<text>.*)<\/a>/;
const result = reg.exec(str);
console.log(result.groups.url); // http://www.atguigu.com
console.log(result.groups.text); // 尚硅谷
反向断言
ES9支持了反向断言,通过对匹配结果前面的内容进行判断,从而对匹配到的元素进行筛选。
示例:
// 字符串
let str = "JS5201314你知道么555啦啦啦";
// 需求:我们只想匹配到555
// 正向断言
const reg = /\d+(?=啦)/; // 前面是数字后面是啦
const result = reg.exec(str);
console.log(result);
// 反向断言
const reg1 = /(?<=么)\d+/; // 后面是数字前面是么
const result1 = reg.exec(str);
console.log(result1);
dotAll模式
一般而言,在正则表达式中.
符号用于匹配除回车外的任何单一字符,在ES9中,我们可以通过添加s
来设置为dotAll模式,使.
也可以匹配到换行符。
// 正则扩展:dotAll 模式
// dot就是. 元字符,表示除换行符之外的任意单个字符
let str = `
<ul>
<li>
<a>肖申克的救赎</a>
<p>上映日期: 1994-09-10</p>
</li>
<li>
<a>阿甘正传</a>
<p>上映日期: 1994-07-06</p>
</li>
</ul>
`;
// 需求:我们想要将其中的电影名称和对应上映时间提取出来,存到对象
// 之前的写法
// const reg = /<li>\s+<a>(.*?)<\/a>\s+<p>(.*?)<\/p>/;
// dotAll 模式
const reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/gs;
// 匹配结果:[肖申克的救赎、上映日期: 1994-09-10], [阿甘正传,上映日期: 1994-07-06]
ES11:可选链操作符
在读取对象属性值时,使用可选链操作符,可以自动进行判断,如存在该属性则读取,作用是省略对对象是否传入的层层判断结构。
语法:.?
function main(obj){
// const dbHost = obj && obj.db && obj.db.host;
const dbHost = obj?.db?.host;
// 如没有该属性,则返回undefined
console.log(dbHost); // 192.168.1.100
}
main({
db: {
host:'192.168.1.100',
username: 'root'
},
cache: {
host: '192.168.1.200',
username:'admin'
}
})
ES11:BigInt
这是一个新的数据结构,表示更大的整数。
// 大整形,直接在数字后加n即可转为bigint
let n1 = 521n;
console.log(n1, typeof(n1)); // 521n "bigint"
// 函数方式转为bigint
let n2 = 123;
console.log(BigInt(n2)); // 123n
console.log(BigInt(1.2)); // 报错,浮点数不可转换
ES11:globalThis
该属性始终指向全局对象 window
。
console.log(globalThis);
// Window {......}
ES6 面向对象
面向对象的特性:
- 封装性
- 继承性
- 多态性
ES6语法 class与super
ES6提出了一种新的面向对象语法class,其本质上是语法糖,功能基本都可以用ES5语法实现。
创建 类
class Name {
constructor(x, y) {
this.x = x;
this.y = y;
}
fn() {
console.log('这是对象的fn方法')
}
}
var xx = new Name(1, 2);
xx.x // 1
xx.y // 2
xx.fn() // '这是对象的fn方法'
类必须使用 new 来实例化对象
构造函数 constructor
constructor()
方法是类的构造函数(默认方法),用于传递参数并返回实例对象。
当我们通过 new 命令生成对象实例时,会自动调用这个方法。如果在class类定义中,没有写构造函数,class内部会自动给我们创建一个constructor()构造函数。
构造函数定义时不需要加 function
类的共有方法
类中定义方法的时候,不需要在函数前面加function,同时方法之间不能加 逗号 分隔。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
say() {
console.log(this.name + ' 你好')
}
}
// 创建实例
var ldh = new Person('刘德华', 58);
console.log(ldh.name, ldh.age);
ldh.say();
静态成员 static
类的静态成员在ES6中可以通过static关键字来定义。不能在类的实例上调用静态成员,而应该由类本身调用静态成员。
ES5写法:
function Phone() {};
Phone.name = 'iphone';
Phone.open = function () {
console.log('打开手机');
}
// 调用静态成员:
console.log(Phone.name) // 'iphone'
Phone.open() // '打开手机'
ES6写法:
class Phone{
static name = 'iphone';
static open() {
console.log('打开手机');
}
}
// 调用静态成员:
console.log(Phone.name) // 'iphone'
Phone.open() // '打开手机'
类的继承
子类可以通过继承extends
来获得父类的属性和方法。
super
关键字:可以访问和调用父类的函数(构造函数、普通函数方法)。
情况一:使用super调用父类的构造函数
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
class Son extends Father {
constructor(x, y, grade) {
super(x, y); // 调用了父类中的构造函数
this.grade = grade; // 在super之后再使用this进行子类的构造
}
}
var son = new Son(1, 2);
var son1 = new Son(11, 22);
son.sum();
son1.sum();
注意:
- 此时
son.sum()
语句会调用父类的sum()方法,而sum()方法中的this是指向父类构造函数的,因此如果在子类的构造函数中需要使用super(x, y)
来调用父类的构造函数并将值传递给父类构造函数,此时再son.sum()
时,sum()方法的x,y就有值了。 - 子类在构造函数中必须在this之前使用super(必须先调用父类的构造函数,再使用子类构造方法)
情况二:使用super调用父类的普通函数
class Father {
say() {
return '我是爸爸';
}
}
class Son extends Father {
say() {
console.log(super.say() + '的儿子');
// super.say() 就是调用父类中的普通函数 say()
}
}
var son = new Son();
son.say(); // 输出结果:我是爸爸的儿子
继承中的属性或者方法查找原则: 就近原则
- 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
- 继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
总结:注意点
- 在ES6中的类class是没有变量提升的,因此必须要先定义类再实例化对象
- 要想在类中使用自身的共有属性和方法,必须在使用前加
this.
重写
子类定义中想要重写父类的方法,可以直接定义,覆盖父类的方法即可。
this
指向
var that;
var _that;
class Star {
constructor(uname, age) {
// constructor 里面的this 指向的是 创建的实例对象
that = this;
console.log(this);
this.uname = uname;
this.age = age;
this.btn = document.querySelector('button');
this.btn.onclick = this.sing;
}
sing() {
// 这个sing方法里面的this 指向的是 btn 这个按钮,因为这个按钮调用了这个函数
console.log(this);
console.log(that.uname); // that里面存储的是constructor里面的this
}
dance() {
// 这个dance里面的this 指向的是实例对象 ldh 因为ldh 调用了这个函数
_that = this;
console.log(this);
}
}
var ldh = new Star('刘德华');
console.log(that === ldh); // true
ldh.dance();
console.log(_that === ldh); // true
总结:
- 在构造函数内,this指向的就是该实例对象(ldh)
- 在类的方法函数中,this指向这个方法被调用时的调用者
- sing方法因为是被btn按钮调用,this就指向btn按钮元素
- dance方法因为调用者是ldh,this就指向实例对象ldh(和构造函数的this指向一样)
setters与getters
一个 getter
是一个获取某个特定属性的值的方法。一个 setter
是一个设定某个属性的值的方法。你可以为预定义的或用户定义的对象定义 getter 和 setter 以支持新增的属性。定义 getter 和 setter 的语法采用对象字面量语法。
其与ES5中新增的对象方法Object.defineProperty
类似。
下面例子描述了getters 和 setters 是如何为用户定义的对象 o
工作的。
var o = {
a: 7,
get b() {
return this.a + 1;
},
// 注意,为属性添加set方法时,需传值
set c(x) {
this.a = x / 2
}
};
console.log(o.a); // 7
console.log(o.b); // 8
o.c = 50;
console.log(o.a); // 25
ES11:私有属性的定义
在ES11,我们可以用#
在类的内部定义私有属性,私有属性是不能在外部被直接访问到的,只能在类的内部被调用和访问。
class Person{
// 私有属性
#age;
// 构造方法
constructor(name, age){
this.name = name;
this.#age = age;
}
intro(){
console.log(this.name);
console.log(this.#age);
}
}
//实例化
const girl = new Person('晓红', 18);
// console.log(girl.name); // '晓红'
// console.log(girl.#age); // wrong
girl.intro(); // '晓红' 18
ES6:迭代器 & 生成器
遍历:处理集合中的每个项是很常见的操作,这一操作也被称为迭代。
在ES6之前,JavaScript提供了许多用于迭代的方法,从简单的for
循环到map()
和filter()
。而在ES6,迭代器和生成器将迭代的概念直接带入核心语言,并提供了一种机制来自定义for...of
循环的行为。
ES6创造了一种新的二遍历命令for...of
循环,iterator接口(迭代器)主要供其使用,原生具备iterator接口的数据结构(可以用for…of遍历)
- Array
- Arguments
- Set
- Map
- String
- TypedArray
- Nodelist
迭代器
在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能返回一个返回值。 更具体地说,迭代器是通过使用 next() 方法实现 Iterator protocol
的任何一个对象,该方法返回具有两个属性的对象: value
,这是序列中的 next 值;和 done
,如果已经迭代到序列中的最后一个值,则它为 true 。如果 value 和 done 一起存在,则它是迭代器的返回值。
一旦创建,迭代器对象可以通过重复调用next()
显式地迭代。 迭代一个迭代器被称为消耗了这个迭代器,因为它通常只能执行一次。 在产生终止值之后,对next()
的额外调用应该继续返回{done:true}
。
Javascript中最常见的迭代器是Array迭代器,它只是按顺序返回关联数组中的每个值。 虽然很容易想象所有迭代器都可以表示为数组,但事实并非如此。 数组必须完整分配,但迭代器仅在必要时使用,因此可以表示无限大小的序列,例如0和无穷大之间的整数范围。
例子:它允许创建一个简单的范围迭代器,它定义了从开始(包括)到结束(独占)间隔步长的整数序列。 它的最终返回值是它创建的序列的大小,由变量iterationCount跟踪。
function makeRangeIterator(start = 0, end = Infinity, step = 1) {
let nextIndex = start;
let iterationCount = 0;
const rangeIterator = {
next: function() {
let result;
if (nextIndex < end) {
result = { value: nextIndex, done: false }
nextIndex += step;
iterationCount++;
return result;
}
return { value: iterationCount, done: true }
}
};
return rangeIterator;
}
使用这个迭代器的方式:
let it = makeRangeIterator(1, 10, 2);
let result = it.next();
while (!result.done) {
console.log(result.value); // 1 3 5 7 9
result = it.next();
}
console.log("Iterated over sequence of size: ", result.value); // 共迭代:5次
工作原理:
- 创建一个指针对象,指向当前数据结构的起始位置;
- 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员;
- 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员;
- 每调用 next 方法返回一个包含 value 和 done 属性的对象;
生成器
虽然自定义的迭代器是一个有用的工具,但由于需要显式地维护其内部状态,因此需要谨慎地创建。生成器函数提供了一个强大的选择:它允许你定义一个包含自有迭代算法的函数, 同时它可以自动维护自己的状态。 生成器函数使用 function*语法编写。 最初调用时,生成器函数不执行任何代码,而是返回一种称为Generator的迭代器。 通过调用生成器的下一个方法消耗值时,Generator函数将执行,直到遇到yield关键字。
可以根据需要多次调用该函数,并且每次都返回一个新的Generator,但每个Generator只能迭代一次。
我们现在可以调整上面的例子了。 此代码的行为是相同的,但实现更容易编写和读取。
function* makeRangeIterator(start = 0, end = Infinity, step = 1) {
for (let i = start; i < end; i += step) {
yield i;
}
}
var a = makeRangeIterator(1,10,2)
a.next() // {value: 1, done: false}
a.next() // {value: 3, done: false}
a.next() // {value: 5, done: false}
a.next() // {value: 7, done: false}
a.next() // {value: 9, done: false}
a.next() // {value: undefined, done: true}
Promise
意义
什么是promise?
它是ES6引入的异步编程的新解决方案,Promise是一个对象,用来封装异步操作,并可以获取起成功或失败的结果。
- 用于异步计算
- 可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果
- 可以在对象之间传递和操作promise,帮助我们处理队列
异步
为了避免界面冻结,需要引入异步这一概念,先做一般的任务、工作,然后等完成前面的工作后,通过回调或事件,去做之前挂起的任务。
常见的异步操作:
- 事件监听
element.addEventListener('click', function() { ...此处就是异步 })
- 回调函数
$.ajax('http://...', { success (res) { 此处就是异步 } })
同时,在使用异步回调的过程中,也会遇到一些问题:
- 在ES6之前,处理异步任务完全依靠回调函数的形式来处理
- 很容易进入到回调地狱中,剥夺了函数 return 的能力
- 可以解决异步问题,但是代码可读性差,维护困难
- 稍有不慎就会踏入回调地狱,嵌套层次深,不易维护
回调地狱:
使用方式
const p = new Promise(function (resolve, reject) {
setTimeout(function () {
// 情形一:成功
if (success) {
let data = 'success';
resolve(data) // 调用resolve方法即会将Promise对象状态改为成功,执行p.then的第一个回调函数
} else (
// 情形二:失败
let data = 'error';
reject(data) // 调用reject方法会将Promise对象状态改为失败,执行p.them的第二个回调函数
}
}
});
// 调用Promise,执行异步操作并针对异步操作的结果作出反应
p.then(function (value) {
console.log('success', value) // 成功后执行的函数
}, function (reason) {
console.log('error', reason) // 失败后执行的函数
});
- resolve作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
- reject作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
- promise有三个状态:
1、pending[待定] 初始状态
2、fulfilled[实现] 操作成功
3、rejected[被否决] 操作失败
当promise状态发生改变,就会触发then()里的响应函数处理后续步骤;
promise状态一经改变,不会再变。 - Promise对象的状态改变,只有两种可能:
从pending变为fulfilled
从pending变为rejected。
这两种情况只要发生,状态就凝固了,不会再变了。
.then()
:
- 接收两个函数作为参数,分别代表fulfilled(成功)和rejected(失败)
- .then()返回一个新的Promise实例,所以它可以链式调用
- 当前面的Promise状态改变时,.then()根据其最终状态,选择特定的状态响应函数执行
- 状态响应函数可以返回新的promise,或其他值,不返回值则认为它返回了一个null;
- 如果返回新的promise,那么下一级.then()会在新的promise状态改变之后执行
- 如果返回其他任何值,则会立即执行下一级.then()
错误捕获与处理
方式一:
var p = new Promise((resolve) => {
setTimeout(() => {
throw new Error('error!!')
}, 2000)
})
p.then((value) => {
console.log(value);
}).catch(error => {
console.log('error:', error)
}
方式二:
var p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('error!!')
}, 2000)
});
p.then((value) => {
console.log(value)
}, (reason) => {
console.log('error:', reason)
});
第一种:throw new Error('错误信息').catch( () => {错误处理逻辑})
第二种:reject('错误信息').then(() => {}, () => {错误处理逻辑})
推荐使用第一种catch方式,更加清晰好读,并且可以捕获前面所有的错误(可以捕获N个then回调错误)
批量执行:all() 方法
Promise.all([p1, p2, p3])
用于将多个promise实例,包装成一个新的Promise实例,返回的实例就是普通的promise。
它接收一个数组作为参数,数组里可以是Promise对象,也可以是别的值,只有Promise会等待状态改变当所有的子Promise都完成,该Promise完成,返回值是全部值的数组,当有任何一个失败,该Promise就失败,返回值是第一个失败的子Promise结果。
//切菜
function cutUp(){
console.log('开始切菜。');
var p = new Promise(function(resolve, reject){ //做一些异步操作
setTimeout(function(){
console.log('切菜完毕!');
resolve('切好的菜');
}, 1000);
});
return p;
}
//烧水
function boil(){
console.log('开始烧水。');
var p = new Promise(function(resolve, reject){ //做一些异步操作
setTimeout(function(){
console.log('烧水完毕!');
resolve('烧好的水');
}, 1000);
});
return p;
}
Promise.all([cutUp(), boil()])
.then((result) => {
console.log('准备工作完毕');
console.log(result);
})
任一执行:race()方法
该方法类似于Promise.all()
方法,区别在于它只要有任意一个完成就算完成。
let p1 = new Promise(resolve => {
setTimeout(() => {
resolve('I\`m p1 ')
}, 1000)
});
let p2 = new Promise(resolve => {
setTimeout(() => {
resolve('I\`m p2 ')
}, 2000)
});
Promise.race([p1, p2])
.then(value => {
console.log(value)
})
ES11:Promise.allSettled
与Promise.all()
类似,此方法可以执行多个异步函数,且不论执行结果,都返回一个fulfilled
状态的Promise,并在PromiseResult
中展示异步函数的执行状态和结果值。
//声明两个promise对象
const p1 = new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve('商品数据 - 1');
},1000)
});
const p2 = new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve('商品数据 - 2');
// reject('出错啦!');
},1000)
});
//调用 allsettled 方法
const result = Promise.allSettled([p1, p2]);
console.log(result);
// 返回一个Promise
// [[PromiseState]]: "fulfilled"
// [[PromiseResult]]: Array(2)
// 0: {status: "fulfilled", value: "商品数据 - 1"}
// 1: {status: "fulfilled", value: "商品数据 - 2"}
ES7:async
& await
async
函数是使用async
关键字声明的函数。 async
函数是AsyncFunction
构造函数的实例, 并且其中允许使用await
关键字。async
和await
关键字让我们可以用一种更简洁的方式写出基于Promise
的异步行为,而无需刻意地链式调用promise
。
语法:
async function name([params]) {
statements
}
- name:函数名称
- params:要传递给函数的参数
- statements:包含函数主体的表达式,可以使用await机制
返回值:当使用async
函数后,该函数的返回值必然是一个Promise
假设返回值是一个不像Promise的返回值return 1
,那么实际返回值就会被包装成一个fulfilled
状态的Promise,value即return的值(在这里就是1
):[[PromiseState]]: "fulfilled"、[[PromiseResult]]: "1"
;
假设在async函数中抛出错误throw new Error('have error')
,那么实际返回值就是一个rejected
状态的Promise,value为错误信息;
await + async
async/await
的目的为了简化使用基于promise的API时所需的语法。async/await
的行为就好像搭配使用了生成器和promise。它们其实是语法糖。
async函数可能包含0个或者多个await
表达式。await表达式会暂停整个async函数的执行进程并出让其控制权,只有当其等待的基于promise的异步操作被兑现或被拒绝之后才会恢复进程。Promise
的解决值会被当作该await表达式的返回值。使用async / await
关键字就可以在异步代码中使用普通的try / catch
代码块。
- await 必须写在 async函数中
- await 右侧的表达式一般为 Promise 对象
- await 表达式的返回值是Promise 成功的解决值
- await 右侧表达式的Promise如果失败(rejected)了,就会抛出异常,需要用
try...catch
捕获处理
async函数的函数体可以被看作是由0个或者多个await表达式分割开来的。从第一行代码直到(并包括)第一个await表达式(如果有的话)都是同步运行的。这样的话,一个不含await表达式的async函数是会同步运行的。然而,如果函数体内有一个await表达式,async函数就一定会异步执行。
更重要的是,在await表达式之后的代码可以被认为是存在在链式调用的then回调中,多个await表达式都将加入链式调用的then回调中,返回值将作为最后一个then回调的返回值。
案例
在接下来的例子中,我们将使用await执行两次promise,整个foo函数的执行将会被分为三个阶段。
foo
函数的第一行将会同步执行,await将会等待promise的结束。然后暂停通过foo
的进程,并将控制权交还给调用foo
的函数。- 一段时间后,当第一个promise完结的时候,控制权将重新回到
foo
函数内。示例中将会将1
(promise状态为fulfilled)作为结果返回给await表达式的左边即result1
。接下来函数会继续进行,到达第二个await区域,此时foo
函数的进程将再次被暂停。 - 一段时间后,同样当第二个promise完结的时候,
result2
将被赋值为2
,之后函数将会正常同步执行,将默认返回undefined
。
注意:promise链不是一次就构建好的,相反,promise链是分阶段构造的,因此在处理异步函数时必须注意对错误函数的处理。
例如,在下面的代码中,在promise链上配置了.catch
处理程序,将抛出未处理的promise错误。这是因为p2
返回的结果不会被await处理。
async function foo() {
const p1 = new Promise((resolve) => setTimeout(() => resolve('1'), 1000))
const p2 = new Promise((_,reject) => setTimeout(() => reject('2'), 500))
const results = [await p1, await p2] // 不推荐使用这种方式,请使用 Promise.all或者Promise.allSettled
}
foo().catch(() => {}) // 捕捉所有的错误...
模块化
什么是模块化
模块化就是指将一个大的程序文件,拆分成许多个小的文件,然后将小文件组合起来。
模块化的好处:
- 防止命民冲突,独立作用域
- 提高代码复用性和可读性
- 提高项目代码的维护性
ES6之前的模块化规范有:
- CommonJS => NodeJS, Browserify
- AMD => requireJS
- CMD => seaJS
使用方式
模块化功能主要由两个命令构成:export
和import
。
- export 用于规定模块的对外接口(导出模块),即把内容暴露出去
- import 用于输入其他模块提供的功能(导入模块),即把内容导入进来
方式一
对模块进行逐个的导出和导入。
假设有一个用于导出的文件m.js
export let school = "xxx";
export function teach() {
console.log('teach class');
}
以及一个接受导入的文件index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>模块化</title>
</head>
<body>
<script type="module">
// 引入m.js模块内容
import * as m from "./js/m.js";
console.log(m); // Module...
console.log(m.school); // xxx
m.teach(); // 'teach class'
</script>
</body>
</html>
方式二
对模块进行统一的导出(暴露):
m.js
的内容
// 统一暴露(导出)
let school = "xxx";
function teach() {
console.log('teach class');
}
export {school, teach}
此方式在index.html
中的导入、调用方法与方式一 一致。
方式三
对模块采取默认导出方式:
m.js
的内容
export default{
school: 'xxx',
teach: function() {
console.log('teach class');
}
}
此方法在index.html
中导入和调用时,需要加上default,因为算是导入了一个对象default。
import * as m from "./js/m.js";
console.log(m.default.school);
m.default.teach();
最常用方式:解构赋值形式
// index.html 部分代码
<script type="module">
// 1. 基本解构赋值形式,之后可以直接使用
import {school, teach} from "./src/js/m1.js";
// 2. 可能会出现重名冲突,可以设定别名
import {school as guigu, findJob} from "./src/js/m2.js";
// 3. 当导入 export default默认导出模块时,必须使用别名
import {default as m3} from "./src/js/m3.js";
// 此时,这些变量都可以直接使用了
console.log(school, guigu, m3.school)
// 4. 简便形式 针对默认暴露时可以使用
import m3 from "./src/js/m3.js";
console.log(m3);
</script>
ES11:动态import导入
可以动态导入模块,何时使用就何时导入该模块。
// import * as m1 from "./hello.js"; // 传统静态导入
//获取元素
const btn = document.getElementById('btn');
btn.onclick = function(){
import('./hello.js').then(module => {
module.hello();
});
}