剖析vue原理及运行机制(下)

上文链接:
剖析vue原理及运行机制(上)

专栏链接(免费):
vue内部实现原理分析


让我们跨过一切,来看一个很重要的知识点:

批量异步更新——nextTick原理

回顾前面一堆知识点,我们大概已知晓Vue是如何在我们修改data后修改视图了:这其实是一个 setter - Dep - Watcher - patch - 视图 的过程
现在假设有如下情况:

<templete>
	<div>
		<div>{{num}}</div>
		<div @click="hClick">click me</div>
	</div>
</templete>
<script>
	export default{
		data(){
			return{
				num:0
			}
		},
		methods:{
			hClick(){
				for(let i=0;i<100;i++){
					this.num++;
				}
			}
		}
	}
</script>

这乍一看没啥 —— 不就是按下按钮,num被循环自增1000次嘛。

但我们很快想到:按我们之前的理解,每次num自增时,都会触发num的setter。也就是说,上面提到的“流程”在这个demo中要被跑1000次!太可怕了。
Vue肯定不会用这样的方式。实际上vue默认每当触发某个数据的setter时,对应的Watcher对象其实会被push进一个队列queue中,在下一个tick时将这个queue全部拿出来run(Watcher内一个方法,用来触发patch操作)一遍。
简单实现nextTick:(这里用的是setTimeout)

let callbacks=[];
let pending=false;
function nextTick(cb){
	callbacks.push(cb);
	if(!pending){
		pending=true;
		setTimeout(flushCallbacks,0);
	}
}
function flushCallback(){
	pending=false;
	const pipes=callbacks.slice(0);
	callbacks.length=0;
	for(let i=0;i<pipes.length;i++){
		pipes[i]();
	}
}

既然如此,在上面自增1000的例子中,我们并不需要在下一个tick时执行1000个同样的Watcher对象去修改界面,而只需一个Watcher对象,使其将界面上的0变成1000即可。

那么,我们就需要一个“过滤”操作 —— 同一个Watcher在同一个tick时应该只被执行一次。即 队列中不应该出现重复的Watcher对象


重写Watcher
实现update,在修改数据后由Dep来调用,而run才是真正触发patch的方法:

let uid=0;
let has={};
let queue=[];
let waiting=false;

class Watcher{
	constructor(){
		this.id=++uid;
		Dep.target=this;
	}
	update(){
		console.log('watch'+this.id+'update');
		queueWatcher(this);   //将update自身传进去
	}
	run(){
		console.log('watch'+this.id+'->视图更新啦...');
	}
}
Dep.target=null;

function queueWatcher(watcher){
	const id=watcher.id;
	if(has[id]===null){
		has[id]=true;
		queue.push(watcher);
		if(!waiting){
			waiting=true;
			nextTick(flushScheduleQueue);   //上面重写nextTick部分的代码
		}
	}
}
function flushScheduleQueue(){
	let watcher,id;
	for(index=0;index<queue.length;index++){
		watcher=queue[index];
		id=watcher.id;
		has[id]=null;
		watcher.run();
	}
	waiting=false;
}

Vuex

Vue还有个比较重要的机制 —— vuex —— 项目内数据共享(数据通信)
在这里插入图片描述
首先安装vuex:

cnpm install vuex --save

下面以我近期所做项目(仿去哪网App)部分内容来分析:

在src目录下单独创建文件夹store,在其中创建文件index.js:

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

let defaultVity='北京'
try{
	if(localStorage.city){
		defaultCity=localStorage.city
	}
}catch(e){}
export default new Vuex.Store({
	state:{
		city:defaultCity
	},
	//actions:{
	//	changeCity(ctx,city){
	//		ctx.commit('changeCity',city)
	//	}
	//}
	mutations:{
		changeCity(state,city){
			state.city=city
			try{
				localStorage.city=city
			}catch(e){}
		}
	}
})

在main.js中引入:

import store from './store'
//在new Vue({...})中加入:
new Vue({
	//...
	store,
	//...
})

在需要用到“city”的地方改写,如下:

{{this.$store.state.city}}

在需要改变city值的地方改写,如下:

methods:{
	handleCity(city){
		this.$store.commit('changeCity',city);   
		//this.$store.dispatch('changeCity',city);   //dispatch调用的是store中的actions属性(异步方法聚集地)
	}
}

除了上面的方式,vuex中还内置了一个方法,常被用于【本地存储】:

store.subscribe((mutations,state)=>{
	localStorage.setItem('city',state.city);
});

取的时候也如上所示:

city:localStorage.getItem('city') || '上海';
发布了195 篇原创文章 · 获赞 391 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_43624878/article/details/103761483
今日推荐