vue面试题目整理---自我的整理学习笔记(已完)


vue基本应用


一、vue的生命周期与值处理


1、vue的生命周期

        beforeCreat create

        beforeMount  mounted

        beforeUpdata   updated

        beforeDestroy   destroyed

2.插入值、表达式

<p>文本差值{{message}}</p>
<p>JS表达式{{flag?'yes':'no'}}(只能是表达式,不能是js语句)</p>

3.指令、动态属性

<p :id="dataVal">动态属性id</p>


data() {
    return {
        dataVal:`id-$Data.new()`
    }
}

4、v-html:会有xss风险,会覆盖子组件

<p v-html="rawHTML">
    <span>[注意] 使用 v-html 之后,会将覆盖子元素</span>
</p>


data() {
    return{
        rawHTML:'指令 - 原始 html <b>加粗</b> <i>斜体</i>',
    }
}

二、computed和watch


1、computed基础应用

<div id="example">
    <p>{{ message }}</p>
    <p>{{ reversedMessage }}</p>
</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: 'Hello'
  },
  computed: {
    // 计算属性的 getter
    reversedMessage: function () {
      // `this` 指向 vm 实例
      return this.message.split('').reverse().join('')
    }
  }
})

2.watch的基础应用

<div id="demo">{{ fullName }}</div>
var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
    firstName: function (val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName: function (val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
})

3、computed有缓存,data不变则不会重新计算

<div>
    <p>num {{num}}</p>
    <p>double1 {{double1}}</p>
    <input v-model="double2" />
</div>
export default {
		data() {
			return {
				num:20
			}
		},
		computed:{
			double1(){
				return this.num * 2
			},
			double2:{
				get(){
					return this.num * 3
				},
				set(val){
					this.num = val / 2
				}
			}
		}
}

2、watch如何深度监听,watch监听引用类型,拿不到oldVal

<input v-model="name" />
<input v-model="info.city" />
export default {
		data() {
			return {
				name: '双越',
				info: {
					city: '北京'
				},
				num:'37'
			}
		},
		watch: {
			name(oldVal, val) {
				console.log('watch name', oldVal, val); //值类型可以正常拿到oldVal和val
			},
			info: {
				handler(oldVal, val) {
					console.log('oldVal', oldVal, 'val', val); //引用类型,拿不到oldVal,因为指针相同,此时已经指向了新的val
				},
				deep: true // 深度监听
			}
		},
}

注意:

1.基本类型介绍

       Undefined、Null、Boolean、Number、String

2.引用类型介绍

        一类对象所具有的属性和方法

Object类型,Array类型,Date类型,RegExp类型(正则),function类型。还有基本包装类型,也是一种引用类型,ECMAScript还提供了 3个特殊的引用类型:Boolean、Number和String。

三、class和style


1、使用动态属性,使用驼峰式写法

<div>
		<topNavi></topNavi>
		<view :class="{black:isBlack,yellow:isYellow}">使用class</view>
		<view :class="[black,yellow]">使用class</view>
		<view :style="styleData">使用class</view>
</div>
export default {
		data() {
			return {
				isBlack:true,
				isYellow:true,
				
				black:'black',
				yellow:'yellow',
				
				styleData:{
					fontSize:'40px',//转换为驼峰式写法
					color:'red',
					backgroundColor:'#ccc',//转换为驼峰式写法
				}
			}
		},
}
<style scoped>
	.black{
		color: #0000FF;
	}
	
	.yellow{
		background: yellow;
	}
</style>

四、条件渲染


1、v-if v-else 的用法,可使用变量,也可以使用===表达式

2、v-if和v-show的区别

        v-if是直接渲染,另外一个直接不渲染

        v-show是渲染两个,另外一个style是display:none;

3、v-if和v-show的使用场景

        不频繁切换渲染用v-if,频繁切换渲染用v-show

<template>
	<div>
		<topNavi></topNavi>
		<view v-if="type==='a'">A</view>
		<view v-else-if="type==='b'">B</view>
		<view v-else>other</view>
		
		<view v-show="type==='a'">A</view>
		<view v-show="type==='b'">B</view>
	</div>
</template>
data() {
		return {
			type:'a',
		}
	},

五、循环(列表)渲染


1、如何遍历对象?---也可以用v-for

<template>
	<div>
		<topNavi></topNavi>
		<view>数组的遍历</view>
		<view>
			<view v-for="(item,index) in listArr" :key="item.id">
				{{index}} {{item.id}} {{item.title}}
			</view>
		</view>

		<view>对象的遍历</view>
		<view>
			<view v-for="(val,key,index) in listObj" :key="key">
				{{index}} {{key}} {{val.title}}
			</view>
		</view>
	</div>
</template>
<script>
	import topNavi from '../components/topNavi.vue';
	export default {
		data() {
			return {
				listArr: [{
					id: 'a',
					title: '标题1'
				}, {
					id: 'b',
					title: '标题2'
				}, {
					id: 'c',
					title: '标题3'
				}],

				listObj: {
					a: {
						title: '标题1'
					},
					b: {
						title: '标题2'
					},
					c: {
						title: '标题3'
					}
				}
			}
		},
		components: {
			topNavi
		},
		methods: {}
	}
</script>

2、key的重要性、key不能乱写(如random或者index)

        注意:key是dom元素的唯一标识,每一个页面的key必须要唯一。因为如果key不唯一,渲染页面变化时页面会出现错误操作。

3、v-for和v-if不能一起用!

        注意:首先vue的规定是不能这么写,其次是因为v-for计算级别是高于v-if的,如果写在一起会每一个v-for的计算后都会去计算一遍v-if这样会降低浏览器性能。可以将v-if写在v-for外层或者内层里面。

六、事件 


<template>
	<div>
		<topNavi></topNavi>
		<view>{{number}}</view>
		<button @click="increment1">+1</button>
		<button @click="increment2(2,$event)">+1</button>
	</div>
</template>
<script>
	import topNavi from '../components/topNavi.vue';
	export default {
		data() {
			return {
				number:0
			}
		},
		components: {
			topNavi
		},
		methods: {
			increment1(event){
				console.log('event',event);
				console.log('event',event.target);
				console.log('event',event.currentTarget);
			}
		}
	}
</script>

1、enent参数,自定义参数

onChange(val,event){
    //val:自定义参数
    //enent参数
}

        注意:event是原生的 

2、事件修饰符,按键修饰符

        事件修饰符

<!--阻止单击事件继续传播-->
<a v-on:click.stop="doTish"></a>

        按键修饰符

<!--按下ctrl触发-->
<button @click.ctrl="onClick">A</button>

3、【观察】事件绑定到了那里

        注意:vue的事件我把它放在什么元素下,它就会挂载在什么元素下

七、表单


1、v-model

<template>
	<div>
		<topNavi></topNavi>
		<!-- 去掉输入框前后的空格 -->
		<input type="text" v-model.trim="name" />
		<!-- 输入完成后才会变化 -->
		<input type="text" v-model.lazy="name" />
		<!-- 转化成数字 -->
		<input type="text" v-model.number="age" placeholder="转化成数字" />
	</div>
</template>

2、常见表单项textarea、checkbox、radio、select

