JavaScript学习笔记(四) ES6
思维导图
文章目录
- 思维导图
- 1. 简介
- 2. let 与 const
- 2.1 let
- 2.2 const
- 2.3 let、const、var的区别
- 3. 解构赋值
- 3.1 数组的解构赋值
- 3.2 对象的解构赋值
- 4. 箭头函数
- 5. rest 剩余参数
- 5.1 用于数组
- 5.2 用于对象
- 6. 扩展运算符
- 6.1 数组的扩展运算符
- 6.2 对象的扩展运算符
- 7. Array 的扩展方法
- 7.1 from()
- 7.2 find()
- 7.3 findIndex()
- 7.4 includes()
- 8. String 的扩展方法
- 8.1 模板字符串
- 8.2 startsWith()
- 8.3 repeat()
- 9. 数值扩展
- 10. 对象的扩展
- 11. Set集合
- 11.1 基本使用
- 11.2 巧用
- 12. Map
- 13. 类和对象
- 13.1 类简介
- 13.2 对象简介
- 13.3 创建类
- 13.4 构造函数
- 13.5 类添加方法
- 13.6 类的静态成员
- 13.7 类的私有成员
- 14. 类的继承
- 14.1 继承
- 14.2 super 关键字
- 14.3 方法的重写
- 14.4 get 和 set
- 14.5 注意点
- 15. 简化对象写法
- 16. 形参赋初值
- 17. Symbol类型
- 17.1 Symbol 基本使用
- 17.2 Symbol 内置值
- 18. Iterator迭代器
- 19. Generator 生成器函数
- 19.1 Generator简介
- 19.2 next 带参数
- 20. ES6 模块化
- 20.1 模块化规范分类
- 20.2 何谓ES6模块化规范
- 20.3 体验ES6 模块化
- 20.4 ES6模块化的基本语法
- 20.4.1 默认导入或导出
- 20.4.2 按需导入或导出
- 20.4.3 直接导入并执行模块中的代码
- 21. Promise
- 21.1 回调地狱
- 21.2 Promise的基本概念
- 21.3 promise 的基本流程
- 21.4 promise 的基本使用
- 21.4.1 基本用法
- 21.4.2 Promise 构造函数
- 21.4.3 then 方法
- 21.4.4 catch 方法
- 21.4.5 Promise.resolve 方法
- 21.4.6 Promise.reject 方法
- 21.4.7 all 方法
- 21.4.8 race 方法
- 21.5 几个关键问题
- 21.6 Promise 案例练习
- 21.6.1 基于then-fs读取文件内容
- 21.6.2 then-fs的基本使用
- 21.6.3 基于Promise顺序读取文件内容
- 21.6.4 通过.catch捕获错误
- 21.6.5 使用Promise.all()方法
- 21.6.6 使用Promise.race()方法
- 21.7 基于Promise封装读文件的方法
- 21.7.1 getFile方法的基本定义
- 21.7.2 创建具体的异步操作
- 21.7.3 获取.then的两个实参
- 21.7.4 调用resolve和reject回调函数
- 22. async/await
- 22.1 什么是async/await
- 22.2 async 函数
- 22.3 await 表达式
- 22.4 async结合await示例
- 22.5 async/await的使用注意事项
- 23. EventLoop
- 23.1 JS是单线程语言
- 23.2 同步任务和异步任务
- 23.3 同步/异步任务的执行过程
- 23.4 EventLoop的基本概念
- 24. 宏任务和微任务
- 24.1 何谓宏任务和微任务
- 24.2 宏任务和微任务的执行顺序
- 25. 其他
- 25.1 ** 运算符
- 25.2 Object新增方法
1. 简介
- ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的一项脚本语言的标准化规范。
- ES6 实际上是一个泛指,泛指ES2015 及后续的版本,涵盖了 ES2015、ES2016、ES2017等等
- 查看兼容性:http://kangax.github.io/compat-table/es6/
- ECMAScript历史版本查看网址:历史版本查看
2. let 与 const
ES6中新增的用于声明变量的关键字。
2.1 let
- let声明的变量只在所处于的块级有效
if (true) {let b = 20;console.log(b); //20if (true) {let c = 30;}console.log(c);// c is not defined
}
console.log(b);// b is not defined
- 使用let关键字声明的变量才具有块级作用域,使用var声明的变量不具备块级作用域特性。
if (true) {let num = 100;var abc = 200;
}
console.log(abc); //200
console.log(num) //num is not defined
- 防止循环变量变成全局变量
for (var i = 0; i < 2; i++) {}
console.log(i); //2
for (let j = 0; j < 2; j++){}
console.log(j); // j is not defined
- 使用let关键字声明的变量没有变量提升
console.log(b); // undefined
var b = 200;
console.log(a); //Cannot access 'a' before initialization
let a = 100;
- 使用let关键字声明的变量具有暂时性死区特性
var num = 10;
if (true) { //num和这个块绑定了,同时块中num又在申明前使用了,就报错了console.log(num); //Cannot access 'num' before initializationlet num = 20;
}
- 面试题
此题的关键点在于每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的,函数执行时输出的是自己上一级(循环产生的块级作用域)作用域下的i值.
var arr = [];
for (var i = 0; i < 2; i++) {arr[i] = function () {console.log(i);};
}
arr[0](); // 2
arr[1](); // 2
let arr = [];
for (let i = 0; i < 2; i++) {arr[i] = function () {console.log(i);};
}
arr[0](); // 0
arr[1](); // 1
2.2 const
作用:声明常量,常量就是值(内存地址)不能变化的量。
- 具有块级作用域
if (true) {const a = 10;if (true) {const a = 20;console.log(a); // 20}console.log(a);//10
}
console.log(a);//a is not defined
- 声明常量时必须赋值
//const PI = 3.14;
const PI;//Missing initializer in const declaration
- 常量赋值后,值不能修改。
const PI = 3.14; //基本数据类型值不可更改
// PI = 100; //Assignment to constant variable.
const ary = [100, 200]; //复杂数据类型内存地址不可更改,内容可变
ary[0] = 123;
ary = [1, 2]; //Assignment to constant variable.
console.log(ary);
2.3 let、const、var的区别
- 使用 var 声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象。
- 使用 let 声明的变量,其作用域为该语句所在的代码块内,不存在变量提升。
- 使用 const 声明的是常量,在后面出现的代码中不能再修改该常量的值。(基本数据类型值不可更改,复杂数据类型内存地址不可更改,内容可变)
- 应用场景:声明对象类型使用 const,非对象类型声明选择 let
var | let | const |
---|---|---|
函数级作用域 | 块级作用域 | 块级作用域 |
变量提升 | 不存在变量提升 | 不存在变量提升 |
值可更改 | 值可更改 | 值不可更改 |
3. 解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构
3.1 数组的解构赋值
- 可以从数组中提取值,按照对应位置,对变量赋值。
let ary = [1,2,3];
let [a, b, c ] = ary;
console.log(a);// 1
console.log(b);// 2
console.log(c); //3
- 如果解构不成功,变量的值就等于undefined
let [a] = [];
console.log(a); //undefined
let [b,c] = [1];
console.log(b); // 1
console.log(c); // undefined
3.2 对象的解构赋值
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let person = { name: 'zhangsan',age: 20,cando: function(){console.log('唱歌');}
};
let { name, age, cando} = person;
console.log(name); // 'zhangsan'
console.log(age); // 20
cando(); //输出 唱歌
- 对象的解构赋值,可以很方便地将对象的成员起别名
let person = { name: 'zhangsan', age: 20, };
let {name: myName, age: myAge} = person; // myName myAge 属于别名 ,
//从person对象中匹配name,age属性,如果有,则赋值到myName,myAge
console.log(myName); // 'zhangsan'
console.log(myAge); // 20
4. 箭头函数
ES6中新增的定义函数的方式,简化函数定义语法
() => {} //(放参数) ,{放函数体}
const fn = () => {}
const fn1 = () => {console.log(1);
}
// 相当于
//const fn1 = function(){// console.log(1);
//}
- 函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号,且必须省略return
function sum1(n1, n2){return n1 + n2;
}
const sum2 = (n1,n2) => {return n1 + n2;
}
const sum3 = (n1,n2) => n1 + n2;
console.lof(sum3(1,2));
- 如果形参只有一个,可以省略小括号
function fn1 (v) {return v;
} //等价于下面
const fn2 = v => v;
- 不能使用arguments变量
function fn1() {console.log(arguments);
}
fn1(1, 2, 3);// 有输出
let fn = () => {console.log(arguments);
}
fn(1, 2, 3); // 报错 arguments is not defined
- 箭头函数没有自己的this对象,内部的this就是定义时上层作用域中的this。箭头函数内部的this指向是固定的,相比之下,普通函数的this指向是可变的。
function fn () {console.log(this);return () => {console.log(this);};
}
let obj = {name: 'zhangsan'};
fn(); //window
fn()(); // window window
ffn = fn.bind(obj);
ffn(); // obj
ffn()(); // obj obj
- 例题1:this指向
let obj = { // 对象没有作用域age: 20,say: () => {console.log(this); //windowconsole.log(this.age); //undefined}
}
obj.say();
- 例题二 :this指向
window.name = '凉宫'
function getName() { // 普通函数定义console.log(this.name);
}
let getName2 = () => {console.log(this.name);
}
getName(); //凉宫
getName2(); // 凉宫
const school = {name: 'it'
}
getName.call(school); // it
getName2.call(school); // 凉宫;call,bind等无法改变箭头函数this指向
- 例题三: this指向,实现点击div 几秒后变色
let ad = document.querySelector('.ad'); // ad为某个div类名
ad.addEventListener('click', function () {let _this = this; //保存外层的this;setTimeout(function () { //setTimeout 内的this指向window,故要做处理_this.style.background = 'blue';}, 4000);
})
ad.addEventListener('click', function () {setTimeout(() => { // 箭头函数的this 指向在函数申明时所在作用域下的this this.style.background = 'pink';}, 2000);
})
使用场景:
- 箭头函数适合与 this 无关的回调. 定时器, 数组的方法回调
- 箭头函数不适合与 this 有关的回调. 事件回调, 对象的方法(this会指到对象外层的this,而不是这个对象)
5. rest 剩余参数
5.1 用于数组
- 剩余参数:剩余参数语法允许将一个不定数量的参数表示为一个数组。
- 用于获取函数的实参,用来代替 arguments
用法示例1:
function date(){ //arguments示例console.log(arguments);
}
date('凉宫','长门','朝比奈');
//rest 参数示例
function date2(...args){console.log(args);
}
date2('凉宫','长门','朝比奈');
//rest 参数必须要放到参数最后
function fn(a,b,...args){console.log(a); //1console.log(b);// 2console.log(args); //[3,4,5,6]
}
fn(1,2,3,4,5,6);
let ary1 = ['张三' , '李四', '王五'];
let [s1, ...s2] = ary1;
console.log(s1); // 张三
console.log(s2); // ['李四', '王五']
用法示例2:配合forEach遍历
const sum = (...args) => {let total = 0;args.forEach(item => total += item);return total;
};
console.log(sum(10, 20)); //30
console.log(sum(10, 20, 30)); // 60
- 剩余参数和解构配合使用
let students = ['wangwu', 'zhangsan', 'lisi'];
let [s1, ...s2] = students;
console.log(s1); // 'wangwu'
console.log(s2); // ['zhangsan', 'lisi']
5.2 用于对象
Rest 参数与 spread 扩展运算符在 ES6 中已经引入,不过 ES6 中只针对于数组,在 ES9 中为对象提供了像数组一样的 rest 参数和扩展运算符
//rest 参数
function connect({ host, port, ...user }) {console.log(host); // 127.0.0.1console.log(port); // 3306console.log(user); // {username: 'root', password: 'root'}
}
connect({host: "127.0.0.1",port: 3306,username: "root",// 把后面多的参数,直接封装成一个对象password: "root",
});
6. 扩展运算符
扩展运算符(spread)也是三个点(...
)。
6.1 数组的扩展运算符
作用:将一个数组转为用逗号分隔的参数序列,对数组进行解包。
let ary = ["a", "b", "c"];
//...ary // "a", "b", "c"
console.log(...ary); // a b c
console.log("a", "b", "c");// a b c
//========================
const people = ['凉宫', '长门', '朝比奈'];
function say() {console.log(arguments);
}
say(...people);//等价于 say('凉宫','长门','朝比奈');
// 注意和rest剩余参数区分
- 数组的克隆
const a = ['h', 'e', 'y'];
const b = [...a]; // b = ['h','e','y']; 若果数组里面有复杂数据类型,是浅拷贝
- 扩展运算符可以应用于合并数组。
let ary1 = [1, 2, 3];
let ary2 = [4, 5, 6];
// ...ary1 // 1, 2, 3
// ...ary1 // 4, 5, 6
let ary3 = [...ary1, ...ary2];
console.log(ary3); //[1, 2, 3, 4, 5, 6]
- 合并数组的第二种方法
let ary1 = [1, 2, 3];
let ary2 = [4, 5, 6];
ary1.push(...ary2);
console.log(ary1);//[1, 2, 3, 4, 5, 6]
- 利用扩展运算符将伪数组转换为真正的数组
<div>1</div>
<div>4</div>
<div>3</div>
<script>let oDivs = document.getElementsByTagName('div');console.log(oDivs); //oDivs是个对象let ary = [...oDivs]; // [div, div, div]ary.push('a');//将伪数组转为数组的好处是可以用数组的方法console.log(ary); //[div, div, div, 'a']
</script>
6.2 对象的扩展运算符
//对象合并
const msg = {name: "凉宫",age: 15,
};
const features = {hight: "158cm",isCute: true,
};
const person = {...msg,...features,
};
console.log(person); // {name: '凉宫', age: 18, hight: '158cm', cute: true}
7. Array 的扩展方法
7.1 from()
将类数组或可遍历对象转换为真正的数组
let arrayLike = {"0": "张三","1": "李四","2": "王五","length": 3 //要加这个属性
}
let ary = Array.from(arrayLike);
console.log(ary); //Array(3)
方法还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
let arrayLike = {"0": 1,"1": 2,"length": 2
}
let ary = Array.from(arrayLike, item => item * 2)
console.log(ary); //Array(2)
7.2 find()
用于找出第一个符合条件的数组成员,如果没有找到返回undefined
let ary = [{id: 1,name: '张三'
}, {id: 2,name: '李四'
}];
let target = ary.find(item => item.id == 2); //参数为函数, 返回true则找到;item为每次遍历的元素
console.log(target);
7.3 findIndex()
用于找出第一个符合条件的数组成员的位置,如果没有找到返回-1
let ary = [10, 20, 50];
let index = ary.findIndex(item => item > 15);
console.log(index); //1
7.4 includes()
表示某个数组是否包含给定的值,返回布尔值。
let ary = ["a", "b", "c"];
let result = ary.includes('a');
console.log(result); // true
result = ary.includes('e');
console.log(result);// false
// 以前是通过indexof函数,找不到则返回-1,否则返回下标
8. String 的扩展方法
8.1 模板字符串
- ES6新增的创建字符串的方式,使用反引号定义。
let name = `zhangsan`;
- 模板字符串中可以解析变量。
${}
let name = `张三`;
let sayHello = `我的名字叫${name}`;
console.log(sayHello); //我的名字叫张三
- 模板字符串中可以换行
let html = `<div><span>${result.name}</span><span>${result.age}</span></div>
`;
console.log(html);
- 在模板字符串中可以调用函数,显示函数的返回值
const fn = () => {return '我是fn函数'
}
let html = `我是模板字符串${fn()}`;
console.log(html);// 我是模板字符串我是fn函数
8.2 startsWith()
- startsWith():表示参数字符串是否在原字符串的头部,返回布尔值
- endsWith():表示参数字符串是否在原字符串的尾部,返回布尔值
let str = 'Hello ECMAScript 2015';
let r1 = str.startsWith('Hello');
console.log(r1);//true
let r2 = str.endsWith('2015');
console.log(r2) //true
8.3 repeat()
repeat方法表示将原字符串重复n次,返回一个新字符串。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
9. 数值扩展
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;
可以表示二进制和八进制,十六进制
let b = 0b1010;
let o = 0o777;
let d = 100;
let x = 0xff;
console.log(x); //255
Number.isFinite
:检测一个数值是否为有限数
console.log(Number.isFinite(100)); // true
console.log(Number.isFinite(100/0)); // false
console.log(Number.isFinite(Infinity)); // false
Number.isNaN
:检测一个数值是否为 NaN;返回true表示不是数组,false表示是一个数字
console.log(Number.isNaN(123)); false
字符串转整数:Number.parseInt
,
字符串转浮点数: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, 0,-1
console.log(Math.sign(100)); // 1
console.log(Math.sign(0)); // 0
console.log(Math.sign(-20000)); // -1
10. 对象的扩展
Object.is
:用来比较两个值是否严格相等
Object.is('foo', 'foo')// true
Object.is({}, {})// false
// 有 两个比较特殊(了解即可)
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
Object.assign
:对象的合并,将源对象的所有可枚举属性,复制到目标对象上;若有同名属性,后面的会覆盖前面的
const target = {host: 'localhost',port: 3306,
};
const source1 = {host: '127.0.0.1',port: 33060,test: 'test'
}
console.log(Object.assign(target , source1));
JavaScript 语言的对象继承是通过原型链实现的。ES6 提供了更多原型对象的操作方法
Object.setPrototypeOf
:设置一个对象的原型对象(prototype)
Object.getPrototypeof
:读取一个对象的原型对象
const nation = {name: "China",
};
const cities = {area1: "beijing",
};
Object.setPrototypeOf(cities, nation); // 将nation对象设为cities对象的原型
nation.language = "chinese"; // 往原型对象上加属性
console.log(cities.language);
console.log(Object.getPrototypeOf(cities)); // 获取cities的原型对象
11. Set集合
11.1 基本使用
- ES6 提供了新的数据结构Set(集合)。它类似于数组,但是成员的值都是唯一的,没有重复的值。集合实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历
- Set本身是一个构造函数,用来生成 Set 数据结构。
const s = new Set();
Set函数可以接受一个数组作为参数,用来初始化。
const s2 = new Set(["a", "b"]);
console.log(s2.size); //2
Set没有重复的值。可以用于数组去重
const s3 = new Set(["a","a","b","b"]);
console.log(s3.size); //2
const ary = [...s3]; //将set数据结构分解成以,号分割的离散量,再加个[]成了数组
console.log(ary);
集合的属性和方法:
size()
:返回集合的元素个数add(value)
:添加某个值,返回 Set 结构本身delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功has(value)
:返回一个布尔值,表示该值是否为 Set 的成员clear()
:清除所有成员,没有返回值
const s4 = new Set();
//向set结构中添加值 使用add方法
s4.add('a').add('b');
console.log(s4.size); //2
//从set结构中删除值 用到的方法是delete
const r1 = s4.delete('c');
console.log(s4.size)
console.log(r1); //false
//判断某一个值是否是set数据结构中的成员 使用has
const r2 = s4.has('d');
console.log(r2); //false
//清空set数据结构中的值 使用clear方法
s4.clear();
console.log(s4.size);
遍历:Set 结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。
const s5 = new Set(['a', 'b', 'c']);
s5.forEach(value => {console.log(value);
})
11.2 巧用
- 数组去重
let arr = [1, 2, 3, 4, 3, 2, 1];
let result = [...new Set(arr)];// 把arr扔到set里自动去重,再通过扩展运算符展开,最后纳入[]
console.log(result); // [1, 2, 3, 4]
- 求交集
let arr = [1, 2, 3, 4];
let arr2 = [3, 4, 5, 6];
let result = [...new Set(arr)].filter(item => {let s2 = new Set(arr2);return s2.has(item);
});
console.log(result); //[3, 4]
- 并集
let arr = [1, 2, 3, 4];
let arr2 = [3, 4, 5, 6];
let union = [...new Set([...arr, ...arr2])];
console.log(result); // [1, 2, 3, 4, 5, 6]
- 差集
let arr = [1, 2, 3, 4];
let arr2 = [3, 4, 5, 6];
let diff = [...new Set(arr)].filter(item => !(new Set(arr2).has(item)));
console.log(diff); // [1, 2]
12. Map
- ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是“键” 的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
- Map 也实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历。
Map的属性和方法:
size
:返回 Map 的元素个数set
:增加一个新元素,返回当前 Mapget
:返回键名对象的键值has
:检测 Map 中是否包含某个元素,返回 boolean 值clear
:清空集合,返回 undefined
//声明 Map
let m = new Map();
//添加元素
m.set('name', '凉宫');
m.set('say', function () {console.log("hello!!");
});
// obj作为键
let obj = {age: 20
};
m.set(obj, ['北京', '上海']);
//size 获得多少对键值对
// console.log(m.size); // 3
//删除
// m.delete('name');
//获取
m.get('say');
m.get(obj);
//清空
m.clear();
//遍历
for (let v of m) {console.log(v);
}
13. 类和对象
- 面向对象的思维特点:
① 抽取(抽象)对象共用的属性和行为组织(封装)成一个类(模板)
② 对类进行实例化, 获取类的对象
13.1 类简介
- 在 ES6 中新增加了类的概念,可以使用
class
关键字声明一个类,之后以这个类来实例化对象。 - 类抽象了对象的公共部分,它泛指某一大类(class)
- 对象特指某一个,通过类实例化一个具体的对象
13.2 对象简介
- 在 JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等。
- 对象是由属性和方法组成的:
①属性:事物的特征,在对象中用属性来表示(常用名词)
②方法:事物的行为,在对象中用方法来表示(常用动词)
13.3 创建类
- 类申明语法:
class className { // class body
}
- 创建实例:
let xx = new className();
- 注意: 类必须使用 new 实例化对象
13.4 构造函数
constructor()
方法是类的构造函数(默认方法),用于传递参数,返回实例对象,通过new
命令生成对象实例时,自动调用该方法。如果没有显示定义, 类内部会自动给我们创建一个constructor()
class Person {// 类的共有属性放到 constructor 里面constructor(name,age) { // constructor 构造方法或者构造函数 this.name = name;this.age = age; }
}
创建实例:
let p = new Person('凉宫', 18);
13.5 类添加方法
语法:
class Person {constructor(name,age) { // constructor 构造器或者构造函数 this.name = name;this.age = age; }say() {console.log(this.name + '你好'); }
}
创建实例并调用方法:
let p = new Person('凉宫', 15);
p.say()
注意: 方法之间不能加逗号分隔,同时方法不需要添加 function 关键字。
对比ES5和ES6创建类的方式:
function Bike(brand, price) {this.brand = brand;this.price = price;
}
//添加方法
Bike.prototype.ride = function () {console.log("骑车!!");
}
//实例化对象
let bike = new Bike('大行', 5999);
bike.ride();
//class
class Car {//构造方法 名字不能修改constructor(brand, price) {this.brand = brand;this.price = price;}//方法必须使用该语法, 不能使用 ES5 的对象完整形式drive() {console.log("开车!!");}
}
let car = new Car("奔驰", 499999);
car.drive();
13.6 类的静态成员
ES6添加静态成员,用类名去访问
class Phone {//静态属性static name = '手机';static change() {console.log("我可以改变世界");}
}
let nokia = new Phone();
console.log(nokia.name); // undefined;不能通过实例去访问静态成员
console.log(Phone.name); // 可以,通过类名去访问
回顾:ES5中添加静态成员如下:
function Phone() {}
Phone.name = '手机'; // 直接给类(方法)添加成员,是静态成员
Phone.change = function () {console.log("我可以改变世界");
}
Phone.prototype.price = '6k'; // 通过给原型链添加属性,实例可以访问到
let nokia = new Phone(); //
console.log(nokia.name); // undefined ,不能通过实例去访问
// nokia.change();
console.log(nokia.price); // 可以访问
13.7 类的私有成员
可以通过 # 的方式来标识私有属性和方法。
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("小加加", 16, "3w-ton");
// console.log(girl.#weight); // 类外部无权访问私有属性
girl.intro(); // 通过类内部方法可调用
14. 类的继承
14.1 继承
- 语法:
class Father{ // 父类
}
class Son extends Father { // 子类继承父类
}
- 案例:
class Father { constructor(surname) {this.surname= surname; }say() {console.log('你的姓是' + this.surname); }
}
class Son extends Father{ // 这样子类就继承了父类的属性和方法
}
let damao= new Son('刘');
damao.say();
- 继承中的属性或者方法查找原则采用就近原则:继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的,如果子类里面没有,就去查找父类有没有这个方法。
14.2 super 关键字
super
关键字用于访问和调用对象父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数super.方法()
class Person { // 父类 constructor(surname){this.surname = surname; }
}
class Student extends Person { // 子类继承父类 constructor(surname,firstname){super(surname); // 调用父类的constructor(surname) this.firstname = firstname; // 定义子类独有的属性}
}
- 注意: 子类在构造函数中使用
super
, 必须放到 this 前面 (必须先调用父类的构造方法,再使用子类构造方法)
14.3 方法的重写
子类覆盖父类(方法名、返回值、参数的类型和个数)相同的方法
class Father {constructor(name) {this.name = name;};call() {console.log("手机可以打电话!");};
};
class Son extends Father {constructor(name) {super(name);};call() {console.log("这是子类的函数");};
};
const xiaomi = new Son("小米");
xiaomi.call(); // 调用的是子类的方法
14.4 get 和 set
在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
- get 关键字将对象属性与函数进行绑定,当属性被访问时,对应函数被执行
- set 关键字将对象属性与函数进行绑定,当属性被赋值时,对应函数被执行。
class Phone {get price() {console.log("价格属性被读取了");return 'xxx';}set price(newVal) {console.log('价格属性被修改了');}
}
//实例化对象
let s = new Phone();
console.log(s.price);
s.price = 'free';
14.5 注意点
- 在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象
- 类里面的共有属性和方法一定要加this使用
- constructor 里面的this指向实例对象, 方法里面的this 指向这个方法的调用者
<button>点击</button>
<script>let that;let _that;class Star {constructor(uname, age) {// constructor 里面的this 指向的是 创建的实例对象that = this;this.uname = uname;this.age = age;// this.sing();this.btn = document.querySelector('button');this.btn.onclick = this.sing;}sing() {// 这个sing方法里面的this 指向的是 btn 这个按钮,因为这个按钮调用了这个函数console.log(that.uname); // that里面存储的是constructor里面的this}dance() {// 这个dance里面的this 指向的是实例对象 ldh 因为ldh 调用了这个函数_that = this;console.log(this);}}let ldh = new Star('刘德华');console.log(that === ldh);ldh.dance();console.log(_that === ldh);// 1. 在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象// 2. 类里面的共有的属性和方法一定要加this使用.
</script>
15. 简化对象写法
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
let name = '凉宫';
let change = function () {console.log('我可以改变');
}
const school = { //属性和方法简写name,change,improve() {console.log('提升自我');}
}
// 对比原先写法
const school1 = {name: name,change: change,improve: function () {console.log('提升自我');}
}
16. 形参赋初值
ES6 允许给函数参数赋值初始值
- 形参初始值 具有默认值的参数, 一般位置要靠后(潜规则)
function add(a,b,c=10) {return a + b + c;
}
let result = add(1,2);
console.log(result);
- 与解构赋值结合
function connect({host="127.0.0.1", username,password, port}){console.log(host)console.log(username)console.log(password)console.log(port)
}
connect({host: 'baidu.com',username: 'root',password: 'root',port: 3306
})
17. Symbol类型
ES6引入了一种新的原始数据类型 Symbol ,表示独一无二的值。它是JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型。
Symbol 特点
- Symbol 的值是唯一的,用来解决命名冲突的问题
- Symbol 值不能与其他数据进行运算
- Symbol 定义的对象属性不能使用
for…in
循环遍历 , 但是可以使用Reflect.ownKeys
来获取对象的所有键名
17.1 Symbol 基本使用
- 创建Symbol
let s = Symbol();
let s2 = Symbol('凉宫'); //可以接受一个字符串作为参数,用于描述区分。
let s3 = Symbol('凉宫');
console.log(s2 === s3); // false
- 通过Symbol.for()创建;通过基于传入的字符串作为参数,新建Symbol 值;若原先已经用该字符串创建过,则直接返回已创建过的Symbol 值
let s1 = Symbol.for('凉宫');
let s2 = Symbol.for('凉宫');
console.log(s1 === s2);// true
- 不能与其他数据进行运算,如下都会报错
let s = Symbol();
let result1 = s + 100; //数字运算
let result2 = s > 100; //比较
let result3 = s + 'h'; //字符串拼接
let result4 = s + s; //自己加自己
- 用于对象的属性名,能保证不会出现同名的属性
let mySymbol = Symbol();
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {[mySymbol]: 'Hello!'
};
// Symbol 值作为对象属性名时,不能用点运算符。
a.mySymbol; //undefined
17.2 Symbol 内置值
除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。可以称这些方法为魔术方法,因为它们会在特定的场景下自动执行。
内置值 | 描述 |
---|---|
Symbol.hasInstance | 当其他对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法 |
Symbol.isConcatSpreadable | 对象的 Symbol.isConcatSpreadable 属性等于的是一个布尔值,表示该对象用于 Array.prototype.concat()时,是否可以展开。 |
Symbol.species | 创建衍生对象时,会使用该属性 |
Symbol.match | 当执行 str.match(myObject) 时,如果该属性存在,会调用它,返回该方法的返回值。 |
Symbol.replace | 当该对象被 str.replace(myObject)方法调用时,会返回该方法的返回值。 |
Symbol.search | 当该对象被 str. search (myObject)方法调用时,会返回该方法的返回值。 |
Symbol.split | 当该对象被 str. split (myObject)方法调用时,会返回该方法的返回值。 |
Symbol.iterator | 对象进行 for…of 循环时,会调用 Symbol.iterator 方法,返回该对象的默认遍历器 |
Symbol.toPrimitive | 该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。 |
Symbol. toStringTag | 在该对象上面调用 toString 方法时,返回该方法的返回值 |
Symbol. unscopables | 该对象指定了使用 with 关键字时,哪些属性会被 with环境排除。 |
详情可参考:阮一峰ECMAScript 6 入门
18. Iterator迭代器
迭代器(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 属性的对象
注: 需要自定义遍历数据的时候,要想到迭代器。
小示例:自定义遍历数据
//声明一个对象
const sos = {name: "sos团",stus: ['凉宫','长门','阿虚',],// 自定义iterator 接口,自定义遍历[Symbol.iterator]() {//索引变量let index = 0;return {next: () => {if (index < this.stus.length) {const result = { value: this.stus[index], done: false };//下标自增index++;//返回结果return result;} else {return { value: undefined, done: true };}}};}
}
//遍历这个对象 , 这个v是属性值
for (let v of sos) {console.log(v);
}
19. Generator 生成器函数
19.1 Generator简介
- 生成器函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同
- Generator 函数有两个特征。一是,function关键字与函数名之间有一个
*
星号;二是,函数体内部使用yield
表达式,定义不同的内部状态 - 调用 Generator 函数后,会指向内部状态的Iterator Object,要手动
next
才会执行yield表达式
//生成器其实就是一个特殊的函数
//异步编程 如:纯回调函数 node fs ajax mongodb
//Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行
function* gen() {// 业务逻辑1yield '一只没有耳朵';// 业务逻辑2yield '一只没有尾部';// 业务逻辑3return 'xxx'; //结束执行
}
let iterator = gen();
console.log(iterator.next());// 结果:{value: '一只没有耳朵', done: false}
console.log(iterator.next());// 结果:{value: '一只没有尾部', done: false}
console.log(iterator.next());// 结果:{value: 'xxx', done: true}
// value属性就是当前yield表达式的值,done属性的值false,表示遍历还没有结束
//for...of循可以自动遍历 Generator 函数运行时生成的Iterator对象,且不需要调用next方法
// for (let v of gen()) {// console.log(v);
// }
19.2 next 带参数
next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
function* gen(arg) {console.log('arg:', arg);let one = yield 111;console.log('one:', one);let two = yield 222;console.log('two:', two);
}
//执行获取迭代器对象
let iterator = gen('AAA');
//next方法可以传入实参
iterator.next(); //在这里 输出了 AAA
console.log(iterator.next('BBB')); //{value: 222, done: false}
console.log(iterator.next('CCC')); // {value: undefined, done: true}
小示例:
// 异步编程 文件操作 网络操作(ajax, request) 数据库操作
// 1s 后控制台输出 111 2s后输出 222 3s后输出 333
// 回调地狱
// setTimeout(() => {// console.log(111);
// setTimeout(() => {// console.log(222);
// setTimeout(() => {// console.log(333);
// }, 3000);
// }, 2000);
// }, 1000);//模拟 先获取用户数据 再订单数据 最后商品数据
function getUsers() {setTimeout(() => {let data = '用户数据';//调用 next 方法, 并且将数据传入iterator.next(data);}, 1000);
}
function getOrders() {setTimeout(() => {let data = '订单数据';iterator.next(data);}, 1000)
}
function getGoods() {setTimeout(() => {let data = '商品数据';iterator.next(data);}, 1000)
}
function* gen() {let users = yield getUsers();let orders = yield getOrders();let goods = yield getGoods();
}
//调用生成器函数
let iterator = gen();
iterator.next();
20. ES6 模块化
20.1 模块化规范分类
- 在ES6模块化规范诞生之前,JavaScript社区已经尝试并提出了AMD、CMD、CommonJS等模块化规范。
- 这些由社区提出的模块化标准,存在一定的差异性与局限性、并不是浏览器与服务器通用的模块化标准,例如:
① AMD和CMD适用于浏览器端的Javascript模块化
② CommonJS适用于服务器端的Javascript模块化 - 太多的模块化规范给开发者增加了学习的难度与开发的成本。因此,大一统的ES6模块化规范诞生了!
20.2 何谓ES6模块化规范
- ES6模块化规范是浏览器端与服务器端通用的模块化开发规范。它的出现极大的降低了前端开发者的模块化学习成本,开发者不需再额外学习AMD、CMD 或 CommonJS等模块化规范。
- ES6模块化规范中定义:
- 每个js 文件都是一个独立的模块
- 导入其它模块成员使用
import
关键字 - 向外共享模块成员使用
export
关键字
20.3 体验ES6 模块化
node.js 中默认仅支持Common]S模块化规范,若想基于node.js体验与学习ES6的模块化语法,可以按照如下两个步骤进行配置:
- ① 确保安装了v14.15.1或更高版本的node.js(在终端
node -v
查看版本) - ② 在package.json 的根节点中添加"type": "module"节点
20.4 ES6模块化的基本语法
- ES6的模块化主要包含如下3种用法:
① 默认导出与默认导入
② 按需导出与按需导入
③ 直接导入并执行模块中的代码
20.4.1 默认导入或导出
1、默认导出语法: export default 默认导出的成员
let n1 = 10; // 定义模块私有成员 n1
let n2 = 20; // 定义模块私有成员 n2(外界访问不到n2,因为在此没有被共享出去)
function show() {} // 定义模块私有方法 show
export default { // 使用 export default 默认导出语法,向外共享 n1 和 show 两个成员n1,show,
};
- 默认导出注意事项:每个模块中,只允许使用唯一的一次export default,否则会报错!
2、默认导入语法: import 接收名称 from '模块标识符'
,一般为路径;
// 从 默认导出.js 模块中导入 它向外共享的成员
// 并使用 xxx 成员进行接收
import xxx from "./默认导出.js";
console.log(xxx); //输出结果 { n1: 10, show: [Function: show] }
- 默认导出注意事项:默认导入时的接收名称可以任意名称,只要是合法的成员名称即可
import m1 from './默认导出.js'; //m1 是合法命名
import 123m from './默认导出.js'; // 不合法,不能以数字开头
20.4.2 按需导入或导出
1、按需导出语法:export 按需导出的成员
export let s1 = "aaa"; // 向外导出变量 s1
export let s2 = 123; // 向外导出变量 s2
export function say(){ // 向外导出方法 say
}
2、按需导入语法:import {s1} from '模块标识符'
import { s1, say } from "./按需导出.js";
console.log(s1); // aaa
console.log(say); //[Function: say]
- 按需导入或导出的注意事项
① 每个模块中可以使用多次按需导出
② 按需导入的成员名称必须和按需导出的名称保持一致
③ 按需导入时,可以使用as关键字进行重命名
④ 按需导入可以和默认导入一起使用
// 写在{} 里面的是按需导入的成员,写在{}外面的是默认导入的成员
import info, { s1, say } from "./按需导出.js";
console.log(s1); //aaa
console.log(say); //[Function: say]
console.log(info); // { a: 20}
import {s1 as sss} from './按需导出.js'
console.log(sss)
20.4.3 直接导入并执行模块中的代码
如果只想单纯地执行某个模块中的代码,并不需要得到模块中向外共享的成员。此时,可以直接导入并执行模块代码,示例代码如下:
import "./test.js";
直接导入不需要接收名,没有意义。
21. Promise
21.1 回调地狱
多层回调函数的相互嵌套,就形成了回调地狱。示例代码如下:
setTimeout(() => { // 第1层回调函数console.log("延时1秒后输出");setTimeout(() => {// 第2层回调函数console.log("再延时2秒后输出");setTimeout(() => {// 第 3 层回调函数console.log("再延时3秒后输出");setTimeout(() => {// 第 3 层回调函数console.log("再延时4秒后输出");}, 4000);}, 3000);}, 2000);
}, 1000);
回调地狱的缺点:
- 代码耦合性太强,牵一发而动全身,难以维护
- 大量冗余的代码相互嵌套,代码的可读性变差
如何解决回调地狱:
- 为了解决回调地狱的问题,ES6 (ECMAScript 2015)中新增了Promise的概念。
21.2 Promise的基本概念
① Promise是一个构造函数
- 可以创建Promise的实例
const p = new Promise()
- new出来的Promise实例对象,代表一个异步操作
② Promise.prototype
上包含一个.then()
方法
- 每一次new Promise()构造函数得到的实例对象,都可以通过原型链的方式访问到.then()方法,例如
p.then()
③ .then()
方法用来预先指定成功和失败的回调函数
- p.then(成功的回调函数,失败的回调函数)
p.then(result => {}, error =>{})
- 调用.then()方法时,成功的回调函数是必选的、失败的回调函数是可选的
④ promise 的状态改变
- 1、pending(进行中) 变为 fulfilled(或称resolved)(已成功)
- 2、pending(进行中) 变为 rejected(已失败)
说明: 只有这 2 种改变·, 且一个 promise 对象只能改变一次无论变为成功还是失败, 都会有一个结果数据(值),成功的结果数据一般称为 value, 失败的结果数据一般称为 error
21.3 promise 的基本流程
21.4 promise 的基本使用
21.4.1 基本用法
// 1) 创建 promise 对象(pending 状态), 指定执行器函数
// 形参中的resolve意为解决,是函数类型的数据
// 形参中的reject意为拒绝,也是函数类型的数据
const p = new Promise((resolve, reject) => {// 2) 在执行器函数中启动异步任务setTimeout(() => {const time = Date.now();// 3) 根据结果做不同处理// 3.1) 如果成功了, 调用 resolve(), 指定成功的 valueif (time % 2 === 1) {resolve("成功的值 " + time);// 调用resolve,就会将 promise 对象的状态设置为 resolved『成功』} else {// 3.2) 如果失败了, 调用 reject(), 指定失败的 reasonreject("失败的值" + time); // 调用lreject,将 promise 对象的状态设置为 rejected『失败』}}, 2000);
});
// 4) 能在 promise 指定成功或失败的回调函数中来获取成功的 vlaue 或失败的 error
p.then((value) => { // 这是 promise状态变为【成功】时的回调// 成功的回调函数 onResolved, 得到成功的 vlaueconsole.log("成功的 value: ", value);},(error) => {// 这是promise状态变为【失败】时的回调// 失败的回调函数 onRejected, 得到失败的error信息console.log("失败的 reason: ", error);}
);
【案例:使用 promise 封装 ajax 异步请求】
function promiseAjax(url) {return new Promise((resolve, reject) => {// 1. 创建对象const xhr = new XMLHttpRequest();xhr.responseType = "json"; // 把响应的结果做个格式化 json格式// 2.初始化xhr.open("GET", url);// 3. 发送xhr.send();// 4. 处理响应结果xhr.onreadystatechange = () => {if (xhr.readyState !== 4) return;const { status, response } = xhr;// 请求成功, 调用 resolve(value)if (status >= 200 && status < 300) {resolve(JSON.parse(response));} else {// 请求失败, 调用 reject(reason)reject(new Error("请求失败: status: " + status));}};});
}
promiseAjax("https://api.apiopen.top/api/sentences").then((data) => {console.log("显示成功数据", data);},(error) => {console.warn(error.message);}
);
21.4.2 Promise 构造函数
Promise 构造函数: Promise (excutor) {}
const promise = new Promise(function(resolve, reject) {// 执行异步任务if (/* 异步操作成功 */){resolve(value);} else {reject(error);}
});
说明:
- executor 函数: 称为执行器,格式
(resolve, reject) => {}
- resolve 函数: 内部定义成功时调用的函数
value => {}
- reject 函数: 内部定义失败时调用的函数
error => {}
- executor 会在 Promise 内部立即同步调用;异步操作在执行器中执行
let p = new Promise((resolve, reject) => {// 同步调用console.log(111);//修改 promise 对象的状态reject('error');
});
console.log(222); // 先输出 111 后输出 222
21.4.3 then 方法
promise实例.then(onResolved, onRejected);
promise实例.then(val => {// 成功的回调函数,处理成功后的业务console.log(val);
},error => {// 失败的回调函数,处理失败后的业务console.log(error)
})
- Promise.prototype.then 方法:
(onResolved, onRejected) => {}
- onResolved 函数: 成功的回调函数
(value) => {}
- onRejected 函数: 失败的回调函数
(error) => {}
- 说明: 指定用于得到成功 value 的成功回调和用于得到失败 error 的失败回调,then方法返回的是一个新的Promise对象
21.4.4 catch 方法
let p = new Promise((resolve, reject) => {reject('error'); // 置状态为失败
});
p.then((value) => {// 成功的回调;这里省去失败的回调,改用catch方法捕获失败
}).catch((error) => {// 处理回调函数运行时发生的错误console.log('发生错误!', error);
});
- Promise.prototype.catch 方法:
(onRejected) => {}
- onRejected 函数: 失败的回调函数
(error) => {}
- 说明: 其实是then()的语法糖, 相当于: then(undefined, onRejected)
21.4.5 Promise.resolve 方法
用于将传入的东西转化为promise对象
let p1 = Promise.resolve(123);
let p2 = Promise.resolve(new Promise((resolve, reject) => {// resolve('OK');reject('Error');
}));
- 如果传入的参数为 非Promise类型的对象, 则返回的结果为成功promise对象,值为传入的参数
- 如果传入的参数为 Promise 对象, 则原封不动地返回这个对象,值为原来promise对象的值
21.4.6 Promise.reject 方法
无论传入的是什么参数,返回的都是一个失败的 promise 对象,此promise对象的值为传入的参数值
let p = Promise.reject(521);
let p2 = Promise.reject('iloveyou');
let p3 = Promise.reject(new Promise((resolve, reject) => {resolve('OK');
}));
21.4.7 all 方法
- 接收一个数组, 只有数组中所有的 promise 对象的状态都成功,则返回一个新的成功的promise对象,值为数组中所有promise的值组成的数组;
- 只要有一个失败了,就返回一个失败的proimse对象,值就为第一个失败的promise的值
let p1 = new Promise((resolve, reject) => {resolve('OK');
})
// let p2 = Promise.resolve('Success');
let p2 = Promise.reject('Error');
let p3 = Promise.resolve('Oh Yeah');
const result = Promise.all([p1, p2, p3]);
console.log(result);
21.4.8 race 方法
- 接收一个数组,返回一个新的promise对象;
- 在数组中第一个改变状态的 promise 的对象,就是决定了返回的新的promise对象的状态和值。
let p1 = new Promise((resolve, reject) => {setTimeout(() => {resolve('OK');}, 1000);
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');
//调用
const result = Promise.race([p1, p2, p3]);
console.log(result); // 结果 就是输出基于p2的新的proimse对象
21.5 几个关键问题
1、如何改变 promise 的状态?
- resolve(value): 如果当前是 pending 就会变为 resolved
- reject(reason): 如果当前是 pending 就会变为 rejected
- 抛出异常: 如果当前是 pending 就会变为 rejected
let p = new Promise((resolve, reject) => {//1. resolve 函数// resolve('ok'); // pending => fulfilled (resolved)//2. reject 函数// reject("error");// pending => rejected //3. 抛出错误throw new Error('出错了');
});
2、一个 promise 指定多个成功/失败回调函数,都会调用吗?
答:当 promise 改变为对应状态时都会调用
let p = new Promise((resolve, reject) => {resolve('OK');
});
///指定回调 1
p.then(value => {console.log(value);
});
//指定回调 2
p.then(value => {console.log("我是第二个回调");
});
3、改变 promise 状态 和 绑定回调函数谁先谁后?
注意:这里指绑定回调不是执行回调,注意区分
都有可能,正常情况下是先绑定回调再改变状态, 但也可以先改状态再指定回调
- 如何先改状态再绑定回调?
① 在执行器中直接调用 resolve() / reject() (即不把这两个放在异步任务里调用)
② 延迟更长时间才调用 then() - 什么时候才能得到数据?
① 如果先绑定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
② 如果先改变的状态, 那当绑定回调时, 回调函数就会调用, 得到数据
let p = new Promise((resolve, reject) => {setTimeout(() => { // 这里 是否把resolve放到定时器中,就会产生上述两种情况resolve('OK');}, 1000);
});
p.then(value => {console.log(value);
},err=>{console.log(err);
})
4、promise.then()返回的新 promise 的状态和值由什么决定?
- 简单表达: 由 then()指定的回调函数执行的结果决定
- 详细表达: (注:无论是失败的回调还是成功的回调,都是如下结果)
① 无论回调函数里面是否写return,then的返回值始终是promise对象
② 回调函数中如果抛出异常, 则then返回的新 promise 状态变为 rejected, 值为抛出的异常
③ 如果回调return的是非 promise 的任意值, 则then返回的新 promise 状态变为 resolved, 值就为 回调中return的值
④ 如果回调中return的是 promise对象, 则then返回的 promise 的状态和值就和回调中返回promise对象的一样
let p = new Promise((resolve, reject) => {resolve('ok');
});
//执行 then 方法
let result = p.then(value => {// console.log(value);//1. 抛出错误// throw '出了问题';//2. 返回结果是非 Promise 类型的对象// return 521;//3. 返回结果是 Promise 对象// return new Promise((resolve, reject) => {// // resolve('success');// reject('error');// });
}, error=> {console.warn(error);
});
console.log(result);
5、promise 如何串连多个操作任务?
- promise 的 then()返回一个新的 promise, 可以开成 then()的链式调用
- 通过 then 的链式调用串连多个同步/异步任务
- 下一个then的参数的值是上一个then返回的promise对象的值
let p = new Promise((resolve, reject) => {setTimeout(() => {resolve('OK');}, 1000);
});
p.then(value => {return new Promise((resolve, reject) => {resolve("success");});
}).then(value => {console.log(value); // 输出 success
}).then(value => {console.log(value); // 输出 undefined ,因为上一个then没有为其返回值指定具体的值
})
6、promise 异常穿透?
- 当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调(在最后捕获异常)
- 前面任何操作出了异常, 都会传到最后失败的回调中处理,且只处理最开始的那个异常
let p = new Promise((resolve, reject) => {setTimeout(() => {resolve('OK');// reject('Err');}, 1000);
});
p.then(value => {// console.log(111);throw '失败啦!';
}).then(value => {console.log(222);
}).then(value => {console.log(333);
}).catch(reason => {console.warn(reason);
});
7、如何中断 promise 链?
- 当使用 promise 的 then 链式调用时, 在中间中断, 不再调用后面的回调函数的方法:在回调函数中返回一个 pendding 状态的 promise 对象
- 原因:回调中返回的pendding状态promise,其then返回的promise状态也为pendding,后面的then因为上一个promise状态为pendding(非成功,和非失败),状态不改变就不会再调用回调
let p = new Promise((resolve, reject) => {setTimeout(() => {resolve('OK');}, 1000);
});
p.then(value => {console.log(111);//有且只有一个方式,返回一个 pendding 状态的 promise 对象return new Promise(() => {});
}).then(value => {console.log(222);
}).then(value => {console.log(333);
}).catch(reason => {console.warn(reason);
});
21.6 Promise 案例练习
21.6.1 基于then-fs读取文件内容
由于node.js官方提供的fs模块仅支持以回调函数的方式读取文件,不支持 Promise的调用方式。因此,需要先运行如下的命令,安装 then-fs这个第三方包,从而支持基于Promise 的方式读取文件的内容:
npm install then-fs
21.6.2 then-fs的基本使用
调用then-fs提供的 readFile()方法,可以异步地读取文件的内容,它的返回值是Promise 的实例对象。因此可以调用.then()方法为每个Promise异步操作指定成功和失败之后的回调函数。示例代码如下:
// 基于Promise的方式读取文件
import thenFs from "then-fs";
// 注意:.then()中的失败回调函数是可选的,可以被省略
thenFs.readFile("./day2/file/1.txt", "utf-8").then((r1) => {console.log(r1),(err1) => {console.log(err1.message);};
});
thenFs.readFile("./day2/file/2.txt", "utf-8").then((r2) => {console.log(r2),(err2) => {console.log(err2.message);};
});
thenFs.readFile("./day2/file/3.txt", "utf-8").then((r3) => {console.log(r3),(err3) => {console.log(err3.message);};
});
注意:上述的代码无法保证文件的读取顺序,需要做进一步的改进!
21.6.3 基于Promise顺序读取文件内容
Promise支持链式调用,从而来解决回调地狱的问题。示例代码如下:
import thenFs from "then-fs";
thenFs.readFile("./day2/file/1.txt", "utf-8") //1. 返回值是Promise的实例对象.then((r1) => {//2. 通过 .then 为第一个Promise 实例指定成功之后的回调函数console.log(r1);return thenFs.readFile("./day2/file/2.txt", "utf-8"); // 3. 在第一个.then中 返回一个新的Promise实例对象}).then((r2) => { // 4. 继续使用.then 为上一个.then的返回值(新的Promise实例) 指定成功之后的回调函数console.log(r2);return thenFs.readFile("./day2/file/3.txt", "utf-8"); // 5. 在第二个 .then中再返回一个新的 Promise实例对象 }).then((r3) => { // 6. 继续调用 .then 为上一个 .then 的返回值 指定成功之后的回调函数console.log(r3); });
21.6.4 通过.catch捕获错误
在Promise的链式操作中如果发生了错误,可以Promise.prototype.catch
方法进行捕获和处理:
import thenFs from "then-fs";
thenFs.readFile("./day2/file/1111.txt", "utf-8") // 1111.txt 文件不存在,导致读取失败,后面的3个.then都不执行.then((r1) => {console.log(r1);return thenFs.readFile("./day2/file/2.txt", "utf-8");}).then((r2) => {console.log(r2);return thenFs.readFile("./day2/file/3.txt", "utf-8");}).then((r3) => {console.log(r3);}).catch((err) => {// 捕获第一个发生的错误,并输出错误的信息console.log(err.message);})
;
如果不希望前面的错误导致后续的.then无法正常执行,则可以将.catch 的调用提前,示例代码如下:
import thenFs from "then-fs";
thenFs.readFile("./day2/file/1111.txt", "utf-8") // 1111.txt 文件不存在,导致读取失败.catch((err) => {// 捕获第一个发生的错误,并输出错误信息//由于错误已被及时处理,不影响后续.then的整成运行console.log(err.message);}).then((r1) => {console.log(r1); // undefinedreturn thenFs.readFile("./day2/file/2.txt", "utf-8");}).then((r2) => {console.log(r2); // bbbbbbbbbbreturn thenFs.readFile("./day2/file/3.txt", "utf-8");}).then((r3) => {console.log(r3); // cccccccccc});
21.6.5 使用Promise.all()方法
Promise.all()方法会发起并行的Promise 异步操作,等所有的异步操作全部结束后才会执行下一步的.then操作(等待机制)。示例代码如下:
import thenFs from "then-fs";
// 1. 定义一个数组,存放3个读文件的异步操作
const promiseArr = [thenFs.readFile("./day2/file/1.txt", "utf-8"),thenFs.readFile("./day2/file/2.txt", "utf-8"),thenFs.readFile("./day2/file/3.txt", "utf-8"),
];
// 2. 将Promise 的数组,作为Promise.all()的参数
Promise.all(promiseArr).then(([r1, r2, r3]) => {// 所有文件读取成功(等待机制)console.log(r1, r2, r3);}).catch((err) => { // 捕获 Promise异步操作中的错误console.log(err.message);});
注意: 数组中Promise 实例的顺序,就是最终结果的顺序!
21.6.6 使用Promise.race()方法
Promise.race()方法会发起并行的Promise异步操作,只要任何一个异步操作完成,就立即执行下一步的.then操作(赛跑机制)。示例代码如下:
import thenFs from "then-fs";
// 1. 定义一个数组,存放3个读文件的异步操作
const promiseArr = [thenFs.readFile("./day2/file/1.txt", "utf-8"),thenFs.readFile("./day2/file/2.txt", "utf-8"),thenFs.readFile("./day2/file/3.txt", "utf-8"),
];
Promise.race(promiseArr)// 2. 将Promise 的数组,作为Promise.race()的参数.then((result) => { // 只要任何一个异步操作完成,就立即执行成功的回调函数(赛跑机制)console.log(result);}).catch((err) => { // 捕获 Promise异步操作中的错误console.log(err.message);});
21.7 基于Promise封装读文件的方法
方法的封装要求:
- 方法的名称要定义为getFile
- 方法接收一个形参fpath,表示要读取的文件的路径
- 方法的返回值为Promise 实例对象
21.7.1 getFile方法的基本定义
// 1. 方法的名称为getFile、
// 2. 方法接收一个参数 fpath ,表示要读取的文件路径
function getFile(fpath) {// 3. 方法的返回值为Promise的实例对象return new Promise();
}
第5行代码中的new Promise()
只是创建了一个形式上的异步操作。
21.7.2 创建具体的异步操作
如果想要创建具体的异步操作,则需要在new Promise()构造函数期间,传递一个function 函数,将具体的异步操作定义到function函数内部。示例代码如下:
import fs from "fs";
// 1. 方法的名称为getFile、
// 2. 方法接收一个参数 fpath ,表示要读取的文件路径
function getFile(fpath) {// 3. 方法的返回值为Promise的实例对象// 4. 下面代码表示 这是一个具体的,读文件的异步操作fs.readFile(fpath, "utf-8", (err, dataStr) => {});return new Promise();
}
21.7.3 获取.then的两个实参
通过.then()指定的成功和失败的回调函数,可以在function的形参中进行接收,示例代码如下
21.7.4 调用resolve和reject回调函数
Promise 异步操作的结果,可以调用resolve或reject回调函数进行处理。示例代码如下:
import fs from "fs";
function getFile(fpath) {// resolve 是 成功的回调函数,reject 是失败的回调函数return new Promise(function (resolve, reject) {fs.readFile(fpath, "utf-8", (err, dataStr) => {if (err) return reject(err); // 如果读取失败,则调用失败的回调函数resolve(dataStr); // 如果读取成功,则调用成功的回调函数});});
}
// getFile方法的调用过程
getFile("./day2/file/1.txt").then(成功的回调函数, 失败的回调函数);
getFile("./day2/file/1.txt").then(成功的回调函数, 失败的回调函数);
// getFile("./day2/file/1.txt").then(
// (r1) => {// console.log(r1);
// },
// (err) => { //后面这个失败的函数可以不写,可以用.catch来捕获
// console.log(err.message);
// }
// );
22. async/await
22.1 什么是async/await
- async/await是ES8(ECMAScript 2017)引入的新语法,用来简化Promise 异步操作。
- 在 async/await出现之前,开发者只能通过链式.then()的方式处理Promise异步操作。示例代码如下;
thenFs.readFile("./day2/file/1.txt", "utf-8")
.then((r1) => {console.log(r1);return thenFs.readFile("./day2/file/2.txt", "utf-8");
})
.then((r2) => {console.log(r2);return thenFs.readFile("./day2/file/3.txt", "utf-8");
})
.then((r3) => {console.log(r3);
});
.then链式调用的优点:解决了回调地狱的问题
.then链式调用的缺点:代码冗余、阅读性差、不易理解
使用async/await简化 Promise异步操作的示例代码如下:
import thenFs from "then-fs";
// 按照顺序读取文件1,2,3的内容
async function getAllFile() {const r1 = await thenFs.readFile("./day2/file/1.txt", "utf-8");console.log(r1);const r2 = await thenFs.readFile("./day2/file/2.txt", "utf-8");console.log(r2);const s3 = await thenFs.readFile("./day2/file/3.txt", "utf-8");console.log(s3);
}
getAllFile();
22.2 async 函数
- async 函数的返回值为 promise 对象,可分为成功类型的promise或失败类型的promise
- promise 对象的结果由 async 函数执行的返回值决定
//async 函数
async function fn() {// 1.return的不是promise对象,则这个fn函数的返回值为成功类型的promise// return '尚硅谷'; // 2. 抛出错误, fn返回的结果是一个失败类型的 Promise// throw new Error('出错啦!');// 3. 返回的结果如果是一个 Promise 对象,看它调用的是resolve,还是rejectreturn new Promise((resolve, reject) => {resolve("成功的数据"); // 调用resolve 则fn返回值是成功的promise// reject("失败的错误"); // 调用reject,则fn返回值是失败的promise});
}
const result = fn();
console.log(result);
//async 函数
async function fn() {return new Promise((resolve, reject) => {resolve("成功的数据"); // 在这里指定它成功,下面调用.then方法,就回去执行成功的回调// reject("失败的错误");});
}
const result = fn();
// //调用 then 方法
result.then((value) => { // 成功的回调console.log(value);},(reason) => { // 失败的回调console.warn(reason);}
);
22.3 await 表达式
- await 必须写在 async 函数中, async 函数中可以没有 await
- await 右侧的表达式若为 promise 对象,则await 返回的是 promise 成功的值
- await 右侧表达式是其它值, 直接将此值作为 await 的返回值
- await 的 promise 失败了, 就会抛出异常, 需要通过 try…catch 捕获处理
//创建 promise 对象
const p = new Promise((resolve, reject) => {// resolve("用户数据"); //这个调resolve,让这个p为成功类型的promisereject("失败啦!"); // 这个调reject,让这个p为失败类型的promise// throw new Error("错误啦"); // 模拟抛出错误
});
// await 要放在 async 函数中.
async function main() {try {let result = await p; // await p 表达式,返回成功或失败类型的promise对象的值console.log(result); // 这里的result 就为 上面reject或resolve中参数传过来的值} catch (e) {console.log(e); // 如果上面抛出错误,这里就会捕获}
}
//调用函数
main();
22.4 async结合await示例
//1. 引入 fs 模块
const fs = require("fs");
//读取『为学』.md文件中的内容
function readWeiXue() {return new Promise((resolve, reject) => {fs.readFile("./resources/为学.md", (err, data) => {//如果失败if (err) reject(err);//如果成功resolve(data);});});
}
function readChaYangShi() {return new Promise((resolve, reject) => {fs.readFile("./resources/插秧诗.md", (err, data) => {//如果失败if (err) reject(err);//如果成功resolve(data);});});
}
function readGuanShu() {return new Promise((resolve, reject) => {fs.readFile("./resources/观书有感.md", (err, data) => {//如果失败if (err) reject(err);//如果成功resolve(data);});});
}
//声明一个 async 函数
async function main() { //先获取为学内容let weixue = await readWeiXue();//再获取插秧诗内容let chayang = await readChaYangShi();// 最后获取观书有感let guanshu = await readGuanShu();
}
main();
22.5 async/await的使用注意事项
- 如果在function中使用了
await
,则 function必须被async
修饰 - 在async方法中,第一个await 之前的代码会同步执行,await之后的代码会异步执行
示例例题:
// 1.txt,2.txt,3.txt内容分别放了一堆 a, b,c
import thenFs from "then-fs";
console.log("A");
async function getAllFile() {console.log("C");// 正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。// 这里如果不带await,打印的则是Promise 对象,带了打印文件内容const r1 = await thenFs.readFile("./day2/file/1.txt", "utf-8");console.log(r1);const r2 = await thenFs.readFile("./day2/file/2.txt", "utf-8");console.log(r2);const s3 = await thenFs.readFile("./day2/file/3.txt", "utf-8");console.log(s3);console.log("D");
}
getAllFile();
console.log("B");
结果如下图所示
23. EventLoop
23.1 JS是单线程语言
JavaScript是一门单线程执行的编程语言。也就是说,同一时间只能做一件事情。
单线程执行任务队列的问题:如果前一个任务非常耗时,则后续的任务就不得不一直等待,从而导致程序假死的问题。
23.2 同步任务和异步任务
为了防止某个耗时任务导致程序假死的问题,JavaScript把待执行的任务分为了两类:
- 同步任务( synchronous)
- 又叫做非耗时任务,指的是在主线程上排队执行的那些任务
- 只有前一个任务执行完毕,才能执行后一个任务
- 异步任务( asynchronous)
- 又叫做耗时任务,异步任务由JavaScript委托给宿主环境进行执行
- 当异步任务执行完成后,会通知JavaScript主线程执行异步任务的回调函数
23.3 同步/异步任务的执行过程
① 同步任务由JavaScript主线程依次序执行
② 异步任务委托给宿主环境执行
③ 已完成的异步任务对应的回调函数,会被加入到任务队列中等待执行
④ JavaScript主线程的执行栈被清空后,会读取任务队列中的回调函数,依次序执行
⑤ JavaScript主线程不断重复上面的第4步
23.4 EventLoop的基本概念
JavaScript主线程从“任务队列”中读取异步任务的回调函数,放到执行栈中依次执行。这个过程是循环不断的,所以整个的这种运行机制又称为EventLoop (事件循环)。见上图。
结合EventLoop分析输出顺序:题一:
import thenFs from "then-fs";
console.log("A");
thenFs.readFile("./day2/file/1.txt", "utf-8").then((dataStr) => {console.log("B");
});
setTimeout(() => {console.log("C");
}, 0);
console.log("D");
答:正确的输出结果:ADCB。其中:
- A和D属于同步任务。会根据代码的先后顺序依次被执行
- C和B属于异步任务。它们的回调函数会被加入到任务队列中,等待主线程空闲时再执行
24. 宏任务和微任务
24.1 何谓宏任务和微任务
JavaScript把异步任务又做了进一步的划分,异步任务又分为两类,分别是宏任务和微任务,分别放入宏队列和微队列
- 宏任务(fmacrotask)
- 异步Ajax请求
- setTimeout、setInterval
- 文件操作
- DOM事件回调
- 其他宏任务
- 微任务(microtask)
- Promise.then、.catch和 .finally
- process.nextTick
- mutation回调
- 其他微任务
24.2 宏任务和微任务的执行顺序
每一个宏任务执行之前,都会检查是否存在待执行的微任务,如果有,则执行完所有微任务之后,再继续执行下一个宏任务。
练习1:分析以下代码的输出顺序
setTimeout(() => {console.log("1");
});
new Promise((resolve) => {console.log("2");resolve();
}).then(() => {console.log("3");
});
console.log("4");
正确的输出顺序是:2431
分析:
① 先执行所有的同步任务:(执行第5行、第10行代码)
② 再执行微任务:(执行第8行代码)
③ 再执行下一个宏任务:(执行第2行代码)
练习2:经典面试题
请分析以下代码的输出顺序
console.log("1");
setTimeout(() => {console.log("2");new Promise((resolve) => {console.log("3");resolve();}).then(() => {console.log("4");});
});
new Promise((resolve) => {console.log("5");resolve();
}).then(() => {console.log("6");
});
setTimeout(() => {console.log("7");new Promise((resolve) => {console.log("8");resolve();}).then(() => {console.log("9");});
});
正确的输出顺序是:156234789
25. 其他
25.1 ** 运算符
在 ES7 中引入指数运算符「**」,用来实现幂运算,功能与 Math.pow 结果相同
console.log(2 ** 10); // 1024
console.log(Math.pow(2, 10)); 1024
25.2 Object新增方法
Object.values()
方法返回一个给定对象的所有可枚举属性值的数组Object.entries()
方法返回一个给定对象自身可遍历属性 [key,value] 的数组Object.getOwnPropertyDescriptors
//声明对象
const person = {name: "凉宫",age: 16,friends: ["阿虚", "长门", "朝比奈"],
};
// 获取对象所有的键
console.log(Object.keys(person)); // ['name', 'age', 'friends']
// 获取对象所有的值
console.log(Object.values(person)); // ['凉宫', 16, Array(3)]
// 获取对象的键值对数组
console.log(Object.entries(person)); //[['name', '凉宫'], ['age', 16], ['friends', Array(3)]]
// 适合用来创建 Map
const m = new Map(Object.entries(person));
console.log(m);
// 对象属性的描述对象;
console.log(Object.getOwnPropertyDescriptors(person));
// 比如:
// age:
// configurable: true
// enumerable: true
// value: 16
// writable: true
JavaScript学习笔记(四) ES6相关推荐
- JavaScript学习笔记(四)(DOM)
JavaScript学习笔记(四) DOM 一.DOM概述 二.元素对象 2.1 获取方式 (1).通过ID获取一个元素对象,如果没有返回null (2).通过`标签名`获取一组元素对象,,如果没有返 ...
- JavaScript学习笔记(四十四) 装饰器
装饰器模式(Decorator) 在装饰器模式中,可以在运行时给一个对象动态的添加额外的功能.当和静态类打交道的时候(static classes),这可能是一个挑战.但在JavaScript中,对象 ...
- JavaScript学习笔记(四)——jQuery插件开发与发布
jQuery插件就是以jQuery库为基础衍生出来的库,jQuery插件的好处是封装功能,提高了代码的复用性,加快了开发速度,现在网络上开源的jQuery插件非常多,随着版本的不停迭代越来越稳定好用, ...
- JavaScript学习笔记(四)---闭包、递归、柯里化函数、继承、深浅拷贝、设计模式
JavaScript学习笔记(四)---闭包.递归.柯里化函数.继承.深浅拷贝.设计模式 1. 匿名函数的使用场景 2.自运行 3.闭包 3.1前提: 3.2闭包 4.函数对象的三种定义方式 5.th ...
- JavaScript学习笔记(三)---事件、正则表达式、ES6、运动
JavaScript学习笔记(三)---事件.正则表达式.ES6.运动 27.insertbefore 28.滚动条及事件 29.事件及事件对象 30.鼠标事件对象的属性 31.案例:图片随鼠标移动 ...
- JavaScript学习笔记(第二部分)总共四部分
JavaScript学习笔记(第二部分)总共四部分 4 对象(Object) 字符串String.数值Number.布尔值Boolean.空值Null.未定义Undefined是基本的数据类型,这些数 ...
- javascript学习笔记(第四章图片库--初步了解)
javascript学习笔记(第四章图片库–初步了解) 通过前三章的学习我们已经对这个新的语言有了一个了解,js的语法基本和C语言一致,我们可以通过调用一些document对象中的函数来对实现一些很简 ...
- JavaScript学习笔记(第四部分)总共四部分(完结)
JavaScript学习笔记(第四部分)总共四部分 9 事件 9.1 事件对象 当响应函数被调用时,浏览器每次都会将一个事件对象作为实参传递进响应函数中,这个事件对象中封装了当前事件的相关信息,比如: ...
- JavaScript 学习笔记(第三天)
JavaScript 学习笔记(第三天) 一.数组 1.1.数组的基础 1.2.数据类型分类 1.3.创建数组 1.3.1.字面量创建一个数组 1.3.2.内置构造函数创建数组 1.4.数组的基本操作 ...
- 千锋JavaScript学习笔记
千锋JavaScript学习笔记 文章目录 千锋JavaScript学习笔记 写在前面 1. JS基础 1.1 变量 1.2 数据类型 1.3 数据类型转换 1.4 运算符 1.5 条件 1.6 循环 ...
最新文章
- Mysql提示缺少表的别名报错_mysql对sql中别名引起的Column not found问题
- Java网络编程笔记3
- Tencent JDK 国产化CPU架构支持分享
- 基于java教学管理系统设计(含源文件)
- 三丰三坐标编程基本步骤_加工中心开机回零的两种基本方式及常见问题的应对方法...
- python open r w r+ w+ a的区别
- “两步路·户外助手”谷歌类图源
- 苹果系统与win10连接到服务器,苹果手机怎么连接win10电脑详细步骤
- LVM逻辑管理器(Logical volume Manager)
- 华为 USG 双机热备
- Flex布局子元素对齐方式
- 上课签到 php,福建一高校学生上课需刷脸签到 被赞高大上
- JACK——TeamsMaual6 Team Formation
- Pycharm | cv2爆红 | opencv-python安装 | Requirement already satisfied: opencv-python 有效解决方法
- 网页控制台控制视频倍速
- 服务器上搭建git仓库
- 网络基础之计算机网络参考模型(OSI参考模型与TCP/IP协议簇)
- Java 8中的Base64编码和解码
- 转载:程苓峰:跨国B2C,围猎阿里
- ubuntu vi编辑器按i,左下方没有出现“insert”