大家好,小编来为大家解答以下问题,js必须掌握的知识点,js重要知识点,今天让我们一起来看看吧!
一、数据类型
1. 变量
- 用来存放数据,保存的数据可以修改
var 、 let
声明的就是变量
2. 常量
- 用来存放数据,保存的数据不可修改
const
声明的就是常量- const声明常量的时候必须
赋初始值
- const 声明的值类型是不允许重新赋值的
- const 声明的
引用类型
,在不进行重新赋值的前提下,可以进行修改
3. 变量和常量的本质
- 无论是变量还是常量,其本身都是
数据
,也需要在内存中占用内存空间,保存在内存的栈结构分区中
4. 面试:let、const、var的区别
- let 和 const 不允许
重复声明
,var是可以重复声明的 - let 和 const 没有
变量提升
,必须先声明后使用;var 存在变量提升,可以先使用后声明 - let 和 const 有
块级作用域
,var没有
5. JS数据类型
5.1 基本(值)类型
- Number
- String
- Boolean
- null
- undefinded
- Symbol:唯一的数据
- BigInt:任意精度的整数
undefined 和 null 的区别
- undefined:定义了但没有赋值;或者没有return的函数
- null:定义了且赋值为null
初始值赋值为null,表明现在数据不明,但将来会赋值为对象
结束时赋值为null:让对象成为垃圾对象,被垃圾回收器回收
- 值类型的传递 —— 传递值
- 基本数据类型的值在
栈空间
中存储,如果修改了数据,则是把原来的值直接干掉,重新存放新的值
5.2 引用类型(object)
- Object
- Function
- Array
- Date
- RegExp
当Number、String、Boolean调用方法或属性时,就会变为
基本包装类型
(引用类型)
-
引用类型的传递 —— 传递的是引用(地址)
-
引用数据类型的对象在
堆空间
中存储,该空间的地址在栈空间中存储,如果修改栈空间存储的地址,则指向发生变化,也叫引用发生了变化,此时是在堆空间中重新指向了一个新的内存空间(存储了一个新的对象)
6. typeof 运算符
语法:
typeof 变量名
typeof(变量名)
- 判断变量中存储的
数据类型
- typeof可以判断出的类型:String、Number、Boolean、undefined、Symbol、function
let a = 10
console.log(typeof a) //number
let b = 'hha'
console.log(typeof b) // string
let c = true
console.log(typeof c) // boolean
let d = Symbol(23)
console.log(typeof d) // symbol
let e
console.log(typeof e) // undefined
let fun = function() {}
console.log(typeof fun) // function
- typeof只能判断出是引用类型(object),但判断不出具体是哪种引用类型
- 即:
typeof不能判断 object和array、object和null
let f = null
console.log(typeof f) // object
let obj = {
name:'dudu'
}
console.log(typeof obj) // object
let arr = [2,3,12]
console.log(typeof arr) // object
let date = new Date()
console.log(typeof date) // object
7. instanceof 运算符
语法
对象 instanceof 类型
- 判断当前实例对象是否属于某种数据类型
- 判断右侧函数的显示原型是否在左侧对象的原型链上
8. === 运算符
- ===:称为
等同符
,当两边值的类型相同时,直接比较值,若类型不相同,直接返回false - == :称为
等值符
:当等号两边的类型相同时,直接比较值是否相等,若不相同,则先转化为类型相同的值,再进行比较
类型转换规则:
- 如果等号两边是boolean、string、number三者中任意两者进行比较时,优先转换为数字进行比较。
- 如果等号两边出现了null或undefined,null和undefined除了和自己相等,就彼此相等
注意:NaN==NaN //返回false NaN不等于任何值
除了判断是否等于 ==null 外,其他一律用 ===
二、对象
1. 对象是什么
- 生活中的对象:看得到、摸得着的具体的某个事物,如:正在看视频的这个手机
- 对象(object)是键值对(k:v)的集合,表示属性和值得映射关系
2. 对象的创建方式
2.1 字面量的形式
let obj = {
name:'哈哈',
age:10
}
2.2 new Object() 的形式
let obj = new Object()
obj.name = '嘟嘟'
2.3 工厂函数的形式
function createObject(name,age){
var obj = new Object()
obj.name = name
obj.age = age
return obj
}
var obj1 =createObject('小明',10)
var obj2 =createObject('小红',20)
console.log(obj1,obj2)
2.4 构造函数的形式
function Person(name,gender){
this.name = name
this.gender = gender
this.sayHi=function(){
console.log('您好,我是:'+this.name)
}
}
var per = new Person('小明','男')
per.sayHi()
console.log(per)
- new 做的四件事
- 函数体内部自动创建一个空白对象
- 函数的上下文(this)会指向这个对象
- 函数体内的语句会执行
- 函数会自动返回上下文对象,即使函数没有return语句
2.5 通过 class 创建对象
- class 形式中,
构造器内
的属性都是在实例上
的 - class中的方法,如果是通过
赋值符号
定义的,则该方法在实例
上,否则该方法是在原型上
class Student{
// 构造器,构造器中的属性都是在实例上的
constructor(name,age,gender){
this.name = name
this.age =age
this.gender = gender
}
// 在原型上
sayHi(){
console.log(`您好,我是${this.name},今年${this.age}岁了,是${this.gender}生`)
}
// 在实例上
eat=()=>{
console.log('吃东西啊')
}
}
// 实例化
const stu = new Student('dudu',18,'女')
stu.sayHi()
stu.eat()
console.log(stu)
2.6 单例模式创建对象
- 不管对象创建了多少次,但最终的对象都只有一个
- 应用:轮播图插件、better-scroll插件
function createObj() {
let instance = null
return function(name) {
// 如果对象不存在,则新创建一个,如果已经存在则直接返回
if(!instance) {
// 创建一个对象
instance = new Object()
// 初始化对象属性
instance.name = name
}
return instance
}
}
var getObj = createObj()
var obj1 = getObj('小明')
var obj2 = getObj('小红')
console.log(obj1,obj2) // {name:小明},{name:小明}
console.log(obj1===obj2) // true
- truly变量: !!a === true 的变量 ( 两次取反后为true )
- falsely 变量:!!a === false 的变量
falsely 变量:0、NaN、''、null、undefined、false
- 除以上falsely 变量之外都是 truly 变量
3. 对象的调用属性的方法
-
对象.属性或方法名字
-
对象['属性或方法名']
什么时候使用
对象[属性名字]
的写法- 不确定属性名字是什么(
属性名字是变量
),此时方括号内部的属性名不加引号
- 属性名
不符合js命名规范
的,此时方括号内部的属性名必须加单引号
- 不确定属性名字是什么(
4. 面试:谈谈你对面向对象的理解
- 面向对象是一种编程的思想
- 与之对应对应的是面向过程
- 面向过程讲究的是凡是都需要自己亲力亲为,注重的是过程
- 面向对象具有封装、继承、多态等特性
- JS是属于基于面向对象的一门语言
- 面向对象就是提出需求,然后根据需求分析出需要的属性和方法,然后抽取并定义出相应的构造函数,最终实例化这个对象,通过对象点方法或属性来使用
三、原型
1. 执行函数定义和执行函数
- 执行函数定义:执行函数的
定义
,包含函数声明或者函数表达式两种形式的定义 - 执行函数:函数
内部的语句
被执行 - 先有函数定义的执行,才有执行函数
/**
* 定义函数(函数声明+函数内部语句)
*/
// 函数定义方式一 :函数声明
// 执行函数定义
function fun() {
console.log('我是一个方法')
}
// 函数的调用 —— 执行函数(函数内部的语句被执行)
fun()
// 函数定义方式二:函数表达式
const fun1 = function() {}
常见的函数回调
- DOM事件的回调
- 定时器中的回调
- ajax回调函数
- 生命周期回调函数
- IIFE(立即执行函数 | 匿名函数自调用):隐藏内部实现,减少命名空间污染
2. 原型的产生
- 在
执行函数定义
时就会产生显示原型(prototype)
,而函数本身也是一个实例对象,所以同时也会产生隐式原型(__ proto__)
【注:谷歌中的__proto__显示为[[prototype]]
】
function fun() {
console.log('我是一个方法')
}
console.dir(fun)
3. 面试:谈谈你对原型的理解
- 原型包含prototype 和 __ proto__,原型可以节省内存空间、实现数据共享、实现继承
- 在执行函数定义的时候,显示原型prototype就会被创建,在实例化对象的时候,隐式原型 __ proto__就会被创建
- 因为函数本身也是对象,所以函数除了显示原型prototype外,也拥有隐式原型__ proto__
4. 原型是什么
- 在创建函数时,解析器会自动为函数添加一个属性
prototype(原型)
,这个属性对应着一个对象称为原型对象
- 原型是函数所特有的,任何函数对象都有
显示原型prototype
- 如果函数作为普通函数调用,则prototype无意义
- 若作为构造函数调用,它所创建的对象(实例)中都会有一个隐含的属性,指向该构造函数的原型对象,可以通过
__ proto__ (隐式原型)
访问该隐含属性
function Fn() {}
var obj = {}
// obj是对象(object),会有隐式原型__proto__,所以obj的__proto__和Object的prototype相等
console.log(obj.__proto__ === Object.prototype) //true
console.log(Fn.prototype.__proto__ === Object.prototype) //true
- prototype:浏览器的标准属性,程序员使用的,显示原型,存在于函数中
- __proto __:浏览器的非标准属性,浏览器使用的,隐式原型,存在于实例对象中
- __ ptoto__是在实例化对象时出现的
- 实例的 __ proto__ 与对应函数的 prototype 都指向原型对象
5. 原型的作用
5.1 共享数据,节省内存空间
function Person(name,age) {
this.name = name
this.age = age
this.eat = function(){
console.log(`我是${name}实例中的eat方法`)
}
}
Person.prototype.sayHi = function() {
console.log('我是原型上的方法')
}
const dudu = new Person('嘟嘟',16)
const heihei = new Person('黑黑',19)
dudu.eat()
heihei.eat()
dudu.sayHi()
heihei.sayHi()
- 在构造函数中定义的属性及方法,仅仅是编写代码进行定义而已,而实际上里面定义的属性及方法是属于每个实例对象的
- 所以,创建多个对象,就会开辟多个空间,每个空间中的每个对象都有自己的属性及方法,大量创建对象,对象的方法都不是同一个方法(方法也是函数,函数代码也占用空间)
- 通过将属性或方法绑定在原型上,就可以实现所有实例的数据共享,进而节约内存空间
5.2 通过改变原型实现继承
方法的继承
关键语句:子类.prototype = new 父类()
,这样可以使用父类的方法
- 如果子类要定义自己的方法,则应该写在关键语句之后
// 父类
function Person(name,age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function() {
console.log(`您好,我是${this.name},今年${this.age}岁了`)
}
// 子类
function Student(name,age,studentID) {
this.name = name
this.age = age
this.studentID = studentID
}
// 实现继承的关键语句
Student.prototype = new Person('黑黑',23)
const stu = new Student('嘟嘟',18,134287)
stu.sayHi() //您好,我是嘟嘟,今年18岁了
继承属性
:父类名.call(要继承的属性名)
,这样就可以使用父类的属性
// 父类
function Person(name,age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function() {
console.log(`您好,我是${this.name},今年${this.age}岁了`)
}
// 子类
function Student(name,age,studentID) {
Person.call(name)
Person.call(age)
this.studentID = studentID
}
// 实现继承的关键语句
Student.prototype = new Person('黑黑',23)
const stu = new Student('嘟嘟',18,134287)
stu.sayHi() // 您好,我是黑黑,今年23岁了
- 组合继承:方法继承和属性继承统称为组合继承
6. 原型链
- 原型链:隐式原型链,从对象的__ proto__开始,连接所有对象
- JavaScript规定:
实例可以打点访问它的原型的属性和方法
,这被称为“原型链查找
” - 当访问对象的一个属性或方法时,它会优先在对象自身寻找,如果没有则会去原型对象找
6.1 hasOwnProperty()
- 使用
in
检测对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
console.log('sayHi' in stu) // true
- 使用
hasOwnProperty()
检测对象自身是否含有某属性,只有对象自身含有该属性才会返回true
console.log(stu.hasOwnProperty('sayHi')) // false
6.2 原型链的终点
- 原型对象也是对象,所以也有原型
- 当使用一个对象的属性或方法时,首先会先在对象自身寻找
- 当自身没有,就沿着__ proto__ 在原型对象中寻找
- 如果原型对象中没有,则会寻找原型的原型中,直到找到Object对象的原型(一般只会查找两层)
Object对象
的原型没有原型,如果在Object中仍然没有找到,则返回undefined
6.4 原型对象的constructor
- prototype属性值是对象,它默认拥有
constructor属性
指向对应的构造函数
function sum(a,b) {
return a+b
}
console.log(sum.prototype.constructor === sum) //true
7. instanceof
- 判断对象的具体类型
- 所有的函数都是 Function 的实例对象,所以都会有隐式原型 __proto __
- 所有的对象都是 Object 的实例对象
A instanceof B
- A是实例对象,B是构造函数
- 如果B的 prototype 属性所指向的原型对象是 A 原型链上的某个对象,则返回 true ,否则返回 false
// 构造函数
function Foo() {}
// 实例对象
const f1 = new Foo()
// 实例对象
const f2 = new Foo()
// 实例对象
const o1 = new Object()
// 实例对象
const o2 = {}
console.log(Foo instanceof Object); // true
console.log(Foo instanceof Function); // true
console.log(Object instanceof Object); // true
console.log(Function instanceof Function); // true
console.log(Object instanceof Foo); // flase
console.log(f1 instanceof Function); // false
console.log(f1 instanceof Object); // true
8. 继承(改变原型指向 | 借用构造函数 | 组合 | 拷贝)
8.1 基于构造函数的继承:原型链 + 借用构造函数的组合继承
- 借用父类型构造函数(继承属性):
父类.call()
- 让子类的原型为父类的实例(继承方法):
子类.prototype = new 父类()
- 让子类型原型的构造器为子类型:
子类.prototype.constructor = 子类
8.2 基于 class/类 的继承
- 通过extends 关键字实现继承:
class 子类 extends 父类
- 继承父类的属性:
super(属性名,属性名1,属性名2,...)
四、预解析与作用域
1. JS中的预解析
- 先找关键字 var 、function函数声明
- 找到var 后将 var 后的变量提前声明,但是不赋值(变量提升)
- 找到 function后将function后面的函数提前声明,但是不赋值,即函数在解析之前已经定义完毕了(函数提升)
- 函数表达式的定义提升时会报错
fun()
// 函数表达式
const fun = function() {}
2. 执行上下文
- 执行上下文(动态的):就是一个代码的执行环境(全局执行上下文和函数执行上下文,eval函数执行上下文),包含:执行环境,变量对象,this,作用域链
- 执行上下文的流程:
- js引擎在js代码正式执行前会先创建一个执行环境
- 进入该环境以后会创建一个变量对象,该对象用于收集:变量,函数,函数的参数,this
- 找关键字var,function
- 确认this
- 创建作用域链
- 在全局代码执行前,js引擎就会创建一个栈来存储管理所有的执行上下文
- 在全局执行上下文(window)确定后,将其添加到栈中(压栈)
- 在函数执行上下文创建后,将其添加到栈中(压栈)
- 在当前函数执行完毕后,将栈顶的对象移除(出栈)
- 当所有的代码执行完毕后,栈中只剩下window
- 重点:执行上下文是动态创建的,尤其是针对函数,每调用一次函数都会创建一次执行上下文
面试:谈谈你对执行上下文的理解
- 当代码要执行,但是没有执行,或者将要执行,在预解析之后,
- 此时出现了全局执行上下文环境(全局执行上下文),
- 创建了一个变量对象,用来收集var , function ,函数参数,确定this的指向,
- 默认全局执行上下文是确定了this是window,
- 这个变量对象会被压入到栈中(全局执行上下文的变量对象在栈中的最下面),
- 如果出现了函数调用,此时出现了局部执行上下文环境(局部执行上下文),
- 再次创建一个变量对象,用来收集函数参数,var ,function,改变this的指向,
- 这个变量对象会被再次压入栈中,在全局执行上下文的变量对象的上面,
- 如果当前函数调用完毕,此时出栈(把局部上下文的变量对象干掉),依次弹出变量对象,就结束了
3. 作用域
3.1 作用域
- 作用域:某个变量的合法使用范围
全局作用域
:变量在函数外定义,即为全局变量
GPT改写。全局变量有 全局作用域: 网页中所有脚本和函数均可使用- window对象下的内置属性都是全局作用域
函数作用域
:写在函数内部的变量,就只能在函数内部使用块级作用域
:写在花括号 {} 内部的变量- 常见的块级作用域:
function(){}、for(){}、while(){}、do{}while()、if(){}、switch(){}
- 对象虽然有花括号,但不是块级作用域
当代码书写完毕后,全局作用域就被确定了
在执行代码之前,会先预解析,此时全局的执行上下文出现
调用函数之前,局部作用域就确定了,局部作用域中预解析执行,此时局部的执行上下文就确定了
3.2 自由变量
- 一个变量在当前作用域内没有定义,但被使用了
- 向上级作用域,一层一层的依次寻找,直到找到为止
- 如果在全局作用域中也没有找到,则报错 xxx is not defined
自由变量的查找,是在函数定义的地方,向上级作用域查找,而不是在执行的地方查找!
五、 闭包
1. 闭包产生的条件
- 函数之间存在嵌套关系
- 调用了外部函数
- 内部函数引用了外部函数的数据(变量/函数)
- 则内部函数在
执行函数定义
时,就产生了闭包。
- 闭包也是对象
2. 闭包的作用
- 延长了局部作用域的生命周期,但如果不及时释放会出现内存泄露
- 让函数外部可以操作(读写)函数内部数据(变量/函数)
3. 内存泄露 和 内存溢出
- 内存泄露:程序在申请内存后,无法释放已申请的内存空间就会造成内存泄露。虽然一次泄露不会有太大影响,但内存泄露堆积的后果就是内存溢出
- 内存溢出:程序在申请内存时,没有足够的内存供申请者使用
4. 闭包的释放
- 让内部函数成为垃圾对象,断开指向它的所有引用(比如将外部函数赋值为null)
5. 常见的闭包
- 闭包是作用域应用的特殊情况,有以下两种情况(函数定义和执行的地方不一致)
- 函数作为参数被传递
- 函数作为返回值被返回
- 闭包的特性:函数会记住定义时所处的环境,查找变量时会从定义时的位置开始向上级查找
6. 闭包的应用
- 如在删除列表中某个商品时
7. 面试:谈谈你对闭包的理解
答题方向:闭包的产生、是什么、优缺点、应用
- 如果函数之间存在嵌套关系,外部函数被调用,且内部函数中使用了外部函数的数据(变量或函数),此时在内部函数执行函数定义时,就会产生闭包
- 闭包就相当于是一个容器对象
- 闭包延长了局部函数的声明周期,让函数外部可以操作函数内部的数据
- 但闭包如果不及时释放,就会造成内存泄露
- 常见的闭包包含将函数作为返回值或者参数进行传递
- 比如,在后台管理系统中经常会用到的删除操作,当需要删除某个商品时,需要传入商品id
六、 this指向
1. 判定规则
- 规则一:
对象打点调用函数时,this是打点的对象
对象.方法()
var obj1 = {
a:1,
b:2,
fn() {
console.log(this.a+this.b)
}
}
var obj2 = {
a:3,
b:4,
fn:obj1.fn
}
obj2.fn() // obj2调用的,所以this是obj2。然后函数也是引用类型,进行赋值的时候会指向同一个堆,所以会打印出 7
function outer() {
var a = 20
var b = 12
return {
a:33,
b:44,
fn() {
console.log(this.a+this.b)
}
}
}
outer().fn() // outer()返回一个对象,所以最后也是构成了对象.方法的调用,this就会指向返回的对象,所以会打印出77
- 规则二:
圆括号直接调用函数,则this是window对象
函数()
var obj1 = {
a:1,
b:2,
fn() {
console.log(this.a+this.b)
}
}
var a = 3
var b = 4
var fn = obj1.fn
fn() //通过函数直接调用,this指向window对象,则结果会返回7
function fn() {
return this.a + this.b
}
var a = 1
var b =2
var obj = {
a:3,
b:fn(),
fn:fn
}
var result = obj.fn() //通过对象的形式调用,this指向obj对象,a=3,b:fn()相当于全局调用了fn方法,b中的this指向window,所以b=3
console.log(result) // 6
- 规则三:
数组(类数组对象)枚举出函数进行调用 ,this是这个数组(类数组对象)
数组 [ 下标 ] ( )
类数组对象
:所有的键名为自然数序列(从0开始)且有length属性的对象arguments对象
是最常见的类数组对象,它是函数的实参列表
function fun() {
arguments[3]()
}
fun('A','B','C',function() {
console.log(this[1]) // this指向arguments类数组对象'A','B','C',function(),结果输出B
})
4 规则四:IIFE中的函数,this是window对象
(function() {
…
})()
var a = 1
var obj = {
a:2,
fun:(function(){
var a = this.a; //IIFE中的this指向window,则a=1
return function() {
// a 在函数体内部,则a=1
// return 返回了一个函数
console.log(a + this.a) // 3
}
})()
}
obj.fun() //点的方式调用函数,则this指向obj对象,则this.a=2
- 规则五:
定时器、延时器调用函数,this是window对象
setInterval(函数,时间)
setTimeout(函数,时间)
var obj = {
a:1,
b:2,
fun() {
console.log(this.a + this.b)
}
}
var a = 3
var b = 4
setTimeout(obj.fun, 2000); //this指向window,则this.a=3,this.b=4
setTimeout(() => {
obj.fun() //对象点方法的形式调用函数,则this指向obj对象,this.a=1、this.b=2
}, 2000);
- 规则六:
事件处理函数的this是绑定事件的DOM元素
DOM元素.onclick = function() {
//当函数内部有其他函数,比如定时器时,要考虑this的指向
// 必要时需要备份this,即 var _this = this,这时在内部的函数中就可以用_this指向需要的DOM
}
- 规则七:
call和apply可以指定 this 的指向
函数.call(上下文)
函数.apply(上下文)
-
规则八:
构造函数中,this指向实例对象
-
规则九:
箭头函数中,this会向上级作用域寻找
2. 如何控制函数的 this
- 利用函数的 bind()
- 利用箭头函数
- 利用外部保存了this的变量
七、异步 和 单线程
- 同步:必须等待上一步执行完毕后,下一步才可以执行
- 异步:在等待上一步执行的同时,可以进行其他处理
- 进程:程序的一次执行,它占有一片独有的内存空间
- 线程:CPU的基本调度单位,是程序执行的一个完整流程
- JS是单线程的
1. 同步任务和异步任务
1. 同步任务
非耗时任务
,指主线程上排队执行的任务- 同步会阻塞代码执行
- 只有前一个同步任务执行完成,才能进行下一个同步任务
- 构造函数属于同步任务
2. 异步任务 耗时任务
,指由js委托给**宿主环境
**(js的执行环境,如浏览器、node.js等)进行执行- 异步不会阻塞代码
- 当异步任务执行完成后,会通知JavaScript主线程执行异步任务的**
回调函数 callback
**
2.宏任务和微任务
异步任务
分为宏任务和微任务
2.1 宏任务
- 异步Ajax请求
- setTimeout、setInterval
- DOM 事件监听
- 文件操作
- 其他宏任务
宏队列
:用来存放宏任务的容器
2.2 微任务
- Promise.then、.catch、.finally
- process.nextTick
- async、await
- 其它微任务
微队列
:用来存放微任务的容器
2.3 执行顺序
- 页面第一次渲染:初始化同步代码 ==> 所有的微任务 ==> 渲染界面 ==> 执行第一个宏任务 ==> 所有的微任务 ==> 渲染界面 ==> 执行第一个宏任务
- 界面更新渲染:所有的微任务 ==> 界面渲染 ==> 第一个宏任务
3. 事件轮询机制(event loop,异步实现的原理)
- JS是通过事件轮询机制来实现单线程异步的
4. 面试:谈谈JS的执行机制
- JS是单线程的,是同步去执行的,但是JS也可以通过事件轮询机制进行异步操作
- 首先会顺序执行所有的同步任务,异步任务会被委托给宿主环境
- 已完成的异步任务对应的回调函数,会被放到任务队列中排队
- JS再从任务队列中读取异步任务的回调,但会优先读取所有的微任务
- 这个过程会不断重复
5. Promise
- Promise 可以解决回调地狱问题(可阅读性差,任然需要回调,但是不会嵌套)
- Promise指定回调的时机更加灵活(可以指定在异步操作启动后或完成后)
5.1 promise的理解
- promise是一个**
构造函数
**,可以通过new创建一个实例,new出来的Promise实例对象就代表一个异步操作 - promise的
原型对象(prototype)
上包含一个.then()
方法 then()方法用来指定成功和失败的回调函数
fun.then(成功的回调,失败的回调)
5.2 promise 的状态
- pending:进行中
- fulfilled/resolved:成功
- rejected:失败
状态的改变不可逆,只能从进行中变为成功或失败
5.3 promise.then的理解
- then() 会返回一个新的 promise
- 新promise的结果状态是由then指定的回调函数执行结果决定的
- 抛出错误:失败的reason就是抛出的错误
- 返回失败的promise
- 返回成功的promise
- 返回其他值
- 返回一个非Promise值
- 抛出错误的情况
new Promise((resolve,reject) => {
// 成功的回调
// resolve('成功!')
// 失败的回调
reject('失败~')
}).then(val => {
console.log(val)
},err => {
console.log(err)
// 抛出错误
throw 100
}).then(val => {
console.log('success '+val)
},err => {
console.log('failed '+err)
})
- 返回失败的回调
new Promise((resolve,reject) => {
// 成功的回调
// resolve('成功!')
// 失败的回调
reject('失败~')
}).then(val => {
console.log(val)
},err => {
console.log(err)
// 抛出错误
// throw 100
// 返回失败的回调
return Promise.reject(200)
}).then(val => {
console.log('success '+val)
},err => {
console.log('failed '+err)
})
- 返回成功的回调
new Promise((resolve,reject) => {
// 成功的回调
// resolve('成功!')
// 失败的回调
reject('失败~')
}).then(val => {
console.log(val)
},err => {
console.log(err)
// 抛出错误
// throw 100
// 返回失败的回调
// return Promise.reject(200)
// 返回成功的回调
return Promise.resolve(300)
}).then(val => {
console.log('success '+val)
},err => {
console.log('failed '+err)
})
- 返回其他值
new Promise((resolve,reject) => {
// 成功的回调
// resolve('成功!')
// 失败的回调
reject('失败~')
}).then(val => {
console.log(val)
},err => {
console.log(err)
// 抛出错误
// throw 100
// 返回失败的回调
// return Promise.reject(200)
// 返回成功的回调
// return Promise.resolve(300)
// 返回其他值
return 1000
}).then(val => {
console.log('success '+val)
},err => {
console.log('failed '+err)
})
5.4 Promise.all()方法
Promise.all([Promise1,Promise2,…]) 方法可以把多个Promise实例包装为一个新的Promise实例
只有需要包装的多个Promise实例状态都为成功,新的Promise状态才为成功(等待机制)
const promiseArr = [
thenFs.readFile('./files/1.txt','utf-8'),
thenFs.readFile('./files/2.txt','utf-8'),
thenFs.readFile('./files/3.txt','utf-8')
]
Promise.all(promiseArr)
.then(result => {
console.log(result) //[ '第一个文件', '第二个文件', '第三个文件' ]
})
5.5 Promise.race()方法
Promise.race([promise1,promise2,…])用法和all()类似,
但是只要其中任何一个异步完成,就会立即执行下一步的.then()操作(赛跑机制)
Promise.race(promiseArr)
.then(res => {
console.log(res) //第一个文件
})
6. 谈谈对Promise的理解
- promise可以用来解决回调地狱的问题,promise指定回调的时机是灵活的,可以指定在异步操作启动后或者完成后
- promise有pending执行中、resolved成功、reject失败三种状态,且只能从进行中到成功或失败,是不可逆的
- promise的then会返回一个新的promise
7. async/await
- async/await是ES8引入的新语法,
用于简化Promise异步操作
- 如果某个方法返回的是Promise实例,就可以使用await
async function getAllFile() {
const res1 = await thenFs.readFile('./files/1.txt','utf8')
console.log(res1)
const res3 = await thenFs.readFile('./files/3.txt','utf8')
console.log(res3)
const res2 = await thenFs.readFile('./files/2.txt','utf8')
console.log(res2)
}
getAllFile()
- 如果方法内部使用了await则该方法必须被async修饰
- 在async方法中,第一个await之前的方法会同步执行,await之后的方法会异步执行