Supermall我的项目笔记
1.axios封装
能够在network中再创立独立的js文件,封装不同页面的网络申请,防止页面组件过大过于简单
2.轮播图
钩子挂载实现时,操作DOM树,开启定时器
mounted: function () { // 1.操作DOM, 在前后增加Slide setTimeout(() => { this.handleDom(); // 2.开启定时器 this.startTimer(); }, 3000) },
这里hanleDom()函数获取了元素数量,并克隆第一张和最初一张图片并插入到最初和最前,实现无缝滚动。
轮播图问题
图片加载慢时,会导致图片还没加载实现就执行了mounted中的函数操作DOM并开启了定时器
解决办法:
①原生的mutationObserver监听DOM树变动
//methods中办法swiperObserver() { const targetNode = document.querySelector('.swiper'); const config = {childList: true}; const callback = (mutationsList, observer) => { this.handleDom(); this.startTimer(); observer.disconnect(); }; const observer = new MutationObserver(callback); observer.observe(targetNode, config);}//mounted中调用//为了避免热更新时dom不从新加载而导致监听生效,所以加一个判断mounted() { if(document.querySelector(".swiper").childElementCount == 0) { this.observeSwiper(); } else { this.handleDom() this.startTimer() }}
②监听<img>的onload事件,加载实现后给swiper组件传入一个属性,通过watch监听该属性
<img :src="item.img" @load="imageLoad" alt="轮播图片" />
imageLoad() { if(!this.isLoad) { this.$emit('swiperImageLoad'); this.isLoad = true; }}
<home-swiper @swiperImageLoad></home-swiper>
swiperImageLoad() { this.handleDom() this.startTimer()}
3.商品数据的保留构造
申请三种数据,依据点击取出不同的数据。思考到加载工夫,不应该一次性申请全副数据
goods对象来保留数据
goods: { 'pop': {page: 1,list: {}}, //page保留当前页数,list记录所有数据 'new': {page: 1,list: {}}, 'sell': {page: 1,list: {}}}
4.滚动事件
在mounted中监听滚动事件,来决定显示暗藏返回顶部按钮
mounted() { window.addEventListener('scroll', () => { this.scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; this.isScroll = this.scrollTop > 200 ? true : false; }) },
为了平滑回滚到顶部,应用了scrolltoView
backTopClick() { // document.documentElement.scrollTop = 0; document.querySelector('#home').scrollIntoView({behavior: "smooth", block: "start", inline: "nearest"}) },
实现上拉加载,须要用到clientHeight和scrollHeight
clientHeight即height+padding,是元素可视区域高度
scrollHeight是内容的理论高度+高低padding<如果没有限度div的height,即height是自适应的,那么scrollHeight=clientHeight>
拉到底即scrollTop = scrollHeight - clientHeight
防抖与节流解决
防抖debounce:第一次触发事件时,不立刻执行函数,而是给出一个期限。如果期限值内没有再次触发事件,则执行。如果期限值内再次触发事件,则从新开始计时
debounce(fn, delay) { let timer = null; //利用闭包,保护全局污浊 return function() { if(timer) { clearTimeout(timer); } else { setTimeout(fn, delay); } } }
然而思考到this的指向问题,因而应用apply来批改this指向
debounce(fn, delay) { let timer = null; //利用闭包,保护全局污浊 return function() { if(timer) { clearTimeout(timer); } timer = setTimeout(() => { fn.apply(this) }, delay); } },
如果思考到参数问题(以及event对象),则须要传入arguments,这也是应用apply的益处,不便传参
节流throttle:节流是指每隔一段时间只执行一次事件,也就是第一次触发后必须要距离一段时间后能力触发下一次
节流有两种计划:工夫戳和定时器
//工夫戳相减throttle(fn, delay) { let previous = 0; return function () { let now = +new Date(); if(now - previous > delay) { fn.apply(this); previous = now; } } },
5.非父子组件通信 —— 总线
总线:bus
Vue.prototype.$bus = new Vue()
this.bus.emit('事件名称', 参数)
this.bus.on('事件名称', 回调函数)
6. 组件的offsetTop问题
- 对于组件,是无奈获取offsetTop等属性的,能够用组件属性$el来获取组件中的元素
- 因为资源加载会对高度造成影响,因而如果在mounted中获取offsetTop是不可行的,能够监听轮播图的@load事件
7.来到页面时记录状态和地位
来到首页时,首页组件就会销毁,因而对router-view外层加一个<keep-alive>包装,使其被缓存
然而keep-alive并不会记录缓存地位。因而来到时保留一个地位信息
这里在beforeRouteLeave中把this.scrollTop保留到this.$route.meta.y中,而后应用scrollBehavior
scrollBehavior(to, from, savedPosition) { return to.meta },
这里一开始是在deactive中记录滚动地位,但发现这样会将数据保留到切换后的路由元信息中
8.跳转详情页并携带id
用params形式携带id
{ path: '/detail/:iid', component: Detail, }
itemClick() { //要有后退,所以用push this.$router.push('/detail/' + this.goodsItem.iid) }
9. 详情页问题
数据不从新申请:keep-alive要设置exclude="Detail"
组件应该面向一个对象,因而应用了一个类将数据进行整合
export class GoodsInfo { constructor(itemInfo, columns, services) { this.title = itemInfo.title; this.desc = itemInfo.desc; this.newPrice = itemInfo.price; this.oldPrice = itemInfo.oldPrice; this.discount = itemInfo.discountDesc; this.columns = columns; this.services = services; this.realPrice = itemInfo.lowNowPrice; }}
详情页要暗藏掉tabbar,能够用watch监听路由 *未实现
与服务器替换数据须要工夫戳,而显示时要把工夫戳转换为日期
formatDate(date, fmt) { //获取年份 if (/(y+)/.test(fmt)) { //yy - 19 //RegExp.$1是与正则表达式匹配的第一个子匹配(以括号为标记)字符串 //RegExp对象会保留最近的正则表达式匹配 //substr截取字符串 fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); } //匹配位数不同,且月日时分秒都须要补零,所以和年的匹配离开 let o = { //匹配一个或多个M 'M+': date.getMonth() + 1, 'd+': date.getDate(), 'h+': date.getHours(), 'm+': date.getMinutes(), 's+': date.getSeconds() }; for (let k in o) { if (new RegExp(`(${k})`).test(fmt)) { let str = o[k] + ''; fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str)); } } return fmt;};//传入一位,则截取一位,返回后果为两位function padLeftZero (str) { return ('00' + str).substr(str.length);};
详情页举荐商品间接应用GoodsList组件,然而GoodsList中图片援用的对象不同,因而在组件中增加一个计算属性
computed: { showImage() { return this.goodsItem.img || this.goodsItem.show.img } },
标题栏和滚动地位的互相对应:获取每个元素的offsetTop,然而要留神在哪获取。如果在mounted中,显然有的组件还没有数据,如果在申请数据的then中获取,也会因为渲染线程不同拿不到数据。
能够在updated()中获取,然而要留神这样会频繁获取。
因而,能够在用then中用this.$nextTick(),它将回调提早到下次 DOM 更新循环之后执行。在批改数据之后立刻应用它,而后期待 DOM 更新
留神!!间接应用数组下标赋值是无奈即时更新数据的
this.$nextTick(() => { this.scrollKey = [] this.scrollKey.push(0); this.scrollKey.push(this.$refs.param.$el.offsetTop - 44); this.scrollKey.push(this.$refs.comment.$el.offsetTop - 44); this.scrollKey.push(this.$refs.recommend.$el.offsetTop - 44);})
然而这样仍然存在问题:nextTick执行时示意渲染实现,但图片可能还未申请加载完,因而最好还是将其放在监听img加载的事件中,而后加上防抖或节流
10.混入
混入 (mixin) 提供了一种非常灵活的形式,来散发 Vue 组件中的可复用性能
// 定义一个混入对象var myMixin = { created: function () { this.hello() }, methods: { hello: function () { console.log('hello from mixin!') } }}// 定义一个应用混入对象的组件var Component = Vue.extend({ mixins: [myMixin]})var component = new Component() // => "hello from mixin!"
当组件和混入对象含有同名选项时,这些选项将以失当的形式进行“合并”。
比方,数据对象在外部会进行递归合并,并在发生冲突时以组件数据优先。
同名钩子函数将合并为一个数组,因而都将被调用。另外,混入对象的钩子将在组件本身钩子之前调用。
值为对象的选项,例如 methods
、components
和 directives
,将被合并为同一个对象。两个对象键名抵触时,取组件对象的键值对。
11.购物车
因为一开始是没有购物车商品组件的,所以不能用事件总线实现增加商品到购物车,因而用Vuex来保留
个别mutations中只实现繁多工作,因而将其余批改工作放在actions中,用actions散发
actions: { addCart(context, payload) { //先判断商品是否已增加 //find:返回满足条件的item let product = context.state.cartList.find((item) => { return item.iid === payload.iid }) if(product) { context.commit('addCount', payload) } else { payload.count = 1 context.commit('addToCart', payload) } } },mutations: { addCount(state, payload) { payload.count++ }, addToCart(state, payload) { state.cartList.push(payload) }}
mapGetters
mapGetters辅助函数是将 store 中的 getter 映射到部分计算属性
computed: { ...mapGetters([ 'cartLength' ]) }
Toast弹窗
退出购物车时弹窗,因而对actions中退出购物车办法用promise包裹,在then中进行解决操作。为了不便复用,因而将其封装为插件。
//index.jsimport Toast from "@/components/common/toast/Toast";const obj = {}obj.install = function (Vue) { //创立组件结构器 const toastConstructor = Vue.extend(Toast) //new创立组件结构器组件对象 const toast = new toastConstructor //组件对象手动挂载 toast.$mount(document.createElement('div')) //toast.$el就对应div,而后增加到DOM上 document.body.appendChild(toast.$el) Vue.prototype.$toast = toast;}export default obj//main.jsVue.use(toast)new Vue({ router, render: h => h(App), store, toast}).$mount('#app')//Detail.vuethis.$store.dispatch('addCart', obj).then(res => { this.$toast.showMessage(res, 1500)})
12.优化
1.解决挪动端提早-fastclick
2.图片懒加载lazyload
//main.jsVue.use(VueLazyload, { loading: require('@/assets/img/common/placeholder.png')})//GoodsListItem.vue<img v-lazy="showImage" alt="">
3.px2vw转换 postcss
13. 响应式原理
<img src="C:\Users\Asus\AppData\Roaming\Typora\typora-user-images\image-20210427165545066.png" alt="image-20210427165545066" style="zoom:80%;" />
如何监听数据的扭转
Object.defineProperty监听对象属性扭转
数据传入Vue,通过
//获取key进行遍历Object.keys(obj).forEach(key => { let value = obj[key] //从新定义属性 Object.defineProperty(obj, key, { set(newValue) { //数据扭转时会执行set,因而能够在这里监听 value = newValue //dep.notify() }, get() { //获取数据时会执行get dep.addSub() return value } })})
如何确定数据扭转时要告诉哪些局部
公布-订阅者模式
//发布者class Dep{ constructor() { //记录订阅者 this.subscribe = [] } addSub(watcher) { this.subscribe.push(watcher) } notify() { //告诉 this.subs.forEach(item => { item.update() }) }} //订阅者class Watcher{ constructor(name) { this.name = name; } update() { //更新时执行 } }const dep = new Dep();const w1 = new Watcher('')dep.addSub(w1)
observer阶段:给每个data创立不同dep对象
compiler阶段:依据应用的data创立watcher,例如name应用两次,则创立两个watcher,退出到sub数组中
初始化view:依据el初始化view
数据扭转时,调用dep对象的notify,对每个sub数组中的对象遍历调用update()办法,update()对视图view更新