本文件已定稿,最后修改时间 20240822 16:27


一、Vue脚手架

vue-cli:框架 —-> 脚手架

  1. 安装

    1. 全局安装

      npm install -g @vue/cli

    2. 测试安装是否成功

      vue -V

    3. 安装老版本vue-cli

      npm install -g vue-cli

    4. 安装指定版本

      npm install -g vue-cli@版本号

    5. 查看全部版本

      npm view vue-cli versions --json

    6. 下载项目

      vue init webpack 项目名称

  2. 创建项目

    vue create 项目名称

  3. .vue文件

    三大部分:

    template:盒子布局的
    script:js逻辑
    style:css样式

  4. main.js

    只要运行vue项目,main.js就会执行

    index.html —> main.js —> App.vue

  5. vue是”数据”驱动

    定义数据:

    <script>
    export default {
    data(){
    return{
    str:'123'
    ...
    }
    }
    }
    </script>
  6. 模板语法

    <template>
    <div>
    <h1>{{ str }}</h1>
    </div>
    </template>

二、Vue指令

  1. 指令(v-xxx)

    v-bind:单向绑定

    v-bind:属性名='数据'

    简写:

    :属性名='数据'

    指令不需要加入{{}}

  2. 列表渲染和条件渲染

    1. 列表渲染(v-for)

      <template>
      <div id="app">
      <ul>
      <li v-for='(item,index) in arr' :key='index'>
      {{ item }}
      {{ index }}
      </li>
      </ul>
      </div>
      </template>
    2. 条件渲染(v-if)

      <h1 v-if='types=="张三"'>
      11111
      </h1>
      <h1 v-else-if='types=="李四"'>
      22222
      </h1>
      <h1 v-else>
      33333
      </h1>
    3. 事件添加

      <h1 v-on:click='方法'></h1>

      简写:

      <h1 @click='方法'></h1>

      注意:vue-cli中的方法要添加在methods

    【面试题】v-if 和 v-for 的优先级

    • v-for比v-if高

    • 正常的使用上来说

      • v-if

      • ​ v-for

  3. v-if和v-show的区别

    1. v-if

      创建与删除的操作

    2. v-show

      显示和隐藏(display:none;、display:block;

    【面试题】两者区别

    • v-if 是创建和删除节点、v-show 是隐藏和显示节点

    • 如果用户频繁切换的状态应该用v-show

    • 如果一刷新进入页面的状态v-show的性能差一点

  4. v-model的使用

    • v-bind:单向绑定

    • v-model:双向绑定

  5. 计算属性

    模板语法内如果添加特别复杂的逻辑,就让模板表达式比较重而且难以维护,对于任何复杂逻辑,都应当使用计算属性

    <script>
    export default {
    data () {
    return {
    str:'123'
    }
    },
    computed:{
    changeStr(){
    return this.str.split('').reverse().join('');
    }
    }
    }
    </script>

    计算属性(computed)是基于他们的响应式依赖进行缓存的

    • 【面试题】computed 和 methods 区别
      • computed是有缓存的(如果计算的属性没有发生变化,则执行缓存数据)
      • methods是没有缓存的(也就是template只要render了 [或者重复render] 就会执行methods内所有的方法)
    <template>
    <div id="app">
    <!-- {{ changeStr }} -->
    单价:
    <input type="" name="" v-model='price'>
    数量:
    <input type="" name="" v-model='num'>
    总价:{{ total }}
    <hr />
    {{ str }}
    <button @click='btn'>按钮</button>
    </div>
    </template>
    <script>
    export default {
    name: 'App',
    data () {
    return {
    //str:'123'
    price:'11',
    num:2,
    str:'你好'
    }
    },
    computed:{
    total(){
    console.log('computed');
    return this.price * this.num;
    }
    },
    methods:{
    btn(){
    this.str = '不好';
    },
    totalConut(){
    console.log( 'methods' );
    return this.price * this.num;
    }
    }
    // computed:{
    // changeStr(){
    // return this.str.split('').reverse().join('');
    // }
    // }
    }
    </script>
  6. class和style

    1. class
      1. 对象的写法

        :class='{ class名称:true|false }'

        <template>
        <div id="app">
        <ul>
        <li v-for='(item,index) in list' :key='index' :class='{active:false}'>
        {{ item }}
        </li>
        </ul>
        </div>
        </template>
        <script>
        export default {
        name:'App',
        data(){
        return {
        list:['a','b','c']
        }
        }
        }
        </script>
        <style>
        .active{
        background:red;
        }
        </style>
      2. 用于判断各种赋值的

        :class=' currentClass == index ? "active" : "" '

        点击某一个加上颜色

        <template>
        <div id="app">
        <ul>
        <li v-for='(item,index) in list' :key='index'
        :class=' currentClass == index ? "active" : "" '
        @click='btn(index)'>
        {{ item }}
        </li>
        </ul>
        </div>
        </template>
        <script>
        export default {
        name:'App',
        data(){
        return {
        list:['a','b','c'],
        currentClass:0
        }
        },
        methods:{
        btn(index){
        this.currentClass = index;
        }
        }
        }
        </script>
        <style>
        .active{
        background:red;
        }
        </style>
      3. 数组的写法

        <div :class='[activeClass]'></div>

        <template>
        <div id="app">
        <ul>
        <li v-for='(item,index) in list' :key='index'
        :class=' currentClass == index ? "active" : "" '
        @click='btn(index)'>
        {{ item }}
        </li>
        </ul>
        <div :class='[activeClass]'>
        aaa
        </div>
        </div>
        </template>
        <script>
        export default {
        name:'App',
        data(){
        return {
        list:['a','b','c'],
        currentClass:0,
        activeClass:"active"
        }
        },
        methods:{
        btn(index){
        this.currentClass = index;
        }
        }
        }
        </script>
        <style>
        .active{
        background:red;
        }
        </style>
    2. style
      <template>
      <div id="app">
      <ul>
      <li v-for='(item,index) in list' :key='index'
      :class=' currentClass == index ? "active" : "" '
      @click='btn(index)'>
      {{ item }}
      </li>
      </ul>
      <div :class='[activeClass]'>
      aaa
      </div>
      <div :style='styleClass'>
      这是style的写法
      </div>
      </div>
      </template>
      <script>
      export default {
      name:'App',
      data(){
      return {
      list:['a','b','c'],
      currentClass:0,
      activeClass:"active",
      styleClass:{
      background:'red',
      fontSize:'50px'
      }
      }
      },
      methods:{
      btn(index){
      this.currentClass = index;
      }
      }
      }
      </script>
      <style>
      .active{
      background:red;
      }
      </style>
  7. img的src路径问题

    img :src 一旦是绑定的数据,那么他会类似于请求的数据了,需要从路径上查找

    • 没有绑定在src目录下找

      <img src="./assets/img/log0.png">

    • 绑定了在public目录下找

      <img :src="">

  8. 事件处理

    1. 监听事件

      v-on:事件名称=''

      简写:

      @事件名称=''

    2. vue中的事件 —- 和dom事件一致

      click、mouseover

      <template>
      <div id="app">

      <div class='main' @click='btn($event,"你好")' ></div>

      <div class='main' @mouseover='overBtn' @mouseout='outBtn'></div>

      <input type="" name="" @keyup.enter='upBtn'>

      </div>
      </template>

      <script>
      export default {
      name: 'App',
      methods:{
      btn( e,val ){
      console.log(e,val);
      },
      overBtn(){
      alert('移入');
      },
      outBtn(){
      alert('移出');
      },
      upBtn(){
      alert('111111');
      },
      }
      }
      </script>

      <style type="text/css">
      .container{
      width: 500px;
      height: 500px;
      background: blue;
      }
      .main{
      margin: 10px;
      width: 300px;
      height: 300px;
      background: red;
      }
      </style>
    3. 事件修饰符

      .stop、.prevent、.capture、.self、.once、.passive

      <input type="" name="" @keyup.enter='upBtn'>

      <template>
      <div id="app">

      <div class='main' @click='btn($event,"你好")' ></div>

      <div class='main' @mouseover='overBtn' @mouseout='outBtn'></div>

      <input type="" name="" @keyup.enter='upBtn'>

      <div class='container' @click='parentNode'>
      <div class='main' @click.stop='childNode'></div>
      </div>

      </div>
      </template>

      <script>
      export default {
      name: 'App',
      methods:{
      btn( e,val ){
      console.log(e,val);
      },
      overBtn(){
      alert('移入');
      },
      outBtn(){
      alert('移出');
      },
      upBtn(){
      alert('111111');
      },
      parentNode(){
      alert('父');
      },
      childNode(){
      alert('子');
      }
      }
      }
      </script>

      <style type="text/css">
      .container{
      width: 500px;
      height: 500px;
      background: blue;
      }
      .main{
      margin: 10px;
      width: 300px;
      height: 300px;
      background: red;
      }
      </style>

  9. Vue项目做自适应

    • 局部引入(单独某一页面生效).vue文件中引入

    • 在main.js中全局引入 import './assets/js/flexible.js'

三、Vue组件

  1. 什么是组件

    把较大的网页,进行功能模块的拆分

    1. 组件的操作

      组件的首字母要大写

      父组件引入子组件

      <template>
      <div id="app">
      <Header></Header>
      <Swiper></Swiper>
      <Icons></Icons>
      </div>
      </template>
      <script>
      import Header from './components/Header'
      import Swiper from './components/Swiper'
      import Icons from './components/Icons'
      export default {
      name:'App',
      components:{
      Header,
      Swiper,
      Icons
      }
      }
      </script>
  2. 父组件传值给子组件

    • 父组件

      <Swiper :xxx='parentStr'></Swiper>

      xxx 是一个名称 = ‘ 这里是属性值 ‘

      <script>
      import Swiper from './components/Swiper.vue'
      export default {
      name:'App',
      data(){
      return {
      parentStr:'这是父组件的数据',
      }
      },
      components:{
      Swiper
      }
      }
      </script>
    • 子组件

      <template>
      <div>
      {{ xxx }}
      </div>
      </template>
      export default {
      props:['xxx'],
      }
      -----------------
      export default {
      props:{
      xxx:String,
      }
      }
  3. 子组件传值给父组件

    • 父组件

      <template>
      <div>
      <Child @changeEvent='fn'></Child>
      </div>
      </template>
      <script>
      import Child from './components/Child'
      export default {
      name:'App',
      components:{
      Child
      },
      methods:{
      fn(val){
      alert(val);
      }
      }
      }
      </script>
    • 子组件

      <template>
      <div>
      子组件:{{ str }}
      <button @click='btn'>按钮</button>
      </div>
      </template>
      <script>
      export default {
      data(){
      return {
      str:'这是子组件的数据'
      }
      },
      methods:{
      btn(){
      // changeEvent自定义事件名称
      this.$emit('changeEvent',this.str);
      }
      }
      }
      </script>
  4. 兄弟组件之间的传值(bus)

    • bus.js

      import Vue from 'vue'
      export default new Vue;
    • A.vue

      <template>
      <div>
      AAA
      <button @click='btn'>这是A的按钮</button>
      </div>
      </template>
      <script>
      import bus from './bus'
      export default {
      data(){
      return {
      aStr:'这是A的数据'
      }
      },
      methods:{
      btn(){
      bus.$emit('changeStr',this.aStr);
      }
      }
      }
      </script>
    • B.vue

      <template>
      <div>
      BBB
      </div>
      </template>
      <script>
      import bus from './bus'
      export default{
      computed:{
      bStr(){
      bus.$on("changeStr",(res)=>{
      alert(res);
      })
      return 111;
      }
      }
      }
      </script>
  5. scoped以及原理

    1. 组件内样式局部化:

      <style scoped></style>

    2. 原理

      加入了scoped,就会在节点上添加自定义属性 data-v-xxx

      css选择器,根据属性选择最终添加样式

  6. slot使用以及场景

    Header.vue

    <slot>
    <div>
    {{ title }}
    </div>
    </slot>

    App.vue

    <Header title='分类'></Header>
    <Header>
    <input placeholder="请输入">
    </Header>

    使用场景:

    image-20240725160914310

    image-20240725160946389

  7. Vue插件的使用(swiper插件为例)

    1. 下载

      npm install vue-awesome-swiper -S

    2. 引入

      • 全局引入在main.js

        import VueAwesomeSwiper from 'vue-awesome-swiper'
        import 'swiper/dist/css/swiper.css'
        Vue.use(VueAwesomeSwiper)
      • 局部按需引入:单组件引入

        import 'swiper/dist/css/swiper.css'
        import { swiper, swiperSlide } from 'vue-awesome-swiper'
        export default {
        components:{
        swiper,
        swiperSlide
        }
        }
    3. 配置

      相关文档

  8. 样式穿透

    1. 通用(在选择器前面加入):::v-deep

    2. stylus:>>>

    3. sass和less:/deep/

四、生命周期和接口请求

  1. Vue生命周期

    1. 是什么?

      vue中每一个组件都是独立的,每一个组件都有自己的生命周期,从一个组件创建、数据初始化、挂载、更新、销毁

    2. 有哪些?

      beforeCreate、created

      beforeMount、mounted

      beforeUpdate、updated

      beforeDestroy、destroyed

    3. 打开一个组件会执行哪些生命周期

      beforeCreate、created

      beforeMount、mounted

      this.$data:组件的data数据 、created时有

      this.$el:组件的template节点、mounted时有

  2. axios和代理配置

    vue项目中axios进行接口的请求

    1. 下载

      npm install axios -S

    2. 引入

      在main.js中

      import axios from 'axios'

      Vue.prototype.axios = axios;

    3. 使用

      • GET

        <template>
        <div>
        <ul>
        <li v-for='(item,index) in list' :key='index'>
        {{ item.name }}
        <img :src="item.mgurl">
        </li>
        </ul>
        </div>
        </template>
        <script>
        export default {
        name:'App',
        data(){
        return {
        list:[]
        }
        }
        components:{
        HelloWorld
        },
        created(){
        this.axios({
        url:'xxx',
        params:{
        page:1,
        size:3
        }
        }).then(res=>{
        this.list = res.data.data
        // console.log(res.data.data);
        })
        }
        }
        </script>
      • POST

        <template>
        <div>
        <ul>
        <li v-for='(item,index) in list' :key='index'>
        {{ item.name }}
        <img :src="item.mgurl">
        </li>
        </ul>
        </div>
        </template>
        <script>
        export default {
        name:'App',
        data(){
        return {
        list:[]
        }
        }
        components:{
        HelloWorld
        },
        created(){
        this.axios({
        url:'xxx',
        method:"POST",
        data:{
        page:1,
        size:3
        }
        }).then(res=>{
        this.list = res.data.data
        // console.log(res.data.data);
        })
        }
        }
        </script>
    4. vue项目在开发阶段设置代理

      1. 项目根目录需要新建vue.config.js

      2. 配置代理

        module.exports = {
        devServer:{
        proxy:{
        '/api':{
        // target:'<url>',
        target:'http://localhost:3000',
        changeOrigin:true
        }
        }
        }
        }
        // /api === http://localhost:3000
        axios({
        url:'/api'
        })
    5. 后端

      1. 全局安装

        npm install express-generator -g

      2. 创建项目

        express --view=ejs 项目名称

      3. 安装依赖

        cd server

        npm install

        npm start

      4. 后端接口

        server / routes / index.js

五、Vue路由

  1. 路由安装和介绍

    (router、单页面应用、SPA)

    单页面应用:一个url(只有一个html)

    • 跳转不同组件

    • 路径传值(url传值)

    • 拦截


    1. 使用

      vue create 项目名称

      选择自定义安装:安装Router

      目录结构:

      • router / index.js:路由的配置
      • views:页面(组件)
      • components:页面模块(页面中的组件)
  2. router-link相关配置

    1. to:表示目标路由的链接

      • 跳转形式

        <router-link to="/my" tag='div'>我的</router-link>
        <router-link :to='{path:"/my"}'>我的</router-link>
        <router-link :to='{name:"Cart"}'>购物车</router-link>
      • 路径传值

        <router-link :to='{ name:"Cart", query:{a:1} }'>购物车</router-link>
    2. tag:默认生成a标签,如果希望修改 tag='li'

    3. 组件形式的跳转方式

      replace

      <router-link :to='{ path:"Cart" replace }'>购物车</router-link>

      append

    4. exact:精准路由匹配模式

    5. js的跳转方式

      router.push:打开新页面,并且可以返回上一页

      this.$router.push('/list');

      router.replace:打开新页面,不能返回上一页

      this.$router.replace('/list');

      router.go:打开新页面,跳转几层

      this.$router.go();

      router.back:返回上一页

      this.$router.back();

      传值

      this.$router.push({
      path:'/list',
      query:{
      type:'selectList'
      }
      });
  3. router内文件配置

    const routes = {
    // 直接引入
    {
    path:"/",
    name:"Home",
    component:Home,
    // 二级路由
    children:[
    {
    path:'city',
    name:'city',
    component:()=>import("../views/About.vue"),
    },
    ]
    },
    // 懒加载路由
    {
    path:"/about",
    name:"About",
    component:()=>import("../views/About.vue"),
    },
    // 重定向
    {path:'*',redirect:'/'}
    };
  4. 路径传值

    1. 传值

      this.$router.push({
      path:'/list',
      query:{
      a:1
      }
      });
    2. 接收

      created(){
      console.log( this.$route.query.a );
      }
  5. 导航守卫(拦截)

    1. 全局

      router.beforeEach((to,from,next)=>{})
      router.afterEach((to,from,next)=>{})
    2. 组件内守卫

      beforeRouteEnter((to,from,next)=>{})
      beforeRouteUpdate((to,from,next)=>{})
      beforeRouteLeave((to,from,next)=>{})
    3. 路由独享

      beforeEnter((to,from,next)=>{})

    to:这是你跳转到哪个路由对象

    from:这是你要离开的路由对象

    next:是一个方法,可以接受参数,这个方法必须调用

    • next():告诉保安要过去,去哪里就是to

    • next(false):可以不通过,中断跳转

    • next('/'):保安不让过,你可以去另一个地方进行通过

    router.beforeEach((to,from,next)=>{
    let userInfo = false;
    if(to.name == 'About'){
    if(!userInfo){
    router.push('/login')
    }
    }
    next();
    })
  6. watch:监听

    1. 监听路由的变化

      watch:{
      $route(to,from){
      // to:最新的路由
      // from:上一次的路由
      console.log(to.path, from.path);
      }
      }
    2. 监听数据的变化

      监听b但是是b的c改变了,所以要监听b内所有的属性变化,就要用到深度监听

      <template>
      <div class="about">
      <h1>A:{{ a }}</h1>
      <h2>B:{{ b.c }} </h2>
      <button @click='btn'>按钮</button>
      </div>
      </template>


      <script type="text/javascript">
      export default{

      data () {
      return {
      a:1,
      b:{
      c:1
      }
      }
      },
      methods:{
      btn(){
      this.a = 2;
      this.b.c = 2;
      }
      },
      watch:{

      a(to,from){
      //to ==> 最新的
      //from ==> 上一次的
      console.log(to,from)
      },
      b:{
      handler(to){
      console.log('B:',to.c)
      },
      //深度监听
      deep:true
      }

      }

      }
      </script>

六、Vuex

vuex:状态管理(集中式存储管理应用的所有组件的状态)

vuex的属性:

state: {},       // 放入数据[类似于组件中的data]
getters: {}, // 就是一个计算属性[类似于组件中的computed]
mutations: {}, // 就是一个存放方法的[类似于组件中的methods]
actions: {}, // Action 类似于 mutation [Action 提交的是 mutation,而不是直接变更状态。]
modules: {}, // 分成多个模块,每个模块都有state、getters、mutations、actions
  1. Vuex中的state

    state数据如何拿到

    // 方式一
    {{ $store.state.a }}
    // 方式二
    {{ a }}
    {{ b }}
    {{ arr }}
    <script>
    import { mapState } from 'vuex'
    export default{
    computed:{
    ...mapState(['a','b','arr'])
    }
    }
    </script>
  2. Vuex中的getters

    / store / index.js

    import Vue from "vue";
    import Vuex from "vuex";

    Vue.use(Vuex);

    export default new Vuex.Store({
    state: {
    list:[
    {
    goods_id:1,
    goods_name:'鞋',
    goods_num:3,
    goods_price:299
    },
    {
    goods_id:2,
    goods_name:'衣服',
    goods_num:2,
    goods_price:49
    }
    ]
    },
    getters:{
    total( state ){
    let obj = {
    count:0,
    num:0
    }
    state.list.forEach(v=>{
    //总价
    obj.count += v.goods_num * v.goods_price;
    //总数量
    obj.num += v.goods_num;
    })
    return obj;
    }
    },
    mutations: {},
    actions: {},
    modules: {},
    });
    共:{{ total.num }}
    总计:{{ total.count }}
    <script>
    import { mapState, mapGetters } from 'vuex'
    export default{
    computed:{
    ...mapState(['list']),
    ...mapGetters(['total'])
    }
    }
    </script>
  3. Vuex中的mutations

    <template>
    <div class="home">
    <h1>这是首页Home</h1>
    <ul>
    <li v-for='(item,index) in list' :key='index'>
    {{item.appName}}
    </li>
    </ul>
    </div>
    </template>
    <script>
    import axios from 'axios'
    import {mapMutations,mapState} from 'vuex'
    export default {
    name: "Home",
    created(){
    this.getData();
    },
    computed:{
    ...mapState(['list'])
    },
    methods:{
    ...mapMutations(['initData']),
    getData(){

    axios({
    url:'http://39.101.217.150:8075/apps/list',
    params:{
    page:1,
    size:3
    }
    }).then(res=>{
    this.initData(res.data.data.records);
    })

    }
    }
    };
    </script>
    export default new Vuex.Store({
    state: {
    a:1,
    list:[]
    },
    mutations: {
    initData( state , data ){
    state.list = data;
    },
    changeBtn( state ){
    state.a = 'abc'
    }
    },
    actions: {},
    modules: {},
    });
  4. Vuex中的actions

    <template>
    <div class="home">
    <ul>
    <li v-for='(item,index) in list' :key='index'>
    <input type="radio" :checked='item.checked'>
    </li>
    </ul>
    <label @click='checkedFn'>
    <input type="radio" :checked='checkAll'/>全选
    </label>
    </div>
    </template>

    <script type="text/javascript">
    import {mapState,mapGetters,mapActions} from 'vuex'
    export default{
    computed:{
    ...mapState(['list']),
    ...mapGetters(['checkAll'])
    },
    methods:{
    ...mapActions(['checkedFn'])
    }
    }
    </script>
    import Vue from "vue";
    import Vuex from "vuex";

    Vue.use(Vuex);

    export default new Vuex.Store({
    state: {
    list:[ //===> length 6
    {checked:false},
    {checked:false},
    {checked:false},
    {checked:false},
    {checked:false},
    {checked:false}
    ],
    selectedList:[]
    },
    getters: {
    checkAll( state ){
    return state.list.length == state.selectedList.length;
    }
    },
    mutations: {
    //全选
    checkAll( state ){
    state.selectedList = state.list.map(v=>{
    v.checked = true;
    })
    },
    //全不选
    unCheckAll( state ){
    state.list.forEach(v=>{
    v.checked = false;
    })
    state.selectedList = [];
    }
    },
    actions: {
    checkedFn({commit,getters}){

    getters.checkAll ? commit('unCheckAll') : commit('checkAll')

    }
    },
    modules: {},
    });
    • mapStatemapGetters放在组件中的computed中

    • mapMutationsmapActions放在组件中的methods中

  5. actions和mutations的区别【面试题】

    • action 提交的是 mutation,而不是直接变更状态

    • mutations 是同步的、action 可以包含任意异步操作

    • action 更加容易调试

  6. Vuex中的modules

    目录结构:

    image-20240726231609152

    // index.js
    import Vue from "vue";
    import Vuex from "vuex";
    Vue.use(Vuex);

    import path from './modules/path'
    import order from './modules/order'
    export default new Vuex.Store({
    modules: {
    path,
    order
    }
    });
    // part.js
    export default{
    state:{
    list:['地址1','地址2']
    },
    getters:{},
    mutations:{
    btn(){
    alert(1);
    }
    },
    actions:{}
    }
    <template>
    <div class="home">
    <img alt="Vue logo" src="../assets/logo.png" />

    <button @click='btn'>按钮</button>

    地址:{{pathList}}
    订单:{{orderList}}
    </div>
    </template>

    <script>
    import {mapState,mapMutations} from 'vuex'

    export default {
    name: "Home",
    computed:{
    ...mapState({
    pathList:state=>state.path.list,
    orderList:state=>state.order.list
    })
    },
    methods:{
    ...mapMutations(['btn'])
    }
    };
    </script>

七、Vue面试题

  1. 双向绑定的原理

    通过数据劫持结合发布者订阅者模式,使用Object.defineProperty方法对每个属性的get和set进行拦截,当数据发现变化时,发布消息给订阅者,触发相应的监听回调,实现视图和数据的双向同步更新

    Object.defineProperty(对象,'属性',{
    // 设置劫持
    set(){

    },
    // 获取劫持
    get(){

    }
    })
    <!DOCTYPE html>
    <html>
    <head>
    <title></title>
    </head>
    <body>
    <input type="" name="" id='btnInput'>
    <h1 id='h1'></h1>
    <h2 id='h2'></h2>
    <script type="text/javascript">
    let btnInput = document.getElementById("btnInput");
    let h1 = document.getElementById('h1');
    let h2 = document.getElementById('h2');
    let obj = {};
    //只要赋值,给我console.log( '赋值了' )
    //obj.xx = '123';

    Object.defineProperty(obj,'names',{
    //设置劫持
    set( val ){

    h1.innerHTML = val;
    h2.innerHTML = val;

    console.log( '设置' );

    names=val;
    },
    //获取劫持
    get(){
    return names;
    }
    })

    btnInput.oninput = function(){

    obj.names = this.value;
    }

    </script>

    </body>
    </html>
  2. Object.freeze性能优化

    用于冻结对象,禁止对于该对象的属性进行修改(由于数组本身也是对象,因此该方法可以对数组使用)

    async created(){
    let res = await axios({
    url:'xxx'
    params:{
    page:1,
    size:3
    }
    })
    this.list = Object.freeze(res.data.data.records);
    }
  3. Vue生命周期

    1. vue的生命周期有哪些

      beforeCreate
      created
      beforeMount
      mounted
      beforeUpdate
      updated
      beforeDestroy
      destroyed
    2. 一旦进入组件或者页面,会执行哪些生命周期

      beforeCreate
      created
      beforeMount
      mounted
    3. 如果使用了keep-alive会多出来俩个生命周期

      activated
      deactivated
    4. 如果使用了keep-alive第一次进入组件会执行5个生命周期

      beforeCreate
      created
      beforeMount
      mounted
      activated
    5. 如果使用了keep-alive第二次或者第N次,每次都会执行一个生命周期

      activated
  4. v-show和v-if的区别

    • v-show:显示和隐藏 : display:none进行隐藏 、display:block进行显示
    • v-if:创建和删除:remove、append
    • 区别:
      • 显示和隐藏用:v-show
        创建和删除用:v-if
      • 频繁切换用:v-show
        不频繁切换用:v-if
      • 首次加载:用v-if,不用v-show
        • 如果用v-if可以没有这个盒子,然后再通过v-if进行创建(但是第一次进入页面是没有这个盒子,是不加载的)。
        • 如果用v-show这个盒子不管是显示还是隐藏,该盒子都是在的(节点都是存在)
    • 使用场景:
      • v-show : 加入购物车、分享、蒙层这种都基本上用v-show
        v-if : 首页栏目切换的时候v-if
  5. v-if和v-for的优先级

    v-for的优先级要比v-if的优先级高

    证明这个事情,是在vue.js源码中10997行

    if (el.staticRoot && !el.staticProcessed) {
    return genStatic(el, state)
    } else if (el.once && !el.onceProcessed) {
    return genOnce(el, state)
    } else if (el.for && !el.forProcessed) {
    return genFor(el, state)
    } else if (el.if && !el.ifProcessed) {
    return genIf(el, state)
    } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
    return genChildren(el, state) || 'void 0'
    } else if (el.tag === 'slot') {
    return genSlot(el, state)
    } else {

    注:v-if和v-for不要写在同一个节点上,这个性能很差。(v-if要写在父节点上)

  6. ref、keep-alive、nextTick

    1. ref:获取dom

      场景:如果项目中使用插件,并且插件是要获取dom的,那么就可以使用ref了。

    2. keep-alive:缓存组件

      一旦使用keep-alive会多两个生命周期,activated、deactivated

      功能:提升性能的

    3. nextTick:当dom更新完毕执行内部代码

      场景:使用插件的时候会用到。例如new Swiper这个插件可能会获取当前元素的宽度或者高度,等dom都加载完毕再去获取宽度和高度就不会有任何问题了。

  7. computed、methods、watch区别

    1. computed:计算属性

      可以监听某些数据的变化,并且有缓存。

      如果一进入页面调用,就会触发

    2. methods : 可以放入函数

      没有缓存

      如果一进入页面调用,就会触发

    3. watch :监听(路由和数据)

      当数据发生改变时,才会触发

      可以得到现在的值和过去的值

  8. 组件通信

    1. 父传子

      父:
      <HelloWorld :msg="str" />
      <HelloWorld :msg="str" ></HelloWorld>

      子:
      props:['msg']
      props: {
      msg: String,
      },
    2. 子传父

      子:
      <button @click="changeParentName">改变父组件的name</button>
      export default {
      methods: {
      //子组件的事件
      changeParentName: function() {
      this.$emit('handleChange', 'Jack') // 触发父组件中handleChange事件并传参Jack
      // 注:此处事件名称与父组件中绑定的事件名称要一致
      }
      }
      }

      父:
      <child @handleChange="changeName"></child>
      methods: {
      changeName(name) { // name形参是子组件中传入的值Jack
      this.name = name
      }
      }
    3. 兄弟组件传值

      创建bus作为中转

      import Vue from "vue";
      export default new Vue;
      A组件:
      <button @click='btn'>HelloWorld按钮</button>
      data () {
      return {
      hlStr:"这是helloWorld组件的数据"
      }
      },
      methods:{
      btn(){
      bus.$emit('selectItem',this.hlStr);
      }
      }

      B组件:
      created(){
      bus.$on('selectItem',(val)=>{
      console.log( val , 1111);
      })
      }
  9. slot插槽

    使用场景:组件中有些地方的布局可能大多一致,但是细微有些小小变化

  10. 路由

    1. SPA单页面应用和传统页面跳转有什么区别?

      SPA跳转是一个页面进行切换

      传统页面跳转就是跳转不同的html了

      SPA对于seo部分不是特别好,只能收录一个

      传统的页面对于seo比较好,多个html文件收录

    2. 路径传值

      显示:

      传:
      this.$router.push({
      path:'/about',
      query:{
      key:'你好'
      }
      })
      接:
      this.$route.query

      隐示:

      传:
      this.$router.push({
      name:'About',
      params:{
      key:'你好'
      }
      })
      接:
      this.$route.params
    3. 路由的模式

      mode: “history” http://localhost:8080/about

      mode:”hash” http://localhost:8080/#/about

    4. 路由导航守卫(拦截、路由钩子函数)

      全局

      beforeEach
      beforeResolve
      afterEach

      路由独享

      beforeEnter

      组件内

      beforeRouteEnter
      beforeRouteUpdate (2.2 新增)
      beforeRouteLeave

      场景:要去拦截,判断用户是否是登录状态。功能:进入地址管理,用户如果没有登录是进入不了地址管理(在进入之前判断拦截),需要先登录。

    5. 子路由、动态路由

      子路由:children
      动态路由:path: '/user/:id'

  11. Vuex

    1. Vuex有哪些部分构成

      state、getters、mutations、actions、modules

    2. 什么场景用Vuex

      共享、方便管理、方便维护、组件传值……

      项目:购物车数据,订单数据,用户的登录信息….

    3. mutations和actions的区别

      本质区别:
      mutations 必须是同步函数
      actions “可以包含”任意异步操作

      使用区别:mutations中可以放入函数,actions也可以放入函数,但是一般我们在mutations中放入函数而actions是提交mutations

  12. v-model双向绑定原理

    通过Object.defineProperty劫持数据发生的改变,如果数据发生改变了(在set中进行赋值的),触发update方法进行更新节点内容(),从而实现了数据双向绑定的原理。

  13. diff算法

    功能:提升性能

    虚拟dom —-> 其实就是数据( 把dom数据化 )

    主流:snabbdom、virtual-dom

    snabbdom:https://www.npmjs.com/package/snabbdom

    1. 搭建环境

      npm init -y

      cnpm install webpack@5 webpack-cli@3 webpack-dev-server@3 -S

      cnpm install snabbdom -S

      新建webpack.config.js

      配置webpack.config.js
    2. 虚拟节点 和 真实节点

      虚拟节点:

      {
      children: undefined
      data: {}
      elm: h1
      key: undefined
      sel: "h1"
      text: "你好h1"
      }

      真实节点:

      <h2>你好</h2>
    3. 新老节点替换的规则

      1. 如果新老节点不是同一个节点名称,那么就暴力删除旧的节点,创建插入新的节点。

      2. 只能同级比较,不能跨层比较。如果跨层那么就暴力删除旧的节点,创建插入新的节点。

      3. 如果是相同节点,又分为很多情况

        1. 新节点有没有children

          如果新的节点没有children,那就证明新节点是文本,那直接把旧的替换成新的文本

        2. 新节点有children

          新的有children,旧的也有children —-> 就是diff算法的核心了【3】
          新的有children,旧的没有 —-> 创建元素添加(把旧的内容删除清空掉,增加新的)

        3. diff算法的核心(最复杂的情况)

          1. 旧前 和 新前

            匹配:旧前的指针++ 、 新前的指针++

          2. 旧后 和 新后

            匹配:旧后的指针– 、 新后的指针–

          3. 旧前 和 新后

            匹配:旧前的指针++ 、 新后的指针–

          4. 旧后 和 新前

            匹配:旧后的指针– 、 新前的指针++

          5. 以上都不满足条件 —-> 查找

            新的指针++,新的添加到页面上并且新在旧的种有,要给旧的复制成undefined

          6. 创建或者删除

        注意:如果要提升性能,一定要加入key,key是唯一标示,在更改前后,确认是不是同一个节点。

  14. snabbdom

  15. 手写diff算法 — 生成虚拟dom

    // h.js
    import vnode from './vnode'
    export default function( sel, data, params){
    // h函数的 第三个参数是字符串类型,意味着没有子元素
    if(typeof params == 'string'){
    return vnode(sel, data, underfined, params, underfined)
    }else if(Array.isArray(params)){
    let children = [];
    for(let item of params){
    children.push(item);
    }
    return vnode(sel, data, children, underfined, underfined)
    }
    }
    // index.js
    import h from './h'
    let vnode1 = h('div', {}, '你好');

    let vnode2 = h('ui', {}, [
    h('li', {}, 'a'),
    h('li', {}, 'b'),
    h('li', {}, 'c'),
    h('li', {}, '你好'),
    ])
    console.log(vnode2)
    // vode.js
    export default function(sel, data, children, text, elm){
    return {
    sel,
    data,
    children,
    text,
    elm
    }
    }
  16. 手写diff算法 — patch不是同一个节点 — 相同节点有没有chrildren

    // vode.js
    export default function(sel, data, children, text, elm){
    let key = data.key;
    return {
    sel,
    data,
    children,
    text,
    elm,
    key
    }
    }
    <!-- index.html -->
    <body>
    <div id="container">
    这是container
    </div>
    </body>
    // index.js
    import h from './h'
    import patch from './patch'
    // 获取真实dom节点
    let container = document.getElementById('container');
    // 虚拟节点
    let vnode1 = h('div', {}, '你好');
    let vnode2 = h('ui', {}, [
    h('li', {key:'a'}, 'a'),
    h('li', {key:'b'}, 'b'),
    h('li', {key:'c'}, 'c'),
    h('li', {key:'d'}, 'd'),
    ])
    patch(container, vnode1);
    patch(container, vnode2);
    // patch.js
    import vnode from './vnode'
    import createElement from './createElement'
    import newVnode from './patchVnode'
    export default function(oldVnode, newVnode){
    // 如果oldVnode没有sel,就证明是虚拟节点(让他变成虚拟节点)
    if(oldVnode.sel == underfined){
    oldVnode = vnode(
    oldVnode.tagName.toLowerCase(), // sel
    {}, // data
    [],
    underfined,
    oldVnode
    )
    }
    // 判断两个节点是否是同一个节点
    if(oldVnode.sel === newVnode.sel){
    // 判断条件复杂
    patchVnode(oldVnode, newVnode);
    }else{
    // 不是同一个节点,那么就暴力删除旧的节点,创建插入新的节点
    // 把新的虚拟节点创建为dom节点
    let newVnodeElm = createElement(newVnode);

    // 获取旧的虚拟节点,elm就是真正的节点
    let oldVnodeElm = oldVnode.elm;

    // 创建新的节点
    if(newVnodeElm){
    oldVnodeElm.parentNode.insertBefore(newVnodeElm, oldVnodeElm)
    }
    // 删除旧的节点
    oldVnodeElm.parentNode.removeChild(oldVnodeElm);
    }
    }
    // createElement.js
    // vnode为新节点,就是要创建的节点
    export default function createElement(vnode){
    let domNode = document.createElement(vnode.sel);
    // 判断有没有子节点 children 是不是为underfined
    if(vnode.children == underfined){
    domNode.innerText = vnode.text;
    }else if(Array.isArray(vnode.children)){
    // 新的节点有children(子节点),需要递归创建节点
    for(let child of vnode.children){
    let childDom = createElement(child);
    domNode.appendChild(childDom);
    }
    }
    // 补充elm属性
    vnode.elm = domNode;
    return domNode;
    }
    // patchVnode.js
    import createElement from './createElement'
    import updateChildren from './updateChildren'
    export default function patchVnode(oldVnode, newVnode){
    // 判断新节点有没有children
    if(newVnode.children === underfined){
    // 没有子节点
    // 新节点的文本合旧节点的文本内容是不是一样的
    if(newVnode.text !== oldVnode.text){
    oldVnode.elm.innerText = newVnode.text
    }
    }else {
    // 有子节点
    // 新节点虚拟节点有,旧的虚拟节点有
    if(oldVnode.children !== underfined && oldVnode.children.legth > 0){
    // 最复杂,diff核心
    updateChildren(oldVnode.elm, oldVnode.children, newVnode.children)
    }else{
    // 新节点虚拟节点有,旧的虚拟节点没有

    // 把旧节点内容清空
    oldVnode.elm.innerHTML = '';
    // 遍历新的子节点,创建dom元素
    for(let child of newVnode.children){
    let childDom = createElement(child);
    oldVnode.elm.appendChild(childDom);
    }
    }
    }
    }
    // updateChildren.js
    import patchVnode from './patchVnode'
    import createElement from './createElement'
    // 判断两个虚拟节点是否为同一个节点
    function sameVnode(vNode1, vNode2){
    return vNode1.key == vNode2.key;
    }
    // 参数1:真实dom节点
    // 参数2:旧的虚拟节点
    // 参数3:新的虚拟节点
    export default (parentElm, oldCh, newCh) => {
    let oldStartIdx = 0; // 旧前指针
    let oldEndIdx = oldCh.length - 1; // 旧后指针
    let newStartIdx = 0; // 新前指针
    let newEndIdx = newCh.length - 1; // 新后指针

    let oldStartVnode = oldCh[0]; // 旧前虚拟节点
    let oldEndVnode = oldCh[oldEndIdx]; // 旧后虚拟节点
    let newStartVnode = newCh[0]; // 新前虚拟节点
    let newEndVnode = newCh[newEndIdx]; // 新后虚拟节点

    while(oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx){
    if(oldStartVnode == underfined){
    oldStartVnode = oldCh[++oldStartIdx];

    }
    if(oldEndVnode == underfined){
    oldEndVnode = oldCh[--oldEndIdx];

    }else if(sameVnode(oldStartVnode, newStartVnode)){
    // 旧前 和 新前
    patchVnode(oldStartVnode, newStartVnode);
    if(newStartVnode){
    newStartVnode.elm = oldStartVnode?.elm;
    }
    oldStartVnode = oldCh[++oldStsrtIdx];
    newStartVnode = newCh[++newStsrtIdx];

    }else if(sameVnode(oldEndVnode, newEndVnode)){
    // 旧后 和 新后
    patchVnode(oldEndVnode, newEndVnode);
    if(newEndVnode){
    newEndVnode.elm = oldEndVnode?.elm;
    }
    oldEndVnode = oldCh[--oldEndVnode];
    newEndVnode = newCh[--newEndVnode];

    }else if(sameVnode(oldStartVnode, newEndVnode)){
    // 旧前 和 新后
    patchVnode(oldStartVnode, newEndVnode);
    if(newEndVnode){
    newEndVnode.elm = oldStartVnode?.elm;
    }
    // 把旧前指定的节点移动到旧后指向的节点的后面
    parentElm.insertBefore(oldEndVnode.elm, oldEndVnode.elm.nextSibling)
    oldStartVnode = oldCh[++oldStartVnode];
    newEndVnode = newCh[--newEndVnode];

    }else if(sameVnode(oldEndVnode, newStartVnode)){
    // 旧后 和 新前
    patchVnode(oldEndVnode, newStartVnode);
    if(newStartVnode){
    newStartVnode.elm = oldEndVnode?.elm;
    }
    // 把旧后指定的节点移动到旧前指向的节点的前面
    parentElm.insertBefore(oldStartVnode.elm, oldStartVnode.elm)
    oldEndVnode = oldCh[--oldEndVnode];
    newStartVnode = newCh[++newStartVnode];

    }else{
    // 以上都不满足查找
    // 创建一个对象,存虚拟节点(判断新旧有没有相节点)
    const keyMap = {};
    for(let i = oldStartIdx; i <= oldEndIdx; i++){
    const key = oldCh[i]?.key;
    if(key){
    keyMap[key] = i;
    }
    }
    // 在旧节点中查找新前指向节点
    let idxInOld = keyMap[newStartVnode.key];
    // 如果有,说明数据在新旧虚拟节点中都存在
    if(idxInOld){
    const elmMove = oldCh[idxInOld];
    patchVnode(elmMove, newStartVnode);
    // 处理过的节点,在旧虚拟系欸但的数组中,设置为underfined
    oldCh[idxInOld] = underfined;
    parentElm.insertBefore(elmMove.elm, oldStartVnode.elm);
    }else{
    // 如果没有找到,说明是一个新的节点(创建)
    parentElm.insertBefore(createElement(newStartVnode), oldStartVnode.elm);
    }
    newStartVnode = newCh[++newStartIdx];
    }
    }
    // 结束循环只有两种情况,新增和删除
    // 1、oldStartIdx > oldEndIdx
    // 2、newStartIdx > newEndIdx
    if(oldStartIdx > oldEndIdx){
    const before = newCh[newEndIdx+1] ? newCh[newEndIdx+1].elm : null;
    for(let i = newStartIdx; i <= newEndIdx; i++){
    parentElm.insertBefore(createElement(newCh[i]), before);
    }
    }else{
    // 进入删除操作
    for(let i = oldStartIdx; i <= oldEndIdx; i++){
    parentElm.removeChild(oldCh[i].elm)
    }
    }
    }
  17. 谈一下MVVM框架

    web1.0时代:

    文件全在一起,就是前端和后端的代码全在一起

    问题:

    • 前端和后端都是一个人开发,技术没有侧重点或者责任不够细分
    • 项目不好维护
    • html、css、js页面的静态内容没有,后端是没办法工作的(没办法套数据)

    mvc…都是后端先出的

    web2.0时代:

    ajax出现了,前端和后端数据分离了。

    解决问题:后端不用等前端页面弄完没,后端做后端的事情(写接口),前端布局、特效、发送请求

    问题:

    • html、css、js都在一个页面中,单个页面可能内容也是比较多的(也会出现不好维护的情况)

    出现前端的框架MVC、MVVM

    解决问题:可以把一个特别大的页面进行拆分(组件化),单个组件进行维护

    什么是MVVM:

    Model-View-ViewModel 的简写

    img

    view:视图【dom在页面中展示的内容】

    model:模型【数据层:vue中的data数据】

    ViewModel:视图模型层【就是vue源码】

    img