vue基础梳理

500 阅读18分钟

vue MVVM双向数据绑定绑定原理。

  • M指的是model,模型;V指的是view,视图;VM指的是viewModel,视图模型。model指的是js中的数据,model通过数据绑定实现视图的更新,而视图改变模型是通过DOM事件监听。

vue MVVM的原理:

  • 观察者-订阅者(数据劫持):vue中Observer数据监听器,把一个普通的对象传给Vue实例的data选项,vue将把这个对象的所有属性进行遍历,并使用Object.defineProperty()方法将这些属性全部转为setter和getter方法。当data中的属性发生改变时,则会调用setter方法,访问某个属性时则会调用getter方法。Complie指令解析器,它的作用是对每个元素节点的指令进行解析(解析小胡子里的变量或者是js语句),替换模板数据。初始化视图,并订阅Watcher来更新视图。此时watcher(收集订阅者)会将自己添加到消息订阅器Dep(消息订阅器)中初始化完毕。当数据发生变化时,Observer中的setter会触发,setter会立即调用Dep.notify()函数,Dep开始遍历所有的订阅者,并调用遍历者的update方法,订阅者收到通知后对视图进行响应的改变。
  • Object.defineproperty()方法做的数据绑定,而这个又无法通过兼容性处理,所以Vue2.0不支持IE8以下的浏览器。Vue3.0使用了Proxy代理方法,它的作用就是遍历data中的属性,把它代理到vm的实例上,。
  • Proxy对比于Object.defineProperty()的优点
    • Proxy可以直接监听对象而非属性
    • Proxy可以直接监听数组的变化
    • Proxy有13中拦截方式
    • Proxy返回的是一个新对象,我们可以只操作新对象达到目的,而Object.defineProperty只能遍历对象属性直接修改。

二、vue的生命周期

  1. beforeCreate:实例组件刚被创建,DOM和数据data还没有初始化。当前阶段data、watch、Dom、computed、methods上的数据都不可以访问。
  2. created:数据data已经初始化,方法也已经可以调用,但是DOM未渲染。在这个周期里可以进行请求改变数据并进行渲染。 当前阶段呢无法于dom进行交互。
    • 请求接口一般放在created;虽说在created、beforeMount、mounted三个钩子函数中,data已经创建,但是推荐在created中请求接口;因为在created中有以下优点
      • 能更快获取到服务器的数据,减少页面加载时间。
      • ssr不支持beforeMount和mounted钩子函数,放在created有助于一致性。
  3. beforeMount:DOM未完成挂载,data数据已经初始化完成,但是数据的双向绑定还是显示{{}},这是因为Vue采用了虚拟DOM,先占住一个坑。
  4. mounted:数据和DOM都完成了挂载,在上一个周期占位的数据把值给渲染进去。一般请求会放在这个地方;因为这里请求改变数据之后刚还能渲染。当前阶段真是的Dom完成挂载;数据完成双向绑定,可以访问到Dom节点。
  5. beforeUpdate:只要是页面数据改变都会触发,数据更新之前,页面的数据还是原来的数据
  6. updated:只要是页面数据改变就会触发,数据更新完毕,页面的数据是更新完成后的。
  7. beforeDestory:组件销毁之前执行。
  8. destoryed:组件销毁之后执行。

父子组件生命周期钩子函数的执行顺序

  • 加载渲染过程:父组件beforeCreate=>父组件created=>父组件beforeMount=>子组件beforeCreate=>子组件Created=>子组件beforeMount=>子组件mounted=>父组件mounted
  • 子组件的更新过程:父组件beforeUpdate=>子组件beforeUpdate=>子组件updated=>父组件updated
  • 父组件更新过程:父组件beforeUpdate=>父组件updated
  • 销毁过程:父组件beforeDestroy=>子组件beforeDestroy=>子组件destroyed=>父组件destroyed

vue中class和style绑定

绑定类名方法

  • 对象语法

        <div :class="{active:isActive,text:isHover}"></div>
        
        //当然类名也可以利用计算属性进行设置
        <div :class="classObject"></div>
        data(){
            return {
                isActive:false,
                isHover:true
            }
        },
        computed:{
            classObject(){
                return {
                    active:this.isActive,
                    text:this.isHover
                }
            }
        }
    
    • class中的类名active和text是否存在取决于isActive和isHover是否是true,如上最终的编译结果是
        <div class = "text"></div>
    
  • 数组语法

        <div :class="[isActive?'active':'',text]"></div>
        <!-- 如果三元判断较为繁琐,也可以数组结合对象 -->
        <div :class="[{active:isActive},text]"></div>
        
    

