vue的面试题

234 阅读7分钟

什么是vue.js

解释:

  • 它是一个前端的一个js框架
  • 它是用于构建用户界面的渐进式框架

核心:

  • 数据驱动
  • 双向数据绑定
  • 组件化
  • 模块化
  • MVVM框架

优点:

  • 体积小:压缩后的vue只有20K+
  • 数据双向绑定:让开发者不在操作DOM,把更多的精力投入到业务逻辑中
  • 组件化:增加代码复用性、灵活性、提高开发效率、便于维护
  • 生态丰富、学习成本低:市场上有大量、稳定的基于vue.js的ui框架,学习起来比较容易
  • 便与第三方库或者已有的项目整合
  • 虚拟DOM(VNode)

缺点

SEO难度较大

浅谈Vue、Angulary、React之间的区别

  • vue更加轻量,压缩后只有20k+,react压缩后44k,angulary压缩后56k(只是参考值)
  • vue入门简单,angulary学习较难,react学习的东西相对较多
  • vue和angulary都有内置指令和过滤器并且都支持自定义指令和过滤器
  • 在组开发中vue用的是特殊文件格式.vue,react用的是jsx语法;react用的是函数式编程,两者都有相同点就是组件化
  • vue用的是DOM模板,而react依赖于虚拟DOM

浅谈什么是虚拟DOM(Virtual DOM)

解释:

虚拟DOM不是真实的DOM,而是一个轻量级的js对象,用js对象模拟真实的DOM,当状态发生改变时,通过diff算法比较出虚拟dom的差异,在通过path算法将差异应用到真实DOM上

优点:

提高渲染性能、无需手动操作真实dom

缺点:

首次渲染DOM时,由于多了一层虚拟DOM计算,会比innerHTML插入慢

vue中的MVVM

Ps:MVC全名modelViewController,model(数据),view(视图),controller(控制器)。当用户操作界面时会向后端发送请求,请求会被路由进行拦截,然后转发给对应的控制器进行处理,控制器会获取数据,最后将数据回显到视图上,这种形式是单向的。

解释:

MVVM它是一种设计模式,源于经典的MVC,它的核心是ViewModul(数据视图层),它的出现促进了前端开发和后端业务逻辑的分离,提高了工作效率

MVVM全名model-view-viewModel:

  • M-Model(数据模型)
{
	"url": "/your/server/data/api",
	"res": {
	    "success": true,
	    "name": "IoveC",
	    "domain": "www.cnblogs.com"
	}
}
  • V-View(视图层)
<div id="app">
    <p>{{message}}</p>
    <button v-on:click="showMessage()">Click me</button>
</div>
  • VM-ViewModel(new Vue({.....})视图数据层)
var app = new Vue({
    el: '#app',
    data: {  // 用于描述视图状态   
        message: 'Hello Vue!', 
    },
    methods: {  // 用于描述视图行为  
        showMessage(){
            let vm = this;
            alert(vm.message);
        }
    },
    created(){
        let vm = this;
        // Ajax 获取 Model 层的数据
        ajax({
            url: '/your/server/data/api',
            success(res){
                vm.message = res;
            }
        });
    }
})

三者之间的关系:

  • View可以通过绑定事件的形式影响Model,Model通过绑定数据形式影响View
  • ViewModel是将View和Model连接起来的连接器

vue的生命周期

事物从诞生到消亡的整个过程称为生命周期

什么是vue的生命周期:

实例从开始创建、初始化数据、模板编译、挂载DOM、渲染、更新、渲染、卸载等一系列过程,我们称之为生命周期。

各个生命周期的作用:

生命周期描述
beforeCreate实例创建之前,需要注意的是data和methods还没初始化
created实例创建完成,如果想调用methods方法和data中数据最早只能在created操作
beforeMount实例挂载之前,模板只是在内存中编译好了,并没有真正挂载到页面,此时页面还是旧的
mounted实例挂载完成,如果想操作MOD节点,最早只能在mounted中操作
beforeUpdate实例更新之前,此时页面还是旧的,data中的数据是最新的
updated实例更新完成,此时data中的数据和页面保持同步,都是最新的
beforeDestroy实例销毁之前,此时所有的data、methods、指令、过滤器还可以使用,并没有真正销毁
destroyed实例真正被销毁了,此时所有数据、方法、指令、过滤器都不可使用
activatedkeep-alive专属,组件被激活时调用
deactivatedkeep-alive专属,组件停用是被调用
errorCaptured当捕获一个子孙组件错误是调用

生命周期示意图:

avatar

computed和watch的区别

computed计算属性:

解释:

将数据处理后显示,计算结果会被缓存,除非依赖的状态发生改变才会重新计算

