ES介绍
ES就是ECMAScript简称,JS就是ES的一个实现,这里ECMA(欧洲计算机制造商学会)每年发布一个ES新版本,2020年到了ES11,前置内容: JS,Ajax,Promise
为什么从ES6开始: 变动多,有里程碑意义,新增语法特性
ES兼容性:查询网站,即使不兼容我们也可以通过编译器编译成ES5
ES6新特性
let关键字
- let不可以重复声明,但是var可以
var varIns = "A";
var varIns = "B"; // 合法
let letIns = "A";
let letIns = "B"; // 不合法
-
let具有块级作用域,var没有块作用域,JS的作用域有
- 全局作用域(例如a=1,那么a就是window的成员)
- 函数作用域(例如在函数中var a=1, 那么出了函数a就没了)
- eval作用域
- 块作用域(例如{a=1},if(){},while(){}…,那么在括号外面a就不可用)
-
let不存在变量提升,例如
console.log(a); // 输出undefined
var a=1;
console.log(a); // 输出1
console.log(b); // 报错
let b=1;
console.log(b);
-
**let不影响作用域链 **
- 虽然是块级作用域,但是不影响作用域链 。如果函数内部没有变量会自动往外找
应用场景:以后声明变量使用 let 就对了
实例:
// 我有5个button
var btns = document.querySelectorAll("button");
for(var i = 0;i < btns.length;i++){
btns[i].onclick = function(){
console.log(i); //5 5 5 5 5
}
}
解决:
// 使用this
var btns = document.querySelectorAll("button");
for(let i = 0;i < btns.length;i++){
btns[i].onclick = function(){
console.log(this.i); // 0 1 2 3 4
}
}
//增加参数
var btns = document.querySelectorAll("button");
for(var i = 0;i < btns.length;i++){
btns[i].idx = i;
btns[i].onclick = function(){
console.log(this.idx); // 0 1 2 3 4
}
}
// 修改为let
var btns = document.querySelectorAll("button");
for(let i = 0;i < btns.length;i++){
btns[i].onclick = function(){
console.log(i); // 0 1 2 3 4
}
}
const关键字
- 必须赋初值
- 建议大写
- 不允许重复声明
- 常量值不能修改
- 有块级作用域
- 对于数组/对象的元素修改不算修改,不会报错,因为const对象指向的地址没有变
应用场景:声明对象类型使用 const,非对象类型声明选择 let
解构赋值
可以按照一定模式从数组/对象提取值,对变量进行赋值
-
对数组进行解构
[...]=Array,就是找对应元素分别对应数组不是很常用
let [a,b,c,d]=[111,222,333,"789"]; console.log(a); //111 console.log(b); //222 console.log(c); //333 console.log(d); //"789" -
可以对对象进行解构
{...}=Object, 要求变量名一一对应解构一般用于提取方法,例如我之前要写
t.c()就可以简写成c()了let t={ a:111, b:222, d:333, c:function(){console.log("OK")} } let {a,b,c,d}=t; console.log(a); //111 console.log(b); //222 console.log(c); //function(){console.log("OK")} c(); //ok console.log(d); //333 d(); // Error -
解构时候如果数目不同/不匹配时,会尽量匹配,例如
let [a,b] = [1,2,3] // a=1 b=2 let [a,b,c,d] = [1,2,3] // a=1 b=2 c=3 d=undefined let t={ x:111, y:222, z:333, w:function(){console.log("OK")} } let {x,y} = t; // x=111 y=222 let {z,w,s} = t; // z=333 w=[function] s=undefined -
解构支持默认值
JS确认某个参数要使用默认值是这个参数===undefinedlet [m1,m2="S"] = ["A"] // m1="A" m2="S" let [n1,n2="S"] = ["A",undefined] // n1="A" n2="S" let [p1,p2="S"] = ["A",null] // p1="A" p2=null -
字符串的解构赋值
将字符串转化为数组let [a,b,c] = "Liu" // a="L" b="i" c="u" -
数值与布尔值解构
let {toString: s}=123; console.log(s===Number.prototype.toString) //true let {toString: r}=true; console.log(r===Boolean.prototype.toString) //true -
函数参数解构
function foo([a,b]){ console.log(a,b); // 1,2 } foo([1,2]) function foo2({a=0,b=0}={}){ // 无参时默认{} a,b也有默认值 //function foo2({a,b}={a=0,b=0}){ // 注意,这两种方法出的结果不同 console.log(a,b); } foo2({a:1,b:2}) // 1,2 foo2({a:1}) // 1,0 foo2({a:undefined,b:2}) // 0,2 foo2() // 0,0- 用处 - 交换值`[a,b]=[b,a]` - 接受函数返回对象/数组 - 函数参数定义 - 函数参数默认值 - 加载模块const { SourceMapConsumer, SourceNode} = require("source-map")- 获取map值let map = new Map(); map.set("a","A") map.set("b","B") // 获取键值 for (let [key,value] of map) { // ... } // 获取键名 for (let [key] of map) { // ... } // 获取键值 for (let [value] of map) { // ... } -
补充for的遍历
- for in 便历出来的是属性
- for of 遍历的是value
- 手动给对象添加属性后, for in 是可以将新添加的属性遍历出来 但是for of 不行
- for in 的属性是使用[]不可以使用 “.” eg: data[‘index’] instead of data.index
注意:频繁使用对象方法、数组元素,就可以使用解构赋值形式
模板字符串
模板字符串使用````声明
-
声明
let str = `我也是一个字符串哦!`; console.log(str, typeof str); //我也是一个字符串哦! -
可以出现换行符
let str = `<ul> <li>沈腾</li> <li>玛丽</li> <li>魏翔</li> <li>艾伦</li> </ul>`; //es5需要用‘’和+拼接 -
变量拼接,使用${}
let lovest = '魏翔'; let out = `${lovest}是我心目中最搞笑的演员!!`; console.log(out); //es5: let lovest = '魏翔'; let out = lovest +'是我心目中最搞笑的演员!!'; console.log(out);
注意:当遇到字符串与变量拼接的情况使用模板字符串
对象的简化写法
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁
let name = '尚硅谷';
let change = function(){
console.log('我们可以改变你!!');
}
const school = {
name,
change,
//improve= function(){
improve(){
console.log("我们可以提高你的技能");
}
}
console.log(school);
let aaa = "aaa"
let bbb = "bbb"
console.log({aaa,bbb})
console.log({aaa: "aaa", bbb: "bbb"})
console.log({aaa, bbb, foo(){console.log("OK")}})
console.log({aaa: "aaa", bbb: "bbb", foo:function(){console.log("OK")}})
//{aaa: "aaa", bbb: "bbb"}
//{aaa: "aaa", bbb: "bbb"}
//{aaa: "aaa", bbb: "bbb", foo: ƒ}
//{aaa: "aaa", bbb: "bbb", foo: ƒ}
这里要简写的是变量,不能是常量,例如我想构造{"A":"A"}不能写{"A"}
箭头函数
将
function(a,b)=>{//code}简写为(a,b)=>{//code}
- 声明一个函数
let fn = function(){
}
let fn = (a,b) => {
return a + b;
}
- 调用函数
let result = fn(1, 2);
console.log(result);
-
this 是静态的. this 始终指向函数声明时所在作用域下的 this 的值(call,apply修改对 他无效)
function getName(){ console.log(this.name); } let getName2 = () => { console.log(this.name); } //设置 window 对象的 name 属性 window.name = '尚硅谷'; const school = { name: "ATGUIGU" } //直接调用 getName(); //尚硅谷 getName2(); //尚硅谷 //call 方法调用 getName.call(school); //ATGUIGU getName2.call(school); //尚硅谷 -
不能作为构造实例化对象,因为this不能指向对象,会报错XX不是一个构造函数
let Person = (name, age) => { this.name = name; this.age = age; } let me = new Person('xiao',30); console.log(me); //报错Person不是一个构造函数 -
不能使用 arguments 变量
let fn = () => { console.log(arguments); } fn(1,2,3); //报错,arguments没有定义 -
箭头函数的简写
//1) 省略小括号, 当形参有且只有一个的时候 let add = n => { return n + n; } console.log(add(9)); //2) 省略花括号, 当代码体只有一条语句的时候, 此时 return 必须省略 //而且语句的执行结果就是函数的返回值 let pow = n => n * n; console.log(pow(8)); //3) 特例,我想简写返回对象x => { foo: x }会报错,换成x => ({ foo: x })
实例:
箭头函数适合与 this 无关的回调. 如定时器, 数组的方法回调 箭头函数不适合与 this 有关的回调. 如事件回调, 对象的方法
<div id="ad"></div>
<script>
//需求-1 点击 div 2s 后颜色变成『粉色』
//获取元素
let ad = document.getElementById('ad');
//绑定事件
ad.addEventListener("click", function(){
//保存 this 的值
let _this = this; //此this为ad
//定时器
//普通函数
setTimeout(function() {
//修改背景颜色 this
console.log(this); //此时this为window,所以外层需令_this=this
_this.style.background = 'pink';
}, 2000);
//箭头函数
setTimeout(() => {
//修改背景颜色 this
console.log(this); //箭头函数的this为静态的,指向函数声明时作用域下的this
this.style.background = 'pink';
}, 2000);
});
//需求-2 从数组中返回偶数的元素
const arr = [1,6,9,10,100,25];
//普通函数
// const result = arr.filter(function(item){
// if(item % 2 === 0){
// return true;
// }else{
// return false;
// }
// });
//箭头函数
const result = arr.filter(item => item % 2 === 0);
console.log(result);
// 箭头函数适合与 this 无关的回调. 定时器, 数组的方法回调
// 箭头函数不适合与 this 有关的回调. 事件回调, 对象的方法
</script>
参数默认值
ES6 允许给函数参数赋值初始值
-
形参初始值 具有默认值的参数, 一般位置要靠后(潜规则)
function add(a,c=10,b) { return a + b + c; } let result = add(1,2); console.log(result); //NaN,如果c=10写在最后,就是13 -
与解构赋值结合
//原本写法 option写多次 function connect(option){ this.host=option.host; this.username=option.username; this.password=option.password; this.port=option.port; } 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 })
rest参数
ES6 引入 rest 参数,用于获取函数的实参,用来代替 arguments
- ES5 获取实参的方式
function date(){
console.log(arguments); //对象 0:白芷 1:阿娇 2:思慧
}
date('白芷','阿娇','思慧');
-
rest 参数
function date(...args){ console.log(args);// 数组 ['阿娇','柏芝','思慧'] 可以使用数组方法 filter some every map } date('阿娇','柏芝','思慧'); -
rest 参数必须要放到参数最后,否则报错
function fn(a,b,...args){ console.log(a); //1 console.log(b); //2 console.log(args); //[3,4,5,6] } fn(1,2,3,4,5,6);
注意:rest 参数非常适合不定个数参数函数的场景
spread扩展运算符
『...』 扩展运算符能将『数组』转换为逗号分隔的『参数序列』
-
声明一个数组 ...
const tfboys = ['易烊千玺','王源','王俊凯'];// => '易烊千玺','王源','王俊凯' -
声明一个函数
function chunwan(){ console.log(arguments); } chunwan(tfboys); //arguments只有一个数,为数组['易烊千玺','王源','王俊凯'] chunwan(...tfboys);// 相当于chunwan('易烊千玺','王源','王俊凯'),arguments有三个数分别为'易烊千玺','王源','王俊凯' -
和rest参数的区别
rest:放在形参位置
spread:放在函数调用位置
实例:
//1. 数组的合并 情圣 误杀 唐探
const kuaizi = ['王太利','肖央'];
const fenghuang = ['曾毅','玲花'];
const res1= kuaizi.concat(fenghuang); //['王太利','肖央','曾毅','玲花']
const res2= [...kuaizi, ...fenghuang]; //['王太利','肖央','曾毅','玲花']
console.log(res1);
//2. 数组的克隆
const sanzhihua = ['E','G','M'];
const sanyecao = [...sanzhihua];// ['E','G','M'] //如果有引用数据类型则为浅拷贝
console.log(sanyecao);
//3. 将伪数组转为真正的数组
const divs = document.querySelectorAll('div');
const divArr = [...divs];
console.log(divArr);
//把argument伪数组转为数组,其实没必要因为rest参数更好用
function foo(){
let agms = [...arguments]
console.log(agms)
}
foo(1,2,3,4,5)
symbol
基本使用
ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型。
-
Symbol 特点
- Symbol 的值是唯一的,用来解决命名冲突的问题
- Symbol 值不能与其他数据进行运算
- Symbol 定义 的 对象属 性 不能 使 用 for…in 循 环遍 历 ,但 是可 以 使 用Reflect.ownKeys 来获取对象的所有键名
-
Symbol的创建
//创建Symbol let s = Symbol(); // 不用写new 因为是js的默认数据类型 console.log(s, typeof s); //Symbol() 'symbol' //给symbol传入一个注释 let s2 = Symbol('doing'); let s3 = Symbol('doing'); console.log(s2===s3); //false 'doing'只是一个标志,在ES10中会有新方法对注释进行利用 //Symbol.for 创建 let s4 = Symbol.for('doing'); let s5 = Symbol.for('doing'); console.log(s4===s5); //true //不能与其他数据进行运算 报错 let result = s + 100; let result = s > 100; let result = s + s; -
js类型总结
USONB you are so niubility u undefined s string symbol o object n null number b boolean
-
symbol使用
//向对象中添加方法 up down let game = { name:'俄罗斯方块', up: function(){}, down: function(){} }; //直接game.up=function(){} 可能会产生错误,因为我们不知道game中是否已经有up这个属性了 //声明一个对象 let methods = { up: Symbol(), down: Symbol() }; game[methods.up] = function(){ console.log("我可以改变形状"); } game[methods.down] = function(){ console.log("我可以快速下降!!"); } console.log(game); let youxi = { name:"狼人杀", //Symbol(): function(){ //这么表达不行,Symbol()是一个动态表达式,必须加一个[] //}, [Symbol('say')]: function(){ console.log("我可以发言") }, [Symbol('zibao')]: function(){ console.log('我可以自爆'); } } console.log(youxi) -
Symbol作用
Symbol的作用非常的专一,唯一目的——作为对象属性的唯一标识符,防止对象属性冲突发生
举个例子,你看上了公司前来的前台妹纸,想了解关于她的更多信息,于是就询问Hr同事,扫地阿姨,于是得到类似这样信息:
let info1 = {
name: '婷婷',
age: 24,
job: '公司前台',
description: '平时喜欢做做瑜伽,人家有男朋友,你别指望了'
}
let info2 = {
description: '这小姑娘挺好的,挺热情的,嘿嘿嘿……'
}
显然,你需要对这两个数据进行汇总,结果,就会发现,描述都用了同一个对象属性description,于是整合的时候,就容器冲突,覆盖,导致“人家有男朋友”这么重要的信息都没注意到。
但是,如果要是Symbol,则完全就不要担心这个问题了:
let info1 = {
name: '婷婷',
age: 24,
job: '公司前台',
[Symbol('description')]: '平时喜欢做做瑜伽,人家有男朋友,你别指望了'
}
let info2 = {
[Symbol('description')]: '这小姑娘挺好的,挺热情的,嘿嘿嘿……'
}
let target = {};
Object.assign(target, info1, info2);
/*{name: "婷婷", age: 24, job: "公司前台",
Symbol(description): "平时喜欢做做瑜伽,人家有男朋友,你别指望了",
Symbol(description): "这小姑娘挺好的,挺热情的,嘿嘿嘿……"}*/
注: 遇到唯一性的场景时要想到 Symbol
内置值
除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。可以称这些方法为魔术方法,因为它们会在特定的场景下自动执行。
作为其他对象的属性来使用
class Person{
static [Symbol.hasInstance](param){
console.log(param);
console.log("我被用来检测类型了");
return false;
}
}
let o = {};
console.log(o instanceof Person);//{} 我被用来检测类型了 false
const arr = [1,2,3];
const arr2 = [4,5,6];
arr2[Symbol.isConcatSpreadable] = false; //连接时无法展开
console.log(arr.concat(arr2)); // [1,2,3,Array(3)]
其他Symbol相关
-
Symbol与for…in迭代
Symbols在for…in迭代中不可枚举,如果想要达到效果,借Object.getOwnPropertySymbols(obj)
var obj = {};
obj[Symbol("a")] = "a";
obj[Symbol.for("b")] = "b";
obj["c"] = "c";
obj.d = "d";
for (var i in obj) {
console.log(i); // 输出 "c" 和 "d"
}
- Symbol与JSON.stringify()
当使用JSON.strIngify()时以symbol值作为键的属性会被完全忽略,例如:
JSON.stringify({[Symbol("foo")]: "foo"}); // '{}'
-
Symbol包装器对象作为属性的键 围绕原始数据类型创建一个显式包装器对象从ECMAScript 6开始不再被支持,所以new Symbol()会报错,然而,现有的原始包装器对象,如 new Boolean、new String以及new Number因为遗留原因仍可被创建。
此时,如果我们想创建一个Symbol包装器对象 (Symbol wrapper object),你可以使用Object()函数:
var sym = Symbol("foo");
typeof sym; // "symbol"
var symObj = Object(sym);
typeof symObj; // "object"
当一个Symbol包装器对象作为一个属性的键时,这个对象将被强制转换为它包装过的symbol值:
var sym = Symbol("foo");
var obj = {[sym]: 1};
obj[sym]; // 1
obj[Object(sym)]; // 还是1
迭代器
遍历器(Iterator)就是一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。
基本使用
-
ES6 创造了一种新的遍历命令 for...of 循环Iterator 接口主要供 for...of 消费
-
原生具备 iterator 接口的数据(可用 for of 遍历)
a) Array
b) Arguments
c) Set
d) Map
e) String
f) TypedArray
g) NodeList
-
工作原理
a) 创建一个指针对象,指向当前数据结构的起始位置
b) 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员
c) 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员
d) 每调用 next 方法返回一个包含 value 和 done 属性的对象
以Array为例,Iterator保存在
Array.prototype.Symbol(Symbol.iterator),也就是实例的[].__proto__.Symbol(Symbol.iterator),他对应的值是一个对象//声明一个数组 const xiyou = ['唐僧','孙悟空','猪八戒','沙僧']; //使用 for...of 遍历数组 for(let v of xiyou){ console.log(v); //唐僧 孙悟空 猪八戒 沙僧 } for(let v in xiyou){ console.log(v); //0 1 2 3 } let iterator = xiyou[Symbol.iterator](); //调用对象的next方法 console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next()); /* {value: "唐僧", done: false} // 返回值是一个对象,包含value和done表示是否结束迭代 {value: "孙悟空", done: false} {value: "猪八戒", done: false} {value: "沙僧", done: false} {value: undefined, done: true} // 遍历结束后设置done=true */注: 需要自定义遍历数据的时候,要想到迭代器。
自定义迭代器
//声明一个对象
const banji = {
name: "终极一班",
stus: [
'xiaoming',
'xiaoning',
'xiaotian',
'knight'
],
[Symbol.iterator]() {
//索引变量
let index = 0;
//
let _this = this;
return {
next: function () {
if (index < _this.stus.length) {
const result = { value: _this.stus[index], done: false };
//下标自增
index++;
//返回结果
return result;
}else{
return {value: undefined, done: true};
}
}
};
}
}
//遍历这个对象
for (let v of banji) {
console.log(v); //如果不自定义迭代器,此处会报错,for...of只能用于有迭代器的对象
//自定义了迭代器,依次输出'xiaoming','xiaoning','xiaotian','knight'
}
生成器
生成器是一个特殊函数,是ES6异步编程解决方案, 之前我们异步编程使用的是回调函数,但容易形成回调地狱
生成器函数的基本使用
-
gen函数可以使用yield进行分割,
- yield是ES6的新关键字,使生成器函数执行暂停,yield关键字后面的表达式的值返回给生成器的调用者。它可以被认为是一个基于生成器的版本的return关键字。
- yield关键字实际返回一个IteratorResult(迭代器)对象,它有两个属性,value和done,分别代表返回值和是否完成。
- yield无法单独工作,需要配合generator(生成器)的其他函数,如next,懒汉式操作,展现强大的主动控制特性。
//生成器函数与就是在普通函数声明前面加入了*
function * gen(){
// ====函数第1部分====
yield '一只没有耳朵'; //yield 函数代码的分隔符
// ====函数第2部分====
yield '一只没有尾部';
// ====函数第3部分====
yield '真奇怪';
// ====函数第4部分====
}
//将函数的结果赋值给变量,函数不会立刻运行,变量的值是一个迭代器。
//函数运行,当且仅当变量执行了next(),这个时候变量的值还是迭代器,返回值要另行接受
let iterator = gen();
console.log(iterator.next()); //{value: "一只没有耳朵", done: false}
console.log(iterator.next()); //{value: "一只没有尾部", done: false}
console.log(iterator.next()); //{value: "真奇怪", done: false}
console.log(iterator.next()); //{value: "undefined", done: true}
//遍历
for(let v of gen()){
console.log(v); //一只没有耳朵 一只没有尾部 真奇怪 (返回yield后的表达式)
}
生成器函数的参数
function * gen(arg){
console.log(arg);
let one = yield 111;
console.log(one);
let two = yield 222;
console.log(two);
let three = yield 333;
console.log(three);
}
//执行获取迭代器对象
let iterator = gen('AAA');
console.log(iterator.next()); //AA {value: "111", done: false}
//next方法可以传入实参
console.log(iterator.next('BBB')); //BB {value: "222", done: false}
console.log(iterator.next('CCC')); //CC {value: "333", done: false}
console.log(iterator.next('DDD')); //DD {value: undefined, done: true}
实例
//需求1
// 1s 后控制台输出 111 2s后输出 222 3s后输出 333
// 回调地狱
setTimeout(() => {
console.log(111);
setTimeout(() => {
console.log(222);
setTimeout(() => {
console.log(333);
}, 3000);
}, 2000);
}, 1000);
function one(){
setTimeout(()=>{
console.log(111);
iterator.next();
},1000)
}
function two(){
setTimeout(()=>{
console.log(222);
iterator.next();
},2000)
}
function three(){
setTimeout(()=>{
console.log(333);
iterator.next();
},3000)
}
function * gen(){
yield one();
yield two();
yield three();
}
//调用生成器函数
let iterator = gen();
iterator.next();
//需求2
//变成每秒输出一个前一个数+5的数
function one(){
setTimeout(()=>{
let data=1;
iterator.next(data+5);
},1000)
}
function two(){
setTimeout(()=>{
let data=2;
iterator.next(data+5);
},2000)
}
function three(){
setTimeout(()=>{
let data=3;
iterator.next(data+5);
},3000)
}
function * gen(){
let o=yield one();
console.log(o); //一秒后输出6
let t=yield two();
console.log(t);//一秒后输出7
let e=yield three();
console.log(e);//一秒后输出8
}
//调用生成器函数
let iterator = gen();//若一开始要传参要从这里传
iterator.next();
生成器与Promise结合
- 生成器可以与Promise结合大大简化代码
- yield是参考python的语法,在前端中用处不大,在后端中,就显得比较重要了,因为其优越的可控性,可是极大的提升线程的效率
原来:
try{
const ninjas = syncGetJSON('data/ninja.json');
const missions = syncGetJSON(ninjas[0].missionsUrl);
const missionDetails = syncGetJSON(missions[0].detailsUrl);
//Study the mission description
} catch (e) {
//Oh no,we were not able to get the mission details
}
尽管这段代码对于简化错误处理很方便,但UI被阻塞了。
用户不希望看到这个结果。则可以使用生成器和promise结合。从生成器中让渡后会挂起执行而不会发生阻塞。而且仅需调用生成器迭代器的next方法就可以唤醒生成器并继续执行。而promise在未来触发某种条件的情况下让我们得到它事先允诺的值,而且当错误发生后也会执行相应的回调函数。
结合:
- 把异步任务放入一个生成器中,然后执行生成器函数。
- 所以生成器执行的时候,我们会将执行权让渡给生成器,从而不会导致阻塞。
- 过一会,当承若被兑现,可继续通过迭代器的next函数执行生成器。只要有需要就可以重复这个过程
console.log('-------------将promise和生成器结合---------');
//返回异步结果的函数在等待异步结果返回时应当能够暂停,注意function*,使用生成器
async(function* () {
try{
//对每个任务均执行yield
const ninjas = yield getJSON('data/ninjas.json');
const missions = yield getJSON(ninjas[0].missionsUrl);
const missionDescription = yield getJSON(missions[0].detailsUrl);
//Study the mission description
} catch (e) {
//依旧可以使用标准的语言结构,诸如try-catch语句或循环语句
//Oh no,we were not able to get the mission details
}
});
//定义一个辅助函数,用于对我们定义的生成器执行操作
function async(generator) {
//创建 一个迭代器,进而我们可以控制生成器
var iterator = generator();
//定义函数handle,用于对生成器产生的每个值进行处理。
function handle(iteratorResult) {
//当生成器没有更多结果返回时停止执行。
if (iteratorResult.done) {
return;
}
const iteratorValue = iteratorResult.value;
//如果生成的值是一个promise,则对其注册成功和失败的回调。这是异步处理的部分。如果promise成功返回,则恢复生成器的执行并传入promise的返回结果。如果遇到错误,则生成器抛出异常。
if (iteratorValue instanceof Promise) {
iteratorValue.then(res =>{
handle(iterator.next(res));
}).catch(err =>{
iterator.throw(err);
});
}
}
//重启生成器的执行
try{
handle(iterator.next());
} catch (e) {
iterator.throw(e);
}
}
async函数内,我们声明了一个处理函数用于处理从生成器中返回的值——迭代器的一次“迭代”。如果生成器的结果是一个被成功兑现的承若,我们就是用迭代器的next方法把承诺的值返回给生成器并恢复执行。如果出现错误,承若被违背,我们就使用迭代器的throw方法抛出一个异常。直到生成器完成前,一直重复这几个操作。
注意:
这只是一个粗略的草稿,一个最小化的代码应该把生成器和promise结合在一起。不推荐生产环境下出现这些代码。
现在仔细看看这个生成器,在第一次调用迭代器的next方法后,生成器执行第一次getJSON(‘data/ninjas.json’)调用。此次调用创建了一个promise,该promise会包含需要的信息。但是这个值是异步获取的,所以我们完全不知道浏览器会花多少时间来获取它。但我们明白一件事,我们不希望在等待中阻塞应用的执行。所以对于这个原因,在执行的这一刻,生成器让渡了控制权,生成器暂停,并把控制流还给了回调函数的执行。由于让渡的值是一个promise对象getJSON,在这个回调函数中,通过使用promise的then和catch方法,我们注册了一个success和一个error回调函数,从而继续了函数的执行。当浏览器接收到了响应(可能是成功的响应,也可能是失败的响应),promise的两个回调函数之一则被调用了。如果promise被成功解决,则会执行success回调函数,随之而来的是迭代器next方法的调用,用于向生成器请求新的值,从而向生成器请求新值,从而生成器从挂起状态恢复,并把得到的值回传给回调函数。这意味着,程序又重新进入到生成器函数体内,当第一次执行yield表达式后,得到的值变成从服务器端获取的信息。
下一行代码的生成器函数中,我们使用获取到的数据ninjas[0].missionsUrl来发起新的geJSON请求,从而创建一个新的promise对象,最后返回最新的数据。我们依然不知道这个异步任务会执行多久,所以我们再一次让渡了这次执行,并重复这个过程。只要生成器中有异步任务,这个过程就会重新执行一次。
总结:
这个例子有一点不同,但它结合了许多知识点:
- 函数是第一类对象——我们向async函数了一个参数,该参数也是函数。
- 生成器函数——用它的特性来挂起和恢复执行。
- pomise——帮我们处理异步代码。
- 回调函数——在promise对象上注册成功和失败的回调函数。
- 箭头函数——箭头函数的简洁适合用在回调函数上。
- 闭包——在我们控制生成器的过程中,迭代器在async函数内被创建,随之我们在promise的回调函数内通过闭包来获取该迭代器。
getJSON("data/ninjas.json", (err, ninjas) =>{
if (err) {
console.log('Error fetchig ninjas', err);
return;
}
getJSON(ninjas[0].missingsUrl, (err, missions) =>{
if (err) {
console.log("Error locating ninja missions", err);
return;
}
console.log(missions);
})
});
不同于把错误处理和流程中控制混合在一起,我们使用类似以下写法结束了代码的混乱:
async(function* () {
try{
//对每个任务均执行yield
const ninjas = yield getJSON('data/ninjas.json');
const missions = yield getJSON(ninjas[0].missionsUrl);
//Study the mission description
} catch (e) {
//依旧可以使用标准的语言结构,诸如try-catch语句或循环语句
//Oh no,we were not able to get the mission details
}
});
最终结果结合了同步代码和异步代码的优点。有了同步代码,我们能更容易地理解、使用标准控制流以及异常处理机制、try-catch语句能力。而对于异步代码来说,我们有着天生的阻塞:当等待长时间运行的异步任务时,应用的执行不会被阻塞。
set
ES6 提供了新的数据结构 Set(集合)。它类似于数组,但成员的值都是唯一的,集合实现了 iterator 接口,所以可以使用『扩展运算符...』和『for…of…』进行遍历
集合的属性和方法:
- size 返回集合的元素个数
- add 增加一个新元素,返回当前集合
- delete 删除元素,返回 boolean 值
- has 检测集合中是否包含某个元素,返回 boolean 值
- clear 清空集合,返回 undefined
let s = new Set()
console.log(s,typeof s); // Set(0) {} object
let s2 = new Set([1,2,3,4,5,3,1])
console.log(s2,typeof s2); // Set(5) { 1, 2, 3, 4, 5 } object //自动去重
for(let v of s){
console.log(v); //1 2 3 4 5
}
s2.add(666);
console.log(s2); //Set(6) { 1, 2, 3, 4, 5, 666 }
s2.delete(3);
console.log(s2); //Set(5) { 1, 2, 4, 5, 666 }
console.log(s2.has(9)) //false
console.log(s2.has(5)) //true
console.log(s2,s2.size); //Set(5) { 1, 2, 4, 5, 666 } 5
s2.clear();
console.log(s2,s2.size); //Set(0) { } 0
- 数组去重
//1. 数组去重
let arr = [1,2,3,4,5,4,3,2,1];
let result = [...new Set(arr)];
console.log(result); // 1 2 3 4 5
let arr = [1,2,'3',4,'abc',{a:123},{a:123},{a:'123'},1];
let result = [...new Set(arr)];
console.log(result); //没有达到去重效果
- 求交
//2. 交集
let arr = [1,2,3,4,5,4,3,2,1];
let arr2 = [4,5,6,5,6];
let result = [...new Set(arr)].filter(item => {
let s2 = new Set(arr2);// 4 5 6
if(s2.has(item)){
return true;
}else{
return false;
}
});
//简化写法
let result = [...new Set(arr)].filter(item => new Set(arr2).has(item));
console.log(result); //4 5
- 求并
//3. 并集
let arr = [1,2,3,4,5,4,3,2,1];
let arr2 = [4,5,6,5,6];
let union = [...new Set([...arr, ...arr2])];
console.log(union); //1 2 3 4 5 6
- 求差
//4. 差集
let arr = [1,2,3,4,5,4,3,2,1];
let arr2 = [4,5,6,5,6];
let diff = [...new Set(arr)].filter(item => !(new Set(arr2).has(item)));
console.log(diff); //1 2 3
Map
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历。
Map 的属性和方法:
- size 返回 Map 的元素个数
- set 增加一个新元素,返回当前 Map
- get 返回键名对象的键值
- has 检测 Map 中是否包含某个元素,返回 boolean 值
- clear 清空集合,返回 undefined
//声明 Map
let m = new Map();
//添加元素
m.set('name','尚硅谷');
m.set('change', function(){
console.log("我们可以改变你!!");
});
let key = {
school : 'ATGUIGU'
};
m.set(key, ['北京','上海','深圳']);
//size
console.log(m.size);
//删除
m.delete('name');
//获取
console.log(m.get('change'));
console.log(m.get(key));
//清空
m.clear();
//遍历
for(let v of m){
console.log(v);
}
console.log(m);
class 类
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过 class 关键字,可以定义类。基本上,ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
基本知识
- class 声明类
- constructor 定义构造函数初始化
- extends 继承父类
- super 调用父级构造方法
- static 定义静态方法和属性
- 父类方法可以重写
//es5写法
function Phone(brand, price){
this.brand = brand;
this.price = price;
}
//添加方法
Phone.prototype.call = function(){
console.log("我可以打电话!!");
}
//实例化对象
let Huawei = new Phone('华为', 5999);
Huawei.call(); //我可以打电话!!
console.log(Huawei);
//class
class Phone{
//构造方法 名字不能修改
constructor(brand, price){
this.brand = brand;
this.price = price;
}
//添加方法必须使用该语法, 不能使用 ES5 的对象完整形式call:function(){}
//该方法也是添加在原型上
call(){
console.log("我可以打电话!!");
}
}
let onePlus = new Phone("1+", 1999);
console.log(onePlus);
类的静态成员
//es5写法
function Phone(){
}
//以下方法添加属性,生成的是静态成员,属于函数对象,不能被实例对象访问
Phone.name = '手机';
Phone.change = function(){
console.log("我可以改变世界");
}
Phone.prototype.size = '5.5inch';
let nokia = new Phone();
console.log(nokia.name); //undefined
nokia.change(); //报错 change不是函数
console.log(nokia.size); //5.5inch
//类写法
class Phone111{
//静态属性属于类而不属于实例
static name = '手机';
static change(){
console.log("我可以改变世界");
}
}
let nokia111 = new Phone();
console.log(nokia111.name); //undefined
console.log(Phone111.name); //手机
对象继承
//es5
//手机
function Phone(brand, price){
this.brand = brand;
this.price = price;
}
Phone.prototype.call = function(){
console.log("我可以打电话");
}
//智能手机
function SmartPhone(brand, price, color, size){
Phone.call(this, brand, price);
this.color = color;
this.size = size;
}
//设置子级构造函数的原型
SmartPhone.prototype = new Phone;
SmartPhone.prototype.constructor = SmartPhone;
//声明子类的方法
SmartPhone.prototype.photo = function(){
console.log("我可以拍照")
}
SmartPhone.prototype.playGame = function(){
console.log("我可以玩游戏");
}
const chuizi = new SmartPhone('锤子',2499,'黑色','5.5inch');
console.log(chuizi);
//类
class Phone{
//构造方法
constructor(brand, price){
this.brand = brand;
this.price = price;
}
//父类的成员属性
call(){
console.log("我可以打电话!!");
}
}
class SmartPhone extends Phone {
//构造方法
constructor(brand, price, color, size){
super(brand, price);// Phone.call(this, brand, price)
this.color = color;
this.size = size;
}
photo(){
console.log("拍照");
}
playGame(){
console.log("玩游戏");
}
//重写父类方法,可以重写,但是不能直接去调用父类方法
call(){
console.log('我可以进行视频通话');
}
}
const xiaomi = new SmartPhone('小米',799,'黑色','4.7inch');
// console.log(xiaomi);
xiaomi.call();
xiaomi.photo();
xiaomi.playGame();
get和set
可以对属性进行方法绑定,当要获取属性的时候执行对应get方法,当要设置属性值时执行对应的set方法, 这些属性不能在构造函数的时候被构造,看起来像一个虚拟变量
// get 和 set
class Phone{
get price(){
console.log("价格属性被读取了");
return '9999';
}
//必须要传参
set price(newVal){
console.log('价格属性被修改了');
}
}
//实例化对象
let s = new Phone();
console.log(s.price);//9999
s.price = 'free';
数值扩展
-
Number.EPSILON 是 JavaScript 表示的最小精度
EPSILON 属性的值接近于 2.2204460492503130808472633361816E-16
两个数差值小于这个数则认为这两个数相等
function equal(a, b){ if(Math.abs(a-b) < Number.EPSILON){ return true; }else{ return false; } } console.log(0.1 + 0.2 === 0.3); //false console.log(equal(0.1 + 0.2, 0.3)) //true -
二进制和八进制
ES6 提供了二进制和八进制数值的新的写法,分别用前缀 0b 和 0o 表示。
-
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(123)); //false -
Number.parseInt() 与 Number.parseFloat()
ES6 将全局方法 parseInt 和 parseFloat,移植到 Number 对象上面,使用不变。
console.log(Number.parseInt('5211314love'));//5211314 console.log(Number.parseFloat('3.1415926神奇'));//3.1415926 -
Number.isInteger 用来判断一个数值是否为整数
console.log(Number.isInteger(5)); console.log(Number.isInteger(2.5)); -
Math.trunc 用于去除一个数的小数部分,返回整数部分。
console.log(Math.trunc(3.5)); //3 -
Math.sign 判断一个数到底为正数 负数 还是零
console.log(Math.sign(100)); //1 console.log(Math.sign(0)); //0 console.log(Math.sign(-20000)); //-1
对象扩展
ES6 新增了一些 Object 对象的方法
-
Object.is 比较两个值是否严格相等,与『===』行为基本一致(+0 与 NaN)
console.log(Object.is(120, 120));// true console.log(Object.is(NaN, NaN));// true console.log(Object.is(+0,-0))// false console.log(Object.is([1],[1])) // False console.log(Object.is({A:"1"},{A:"1"})) // False console.log(Object.is({A:"1"},{A:1})) // False console.log(NaN === NaN);// false console.log(+0=== -0);// true -
Object.assign 对象的合并,将源对象的所有可枚举属性,复制到目标对象
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', port: 33060, name: 'atguigu.com', pass: 'iloveyou', test: 'test' test2: 'test2' */ -
Object.setPrototypeOf 可以直接设置对象的原型
const school = { name: '尚硅谷' } const cities = { xiaoqu: ['北京','上海','深圳'] } Object.setPrototypeOf(school, cities); console.log(Object.getPrototypeOf(school)); //xiaoqu: (3) ['北京', '上海', '深圳'] console.log(school);//name: "尚硅谷"[[Prototype]]: Objectxiaoqu: (3) ['北京', '上海', '深圳']
es6模块化
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来。
基本知识
-
模块化的好处
模块化的优势有以下几点:
- 防止命名冲突
- 代码复用
- 高维护性
-
模块化规范产品
ES6 之前的模块化规范有:
- CommonJS => NodeJS、Browserify
- AM => requireJS
- CMD => seaJS19
-
ES6 模块化语法
模块功能主要由两个命令构成:export 和 import。
⚫ export 命令用于规定模块的对外接口
⚫ import 命令用于输入其他模块提供的功能
mod.js文件
export let name = "Hi"
export let nowis=()=> Date.now()
index.html
<script type="module"> // 要写type
import * as m1 from "../mod.js" // 导入为m1
console.log(m1.nowis()); // 调用方法
</script>
暴露数据的方法
- 分别暴露
export let name = "Hi"
export let nowis=()=> Date.now()
- 统一暴露
let name = "Hi"
let nowis=()=> Date.now()
export {name,nowis}
- 默认暴露
export default{
name : "Hi",
nowis: function(){return Date.now()}
}
默认暴露需要修改html
<script type="module">
import * as m1 from "../mod.js"
console.log(m1.default.nowis()) // 多个default
</script>
浏览器引用模块的方法
- 通用引用方法
<script type="module">
import * as m1 from "../mod.js"
</script>
- 解构的方式引用
<script type="module">
import {name,nowis} from "../mod.js"
</script>
如果两个模块有同名函数,解构后会出现变量名重复的问题,可以使用as进行变量名的替换,不影响解构
<script type="module">
import {name,nowis} from "../mod1.js" // 有name nowis
import {name as name2,lstis} from "../mod2.js" // 有name lstis
</script>
对于默认暴露
<script type="module">
import {default as m1} from "../mod.js"
</script>
- 针对默认暴露的简便模式
<script type="module">
import m1 from "../mod1.js" // 因为默认暴露只有一个参数,可以这么做
</script>
文件统一引用
可以把所有模块引用都放在一起,然后直接引用这个文件
app.js
import * as m1 from "../mod1.js"
import * as m2 from "../mod2.js"
import * as m3 from "../mod3.js"
index.html
<script type="module" src="./app.js">
将ES6代码转化为ES5代码
在项目中考虑到项目兼容性,需要将项目的JS进行转化,需要的工具有
Babel: 将ES6代码转化为ES5代码,但是是CommonJS规范 browserify: 打包工具,把CommonJS规范的JS转化为浏览器可读的,这里是简易打包,项目可能需要webpack
npm install babel-cli babel-preset-env browserify -D // 软件分别是 命令行 转化工具 打包工具 -D是开发依赖
npx babel ./src -d ./dist/js --preset=babel-preset-env // -d前是原js目录 -d后是输出目录 最后是使用预设 npx是因为局部安装Babel全局安装则不需要
npx browserify dist/js/mod.js -o ./bundle.js // 打包
浏览器最后引用./bundle.js
将npm包引入浏览器,jQuery为例
bash
npm install jquery
/src/js/app.js
import $ from "jquery";
$("body").css("background","pink");
bash
//每次修改都需要重新打包
npx babel ./src/js -d ./dist/js --preset=babel-preset-env
npx browserify ./dist/js/app.js -o ./dist/bundle.js
第3章 ES7新特性
Array.prototype.includes
Includes 方法用来检测数组中是否包含某个元素,返回布尔类型值
indexof方法返回的是数组下标,不存在则返回-1
const mingzhu = ['西游记','红楼梦','三国演义','水浒传'];
//判断
console.log(mingzhu.includes('西游记')); //true
console.log(mingzhu.includes('金瓶梅')); //false
指数操作符
在 ES7 中引入指数运算符「**」,用来实现幂运算,功能与 Math.pow 结果相同
// **
console.log(2 ** 10);// 1024
console.log(Math.pow(2, 10)); //1024
第 4章 ES8新特性
async 和 await
async 和 await 两种语法结合可以让异步代码像同步代码一样
async 函数
- async 函数的返回值为 promise 对象,
- promise 对象的结果由 async 函数执行的返回值决定
- 不能在全局直接声明async函数,实在不行可以写成(async()=>{})()的立即执行函数形式
await 表达式
- await 必须写在 async 函数中
- await 右侧的表达式一般为 promise 对象
- await 返回的是 promise 成功的值
- await 的 promise 失败了, 就会抛出异常, 需要通过 try...catch 捕获处理
对象方法的扩展
Object.keys()
获取对象所有的键名,返回值是数组
let lower = {
"A":"a",
"B":"b",
"C":"c",
"D":"d",
}
Object.keys(lower).forEach(d=>console.log(d)) // A B C D
Object.values
返回一个给定对象的所有可枚举属性值的数组
let lower = {
"A":"a",
"B":"b",
"C":"c",
"D":"d",
}
console.log(Object.values(lower)) // [ 'a', 'b', 'c', 'd' ]
Object.entries
返回一个给定对象自身可遍历属性 [key,value] 的数组
let lower = {
"A":"a",
"B":"b",
"C":"c",
"D":"d",
}
console.log(Object.entries(lower))
// [ [ 'A', 'a' ], [ 'B', 'b' ], [ 'C', 'c' ], [ 'D', 'd' ] ]
let m = new Map(Object.entries(lower))
console.log(m)
// Map(4) { 'A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd' }
Object.getOwnPropertyDescriptors
获取对象属性的描述对象,这个对象的每一个属性都对应描述中的一个对象,包括值,可写,可删除,可枚举,方便我们进行深层次的对象克隆
let lower = {
"name": "a",
"age": 12,
"sex": "F",
"note": "good",
}
console.log(Object.getOwnPropertyDescriptors(lower))
/**
{
name: { value: 'a', writable: true, enumerable: true, configurable: true },
age: { value: 12, writable: true, enumerable: true, configurable: true },
sex: { value: 'F', writable: true, enumerable: true, configurable: true },
note: {
value: 'good',
writable: true,
enumerable: true,
configurable: true
}
}
*/
第5 章ES9新特性
REST参数与Spread扩展
在ES6中只有对数组的REST/Spread, 在ES9中支持对对象进行REST/Spread
REST
function cnnt({host,port,...args}){
console.log(host); //127.0.0.1
console.log(port); //80
console.log(args); // {pwd: 123, type: "A" }
}
cnnt({
host: "127.0.0.1",
port: 80,
pwd: 123,
type: "A"
})
Spread
let Obj1={
"name":"Liu"
}
let Obj2={
"Sex":"M"
}
let Obj3={
"Age":20
}
let res = {...Obj1,...Obj2,...Obj3}
console.log(res)
//对象的合并 { name: 'Liu', Sex: 'M', Age: 20 }
正则扩展
命名捕获分组
我们可以对政策匹配到的分组$1,$2...赋名,方面我们的使用
在之前
let str = '<iframe class="notranslate">Inner</iframe>';
// 想要获取class和标签内容要写两个()用来分组
let reg = /<iframe class="(.*)".*>(.*)</iframe>/;
let res = reg.exec(str);
console.log(res);
/*
[
'<iframe class="notranslate">Inner</iframe>',
'notranslate',
'Inner',
index: 8,
input: '<iframe class="notranslate">Inner</iframe>',
groups: undefined // 这里是undefined
]
*/
console.log(res[1]); //notranslate
console.log(res[2]); //Inner
也就是res[0]是匹配结构,res[1]是第一个分组,res[2]是第二个分组
es9:
将需要赋值的分组括号由(条件)改为(?<变量名>条件),使用捕获分组会存储着groups中
let str = '<iframe class="notranslate">Inner</iframe>';
// 想要获取class和标签内容要写两个()用来分组
let reg = /class="(?<cls>.*)".*>(?<Inn>.*)</iframe/;
let res = reg.exec(str);
console.log(res);
/*
[
'class="notranslate">Inner</iframe', // [0-2]都没有变
'notranslate',
'Inner',
index: 8,
input: '<iframe class="notranslate">Inner</iframe>',
groups: [Object: null prototype] { cls: 'notranslate', Inn: 'Inner' } // groups变了
]
*/
//更好的获取数据,即使正则结构改变了
console.log(res.groups.cls); //notranslate
console.log(res.groups.Inn); //Inner
反向断言
正向断言是匹配某个串要求不仅要满足串的条件,原串后面的内容也要满足指定条件
例如: 对于字符串’aaa111bbb222’我想要最后一个连续的字母,那么应该匹配的是[a-zA-Z]+判断后一个内容是否满足要求(在正则条件后加上(?=后面的内容)来实现判断)
//如何取出bbb?
let str = 'aaa111bbb222';
let reg = /[a-zA-Z]+(?=2)/
let res = reg.exec(str);
console.log(res)
// [ 'bbb', index: 6, input: 'aaa111bbb222', groups: undefined ]
反向断言是要匹配一个串,满足串前面的内容是指定条件.实现方法是**(?<=前面的内容)条件**,
例如我想匹配第一个出现的数字串
let str = 'aaacc111bbb222';
let reg = /(?<=c)[0-9]+/
let res = reg.exec(str);
console.log(res)
// [ '111', index: 5, input: 'aaacc111bbb222', groups: undefined ]
dotAll模式
在正则中.代表任意除\n外的任意内容,在提取有\n的内容的时候就显得不方便,只需要设置/条件/s即可,就是在最后加入s属性
.*用来匹配任意字符串的时候经常出现贪婪匹配,可以设置.*?禁止贪婪(不加?会直接匹配到阿甘正传后面的)
//dot . 元字符 除换行符以外的任意单个字符
let str = `
<ul>
<li>
<a>肖生克的救赎</a>
<p>上映日期: 1994-09-10</p>
</li>
<li>
<a>阿甘正传</a>
<p>上映日期: 1994-07-06</p>
</li>
</ul>`;
//声明正则
//原来的需要\s+匹配/n
const reg = /<li>\s+<a>(.*?)</a>\s+<p>(.*?)</p>/;
//现在只需要.*?一下子匹配多个/n
const reg = /<li>.*?<a>(.*?)</a>.*?<p>(.*?)</p>/gs; //此处加入s
//执行匹配
// const result = reg.exec(str);
let result;
let data = [];
while(result = reg.exec(str)){
data.push({title: result[1], time: result[2]});
}
//输出结果
console.log(data);
/*
[
{title:肖生克的救赎 ,time:上映日期: 1994-09-10},
{title:阿甘正传 ,time:上映日期: 1994-07-06}
]
*/
第 6 章 ES 10 新特性
Object.fromEntries
Object.entries()可以获取对象所有的键值对,返回值是数组,元素是一个数组,包含键和值, 可以用来构造Map(对象—>数组)Object.fromEntries()可以将一个Map/二维数组转化为对象的形式(数组—>对象)
//二维数组
const result = Object.fromEntries([
['name','嘿嘿嘿'],
['xueke', 'Java,大数据,前端,云计算']
]);
console.log(result ); //{name:'嘿嘿嘿' xueke: 'Java,大数据,前端,云计算'}
//Map
const m = new Map();
m.set('name','Doing');
const result = Object.fromEntries(m);
console.log(result ); //{name:'Doing'}
//Object.entries ES8
const arr = Object.entries({
name: "哈哈哈"
})
console.log(arr); //[name","哈哈哈"]
trimStart 和 trimEnd
在ES5中字符串有trim方法用来清除字符串两边的空白, 现在有start/end指定清除哪一边了空白
let str = ' iloveyou ';
console.log(str); // iloveyou
console.log(str.trimStart()); //iloveyou
console.log(str.trimEnd()); // iloveyou
Array.prototype.flat 与 flatMap
flat译为平面,也就是可以将数组内部数组的元素维度降低,例如
//flat 平
//将多维数组转化为低位数组
const arr = [1,2,3,4,[5,6]];
console.log(arr.flat()); //[1,2,3,4,5,6]
const arr = [1,2,3,4,[5,6,[7,8,9]]];
console.log(arr.flat()); //[1,2,3,4,5,6,Array(3)]
//参数为深度 是一个数字
console.log(arr.flat(2)); //[1,2,3,4,5,6,7,8,9]
//flatMap
const arr = [1,2,3,4];
const result = arr.map(item => item * 10);
console.log(result); //[10,20,30,40]
const result = arr.map(item => [item * 10]);
console.log(result); //[[10],[20],[30],[40]]
const result = arr.flatMap(item => item * 10);
console.log(result); //[10,20,30,40]
const result = arr.flatMap(item => [item * 10]);
console.log(result); //[10,20,30,40]
Symbol.prototype.description
可以用Symbol.description方法获取Symbol的注释
//创建 Symbol
let s = Symbol('doing');
console.log(s.description);//doing
第 7 章 ES 11 新特性
类的私有属性
在传统OOP语言中的对象可以是私有的,ES11引入了这个特性, 定义私有属性只要在前面加入#就可以了
class Person{
//公有属性
name;
//私有属性
#age;
#weight;
//构造方法
constructor(name, age, weight){
this.name = name;
this.#age = age;
this.#weight = weight;
}
intro(){
console.log(this.name);
console.log(this.#age);
console.log(this.#weight);
}
}
//实例化
const girl = new Person('晓红', 18, '45kg');
// console.log(girl.name);
// console.log(girl.#age); //报错
// console.log(girl.#weight); //报错
girl.intro(); //'晓红', 18, '45kg'
Promise.allSettled
- Promise.allSettled(), 不论他包含的Promise的结果是什么,状态都为resolved,并保存每个Promise的结果
- Promise.all(),所有Promise均成功其才resolved,成功则保存每个Promise的结果,失败则保存失败的Promise结果
- 他们都用来做批量异步任务, all一般是用来做前一个成功后一个运行,allSettled一般是要全部运行, 之间没有关联, 要求保存结果
//声明两个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]);
// const res = Promise.all([p1, p2]);
console.log(res);
String.prototype.matchAll
- .matchAll获取批量匹配的所有结果。必须加/g否则报错
- ES11之前如果正则表达式有/g标志,需要多次调用.exec()才可以获取批量匹配的所有结果。如果没有匹配的结果,.exec()就会返回null。如果正则表达式没有/g标志,.exec()总是返回第一次匹配的结果(我的电脑不加/g直接网络崩溃了)。
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);
//.matchAll()为所有匹配的匹配对象返回一个迭代器。所以可以使用for of遍历
// for(let v of result){
// console.log(v);
// }
const arr = [...result];
console.log(arr);
可选链操作符
// ?.
function main(config){
//原本写法,需多次判断参数是否传递,否则当参数未传递时会报错
// const dbHost = config && config.db && config.db.host;
//const dbHost = config.db.host; //如果config没传或者db没传会直接报错
const dbHost = config?.db?.host; //如果没传会返回undefined
console.log(dbHost);
}
main({
db: {
host:'192.168.1.100',
username: 'root'
},
cache: {
host: '192.168.1.200',
username:'admin'
}
})
动态 import 导入
不在文件开头直接导入相关文件,而是在需要时直接使用import()函数实现按需导入
import()的调用返回的是一个promise对象,当正确加载就resolve, resolve的值是模块暴露的对象
- 静态引入
// 文件头
import * as md1 from "./demo.js"
- 动态引入
if(something){
import("./demo.js").then((d)=>{
d.xx(); // xx就是暴露的对象
})
}
BigInt
比int类范围大,用于大数运算
- 字面值写法:
123n - Int转BigInt:
BigInt(123) - BigInt不能和int进行运算,必须把int转为BigInt
//大整形
let n = 521n;
console.log(n, typeof(n)); //521n "bigint"
//函数
let n = 123;
console.log(BigInt(n)); //123n
console.log(BigInt(1.2));//报错
//大数值运算
let max = Number.MAX_SAFE_INTEGER;
console.log(max); //9007199254740991
console.log(max + 1);//9007199254740992
console.log(max + 2);//9007199254740992
console.log(max + 3);//9007199254740992
console.log(BigInt(max)) //9007199254740991
console.log(BigInt(max)+1) //报错
console.log(BigInt(max) + BigInt(1)) //9007199254740992
console.log(BigInt(max) + BigInt(2)) //9007199254740993
globalThis 对象
浏览器的全局对象是window, 但是NodeJS等没有window, 在新的NodeJS/浏览器中都可以使用golbalThis指向全局对象, 使得在浏览器/NodeJS中编程得到了一个统一
console.log(golbalThis) //window;
第 8 章ES12新特性
replaceAll
看到replaceAll这个词,相比很容易联想到replace。在JavaScript中,replace方法只能是替换字符串中匹配到的第一个实例字符,而不能进行全局多项匹配替换,唯一的办法是通过正则表达式进行相关规则匹配替换而replaceAll则是返回一个全新的字符串,所有符合匹配规则的字符都将被替换掉,替换规则可以是字符串或者正则表达式。
let string = 'I like 前端,I like Doing'
//使用replace
let replaceStr = string.replace('like','love')
console.log(replaceStr) // 'I love 前端,I like Doing'
//replace使用正则匹配所有
console.log(string.replace(/like/g,'love')) // 'I love 前端,I love Doing'
//使用replaceAll
let replaceAllStr = string.replaceAll('like','love')
console.log(replaceAllStr) // 'I love 前端,I love Doing'
需要注意的是,replaceAll在使用正则表达式的时候,如果非全局匹配(/g),则replaceAll()会抛出一个异常
let string = 'I like 前端,I like Doing'
console.log(string.replaceAll(/like/,'love')) //TypeError
Promise.any
- Promise.any返回第一个resolve的结果状态,如果所有的promise均reject,则抛出异常表示所有请求失败
- Promise.race 返回最先执行完毕的promise结果,并不在乎其成功或者失败
Promise.any([
new Promise((resolve, reject) => setTimeout(reject, 500, '哎呀,我被拒绝了')),
new Promise((resolve, reject) => setTimeout(resolve, 1000, '哎呀,她接受我了')),
new Promise((resolve, reject) => setTimeout(resolve, 2000, '哎呀,她也接受我了')),
])
.then(value => console.log(`输出结果: ${value}`))
.catch (err => console.log(err))
//输出
//输出结果:哎呀,她接受我了
再来看下另一种情况
Promise.any([
Promise.reject('Error 1'),
Promise.reject('Error 2'),
Promise.reject('Error 3')
])
.then(value => console.log(`请求结果: ${value}`))
.catch (err => console.log(err))
//输出
AggregateError: All promises were rejected
WeakRefs
使用WeakRefs的Class类创建对对象的弱引用(对对象的弱引用是指当该对象应该被GC回收时不会阻止GC的回收行为)
当我们通过(const、let、var)创建一个变量时,垃圾收集器GC将永远不会从内存中删除该变量,只要它的引用仍然存在可访问。WeakRef对象包含对对象的弱引用。对对象的弱引用是不会阻止垃圾收集器GC恢复该对象的引用,则GC可以在任何时候删除它。
WeakRefs在很多情况下都很有用,比如使用Map对象来实现具有很多需要大量内存的键值缓存,在这种情况下最方便的就是尽快释放键值对占用的内存。
目前,可以通过WeakMap()或者WeakSet()来使用WeakRefs
举个栗子
我想要跟踪特定的对象调用某一特定方法的次数,超过1000条则做对应提示
let map = new Map()
function doSomething(obj){
...
}
function useObject(obj){
doSomething(obj)
let called = map.get(obj) || 0
called ++
if(called>1000){
console.log('当前调用次数已经超过1000次了,over')
}
map.set(obj, called)
}
如上虽然可以实现我们的功能,但是会发生内存溢出,因为传递给doSomething函数的每个对象都永久保存在map中,并且不会被GC回收,因此我们可以使用WeakMap
let wmap = new WeakMap()
function doSomething(obj){
...
}
function useObject(obj){
doSomething(obj)
let called = wmap.get(obj) || 0
called ++
if(called>1000){
console.log('当前调用次数已经超过1000次了,over')
}
wmap.set(obj, called)
}
因为是弱引用,所以WeakMap、WeakSet的键值对是不可枚举的
WeakSet和WeakMap相似,但是每个对象在WeakSet中的每个对象只可能出现一次,WeakSet中所有对象都是唯一的
let ws = new WeakSet()
let foo = {}
let bar = {}
ws.add(foo)
ws.add(bar)
ws.has(foo) //true
ws.has(bar) //true
ws.delete(foo) //删除foo对象
ws.has(foo) //false 已删除
ws.has(bar) //仍存在
WeakSet与Set相比有以下两个区别
WeakSet只能是对象集合,而不能是任何类型的任意值 WeakSet弱引用,集合中对象引用为弱引用,如果没有其他对WeakSet对象的引用,则会被GC回收 最后,WeakRef实例有一个方法deref,返回引用的原始对象,如果原始对象被回收,则返回undefined
const cache = new Map();
const setValue = (key, obj) => {
cache.set(key, new WeakRef(obj));
};
const getValue = (key) => {
const ref = cache.get(key);
if (ref) {
return ref.deref();
}
};
const fibonacciCached = (number) => {
const cached = getValue(number);
if (cached) return cached;
const sum = calculateFibonacci(number);
setValue(number, sum);
return sum;
};
对于缓存远程数据来说,这可能不是一个好主意,因为远程数据可能会不可预测地从内存中删除。在这种情况下,最好使用LRU之类的缓存。
逻辑运算符和赋值表达式
逻辑运算符和赋值表达式,新特性结合了逻辑运算符(&&,||,??)和赋值表达式而JavaScript已存在的 复合赋值运算符有:
- 操作运算符:+= -= *= /= %= **=
- 位操作运算符:&= ^= |=
- 按位运算符:<<= >>= >>>=
现有的的运算符,其工作方式都可以如此来理解
表达式:a op= b 等同于:a = a op b
逻辑运算符和其他的复合赋值运算符工作方式不同
表达式:a op= b 等同于:a = a op (a = b)
- a ||= b 等价于 a = a || (a = b)
- a &&= b 等价于 a = a && (a = b)
- a ??= b 等价于 a = a ?? (a = b)
为什么不再是跟以前的运算公式a = a op b一样呢,而是采用a = a op (a = b)。因为后者当且仅当a的值为false的时候才计算赋值,只有在必要的时候才执行分配,而前者的表达式总是执行赋值操作
??=可用来补充/初始化缺失的属性
const pages = [
{
title:'主会场',
path:'/'
},
{
path:'/other'
},
...
]
for (const page of pages){
page.title ??= '默认标题'
}
console.table(pages)
//(index) title path
//0 "主会场" "/"
//1 "默认标题" "/other"
小结:
- &&=: 当LHS值存在时,将RHS变量赋值给LHS
- ||=: 当LHS值不存在时,将RHS变量赋值给LHS
- ??=: 当LHS值为null或者undefined时,将RHS变量赋值给LHS
数字分隔符
数字分隔符,可以在数字之间创建可视化分隔符,通过_下划线来分割数字,使数字更具可读性
const money = 1_000_000_000
//等价于
const money = 1000000000
const totalFee = 1000.12_34
//等价于
const totalFee = 1000.1234
该新特性同样支持在八进制数中使用
const number = 0o123_456
//等价于
const number = 0o123456