<template>
	<div>
		<div>多行文本{{desc}}</div>
		<textarea v-model="desc"></textarea>
		<!-- 注意<textarea>{{desc}}</textarea>是不允许的 -->
		
		<div>复选框{{checked}}</div>
		<input type="checkbox" v-model="checked" />
		
		<div>多个复选框{{checkNames}}</div>
		<input type="checkbox" id="jack" value="jsck" v-model="checkNames" />
		<label for="jack">jack</label>
		<input type="checkbox" id="john" value="john" v-model="checkNames" />
		<label for="john">john</label>
		<input type="checkbox" id="mike" value="mike" v-model="checkNames" />
		<label for="mike">mike</label>
		
		<div>单选{{gender}}</div>
		<input type="radio" id="male" value="male" v-model="gender" />
		<label for="male">male</label>
		<input type="radio" id="female" value="female" v-model="gender" />
		<label for="female">female</label>
		
		<div>下拉列表{{selected}}</div>
		<select v-model="selected">
			<option disabled value="">请选择</option>
			<option>A</option>
			<option>B</option>
			<option>C</option>
		</select>
		
		<div>下拉列表多选{{selectedList}}</div>
		<select v-model="selectedList" multiple>
			<option disabled value="">请选择</option>
			<option>A</option>
			<option>B</option>
			<option>C</option>
		</select>
	</div>
</template>
export default {
	data() {
		return {
			desc:'',
			checked:'',
			checkNames:[],
			gender:'',
			selected:'',
			selectedList:[],
		}
	},
	mounted() {
	},
	methods: {
	}
}

3、修饰符lazy、number、trim

八、Vue组件使用


1、props和$emit

        props接收父组件的传值,$emit子组件调用父组件的方法且传值。

props应用的两种写法

//1、简单直接申明
props:['list'],

//2、标记处传入值的类型
props: {
	list: {
		type: Array,
		default () {
			return []
		}
	}
},

 注意:props传入的值是不能直接进行修改,如果需要修改,可以赋值后再进行修改

2、组件间通讯

        父子间传值通过(属性名)传值,父组件接收子组件传值通过方法的触发。@子组件定义的方法名称。

父组件

<template>
	<div>
		<!-- 应用子组件 -->
		<cc :name="name" @changeName="onChange"></cc>
	</div>
</template>

<script>
	import cc from './components/cc.vue';//引入子组件
	
	export default {
		data() {
			return {
				name:'简隋英'
			}
		},
		components:{
			cc//申明子组件
		},
		methods: {
			onChange(val){
				this.name = val;
			}
		}
	}
</script>

<style>
</style>

 子组件

<template>
	<div>
		{{name}}
		<button @click="onChange">改变名称!</button>
	</div>
</template>

<script>
	export default {
		data() {
			return {

			}
		},
		props:['name'],
		mounted() {},
		methods: {
			onChange(){
				this.$emit('changeName','李玉')
			}
		}
	}
</script>

<style>
</style>

3、自定义事件 event

        注意:vue本身就具有自定义事件的能力。

vue实例  event.js

import Vue from 'vue'
export default new Vue

 父组件 C.vue

<template>
	<div>
		<!-- 应用子组件 -->
		<cc></cc>
		<bb></bb>
	</div>
</template>

<script>
	import cc from './components/cc.vue';//引入子组件
	import bb from './components/bb.vue';//引入子组件
	
	export default {
		data() {
			return {
				name:'简隋英'
			}
		},
		components:{
			cc,//申明子组件
			bb
		},
		methods: {
			onChange(val){
				this.name = val;
			}
		}
	}
</script>

<style>
</style>

 子组件bb.vue

        注意event自定义事件一定要在beforeDestroy()及时销毁,否者可能会造成内存泄露

<template>
	<div>
		bb
	</div>
</template>

<script>
	import event from '../event.js';
	export default {
		data() {
			return {

			}
		},
		mounted() {
			event.$on('onAddTitle',this.addTitle)
		},
		methods: {
			addTitle(title){
				console.log('打印传值',title);
			}
		},
		beforeDestroy() {
			//及时销毁,否者可能会造成内存泄露
			event.$off('onAddTitle',this.addTitle)
		}
	}
</script>

<style>
</style>

 子组件cc.vue

<template>
	<div>
		cc
		<button @click="changeBB">改变bb的值</button>
	</div>
</template>

<script>
	import event from '../event.js';
	export default {
		data() {
			return {
				title:'cc',
			}
		},
		methods: {
			changeBB(){
				event.$emit('onAddTitle',this.title);
			}
		}
	}
</script>

<style>
</style>

4、组件生命周期

        1、create和mounted有什么区别?

        create是初始化了vue的实例,现在并没有开始渲染。mounted是页面在网页中真正绘制完成了。大部分时候我们都应该在mounted里面去进行操作。如:ajax或是绑定事件什么的。

        2、beforeDestroy里面进行的操作

        解除绑定,销毁子组件以及事件监听,定时任务settimeout需要销毁。

5、生命周期(父子组件)

        父组件 :created          父组件:beforeUpdate

        子组件 :created          子组件:beforeUpdate

        子组件 :mounted        子组件:updated

        父组件 :mounted         父组件:updated


vue的高级特性


一、自定义v-model


 父组件

<template>
  <div class="hello">
	  <!--自定义v-model-->
	  <p>{{name}}</p>
	  <customVmodel v-model="name"></customVmodel>
  </div>
</template>

<script>
import customVmodel from './customVmodel.vue'
export default {
	data(){
		return {
			name:''
		}
	},
	components:{
		customVmodel
	}
}
</script>

<style scoped>
</style>

子组件

<template>
	<div>
		<input type="text" :value="text" @input="$emit('change',$event.target.value)" />
	</div>
</template>

<script>
	export default {
		model:{
			prop:'text',
			event:'change'
		},
		props:{
			text:String,
			default(){
				return ''
			}
		}
	}
</script>

<style>
</style>

 注意:1、上面input使用了 :value 而不是v-model

            2、上面的change和model.event要对应起来

            3、text属性要对应起来

二、$nextTick


        1、Vue是异步渲染

        2、data改变之后,Dom不会立刻更新

        3、$nextTick会在Dom渲染之后被触发,以获取最新Dom节点

<template>
  <div>
	  <div ref="ul1">
		  <div v-for="(item,index) in list">{{item}}</div>
	  </div>
	  <button @click="add">添加Dom</button>
  </div>
</template>

<script>
export default {
	data(){
		return {
			list:['a','b','c'],
		}
	},
	methods:{
		add(){
			this.list.push('简隋英');
			this.list.push('李玉');
			
			//异步渲染
			this.$nextTick(()=>{
				const ulElem = this.$refs.ul1;
				console.log(ulElem.childNodes.length);
			})
		}
	}
}
</script>

<style scoped>
</style>

注意:1、vue是异步渲染,$nextTick 等待Dom渲染完再回调

           2、页面渲染时会将data的修改做整合,多次data修改只会渲染一次。 

三、slot插槽(父组件往子组件里插入内容)


1、基本使用

父组件

<template>
  <div>
	  <SlotDemo :url="website.url">
		  {{website.title}}
	  </SlotDemo>
  </div>