绑定style 内联式

  • 对象语法
    <div :style="{color:'red',font-size:'16px'}"></div>
  • 数组语法
    <div :style="[baseStyle,baseColor]"></div>
    
    data(){
        return{
            baseStyle:{
                font-size:'16px',
                line-height:1,
            },
            baseColor:{
                color:'red'
            }
        }
    }
  • 多重值时,用对象和数组实现
        <div :style="{display:['-webkit-box', '-ms-flexbox', 'flex']]"></div>
    

vue中的weabpack随手笔记

在src同级的目录下创建一个vue.config.js文件

module.exports = {
    publicPath:"/baidu", //根路径
    outputDir:"dist", //构建输出目录
    assetsDir:"assets", //存放静态资源;js、css、img等等
    lintOnSave:true,  //是否开启eslint保存检测;有效值:true,false,error
    devServer:{
        open:true, //启动项目默认浏览器自动打开项目
        host:"0.0.0.0", //主机名字
        port:"8080", //端口号
        //proxy反向代理的原理是,客户端发送请求,webpack将请求代理到服务器上,webpack通过devServer将响应数据返回客户端
    	//常见的正向代理是vpn,其他的一般都是反向代理,代理的服务器如果帮助的是客户端则为正向代理,
		//如果是帮助服务器端的则为反向代理
        proxy:{
            // 配置proxy跨域
            "/api":{
                target:"http://localhost:5000", //需要跨域的地址
                pathRewirte:{
                    "^/api":"/api"
                },
            },
        },
    }
}

vue脚手架内置文件介绍

  1. public:主要是放静态资源的文件,例如图片,全局css,引入的js脚本。
  2. package.json:配置文件
  3. src:主要是写咱们开发的源码
    • assets:放静态资源,简易用来放图标,这个的图片可以选择编码转成base64。
    • compoents:用来放公共组件的,复用性比较强。
    • views:放页面级别的组件,复用性比较差。
    • store:vuex 用来存放公共的数据。
    • router:用来放路由配置的。
    • main.js:主模块。
    • app.vue:主组件。

vue中路由配置

  1. 首先利用npm安装vue-router;命令是npm i vue-router
  2. 在src目录下创建一个router文件,在文件下创建一个index.js文件;
import Vue from 'vue';
import VueRouter from 'vue-Router';
Vue.use(VueRouter)
const routes = [
  {
      path:'/home',
      name:'home',
      //路由懒加载;就是要展示这个组件时才加载这个组件的资源,可以减少首屏加载时间(性能优化)
      component:()=>import('../views/home')
      //嵌套路由也就是子路由配置
      chlidren:[
      {
          path:'/home/son',
          name:'son',
          component:()=>import('../views/son')
      }
      ],
      //路由404,也就是路由重定向;这个需要注意只能放在最后,用来重定向的。
      {
          path:'*',
          redirect:'/home',
      }
  }
];
const router = new VueRouter({
    mode:'history',
    routes,
});
export default router;
  1. 在main.js中引入router.js文件
import router from './router'
const vm = new Vue({
    router,
    store,
    render:h(APP)
}).$mount('#app');
  1. 基本的路由配置就完成了。
  2. 如果再深入一点的路由配置就是设置黑白名单,也就是路由拦截。
router.beforEach((to,from,next)=>{
    const noPassUrl = [];
    if(noPassUrl.indexOf(to.path)>-1){
        if(token){
            next()
        }else{
            next({path:'/login'})
        }
    }
    next()
})

vue-router传参的形式有哪些,有什么区别

vue-router的传参形式分为两大类

router.push和<router-link></router-link>

  • 编程式导航router.push,该路由跳转有两个传参形式;1是字符串;2是对象
    • this.$router.push('home'),字符串的方式是直接将路由地址以字符串的方式进行跳转,这种方式很简单但是不能传递参数
    • 对象的形式,分为两个方式
      //传参
       this.$router.push({name:'home',params:{useId:123}})
       //接收参数
       this.$route.params.useId
       
       //传参
       this.$router.push({path:"/home",query:{use:'hello'}})
       //接收参数
       this.route.query.use
      
  • router-link传参
    //传参
    <router-link :to={name:'home',query:{id:25}}"></router-link>
    
    //接收
    console.log(this.route.query.id)
    