本质写法和简写:

computed: { 
    // 本质写法
    rice: {
        get() { // 读取
            return this.name + this.lastName
        },
        set(v) { // 设置 不常用 v指的是修改值
            console.log(v)
        }
    },
    // 简写 
    rice() {
        let result = 0;
        for (let i = 0; i < this.list.length; i++) {
            result += this.list[i].pirc*1
        }
        return result;
    }
}

watch数据监听:

解释:

watch是个对象,以键值对的形式存在,键是被监听者,值可以是函数、函数名、数组、有选项对象(handler/immedieat/deep)

    var app = new Vue({
        el: "#app",
        data:{
            num: 27,
            defaultChuanYi: "T恤短袖",
            yifuArray: ["T恤短袖","夹克长裙","棉衣羽绒服"],
            obj: {
                age: 14,
                sex: "男"
            }
        },
        methods: {
            add () {
                this.num++
                this.yifuArray.push(1)
            },
            reduce () {
                this.num--
            },
            numName(newVal,oldVal) {
                console.log(newVal,oldVal)
            }
        },
        // 1.函数写法
        watch: {
            num: function (newVal,oldVal) {
                if(newVal > 26){
                    this.defaultChuanYi = this.yifuArray[0]
                }else if(newVal > 0 && newVal < 26) {
                    this.defaultChuanYi = this.yifuArray[1]
                }else if(newVal < 0){
                    this.defaultChuanYi = this.yifuArray[2]
                }
            }
        },
        // 2.函数名的写法
        watch: {
            // num: "numName"
            num: {
                handler: "numName",
                immediate: true
            }
        },
        // 3.包含选项对象的写法 (是否首次绑定时执行handler)
        // handler 和 immediate 属性
        // immediate 表示在watch中首次绑定的时候是否执行handler,为true,则执行,为false则和一般使用watch一样,当数据变化才执行handler
        watch: {
            num: {
                handler(newVal,oldVal){
                    console.log(newVal,oldVal)
                },
                immediate: true
            }
        },
        // 4.deep是否深度监听,true深度 false不深度,常用于对象属性的改变
        watch: {
            "obj.age": { // 对象
                handler(newVal,oldVal) {
                    console.log(newVal,oldVal)
                },
                deep:true,
                immediate: true
            },
            yifuArray: { // 数组 (数组的变化不需要深度监听,对象数组需要深度监听)
                handler(newVal,oldVal) {
                    console.log(newVal,oldVal)
                },
                // deep:true,
                immediate: true
            }
        },
        // 5.值为数组时写法
        watch: {
            num: [
                // 函数名
                'numName', 
                // 函数
                function numName1() {
                    console.log(111111111111)
                },
                // 对象
                {
                    handler() {},
                    immediate: true
                }
            ]
        },
    });
    
    // 使用$watch()方法,也就是把我们watch卸载到构造器外部,这样做的好处就是降低我们程序的耦合度
    // app.$watch("num",function (newVal,oldVal) {
    //     if(newVal > 26){
    //         this.defaultChuanYi = this.yifuArray[0]
    //     }else if(newVal > 0 && newVal < 26) {
    //         this.defaultChuanYi = this.yifuArray[1]
    //     }else if(newVal < 0){
    //         this.defaultChuanYi = this.yifuArray[2]
    //     }
    // },{deep:true})

两者之间的区别:

  • computed支持缓存,watch不支持
  • watch支持异步,computed不支持
  • computed是依赖的数据发生改变才重新计算,watch是被监听的数据发生改变才会触发相对应回调
  • 需要依赖某个属性动态获取值的时候使用computed,对于监听的值需要处理复杂的业务逻辑和异步操作时,可使用watch
低耦合:通俗的说就是你写个组件,这个组件在任何项目任何场景随拿随用,不影响其它功能;高内聚:指的是将功能模块集合在一起(例如用户模块,把用户相关的内容放在一起,比如用户的个人信息、用户收藏之类的)

vue中哪些数组方法可以做到响应式(数据改变,视图随之改变)

  • arr.push() 在数组末尾添加一个或多个元素,返回新素组,改变原数组
  • arr.pop() 在数组末尾删除一个元素,返回被删除的元素,改变原数组
  • arr.unShift() 在数组开头添加一个或多个元素,返回新数组,改变原素组
  • arr.shift() 在数组开头删除一个元素,返回被删除的元素,改变原数组
  • arr.splice() 数组的添加、删除、替换,返回的是被删除的元素组成的数组 改变原数组
  • sort() 排序
  • reverse() 反转

Vue.set() 解决某些情况数据变化视图不能随之变化问题

语法:

Vue.set(targer,key,value)

解释:

