vue的细小知识点总结

155 阅读3分钟

vue的生命周期

首先看这一系列的图

在el(vue的options中el对应的元素)这个元素内部使用了vue语法,当new Vue到把这里的内容编译好放到页面上,再修改数据页面更新,到页面的销毁,这一过程中内部调用了很多的钩子函数 从new Vue开始 => 初始化事件/生命周期函数 => 调用beforeCreate方法 => 注入数据并做好数据劫持 => 调用created方法 => 判断是否有el这个属性 (没有就去找vm.$mount(el)) => 查看有没有template这个属性(要是有就用它替换el,编译这个template到render函数,要是没有就编译el)[确定好要编译的模板] =>beforeMount =>渲染到页面完成 => mounted => (当有页面使用的数据改变时)beforeUpdate => 虚拟dom重新渲染完成 => 调用updated => 页面要销毁 beforeDestory => destoryed

综上一般获取数据可以在created中执行,操作页面一般在mounted中执行,得到重新渲染之后的页面在updated中执行,页面更新是异步的,提高了浏览器的渲染效率,有点像渲染队列一样,把同步的数据更新之后,再执行异步的渲染页面的操作这样不用一个数据变化就更新一次,而是所用同步数据更新完了之后一起渲染页面,任务队列就是把所有改变样式的放到任务队列中,直到遇到获取样式或者代码执行完毕之后进行dom的回流和重绘

v-if与v-show的区别

  • v-if 满足加载,不满足不加载,频繁切换消耗性能
  • v-show 无论满不满足都加载,初次加载消耗性能,适合频繁切换(就是修改元素的display值)

概述mvvm的实现原理

首先设定一个Vue类,构造函数传入options,私有属性el,el,data,首先进行数据劫持,vue2.0通过Object.defineProperty(obj,prop,{set(){},get(){}}),Vue3.0通过proxy代理let proxy=new Proxy(obj,{set(){},get(){}}),然后进行页面的编译,在页面编译的过程中,遇到使用数据的时候就要new一个观察者,在new的同时拿到这个观察者把他挂载在Dep.target上,然后触发这个数据的get(就是获取这个数据),在get中把这个watcher放入到这个属性的事件池中,当这个属性发生改变调用set方法,就会调用这个属性事件池的notify方法,接着执行事件池中的每一个watcher的patch方法(进行虚拟dom对比)最后完成页面的更新

computed,watch,method的区别

method性能是最不好的,因为每当页面更新都要重新执行这个方法,拿到它的结果,computed和watch都有缓存,computed中的依赖要是不改变直接拿到缓值,要是改变就从新计算,当有多个依赖时computed代码更简洁,watch要监听每一个依赖

v-model的原理

<input :value="name" @input="changeName"></input>

	new Vue({
    	data(){
        	return {
            	name:"haha"
            }
        },
        methods:{
        	changeName(e){
            	this.name=e.target.value
            }
        }
    })

组件中v-model的原理

<myInput v-model="name"></myInput>

<myInput :value="name" @input=val=>this.name=val></myInput>

子组件中,props接收value

<input :value="value" @input="input"></input>

	module.exports={
    	props:["value"],
        methods:{
        	input(e){
            	this.$emit(input,e.target.value)
            }
        }
    }

<input v-model=""></input>

	module.exports={
    	props:["value"],
        computed:{
        	newVal:{
            	get(){
                	return this.value
                }
                set(val){
                	this.$emit("input",val)
                }
            }
        }
        
    }

vue和react的区别

vue采用的是mvvm双向数据绑定而react是mvc单向数据绑定,vue中对数组是有缺陷的要么改变它的地址,要么通过pop,shift,unshift,push,reverse,sort,splice方法操作数组才可以实现视图更新,通过其他方式更改数组是没有用的 react的jsx如法更灵活扩展性更强更接近原生js

在vue2中使用Object.defineProperty但在vue3中使用Proxy代理,抛弃了defineProperty

在vue2中使用的Object.defineProperty进行数据劫持,会使代码比较麻烦,要循环对象中的每一个属性对他进行数据劫持,而vue3中使用的Proxy一次性代理了所有的属性包括深度属性值是数组或对象的属性,避免了Object.defineProperty的递归

	let obj={name:"a",age:11,newObj:{name:"ha",age:89}}
    let newObj=new Proxy(obj,{
    	get(target,key){
    		return Reflect.get(target,key)
    	},
        set(target,key,value){
            return Reflect.set(target,key,value)
    	}
    })