</template>

<script>
import SlotDemo from './SlotDemo.vue';
export default {
	data(){
		return {
			website:{
				url:'https://www.baidu.com',
				title:'百度'
			}
		}
	},
	components:{
		SlotDemo
	}
}
</script>

<style scoped>
</style>

 子组件

<template>
	<div>
		<a :href="url">
			<slot>
				默认值
			</slot>
		</a>
	</div>
</template>

<script>
	export default {
		data(){
			return {
				
			}
		},
		props:['url']
		
	}
</script>

<style scoped>
</style>

 2、作用域插槽

父组件

<template>
	<div>
		<ScopedSlotDemo :url="website.url">
			<template v-slot="slotProps">
				{{slotProps.slotData.subTitle}}
			</template>
		</ScopedSlotDemo>
	</div>
</template>

<script>
	import ScopedSlotDemo from './ScopedSlotDemo.vue';
	export default {
		data() {
			return {
				website: {
					url: 'https://www.baidu.com',
					title: '百度'
				}
			}
		},
		components: {
			ScopedSlotDemo
		}
	}
</script>

<style scoped>
</style>

  子组件

<template>
	<div>
		<a :href="url">
			<slot :slotData="website">
				<!-- {{website.subTitle}} -->
			</slot>
		</a>
	</div>
</template>

<script>
	export default {
		data(){
			return {
				website:{
					url:'https://www.qq.com/',
					title:'腾讯',
					subTitle:'腾讯新闻'
				}
			}
		},
		props:['url']
	}
</script>

<style>
</style>

注意:作用域插槽父组件访问子组件数据时,是需要用template里的 v-slot访问子组件(动态属性:)映射出来的数据。

 3、具名插槽

父组件

<template>
	<div>
		<NameSlot>
			<template v-slot:header>
				<h1>将插入header slot 中</h1>
			</template>
			<p>将插入main slot 中</p>
			<template #footer>
				<p>将插入footer slot 中</p>
			</template>
		</NameSlot>
	</div>
</template>

<script>
	import NameSlot from './NameSlot.vue';
	export default {
		data() {
			return {
				website: {
					url: 'https://www.baidu.com',
					title: '百度'
				}
			}
		},
		components: {
			NameSlot
		}
	}
</script>

<style scoped>
</style>

  子组件

<template>
	<div>
		<header>
			<slot name="header"></slot>
		</header>
		<main>
			<slot></slot>
		</main>
		<footer>
			<slot name="footer"></slot>
		</footer>
	</div>
</template>

<script>
	export default {
		data() {
			return {}
		}
	}
</script>

<style>
</style>

 注意:父组件template的v-slot:接,子组件slot的动态属性name将两个对应。如果没有动态属性name将自动匹配父组件插值

四、动态组件


1、:is="comonpent-name" 用法

<template>
	<div>
		<div v-for="(val,key) in newData" :key="key">
			<component :is="val.key"></component>
		</div>
	</div>
</template>

<script>
	export default {
		data(){
			return {
				newData:{
					1:{
						key:'text'
					},
					2:{
						key:'text'
					},
					3:{
						key:'image'
					}
				}
			}
		}
	}
</script>

<style>
</style>

2、需要根据数据,动态渲染的场景。即组件类型不确定

五、异步组件


1、import()函数(按需加载,异步加载大组件)

//同步引入组件
import componentName from './componentName';

export default {
	components:{
		componentName,//同步引入组件申明
		componentName:() => import('./componentName'),//异步引入组件
		componentName:() => {
			return import('./componentName')//异步引入组件另一种写法
		}
	}
}

六、keep-alive


1、缓存组件

2、频繁切换,不需要重复渲染

3、Vue常见的性能优化

<template>
	<div>
		<button @click="changeBtl('A')">A</button>
		<button @click="changeBtl('B')">B</button>
		<button @click="changeBtl('C')">C</button>

		<keep-alive><!--v-show-->
			<AA v-if="showVal=='A'"></AA>
			<AA v-if="showVal=='B'"></AA>
			<AA v-if="showVal=='C'"></AA>
		</keep-alive>
	</div>
</template>

<script>
	import AA from './AA.vue';
	import BB from './BB.vue';
	import CC from './CC.vue';
	export default {
		data() {
			return {
				showVal:null,
			}
		},
		components: {
			AA,
			BB,
			CC
		}
	}
</script>

<style scoped>
</style>

 注意:keep-alive和vue的v-show看起来相似。但是v-show的实现方式是根据css的disply:none来实现。而keep-alive的实现方式是在vue框架层级进行的js对象的渲染。v-show相对于keep-alive而言简单粗暴了一些。

七、mixin


1、多个组件有相同逻辑,抽离出来

2、mixin并不是完美的解决方案,会有一些问题

3、Vue3提出的Composition API旨在解决这些问题

引入组件

<template>
	<div>
		<p>{{name}}{{city}}</p>
	</div>
</template>

<script>
	import myMixin from './myMixin.js';
	export default {
		mixins:[myMixin],
		data(){
			return {
				name:'简隋英'
			}
		},
		mounted() {
			console.log('mounted',this.name);
		}
	}
</script>

<style scoped>
</style>

 mixin公共js

export default {
	data() {
		return {
			city: '北京'
		}
	},
	mounted() {
		console.log('mounted', this.city);
	}
}

 注意mixin的问题:

        1、变量来源不明,不利于阅读。

        2、多mixin可能会造成命名冲突。

        3、mixin和组件可能出现多对多的关系,复杂度高。


vuex


将vuex映射到全局

import Vue from 'vue'
import App from './App.vue'
//引入store文件
import store from './store/index.js'

Vue.config.productionTip = false

new Vue({
	store,
	render: h => h(App),
}).$mount('#app')

一、state单一状态树


import Vue from 'vue'
import Vuex from 'vuex'

//挂载Vuex
Vue.use(Vuex)
const store = new Vuex.Store({
	state: {
		name: '简隋英',
		count: 1
	}
})

export default store
//导出  默认值  

1.1、state基本使用与简介

 在组件中访问state单一状态树内的值this.$store.state访问

例如:访问state里的name值

        this.$store.state.name

1.2、mapState辅助函数

使用辅助函数mapState需要先将它导入

import { mapState } from 'vuex'
  • 基本使用方式
<template>
	<div>
		{{count}}{{countNum}}{{mergeVal}}
	</div>
</template>

<script>
	import { mapState } from 'vuex'
	export default {
		data() {
			return {
				countryName:'中国'
			}
		},
		computed:mapState({
			//箭头函数可使代码更简洁
			count: state => state.count,
			
			//传字符串参数‘count’等同于state => state.count
			countNum:'count',
			
			//为了能够使用‘this’获取局部状态,必须使用常规函数
			mergeVal(state){
				return state.count + this.countryName
			}
		})
	}
</script>

<style scoped>
</style>
  • 合并组件中原本的计算属性computed
import { mapState } from 'vuex'
export default {

	//原本的computed计算属性
	computed:{
		computedName1(){ return ...},
		computedName2(){ return ...},
	},
	
	//引入mapState后合并的计算属性
	computed:mapState({
		//将原本的computed计算属性复制过来
		computedName1(){ return ...},
		computedName2(){ return ...},
		
		//再维护vuex
		count: state => state.count,
		countNum:'count',
		mergeVal(state){
			return state.count + this.countryName
		}
	})
}
  •  另外的一种合并原始computed和mapState的方法 es6的  ...mapState({ })