vue在初始化时会遍历data选项,通过Object.defineProperty()给每个属性加上get、set方法,当你新添加的属性时,这个属性是没有set、get方法的,所以视图不更新

  • 当使用数组下标操作数组的时候

    1.this.list[0] = 'vue' 直接改变,视图不会更新

    2.this.list.splice(1,1,'vue') 使用splice可解决

    3.vue.set(targer,value,key) 使用vue.set()可解决

  • 给对象添加新属性

    1.this.object.name = 'vue' 视图不会随之改变

    2.Vue.set(target,key,value) 使用Vue.set() 可解决

  • 删除对象中的某个属性

    1.this.$delete(this.objName,"name")

  • vuex同理,只是数据是必须提前定义在store中的state里才能达到响应式

常用修饰符


<!--阻止冒泡  相当调用event.stopPropagation()-->
<button @click.stop="doThis"></button>

<!-- 使用事件捕获模式 -->
<button @:click.capture="doThis"></button>

<!-- 阻止默认行为 相当调用event.preventDefault()-->
<button @click.prevent="doThis"></button>

<!-- 即阻止冒泡又阻止默认行为 -->
<button @click.stop.prevent="doThis"></button>

<!-- 事件只会触发一次 -->
<button @:click.once="doThis"></button>

<!-- 当前事件发生在元素本身而不是在子元素的时候才触发回调 -->
<button @:click.self="doThis"></button>

<!-- 对象方式可监听多种事件 -->
<button @on="{ mousedown: doThis, mouseup: doThat }"></button>

<!--组件绑定原生事件.native-->
<my-component @click.native="onClick"></my-component>
    

对象增强写法


<!--属性的简写-->

let name = 'zhang';
let lastName = 'wang';

const obj = { // es5写法
    name:name,
    lastName:lastName
}
console.log(obj) // {name: "zhang", lastName: "wang"}

const obj1 = { // es6写法
    name, 
    lastName
}
console.log(obj1) //  {name: "zhang", lastName: "wang"}

<!--方法的的简写-->

const methondObj = { // es5写法
    run: function () { 

    }
}

const methondObj1 = { // es6写法
    name,
    run() {

    }
} 

为什么v-for要加key属性

解释:

vue会默认遵循就地复用策略,当真实的DOM渲染出来之前,vue会在内存中创建VNode,然后VNode与当前真是DOM之间进行对比,已有的进行复用,不同的进行修改,没有的进行创建,最后应用的真实的DOM上。加上key属性相当于加上了唯一标识,让vue更好的复用(有些情况是让vue更好的不复用)。

作用:

key的主要作用是提高渲染性能,高效更新VNode

v-model 在表单控件或者组件上创建双向绑定

解释:

  • v-model其实是一个语法糖,本质包括两个操作

    1.v-bind绑定Value属性

    2.v-on绑定input事件

  • 元素上使(input文本框为列)

    // 两者等价
    <input type="text" v-model="message">
    
    <input type="text" :value="message" @input="message = $event.target.value"> 
    
  • 组件上使用

    一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input的事件,但是像单选框、复选框等类型的输入控件可能会将value 用于不同的目的。model 选项可以用来避免这样的冲突:

    Vue.component('base-checkbox', {
      model: {
        prop: 'checked',
        event: 'change'
      },
      props: {
        checked: Boolean
      },
      template: `
        <input
          type="checkbox"
          v-bind:checked="checked"
          v-on:change="$emit('change', $event.target.checked)"
        >
      `
    })
    

    现在在这个组件上使用 v-model 的时候:

    <base-checkbox v-model="lovingVue"></base-checkbox>
    
    

    这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的属性将会被更新。

    注意你仍然需要在组件的 props 选项里声明 checked 这个 prop
    

全局注册组件和局部注册组件

  • 组件:组件就是自定义标签,这些标签是html不存在的

  • 组件和指令的区别:组件注册的是标签,指令注册的是已有标签的属性

  • 全局组件&局部组件

    本质写法

    const cpnC = Vue.extend({
       template: `<div><p>全局注册</p></div>` 
    })
    
    // 全局注册组件 (cnp 组件名)
    Vue.component('cnp',cpnC)
    
    const app = new Vue({
        el: '#app',
        data: {
           
        },
        // 局部注册
        components: {
            "cnp": cpnC
        }
    })
    

    到了2.x可以简写,但本质vue内部还是调用的Vue.extend()

    // 全局注册组件 (cnp 组件名)
    Vue.component('cnp1',{
       template: `<div><p>全局注册语法糖</p></div>` 
    })
    
    const app = new Vue({
        el: '#app',
        data: {
           
        },
        // 局部注册
        components: {
            "cpn2": {
                template: `<div><p>局部注册语法糖</p></div>` 
            }
        }
    });
    

