Supermall项目笔记1.axios打包可以在网络中创建独立的js文件来封装不同页面的网络请求,避免页面组件太大太复杂2.操作DOM时挂载轮播钩子树,启动定时器mounted:function(){//1.操作DOM,前后添加SlidesetTimeout(()=>{this.handleDom();//2.启动定时器this.startTimer();},3000)},这里hanleDom()函数获取元素个数,克隆第一张和最后一张图片插入到最后一张和最前面,实现无缝滚动。轮播图片问题当图片加载缓慢时,会导致mounted中的函数在图片加载完成之前操作DOM并启动定时器。consttargetNode=document.querySelector('.swiper');constconfig={childList:true};constcallback=(mutationsList,observer)=>{this.handleDom();这个.startTimer();观察者.disconnect();};constobserver=newMutationObserver(回调);observer.observe(targetNode,config);}//在mounted中调用//为了防止热更新时dom被重新加载导致监听失败,添加判断mounted(){if(document.querySelector(".swiper").childElementCount==0){this.observeSwiper();}else{this.handleDom()this.startTimer()}}②监听
的onload事件,加载完成后,传入一个属性给swiper组件,通过watchimageLoad(){if(!this.isLoad){this.$emit('swiperImageLoad');this.isLoad=true;}swiperImageLoadd(){this.handleDom()this.startTimer()}3、商品数据的存储结构请求三种数据,根据点击取不同的数据考虑到加载时间,不应该一次请求所有数据goods对象保存数据goods:{'pop':{page:1,list:{}},//page保存当前页码,list记录所有数据'新':{page:1,列表:{}},'sell':{page:1,列表:{}}}4。Scroll事件监听mounted中的scroll事件决定是否显示或隐藏返回顶部按钮.scrollTop||document.body.scrollTop;this.isScroll=this.scrollTop>200?true:false;})},为了平滑滚动回顶部,使用scrolltoViewbackTopClick(){//document.documentElement.scrollTop=0;document.querySelector('#home').scrollIntoView({behavior:"smooth",block:"start",inline:"nearest"})},实现上拉加载,需要用到clientHeight和scrollHeightclientHeight为height+padding,也就是元素可见区域的高度。scrollHeight为内容实际高度+上下padding<如果div的高度没有限制,即height是自适应的,则scrollHeight=clientHeight>拉到底部,即scrollTop=scrollHeight-clientHeight防抖和节流处理Anti-shakedebounce:第一次触发事件时,不会立即执行该函数,而是给出一个截止时间。如果事件未在截止日期值内再次触发,则执行。如果在限定时间内再次触发事件,则重新开始计时debounce(fn,delay){lettimer=null;//使用闭包保持全局纯净returnfunction(){if(timer){clearTimeout(timer);}else{setTimeout(fn,延迟);但是,考虑到this的指向问题,使用apply来修改这个debounce(fn,delay)的指向{lettimer=null;//使用闭包来维护全局纯度returnfunction(){if(timer){clearTimeout(timer);}timer=setTimeout(()=>{fn.apply(this)},delay);}},如果考虑参数(和事件对象),需要传入回车参数,这也是使用apply的好处,传参方便。节流:节流是指每隔一段时间只执行一个事件,即第一次触发后,必须经过一段时间才能触发下一次节流。两种方案:时间戳和定时器//时间戳减法throttle(fn,delay){letprevious=0;返回函数(){letnow=+newDate();if(now-previous>delay){fn.apply(this);以前=现在;}}},5.非父子组件通信-busbus:busVue.prototype.$bus=newVue()this.bus.emit('事件名称',parameter)this.bus.on('事件名称Call',callbackfunction)6.组件的offsetTop问题对于组件来说,是无法获取到offsetTop等属性的。您可以使用组件属性$el来获取组件中的元素。由于资源加载会影响高度,如果在mountedoffsetTop中获取不可行,可以监听轮播图片的@load事件7.离开页面时,记录状态和位置离开首页时,homepage组件会被销毁,所以在router-view的外层添加一个包,使其缓存,但keep-alive不记录缓存位置所以离开时保存一个位置信息。这里,在beforeRouteLeave中将this.scrollTop保存到this.$route.meta.y中,然后使用scrollBehaviorscrollBehavior(to,from,savedPosition){returnto.meta},这里一开始是在deactive中记录滚动位置,但是发现这样切换后会保存routemeta信息中的数据8.跳转到详情页并携带id使用params携带id{path:'/detail/:iid',component:Detail,}itemClick(){//有返回的需求,所以使用pushthis.$router.push('/detail/'+this.goodsItem.iid)}9.详情页的问题数据不会被重新请求:keep-alive需要设置exclude=“Detail”组件要面向对象,所以用一个类来集成数据导出类GoodsInfo{constructor(itemInfo,列,服务){this.title=itemInfo.title;this.desc=itemInfo.desc;this.newPrice=itemInfo.price;this.oldPrice=itemInfo.oldPrice;this.discount=itemInfo.discountDesc;this.columns=列;this.services=服务;this.realPrice=itemInfo.lowNowPrice;可以使用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));}//匹配的数字不同,月日时分秒需要补零,所以和年份匹配分开leto={//匹配一个或多个M'M+':date.getMonth()+1,'d+':date.getDate(),'h+':date.getHours(),'m+':date.getMinutes(),'s+':date.getSeconds()};for(letkino){if(newRegExp(`(${k})`).test(fmt)){letstr=o[k]+'';fmt=fmt.replace(RegExp.$1,(RegExp.$1.length===1)?str:padLeftZero(str));}}returnfmt;};//pass输入一个则截取一个,返回结果为两位数functionpadLeftZero(str){return('00'+str).substr(str.length);};详情页的推荐商品直接使用了GoodsList组件,但是GoodsList图片所引用的对象不同,所以在computed组件中添加一个computed属性:{showImage(){returnthis.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提供的Mix一种在Vue组件中分发可重用函数的非常灵活的方法//定义一个混合对象varmyMixin={created:function(){this.hello()},methods:{hello:function(){console.log('hellofrommixin!')}}}//定义一个使用mixin对象的组件varComponent=Vue.extend({mixins:[myMixin]})varcomponent=newComponent()//=>"hellofrommixin!“当组件和混入具有相同名称的选项时,这些选项将以适当的方式“合并”。例如,数据对象在内部递归合并,发生冲突时组件数据优先。同名的钩子函数会合并到一个数组中,所以都会被调用。此外,mixin对象的钩子将在组件自己的钩子之前被调用。值为对象的选项,如方法、组件和指令,将被合并到同一个对象中。当两个对象的键名冲突时,取组件对象的键值对。11、由于购物车一开始并没有购物车商品组件,所以无法使用事件总线向购物车添加商品。所以一般mutations中使用Vuex只保存单个job,其他的修改工作都放在action中。用动作分发动作:{addCart(context,payload){//先判断商品是否已经添加//find:返回满足条件的itemletproduct=context.state.cartList.find((item)=>{returnitem.iid===payload.iid})if(product){context.commit('addCount',payload)}else{payload.count=1context.commit('addToCart',payload)}}},mutations:{addCount(state,payload){payload.count++},addToCart(state,payload){state.cartList.push(payload)}}mapGettersmapGetters辅助功能是将store中的getter映射到本地计算属性computed:{...mapGetters(['cartLength'])}窗口添加到购物车时弹出Toast,所以在actions中添加到购物车的方法用promise包裹,处理操作为当时执行。为了方便复用,将其打包为插件。//index.jsimportToastfrom"@/components/common/toast/Toast";constobj={}obj.install=function(Vue){//创建组件构造函数consttoastConstructor=Vue.extend(Toast)//new创建组件构造器组件对象consttoast=newtoastConstructor//组件对象手动挂载toast.$mount(document.createElement('div'))//toast.$el对应div,然后添加到DOM文档。body.appendChild(toast.$el)Vue.prototype.$toast=toast;}exportdefaultobj//main.jsVue.use(toast)newVue({router,render:h=>h(App),store,toast}).$mount('#app')//Detail.vuethis.$store.dispatch('addCart',obj).then(res=>{this.$toast.showMessage(res,1500)})12.优化1.解决移动延迟-fastclick2。图片的延迟加载lazyload//main.jsVue.use(VueLazyload,{loading:require('@/assets/img/common/placeholder.png')})//GoodsListItem.vue3.px2vw转换postcss13.响应式原理如何监听数据变化Object.defineProperty监听对象属性变化并传递数据给Vue,遍历//获取keysObject.keys(obj).forEach(key=>{letvalue=obj[key]//重新定义propertyObject.defineProperty(obj,key,{set(newValue){//set会在数据变化时执行,所以这里可以监听到value=newValue//dep.notify()},get(){//getdep.addSub()返回值会在获取数据时执行}})})如何判断数据变化时通知哪些部分Publisher-SubscriberMode//PublisherclassDep{constructor(){//recordSubscriberthis.subscribe=[]}addSub(watcher){this.subscribe.push(watcher)}notify(){//通知this.subs.forEach(item=>{item.update()})}}//subscriberclassWatcher{构造函数(名称){this.name=名称;}update(){//更新时执行}}constdep=newDep();constw1=newWatcher('')dep.addSub(w1)观察者阶段:为每个数据编译阶段创建不同的dep对象:createwatche根据使用的数据r,比如name被使用了两次,创建两个watcher,添加到sub数组中,初始化视图:当视图数据发生变化时,根据el初始化,调用dep对象的notify,调用update()方法为子数组中的每个对象,update()更新view视图