import { mapState } from 'vuex'
export default {

	//原本的computed计算属性
	computed:{
		computedName1(){ return ...},
		computedName2(){ return ...},
	},
	
	//引入mapState后合并的计算属性
	computed:{
		computedName1(){ return ...},
		computedName2(){ return ...},
		
		//维护vuex
		...mapState({
			count: state => state.count,
			countNum:'count',
			mergeVal(state){
				return state.count + this.countryName
			}
		})
	},
}

二、getter


import Vue from 'vue'
import Vuex from 'vuex'

//挂载Vuex
Vue.use(Vuex)

const store = new Vuex.Store({
	state: {
		name: '简隋英',
	},
	getters: {
		nameInfo(state) {
			return "姓名:" + state.name
		}
	},
})

export default store
//导出  默认值  

2.1、getter基本使用与简介

getter类似于vue组件内的computed计算属性,它对state单一转态树内的数据进行计算,且具有缓存计算结果的功能 

上面代码中用this.$store.state.name访问到的结果是。姓名:简隋英 

2.2、mapGetters

使用辅助函数mapGetters前需要先将其引入

import { mapGetters } from 'vuex'
  • 辅助函数的普通使用 
<template>
	<div>
		<div>{{value1}}</div>
		<div>{{value2}}</div>
	</div>
</template>

<script>
	import { mapGetters } from 'vuex'
	export default {
		computed: mapGetters({
			value1: 'data1', //data1为是state中的值名称
			value2: 'data2' //同上
		})
	}
</script>

<style>
</style>

另一种引入方式 es6... 

<template>
	<div>
		<div>{{value1}}</div>
		<div>{{value2}}</div>
	</div>
</template>

<script>
	import { mapGetters } from 'vuex'
	export default {
		computed: {
			...mapGetters({
				value1: 'data1', //data1为是state中的值名称
				value2: 'data2' //同上
			})
		}
	}
</script>

<style>
</style>

三、mutation

import Vue from 'vue'
import Vuex from 'vuex'

//挂载Vuex
Vue.use(Vuex)

const store = new Vuex.Store({
	state: {
        count: 1
    },
    mutations: {
        countAdd(state){
           // 变更状态
           state.count++
        },
        countAddVal(state,val){
            state.count = state.count + val
        }
    }
})

export default store
//导出  默认值  

3.1、 mutation基本使用与简介

  • 更改store中state内数据的状态是提交mutation,vuex中的mutation非常类似于事件methods
  • 注意:mutation里的只能做同步操作
  • 在vue组件中调用mutations中的方法需要通过store.commit 方法

例如:调用上代码中的countAdd方法

        this.$store.commit("countAdd")

例如:调用上代码中的countAddVal方法,且进行传值

        this.$store.commit("countAddVal" , 7 )

3.2、mapMutations

首先想要使用mapMutations辅助函数需要先将其引入,可将组件中的methods映射为store.commit调用,需要在根节点注入store

import { mapMutations } from 'vuex'

组件内引入mapMutations有两种写法

...mapMutations
  • 数组方式引入[]
import { mapMutations } from 'vuex'
export default {
	methods:{
		...mapMutations([
			"increment"//注意:必须写双引号
		]),
		go(){
			this.increment('李玉')
			//上面的写法和下面的写法可以做相同操作
			// this.$store.commit('increment','李玉')
		}
	}
}
  •  对象引入{}
import { mapMutations } from 'vuex'
export default {
	methods:{
		...mapMutations({
			onChangeName:"increment"
		}),
		go(){
			this.onChangeName('李玉')
			//上面的写法和下面的写法可以做相同操作
			// this.$store.commit('increment','李玉')
		}
	}
}

四、Action


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

Vue.use(Vuex)

const store = new Vuex.Store({
	state: {
		valData: null
	},
	mutations: {
		changeValData(state, stark) {
			state.valData = stark
		},
	},
	actions: {
		changeAsyn({ commit },val){
			setTimeout(() => {
			    commit('changeValData',val)
			}, 1000)
		}
	}
})

export default store

 4.1、action的基本使用与简介

  • action类似于mutation但是又有所不同,不同在于:
  • action提交的是mutation,而不是直接去操作state状态树内的值。
  • atcion里可以提交包含任何异步操作
  • action通过store.dispatch方法触发

例如:触发以上代码中的changeAsyn函数

        this.$store.dispatch("changeAsyn",'简隋英')

4.2、actions同样支持载荷方式和对象方式进行分发

// 以载荷形式分发
store.dispatch('incrementAsync', {
  amount: 10
})

// 以对象形式分发
store.dispatch({
  type: 'incrementAsync',
  amount: 10
})

4.3、mapActions

 首先想要使用mapActions辅助函数需要先将其引入,可将组件中的methods映射为store.dispatch调用,需要在根节点注入store

import { mapActions } from 'vuex'

import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

      // `mapActions` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
    })
  }
}

 4.4、组合actions

Action 通常是异步的,那么如何知道 action 什么时候结束呢?更重要的是,我们如何才能组合多个 action,以处理更加复杂的异步流程?

首先,你需要明白 store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise:

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}

现在你可以: 

store.dispatch('actionA').then(() => {
  // ...
})

 在另外一个 action 中也可以:

actions: {
  // ...
  actionB ({ dispatch, commit }) {
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')
    })
  }
}

vue-router 


一、vue-router基础


 1.1、vue3加入创建一个router

  • 在src目录下创建一个router的文件,且在内新建一个index.js文件
  • 在main.js文件中引入index.js文件并使用他
import router from './router/index'

createApp(App).use(router).mount('#app')
  • router文件内index.js文件内容 
import {createRouter, createWebHistory} from 'vue-router'
import HelloWorld from '@/components/HelloWorld'

const routes = [
  { path: '/', component: HelloWorld }
]

const router = createRouter({
    history: createWebHistory(), 
    routes
})
 
export default router
  •  我们可以在任意组件中以 this.$router 的形式访问它,并且以 this.$route 的形式访问当前路由:
//页面跳转
this.$router.push('/a')

//访问路由内容
this.$route

1.2、路由传值方式有三种

 第一种:动态参数传值

配置: 

传值:

接收:

第二种 :params和name

传值: 

接收:

  

第三种:path和query

传值:

接收:

 1.2、路由获取参数的两种常见方式params和query

由于动态路由也是传递params的,所以在this.$router.push()方法中path不能和params一起使用,否者params将无效,此时需要用name来指定页面

 在目标页面通过this.$route.params获取参数:

this.$router.push({name:"menuLink",params:{id:123}})
this.$router.push('/User/123')

//接收
<p>提示:{{this.$route.params.id}}</p>

在目标页面通过this.$route.query 获取参数 

//传值
this.$router.push({path:"/menLink",query:{alert:"页面跳转成功"}})

//用query获取值
<p>提示:{{this.$route.query.alert}}</p>

二、路由模式(hash 和 H5 history)


