JavaScript--闭包和Promise
闭包
闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。
概述:闭包是一种书写代码一种结构,这种结构拥有的特性就是内部的空间在使用中不会被回收。(内部的变量以及对应的参数不会被gc回收)
函数的预编译过程
- 开辟函数的内存空间
- 开辟一个代码的对应的内存空间。
- 将对应的代码块的引用放入这个函数的内存空间
函数的执行过程
- 开辟一个执行空间(执行栈)
- 将对应的函数的里面的代码块内容的放到方法执行栈中进行执行
- 执行完 对应的执行空间销毁了 那么对应的里面的代码块内容也就销毁(gc 回收)
函数的示例
function reduce(){
var i = 0
i++
}
reduce() //1
reduce() //1
//因为里面的i执行完以后就会销毁 那么对应每次创建的i这个变量都是一个新的变量
- 通过上述的示例代码,我们知道对应的每次执行函数,里面的变量或者参数都会重新声明,这个也就意味着每次里面的变量或参数都是一个新的变量,这个时候有些情况我们就不满足。那么如果我想让他里面的变量不回收,是不是需要有个内容给他保持引用,我的变量就不会被gc 回收。
- 根据上述讲解 我们就可以得到 为什么i为全局变量的时候 俩次的值不一样
var i = 0
function reduce(){
i++
}
reduce() //1
reduce() //2
// 因为i保持对应的引用 所以没有被回收
- 那么根据上述可得 如果我在函数内返回对应的一个引用,在这个引用内包含对i的引用 那么i 是不是也不会被回收。
function reduce(){
var i = 0
i++
retrun {
i
}
}
let obj = reduce()
console.log(obj.i)//1
obj.i++
console.log(obj.i)//2
- 根据上述代码 就是如果我的函数里面返回一个引用 这个引用内包含了对应外部函数的参数或变量,那么我的这个外部函数的参数他不会随着函数里面代码的块的销毁而销毁。那么我同时又知道函数也是引用数据类型,那么我们是不是可以直接返回函数(方便调用)。内部函数包含外部函数的变量 这个时候对应的变量也不会被销毁了。
function reduce(){
var i = 0
return function(){
i++
return i
}
}
//调用
let fn = reduce()
console.log(fn()) //1
console.log(fn()) //2
函数嵌套函数(返回函数),内部函数保持对应外部函数的变量(参数)的引用。 这个称为闭包(外部的变量不会被gc回收)。
闭包的特性
- 可以通过内部函数引用 ,在调用内部函数的过程中访问外部函数的变量。
- 外部函数的参数不会被gc 回收
- 内部函数时刻保持对应外部函数的引用
闭包的优点
- 不会被gc回收
- 扩大外部函数内变量的使用范围
- 避免函数内的变量被外部所污染
闭包的缺点
- 导致内存泄漏(内存溢出) 消耗内存
- 内部函数要时刻保持对应外部函数的引用
闭包的运用
- 作为缓存
- 防抖 节流
- 函数柯里化
防抖
概述:在规定时间内只执行一次(执行最后一次)
电梯关门案例
- a 进入电梯 等待5s后 就可以上升了
- 在a等待了4s中后 b过来 那么之前的等待就结束了 开始新的等待
- 在b等待了3s后 c过来 那么之前的等待也结束了 开始新的等待
- .... 直到最后一次等待结束 电梯就上升 (实际电梯上升这个操作 只执行一次 是最后一次)
防抖的实现
//执行的函数 等待的时间
function debounce(fn,delay){
var timer = null //记录有没有人在等
return function(){
if(timer) clearTimeout(timer) //有人等 上次等待清除
timer = setTimeout(fn,delay) //开始新的等待
}
}
示例
let fn = debounce(function(){
console.log('移进去了');
},500)
//div移进只执行最后一次
document.querySelector('div').onmouseenter = function(){
fn()
}
节流
概述: 在一定时间范围内 执行第一次 (减少执行次数)
高铁上厕所案例
- 当灯是绿灯的时候 a进去了 这个灯变红灯
- a 没有执行完的时候 b进不去 只有当a 执行完 把红灯变成绿灯 b才能进去
- 同样的 c也是一样的
function throttle(fn,delay){
var timer = null //默认没有人
return function(){
//判断当前是否有人
if(timer) return //有人不做了 判断节流阀是否开启
timer = setTimerout(()=>{
fn()
timer = null //做完了 将节流阀关闭
},delay)
}
}
示例
let fn = throttle(function(){
console.log('移进去了');
},5000)
document.querySelector('div').onmouseenter = function(){
fn()
}
函数柯里化(颗粒化 )
概述:拆,将一个多个参数的函数,拆分为多个一个参数的函数,自由的组合,调用后续加强更好的更改。
参数传满了 返回的是对应的值 参数没传满返回的是函数
本身的函数
function sum(a,b){
return a + b
}
sum(1,2) //3
简单函数柯里化
//函数柯里化
function fn(a){
return function(b){
return a + b
}
}
fn(1)(2) //3
高阶函数柯里化封装(底层的支持 相关的维护)
//如果我的参数有100个 需要一个加强的函数 柯里化封装的函数
//fn 表示普通函数
function curring(fn){
//获取第一个传入的所有参数 截取 arguments
let arg = Array.prototype.slice.call(arguments,1) //Array.from(arguments).slice(1)
return function(){
let newArg = Array.from(arguments).concat(arg) //将前面的参数和后面的参数连接
//参数没到继续返回函数 参数到了才返回值
if(newArg.length<fn.length){
//继续调用curring方法 第一个参数传递 函数 第二个传递所有的参数
return curring.call(this,fn,...newArg)
}else{
return fn(...newArg)
}
}
}
let sum2 = curring(sum1,1)
// console.log(curring(sum1,1));
console.log(sum2(2)(3));
let sum3 = curring(sum1)
console.log(sum3(2)()()()()(3)()()(1));
promise
概述:promise是es6新增的用于解决回调地狱问题的一个类。
回调地狱
回调函数解决了异步的问题
setTimeout(()=>{
console.log('hello')
},0)
setTimeout(()=>{
console.log('hi')
},0)
console.log('吃饭')
//吃饭 === hello === hi
//正常顺序 hello === hi === 吃饭(使用回调函数)
setTimeout(()=>{
console.log('hello')
setTimeout(()=>{
console.log('hi')
console.log('吃饭')
},0)
},0)
概述:回调函数的无限嵌套 这个被称为回调地狱,它并不报错,只是这个代码失去了价值(没有了可维护性和可阅读性)
//声明一个函数fn 里面传入了一个f的回调函数
function fn(f) {
console.log('hello')
f() //执行回调函数
}
//可维护性没有了 可读性也没有了 当一个代码的可维护性和可读性都没有了那么这个代码就没书写的必要
//这个被称为回调地狱 这个代码并不会报错 但是他失去了可维护性和可读性 就没有了价值
fn(function () {
console.log('你好')
fn(function () {
console.log('世界')
fn(function () {
console.log('吃饭')
fn(function () {
console.log('睡觉')
fn(function () {
console.log('打豆豆')
//....
})
})
})
})
})
- promise 可以解决异步的执行问题 被设计为同步的 内部是异步的(异步代码)
- promise是es6新的增的类 它可以通过通过new关键词来构建(里面传递是一个函数 这个函数里面包含了俩个形参)
new Promise((resolve,reject)=>{
//代码
setTimeout(()=>{
console.log('hello')
})
})
promise简介
promise 翻译为中文叫做承诺,它具备三种状态 等待状态 成功状态 失败状态
成功状态(做相关处理)then 表示成功以后调用的函数(resolve函数调用 表示成功)
失败状态 (做相关处理)catch 表示失败以后调用的函数 (reject函数调用 表示失败)
等待状态 (没有处理)
//promise三种状态 等待 成功 失败
//每次只有一种状态 一旦成功就不会失败
new Promise((resolve,reject)=>{
//resolve表示成功 它是一个函数 它的调用表示成功
// resolve('成功了') //resolve里面传递的参数会到then里面
//reject表示失败 它也是一个函数 它的调用表示失败
reject('失败了') //传递值到catch里面
})
// .then(data=>{
// console.log(data) //resolve传递的参数
// }) //表示成功 里面也是个函数
.catch(error=>{
console.log(error);
}) //里面也是一个函数 里面的值就是reject里面传递的值
- resolve里面传递的参数会到then里面
- reject里面传递的参数会到catch里面
注意:无论 resolve()和 reject()中的哪个被调用,状态转换都不可撤销了。于是继续修改状态会静默
失败。
let p = new Promise((resolve, reject) => {
resolve();
reject(); // 没有效果
});
setTimeout(console.log, 0, p); // Promise <resolved>
promise的方法
静态方法
all 传入一个promise数组(可迭代对象) 并行执行 返回一个promise实例(所有的成功才会返回成功的 有一个失败直接返回的失败)
let promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('第一个promise');
resolve('1')
}, 10)
})
let promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('第2个promise');
reject('2')
}, 20)
})
let promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('第3个promise');
resolve('3')
}, 30)
})
let promise4 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('第4个promise');
reject('4')
}, 40)
})
//执行里面所有的promise 传入promise数组(实现了迭代器) 返回一个promise实例 (reject调用报错)
//then调用 要等所有成功了的才能返回 .catch 只要有一个reject调用它就会触发
let result = Promise.all([promise1,promise2,promise3,promise4])
result.catch(err=>{
console.log(err);
})
race 竞速 传入一个promise数组(可迭代对象) 并行执行里面promise 返回第一个执行的完的promise实例(镜像)
//竞速 并行执行里面promise 返回最先执行完的promise(不管是成功还是失败)
let first = Promise.race([promise1,promise2,promise3,promise4])
first.then(data=>{
console.log(`data`, data);
})
reject 传递一个数据 返回一个失败的promise对象
//reject 表示失败 resolve 成功
// 返回promise对象
let rejectPromise = Promise.reject('hello')
rejectPromise.catch(err=>{
console.log(err);
})
resolve 传递一个数据 返回一个成功的promise对象
let resolvePromise = Promise.resolve('hi')
resolvePromise.then(data=>{
console.log(data);
})
allSettled 传递一个promise数组 不相互依赖执行 返回一个promise实例 里面包含了所有执行的promise结果(有一个成功就成功了)
const pro1 = Promise.resolve(3);
const pro2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const pro = [pro1, pro2];
//不相互依赖执行 返回所有promise的结果
Promise.allSettled(pro).
then((results) => results.forEach((result) => console.log(result.status)));
非静态方法
then
catch
finally
// then 成功的处理
// catch 失败的处理
// 不管成功还是失败都会调用的 finally
promise1.finally(()=>{
console.log('完成了');
})
promise解决回调地狱
通过在then方法里面返回一个新的promise对象来解决回调地狱的问题
//promise来解决回调地狱
new Promise((resolve, reject) => {
setTimeout(() => {
console.log('第一句');
resolve()
}, 20)
}).then(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('第二句');
resolve()
}, 10)
})
}).then(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('第三句');
resolve()
}, 5)
})
}).then(()=>{
console.log('第四句');
})
es7 async await
概述:
es7 新增async关键词 和 await关键词 ,async是用于修饰(声明)函数的,await是用来修饰promise的,async修饰的函数会返回一个新的promise对象,await只能在async修饰的函数内使用。await会使当前的线程陷入阻塞,只有当前await修饰的promise完成后面的才能执行( 当前线程才会放开 )。
async
- 修饰函数的会返回的新的promise对象
- 函数内部return语句返回的值,会成为then方法回调函数的参数。
- 函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
// async修饰函数 修饰的函数它会返回一个promise对象
// 里面的return 相当 resolve调用
// 里面的报错 相当于 reject调用
async function fn() {
setTimeout(() => {
console.log('hello')
},0)
// throw new Error('你错了')
return '吃饭了码'
}
console.log(fn()); //promise对象
let promise = fn()
promise.then((value)=>{
console.log('world');
console.log(value);
})
// promise.catch((err)=>{ //来到catch
// console.log('world');
// console.log(err);
// })
await
- await 只能在async里面使用
- 正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
- 等待它会使当前的线程进行等待(只有当前的promise执行完(不管成功还是失败)它才会放开当前等待)
async function hello(){
await new Promise((resolve,reject)=>{ //线程等待
console.log(1);
resolve() //完成以后 线程放行
})
await new Promise((resolve,reject)=>{ //线程等待
console.log(2);
resolve() //完成以后 线程放行
})
console.log(3);
}
hello()