为什么data选项是个函数

  • 因为js中的对象是引用关系,组件中的data是个函数的话,每次返回的会是一个新对象,避免组件复用时相互影响
  • 如果组件中的data不是一个函数的的话,vue会直接报错

v-slot插槽

解释:

  • 主要作用是决定组件内部显示怎么的内容
  • 好处是让封装的组件更有扩展性、灵活性
  • 在2.6中我们为具名插槽和作用域插槽引入新的统一语法v-slot,用于替代slot和slot-scope
  • v-slot只能用在组件上和template上,不能用在普通元素上,而slot可以用在组件上。(当只要默认插槽时才用在组件上,出现多个插槽必须用在templae上)
  • 一个不带name属性的slot元素有个默认名字default
  • v-slot的语法糖 "#"
  • v-slot:name = "slotProps"和v-slot:name的区别:前者叫作用域插槽,后者叫具名插槽
  • 插槽内容可以是任何代码也可以是组件

2.6以前的用法 (slot slot-scope):

<body>
    <!-- 1.普通插槽 -->
    <!-- 父组件模板 -->
    <div id="app">
        <cpn>
            <p>11111111111</p>
        </cpn> 
    </div>

    <!-- 子组件模板  -->
    <template id="cpnTemp">
        <div>
            <div>子组件标题显示</div>
            <slot></slot>
        </div>
    </template>
    
    <!-- 2.具名插槽 -->
    <!-- 父组件模板 -->
    <div id="app">
        <cpn>
            <p slot="slot1">xxxx我是slot1</p>
            <p slot="slot2">xxxx我是slot2</p>
            <p slot="slot3">xxxx我是slot3</p>
        </cpn> 
    </div>

    <!-- 子组件模板  -->
    <template id="cpnTemp">
        <div>
            <div>子组件标题显示</div>
            <slot name="slot1">我是slot1</slot>
            <slot name="slot2">我是slot2</slot>
            <slot name="slot3">我是slot3</slot>
        </div>
    </template>

    <!-- 3.作用域插槽 -->
    <!-- 父组件模板 -->
    <div id="app">
        <cpn>
            <!-- 用在普通元素上 -->
            <div slot="slot1" slot-scope="messagePro">
                <p>{{messagePro.message}}</p>
            </div>

            <!-- 用在template上 -->
            <template slot="slot1" slot-scope="{message}">
                <div>
                    <p>{{message}}</p>
                </div>
            </template>
        </cpn> 
    </div>

    <!-- 子组件模板  -->
    <template id="cpnTemp">
        <div>
            <div>子组件标题显示</div>
            <slot name="slot1" :message="message">默认内容</slot>
        </div>
    </template>
</body>

<script>
    const app = new Vue({
        el: '#app',
        data: {

        },
        components: {
            cpn: {
                template: '#cpnTemp',
                data() {
                    return {
                        message: '这是子组件传递给父组件的数据'
                    }
                }
            }
        }
    });
</script>

2.6以后用法 (v-slot):

<body>
    <!-- 1.普通插槽-->
    <!-- 父组件 -->
    <div id="app">
        <cpn></cpn>
    </div>

    <!-- 子组件  -->
    <template id="cpnTemp">
        <div>
            <div>子组件</div>
            <slot>我是默认内容</slot>
        </div>
    </template>
    
    <!-- 2.具名插槽-->
    <!-- 父组件 -->
    <div id="app">
        <cpn>
            <template v-slot:header>
                <div>
                    <p>这个是___hearder内容</p>
                </div>
            </template>

            <template v-slot:default>
                <div>
                    <p>这个是___section内容</p>
                </div>
            </template>

            <template #footer>
                <div>
                    <p>这个是___footer内容</p>
                </div>
            </template>
        </cpn>
    </div>

    <!-- 子组件  -->
    <template id="cpnTemp">
        <div>
            <div>子组件</div>
            <header>
                <slot name="header">我是头部内容</slot>
            </header>
            
            <section>
                <slot>我是中间内容</slot>
            </section>
            
            <footer>
                <slot name="footer">我是底部内容</slot>
            </footer>
        </div>
    </template>
    
    <!-- 3.作用域插槽 (插槽内容由子组件提供)-->
    <!-- 父组件 -->
    <div id="app">
        <!-- v-slot用在template上 -->
        <cpn>
            <template v-slot:header="{message,messagetow}">
                <div>
                    <p>{{message}}</p>
                    <p>{{messagetow}}</p>
                </div>
            </template>

            <template #default="slotProps">
                <div>
                    <p>{{slotProps.message}}</p>
                </div>
            </template>
        </cpn>

        <!-- 用在组件上 当只有默认插槽时(一个插槽时)使用在组件上,当出现多个插槽必须写template上 -->
        <!-- <cpn #default="slotProps">
            <p>{{slotProps.message}}</p>
        </cpn> -->
    </div>

    <!-- 子组件  -->
    <template id="cpnTemp">
        <div>
            <div>子组件</div>
            
            <header>
                <slot name="header" :message="message" :messagetow="messagetow">我是头部内容</slot>
            </header>

            <section>
                <slot :message="message">我是中间内容</slot>
            </section>
            
            <!-- <slot :message="message">当只有默认插槽时(一个插槽时)使用在组件上,当出现多个插槽必须写template上</slot> -->
        </div>
    </template>