2.1、hash模式

hash模式使用 createWebHashHistory() 创建的

import { createRouter , createWebHashHistory } from 'vue-router'

const router = createRouter({
    history: createWebHashHistory(),
    routes: [
        ...
    ]
})

它在内部传递的实际URL之前使用了一个哈希字符(#),由于这部分URL从未被发送到服务器,所以它不需要在服务器层面上进行任何特殊处理。

例如:http://abc.com/#/user/10

2.2、H5 history 模式

H5 history 模式用 createWebHistory() 创建的

import { createRouter, createWbeHistory } from 'vue-router'

const router = createRouter({
    history: createWebHistory(),
    routes: [
        ...
    ]
})

 使用这种模式时URL的显示

例如:https://abc.com/user/10

当访问到单个页面的客户端应用,如果没有适当的服务器配置,用户会得到一个404的错误返回。

此时解决这个问题需要在后端的服务器上添加一个回退路由。如果URL不匹配任何静态路由资源,他应当提供应用程序中index.html相同的页面。

服务器配置去vue-router官网看吧!

不同的历史模式 | Vue RouterVue.js 的官方路由https://router.vuejs.org/zh/guide/essentials/history-mode.html#html5-%E6%A8%A1%E5%BC%8F不过当想使用H5 history模式有需要显示不同的404好看的显示可以在前端做一个拦截。

const router = createRouter({
    history: createWebHistory(),
    routes: [
        {path:'/:pathMatch(.*)', component: NotFoundComponent}
    ]
})

 例如:腾讯qq.com

三、路由配置(动态路由、懒加载)


3.1、动态路由

 路由参数用表示。当一个路由被匹配时,它的params的值将在每个组件中以this.$route.params的形式暴露出来,因此我们可以通过更新User模板来呈现当前用户ID

const User = {
    template: '<div>User {{ $route.params.id }}</div>'
}

const router = [
    //动态字段以冒号开始
    { path: '/users/:id', component: User},
]

3.2、懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效。

Vue Router 支持开箱即用的动态导入,这意味着你可以用动态导入代替静态导入:

const User = () => import(./view/User)

const router = createRouter({
    routes: [
        path: '/users/:id', component: User
    ]
})


vue原理


一、组件化基础


  • 组件化

 “很久以前”就有组件化了,asp jsp php 就已经有组件化了,nodejs中也有类似的组件化。

传统组件,只是静态渲染,更新还要依赖于操作DOM(如:jQuery就是当时流行的操作DOM的工具)

  • 数据驱动视图(MVVM:vue的 和 setState:React的)

这是在组件化上面进行了一个微创新,叫数据驱动视图

 数据驱动视图是我们渲染页面时,不需要自己去操作DOM了。而是通过去更改数据,然后修改数据通过viewModel自己从新去修改DOM。

  • MVVM

 <template>标签内的东西                                                       vue里面的data () { return {} }

M:对应Model,data

V:View ,template

VM:ViewModel,vue的后台更新Dom块的操作

二、vue响应式原理


  • 组件data的数据一旦变化,立刻触发视图的更新
  • 实现数据驱动视图的第一步
  • 考察vue原理的第一道题

2.0、核心API - Object.defineProperty 基础写法

const obj = {}
let name = '楚晚宁'
Object.defineProperty(obj, 'url', {//obj.url=
	get() {
		console.log('第一个',name);
		return name
	},
	set(newVal) {
		console.log('可爱',newVal);
		name = newVal
	},
	configurable: true, // 可删除
	enumerable: true // 可枚举(遍历)
})
console.log(obj.url);
obj.url = '墨燃'
console.log('最后一个',name);
//第一个 楚晚宁
//楚晚宁
//可爱 墨燃
//最后一个 墨燃

2.1、核心API - Object.defineProperty 

//准备数据
const data = {
	name:'zhangsan',
	age:20
}

observer(data)

function observer(target) {
	if(typeof target !== 'object' ||  target ===null){
		return target
	}
	for(let key in target){
		defineReactive(target,key,target[key])
	}
}

//重新定义属性监听起来
function defineReactive(target,key,value){
	//核心API
	Object.defineProperty(target,key,{
		get(){
			return name
		},
		set(newValue){
			if(newValue != value){
				value = newValue
				updataView()
			}
		}
	})
}

function updataView(){
	console.log('更新视图')
}
  •  data.name = 'lishi';data.age = 21

        将会输出两次“更新视图”。

  •  data.x = '100'

        不会输入任何值,因为对象中没有对data.x进行声明

  • 上面的代码无法实现深度监听准备对象,只能实现浅监听。想要实现深度监听只能如下代码:
function updataView(){
	console.log('更新视图2')
}

//重庆定义属性监听起来
function defineReactive(target,key,value){
	//深度监听
	observer(value)
	
	//核心API
	Object.defineProperty(target,key,{
		get(){
			return name
		},
		set(newValue){
			if(newValue != value){
				//新增加属性的监听
				observer(value)
				
				value = newValue
				updataView()
				
			}
		}
	})
}

function observer(target) {
	if(typeof target !== 'object' ||  target ===null){
		return target
	}
	for(let key in target){
		defineReactive(target,key,target[key])
	}
}

const data = {
	name:'zhangsan',
	age:20,
	info:{
		address:'北京'
	}
}

observer(data)

data.name = 'lishi'//普通监听
data.x = '100'//新增属性监听
data.info.address = '重庆'//深度监听

2.2、Object.defineProperty的缺点

  • 深度监听,需要递归到底,一次性计算量大
  • 无法监听新增/删除属性(Vue.set   Vue.delete)Vue2.0需要,Vue3.0不需要
  • 无法原生监听数组,需要特殊处理

2.3、实现监听数组

function updataView(){
	console.log('更新视图2')
}

//重新定义数组的原型
const oldArrayProperty = Array.prototype
//创建新对象,原型指向oldArrayProperty,再扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);
['push','pop'].forEach(methodName =>{
	arrProto[methodName] = function(){
		console.log('asdf');
		updataView()//更新视图
		oldArrayProperty[methodName].call(this,...arguments)
	}
})

//重新定义属性监听起来
function defineReactive(target,key,value){
	//深度监听
	observer(value)
	
	//核心API
	Object.defineProperty(target,key,{
		get(){
			return name
		},
		set(newValue){
			if(newValue != value){
				//新增加属性的监听
				observer(newValue)
				
				value = newValue
				updataView()
				
			}
		}
	})
}

function observer(target) {
	if(typeof target !== 'object' ||  target ===null){
		return target
	}
	//数组
	if(Array.isArray(target)){
		target.__proto__ = arrProto
		console.log('显示',target);
	}
		//对象
		for(let key in target){
			defineReactive(target,key,target[key])
		}
}

let data = {
	name:'zhangsan', 
	age:20,
	info:{
		address:'北京'
	},
	nums: [10,20,30]
}

let nums = [10,20,30]
console.log(data.nums);
observer(data)

// data.name = 'lishi'//普通监听
// data.x = '100'//新增属性监听
// data.info.address = '重庆'//深度监听

data.nums.push(4)

2.4、 proxy

2.4.1、基本使用

let data = {
	name:'周',
	age:20
}

const proxyData = new Proxy(data,{
	get(target,key,receiver){
		const result = Reflect.get(target,key,receiver)
		console.log('get',key);
		return result
	},
	set() {
		const result = Reflect.set(target,key,val,receiver)
		console.log('set',key,val);
		return result
	},
	deleteProperty(){
		const result = Reflect.deleteProperty(target,key)
		console.log('delete property',key);
		return result
	}
})

2.4.2、Reflect

  • 和proxy能力一一对应
  • 规范化、标准化、函数式 
const obj = {a:100,b:100}

//判断数对象中是否有某个元数
//以前
if('a' in obj){}
//reflect
if(Reflect.has(obj,'a')){}
//还有一种
if(obj.hasOwnProperty('a')){}
  •  替代掉Object上的工具函数
const obj = {a:100,b:100}

console.log(Object.getOwnPropertyNames(obj));
//['a','b']
console.log(Reflect.ownKeys(obj));
//['a','b']

2.4.3、实现响应式 

function reactive(target = {}) {
	if (typeof target !== 'object' || target == null) {
		//不是对象或数组,则返回
		return target
	}

	const proxyConf = {
		get(target, key, receiver) {
			//只处理本身(非原型的)属性
			const ownKeys = Reflect.ownKeys(target)
			if(ownKeys.includes(key)){
				console.log('get',key);//监听
			}
			const result = Reflect.get(target, key, receiver)
			//深度监听
			//性能如何提升的
			return reactive(result)
		},
		set(target,key,val,receiver) {
			//重复的数据,不处理
			if(val === target[key]){
				return true
			}
			
			const ownKeys = Reflect.ownKeys(target)
			if(ownKeys.includes(key)){
				console.log('已有的 key', key);
			}else{
				console.log('新增的 key', key);
			}
			
			const result = Reflect.set(target, key, val, receiver)
			return result
		},
		deleteProperty(target,key) {
			const result = Reflect.deleteProperty(target, key)
			console.log('delete property', key);
			return result
		}
	}

	//生成代理对象
	const observed = new Proxy(target, proxyConf)
	return observed
}

const data = {
	name: 'zhang',
	age: 20,
	info:{
		city:'beijing',
		a:{
			b:{
				c:{
					d:'e'
				}
			}
		}
	}
}

const proxyData = reactive(data)
proxyData.name = '11'
proxyData.city = '11'
console.log(proxyData.info);
delete proxyData.name
  • 深度监听性能更好
  • 可监听 新增/删除 属性
  •  可原生监听数组的变化

2.5、总结 

  1. proxy能避免Object.defineProperty的问题
  2. proxy无法兼容所有浏览器,无法polyfill

三、虚拟DOM(virtual DOM)和diff算法


  • vdom是实现vue和react的重要基石
  • diff算法是vdom中最核心、最关键的部分

3.1、虚拟DOM(vdom)

  •  DOM操作非常的耗费性能
  • JS是非常快的
  • 以前用JQuery,可以自行控制DOM操作的时机,手动调整
  • Vue和react都是数据驱动视图,如何有效控制DOM操作?

3.2、解决方案-vdom (上面问题)

  • 有了一定复杂度,想减少计算次数比较困难
  • 能不能把计算,更多的转移动为JS计算?因为JS执行速度很快
  • vdom-用JS模拟DOM结构,计算出最小的变更,操作DOM

3.3、用JS模拟DOM结构

<div id="div1" class="container">
	<p>vdom</p>
	<ul style="font-size: 20px;">
		<li>a</li>
	</ul>
</div>
{
	tag:'div',
	props:{
		className:'container',
		id:'div1'
	},
	children:[{
		tag:'p',
		children:'vdom'
	},{
		tag:'ul',
		props:{
			style:'font-size: 20px;'
		},
		children:[{
			tag:'li',
			children:'a'
		}]
	}]
}

3.4、通过snabbdom学习vdom

  • 简洁强大的vdom库,易学易用
  • Vue参考它实现的vdom和diff

GitHub - snabbdom/snabbdom: A virtual DOM library with focus on simplicity, modularity, powerful features and performance.A virtual DOM library with focus on simplicity, modularity, powerful features and performance. - GitHub - snabbdom/snabbdom: A virtual DOM library with focus on simplicity, modularity, powerful features and performance.https://github.com/snabbdom/snabbdom

  • vue3.0重写了vdom的代码,优化了性
//引入
import {
  init,
  classModule,
  propsModule,
  styleModule,
  eventListenersModule,
  h,
} from "snabbdom";
//初始化
const patch = init([
  // Init patch function with chosen modules
  classModule, // makes it easy to toggle classes
  propsModule, // for setting properties on DOM elements
  styleModule, // handles styling on elements with support for animations
  eventListenersModule, // attaches event listeners
]);

const container = document.getElementById("container");
//第一个vnode
const vnode = h("div#container.two.classes", { on: { click: someFn } }, [
  h("span", { style: { fontWeight: "bold" } }, "This is bold"),
  " and this is just normal text",
  h("a", { props: { href: "/foo" } }, "I'll take you places!"),
]);
//调用渲染
patch(container, vnode);
//第二个vnode函数
const newVnode = h(
  "div#container.two.classes",
  { on: { click: anotherEventHandler } },
  [
	h(
	  "span",
	  { style: { fontWeight: "normal", fontStyle: "italic" } },
	  "This is now italic type"
	),
	" and this is still just normal text",
	h("a", { props: { href: "/bar" } }, "I'll take you places!"),
  ]
);
//覆盖上一个vnode更新
patch(vnode, newVnode); 

3.5、vdom的总结

  • jQuery更新DOM需要直接渲染操作全部的DOM
  • 用JS模拟DOM结构(vnode)
  • 新旧vnode对比,得出最小的更新范围,最后更新DOM
  • 数据驱动视图模式下,有效的控制DOM的操作

四、diff算法概述


4.1、diff算法 

  • diff算法是vdom中最核心,最关键的部分
  • diff算法能在日常中使用vue react中提现出来(如key)

4.2、diff算法概述

  •  diff即对比,是一个广泛的概念,如linux diff命令、git diff等
  • 两个对象也可以做diff,如:http://github.com/cujojs/jiff
  • 两颗树做diff,如上的vdom diff 

4.3、树diff的时间复杂度O(n^3) 

  • 第一,遍历tree1;第二,遍历tree2;第三,排序
  • 1000个节点,要计算1亿次,算法不可用

4.4、优化时间复杂度到O(n) 

  • 只比较第一层级,不跨级比较
  • tag不想共,则直接删除重建,不再深度比较
  • tag和key,两者都相同,则认为是相同节点,不再深度比较

tag不同

 

五、snabbdom-源码解读


5.1、h()函数

  • h.ts
//只传入标签(h函数的第一个参数)
export function h(sel: string): VNode;
//只传入标签和data(如事件,h函数的第二个参数)
export function h(sel: string, data: VNodeData | null): VNode;
//只传入标签和children(h函数的最后一个[]参数)
export function h(sel: string, children: VNodeChildren): VNode;
//传入标签,data,children
export function h(sel: string,data: VNodeData | null,children: VNodeChildren): VNode;
export function h(sel: any, b?: any, c?: any): VNode {
    //一系列处理
    //返回一个vnode
    return vnode(sel, data, children, text, undefined);
}
  •  vnode()函数:vnode.ts
export function vnode(
  sel: string | undefined,
  data: any | undefined,
  children: Array<VNode | string> | undefined,
  text: string | undefined,
  elm: Element | DocumentFragment | Text | undefined
): VNode {
  const key = data === undefined ? undefined : data.key;

	//返回children, text二者只能有一个,要么是个文本,要么是子元素
	//elm是DOM元素,决定返回的内容更新在什么地方
	//key是唯一值,所有的组件都不拒绝key,可以不只是存在于v-for中
  return { sel, data, children, text, elm, key };
}

 5.2、patch()函数

  • 传入值
return function patch(
	//传入旧的vnode
    oldVnode: VNode | Element | DocumentFragment,
	//传入新的vnode
    vnode: VNode
  ):
  •  cbs回调,执行pre hook,生命周期
//cbs回调,执行pre hook,生命周期
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]();
  • 第一个参数是DOM的情况
 //第一个参数不是vnode,patch(container, vnode),第一个参数是DOM元素
    if (isElement(api, oldVnode)) {
	//创建一个空的vnode,关联到这个DOM元素
      oldVnode = emptyNodeAt(oldVnode);
    } else if (isDocumentFragment(api, oldVnode)) {
      oldVnode = emptyDocumentFragmentAt(oldVnode);
    }
  •  相同的vnode
