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!"

当组件和混入对象含有同名选项时,这些选项将以失当的形式进行“合并”。

比方,数据对象在外部会进行递归合并,并在发生冲突时以组件数据优先。

同名钩子函数将合并为一个数组,因而都将被调用。另外,混入对象的钩子将在组件本身钩子之前调用。

值为对象的选项,例如 methodscomponentsdirectives,将被合并为同一个对象。两个对象键名抵触时,取组件对象的键值对。

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更新