</body>

<script>
    const app = new Vue({
        el: '#app',
        data: {

        },
        components: {
            cpn: {
                template: '#cpnTemp',
                data() {
                    return {
                        message: '这是子组件传递给父组件的数据message',
                        messagetow: '这是子组件传递给父组件的数据messageTow'
                    }
                }
            }
        }
    });
</script>

runtime-compiler和runtime-only的区别

  • 两者区别主要在main.js文件中
// runtime-compiler
new Vue({
  el: '#app',
  components: { App },
  template: '<App/>'
})
// runtime-only
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')
  • runtime-only构建的项目体积更小,运行速度更快,性能会更高点

组件的渲染过程:

先将template解析成ast(抽象语法树),ast编译成render函数,通过render函数创建虚拟dom,最终渲染真实的DOM。

可以看出runtime-only比runtime-compiler少了一个过程,所有runtime的性能更好点

浅谈async和awite的原理和使用

解释:

async和awite其实就是异步等待的意思

原理:

当函数加上async、awite时,这个函数其实返回的就是Promise对象,即使我们调用的是异步代码,它也会变成类似同步,等异步代码执行完在执行同步

使用:

<script>
    const app = new Vue({
        el: '#app',
        data: {
            totlPirc: 0,
            lastName: ''
        },
        methods: {
        // 模拟异步请求
        addRun() {
          return new Promise((resolve,rejict)=>{
            setTimeout(() => {
              resolve('异步结果')
            },2000)
          })
        },
        // async、await
        async getList() {
          console.log('我在前执行');
          this.data = await this.addRun();
          console.log('我在后执行')
          console.log(this.data)
        }
      },
      created() {
        console.log('组件被创建执行');
        this.getList()
        console.log('craated里不用等待');
        console.log(this.getList()) // 这个async返回的其实是个Promist对象
        // console.log(this.data) 拿不到数据
      },
      updated() {
        console.log(this.data)
      },
    });
</script>

父组件如何监听子组件的生命周期

使用@hook:mounted(每一个生命周期)= 'Event'


//  Parent.vue
<child @hook:mounted="doSomething" ></child>

doSomething() {
   console.log('父组件监听到 mounted 钩子函数 ...');
},

//  Child.vue
mounted(){
   console.log('子组件触发 mounted 钩子函数 ...');
}

vue的响应式原理

data变化更新view,vue是如何做到的呢。其实vue是通过数据劫持配合发布-订阅者模式实现的

具体实现总结以下几点

  • 实现一个监听器Observer

    当把data选项传递给vue时,vue会将data进行遍历,然后利用Object.defineProperty给每个属性加上get、set方法,当数据被使用会触发get方法,当数据被修改会触发set方法,这样就能监听到数据了

  • 实现一个解析器compiley

    用于解析vue模板的指令,初始化视图,给对应的节点绑定更新函数,当数据发生变化,收到通知,会调用更新函数更新数据

  • 实现一个订阅者watcher

    它是Observer和compiler之间通信的桥梁,主要任务是订阅Observer中属性值变化的消息,当收到消息后,就会通知解析器

  • 实现一个订阅器Dep

    主要用于收集watcher,进行统一管理

总结:当数据被修改时通知对应的订阅器Dep,Dep通知对应的watcher,watcher在通知compiler,compiler调用对应更新函数更新数据。

.sync、v-mode (父子组件通信,都是单项的,很多时候需要双向通)

  • .sync修饰符

    他会自动的被扩展为一个自动监听父组件属性的监听器(v-on)

    <comp :foo.sync="foo "></comp>
    等价于
    <comp :foo="bar" @update:foo="foo = $event"></comp>
    this.$emit( 'update:foo', newValue )
    
  • v-mode (参考以上的v-mode)

axios

axios:

axios是一个基于Promise的HTTP库,主要用于网路请求

特点:

  • 支持Promise
  • 请求拦截、响应拦截
  • 能转化请求的数据和响应的数据
  • 安全性较高

组件化思想

组件

