JS原理课
知识点自测
今天课程中涉及到的已学习知识点
- 函数的
call方法-文档链接
// 以指定的this调用函数,并通过 从第二个参数开始依次传递参数
function func(food,drink){
console.log(this)
console.log(food)
console.log(drink)
}
const obj = {
name:'小黑'
}
func.call(obj,'西蓝花','咖啡')
- 函数的
apply方法-文档链接
// 以指定的this调用函数,并通过 数组的形式 传递参数
function func(food,drink){
console.log(this)
console.log(food)
console.log(drink)
}
const obj = {
name:'小黑'
}
func.apply(obj,['西蓝花','咖啡'])
- 函数的
bind方法-文档链接
function func(food, drink) {
console.log(this)
console.log(food)
console.log(drink)
}
const obj = {
name: '小黑'
}
const bindFunc = func.bind(obj, '花菜')
bindFunc('可乐')
- 剩余参数-文档链接
function func(...args){
console.log(args)// 以数组的形式获取传入的所有参数
}
func('西蓝花','西葫芦','西洋参','西芹')
- Promise核心用法-文档链接
const p = new Promise((resolve, reject) => {
setTimeout(() => {
const num = parseInt(Math.random() * 10)
if (num > 5) {
resolve(`成功啦--${num}`)
} else {
reject(`失败啦--${num}`)
}
}, 1000)
})
p.then(res => {
console.log(res)
}, err => {
console.log(err)
})
- URLSearchParams核心用法-文档链接
// 实例化时支持传入JS对象
const params = new URLSearchParams({ name: 'jack', age: 18 })
// toString方法 返回搜索参数组成的字符串,可直接使用在 URL 上。
console.log(params.toString())
- Object.create核心用法-文档链接
const person = {
name: 'itheima',
foods: ['西蓝花', '西红柿', '西葫芦']
}
// 将传入的对象作为原型,创建一个新对象(浅拷贝)
const clone = Object.create(person)
clone.name = 'itheima'
clone.foods.push('西北风')
console.log(clone.foods === person.foods)// true
- Object.assign核心用法-文档链接
const person = {
name: 'itheima',
foods: ['西蓝花', '西红柿', '西葫芦']
}
const son = {
name: 'rose',
}
// 参数1 目标对象
// 参数2 源对象
// 将源对象的自身属性复制到目标对象,并返回目标对象
const returnTarget = Object.assign(son, person)
console.log(returnTarget === son)// true
console.log(son.name)// itheima
console.log(son.foods === person.foods)// true
JS中的this
这一节咱们来学习JS中this相关的知识点
这一节咱们会学习的有:
- 如何确认
this指向 - 如何改变
this指向 - 手写
call,apply,bind
如何确认this指向:
在绝大多数情况下,函数的调用方式决定了 this 的值(运行时绑定)
谁调用就是谁,直接调用就是window
// 案例1
function func() {
console.log(this) // ?
}
func()
// 案例2
const person = {
name: 'jack',
sayHi: function () {
console.log(this)// ?
function inner() {
console.log(this)// ?
}
inner()
},
sayHello(){
console.log(this)// ?
setTimeout(function(){
console.log(this)// ?
})
}
}
person.sayHi()
person.sayHello()
小结:
如何确认this指向
- 在绝大多数情况下,
this的指向由什么决定?
如何改变this指向
主要有2类改变函数内部this指向的方法:
-
调用函数并传入具体的
this:-
call:- 参数1:
this - 参数2-n:传递给函数的参数
- 参数1:
-
apply-数组作为参数- 参数1:
this - 参数2:以数组的形式,传递给函数的参数
- 参数1:
-
-
创建绑定
this的函数:- bind:返回一个绑定了
this的新函数 - 箭头函数:最近的this是谁,就是谁
- bind:返回一个绑定了
调用函数并传入具体的this:
function funcA(p1, p2) {
console.log('funcA-调用')
console.log(this)
console.log('p1:', p1)
console.log('p2:', p2)
}
const obj = {
name: 'jack'
}
// call参数
// 参数1 this值
// 参数2-参数n 挨个传入函数的参数
funcA.call(obj, 1, 2)
// apply参数
// 参数1 this值
// 参数2 以数组的形式传入函数的参数
funcA.apply(obj, [3, 4])
创建绑定this的函数:
function funcB(p1, p2) {
console.log('funcB-调用')
console.log(this)
console.log('p1:', p1)
console.log('p2:', p2)
}
const person = {
name: 'itheima'
}
// bind参数
// 参数1 this值
// 参数2-参数n 绑定的参数
const bindFuncB = funcB.bind(person, 123)
bindFuncB(666)
const student = {
name: 'lilei',
sayHi: function () {
console.log(this)
// 箭头会从自己作用域链的上一层继承this
const inner = () => {
console.log('inner-调用了')
console.log(this)
}
inner()
}
}
student.sayHi()
小结:
如何改变this指向
-
如何在调用函数并传入具体的
this?- call:
- apply:数组作为参数
-
如何创建绑定
this的函数?- bind
- 箭头函数
手写call方法
这一节咱们来实现myCall方法,实际用法和call方法一致,核心步骤有4步
// 实现myCall 可以实现如下的调用效果
const obj2 = {
name: '我是小小黑'
}
function func2(drink, food) {
console.log(`我叫${this.name},我喜欢喝${drink},我爱吃${food}`)
}
// 参数1:this
// 参数2-参数n:参数列表
func2.myCall(obj2, '咖啡', '西兰花炒蛋')
- 如何定义
myCall? - 如何让函数内部的
this为某个对象? - 如何让
myCall接收参数2-参数n? - 使用Symbol调优
myCall?
// 1. 如何定义`myCall`
Function.prototype.myCall = function () {
// 逻辑略
}
// 2 如何让函数内部的`this`为某个对象
Function.prototype.myCall = function (thisArg) {
// this 是调用myCall的 函数
// thisArg 指定的this
// 为他添加一个自定义属性,让函数成为他的该属性
thisArg['fn'] = this
// 调用并获取结果
const res = thisArg['fn']()
// 移除添加的自定义属性
delete thisArg['fn']
// 返回调用结果
return res
}
// 3 如何让`myCall`接收参数2-参数n
Function.prototype.myCall = function (thisArg, ...args) {
thisArg['fn'] = this
// 调用并获取结果
const res = thisArg['fn'](...args)
// 移除添加的自定义属性
delete thisArg['fn']
// 返回调用结果
return res
}
// 4 使用`Symbol`调优`myCall`
Function.prototype.myCall = function (thisArg, ...args) {
// 使用Symbol生成唯一标记,避免和原属性冲突
const fn = Symbol()
thisArg[fn] = this
const res = thisArg[fn](...args)
// 移除添加的自定义属性
delete thisArg[fn]
// 返回调用结果
return res
}
// 测试代码
const obj2 = {
name: '我是小小黑'
}
function func2(drink, food) {
console.log(`我叫${this.name},我喜欢喝${drink},我爱吃${food}`)
}
func2.myCall(obj2, '咖啡', '西兰花炒蛋')
小结:
手写call方法
- 如何定义
myCall? - 如何让函数内部的
this为某个对象? - 如何让
myCall接收参数2-参数n? - 使用
Symbol调优myCall?
Function.prototype.myCall = function (thisArg, ...args) {
const fn = Symbol()
thisArg[fn] = this
const res = thisArg[fn](...args)
delete thisArg[fn]
return res
}
手写apply方法
这一节咱们来实现myApply方法,实际用法和apply方法一致,核心步骤依旧4步
// 实现myApply 可以实现如下的调用效果
const obj2 = {
name: '我是小小黑'
}
function func2(drink, food) {
console.log(`我叫${this.name},我喜欢喝${drink},我爱吃${food}`)
}
// 参数1:this
// 参数2:数组形式传入的参数列表
func2.myApply(obj2, ['咖啡', '西兰花炒蛋'])
- 如何定义
myApply? 函数Function的原型上 - 如何让函数内部的
this为某个对象?给对象动态增加方法,方法为原函数,通过对象调用即可 - 如何让
myApply接收数组形式的参数列表?定义参数2,接收数组即可 - 使用Symbol调优
myApply?和原对象的属性重名
// 1. 如何定义`myApply`
Function.prototype.myApply = function () {
// 逻辑略
}
// 2 如何让函数内部的`this`为某个对象
Function.prototype.myApply = function (thisArg) {
// 为他添加一个自定义属性,让函数成为他的该属性
thisArg['fn'] = this
// 调用并获取结果
const res = thisArg['fn']()
// 移除添加的自定义属性
delete thisArg['fn']
// 返回调用结果
return res
}
// 3 如何让`myApply`接收参数2-参数n
Function.prototype.myApply = function (thisArg, args) {
thisArg['fn'] = this
// 调用并获取结果
// 用... 将args展开传入
const res = thisArg['fn'](...args)
// 移除添加的自定义属性
delete thisArg['fn']
// 返回调用结果
return res
}
// 4 使用`Symbol`调优`myApply`
Function.prototype.myApply = function (thisArg, args) {
// 使用Symbol生成唯一标记,避免和原属性冲突
const fn = Symbol()
thisArg[fn] = this
const res = thisArg[fn](...args)
// 移除添加的自定义属性
delete thisArg[fn]
// 返回调用结果
return res
}
// 测试代码
const obj2 = {
name: '我是小小黑'
}
function func2(drink, food) {
console.log(`我叫${this.name},我喜欢喝${drink},我爱吃${food}`)
}
func2.myApply(obj2, ['咖啡', '西兰花炒蛋'])
小结:
手写apply方法
- 如何定义
myApply? 函数的原型上 - 如何让函数内部的
this为某个对象? 动态给对象添加方法,通过对象的方式调用方法 - 如何让
myApply接收数组形式的参数列表? 形参: args,调用时,...args - 使用Symbol调优
myApply?避免和默认属性重名
Function.prototype.myApply = function (thisArg, args) {
const fn = Symbol()
thisArg[fn] = this
const res = thisArg[fn](...args)
delete thisArg[fn]
return res
}
手写bind方法
这一节咱们来实现myBind方法,实际用法和bind方法一致,核心步骤为2步
const obj = {
name: '我是小小黑'
}
function func(drink, food) {
console.log(`我叫${this.name},我喜欢喝${drink},我爱吃${food}`)
}
// 调用bind获取绑定this的新函数,参数1为可乐
const bindFunc = func.bind(obj, '可乐')
// 调用函数,只需要传递参数2即可
bindFunc('西蓝花炒蛋')
- 如何返回一个绑定了
this的函数? - 如何实现绑定的参数,及传入的参数合并?
// 1 如何返回一个绑定了`this`的函数
Function.prototype.myBind = function (thisArg) {
// myBind函数调用时,this就是函数本身
return () => {
// 通过call方法将传入的 thisArg 作为this进行调用
this.call(thisArg)
}
}
// 2 如何实现绑定的参数,及传入的参数合并
// ...args 接收绑定参数
Function.prototype.myBind = function (thisArg, ...args) {
// ...args2 接收调用时的参数
return (...args2) => {
// thisArg 需要指定的this
// args 调用myBind时传入的参数
// args2 调用新函数时传入的参数
this.call(thisArg, ...args, ...args2)
}
}
const obj = {
name: '我是小小黑'
}
function func(drink, food) {
console.log(`我叫${this.name},我喜欢喝${drink},我爱吃${food}`)
}
// 调用bind获取绑定this的新函数,参数1为可乐
const bindFunc = func.bind(obj, '可乐')
// 调用函数,只需要传递参数2即可
bindFunc('西蓝花炒蛋')
小结
手写bind方法
- 如何返回一个绑定了
this的函数? - 如何实现绑定的参数,及传入的参数合并?
Function.prototype.myBind = function (thisArg, ...args) {
return (...args2) => {
this.call(thisArg, ...args, ...args2)
}
}
JS继承-ES5
这一节咱们来学习如何在JS中实现继承,首先看看在ES6之前可以如何实现继承
**继承:**继承可以使子类具有父类的各种属性和方法,而不需要再次编写相同的代码
这一节咱们会学习ES5中常见的继承写法(命令来源于 《JavaScript高级程序设计》)
- 原型链实现继承
- 构造函数继承
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承
// 父类
function Parent(){
this.name = name
this.foods = ['西蓝花', '西红柿']
this.sayFoods = function () {
console.log(this.foods)
}
}
ES5-原型链实现继承
**核心步骤:**希望继承谁,就将谁作为原型
**缺点:**父类中的引用数据类型,会被所有子类共享
// 父类
function Parent(name) {
this.name = name
this.foods = ['西蓝花', '西红柿']
this.sayFoods = function () {
console.log(this.foods)
}
}
// 子类
function Son() {
}
// 将父类的实例 作为子类的原型
Son.prototype = new Parent('jack')
const s1 = new Son()
s1.sayFoods()// ['西蓝花', '西红柿']
const s2 = new Son()
s2.sayFoods() // ['西蓝花', '西红柿']
s2.foods.push('西葫芦')
s2.sayFoods()// ['西蓝花', '西红柿', '西葫芦']
s1.sayFoods()// ['西蓝花', '西红柿', '西葫芦']
小结:
ES5-原型链实现继承
- 原型链实现继承的核心步骤?
- 这种继承方法的缺点是?
ES5-构造函数继承
**核心步骤:**在子类的构造函数中通过call或apply父类的构造函数
**缺点:**子类没法使用父类原型上的属性/方法
// 父类
function Parent(name) {
this.name = name
}
Parent.prototype.sayHi = function () {
console.log('你好,我叫:', this.name)
}
// 子类
function Son(name) {
Parent.call(this, name)
}
const s1 = new Son('lucy')
const s2 = new Son('rose')
s1.sayHi() // 报错
小结:
ES5-构造函数继承
- 构造函数继承的核心步骤是?
- 这种继承方法的缺点是?
ES5-组合继承
通过组合继承,结合上面2种方法的优点
核心步骤:
- 通过原型链继承公共的属性和方法
- 通过构造函数继承实例独有的属性和方法
**特点:**调用了2次构造函数
// 父类
function Person(name) {
this.name = name
}
// 公共的属性和方法加父类原型上
Person.prototype.sayHi = function () {
console.log(`你好,我叫${this.name}`)
}
// 子类构造函数
function Student(name, age) {
// 调用父类构造函数传入this
Person.call(this, name)
// 子类独有的属性和方法单独设置
this.age = age
}
// 设置子类的原型为 父类实例
Student.prototype = new Person()
// 调用子类的构造函数
const s = new Student('李雷', 18)
// 可以使用原型链上的 属性和方法 也可以使用 通过构造函数获取的父类的属性和方法
小结:
ES5-组合继承
- 组合继承的核心步骤:
- 组合继承的特点:
ES5-原型式继承
直接基于对象实现继承
**核心步骤:**对某个对象进行浅拷贝(工厂函数或Object.create),实现继承
**缺点:**父类中的引用数据类型,会被所有子类共享
// 可以用 Object.create替代
function objectFactory(obj) {
function Fun() { }
Fun.prototype = obj
return new Fun()
}
const parent = {
name: 'parent',
age: 25,
friend: ['rose', 'ice', 'robot'],
sayHi() {
console.log(this.name, this.age)
}
}
const son1 = objectFactory(parent)
const son2 = objectFactory(parent)
son1.friend.push('lucy')
console.log(son2.friend)
小结:
ES5-原型式继承
- 原型式继承的核心步骤是?
- 原型式继承的缺点是?
ES5-寄生式继承
核心步骤:
定义工厂函数,并在内部:
- 对传入的对象进行浅拷贝(公共属性/方法)
- 为浅拷贝对象增加属性/方法(独有属性/方法)
function createAnother(origin) {
// Object.create基于原型创建新对象,对属性进行浅拷贝
const clone = Object.create(origin)
// 为对象增加属性/方法
clone.sayHi = function () {
console.log('你好')
}
return clone
}
const parent = {
name: 'parent',
foods: ['西蓝花', '炒蛋', '花菜']
}
const son1 = createAnother(parent)
const son2 = createAnother(parent)
小结:
寄生式继承
-
寄生式继承的核心步骤是?
-
基于对象,创建新对象
-
增加新的属性和方法
-
-
寄生式继承和上一节学习的原型式继承的区别是?
- 创建出来的新对象,会额外的增加新的属性/方法
ES5-寄生组合式继承
核心步骤:
- 通过构造函数来继承属性
- 通过原型链来继承方法
// 继承原型函数
function inheritPrototype(son, parent){
const prototype = object.create(parent.prototype)
prototype.constructor = son
son.prototype = prototype
}
// 父类
function Parent(name) {
this.name = name
this.foods = ['西蓝花', '西葫芦', '西红柿']
}
Parent.prototype.sayHi = function () {
console.log(this.name, `我喜欢吃,${this.foods}`)
}
// 子类借用父类的构造函数
function Son(name, age) {
Parent.call(this, name)
this.age = age
}
// 完成原型继承
inheritPrototype(Son,Parent)
// 可以继续在原型上添加属性/方法
Son.prototype.sayAge = function () {
console.log('我的年龄是', this.age)
}
const son1 = new Son('jack', 18)
const son2 = new Son('rose', 16)
小结:
ES5-寄生组合式继承
- 寄生组合式继承的核心步骤是:
- 通过构造函数继承:
- 通过原型链来继承:
- 寄生组合式继承和组合式继承的区别是:
JS继承-ES6
这一节咱们来学习在ES6中class关键字的使用,并且使用它来实现继承
ES6中推出了class类,是用来创建对象的模板.class可以看作是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
这一节咱们会学习:
- class核心语法
- class实现继承
- class语法补充
class核心语法
核心语法:
- 如何定义及使用类:
- 如何定义实例属性/方法:
// 定义类
class Person {
// 实例属性,方便一眼确认有哪些
name
food
// 构造方法,类似于构造函数,new的时候会调用,内部的this就是实例化的对象
constructor(name, food) {
this.name = name
this.food = food
}
// 实例方法
sayHi() {
console.log(`你好,我叫${this.name},我喜欢吃${this.food}`)
}
}
const p = new Person('小黑', '西蓝花')
p.sayHi()
小结:
class核心语法:
- 针对如下代码,实例属性,构造函数,实例方法,分别是?
class Person{
name
food='西兰花炒蛋'
constructor(name){
this.name=name
}
sayHi(){
console.log('你好,我叫:',this.name)
}
}
class实现继承
关键语法:
// 在上一份代码的基础上继续编写下面代码
class Student extends Person {
song
constructor(name, food, song) {
// 子类构造函数使用this以前必须调用super
super(name, food)
this.song = song
}
// 添加方法
sing() {
console.log(`我叫${this.name},我喜欢唱${this.song}`)
}
}
const s = new Student('李雷', '花菜', '孤勇者')
s.sayHi()
s.sing()
小结:
class实现继承
- 子类继承父类的使用的关键字是?
- 子类构造函数中必须先调用什么方法?
class私有,静态属性和方法
补充语法:
class Person {
constructor(name) {
this.name = name
}
// 通过#作为前缀添加的属性会变为私有
// 私有属性
#secret = '我有一个小秘密,就不告诉你'
// 私有方法
#say() {
// 私有属性可以在
console.log('私有的say方法')
}
info() {
// 在类的内部可以访问私有属性调用私有方法
console.log(this.#secret)
this.#say()
}
// 通过 static定义静态属性/方法
static staticMethod() {
console.log('这是一个静态方法')
console.log(this)
}
static info = '直立行走,双手双脚'
}
const p = new Person('jack')
console.log(p)
// 外部无法访问 点语法访问直接报错,通过[]无法动态获取
console.log(p['#secret'])
p.info()
// 通过类访问静态属性/方法
Person.staticMethod()
console.log(Person.info)
小结:
class语法补充
- class中私有属性/方法定义和使用时使用的关键字是?
- class中静态属性/方法定义和使用是使用的关键字是?
fetch
这一节咱们来学习内置函数
fetch
全局的fetch函数用来发起获取资源请求.他返回一个promise,这个promise会在请求响应后被resolve,并传回Response对象
这一节咱们会学习的有:
-
fetch核心语法 -
fetch结合URLSearchParams发送get请求:-
const obj = { name:'jack', age:18 } name=jack&age=17
-
-
fetch发送post请求,提交JSON数据 -
fetch发送post请求,提交FormData数据
fetch核心语法
核心语法:
fetch(资源地址,{...配置项对象})
.then(response=>{
// 接收请求
})
小结:
fetch核心语法
fetch函数的参数:- 参数1:
- 参数2:
fetch获取到响应结果,需要如何解析:
fetch(参数1,参数2)
.then(response=>{
// 接收请求
})
fetch结合URLSearchParams发送get请求:
需求:
- 使用
fetch结合URLSearchParams调用地区查询接口
;(async function () {
const params = new URLSearchParams({
pname: '广东省',
cname: '广州市'
})
const url = `http://hmajax.itheima.net/api/area?${params.toString()}`
// fetch函数返回的是 Promise对象,通过await等待获取response对象
const res = await fetch(url)
// .json方法返回的是Promise对象 继续通过await等待
const data = await res.json()
})()
小结:
fetch结合URLSearchParams发送get请求:
fetch发送get请求时,是否需要设置请求方法?URLSearchParams的作用是?
post请求-提交JSON
需求:
fetch发送post请求,提交JSON数据- 测试接口-用户注册
核心步骤:
- 根据文档设置请求头
- 通过配置项设置,请求方法,请求头,请求体
; (async function () {
// 通过headers设置请求头
const headers = new Headers()
// 通过 content-type指定请求体数据格式
headers.append('content-type', 'application/json')
// 参数1 url
// 参数2 请求配置
const res = await fetch('http://hmajax.itheima.net/api/register', {
method: 'post',// 请求方法
headers, // 请求头
// 请求体
body: JSON.stringify({ username: 'itheima9876', password: '123456' })
})
const json = await res.json()
console.log(json)
})()
小结:
post请求-提交JSON
fetch函数的第二个参数可以设置请求头,请求方法,请求体
post请求-提交FormData
需求:
fetch发送post请求,提交FormData数据(上传+回显)- 测试接口-上传图片
核心步骤:
- 通过
FormData添加文件 - 通过配置项设置,请求方法,请求体(
FormData不需要设置请求头)
<input type="file" class="file" accept="image/*">
<script>
document.querySelector('.file').addEventListener('change', async function (e) {
// 生成FormData对象并添加数据
const data = new FormData()
data.append('img', this.files[0])
const res = await fetch('http://hmajax.itheima.net/api/uploadimg', {
method: 'post',
body: data
})
const json = await res.json()
console.log(json)
})
</script>
小结:
post请求-提交FormData
fetch提交FormData时是否需要额外设置请求头?FormData添加键值对使用哪个方法?
Generator
这一节咱们来学习
generator
Generator对象由生成器函数返回并且它符合可迭代协议和迭代器协议.他可以用来控制流程,语法行为和之前学习的函数不一样
Generator-核心语法
核心语法:
- 如何定义生成器函数:
- 如何获取
generator对象 yield表达式的使用- 通过
for of获取每一个yield的值
// 1. 通过function* 创建生成器函数
function* foo() {
// 遇到yield表达式时会暂停后续的操作
yield 'a'
yield 'b'
yield 'c'
return 'd'
}
// 2. 调用函数获取生成器
const f = foo()
// 3. 通过next方法获取yield 之后的表达式结果,会被包装到一个对象中
// 执行一次next 即可获取一次 yield之后的表达式结果
const res1 = f.next()
console.log(res1)// {value: 'a', done: false}
const res2 = f.next()
console.log(res2)// {value: 'b', done: false}
const res3 = f.next()
console.log(res3)// {value: 'c', done: false}
// 最后一次可以拿到return的结果
const res4 = f.next()
console.log(res4)// {value: 'd', done: true}
// done 为true之后,获取到的value为undefined
const res5 = f.next()
console.log(res5)// {value: undefined, done: true}
// 4. 通过for of 获取每一个yield之后的值,
const f2 = foo()
for (const iterator of f2) {
console.log(iterator)
}
小结:
Generator-核心语法
- 如何生成
Generator对象? - 如何获取
yield表达式之后的结果?
Generator-id生成器
**需求:**使用Generator实现一个id生成器id
function* idGenerator() {
// 逻辑略
}
const idMaker = idGenerator()
// 调用next方法,获取id(每次累加1)
const { value: id1 } = idMaker.next()
console.log(id1)
const { value: id2 } = idMaker.next()
console.log(id2)
核心步骤:
- 定义生成器函数
- 内部使用循环,通过
yield返回id并累加
// 1. 通过function* 创建生成器函数
function* generator() {
let id = 0
// 无限循环
while (true) {
// id累加并返回
yield id++
}
}
// 2. 调用函数获取生成器
const idMaker = generator()
// 3. 需要id的时候 通过next获取即可
const { value: id1 } = idMaker.next()
console.log(id1)
const { value: id2 } = idMaker.next()
console.log(id2)
小结:
-
生成器函数内部的代码什么时候执行?
Generator-流程控制
遇到yield表达式时会暂停后续的操作
**需求:**使用Generator实现流程控制
function* weatherGenerator() {
// 逻辑略
yield axios()
}
// 获取Generator实例
const weather = weatherGenerator()
// 依次获取 北上广深的天气 (axios)
weather.next()
核心步骤:
yield后面跟上天气查询逻辑- 接口文档-天气预报
- 参考
code:北京 110100 上海 310100 广州 440100 深圳 440300
<button class="getWeather">天气查询</button>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/1.3.6/axios.js"></script>
<script>
/**
* 需求:流程控制,依次查询,北上广深的天气预报
* 参考code: 北京 110100 上海 310100 广州 440100 深圳 440300
* 接口文档: https://apifox.com/apidoc/project-1937884/api-49760220
* */
function* weatherGenerator() {
// 北京
yield axios('http://hmajax.itheima.net/api/weather?city=110100')
// 上海
yield axios('http://hmajax.itheima.net/api/weather?city=310100')
// 广州
yield axios('http://hmajax.itheima.net/api/weather?city=440100')
// 深圳
yield axios('http://hmajax.itheima.net/api/weather?city=440300')
}
const cityWeather = weatherGenerator()
document.querySelector('.getWeather').addEventListener('click', async () => {
const res = await genCity.next()
console.log(res)
})
</script>
小结:
- 使用
Generator控制流程的本质是利用哪个关键字来分隔逻辑?
函数柯里化
这一节咱们来学习函数柯里化,在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
这一节咱们会学习的有:
- 什么是函数柯里化
- 经典面试题
- 柯里化实际应用
柯里化 作为一种高阶技术, 可以提升函数的复用性和灵活性。
什么是函数柯里化
函数柯里化 (Currying) 是一种将多个参数的函数转换为单个参数函数的技术
转换完毕之后的函数:只传递函数的一部分参数来调用,让他返回一个新的函数去处理剩下的参数。
例子:
// 调整函数 sum
function sum(num1, num2) {
return num1 + num2
}
// 改写为 可以实现如下效果
console.log(sum(1)(2))//
核心步骤:
sum改为接收一个参数,返回一个新函数- 新函数内部将参数1,参数2累加并返回
function sum(num1) {
return function (num2) {
return num1 + num2
}
}
柯里化面试题-全局变量
柯里化在面试的时候一般以笔试题出现,比如
需求:
function sum(a, b, c, d, e) {
return a + b + c + d + e
}
// 改写函数sum实现:参数传递到5个即可实现累加
// sum(1)(2)(3)(4)(5)
// sum(1)(2,3)(4)(5)
// sum(1)(2,3,4)(5)
// sum(1)(2,3)(4,5)
核心步骤:
- 接收不定长参数
- 存储已传递的参数
- 判断长度
- 满足5:累加
- 不满足:继续返回函数本身
let nums = []
function currySum(...args) {
nums.push(...args)
if (nums.length >= 5) {
return nums.reduce((prev, curv) => prev + curv, 0)
} else {
return currySum
}
}
柯里化面试题-使用闭包
需求:
- 使用闭包将上一节代码中的全局变量,保护起来
- 支持自定义累加的参数个数
function sumMaker(length){
// 逻辑略
}
// 支持5个累加
const sum5 = sumMaker(5)
// 支持7个累加
const sum7 = sumMaker(7)
sum7(1,2,3)(4,5,6,7)
核心步骤:
- 定义外层函数:
- 定义参数
length - 将全局变量迁移到函数内
- 定义参数
- 定义内层函数:
- 参数长度判断,使用传入的参数
length - 直接复用上一节的逻辑,并返回
- 参数长度判断,使用传入的参数
function sumMaker(length) {
let nums = []
function inner(...args) {
nums.push(...args)
if (nums.length >= length) {
return nums.reduce((prev, curv) => prev + curv, 0)
} else {
return inner
}
}
return inner
}
柯里化实际应用-类型判断
通过参数复用,实现一个类型判断生成器函数
需求:
- 将下列4个类型判断函数,改写为通过函数
typeOfTest动态生成
// 有如下4个函数
function isUndefined(thing) {
return typeof thing === 'undefined'
}
function isNumber(thing) {
return typeof thing === 'number'
}
function isString(thing) {
return typeof thing === 'string'
}
function isFunction(thing) {
return typeof thing === 'function'
}
// 改为通过 typeOfTest 生成:
const typeOfTest =function(){
// 参数 和 逻辑略
}
const isUndefined = typeOfTest('undefined')
const isNumber = typeOfTest('number')
const isString = typeOfTest('string')
const isFunction = typeOfTest('function')
// 可以通过 isUndefined,isNumber,isString,isFunction 来判断类型:
isUndefined(undefined) // true
isNumber('123') // false
isString('memeda') // true
isFunction(() => { }) // true
核心步骤:
typeOfTest接收参数type用来接收判断的类型- 内部返回新函数,接收需要判断的值,并基于
type进行判断 - 使用箭头函数改写为最简形式~~传送门
const typeOfTest = (type) => {
return (thing) => {
return typeof thing === type
}
}
柯里化实际应用-固定参数
依旧是一个参数复用的实际应用
需求:
- 将如下3个请求的函数(都是post请求),变为通过
axiosPost函数动态生成 - 实现函数
axiosPost
// 将如下3个请求,改写为调用 axiosPost函数即可实现
axios({
url: 'url1',
method: 'post',
data: {}
})
axios({
url: 'url2',
method: 'post',
data: {}
})
axios({
url: 'url3',
method: 'post',
data: {}
})
const axiosPost = () => {
// 参数,逻辑略
}
axiosPost('url1', data1)
axiosPost('url2', data2)
axiosPost('url3', data3)
核心步骤:
- 函数内部固定请求方法,post
- 函数内部调用
axios发请求即可 axios内部就是这样实现的传送门:
const axiosPost = (url, data) => {
return axios({
url, data,
method: 'post'
})
}
小结:
-
函数柯里化是一种函数式编程思想:将多个参数的函数转换为单个参数函数,调用时返回新的函数接收剩余参数
-
常见面试题
function sum(a, b, c, d, e) { return a + b + c + d + e } // 改写函数sum实现:参数传递到5个即可实现累加 // sum(1)(2)(3)(4)(5) // sum(1)(2,3)(4)(5) // sum(1)(2,3,4)(5) // sum(1)(2,3)(4,5) -
常见应用:固定参数,比如
axios中的:
手写Promise
这一节咱们一起来手写Promise
这一节咱们会学习的有:
- 实现Promise的核心用法
- Promise的静态方法
- 实现Promise的静态方法
首先明确Promise的核心用法
const p = new Promise((resolve, reject) => {
resolve('success')
// 或者
// reject('error')
})
// then方法的参数1: 状态为成功的回调函数
// then方法的参数2: 状态为失败的回调函数
p.then(res => {
console.log(res)
}, err => {
console.log(err)
})
手写Promise-构造函数
需求:
- 实现MyPromise类,可以用如下的方式实例化
- 实例化时传入回调函数
- 回调函数立刻执行
- 回调函数接收函数
resolve和reject
const p = new MyPromise((resolve, reject) => {
// resolve()
// reject()
})
核心步骤:
- 定义类
MyPromise - 实现构造函数,接收
executor--传入的回调函数 - 构造函数中定义
resolve和reject并传入executor
// 1. 定义类
class MyPromise {
// 2. 构造函数
// executor 执行器,实例化时立刻调用
constructor(executor) {
// 3. 定义 resolve reject 传入executor
const resolve = () => {
console.log('resolve-call')
}
const reject = () => {
console.log('reject-call')
}
executor(resolve, reject)
}
}
手写Promise-状态、成功or失败原因
需求:
MyPromise增加state属性,只能是如下3个值pending:待定,默认状态fulfilled:已兑现,操作成功rejected:已拒绝,操作失败
MyPromise增加result属性,记录成功/失败原因- 调用
resolve或reject,修改状态,并记录成功/失败原因
const p = new MyPromise((resolve, reject) => {
// resolve('成功结果')
reject('失败原因')
})
console.log(p)
核心步骤:
- 定义常量保存状态,避免硬编码
MyPromise中定义- 属性:
state保存状态,result成功/失败原因 - 修改
state的私有方法,修改状态并记录result - 注意:
state只有在pending时,才可以修改,且不可逆
- 属性:
// 1. 定义常量保存状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
// 2. 定义属性 state(状态) reason(成功/失败原因)
state = PENDING
result = undefined
constructor(executor) {
// 3. 实现 resolve和reject内部逻辑
const resolve = (result) => {
this.#changeState(FULFILLED, result)
}
const reject = (result) => {
this.#changeState(REJECTED, result)
}
executor(resolve, reject)
}
// 4. 提取resolve和reject内部公共逻辑
#changeState(state, result) {
if (this.state !== PENDING) {
return
}
this.state = state
this.result = result
}
}
手写Promise-then方法的核心功能
需求:
- then方法的回调函数1: 状态变为
fulfilled时触发,并获取成功结果 - then方法的回调函数2: 状态变为
rejected时触发,并获取失败原因 - then方法的回调函数1或2没有传递的特殊情况处理,参考:then方法的参数
const p = new MyPromise((resolve, reject) => {
// resolve('成功结果')
reject('失败原因')
})
p.then(res => {
console.log('success:', res)
}, err => {
console.log('error:', err)
})
核心步骤:
- 增加
then方法,根据不同的状态执行对应的回调函数,并传入result- 参数1:成功的回调函数
- 参数2:失败的回调函数
- 没有传递
onFulfilled,onRejected时,设置默认值(参考文档)
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
state = PENDING
result = undefined
constructor(executor) {
const resolve = (result) => {
this.#changeState(FULFILLED, result)
}
const reject = (result) => {
this.#changeState(REJECTED, result)
}
executor(resolve, reject)
}
#changeState(state, result) {
if (this.state !== PENDING) {
return
}
this.state = state
this.result = result
}
// 1. 增加then方法
then(onFulfilled, onRejected) {
// 2. 处理未传入回调函数的特殊情况
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => {
throw reason
}
if (this.state === FULFILLED) {
onFulfilled(this.result)
} else if (this.state === REJECTED) {
onRejected(this.result)
}
}
}
手写Promise-then方法支持异步和多次调用(非链式)
需求:
- 实例化传入的回调函数,内部支持异步操作
- then方法支持多次调用
const p = new MyPromise((resolve, reject) => {
setTimeout(() => {
// resolve('成功结果')
reject('失败原因')
}, 2000)
})
p.then(res => {
console.log('success1:', res)
}, err => {
console.log('error1:', err)
})
p.then(res => {
console.log('success2:', res)
}, err => {
console.log('error2:', err)
})
p.then(res => {
console.log('success3:', res)
}, err => {
console.log('error3:', err)
})
核心步骤:
- 定义属性,保存传入的回调函数:[]
- 调用
then方法并且状态为pending时保存传入的成功/失败回调函数 - 调用
resolve和reject时执行上一步保存的回调函数
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
state = PENDING
result = undefined
// 1. 添加handlers属性保存then方法添加的回调函数
handlers = []
constructor(executor) {
const resolve = (result) => {
this.#changeState(FULFILLED, result)
// 4. 调用runHandlers 执行回调函数
this.#runHandlers()
}
const reject = (result) => {
this.#changeState(REJECTED, result)
// 4. 调用runHandlers 执行回调函数
this.#runHandlers()
}
executor(resolve, reject)
}
// 3. 抽取方法 执行 fulfilled/rejected状态时的回调函数
#runHandlers() {
while (this.handlers.length > 0) {
const { onFulfilled, onRejected } = this.handlers.shift()
if (this.state === FULFILLED) {
onFulfilled(this.result)
} else {
onRejected(this.result)
}
}
}
#changeState(state, result) {
if (this.state !== PENDING) {
return
}
this.state = state
this.result = result
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}
if (this.state === FULFILLED) {
onFulfilled(this.result)
} else if (this.state === REJECTED) {
onRejected(this.result)
} else {
// 2. 状态为 pending 时,将回调函数添加到数组中
this.handlers.push({
onFulfilled, onRejected
})
}
}
}
手写Promise-链式编程-成功状态+普通返回值
需求:
then的链式编程- 目前只考虑
resolve内部返回普通值的情况
const p = new MyPromise((resolve, reject) => {
resolve(1)
})
p.then(res => {
console.log(res)
return 2
}).then(res => {
console.log(res)
return 3
}).then(res => {
console.log(res)
return 4
})
核心步骤:
- 调整
then方法,返回一个新的MyPromise对象 - 内部获取
onFulfilled的执行结果,传入resolve方法继续执行
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
state = PENDING
result = undefined
handlers = []
constructor(executor) {
const resolve = (result) => {
this.#changeState(FULFILLED, result)
this.#runHandlers()
}
const reject = (result) => {
this.#changeState(REJECTED, result)
this.#runHandlers()
}
executor(resolve, reject)
}
#runHandlers() {
while (this.handlers.length > 0) {
const { onFulfilled, onRejected } = this.handlers.shift()
if (this.state === FULFILLED) {
onFulfilled(this.result)
} else {
onRejected(this.result)
}
}
}
#changeState(state, result) {
if (this.state !== PENDING) {
return
}
this.state = state
this.result = result
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}
// 1. 创建并返回新的Promise对象
const p2 = new MyPromise((resolve, reject) => {
if (this.state === FULFILLED) {
const res = onFulfilled(this.result)
// 2. 继续调用resolve方法
resolve(res)
} else if (this.state === REJECTED) {
onRejected(this.result)
} else {
this.handlers.push({
onFulfilled, onRejected
})
}
})
return p2
}
}
手写Promise-链式编程-成功状态+返回Promise
需求:
then的链式编程- 目前考虑
resolve内部返回MyPromise的情况
const p = new MyPromise((resolve, reject) => {
resolve(1)
})
p.then(res => {
console.log(res)
return new MyPromise((resolve, reject) => {
resolve(2)
})
}).then(res => {
console.log(res)
return new MyPromise((resolve, reject) => {
resolve(3)
})
}).then(res => {
console.log(res)
})
核心步骤:
- 内部获取
onFulfilled的执行结果: - 如果是
MyPromise实例,继续then下去并传入resolve和reject
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
state = PENDING
result = undefined
handlers = []
constructor(executor) {
const resolve = (result) => {
this.#changeState(FULFILLED, result)
this.#runHandlers()
}
const reject = (result) => {
this.#changeState(REJECTED, result)
this.#runHandlers()
}
executor(resolve, reject)
}
#runHandlers() {
while (this.handlers.length > 0) {
const { onFulfilled, onRejected } = this.handlers.shift()
if (this.state === FULFILLED) {
onFulfilled(this.result)
} else {
onRejected(this.result)
}
}
}
#changeState(state, result) {
if (this.state !== PENDING) {
return
}
this.state = state
this.result = result
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}
const p2 = new MyPromise((resolve, reject) => {
if (this.state === FULFILLED) {
const res = onFulfilled(this.result)
// 1. 判断是否为MyPromise的实例
if (res instanceof MyPromise) {
// 2. 继续调用then方法 传入 resolve 和 reject
res.then(resolve, reject)
} else {
resolve(res)
}
} else if (this.state === REJECTED) {
onRejected(this.result)
} else {
this.handlers.push({
onFulfilled, onRejected
})
}
})
return p2
}
}
手写Promise-链式编程-失败状态
需求:
then的第二个回调函数,执行reject时的链式编程
const p = new MyPromise((resolve, reject) => {
resolve(1)
})
p.then(res => {
console.log(res)
return new MyPromise((resolve, reject) => {
reject(2)
})
}).then(undefined, err => {
console.log('err:', err)
})
核心步骤:
- 参考
resolve的逻辑 - 先实现功能,再抽取为函数直接调用
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
state = PENDING
result = undefined
handlers = []
constructor(executor) {
const resolve = (result) => {
this.#changeState(FULFILLED, result)
this.#runHandlers()
}
const reject = (result) => {
this.#changeState(REJECTED, result)
this.#runHandlers()
}
executor(resolve, reject)
}
#runHandlers() {
while (this.handlers.length > 0) {
const { onFulfilled, onRejected } = this.handlers.shift()
if (this.state === FULFILLED) {
onFulfilled(this.result)
} else {
onRejected(this.result)
}
}
}
#changeState(state, result) {
if (this.state !== PENDING) {
return
}
this.state = state
this.result = result
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => {
throw reason;
};
const p2 = new MyPromise((resolve, reject) => {
if (this.state === FULFILLED) {
// const res = onFulfilled(this.result)
// if (res instanceof MyPromise) {
// res.then(resolve, reject)
// } else {
// resolve(res)
// }
this.#runPromise(onFulfilled, resolve, reject)
} else if (this.state === REJECTED) {
// 1. 参考成功状态的逻辑实现 失败状态
// const res = onRejected(this.result)
// if (res instanceof MyPromise) {
// res.then(resolve, reject)
// } else {
// reject(res)
// }
this.#runPromise(onRejected, resolve, reject)
} else {
this.handlers.push({
onFulfilled, onRejected
})
}
})
return p2
}
// 2. 抽取 then中的逻辑,并替换掉原本代码
#runPromise(callback, resolve, reject) {
const res = callback(this.result)
if (res instanceof MyPromise) {
res.then(resolve, reject)
} else {
this.state === FULFILLED ? resolve(res) : reject(res)
}
}
}
手写Promise-链式编程-支持异步
需求:
- 执行异步操作时,支持链式编程
const p = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 2000)
})
p.then(res => {
console.log(res)
return new MyPromise((resolve, reject) => {
setTimeout(() => {
reject(2)
}, 2000)
})
})
.then(undefined, err => {
console.log('err:', err)
})
核心步骤:
- then的内部将
resolve,reject也推送到数组中 - 调整
runHandlers函数,内部直接调用runPromise函数即可
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
state = PENDING
result = undefined
handlers = []
constructor(executor) {
const resolve = (result) => {
this.#changeState(FULFILLED, result)
this.#runHandlers()
}
const reject = (result) => {
this.#changeState(REJECTED, result)
this.#runHandlers()
}
executor(resolve, reject)
}
#runHandlers() {
while (this.handlers.length > 0) {
// 2. 解构出resolve,reject执行和上一步一样的逻辑
const { onFulfilled, onRejected, resolve, reject } = this.handlers.shift()
if (this.state === FULFILLED) {
this.#runPromise(onFulfilled, resolve, reject)
} else {
this.#runPromise(onRejected, resolve, reject)
}
}
}
#changeState(state, result) {
if (this.state !== PENDING) {
return
}
this.state = state
this.result = result
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => {
throw reason;
};
const p2 = new MyPromise((resolve, reject) => {
if (this.state === FULFILLED) {
this.#runPromise(onFulfilled, resolve, reject)
} else if (this.state === REJECTED) {
this.#runPromise(onRejected, resolve, reject)
} else {
// 1. 将 resolve和reject也推送到数组中
this.handlers.push({
onFulfilled, onRejected, resolve, reject
})
}
})
return p2
}
#runPromise(callback, resolve, reject) {
const res = callback(this.result)
if (res instanceof MyPromise) {
res.then(resolve, reject)
} else {
this.state === FULFILLED ? resolve(res) : reject(res)
}
}
}
手写Promise-使用微任务
需求:
- 如下代码打印结果为
1,2,4,3
console.log(1)
const p = new MyPromise((resolve, reject) => {
console.log(2)
resolve(3)
})
p.then(res => {
console.log(res)
})
console.log(4)
核心步骤:
- 使用
queueMicrotask包裹runPromise的内部逻辑即可 - 传送门:MDN-queueMicrotask
- 传送门:MDN-queueMicrotask使用指南
#runPromise(callback, resolve, reject) {
// 1. 使用queueMicrotask 包裹内部逻辑即可
queueMicrotask(() => {
const res = callback(this.result)
if (res instanceof MyPromise) {
res.then(resolve, reject)
} else {
this.state === FULFILLED ? resolve(res) : reject(res)
}
})
}
小结:
手写Promise的核心代码:
// 保存状态的常量
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
// 状态
state = PENDING
// 成功、失败原因
result = undefined
// 待执行的回调函数
handlers = []
// 构造函数
constructor(executor) {
// 定义 resolve和reject
const resolve = (result) => {
this.#changeState(FULFILLED, result)
this.#runHandlers()
}
const reject = (result) => {
this.#changeState(REJECTED, result)
this.#runHandlers()
}
// 接收传入的执行器,接收定义的resolve和reject
executor(resolve, reject)
}
// 根据状态执行回调函数的 私有方法
#runHandlers() {
while (this.handlers.length > 0) {
// 2. 解构出resolve,reject执行和上一步一样的逻辑
const { onFulfilled, onRejected, resolve, reject } = this.handlers.shift()
if (this.state === FULFILLED) {
this.#runPromise(onFulfilled, resolve, reject)
} else {
this.#runPromise(onRejected, resolve, reject)
}
}
}
// 修改状态的 私有方法
#changeState(state, result) {
if (this.state !== PENDING) {
return
}
this.state = state
this.result = result
}
// then方法,接收成功和失败的回调函数
then(onFulfilled, onRejected) {
// onFulfilled 和 onRejected 的非空判断
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => {
throw reason;
};
// 保证链式编程,返回Promise
const p2 = new MyPromise((resolve, reject) => {
if (this.state === FULFILLED) {
this.#runPromise(onFulfilled, resolve, reject)
} else if (this.state === REJECTED) {
this.#runPromise(onRejected, resolve, reject)
} else {
this.handlers.push({
onFulfilled, onRejected, resolve, reject
})
}
})
return p2
}
// 满足执行条件,执行回调函数的 私有方法
#runPromise(callback, resolve, reject) {
// 使用微任务队列
queueMicrotask(() => {
const res = callback(this.result)
if (res instanceof MyPromise) {
res.then(resolve, reject)
} else {
this.state === FULFILLED ? resolve(res) : reject(res)
}
})
}
}
手写Promise-实例方法catch
需求:
- 实现实例方法
catch,可以实现如下调用
const p = new MyPromise((resolve, reject) => {
reject(1)
})
p.then(res => {
console.log(res)
}).catch(err => {
console.log('err:', err)
})
核心步骤:
- 参考文档,catch等同于:
then(undefined,onRjected) - 直接添加
catch方法,内部调用then - 使用
try-catch包裹runPromise,出错时,调用reject
// 添加catch方法,内部参考文档的方式调用then即可
catch(onRjected) {
return this.then(undefined, onRjected)
}
// 处理回调函数执行结果
#runPromise(callBack, resolve, reject) {
queueMicrotask(() => {
try {
// 调用回调函数 获取执行的结果
const res = callBack(this.result)
if (res instanceof MyPromise) {
// res是Promise对象 then方法
res.then(resolve, reject)
} else {
// 如果是普通的值,直接resolve
if (this.state === FULFILLED) {
resolve(res)
} else if (this.state === REJECTED) {
reject(res)
}
}
} catch (error) {
return reject(error)
}
})
手写Promise-实例方法finally
需求:
- 无论成功失败都会执行
finally的回调函数 - 回调函数不接受任何参数
const p = new Promise((resolve, reject) => {
reject('error')
})
p.then(res => {
console.log(res)
}).catch(err => {
console.log(err)
}).finally(() => {
console.log('finally执行啦')
})
核心步骤:
- 参考文档:finally方法类似于调用
then(onFinally,onFinally),且不接受任何回调函数
finally(onFinally) {
return this.then(onFinally,onFinally)
}
手写Promise-静态方法resolve
需求:
- 返回一个带有成功原因的
Promise对象
// 返回一个值为2的Promise对象
MyPromise.resolve(2).then(res => {
console.log(res)
})
const p = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
// 直接返回传入的p
MyPromise.resolve(p).then(res => {
console.log(res)
})
核心步骤:
- 增加静态方法
resolve,根据传入的值返回不同的结果即可
static resolve(value) {
// 如果是Promise 返回Promise
if (value instanceof MyPromise) {
return value
}
// 如果都不是的话,直接返回一个新的Promise对象 将value传递给resolve
return new MyPromise((resolve, reject) => {
resolve(value)
})
}
手写Promise-静态方法reject
需求:
- 返回一个带有拒绝原因的
Promise对象
MyPromise.reject('error').catch(err => {
console.log(err)
})
核心步骤:
- 添加静态方法内部返回有拒绝原因的
Promise对象即可
static reject(err) {
return new MyPromise((resolve, reject) => {
reject(err)
})
}
手写Promise-静态方法race
需求:
- 接收Promise数组
- 第一个Promise成功或失败时,返回一个该Promise对象及原因
const promise1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('one')
}, 500)
})
const promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject('two')
}, 100)
})
MyPromise.race([promise1, promise2]).then((value) => {
console.log('value:',value)
}, err => {
console.log('err:', err)
})
核心步骤:
- 内部返回新的Promise对象:
- 参数判断:
- 不是数组:报错
- 是数组:挨个解析
- 任意一个Promise对象成功或失败,直接resolve或reject即可
- 参数判断:
static race(promises) {
return new MyPromise((resolve, reject) => {
// 参数校验
if (Array.isArray(promises)) {
// 如果传入的promises是空数组,则返回的promise就将永远等待
promises.forEach(item => {
// 通过 Promise.resolve进行处理,只要有任何一个为 成功/拒绝 即可响应结果
MyPromise.resolve(item).then(resolve, reject)
})
} else {
// 参数错误
return reject(new TypeError('Argument is not iterable'))
}
})
}
手写Promise-静态方法all
需求:
- 接收Promise数组,
- 所有Promise都成功时,返回一个成功的Promise对象及成功数组
- 任何一个Promise失败,返回一个失败的Promise对象及第一个失败原因
const promise1 = MyPromise.resolve(3);
const promise2 = 42;
const promise3 = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject('success')
}, 1000);
});
MyPromise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
核心步骤:
-
包裹一个新的Promise并返回,内部进行参数校验
-
非数组:报错
-
数组:循环挨个解析
-
长度为0:直接返回成功状态的Promise
-
长度不为0:挨个解析:forEach
-
不是Promise对象:直接记录结果并判断是否解析完毕
-
是Promise对象:调用then
-
成功:记录结果并判断是否解析完毕
-
失败:直接reject
-
-
-
-
static all(promises) {
return new MyPromise((resolve, reject) => {
// 参数校验
if (Array.isArray(promises)) {
// 是数组再继续执行
// 存储结果
const result = []
let count = 0
// 如果长度为0 直接返回 fulfilled状态的Promise即可
if (promises.length === 0) {
return resolve(promises)
}
// 挨个处理
promises.forEach((item, index) => {
if (item instanceof MyPromise) {
// 如果是Promise
item.then(res => {
count++
// 这么做的目的是保证 结果的顺序 和 promise每一项的一致
result[index] = res
count === promises.length && resolve(result)
}, err => {
// 任何一个失败 无视其他的promise直接 reject即可
reject(err)
})
} else {
// 如果不是Promise 原样添加在数组中
count++
result[index] = item
// 全部处理完毕时,响应结果
count === promises.length && resolve(result)
}
})
} else {
//
// 错误提示
return reject(new TypeError('Argument is not iterable'))
}
})
}
手写Promise-静态方法allSettled
需求:
- 传入Promise数组,当所有对象都已敲定时
- 返回一个新的Promise对象及以数组形式保存的结果
const promise1 = Promise.resolve('1');
const promise2 = new Promise((resolve, reject) => setTimeout(() => {
reject('two')
}, 1000));
const promises = [promise1, promise2];
Promise.allSettled(promises).
then((results) => { console.log(results) })
核心步骤:
- 增加静态方法
allSettled - 内部逻辑和
all类似,需要特别注意的地方:- 成功和失败的原因都会通过对象记录起来
- 返回一个记录了成功
{state:FULFILLED,value:'xxx'}失败{state:REJECTED,reason:'xxx'}的结果数组
static allSettled(promises) {
return new MyPromise((resolve, reject) => {
// 参数校验
if (Array.isArray(promises)) {
let result = []// 结果数组
let count = 0 // 计数器
// 空数组直接返回
if (promises.length === 0) return resolve(promises)
// 挨个处理内部的Promise对象
promises.forEach((item, index) => {
// 使用resolve转为promise统一处理
MyPromise.resolve(item).then(value => {
// 成功状态
count++
result[index] = {
state: FULFILLED,
value
}
// 处理完毕之后 resolve
count === promises.length && resolve(result)
}, reason => {
// 失败状态
count++
// 失败状态 值为 reason
result[index] = {
state: REJECTED,
reason
}
// 成功和失败最终都对应到 resolve
count === promises.length && resolve(result)
})
})
} else {
return reject(new TypeError('Argument is not iterable'))
}
})
}
手写Promise-静态方法any
需求:-传送门
- 传入
Promise数组,- 任何一个
Promise对象敲定时,返回一个新的Promise对象,及对应的结果 - 所有Promise都被拒绝时,返回一个包含所有拒绝原因的
AggregateError错误数组
- 任何一个
const promise1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject('error1')
}, 2000);
});
const promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject('error2')
}, 3000);
});
const promise3 = new MyPromise((resolve, reject) => {
setTimeout(() => {
// resolve('success1')
reject('error3')
}, 1000);
});
MyPromise.any([promise1, promise2, promise3]).then((values) => {
console.log(values);
}, err => {
console.log('err:', err)
})
核心步骤:
- 类似于
all核心区别- 数组长度为0,直接返回错误数组
- 任何一个成功,直接成功
- 通过数组记录失败原因,都失败时响应错误
static any(promises) {
return new MyPromise((resolve, reject) => {
// 参数校验
if (Array.isArray(promises)) {
let errors = []
let count = 0
// AggregateError包含多个错误对象的 单个错误对象(错误对象容器)
if (promises.length === 0) return reject(new AggregateError('All promises were rejected'))
// 挨个处理
promises.forEach(item => {
item.then(value => {
// 只要一个成功 就成功
resolve(value)
}, reason => {
count++
errors.push(reason)
// 如果没有一个promise成功 就把所有的错误原因合并到一起 一起抛出
count++ === promises.length && reject(new AggregateError(errors))
})
})
} else {
// 参数格式有误
return reject(new TypeError('Argument is not iterable'))
}
})
}
JS设计模式
这一节咱们来学习JS中的设计模式
设计模式的指的是:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。通俗一点说,设计模式就是给面向对象软件开发中的一些好的设计取个名字。
目前说到设计模式,一般指的是《设计模式:可复用面向对象软件的基础》一书中提到的23种常见的软件开发设计模式。
JavaScript中不需要生搬硬套这些模式,咱们结合实际前端开发中的具体应用场景,来看看有哪些常用的设计模式
这一节咱们会学习:
- JS中的常用设计模式
- 设计模式在开发/框架中的应用场景
工厂模式
在JavaScript中,工厂模式的表现形式就是一个直接调用即可返回新对象的函数
// 定义构造函数并实例化
function Dog(name){
this.name=name
}
const dog = new Dog('柯基')
// 工厂模式
function ToyFactory(name,price){
return {
name,
price
}
}
const toy1 = ToyFactory('布娃娃',10)
const toy2 = ToyFactory('玩具车',15)
应用场景
-
Vue2->Vue3:
- 启用了
new Vue,改成了工厂函数createApp-传送门 - 任何全局改变 Vue 行为的 API(vue2) 现在都会移动到应用实例上(vue3)
- 就不会出现,Vue2中多个Vue实例共享,相同的全局设置,可以实现隔离
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> #app1, #app2 { border: 1px solid #000; } </style> </head> <body> <h2>vue2-全局注册组件</h2> <div id="app1"> 实例1 <my-title></my-title> </div> <div id="app2"> 实例2 <my-title></my-title> </div> <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.9/vue.js"></script> <script> Vue.component('my-title', { template: '<h2 style="color:orange">标题组件</h2>' }) const app1 = new Vue({ el: "#app1" }) const app2 = new Vue({ el: "#app2" }) </script> </body> </html> - 启用了
-
axios.create:
- 基于传入的配置创建一个新的
axios实例,传送门 - 项目中有2个请求基地址如何设置?
- 基于传入的配置创建一个新的
// 1. 基于不同基地址创建多个 请求对象
const request1 = axios.create({
baseURL: "基地址1"
})
const request2 = axios.create({
baseURL: "基地址2"
})
const request3 = axios.create({
baseURL: "基地址3"
})
// 2. 通过对应的请求对象,调用接口即可
request1({
url: '基地址1的接口'
})
request2({
url: '基地址2的接口'
})
request3({
url: '基地址3的接口'
})
小结:
-
工厂模式:JS中的表现形式,返回新对象的函数(方法)
-
function sayHi(){} // 函数 const obj ={ name:'jack', sayHello(){} // 方法 }
-
-
日常开发中,有2个很经典的场景
vue3中创建实例的api改为createApp,vue2中是new Vue- Vue3中,没有影响所有Vue实例的api了,全都变成了影响某个app对象的api,比如
Vue.component-->app.component
- Vue3中,没有影响所有Vue实例的api了,全都变成了影响某个app对象的api,比如
axios.create基于传入的配置,创建一个新的请求对象,可以用来设置多个基地址
单例模式
单例模式指的是,在使用这个模式时,单例对象整个系统需要保证只有一个存在。
需求:
- 通过静态方法
getInstance获取唯一实例
const s1 = SingleTon.getInstance()
const s2 = SingleTon.getInstance()
console.log(s1===s2)//true
核心步骤:
- 定义类
- 私有静态属性:
#instance - 提供静态方法
getInstance:- 调用时判断
#instance是否存在: - 存在:直接返回
- 不存在:实例化,保存,并返回
- 调用时判断
class SingleTon {
constructor() { }
// 私有属性,保存唯一实例
static #instance
// 获取单例的方法
static getInstance() {
if (SingleTon.#instance === undefined) {
// 内部可以调用构造函数
SingleTon.#instance = new SingleTon()
}
return SingleTon.#instance
}
}
实际应用:
- vant组件库中的弹框组件,保证弹框是单例
- vue中注册插件,用到了单例的思想(只能注册一次)
小结:
- 单例模式:
- 保证,应用程序中,某个对象,只能有一个
- 自己实现:
- getInstance方法,
- 实例存在->返回
- 实力不存在->创建,保存->返回
- getInstance方法,
- 应用场景:
- 我在看源码的时候,发现,vant的toast和notify组件都用到了单例
- 多次弹框,不会创建多个弹框,复用唯一的弹框对象
- vue中注册插件,vue3和vue3都会判断插件是否已经注册,已注册,直接提示用户
- 我在看源码的时候,发现,vant的toast和notify组件都用到了单例
观察者模式
在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。
举个例子:
dom事件绑定,比如
window.addEventListener('load', () => {
console.log('load触发1')
})
window.addEventListener('load', () => {
console.log('load触发2')
})
window.addEventListener('load', () => {
console.log('load触发3')
})
- Vue的生命周期钩子:
- vue框架,提供给开发者,在Vue实例特定时期,添加自定义逻辑的,一种机制
- Vue的响应式原理:
发布订阅模式01-应用场景
发布订阅模式可以实现的效果类似观察者模式,但是两者略有差异,一句话描述:一个有中间商(发布订阅模式)一个没中间商(观察者模式)
应用场景:
发布订阅模式02-自己写一个事件总线
需求:
const bus = new HMEmitter()
// 注册事件
bus.$on('事件名1',回调函数)
bus.$on('事件名1',回调函数)
// 触发事件
bus.$emit('事件名',参数1,...,参数n)
// 移除事件
bus.$off('事件名')
// 一次性事件
bus.$once('事件名',回调函数)
核心步骤:
- 定义类
- 私有属性:
#handlers={事件1:[f1,f2],事件2:[f3,f4]} - 实例方法:
- $on(事件名,回调函数):注册事件
- $emit(事件名,参数列表):触发事件
- $off(事件名):移除事件
- $once(事件名,回调函数):注册一次性事件
基础模板:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>自己实现事件总线</h2>
<button class="on">注册事件</button>
<button class="emit">触发事件</button>
<button class="off">移除事件</button>
<button class="once-on">一次性事件注册</button>
<button class="once-emit">一次性事件触发</button>
<script>
class HMEmmiter {
// 逻辑略
}
// 简化 querySelector调用
function qs(selector) {
return document.querySelector(selector)
}
// 注册事件
qs('.on').addEventListener('click', () => {
})
// 触发事件
qs('.emit').addEventListener('click', () => {
})
// 移除事件
qs('.off').addEventListener('click', () => {
})
// 一次性事件注册
qs('.once-on').addEventListener('click', () => {
})
// 一次性事件触发
qs('.once-emit').addEventListener('click', () => {
})
</script>
</body>
</html>
class HMEmmiter {
#handlers = {}
// 注册事件
$on(event, callback) {
if (!this.#handlers[event]) {
this.#handlers[event] = []
}
this.#handlers[event].push(callback)
}
// 触发事件
$emit(event, ...args) {
const funcs = this.#handlers[event] || []
funcs.forEach(func => {
func(...args)
})
}
// 移除事件
$off(event) {
this.#handlers[event] = undefined
}
// 一次性事件
$once(event, callback) {
this.$on(event, (...args) => {
callback(...args)
this.$off(event)
})
}
}
原型模式
在原型模式下,当我们想要创建一个对象时,会先找到一个对象作为原型,然后通过克隆原型的方式来创建出一个与原型一样(共享一套数据/方法)的对象。在JavaScript中,Object.create就是实现原型模式的内置api
应用场景:
vue2中重写数组方法:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>原型模式</h2>
<div id="app"></div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.9/vue.js"></script>
<script>
const app = new Vue({
el: "#app", data: {
foods: ['西瓜', '西葫芦', '西红柿']
}
})
console.log(app.foods.push === Array.prototype.push)
</script>
</body>
</html>
自己描述:
- vue2中数组重写了7个方法,内部基于数组的原型
Array.prototype创建了一个新对象 Object.create浅拷贝- 内部
- 调用数组的原方法,获取结果并返回---方法的功能和之前一致
- 通知了所有的观察者去更新视图
const app = new Vue({
el:"#app",
data:{
arr:[1,2,3]
}
})
app.arr.push === Array.prototype.push //false
- 原型模式,基于某个对象,创建一个新的对象,JS中,通过Object.create即可实现,Vue中重写数组方法就是这么做的 ↑
代理模式
代理模式指的是拦截和控制与目标对象的交互,在JavaScript中通过Proxy,即可实现对象的代理,传送门
核心语法:
- 初始对象可以直接修改任意属性
- 通过
Proxy生成代理对象,限制访问
// 目前obj对象的name和age属性可以被随意修改
const obj = {
name: 'jack',
}
// 通过Proxy创建代理对象
const objProxy = new Proxy(obj, {
// []语法进行对象的取值和赋值
get(target, key) {
console.log('get触发')
// return target[key]
},
// 结合Reflect的静态方法替换[]语法
set(target, key, value) {
console.log('set触发')
// target[key]=value
}
})
// 代理对象属性赋值,触发set
objProxy.name = 'rose'
// 代理对象属性取值,触发get
console.log(objProxy.name)
需求:
基于上一份代码实现:
- 属性取值和赋值时,如果属性不存在,报错
- 修改name时,只能设置字符串,否则报错
关键步骤:
- 在
get中添加取值判断逻辑 - 在
set中添加赋值判断逻辑
// 目前obj对象的name和age属性可以被随意修改
const obj = {
name: 'jack',
age: 18
}
// 通过Proxy创建代理对象
const objProxy = new Proxy(obj, {
// []语法进行对象的取值和赋值
get(target, key) {
if (!target[key]) {
throw new Error('属性不存在')
}
return target[key]
},
set(target, key, value) {
if (!target[key]){
throw new Error('属性不存在')
}
if (key === 'name') {
// 判断类型
if (typeof value === 'string') {
target[key]=value
} else {
throw new Error('name属性只能设置字符串')
}
}
}
//
})
// 代理对象属性赋值,触发set
objProxy.name = 'rose'
// 不存在friend 报错
objProxy.friend = 'rose'
实际应用:
Vue3的响应式原理-传送门
-
通过
Proxy创建响应式对象 -
getter/setter用于ref -
Vue2考虑兼容,用的是兼容性好的
Object.defineProperty,但是无法跟踪动态增加的属性 -
Vue3中用了Proxy,他对于动态增加的属性,也可以检测到,但是Vue3中也用了Object.defineProperty-
reactive用的是Proxy- 注意点:解构之后会丢失响应性,需要用
toRefs
- 注意点:解构之后会丢失响应性,需要用
-
ref用的是Object.defineProperty
-
-
观察者模式-->虚拟dom->diff算法
迭代器模式
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示.简而言之就是:遍历
遍历作为日常开发中的高频操作,JavaScript中有大量的默认实现:比如
Array.prototype.forEach:遍历数组NodeList.prototype.forEach:遍历dom,document.querySelectorAllfor infor of
面试题:
for in和for of的区别?
Object.prototype.objFunc = function () { }
Array.prototype.arrFunc = 'arrFunc'
const foods = ['西瓜', '西葫芦', '西兰花']
for (const key in foods) {
console.log('for-in:key', key)
}
for (const iterator of foods) {
console.log('for-of:iterator', iterator)
}
可迭代协议和迭代器协议:
-
可迭代协议:传送门
- 给对象增加属方法
[Symbol.iterator](){} - 返回一个符合迭代器协议的对象
- 给对象增加属方法
-
迭代器协议:传送门
- next方法,返回对象:
{done:true},迭代结束{done:false,value:'xx'},获取解析并接续迭代
- next方法,返回对象:
-
面试问及:
- for of可以遍历一部分的类型,比如数组,map
- 对象无法遍历,因为对象没有实现 可迭代协议,迭代器协议
- 可迭代协议,迭代器协议,约定了:
- 可迭代协议:对象上要有一个指定属性的函数,返回 满足迭代器要求的对象
- 迭代器协议:
next方法,返回{done:true},{done:false,value:'x'} - 我自己尝试写过一下,但是仅针对语法
- 可以和面试官讨论一下,可以用在哪?
-
直接打印对象,看到Symbol(Symbol.iterator),说明可以使用
for of
JS调用栈
这一节开始学习JS调用栈
这一节咱们会学习:
- 执行上下文和调用栈
- 栈溢出
执行上下文和调用栈
执行上下文:是指在代码执行时,JavaScript引擎创建的一种数据结构,它包含了函数执行时的状态信息,例如变量、函数参数、函数返回值等。
在以下三种情况下会创建执行上下文
-
JavaScript执行全局代码时,创建全局执行上下文
-
调用函数时,创建函数执行上下文
-
使用 eval 函数时,创建执行上下文
- 给他一个字符串,解析为js并执行
我们通过调试工具确认一下
const num = 0
function funA(a, b) {
return a + b
}
function funcB(c) {
const res = funA(1, 2)
return res + c
}
num = funcB(3)
调用栈:
- 执行上下文会存在JS调用栈中,栈的结构特点是:先进后出
栈溢出
栈的容量是有限的,如果内部的内容一直得不到释放,就会出现栈溢出,比如
function sum() {
let i = 0;
i++
sum()
}
sum()