//相同的vnode
    if (sameVnode(oldVnode, vnode)) {
      patchVnode(oldVnode, vnode, insertedVnodeQueue);
    } 
  • sameVnode判断vnode完全相同
function sameVnode(vnode1: VNode, vnode2: VNode): boolean {
  //vnode1和vnode2的key相同
  const isSameKey = vnode1.key === vnode2.key;
  //vnode1和vnode2的data数据相同
  const isSameIs = vnode1.data?.is === vnode2.data?.is;
  //vnode1和vnode2的sel元素相同
  const isSameSel = vnode1.sel === vnode2.sel;
  //以上3个相同说明为同一个元素
  return isSameSel && isSameKey && isSameIs;
}
  • 两个vnode不相同(直接删除重建)
createElm(vnode, insertedVnodeQueue);
  •  patchVnode相同函数vnode处理计算
function patchVnode(
    oldVnode: VNode,
    vnode: VNode,
    insertedVnodeQueue: VNodeQueue
  ) {
    //类似于执行了一个生命周期的公式hook
    const hook = vnode.data?.hook;
    hook?.prepatch?.(oldVnode, vnode);
    //设置vnode.element,把旧的vnode新建牵引新的elem
    const elm = (vnode.elm = oldVnode.elm)!;
    //旧的子元素,children
    const oldCh = oldVnode.children as VNode[];
    //新的子元素,children
    const ch = vnode.children as VNode[];
    //做一个截断,oldVnode和vnode相同直接返回,
    if (oldVnode === vnode) return;
    //hook相关
    if (vnode.data !== undefined) {
      for (let i = 0; i < cbs.update.length; ++i)
        cbs.update[i](oldVnode, vnode);
      vnode.data.hook?.update?.(oldVnode, vnode);
    }
    //isUndef相当于判断是不是undefined
    //vnode.text ===  undefined 
    //就说明元素标签内的text内容为undefined,且标签内的children不为undefined
    //children有值
    if (isUndef(vnode.text)) {
      //新旧都有 children
      if (isDef(oldCh) && isDef(ch)) {
        //新旧children不相同,更新children
        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue);
        //新的children有,旧的children无 (旧有text)
      } else if (isDef(ch)) {
        //旧的text有值,先设置为空
        if (isDef(oldVnode.text)) api.setTextContent(elm, "");
        //添加新的children
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
       //新的children无,旧的children有
      } else if (isDef(oldCh)) {
        //直接移除旧的children
        removeVnodes(elm, oldCh, 0, oldCh.length - 1);
        //旧的text有值,新的text没有值
      } else if (isDef(oldVnode.text)) {
        //那elm内容直接标记为“”空
        api.setTextContent(elm, "");
      }
      
      //vnode.text !== undefined
      //说明标签内children为空
      //新的vnode.text !== 旧的vnode.text
    } else if (oldVnode.text !== vnode.text) {
      //移除旧的children
      if (isDef(oldCh)) {
        removeVnodes(elm, oldCh, 0, oldCh.length - 1);
      }
      //再设置新的text
      api.setTextContent(elm, vnode.text!);
    }
    hook?.postpatch?.(oldVnode, vnode);
  }
  • updateChildren 新旧children不相同,更新children