组件就是自定义标签,这些标签是html不存在的

思想

将一个完整的页面拆分成许多个组件,每个组件实现对应的功能,而每一个组件又可以细分,最后进行组合

优点

增加复用性、灵活性、提高工作效率、便于维护

模块化

举个例子:开发项目的时候会有多人开发,会引入多个js文件,这样会导致
变量冲突问题,虽然可以用函数自运行的方法解决,但是会引发其他问题,比 如代码不能够复用,这个时候模块化就很好的解决这类问题

  • 模块化的核心是导入和导出
  • 常见的模块化有AMD、CMD、CommonJs、ES6的modules

以CommonJs和ES6的modules举例

CommonJs 导出
module.exports = {
    num,
    ji
}

CommonJs 导入
let {num,ji} = require('./math');

es6的导出
export const result = 0;

export default function() {
    
}

ES6的导入
import {result,info} from './math'

import name from './math'

import * as math from './math' 

exportexport default 两者区别:

a.exportexport default两者均可导出常量、函数、文件、模块
b.一个文件中export可以有多个,export default只能一个
c.通过export导出,导入时需要加 {} ,而export default不需要

优点

  • 避免代码冲突,造成不必要的麻烦
  • 提高工作效率
  • 提高维护性

浅谈服务端渲染 SSR

解释

简单的来说就是整个页面在服务端渲染好,然后返回给客服端展示

优点

更好的SEO、首屏加载更快(缓慢的解决办法:模块化、按需加载、占位图)

缺点

服务器压力大、学习成本高

什么是SPA单页面富应用

解释

整个网站只有一个html页面,当页面初始化时会向服务器一次性请求所有资源(如:html,css,js),部分页面按需加载(如:路由懒加载),然后在通过路由机制改变html的页面内容。

优点

用户体验好、避免页面重新加载、减少服务器压力

缺点

初始化时间较长、SEO难度较大

组件通信-父传子-Props选项

<body>
    <div id="app">
        <div>
            <p>父组件显示</p>
            <p>{{object}}</p>
            <p>{{defaultString}}</p>
            <cpn :init="object" :default-string="defaultString"></cpn>
        </div>
    </div>
    <template id="cpnTemplate">
        <div>
            <p>子组件显示</p>
            <button @click="modify()">修改</button>
            <p>{{init}}</p>
            <p>{{defaultString}}</p>
        </div>
    </template>
</body>
<script>
    const cpnTemplate = {
        template: '#cpnTemplate',
        props: {
            init: {
                type: Object,
                default(){
                    return {
                        name: '默认'
                    }
                }
            },
            defaultString: {
                type: String,
                default: '默认Sring'
            }
        },
        // 验证子组件是否能直接修改父组件传递的值
        methods: { 
            modify() {
                // 如果是对象或数组可以直接修改
                this.init.name = '王' 
                // 如果是字符串直接报错
                this.defaultString = '直接修改String类型报错' 

            }
        },
    }
    
    const app = new Vue({
        el:'#app',
        data: {
            object: {
                name: '小张',
                 age: 18
            },
            defaultString: 'string'
        },
        components: {
            'cpn': cpnTemplate
        }
    });
    
    // porps的验证
    Vue.component('my-component', {
        props: {
            // 必须是数字类型
            prop1: Number,
            // 必须是字符串或者数字类型
            prop2: [String, Number],
            // 布尔,默认true
            prop3: {
                type: Boolean,
                default: true
            },
            // 数字且必传
            prop4: {
                type: Number,
                required: true
            },
            // 如果是数组或者对象,默认必须是一个函数返回
            prop5: {
                type: Object,
                default: function () {
                    return {}
                }
            },
            // 自定义一个校验函数
            prop6: {
                validator: function (value) {
                    return value > 10
                }
            }
        // 注:type类型可以为Number、String、Boolean、Object、Array、Function,Symbol
        }
    })
    
    // 总结:
    // 如果父组件向子组件传递的参数是对象或者数组,子组件可以直接修改接受的参数,并且父组件也会被修改
    // 如果传递的参数是字符串,直接修改会报错
    // 不推荐子组件直接修改父组件传递过来的参数,避免这个参数在多个子组件中使用造成不必要的麻烦
</script>

父组件访问子组件方式 refs、children

<body>
    <div id="app">
        <p>父组件</p>
        <button @click="cpnAdd()">父组件访问子组件</button>
        <cpn ref="cpn"></cpn>
        <cpn ref="cpn1"></cpn>
    </div>
    <template id="cpn">
        <div>
            <p>子组件</p>
            <p>{{num}}</p>
            <button @click="add()">add</button>
        </div>
    </template>