接收参数也可以换高级写法

  • 路由传参的高级写法

    //传参
    this.$router.push({name:"home",query:{user:"张三"}})
    
    //在路由中写
    {
        path:'home',
        name:'home',
        props:route => ({user:route.query.user})
    }
    
    //在接收的组件中利用props接收
    props:{
        user:{
            type:String,
            default:() => ''
        }
    }
    
    
  • params和query传参的区别

    • params这种传递参数的形式,如果在目标页面刷新,传递的参数会丢失。
    • query传参,参数会附在路由地址后面,在目标页刷新,数据不会丢失。

axios和ajax的区别,axios的基本配置。

  • axios和ajax的区别:
    • axios是基于promise的HTTP库,axja不支持promise,如果多个请求之间有先后顺序的话就会出现回调地狱。
  • axios的基本配置。
    1. 先利用npm安装axios,命令npm i axios;
    2. 在src目录下创建一个request文件,在次文件下创建index.js,引入并配置。
    import axios from 'axios'
    const service = axios.create({
        timeout:6000; //设置请求超时时间。
        //设置请求头传参类型
        headers:{ "Content-Type": "application/x-www-form-urlencoded "}, 
        baseURL:''//这是请求地址的基础路径,将自动加载url前面
        transformRequest:[function(data){
            // 对data(传参)进行任意处理
            //post请求传参为formdata时处理参数
            const d = qs.stringify(data);
            return d;
        }],
        withCredentials:true, //默认值为false,表示跨域请求时是否需要凭证。配置cookie
    })
    
    //请求拦截
    axios.interceptors.request.use(config=>{
        //请求之前做什么
        if(token){
            config.headers.Authorization = token
        }
        return config
    },error=>{
        return Promise.reject(error)
    }
    )
    
    //请求响应拦截
    axios.interceptors.response.use(
      response=>{
           //对响应数据做什么
           return response
    },
      error=>{
          if(error.response.state==401){
              //这里写清除token
              return Promise.rejece(error)
          }
      }
    )
    //移除请求拦截器
    var myInterceptors = axios.interceptors.request.use(config=>{
        return config
    })
    axios.interceptors.request.eject(myInterceptors)
    
    export default service
    

vue中组件的数据的传递

父子组件之间的传递

  • 父传子;在父级组件中自定义属性,子组件通过props接收参数。
// 子组件代码Son.vue
<template>
  <div class="header">
      <div class="logo">{{logo}}</div>
  </div>