function updateChildren(
    parentElm: Node,
    oldCh: VNode[],
    newCh: VNode[],
    insertedVnodeQueue: VNodeQueue
  ) {
    //开始Startindex
    let oldStartIdx = 0;
    let newStartIdx = 0;
    
    //结束EndIdx
    let oldEndIdx = oldCh.length - 1;
    let newEndIdx = newCh.length - 1;
    
    let oldStartVnode = oldCh[0];
    let oldEndVnode = oldCh[oldEndIdx];
    
    let newStartVnode = newCh[0];
    let newEndVnode = newCh[newEndIdx];
    let oldKeyToIdx: KeyToIndexMap | undefined;
    let idxInOld: number;
    let elmToMove: VNode;
    let before: any;

    //进行循环用到上面所定义的4个index
    //循环的是以两边往中间进行
    //结束是当循环走到中间的时候结束
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (oldStartVnode == null) {
        oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left
      } else if (oldEndVnode == null) {
        oldEndVnode = oldCh[--oldEndIdx];
      } else if (newStartVnode == null) {
        newStartVnode = newCh[++newStartIdx];
      } else if (newEndVnode == null) {
        newEndVnode = newCh[--newEndIdx];
        //开始和开始做对比
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
        oldStartVnode = oldCh[++oldStartIdx];
        newStartVnode = newCh[++newStartIdx];
        //结束和结束做对比
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
        oldEndVnode = oldCh[--oldEndIdx];
        newEndVnode = newCh[--newEndIdx];
        //开始和结束做对比
      } else if (sameVnode(oldStartVnode, newEndVnode)) {
        // Vnode moved right
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
        api.insertBefore(
          parentElm,
          oldStartVnode.elm!,
          api.nextSibling(oldEndVnode.elm!)
        );
        oldStartVnode = oldCh[++oldStartIdx];
        newEndVnode = newCh[--newEndIdx];
        //结束和开始做对比
      } else if (sameVnode(oldEndVnode, newStartVnode)) {
        // Vnode moved left
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
        api.insertBefore(parentElm, oldEndVnode.elm!, oldStartVnode.elm!);
        oldEndVnode = oldCh[--oldEndIdx];
        newStartVnode = newCh[++newStartIdx];
        //以上4个都未命中
      } else {
        if (oldKeyToIdx === undefined) {
          oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
        }
        //拿新节点的key,是否能对应上 oldch 中的某个节点的key
        idxInOld = oldKeyToIdx[newStartVnode.key as string];
        //没有对应上
        if (isUndef(idxInOld)) {
          // 直接插入 new element
          api.insertBefore(
            parentElm,
            createElm(newStartVnode, insertedVnodeQueue),
            oldStartVnode.elm!
          );
        //对应上了
        } else {
          //对应上key的节点
          elmToMove = oldCh[idxInOld];
          //sel不相等
          if (elmToMove.sel !== newStartVnode.sel) {
            //直接插入 new element
            api.insertBefore(
              parentElm,
              createElm(newStartVnode, insertedVnodeQueue),
              oldStartVnode.elm!
            );
            //sel相等,key相等
          } else {
            patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);
            oldCh[idxInOld] = undefined as any;
            api.insertBefore(parentElm, elmToMove.elm!, oldStartVnode.elm!);
          }
        }
        newStartVnode = newCh[++newStartIdx];
      }
    }