vue组件data为啥必须为函数

因为组件是会复用的如果使用对象就会多个组件的实例共用一个堆内存对象,造成互相干扰

对keep-alive的理解

keep-alive是缓存一个组件让他不会每次显示(第一次除外)的时候都创建,每次关闭的时候都销毁,而是把他之前的状态的虚拟dom缓存起来,等到下次显示时直接从缓存中获取

插槽的使用

插槽在组件调用的标签内部的结构会替代组件中的标签,具名插槽在标签上定义插槽的名字 在使用组件的时候,子组件的标签内部的元素节点上通过slot="xxx"来决定这段结构取代哪个 新写法是<template v-slot:xxx></template>或者<template #xxx></template>,如果想获得slot标签上的属性,就是子组件中的值,通过slot-scope="yyy" slot="xxx",通过yyy.bbb得到,新写法<template v-slot:xxx="yyy"></template>或者<template #xxx="yyy"></template>

vue组件间的通信

props+$emit

首先说父子组件间的通讯,父组件采用props向子组件传值,通过自定义事件,在子组件中$emit一下事件修改父组件中数据

children+children+parent+$refs

通过children/children/parent/$refs.xxx得到相应组件的实例,可直接拿到挂载在实例上的data,computed,methods,但是通常不提倡这么操作

attrs/attrs/listeners

attrs会得到上个组件传来的属性中props没有的那部分,可以利用这一点进行爷孙传值,爷传父,父不通过props接收,只通过自定义属性把attrs会得到上个组件传来的属性中props没有的那部分,可以利用这一点进行爷孙传值,爷传父,父不通过props接收,只通过自定义属性把attrs传递给孙子组件(v-bind="$attrs")

