剖析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属性(异步方法聚集地)
}
}
上一篇: 20.核心线程池的内部实现
下一篇: 那我就把十块钱都给你