</body>
<script>
     // 父
    const app = new Vue({
        el: '#app',
        data: {
           
        },
        methods: {
            cpnAdd() {
                // 1.$refs 常用 默认是空对象,且指向子组件集合 {cpn: VueComponent, cpn1: VueComponent}
                this.$refs 
                this.$refs.cpn.add()

                // 2.$children 不常用 是个数组,且指向子组件集合 [VueComponent, VueComponent]
                this.$children 
                this.$children[0].num
            }
        },
        // 子
        components: { 
            cpn: {
                template: '#cpn',
                data() {
                    return {
                        num: 1
                    }
                },
                methods: {
                    add() {
                        this.num++
                    }
                },
            }
        }
    });
</script>

组件通信-子传父-this.$emit()

<body>
    <div id="app">
        <div>
            <cpn @feilei="feileiData"></cpn>
        </div>
    </div>
    <template id="cpnTemplate">
        <div>
            <ul>
                <li v-for="(item,index) in list" 
                :key="item.id" 
                @click="childrenData(item)">
                    {{item.name}}
                </li>
            </ul>
        </div>
    </template>
</body>
<script>
    // 子组件
    const cpnTemplate = {
        template: '#cpnTemplate',
        data() {
            return {
                list: [
                    {id: 1, name: '热门推荐'},
                    {id: 2, name: '人民日报'}
                ]
            }
        },
        methods: {
            childrenData(item) {
                this.$emit('feilei',item)
            }
        },
    }

    // 父组件
    const app = new Vue({
        el:'#app',
        data: {
            object: {
                name: '小张',
                 age: 18
            },
            defaultString: 'string'
        },
        components: {
            'cpn': cpnTemplate
        },
        methods: {
            feileiData(item) {
               console.log(item)
            }
        },
    });
    // 子组件通过this.$emit('envet',parameter)自定义事件,父组件监听此事件
</script>

子组件访问父组件的方式 parent 、root

<body>
    <div id="app">
        <p>父组件</p>
        <p>{{num}}</p>
        <cpn></cpn>
    </div>
    <template id="cpn">
        <div>
            <p>子组件</p>
            <button>子组件访问父组件的方式$parent</button>
            <cpn1></cpn1>
        </div>
    </template>
    <template id="cpn1">
        <div>
            <p>孙子组件cpn1</p>
            <button  @click="parentAdd">孙子组件cpn1访问父组件cpn的方式$parent</button>
        </div>
    </template>
</body>
<script>
    const app = new Vue({ // 父组件
        el: '#app',
        data: {
           num: 1
        },
        components: { // 子组件
            cpn: {
                template: '#cpn',
                data() {
                    return {
                        chenrui: '小陈'
                    }
                },
                components: { //孙组件
                    cpn1: {
                        template: '#cpn1',
                        methods: {
                            parentAdd() {
                                // 1.this.parent 子组件访问父组件的方式
                                // console.log(this.$parent)

                                // 2.this.$root // 访问根组件
                                // console.log(this.$root)
                            }
                        },
                    }
                }
            }
        }
    });
</script>

注意:
    1.尽量不要让子组件直接访问父组件的数据,这样耦合度会很高
    2.不要通过$parent直接修改父组件的数据,因为父组件的数据可能多个子组件使用,导致出现不必要问题
    3.低耦合通俗的说就是一个组件在任何复杂的项目中都何用即拿即用,高耦合相反

非父子组件通信

事件总线:

1.main.js文件中全局实例化$bus事件总线
    Vue.prototype.$bus = new Vue()
    
2.发送事件总线
    this.$bus.$emit('Event',params)
    
3.监听事件总线
    this.$bus.$on('Event',callBack)

vuex

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import {PRICE} from './mutations-type'


// 1.什么是vuex
// vuex是状态管理工具,简称仓库

// 2.什么时候使用vuex
// 当多个组件需要使用同一个状态的时候,例如登录状态、用户名称、头像

// 3.vuex的搭建
// @1.安装vuex并且使用vue.use()进行调用
// @2.创建store(xuex)对象 new Vuex.Store({})
// @3.将store(vuex)对象挂载到vue实例上

// 4.vuex的状态管理官网图理解

// 5.vuex的五个核心概念

