swiper https://uniapp.dcloud.net.cn/component/swiper.html
首页有100条数据
如果渲染更多的swiper,那么数据列表的内容越来越多,导致创建了很多的节点,这时的vue中的different就需要对比很多次,造成内存消耗和内存占用
长列表渲染大量的节点导致的问题:
占用大量的GPU资源,导致卡顿
节点越多,就越耗费性能
长列表写法
<template>
<view class='main'>
<!--长列表-->
<view :style="{ height:itemHeight * listAll.length + 'px' }" class='swiper'>
<!--课时区域渲染的数据-->
<view class='swiper-item' :style='{top:top+"px"}'>
<view v-for='item in showList' :key='item.id' :style="{width:'100%',height:height+'px'}">
<image :src="item.skus[0].resources[0].resPath" class='goods-img'></image>
</view>
</view>
</view>
</view>
</template>
<script>
/*
1. 如果加载10条,超过了怎么办?
2. 加入swiper
*/
import { goodsPage } from '@/utils/api/goods.js'
export default{
data () {
return {
listAll:[], //所有数据 : 后端给的10W条数据
showList:[], //可视区域显示的数据
itemHeight:100, //每一条数据的高度
showNum:3, //每次加载到可视区域的数据
top:0, //偏移量
scrollTop:0, //滚动距离
startIndex:0, //截取数据的开始位置
endIndex:0, //截取数据的结束位置
pages:{ //拿到后端的数据量
current:1,
size:100
},
height:0, //可视区域高度
}
},
onLoad() {
//获取初始化数据
this.getAllList();
//可视区域高度
this.itemHeight = this.height = uni.getSystemInfoSync().windowHeight;
},
//监听页面滚动
onPageScroll( e ){
//滚动距离赋值
this.scrollTop = e.scrollTop;
//截取数据范围
this.getShowList();
},
methods:{
//初始化拿到的后端数据
async getAllList(){
//拿到后端给的所有数据
let { current , size } = this.pages;
let res = await goodsPage( { current,size });
this.listAll = Object.freeze(this.convert( res.data.records ));
//拿到截取后的数据
this.getShowList();
},
//转换后端给的数据
convert( record ){
record.forEach( item=>{
(item.skus).forEach( skusItem=>{
skusItem.resources = JSON.parse(skusItem.resources);
})
})
return record;
},
//计算可视区域高度
getShowList(){
//滚动的距离
this.scrollTop = this.scrollTop || 0;
//滚动的距离/某一个元素的宽度
this.startIndex = Math.floor(this.scrollTop/this.itemHeight);
//开始位置+每次加载几个数据
this.endIndex = this.startIndex + this.showNum;
//截取展示到可视区域的数据
this.showList = this.listAll.slice( this.startIndex, this.endIndex );
//item作为整除 , 随机滑时第一条数据的偏移量
this.top = this.scrollTop - ( this.scrollTop % this.itemHeight );
}
}
}
</script>
<style>
.main{
width: 100vw;
height: 100vh;
}
.swiper{
position: relative;
}
.swiper-item{
width:100%;
position: absolute;
}
.goods-img{
width:100%;
height: 100%;
}
</style>
虚拟列表的实现非两种
列表高度固定
列表高度动态
<template>
<view class='main'>
<swiper :style="{ height:height+ 'px' }" class='swiper' vertical @change='swiperChange'
:current='swiperActiveIndex' :circular='true'>
<swiper-item :style="{ height:height+ 'px' }" class='swiper-item' v-for='(item,index) in swiperList'
:key='index'>
<view class='goods-item' @click='show=true'>
{{ item.title }}
</view>
<image @click='detailGoods(item.id)' :src="item.skus[0].resources[0].resPath"
:style="{ height:height+ 'px' }"></image>
</swiper-item>
</swiper>
<!--价格弹出层-->
<u-popup :show="show" mode="bottom" @close="show=false">
<view class="transition">
<view class='transition-content'>
<view>{{ changePrice( goods.price) }}</view>
<view>{{ goods.title }}</view>
<view style='display: flex;justify-content: space-around;'>
<button open-type='share'>
<view>分享</view>
</button>
<view @click='$u.debounce(fav, 500)' :class='goods.isFav==1?"fav-active":""'>收藏</view>
<view @click="goPayment">立即购买</view>
</view>
</view>
</view>
</u-popup>
<!--提示信息-->
<u-toast ref="uToast"></u-toast>
<!--选择规格-->
<u-popup :show="isShow" mode="bottom" @close="isShow=false">
<view>
<view class='go-title'>
<image :src='goodsCurrent.img'></image>
<text>{{ changePrice( goodsCurrent.currentPrice) }}</text>
</view>
<view class='goods-list'>
<view class='list-title' v-for='item in spec' :key='item.id'>
<view class='list-type'>{{item.name}}({{item.children.length}})</view>
<view class='list-info'>
<view class='list-name' v-for='(k,index) in item.children' :key='index'
:class='k.active?"active":""' @click="selectCommodity(item,k.name)">{{ k.name }}</view>
</view>
</view>
</view>
<view v-if="goodsCurrent.stock<=0">当前商品没了~</view>
<view class='go-number'>
<view>购买数量</view>
<u-number-box :min="1" v-model="number"></u-number-box>
</view>
<view class='go-payment'>
<button :disabled="goodsCurrent.stock<=0" @click="confirmOrder">立即购买</button>
</view>
</view>
</u-popup>
</view>
</template>
<script>
import {
goodsPage,
addCollect,
deleteCollect,
getGoods
} from '@/untils/api/goods.js';
import {
priceMixin
} from "@/mixins/price";
import {
mapState
} from 'vuex'
export default {
mixins: [priceMixin],
data() {
return {
dataList: [], //所有数据
swiperList: [], //dom展示的数据
swiperLength: 3, //轮播图总数量
pages: { //请求接口的参数
current: 1,
size: 10
},
height: 0, //可视区域高度
currentIndex: 0, //当前展示数据在列表中的索引
swiperActiveIndex: 0, //当前轮播图的索引
show: false, //点击商品
isShow: false, //点击立即购买
goods: {
img: '',
title: '',
price: '',
isFav: '',
id: '',
},
spec: [], //商品的规格
goodsCurrent: {}, //当前选择商品的价格,图片,规格...
number: 1,
}
},
onLoad() {
//获取初始化数据
this.getAllList();
//可视区域高度
this.height = uni.getSystemInfoSync().windowHeight;
},
computed: {
...mapState({
info: state => state.user.info
})
},
methods: {
xxx(id) {
},
//获取到所有数据
async getAllList() {
let {
current,
size
} = this.pages;
let res = await goodsPage({
current,
size
});
this.dataList = Object.freeze(this.convert(res.data.records));
//获得截取后的数据
this.getShowList();
},
//转换后端给的数据
convert(record) {
record.forEach(item => {
(item.skus).forEach(skusItem => {
skusItem.resources = JSON.parse(skusItem.resources);
})
})
return record;
},
//设置截取后的数据
getShowList() {
//起始位置
let currentValue = this.dataList[this.currentIndex];
//中间位置
let nextValue = this.dataList[this.getDataIndex(this.currentIndex + 1)];
//最后位置
let prevValue = this.dataList[this.getDataIndex(this.currentIndex - 1)];
//给要渲染的数据填充个数
this.swiperList = new Array(3);
//展示数据赋值操作
this.swiperList[this.swiperActiveIndex] = currentValue;
this.swiperList[this.getSwiperIndex(this.swiperActiveIndex + 1)] = nextValue;
this.swiperList[this.getSwiperIndex(this.swiperActiveIndex - 1)] = prevValue;
//当前展示商品
let goods = this.swiperList[this.swiperActiveIndex];
this.goods = {
img: goods?.skus[0]?.resources[0]?.resPath,
title: goods?.title,
price: goods?.skus[0]?.currentPrice,
isFav: goods?.isCollected,
id: goods?.id
}
},
//滑动轮播图触发的事件
swiperChange(event) {
let current = event.detail.current;
if ([1, 1 - this.swiperLength].includes(current - this.swiperActiveIndex)) {
//向下滑动
this.currentIndex = this.getDataIndex(this.currentIndex + 1);
} else {
//向上滑动
this.currentIndex = this.getDataIndex(this.currentIndex - 1);
}
this.swiperActiveIndex = current;
this.getShowList();
},
getDataIndex(index) {
if (index < 0) {
return this.dataList.length - 1;
} else if (index >= this.dataList.length) {
return 0;
} else {
return index;
}
},
getSwiperIndex(index) {
if (index < 0) {
return this.swiperLength - 1;
} else if (index >= this.swiperLength) {
return 0;
} else {
return index;
}
},
//进入商品详情
detailGoods(id) {
uni.navigateTo({
url: `/pages/goods?id=${id}`
})
},
//收藏功能
fav() {
let {
id
} = this.goods;
//要收藏
if (this.goods.isFav == 0) {
//接口
addCollect(id).then(res => {
console.log(res);
})
this.$refs.uToast.show({
type: 'success',
message: "成功",
})
//更新视图数据
this.goods.isFav = 1;
return;
}
//取消收藏
if (this.goods.isFav == 1) {
//接口
deleteCollect(id).then(res => {
console.log(res);
})
this.$refs.uToast.show({
type: 'success',
message: "取消收藏",
})
//更新视图数据
this.goods.isFav = 0;
return;
}
},
// 立即购买选择规格
goPayment() {
this.isShow = true
this.show = false
//当前商品详情
getGoods(this.goods.id).then(res => {
let spec = []; //最后的数据结构
let data = res.data;
console.log(data)
let specialSpecArray = data.specialSpecArray;
let specialSpec = JSON.parse(data.specialSpec);
let skus = data.skus; //选择出所有规格
//重构skus
for (let i = 0; i < skus.length; i++) {
skus[i].resources = JSON.parse(skus[i].resources);
skus[i].specialSku = JSON.parse(skus[i].specialSku);
//重构skus数据结构
for (let key in skus[i].specialSku) {
skus[i][key] = skus[i].specialSku[key];
skus[i]['img'] = skus[i].resources[0]['resPath'];
}
}
this.skus = skus
//选择默认当前规格
let goodsCurrent = skus[0];
this.goodsCurrent = goodsCurrent;
//整理最后的数据结构
specialSpecArray.forEach(v => {
let children = []; //spec数据结构内部的chidlren
//整理children的数据结构
for (let i = 0; i < specialSpec[v.id].length; i++) {
children.push({
name: specialSpec[v.id][i],
active: specialSpec[v.id][i] == goodsCurrent[v.id] ? true : false
})
}
spec.push({
id: v.id,
name: v.name,
children
})
})
this.spec = spec;
})
},
// 选择规格
selectCommodity(item, name) {
item.children.forEach(v => {
v.active = false;
if (v.name == name) {
v.active = true
}
})
// 切换数据,对应展示不同的数据
// console.log(this.name)
let currentObj = {};
for (let i = 0; i < this.spec.length; i++) {
this.spec[i].children.forEach(v => {
// console.log(v)
if (v.active) {
currentObj[this.spec[i].id] = v.name
}
})
}
console.log(this.skus, currentObj)
// 切换数据,对应展示数据
for (let i = 0; i < this.skus.length; i++) {
let item = this.skus[i];
// console.log(this.skus[1], currentObj)
let number = 0; //同价规格有几种情况
for (let k in currentObj) {
if (item[k] == currentObj[k]) {
number++
}
}
// 如果相等的次数一样,则表示命中,那么就会把这个数组中的对象提炼出来
if (number == Object.keys(currentObj).length) {
this.goodsCurrent = item
}
}
},
confirmOrder() {
// 判断是否是登录状态
if (!this.info.status) {
return uni.navigateTo({
url: '/login/login'
})
}
//2. 组织传递的参数
let {
id,
spuId,
img,
title,
currentPrice,
} = this.goodsCurrent;
let params = {
skuId: id,
spuId,
counter: this.number,
img,
price: currentPrice,
number: this.number,
title
}
console.log(params)
//3. 进入确认订单页面
uni.navigateTo({
url: `/order/order/confirmOrder?goods=${ JSON.stringify( params ) }`
})
}
}
}
</script>
<style lang='scss'>
.main {
width: 100%;
.swiper-item {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
}
.goods-item {
position: absolute;
z-index: 999;
bottom: 64rpx;
right: 34rpx;
width: 90%;
padding: 10rpx;
background: #0E0E0E;
border-radius: 36px 36px 36px 36px;
color: #fff;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
opacity: 0.5;
}
.transition-img {
width: 100%;
height: 770rpx;
}
.transition-img image {
width: 100%;
height: 100%;
}
.transition-content {
background-color: #FFFFFF;
}
.fav-active {
color: red;
}
/*立即购买*/
.go-title {
display: flex;
align-items: center;
image {
width: 115rpx;
height: 116rpx;
background: #FFFFFF;
border-radius: 7rpx 7rpx 7rpx 7rpx;
border: 1px solid #707070;
}
text {
font-size: 43rpx;
font-weight: bold;
color: #2C00FF;
}
}
.goods-list {
padding: 80rpx;
.list-title {
padding: 27rpx 0;
}
.list-info {
display: flex;
.list-name {
background: #F7F7F7;
border-radius: 7rpx 7rpx 7rpx 7rpx;
}
.active {
border: 4rpx solid #2C00FF;
}
}
}
.list-name+.list-name {
margin-left: 20rpx;
}
.go-number {
display: flex;
align-items: center;
justify-content: space-between;
}
.go-payment {
display: flex;
align-items: center;
align-items: center;
}
.go-payment button {
width: 490rpx;
height: 85rpx;
color: #fff;
background: linear-gradient(128deg, #525252 0%, #000000 100%);
border-radius: 43rpx 43rpx 43rpx 43rpx;
}
</style>
十、性能问题
10.1 你做过哪些性能优化
a> 冻结对象 : Object.freeze
因为如果vue直接复制给data属性值,会走observer,使用Object.freeze冻结可以提升效率
****这个对象以后不能修改了
10.2 假如后端一次性给你10W条数据,你如何把数据放在页面
虚拟列表 、 长列表
前端拿到后端给的10w条数据,前端在页面上所呈现的数据是有限的(3个:从总的数据中截取出来)
十一、面试题
1. 关于this.$nextTick
干什么的:获取更新后的dom
原理:是一个异步的行为
class Vue{
constructor( options ){
options.created.bind(this)();
this.$el = document.querySelector(options.el);
options.mounted.bind(this)();
}
$nextTick( callback ){
return Promise.resolve().then(res=>{
callback()
})
}
}
new Vue({
el:"#app",
created(){
console.log( '这是created' );
this.$nextTick(()=>{
console.log( 'created', this.$el );
})
},
mounted(){
console.log( 'mounted', this.$el );
}
})
2. 如果要在created中获取dom怎么办?
异步就行了 , 比如使用$nextTick
十二、分包处理
原因:所有分包大小不超过20M 单个分包/主包大小不能超过2M
解决方案:小程序分包加载
面试中:
1. 小程序项目上线你会做什么?
分包
2. 小程序项目如果超过体积大小了怎么办?
分包
流程:
1> 打开manifest.json源码视图
"mp-weixin" : {
"optimization":{"subPackages":true}
},
2> 打开pages.json进行分包
pages:[],
"subPackages": [
{
"root": "login",
"pages": [
{
"path": "login"
},
{
"path": "bindPhone"
}
]
}
],
面试题:如果想让vue中的created放在mounted后执行
上代码
export default{
async created(){
let res=await axios({
url:'',
})
console.log(res)
console.log('created')
},
mounted(){
console.log('mounted')
}
}
父子组件生命周期的执行顺序
js原生是一个单线程语言
event loop事件循环机制
先执行同步代码
再执行异步代码:可以操作执行时间的,不确定执行时间的事件
异步分为2类(微任务、宏任务)
微任务:promise.all promise.resolve.then……
宏任务:定时器
先执行微任务在执行宏任务
在上图的情况下,先执行微任务在执行宏任务,后比较两个setTimeout那个时间段先执行哪一个
谁快先执行谁300秒 295秒 200秒
十三、混入
混入就是在.vue文件【组件】,可复用功能【写js】,也能用生命周期
混入的缺点 :混入中的属性和方法,在组件中去使用,很难找到来源(难以维护)https://www.xuexiluxian.cn/blog/detail/d6411636820744289a735d55a465fb0a
十一、2月24作业
1. 项目登录做了,多去看看逻辑上哪里有问题?
2. 如何把表格的数据放在页面上(导入)
3. 如何实现架构图
4. 数据重构 ex : 扁平化数据转换成多叉树 ...
import XEUtils from 'xe-utils';
获取可是区域的高度
//获取可视区域高度
this.height = uni.getSystemInfoSync().windowHeight;//不包含tabbar
this.$el.clientHeight//包含tabbar
相关作者
- 获取点赞0
- 文章阅读量264
评论()