</template>
<script>
  export default {
      name:"Son",
      data(){
          return {
              logo:"",//组要从父级组件中获取logo
          }
      },
      props:['logo'], //接收父组件传过来的logo  //其中props支持数组和对象的形式
     
      props:{ 
          // 自定义验证函数 
          propF: { validator: function (value) { 
          // 这个值必须匹配下列字符串中的一个 
          return ['success', 'warning', 'danger'].indexOf(value) !== -1 },
          propA:{
              type:Number,
              default:2
              
              //默认值如果是个引用数据类型,必须写法如下
              default:()=>{
                  a:1
              }
          }
      }
      
  }
</script>

// 父组件Parent.vue
<template>
  <div class= "main">
      <Son :logo = logoMsg></Son>
  </div>
</template>

import Son from './components/Son'
<script>
  export defalut {
      name:"parent",
      data(){
          return {
              logoMsg:"图片",
          },
      },
      components:{
          Son
      }
  }
</script>

子传父:子组件通过触发自身函数,利用this.$emit触发父级组件中的自定义事件并传参。

// 子组件Son.vue
<template>
  <div class="son">
    <lable>
      <input v-model= "userName" @change="setUser" />
    </lable>  
  </div>
</template>
<script>
export default {
    name:"son",
    data(){
        return{
            userName:"",
        }
    },
    methods:{
        setUser(){
            this.$emit("user",this.userName)
        }
    }
}
</sript>

//父组件parent.vue
<template>
  <div class='main'>
      <Son @uer="getUser"></Son>
      <p>用户名{{userName}}</p>
  </div>
</template>

import Son from "./components/Son"
<script>
export default {
  name:"parent",
  data(){
      return{
          userName:"",
      }
  },
  components:{
      Son
  },
  methods:{
      getUser(name){
          this.userName = name
      }
  }
}
</script>

兄弟间的数据传递VueX

  • 在src文件夹下创建一个store文件,然后在store文件下创建一个index.js文件
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

export default new Vuex.Store({
  //state 存储的是store中变量,state的变量官方不建议直接修改,而是通过mutation中的方法进行修改
    state:{
        age:"",
        name:"",
    },
  // mutations存放的是修改state中变量的方法,只支持同步的方法。通过this.$store.commit("setUrl")
  触发,如果传参只是一个可以直接传,如果想要多个传参则需要已对象的形式进行传参。
  例如:this.$store.commit('setUrl',{age:15,name:'hello'})
    mutations:{
        setUrl(state,data){
            state.age = data.age,
            state.name = data.name
        }
    },
  // actions可以存放一些公共函数,它支持同步和异步的函数。也可以异步修改state的值,
  原理是通过action里的方法触发mutations里的方法,进一步去改变state里的变量。
  通过this.$store.dispatch('getName'),传递多个参数时。与mutations同理。
    actions:{
        getName(name){
            console.log(name)
        },//存放公共方法
        // 异步修改state中的变量
        setAge({commit},data){
            commit("setUrl",data)
        }
    },
    // getters 可以对state中的变量进行加工后传递外界。getters有两个参数。
    第一个参数时state,第二个参数时getters,当前getters对象用于这个对象下的getters使用
    通过this.$store.getters(fullInf)调用。
    getters:{
        nameInf(state){
            return "姓名:"+ state.name
        },
        fullInf(state,getters){
            return getters nameInf + '年龄:'+state.age
        }
    }
})

$ref实现通信-父传子 子传父

  • ref如果用在子组件上,通过$refs可以获取到子组件上的方法和属性。如果不是只是在普通的dom元素上使用,有选择器的作用。
//子组件
<template>
  <div class="child">
      {{msg}}
  </div>
</template>
<script>
  export default {
      data(){
          return{
              msg:''
          }
      },
      created(){
          console.log(this.$parent.name)  //获取父组件的name属性
      },
      methods:{
          getMsg(m){
              this.msg = m
          }
      }
  }
</script>
//父组件
<template>
  <div class="parent">
      <Child ref='son'></Child>
      <div>{{name}}</div>
  </div>
</template>
import Child from './Child'
<script>
  export default {
      data(){
          return {
              name:'父组件',
          }
      },
      mounted(){
          this.$refs.son.getMsg('我是子组件')
      }
  }
</script>

vue常用指令、相似功能指令间的区别以及注意事项

vue中的常用指令

  • v-html:用于解析html代码并展示
<template>
  <div v-html=msg></div>
</template>

<script>
  export default {
      data(){
          return{
              msg:'<h1>vue的v-html指令</h1>'
          }
      }
  }
</script>
  • v-text:用于输出纯文本
  • v-if:条件判断指令,值是ture是展示。
  • v-show:显示隐藏dom元素。
  • v-for:
    • 数组迭代,最好在后面加上:key
        <template>
          <div v-for="(item,index) in ary :key=index" ></div>
        </template>
    
        <script>
          export default {
              data(){
                  return{
                      ary:[1,2,3,4]
                        }
                     }
          }
         </script>
    
    • v-for循环对象
          <ul>
              <li v-for="(value,name,index) in object" :key="name">
                 val:{{value}} key:{{name}}
              </li>
          </ul>
          
          //输出val:标题,key:title
          //val:年龄,key:age
          //val:名字,key:name
          //index 为索引
          data(){
              return{
                  object:{
                      title: "标题",
                      age: "年龄",
                      name: "名字",
                  }
              }
          }
      
      • v-for循环数字
          <ul>
              <li v-for="item in 10" :key="name">
                 {{item}}
              </li>
          </ul>
          
          //li会被循环三次
      
  • v-bind:绑定属性的指令,一般简写为:
  • v-on:绑定事件的指令,一般简写为@
  • v-model:实现表单数据和data数据双向绑定。

vue指令的区别和注意事项。

  • v-if和v-show的区别:
    • v-if和v-show都有着显示隐藏的作用;v-if是创建或者删除dom元素;v-show是在该元素的css上添加display:none/true;dom元素一直都在。频繁切换的时候使用v-show更加合适。
  • v-for使用时需要的注意事项。
    • 使用时最好在后面添加:key属性。相当于给组件加了一个唯一标识。key的作用:让diff操作更准确、更迅速。
  • v-for和v-if同时使用;因为v-for的优先级比v-if高;这意味着v-if将重复与每个循环的元素中;非常影响性能。

永远不要把v-for和v-if同时用在一个元素上

  • 如果我们在同一个元素上同时使用时,Vue是如何处理的
    <ul> 
        <li v-for="user in users" v-if="user.isActive" :key="user.id"> 
            {{ user.name }} 
        </li> 
    </ul>
    
    //因为v-for的优先级比v-if高,所以会进行以下的运算
    this.users.map(user=>{
        if(user.isActive){
            return user.name
        }
    })
    
    /**当我们只需要渲染部分元素时,也会直接将整个this.users循环,非常影响性能  
    我们可以将v-if放到ui标签上或者使用cpmputed计算属性。*/
    
  • 使用计算属性过滤v-if的条件
<ul>
    <li v-for="user in activeUsers" :key='user.id'>
        {{user.name}}
    </li>
</ul>

computed:{
    activeUsers(){
        return this.users.filter(user=>{
            return user.isActive
        })
    }
}

用key来管理可复用的元素

  • Vue会尽可能高效的渲染元素,通常会复用已有的元素而不是从头开始渲染。这么做除了使Vue变得非常快之外,还有一些好处。例如你允许用户使用不同的方式登录时;
    <template v-if="loginType=='userName'">
        <label>用户名</label>
        <input placeholder="请输入用户名" />
    </template>
    
     <template v-else>
        <label>邮箱</label>
        <input placeholder="请输入邮箱号" />
    </template>
    
    • 上面的代码切换loginType时,将不会清除用户已经输入的内容,因为两个模板使用了相同的元素,label元素不会被替换掉,他只会将邮箱替换掉用户名,input元素也是,仅仅是替换掉placeholder属性,而input框的输入内容不会变。因此如果我们想要这两个input元素是独立的,只需要添加唯一值key。如下,这样切换登录方式时,input框是两个独立的元素,输入框的内容只在当前状态下生效,互不影响。
     <template v-if="loginType=='userName'">
        <label>用户名</label>
        <input placeholder="请输入用户名" key='userName' />
     </template>
    
     <template v-else>
        <label>邮箱</label>
        <input placeholder="请输入邮箱号" key='email' />
     </template>
    

vue常用的事件修饰符

v-model的常用修饰符

  • .lazy 等待input失去光标的时候,在去改变v-model的值以及change事件也是。
        <input type="text" v-model.lazy="int" />
    
  • .number 自动将用户输入的非数字的值
    <input type="text" v-model.number="int" />
    
  • .trim 自动过滤用户输入的首尾空格
    <input type="text" v-model.trim="int" />
    

键盘事件的按键别名

  • .enter 回车触发事件
        <input type="text" v-model.trim="int" @keyup.enter="enterChange" />
    
  • .tab tab键
  • .esc esc键
  • .space 空格键
  • up 上键
  • .down 下键
  • .left 左键
  • .right 右键

如若在elementUI上直接写键盘事件是不生效的,因为elmentUI对input进行了封装,原生的事件不起作用,如果想要原生事件,在后面加上.native

  •   <input type="text" v-model.trim="int" @keyup.enter.native="enterChange" />
    

键盘事件的修饰符

  • .ctrl

        <input type="text" v-model.trim="int" @keyup.enter.ctrl="enterChange" />
        <!-- 同时按下ctrl和回车键触发事件  以下同理 ->
    
  • .alt

  • .shift

  • .meta

  • .stop:阻止事件继续传播,阻止冒泡和捕获

    <a v-on:click.stop="doThis"></a>
    
  • capture::将默认的冒泡事件修改为捕获。

    <div @click.capture="fn('父元素')">
      父元素
      <div @click.capture="fn('子元素')">子元素
          <div @click='fn('孙子元素')'>孙子元素</div>
      </div>
    </div>
    
    methods:{
    //默认是先打印孙子元素 子元素 再打印父元素
        fn(val){
            console.log(val)  
    // 在父元素加上.capture后,先打印父元素,打印孙子元素,在打印孙子元素。这个只是改变单个元素的顺序
    
    //父元素和子元素加上.capture后,先打印父元素,打印子元素,最后打印孙子元素
        }
    }
    
  • .prevent: 阻止默认事件

    • 可用于阻止浏览器上鼠标右键点击出现弹窗
          <div @contextmenu.prevent="fn(3)">孙子元素</div>  
          //如果没有写.prevent 会触发fn事件,fn执行完成后,还会触发默认的鼠标右键事件,
          //在浏览器上会自动弹出弹窗
      
    • 可阻止form表单中submit按钮点击刷新页面的问题
    • 可阻止a标签点击跳转的问题
  • .once:事件只触发一次

  • .self:通过捕获或者冒泡触发时,只有点击本元素才会触发。即事件不是从内部元素触发的

<div @click="fn('父元素')">
      父元素
      <div @click.self="fn('子元素')">子元素
          <div @click='fn('孙子元素')'>孙子元素</div>
      </div>
</div>
methods:{
    //默认是先打印孙子元素   子元素  再打印父元素
        fn(val){
            console.log(val)  //在孙子元素加上.self后,先打印孙子元素,再打印父元素。不会触发子元素的事件
        }
}    
    //捕获同理不会触发self的元素事件
  • .passive
    • passive主要用在移动端的scroll事件,来提高浏览器响应速度,提升用户体验。因为passive=true等于提前告诉了浏览器,touchstart和touchmove不会阻止默认事件,手刚开始触摸,浏览器就可以立刻给与响应;否则,手触摸屏幕了,但要等待touchstart和touchmove的结果,多了这一步,响应时间就长了,用户体验也就差了。
  • 支持多个修饰符搭配使用

computed和watch的区别和运用场景

  • computed是计算属性
    • 依赖于其他属性,一个计算属性的函数可以同时依赖多个属性,并且computed值有缓存,只有它所依赖的值发生改变,computed的值才会发生改变。
    • 不支持异步,当computed内有异步操作时无效,无法监听数据变化。
    • 应用场景:简化template里{{}}的计算;处理props和$emit的传值。主要用来处理数据。如果仅是简单的处理数据,我们优先使用computed,而不是methods,因为有缓存,性能优化。
    • 用法
    <template>
      <div class="box">
      <input v-model='age' />
      {{info}}
      </div>
    </template>
    
    <script>
      export default {
          data(){
              return {
                  text:'Hello',
                  age:'15',
                  weight:'50',
                  information:''
              }
          },
          computed:{
              //简易写法  这个是计算属性的,getter的方法。计算属性默认只有getter属性,不过他也提供了设置属性setter
              info(){
                  return `${this.text}${this.age}`
              },
              isHealthy:{
                  get(){
                      return this.weight>50? '健康':'亚健康'
                  },
                  set(val){
                      //当我们改变isHealthy的值时,触发这个函数。
                      console.log(val) //改变的值
                  }
              }            
          }
      }
    </script>
    

计算属性VS方法

  • 使用函数方法也可以实现计算属性的效果,为什么使用计算属性

        <div>{{countTotal()}}</div>
        
        <div>{{total}}</div>
        
        data(){
            return{
                number:10,
                unitPrice:5,
                total:0
            }
        },
        computed:{
            total(){
                return this.number + this.unitPrice
            }
            
        },
        methods:{
           countTotal(){
               return this.number + this.unitPrice
           } 
        }
    
    • 计算属性是基于他的响应式依赖进行缓存的,只有在相关的依赖值发生变化时,才会重新计算,这意味着如果this.number和this.unitPrice这两个如果都没有变化时,多次访问这个页面,计算属性会直接返回之前计算的结果,不会重新计算,而methods每次进入这个页面都会重新计算,影响性能。性能优化可以有。
  • watch是侦听属性

    • 依赖于其他属性,一个侦听函数中,只能侦听一个依赖属性,依赖属性改变,直接触发响应的操作。
    • 没有缓存,更多的是观察者的作用。
    • 支持异步。
    • 应用场景:当我们需要在数据变化时,执行异步或者开销较大的操作时。比如请求接口等。
    • 用法
    <template>
      <div class="home">
          <input v-model="psd" />
          {{msg}}
      </div>
    </template>
    <script>
      export default {
          data(){
              return{
                  psd:"",
                  msg:'',
                  obj:{
                      a:1
                  }
              }
          },
          watch:{
          //监听psd;如若psd发生变化则触发函数
          //可以不传参
              psd(){
                  //变化后的操作
              },
          //传参
              pad(newVal,oldVal){
              //newval新改变的数,这里也就是pad,oldVal之前的数
                  setTimeout(()=>{
                      if(newVal.length>4){
                          this.msg = '密码太长'return
                      };
                      if(newVal.length<3){
                          this.msg="密码太短";
                          return
                      };
                      this.msg = ''
                  },1000)
              },
          // 当监听的是一个对象时,默认情况下,handler只监听了属性引用的变化,也就是只监听了一层,
          但对象内部的属性时监听不到的,所以我们需要添加deep:true;才能监听到对象里属性的变化;
          但是这也非常耗性能。
              obj(){
                  handler(){
                      console.log("obj.a发生改变")
                  },
                  deep:true,
              },
          //immediate 初始化就会执行一遍监听
              obj(){
                  handler(){
                      //执行需要的操作
                  },
                  immediate:true,
              },
          //监听对象的具体属性,就不需要设置deep为true了
              obj.a(){
                  handler(){
                      //执行需要的操作
                  }
              }
          }
      }
    <script>
    
  • 注意:不要在computed或者watch中修改所依赖的值,否则会陷入死循环。

vue不能检测哪些数组的变动;怎么用$set解决对象新增属性不能被响应的问题。

  • 由于js的限制,vue不能检测到以下数组的变动。
    • 利用索引直接修改或者赋值数组项时
    • 修改数组长度时
  • 由于js的限制,vue不能检测到对象属性的添加或者删除。
  • 解决办法如下:
<template>
    <div class='main'>
        {{obj}}{{ary}}
    </div>
</template>
<script>
    export default {
        data(){
            return{
                obj:{},
                ary:[1,2,3]
            }
        },
        mounted(){
            this.obj.a = '你好'//虽然说console.log()已经更新,但是view层并没有更新。
            页面展示的是一个空的对象
            this.ary[2] = 5 //和obj一样,view层并没有更新,页面展示[1,2,3]
            //解决vue数组变动检测不到的问题。
            //1、通过$set解决
            this.$set(ary,2,5)//
            //2、通过splice解决
            this.ary.splice(2,1,5)//ary数组第二索引开始往后第一个替换成5
            //解决vue中对象属性增删检测问题
            //1、通过$set解决
            this.$set(obj,'a',[1,2,3,4,5,6])
            
        }
    }
</script>

对vue项目进行过哪些优化

代码层优化

多个if判断如何优雅的写

  • 上代码
function fn(item){
    if(a){
        console.log(a)
    }else if(b){
        console.log(b)
    }else if(c){
        console.log(c)
    }
}
//优化方案一
function fn(item){
    let arr = [a,b,c]
    arr.includes(item)? console.log(item):return
}

//优化方案二
function fn(item){
    let obj = {
    a:fn1
    b:fn2
    c:fn3
    }
    obj[item]()
}
  • v-if和v-show区分场景使用;v-show在频繁切换的时候使用。
  • computed和watch区分场景使用,computed处理简单数据时使用;watch处理异步和繁杂的操作。
  • v-for时使用:key,同时避免使用v-if;v-for的优先级比v-if高,导致每个循环都有v-if操作,耗性能;key添加唯一标识,让diff的更准确、更快速。
  • 图片懒加载
  • 第三方插件按需引入;例如element-ui
    1. 首先安装element-ui依赖 npm i element-ui -S
    2. 利用babel-plugin-component,npm i babel-plugin-component -D
    3. 在src同级文件下创建.babelrc文件夹写入以下配置
      {
          "presets":[["es2015",{"module":false}]],
          "plugins":[
             [
                 "component",
                 {
                     "libraryName":"element-ui",
                     "styleLibraryName":"theme-chalk"
                 }
             ]
          ]
      }
      
    4. 在main.js中按需引入
      import Vue from 'vue';
      import { Btton, Select} from 'element-ui';
      Vue.use(Btton);
      Vue.use(Select);
      
    5. 路由懒加载
    {
        path:'/home',
        component:()=>import('./home'),
    }
    
    6.防抖节流
  1. webpack层面优化
    • webpack对图片压缩
    • 减少ES6转为ES5的冗余代码
    • 提取公共代码
    • 提取组件的css
  2. 基础的web技术优化
    • 开启gzip压缩
    • 浏览器缓存
    • CDN的使用

说说你对SPA单页面的理解,它的优缺点是什么?

  • SPA紧在页面初始化时加载html、js、css。一旦页面完成,SPA不会因为用户的操作而进行页面的重新加载;取而代之的是利用路由机制实现html的内容变换。
  • 优点:
    • 用户体验好,快,内容的改变不会重新加载整个页面;避免了不必要的跳转和重新渲染。
    • 相对于服务器的压力也较小
    • 前后端职责分离,架构清晰,前端负责交互逻辑,后端负责数据处理。
  • 缺点
    • 初次加载耗时较长,需要加载页面的时候将js、css统一加载,部分页面按需加载。

vue中slot的使用

  • slot插槽可以理解为一个占位符,他的可以在组件内添加元素及内容,自定义的组件在其中间填写内容是不会展示的,这时候我们可以用插槽解决。
  • 默认插槽
//先创建一个子组件
<template>
    <div class = "son">
        <p>我是子组件插槽</p>
        <slot></slot>
    </div>
</template>

//父组件
<template>
    <div class = "father">
        <p>我是父组件插槽</p>
        <Son>吊毛刘</Son>
    </div>
</template>

import Son from '@/components/Son'
<script>
    export default {
        components: {
            Son
        }
    }
</script>

//页面展示
//我是父组件插槽
//我是子组件插槽
//吊毛刘
  • 具名插槽
//子组件
<template>
    <div>
    我是子组件插槽
    <slot name=header></slot>
    <slot name="footer"></slot>
    </div>
</template>

//父组件
<template>
    <div class = "father">
        <template name="header">
        一入前端深似海
        </template>
        <P name='footer'>有毛病</p>
    </div>
</template>

import Son from '@/components/Son'
<script>
    export default {
        components: {
            Son
        }
    }
</script>

//页面显示
//我是子组件插槽
//一入前端深似海
//有毛病

vue过渡和动画

过渡

  • vue在插入、移除、更新DOM时,提供多种不同方式的应用过渡效果。vue提供了内置的过渡封装组件,该组件用于包裹实现过渡效果的组件。
  • 先上波实例,了解transiton的语法。
<div class= 'home'>
    <button @click = "show = !show">开始变色</button>
    <transition name = 'fade'>
        <div class = "font">颜色变变变</div>
    </transition>
</div>

export default {
    data(){
        return{
            show:true
    }
}

<style lang = "less" scope>
    .font{
        color:balck;
        opacity:1;
    }
    .fade-enter-active,.fade-leave-active{
        transition:all 2s
    }
    .fade-enter,.fade-leave-to{
        color:yellow
    }
</style>
  • 过渡其实就是一个淡入淡出的效果,vue在元素的显示隐藏的过渡中,提供了6个clss来切换
    • v-enter:定义进入过渡的开始状态,在元素插入之前生效,在元素被插入的下一帧移除。
    • v-enter-active:定义过渡生效时的状态。在元素被插入之前生效,在整个进入过渡的阶段应用,在过渡/动画完成之后移除,这个类一般用来定义进入过渡过程的时间、延迟和曲线函数。
    • v-enter-to:定义进入过渡的结束状态,在元素被插入之后的下一帧移除,在过渡/动画完成之后移除。
    • v-leave:定义离开过渡的开始状态,在离开过渡被触发时立刻生效,下一帧被移除。
    • v-leave-active:定义离开过渡生效时的状态,在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在离开动画/过渡完成之后移除,这个类可以用来定义离开过渡的时间、延迟和曲线函数。
    • v-leave-to:定义离开过渡的结束状态,在过渡/动画完成之后移除。
    • 对于这些过渡中切换的类名来说,如果你使用一个没有名字的<transition>,则v-是这些类名默认的前缀。如果使用<transition name = "name">,那么直接在样式里用name-类名即可
  • 自定义过渡类名
    • enter-class
    • enter-active-class
    • leave-active-class
    • leave-class
    • 可以结合animation插件设置类名这样就不用写@keyframes

动画

  • 例子
<div class= 'home'>
    <button @click = "show = !show">开始变色</button>
    <transition name = 'fade'>
        <div class = "font">颜色变变变</div>
    </transition>
</div>

export default {
    data(){
        return{
            show:true
    }
}

<style lang = "less" scope>
    .font{
        color:balck;
        opacity:1;
    }
    .fade-enter-active,.fade-leave-active{
        animation:bounce 2s
    }
    //因为有0%,所以不用设置初始值,也就是v-enter和v-leava-to
    @keyframes bounce {
        0%{
            color:balck 
        }
        25%{
            color:yellow
        }
        50%{
            color:red
        }
        100%{
            color:black
        }
    }
</style>

使用require.context实现前端工程自动化

  • 在公共组件中创建index.js文件,写入以下代码,然后在main.js中引入index.js文件,这样就不需要在页面在引入公共组件和注册组件了,比如组件名GvDialog,直接用<gv-dialog/>,如果是GvSearchTable则为<gv-search-table/> 即可,注意格式必须如示例哦。
import Vue from 'vue';
const req = require.context(
// 其组件目录的相对路径
'./',
// 是否查询其子目录
false, 
// 匹配基础组件文件名的正则表达式  匹配.vue结尾的文件
/.vue$/);
const requireAll = file =>
    //将处理好的moudules添加进空对象中,e为当前项
    file.keys().reduce((modules, e) => {
        //modules下方设置为空对象,这里modules对象,用组件名当为key,value为组件信息 file(e).default        
        //将每一个组件对象添加进modules
        modules[e.match(/\w+.vue$/)[0].slice(0, -4)] = file(e).default;
        return modules;
    }, {});

const components = requireAll(req);  //处理好的组件对象信息
//循环对象
for (var key in components) {
    var k = key.replace(/[A-Z]/g, (e, i) => {
        //将组件名的驼峰命名改为小写,用-隔开
        if (i === 0) {
            return e.toLowerCase();
        } else {
            return '-' + e.toLowerCase();
        }
    });k
    //定义全局组件,将处理好的组件名字定位为全局组件名,组件为组件信息components[key]
    Vue.component(k, components[key]);
}