export const store = new Vuex.Store({
    // state: 用于存储数据 this.$store.state.
    state: {  
        producttata: [
            {name: '张一',price: '200'},
            {name: '张二',price: '300'},
            {name: '张三',price: '400'},
            {name: '张四',price: '500'}
        ]
    },
    // getters用于获取数据 类似computed 
    // 获取方式:this.$store.getters. 、...mapGetters([])
    // state是默认的第一参数,getters是第二个
    getters: { 
        reductionProduct(state,getters){
            let product = state.producttata;
            let changeProduct = product.map((val,index,arr) => {
                return {
                    name: '__' +  val.name,
                    price: val.price / 2
                }
            })
            return changeProduct
        }
    },
    // 用于修改数据 (同步处理 当你触发事件修改数据的时候使用mutations)
    // this.$store.commit('事件类型',params)
    // this.$store.commit({'type',payload})
    // 使用常量来代替mutations中的事件类型 列如:[PRICE]方法
    // mutations中的方法必须是同步的,否则devtools无法检测数据的变化,不利于调试
    mutations: { 
        [PRICE](state,payload) {
            state.producttata.forEach( value => {
                value.price -= payload
            })
        }
    },
    // 类似mutations,但它是用来代替mutations进行异步操作 
    // actives里的方法可以返回Promise(return new Promise)对象,而this.$sotre.dispatch('name',payload)等于当前这个promise对象
    actions: { 
        // price(context,num) {
        //     setTimeout(() => {
        //         context.commit(PRICE,num)
        //     },1000)
        // },

        price(context,payload) {
            return  new Promise((resolve,reject) => {
                setTimeout(() => {
                    context.commit(PRICE,payload)
                    resolve()
                },1000)
            })
            .then((res) => {
                console.log('修改成功')
            })
            .catch((error) => {
                console.log('修改失败')
            })
        }
    },
    // 模块的意思
    // 因为vuex使用的是单一状态树,当我们状态特别多的时候,vuex允许我们将store分割成模块,
    // 单一状态树:也叫单一数据源,整个项目中只创建一个store,便于管理、维护以及调试
    // 每个模块都有自己的state,getters,mutations,actives,modules :例如moduleA
    // 在组件上使用的方法和上面大致相同
    modules: {
        moduleA: {
            state: {},
            getters: {},
            mutations: {},
            actions: {},
            modules: {}
        }
    }
})

vue-router参数传递(params、query)

params (传递单个参数)

// 传递
<router-link :to="/user/+id">用户</router-link>
// 在路由中配置
 { 
  path: '/user/:id',
  component: User
 }
// 获取
this.$route.params.id

query (传递多个参数)

// 方式一
<router-link :to="{
  path:'/profile',
  query:{
    name:'xiaosan',
    age:age
  }
}">跳转</router-link>

// 方式二
this.$router.push({
  path:'/profile',
  query:{
    name:'xiaosan',
    age:age 
  }
})
// 获取:
this.$route.query.name

路由懒加载

场景

通常我们会在路由中定义很多页面,打包后这些页面会放在一个js中,导致页面过大,如果一次性向服务器请求这个页面,请求时间过长,甚至用户界面会出现短暂的空白

作用

主要作用是将不同的路由对应的组件打包成一个个代码块,当哪个路由被访问就加载哪个组件

写法

const path = () => import('/path.vue')

keep-alive

作用

keep-alive是vue提供的内置组件,主要用于保持组件的状态,避免组件重新加载,有缓存能力

//使用方法一
<keep-alive exclude="componentName1,componentName2,...">
  <router-view></router-view>
</keep-alive>

两个属性:include、exclude 
    include: 哪些组件可以被缓存
    exclude:哪些组件是不被缓存的
//使用方法二
{
    path: '/category',
    name: 'category',
    component: Category,
    meta: {
      keepAlive: false // 不被缓存
    }
}

<keep-alive>
  <router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
 两个函数:
    activated() // 组件被激活时调用
    deactivated() 组件被停用时调用
    必须被keep-alive包含住的组件才可以被触发调用

router-link除了to之外的几个属性

tag:将router-link渲染成指定元素

<router-link to="/home" tag="div">首页</router-link>

repalce: 浏览器的前进后退功能不能使用

<router-link to="/home" replace>首页</router-link>

active-class:

a.修改router-link默认的class名router-link-active
        
b.例如:<router-link to="/home" active-class="active" >首页</router-link>

c.统一修改在路由实例中添加linkActiveClass: 'active'

对vue项目进行哪些优化

  • 区分v-if、v-show的使用场景
  • 区分computed和watch的使用场景
  • 使用v-for时必须加key属性
  • 使用图片懒加载
  • 使用路由懒加载
  • 使用防抖和节流
  • 减少冗余代码
  • 提取公共代码
  • 第三方插件按需加载
  • 事件的销毁
Vue 组件销毁时,会自动清理它与其它实例的连接,解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。 如果在 js 内使用 addEventListene 等方式是不会自动销毁的,我们需要在组件销毁时手动移除这些事件的监听,以免造成内存泄露

<script>
    created() {
      addEventListener('click', this.click(), false)
    },
    beforeDestroy() {
      removeEventListener('click', this.click(), false)
    }
</script>