JavaScript(二)-cnblog
文章目录
异步编程
1. Generator函数
- 生成迭代器函数,与普通函数不同,可以分段执行
基本使用
function *go(){
console.log('1')
// yield "a" 这个整体 说next函数的一次返回值
// 下一次next的参数传递给a,与右边无关
let a=yield "a"
console.log("2")
let b=yield a
console.log("3")
return b
}
// 直接执行返回的是一个迭代器对象
let it=go("test")
// 第一次next,返回值是一个对象
let r1=it.next() // {done:false,value:"a"}
// 第二次next
let r2=it.next("传递的参数") // 参数会赋值给上一次的yield的左边
// 也即 let a="传递的参数"
// 返回值 yield a //也就是a {done:false,value:"传递的参数"}
// 第三次next,走完了
let r3=it.next("666") // "666参数给上一次yield的左边 let b="666"
// r3={done:true,value:"666"}
一道例题
function* test(num) {
let x = 3 * (yield(num + 1));
let y = yield(x / 3);
return (x + y + num);
}
let n = test(6);
console.log(n.next()); // {done:false,value:7}
console.log(n.next(3)); // {done:false,value:3}
console.log(n.next(3)); // {done:false,value:18}
for of 迭代Generator函数
function* test() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of test()) {
console.log(v);
}
// 1
// 2
//3
// 4
// 5
// 6
对象可迭代
- 本来 for of 不能用于对象的属性的遍历
let obj={
name:'zs',
age:18
}
function* test(obj){
// 我们yield化
let keys=Reflect.ownKeys(obj)
for(let key of keys){
// yield 右边的值,即为迭代的值
yield [key,obj[key]]
}
}
for(let item of test(obj)){
console.log(item)
}
// {0:"name",1:"zs"}
// {0:"age",1:18}
2. yield * 语句
- 用于在Genarator函数中调用另一个Genarator函数
function* test() {
yield 'a';
yield 'b';
}
function* test1() {
yield 'x';
yield* test();
yield 'y';
}
for (let v of test1()) {
console.log(v);
}
// 等价于
function* test() {
yield 'a';
yield 'b';
}
function* test1() {
yield 'x';
for (let v of test()) {
console.log(v);
}
yield 'y';
}
for (let v of test1()) {
console.log(v);
}
3. Genarator函数中的this指向
- 创建构造函数
function* Person(){
yield this.name=""
yield this.age=""
}
let p=new Person() // 报错
// 解决
let p={}
// 修改this指向
let it=Person.bind(p)()
it.next()
4. Generator 的应用
状态管理
- 需求:实现状态的循环 0,1,2…循环
// 实现图片的切换
let btn=document.querySelector("#btn")
let img=document.querySelector("#img")
// 传统写法
let flag=false
btn.addEventListener("click",function(){
if(!flag){
img.src='images/b.png'
}else{
img.src='images/a.png'
}
flag=!flag
})
- 使用yield每次返回不同状态
let it=test()
btn.addEventListener("click",function(){
it.next() // 每走一步指向一次循环内的操作
})
function *test(){
while(true){
img.src='images/b.png'
yield 1
img.src='images/a.png'
yield 2
}
}
实现异步请求
// 发起ajax请求
function ajax(url){
makeAjaxCall(url,function(res){
it.next(response)
})
}
// 我们写一个Generator 函数,把这个ajax请求独立成一步,然后和同步代码同步指向
function* f(){
// 同步等待ajaxi
let result=yield ajax(url)
console.log(result)
}
let it=f()
it.next()
5. Promise对象
基本使用
let promise=new Promise(function(resolve,reject){
setTimeout(function(){
let num=Math.random()
if(num<0.3){
resolve("成功")
}else{
reject("失败")
}
},3000)
})
promise.then(
function(value){
console.log(value)
},
function(reason){
console.log(reason)
}
)
函数的promise化,promise化ajax请求
// 返回值为一个promise对象
let myAjax=function(url){
let p=new Promise((resolve,reject)=>{
// 正常发起ajax请求
let xhr=new XMLHttpRequest()
xhr.open(url)
xhr.setRequestHeader("Accept","application/json")
xhr.send()
xhr.onreadystatechange=function(){
if(xhr.readystate===4&&xhr.status===200){
resolve(xhr.responseText)
}else{
reject(new Error(xhr.statusText))
}
}
})
return p
}
myAjax("http://xxx.com").then(res=>{
// 成功的回调
},err=>{
// 失败的回调
})
promise化链式调用解决回调地狱问题
.then
方法返回一个全新的promise对象
let p1=myAjax("http://x.com").then(res=>{
// 成功之后发起下一次ajax请求
return myAjax(res)
},err=>{
})
p1.then(res=>{
// 第二次ajax请求的解果
console.log(res)
},err=>{
})
// 而不是回调地狱的格式
myAjax("http://x.com").then(res=>{
// 成功之后发起下一次ajax请求
myAjax(res).then(res2=>{
// 二次ajax请求的解构
},err2=>{
})
},err=>{
})
Promise.all
- 并行的异步请求
// 其中一个失败,Promise.all失败
Promise.all([
myAjax("http://x.com"),
myAjax("http://p.com"),
// 注意如果这个请求失败,但是后面捕获到了(添加了catch),返回的是一个全新的promise对象,不会认为请求失败
myAjax("http://g.com").catch(err=>)
]).then(res=>{
},err=>{
}).catch(err=>{
})
Promise.race
- 多个异步操作,一个成功就结束promise
// 应用场景:1s内请求没有回来,就认为超时了
Promise.all([
myAjax("http://x.com"),
myAjax("http://p.com"),
//时间超时
setTimeout(()=>reject(new Error('timeout'),1000)
]).then(res=>{
},err=>{
}).catch(err=>{
})
promise.resolve(val)
- 将值转换成功的promise对象
promise.reject(val)
- 将值转换为失败的promise对象
6. 手写Promise
// 构建自己的promise对象
function MyPromise(task){
let that=this
// 设置当前promise的对象
that.status='Pending'
// 设置resolve或者reject的值
that.value=undefined
// 设置成功或者失败的回调函数数组
this.onResolvedCallbacks=[]
this.onRejectedCallbacks=[]
// 定义resolve和reject函数
function resolve(value){
// reject函数被执行
// 判断状态,状态只能改变一次,每次返回新的promise对象
if(that.status==='Pending'){
that.status='Resolved'
// 修改完成之后,(.then)执行成功或者失败的处理函数
that.value=value
// 执行成功的回调函数
this.onResolvedCallbacks.forEach(item=>{
item(that.value)
})
}
}
function reject(reason){
// reject函数被执行
// 判断状态,状态只能改变一次,每次返回新的promise对象
if(that.status==='Pending'){
that.status='Rejected'
// 修改完成之后,(.then)执行成功或者失败的处理函数
that.value=value
// 执行失败的回调函数
this.onRejectedCallbacks.forEach(item=>{
item(that.value)
})
}
}
// 执行resolve和reject函数
// 我们处理.catch的方法
try{
task(resolve,reject)
}catch(e){
reject(e)
}
}
// 每个实例对象上都存在.then方法
MyPromise.prototype.then=function(onFulfilled,onRejected){
let that=this
if(that.status==='resolved'){
onFulFilled(that.value)
}
if(that.status==='rejected'){
onRejected(this.value)
}
// 每次.then是不是都会传入成功失败的回调函数
that.onResolvedCallbacks.push(onFulfilled)
that.onRejectedCallbacks.push(onRejected)
}
// 使用promise对象
// 传递的参数为一个函数,参数为resolve和reject对象
let p=new MyPromise(function(resolve,reject){
// 在这个函数体里面会调用resolve和reject函数
})
- 总结:看起来更像是一种设计模式,对一个异步操作,有成功和失败的状态,成功怎么处理,失败怎么处理 (if,else ===> getType() 0:fnxx 1:fnxxx)
7. 异步编程总结
- 回调函数:回调地狱问题
- promise:处理回调地狱问题(.then链式调用)
- Generator:异步操作在代码里体现为同步
- async:promise+Generator的合成体
async
async function test() {
let result = await Math.random();
console.log(result);
}
test();
- Generator
function* main() {
let result = yield request("http://xxx.com/api");
let resp = JSON.parse(result);
console.log(resp.value);
}
function request(url) {
makeAjaxCall(url, function(response){
it.next(response);
});
}
let it = main();
it.next();
注意到
let result = await Math.random();
let result = yield request("http://xxx.com/api");
// 是不是很像
8. async 的基本使用
function ajax(second){
return new Promise(function(resolve,reject){
setTimeout(()=>{
let num=Math.random()
if(num>0.5){
resolve('成功了')
}else{
reject('失败了')
}
},second)
})
}
async function awaitDemo(){
let result=await sleep(3000)
return result
}
awaitDemo().then((res)=>{
},err=>{
})
存在依赖关系的async
function ajax(second){
return new Promise(function(resolve,reject){
setTimeout(()=>{
let num=Math.random()
if(num>0.5){
resolve('成功了')
}else{
reject('失败了')
}
},second)
})
}
async function awaitDemo(){
// 这一步的result作为另一个异步操作的输入
let result=await sleep(3000)
// 这里我们可以先对result进行处理
// ... 一系列同步操作
// 其实await修饰的的右方是异步操作
// 但执行还是按同步的规则来,也就是result的额外处理完成后,下次请求才会发起
let result2=await sleep(3000+result)
let result3=await sleep(result2+3000)
return result
}
awaitDemo().then((res)=>{
},err=>{
})
并行关系的异步请求
function ajax(second){
return new Promise(function(resolve,reject){
setTimeout(()=>{
let num=Math.random()
if(num>0.5){
resolve('成功了')
}else{
reject('失败了')
}
},second)
})
}
async function awaitDemo(){
// 三个ajax请求相互没有关联,并行发送
// 全部完成后关闭loading效果
let p1=ajax(3000)
let p2=ajax(3000)
let p3=ajax(3000)
let ps=await Promise.all([p1,p2,p3])
// ps为三次请求结果的数组
console.log('关闭loading')
}
awaitDemo().then((res)=>{
},err=>{
})
模块化
1. 模块化的发展过程
- 最主要的问题:解决模块之间的冲突问题
每个模块导出一个对象,方法和属性都挂载在对象上
var moduleObj = {
userName: 'zhangsan',
fn: function () {
console.log('hello world')
}
}
立即执行函数,形成一个独立的函数作用域
; (function () {
var userName = 'lisi';
function fn1() {
console.log(userName);
}
function fn2() {
console.log('hello world')
}
window.moduleObj = {
fn1: fn1,
fn2: fn2
};
})()
2. 模块化规范
node端
- CommonJS
1. 一个文件一个模块
2. 导出module.exports
3. 导入required
4. 每个模块独立作用域
AMD:异步模块化定义规范
// 解决浏览器端使用异步加载模块效率低的问题
ES
浏览器端的导入导出规范
1. 严格模式,默认this为undefined
2. 每个模块独立作用域
3. 延迟脚本执行,defer
// 基本使用
// src属性可用也可不要,可以直接使用import语法
<script type="module" src>
// import 语法
</script>
- 导出规则
export {v1,v2} // 导出的是v1,和v2遍历
export default {v1,v2} // 默认导出,导出的是一个对象,包含v1,v2属性
1、注意语法的问题
2、通过export 对成员进行导出操作,导出的是成员的引用
3、导出的成员是只读的,那么我们导入了以后是不能对成员进行修改的。
- 导入规则
1、import后面的from跟的是导入的文件的路径,并且是一个完整路径。
2、如果我们这里是执行某个模块,并不需要提取其中的成员
import {} from './module.js'
import './module.js
3、如果某个模块中导出的成员比较多,同时我们都需要这些导出的模块成员,
import * as m from './module.js'
4、如果需要进行动态的导入
import('./module.js').then(function(module) {
console.log(module.)
})
5、在进行导出的时候,使用了export 和export default,导入import应该怎样进行处理?
import title, { userName, userAge } from "./module.js";
项目的组件导入技巧
- components文件夹
button.js
export const button='button'
text.js
const text='编辑组件'
export default text
index.js
export {button} from './button.js'
export {default as text} from './text.js'
其他页面使用
import {button,text} from './components/index.js'
性能优化
1. 内存管理
- 内存的申请,使用,释放
2. 垃圾回收
- 释放不在被引用的对象占用的内存空间
3. 垃圾回收算法
- 引用计数法
// 1. 标记对象存在的引用树
// 2. 引用数量为0释放
// 优点,立即回收,减少查询暂停的情况
// 缺点,循环引用时无法释放,时间开销大
- 标记清除法
标记:标记从顶层对象找不到的变量
清除:清除被标记的变量
缺点
- 磁盘碎片化,内存空间不连续
标记整理算法
- 标记:标记从顶层对象找不到的变量
- 整理:把标记和没有标记的都放在一块连续的空间,然后释放标记的
优点
- 解决了磁盘碎片化的问题
缺点
- 时间开销变大
4. v8引擎的分代回收
新生代回收算法
1. 回收时,分成from和to空间(空白且与from空间等大)
2. 标记from空间中活动的空间
3. 移动标记的空间到to空间
4. 释放from空间
5. 交换from to空间
老生代回收算法
// 标记清除
1. 标记活动的空间
2. 释放没有标记的空间
// 标记整理
1. 标记活动的空间
2. 移动标记的空间到连续的内存块
3. 释放前面一段没有标记的空间
6、V8引擎垃圾回收的机制
V8
引擎采用分代回收的思想,将内存分为新生代和老生代。
第一步: 在From空间中分配了3个对象A、B、C
第二步:GC
进来判断对象B没有其他引用,可以回收,对象A和C依然为活跃对象.
第三步:将活跃对象A、C从From空间复制到To空间
第四步:清空From空间的全部内存
第五步:交换From空间和To空间
第六步:在From空间中又新增了2个对象D、E
第七步:下一轮GC
进来发现对象D没有引用了,做标记
第八步:将活跃对象A、C、E从From空间复制到To空间
第九步:清空From空间全部内存
第十步:继续交换From空间和To空间,开始下一轮
当一个对象经过多次复制后仍然存活,它就会被认为是生命周期较长的对象,这种生命周期较长的对象会被移动到老生代中。
对象从新生代移动到老生代的过程就叫做晋升。
对象晋升的条件主要有两个:
第一:对象从From空间复制到To空间时,会检查它的内存地址来判断这个对象是否已经经历过一次清除回收。如果已经经历过了,会将该对象从From空间移动到老生代空间中,如果没有,则复制到To空间
第二:当要从From空间复制一个对象到To空间时,如果To空间已经使用了超过25%,则这个对象直接晋升到老生代中。设置25%这个阈值的原因是当这次清除回收完成后,这个To空间会变为From空间,接下来的内存分配将在这个空间中进行。如果占比过高,内存就不够了,这样就会影响后续的内存分配。
下面我们再来看一下**V8
引擎对老生代对象回收的实现过程。**
7. 全局变量问题
- 局部作用域查找全局作用域的变量耗时
- 内存占用时间长
做标记
[外链图片转存中…(img-gxnDlueQ-1678503311904)]
第八步:将活跃对象A、C、E从From空间复制到To空间
[外链图片转存中…(img-yf4umkzQ-1678503311904)]
第九步:清空From空间全部内存
[外链图片转存中…(img-814FBnh2-1678503311904)]
第十步:继续交换From空间和To空间,开始下一轮
[外链图片转存中…(img-jgCOgTdY-1678503311905)]
当一个对象经过多次复制后仍然存活,它就会被认为是生命周期较长的对象,这种生命周期较长的对象会被移动到老生代中。
对象从新生代移动到老生代的过程就叫做晋升。
对象晋升的条件主要有两个:
第一:对象从From空间复制到To空间时,会检查它的内存地址来判断这个对象是否已经经历过一次清除回收。如果已经经历过了,会将该对象从From空间移动到老生代空间中,如果没有,则复制到To空间
第二:当要从From空间复制一个对象到To空间时,如果To空间已经使用了超过25%,则这个对象直接晋升到老生代中。设置25%这个阈值的原因是当这次清除回收完成后,这个To空间会变为From空间,接下来的内存分配将在这个空间中进行。如果占比过高,内存就不够了,这样就会影响后续的内存分配。
下面我们再来看一下**V8
引擎对老生代对象回收的实现过程。**
[外链图片转存中…(img-1FRHfKYP-1678503311905)]
[外链图片转存中…(img-dLR0OSv0-1678503311905)]
[外链图片转存中…(img-PNmMLCft-1678503311906)]
[外链图片转存中…(img-Oi38cME3-1678503311906)]
[外链图片转存中…(img-GTdZKFRm-1678503311906)]
[外链图片转存中…(img-SjVE1Pgf-1678503311907)]
[外链图片转存中…(img-dGjSL31d-1678503311907)]
7. 全局变量问题
- 局部作用域查找全局作用域的变量耗时
- 内存占用时间长
- 变量命名冲突