总结:不使用key和使用key的情况

不使用key直接全部新建,使用key还能对比一下做置换,这样能够提升性能

六、编译模板

  • 模板是vue开发中最常用的一部分,即与使用相关的原理
  • 它不是html,有指令、插值、js表达式
  • 前置知识:JS的with语法
  • vue template complier 将模板编译为render函数
  • 执行render函数生成vnode

6.1、with语法

  • 改变{ } 内自由变量的查找规则,当做obj属性来查找
  • 如果找不到匹配的obj属性,就会报错
  • with要慎用,它打破了作用域规则,易读性变差 

6.2、编译模板

  • 模板不是html,有指令、差值、JS表达式、能实现判断循环
  • html是标签语言,只有JS才能实现判断、玄幻(图灵完备的)
  • 因此,模板一定是转换为某种JS代码,即编译模板
const compiler= require('vue-template-compoler')

//插值后面就所有的模板都写在这里
const template = `<p>{{message}}</p>`

//编译
const res = compiler.compile(template)
console.log(res.render)
  • console的打印:

         with(this){return _c('p',[_v(_s(message))])}

  • _c   createElement() 相当于 h()
  • _v   createTextVNode()
  • _s   toString()
  • h函数返回的是一个vnode,createElement函数也是返回一个vnode
const template = `<p>{{flag ? message : 'no message found'}}</p>`
  • console的打印:

         with(this){return _c('p',[_v(_s(flag ? message : 'no message found'))])}

  • _s函数内是个表达是,本身返回的就是个函数,函数内执行三元运算就可以直接知道结果,所以_c函数最终也还是会返回一个vnode
const template = `<div id="div1" class="container">
					<img :src="imgUrl">
				  </div>`
  •  console的打印:

         with(this){return _c('div',
                                    {staticClass:'container',attrs:{'id':'div1'}},
                                    [_c('img',{attrs:{'src':imgUrl}})])}

const template = `<div>
					<p v-if="flag === 'a'">A</p>
					<p v-else>B</p>
				  </div>`
  •  console的打印:

        with(this){return _c('div',[(flag === 'a'?_c('p',[_v("A")]):_c('p',[_v("B")])])} 

const template = `<ul>
					<li v-for="item in list" :key="item.id">{{item.title}}</li>
				  </ul>`
  •   console的打印:

         with(this){return _c('ul',_l((list),function(item){return _c('li',{key:item.id},[_v(_s(item.title))])}))}

const template = `<button @click="clickHander">submit</button>`
  •  console的打印:

         with(this){return _c('button',{on:{click:'clickHander'}},[_v('submit')])}

const template = `<input type="text" v-model="name" />`
  • console的打印:

 with(){
                    return _c('input',{directives:[{name:"model",rawname:"v-model",value:(name),expression:"name"}],
                    attrs:{"type":"text"},domProps:{"value":(name)},on:{"input":function($event.target.composing)return;
                    name=$event.target.value}})
                }

  • 将input绑定输入on:input事件,将$event.target.value绑定到输入的值内

 总结:

  1. 模板编译为render函数,执行render函数返回vnode
  2. 基于vnode再执行patch和diff
  3. 使用webpack vue-loader,会在开发环境下编译模板

 6.3、vue组件中使用使用render代替template 

Vue.component('heading',{
	render: function(createElement) {
		return createElement(
			'h1',
			[
				createElement('a',{
					attrs: {
						name: 'headerId',
						href: '#' + 'headerId'
					}
				},'this is a tag')
			]
		)
	}
})
  • 在一些复杂的情况下,不能用template的情况下,可以考虑用render
  • react一直在用render,没用tempate 

6.4、vue组件是如何渲染更新的

 6.4.1、初次渲染过程

  • 解析模板为render函数,(或在开发环境已完成,vue-loader)
  • 触发响应式,监听data属性,getter和setter
  • 执行render函数,生成vnode,patch(elem,vnode)

 6.4.2、更新过程 

  • 修改data,触发setter(此前getter已经被监听)
  • 重新执行render函数执行newVnode
  • patch(vnode,newVnode) 

 6.5、异步渲染

  1. $nextTick()
  2. 汇总data的修改,一次性更新视图
  3. 减少dom操作次数,提高性能

6.6、总结

  1. 渲染和响应式的关系
  2. 渲染和模板编译的关系
  3. 渲染和vdom的关系 
  • 初次渲染过程
  • 更新过程
  • 异步渲染 

七、前端路由原理 

7.1、如何用js实现hash路由

  1.  稍微复杂的SPA,都需要路由(单页面应用SPA)
  2. vue-router也是vue全家桶的标配之一
  3. 属于“日常使用相关原理”

7.1.1、网页url组成部分

 7.1.2、hash的特点

  • hash变化会触发网页跳转,即浏览器的前进和后退
  • hash变化不会刷新页面,SPA必须的特点
  • hash永远不会提交到server 端(前端自生自灭)
//hash变化包括
//a、js修改url
//b、手动修改url的hash
//c、浏览器前进和后退
window.onhashChange = (event) =>{
	console.log('old url',event.oldUrl)
	console.log('new url',event.newUrl)
	
	console.log('hash',location.hash)
}

//页面初次加载,获取hash
document.addEventListener('DOMContentLoade',()=>{
	console.log('hash',location.hash)
})

//JS修改url
document.getElementById(btn1).addEventListener('click',{
	location.url = '#/user'
})

 7.2、H5 history

  1. 用url规范的路由,但跳转时不刷新页面
  2. history.pushState
  3. windw.onpopstate
//页面初次加载,获取path
document.addEventListener('DOMContentLoade',()=>{
	console.log('load',location.hash)
})
//打开一个新的路由
//【注意】用pushState方式,浏览器不会刷新页面
document.getElementById(btn1).addEventListener('click',{
	const state = {name: 'page1'}
	console.log('切换路由到','page1')
	history.pushState(state,'','page1')
})
//监听浏览器前进还是后退
window.onpopState = (event) =>{
	console.log('onpopState',levent.state,location.pathname)
}

history模式需要server端的配和(node)

 

  •  无论访问的什么路由最终返回的都是index.html

 两者的选择

  1. to B的系统推荐用hash,简单易用,对url规范不敏感(toB后端管理系统)
  2. to C的系统可以考虑用history H5 ,但是需要服务端的支持(to C的系统不需要管理SU,不需要搜索引擎都可以不用history H5模式)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值