listeners会存储父组件传递过来的所有自定义事件方法,可通过父组件传递给孙子组件(von="listeners会存储父组件传递过来的所有自定义事件方法,可通过父组件传递给孙子组件(v-on="listeners"),在孙子组件中通过$emit调用修改爷组件的值

provide+inject

provide中的值在它的所有子组件及孙子组件...都可以使用,但他不是响应式的,通过inject:[""]属性拿到provide中的值

对vuex的理解

vuex是公共数据管理,通常有多个组件要用的值或者数据传递不方便的值都放在vuex中供各个组件使用其中的值,vuex是有一定缺点的就是每次刷新数据就要重新请求,最好把vuex与localStorage结合使用,把部分本就安全的值放在本地存储

vuex的源码大致思路

首先它的用法引入后通过Vue.use(Vuex),然后new Vuex.store(options)得到store实例挂载到vue的options中 可以确定Vuex是一个对象,有install方法和Store类,在install方法中利用Vue.mixin({})全局注入把$store这个属性注入到每一个vue组件实例上

	let Vue=ull
	function install(_vue){
    //利用全局注入在beforeCreate生命周期执行的时候为每一个vue组件添加$store
    //值为挂载在入口文件中vue实例中的store
    	if(Vue&&Vue===_vue){
    		console.error("Vue.use只能执行一次")
        	return
    	}
    	Vue=_vue
        Vue.mixin({
        	beforeCreate(){
            	if(this.$options.store){
                	this.$store=this.$options.store
                }else if(this.$parent){
                	this.$store=this.$parent.$store
                }
            }
        })
    }
    /**
    根据store实例的使用和options的传递来确定Store的私有属性和prototype上的方法,options中有state对象,使用时直接store实例.state.xxx所以所以为实例添加state属性this.state=options.state,传入的mutations对象每个属性都是方法,调用commit传入方法名和option就可以执行传入的options中对应的方法,这里不直接this.mutations=options.mutations,而是遍历mutations的所有属性,赋值给this.mutations,把值外层套一个函数,内部通过call执行原来的函数,并传递两个实参
    */
    class Store{
    	constructor(options){
        //为了实现vuex中的state的双向数据绑定
        	let vm=new Vue({
            	data:{
                	state:options.state
                }
            })
        	this.state=vm.state
            this.mutations={}
            this.actions={}
            this.getters={}
            Object.keys(options.mutations).forEach(key=>{
            	this.mutations[key]=(option)=>{
                	options.mutations[key].call(this,this.state,option)
               	}
            })
            Object.keys(options.actions).forEach(key=>{
            	this.actions[key]=(option)=>{
                	options.actions[key].call(this,this,option)
               	}
            })
            Object.keys(options.getters).forEach(key=>{
            	this.getters[key]=options.getters[key](this.state)
            })
        },
        commit(type,option){
        	this.mutations[type](option)
        },
        dispatch(type,option){
        	this.actions[type](option)
        }
    }
    export function mapState(options){
    	let computedState={}
        if(Array.isArray(options)){
        	options.forEach(key=>{
            	computedState[key]=function(){
                	return this.$store.state[key]
                }
            })
        }else if(Object.prototype.toString.call(options)==="[object Object]"){
        	for(let key in options){
            	computedState[key]=function(){
                	return this.$store.state[options[key]]
                }
            }
        }
        
        return computedState
    }
    export function mapGetters(options){
    	let computedGetters={}
        if(Array.isArray(options)){
        	options.forEach(key=>{
            	computedGetters[key]=function(){
                	return this.$store.getters[key]
                }
            })
        }else if(Object.prototype.toString.call(options)==="[object Object]"){
        	for(let key in options){
            	computedGetters[key]=function(){
                	return this.$store.getters[options[key]]
                }
            }
        }
        
        return computedState
    }
    export function mapMutations(options){
    	let methodMutations={}
        options.forEach(key=>{
        	methodMutations[key]=function(option){
            	this.$store.commit(key,option)
            }
        })
    }
    export function mapActions(options){
    	let methodActions={}
        options.forEach(key=>{
        	methodActions[key]=function(option){
            	this.$store.dispatch(key,option)
            }
        })
    }

vue-router

vue-router的使用:npm安装后,引入一个文件中通过Vue.use(VueRouter),然后new VueRouter(options)得到的router挂载到vm的options上,options中的mode和routes属性,mode为hash和history模式,使用history要注意与后台的配合 routes(每一个对象中属性:path,component,meta,children,name)是一个path与component的对应关系,vue-router为全局提供了router-link和router-view两个组件,在使用router-link时要传入to这个属性,tag是显示的标签(默认是a),to的属性值可以为字符串(指向一个path),对象({path:'',query:{}}/{name:'',params:{}}),router-view是组件显示的地方可以与keep-alive结合使用,导航守卫是router实例调用,最常见的就是beforeEach,在每次路由改变页面跳转之前的时候调用,如果不执行next就不会跳转,next()继续跳转,next('./...')跳转到这个路由,可以通过这个钩子函数实现title的设定 还可以实现未登录情况下向登录页面的跳转

	router.beforeEach((to,from,next)=>{
    	//
    })

vue-router源码

	let Vue=null
	class VueRouter{
    	constructor(options){
        	this.routes=options.routes
            this.mapRouter={}
            this.routes.forEach(item=>{
            	this.mapRouter[item.path]=item.component
            })
            this._router=location.hash.match(/#([^?]+)/)[1]
            window.addEventListener("hashchange",()=>{
            	this._router=location.hash.match(/#([^?]+)/)[1]
            })
            window.addEventListener("load",()=>{
            	let hash=location.hash||"#/"
            	this._router=hash.match(/#([^?]+)/)[1]
            })
            Vue.util.defineReactive(this,_router,"./")
        }
    }
    //install的时候通过全局注入为每个vue组件实例添加beforeCreate钩子函数,把router实例挂载到$router上
    //还要定义全局组件
    VueRouter.install=function(_vue){
    	if(Vue&&Vue===_vue){
    		console.erroe("不可多次install")
   	 	}
    	Vue=_vue
        Vue.mixin({
        	beforeCreate(){
            	if(this.$options.router){
                	this.$router=this.$options.router
                }else if(this.$parent){
                	this.$router=this.$parent.$router
                }
            }
        })
        Vue.component("router-link",{
        	props:{
            	to:{
                	type:[String,Object],
                    required:true
                }
            },
        	render(h){
            	return h("a",{
                	attrs:{
                    	href:"#"+this.to
                    },
                },this.$slots.default)
            }
        })
        Vue.component("router-view",{
        	render(h){
            	return h(this.$router.mapRouter[this.$router._router])
            }
        })
    }

总之就是install的时候要通过全局注入+beforeCreate实现每个组件中的$router的挂载,全局定义组件router-link&& router-view,在定义router-view的时候要实时获取当前的url的hash值,所以要hashchange事件监听改变this._router 在页面加载完成后不会触发haschange所以监听load事件得到this._router,但是这样在this._router改变时页面不会重新渲染就要用到Vue.util.defineReactive(this,_router,"./")实现数据更新的同时页面也更新