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

这是笔者最近一段时间的学习收获,将其整理成文,但愿能对各位有些许帮助。文章稍长,但绝对精粹。也算是笔者为各位献上的一份“元旦大礼”吧~~
(因文章过长,故将其整理为上、下两篇,之间连续贯通)javascript


Vue运行机制全局预览

【初始化及挂载】=>【编译】(parse-optimize-generate)=>【render function渲染】(响应式)=>【Virtual DOM】=>【更新视图】html

初始化及挂载
在这里插入图片描述
在new了vue以后,Vue会调用_init函数进行(全局)初始化。初始化生命周期、事件、props、methods、data、computed、watch等。其中最重要的是经过Object.defineProperty设置setter和getter函数,用来实现【响应式】和【依赖收集】。
初始化后调用 $mount 挂载组件 —— 若是是运行时编译,即不存在 render function 可是存在templete的状况,(此后)还需进行【编译】步骤。vue

编译
在这里插入图片描述java

  • parse :用正则等方法解析templete模板中的指令、class、style等数据,造成AST(抽象语法树——源代码的抽象语法结构的树状表现形式)
  • optimize :主要为了标记static节点。这是vue的一处优化——后面当update更新界面时,会有一个 patch 的过程,diff算法会直接跳过静态节点,从而减小了比较的过程,优化了patch的性能
  • generate :将AST转化为 render function 字符串的过程。获得 render & staticRenderFns 字符串

经历以上三个阶段后,组件中就会存在渲染VNode所需的render function了。react

响应式
Vue的响应式中作出卓越贡献的也就getter和setter了。
当 render function 被渲染时,由于会读取所需对象的值,因此会触发getter进行【依赖收集】,其目的是将观察者Watcher对象存放到当前闭包中的订阅者Dep的subs中,造成以下关系:
在这里插入图片描述
在修改对象值时,会触发对应的setter,setter通知以前【依赖收集】获得的Dep中每个Watcher,告诉他们本身的值改变了,须要从新渲染视图。这时候这些Watcher就开始调用update来更新视图 —— 固然,这中间还有一个patch的过程,以及使用队列来异步更新的策略。web

Virtual DOM
前面说 render function 会转化为VNode节点。而Virtual DOM实际上是一棵以JavaScript对象为基础的树。用对象属性描述节点。它实际上只是一层对真是DOM的抽象。
正是因为Virtual DOM是以JavaScript对象为基础而不依赖平台环境。因此使他拥有了跨平台的能力。算法

一个简单的例子:express

// AST
{ 
 
   
	tag:'div',
	children:[
		{ 
 
   
			tag:'a',
			data:{ 
 
   
				directives:[   //属性
					{ 
 
   
						rawName:'v-show',
						expression:'isShow',
						name:'show',
						value:true
					}
				],
				staticClass:'demo'
			},
			text:'click me'
		}
	]
}

渲染以后就获得:闭包

<div>
	<a class="demo" v-show="isShow">click me</a>
</div>

这两段代码的逆向过程,其实就是一个HTML解析器解析标签的过程。让咱们来逐步看看:异步


响应式系统

对vue稍有深究的人都必定知道,Vue的【响应式】是基于Object.defineProperty实现的。
其使用方法:

Object.defineProperty(obj,prop,descriptor)
                //目标对象、须要操做的目标对象的属性名、描述符(属性汇集地)

好比,通常更喜欢这样写:

Object.defineProperty(obj,prop,{ 
 
   
	enumerable:是否能够枚举
	configurable:是否能够被修改或删除
	get:
	set:
})

实现Observer
首先定义一个函数cb,用来模拟视图更新。内部是一些更新视图的方法:

function cb(val){ 
 
   
	//渲染视图
	console.log('视图更新了...');
}

而后咱们定义一个defineReactive,这个方法经过Object.defineProperty来实现对对象的【响应式】化,入参固然是obj(须要绑定的对象)、key(obj的一些属性)、val(具体值),通过defineReactive处理后,obj的key属性会在读的时候触发reactiveGetter方法,在写的时候触发reactiveSetter方法:

function defineReactive(obj,key,val){ 
 
   
	Object.defineProperty(obj,key,{ 
 
   
		enumable:true,
		configurable:true,
		get:function reactiveGetter(){ 
 
   
			return val;
		},
		set:function reactiveSetter(newVal){ 
 
   
			if(newVal===val) return;
			cb(newVal);
		}
	});
}

这些固然是不够的。咱们须要在这上面再封装一层observer —— 它传入一个value(须要“响应式”化的对象)参数,经过遍历他全部属性的方式来对该对象的每个属性作defineReactive处理:

function observer(value){ 
 
   
	if(!value || (typeof value !== 'object')){ 
 
   
		return;
	}
	Object.keys(value).forEach((key)=>{ 
 
   
		defineReactive(value,key,value[key]);
	});
}

最后,咱们把它们封装到“vue”中:

class Vue{ 
 
   
	constructor(options){ 
 
   
		this._data=options.data;
		observer(this._data);
	}
}

这时,就能够“随心所欲了”:

let vm=new Vue({ 
 
   
	data:{ 
 
   
		test:"I'm mxc"
	}
});
vm._data.test="hello world!";   //“视图更新了...”

这样代码就“完美”了吗?


响应式系统的依赖收集追踪原理

为何要“追踪”?
假如咱们如今有一个Vue对象:

new Vue({ 
 
   
	templete:`<div> <span>{ 
    {text1}}</span> <span>{ 
    {text2}}</span> </div>`,
	data:{ 
 
   
		text1:'text1',
		text2:'text2',
		text3:'text3'
	}
});

而后咱们这么作了:

this.text3="I'm text3";

如上,咱们修改了text3的数据,但视图中并不须要text3,因此咱们并不须要调用cb函数。

要解决这个问题,就须要大名鼎鼎的【订阅】 & 【观察者】模式了:
订阅者Dep —— 存放Watcher对象 (->其实这能够说成是一个“消息管理中心”)

class Dep{ 
 
   
	constructor(){ 
 
   
		this.subs=[]
	}
	addSub(sub){ 
 
   
		this.subs.push(sub);
	}
	//通知全部Watcher更新视图
	notify(){ 
 
   
		this.subs.forEach((sub)=>{ 
 
   
			sub.update();
		});
	}
}

观察者Watcher:

class Watcher{ 
 
   
	constructor(){ 
 
   
		//在new一个Watcher对象时将该对象赋值给Dep.target,在get中会用到
		Dep.target=this;
	}
	update(){ 
 
   
		console.log('视图更新啦...');
	}
}
Dep.target=null;

接下来要去修改一下defineReactive以及Vue的构造函数,来完成【依赖收集的注入】:(咱们再也不须要“cb”了)

function defineReactive(obj,key,val){ 
 
   
	const dep=new Dep();
	Object.defineProperty(obj,key,{ 
 
   
		enumable:true,
		configurable:true,
		get:function reactiveGetter(){ 
 
      //getter【依赖收集】
			dep.addSub(Dep.target);
			return val;
		},
		set:function reactiveSetter(newVal){ 
 
   
			if(newVal===val) return;
			dep.notify();
		}
	});
}
class Vue({ 
 
   
	constructor(options){ 
 
   
		this._data=options.data;
		observer(this._data);
		new Watcher();
		//这里模拟render的过程,为了触发test属性的get函数
		console.log('render',this._data.test);
	}
})

咱们在闭包中增长了一个Dep类的对象,用来收集Watcher对象。在对象被“读”的时候,会触发reactiveGetter,把当前Watcher对象(存放在Dep.target中)收集到Dep类中去;以后“写”的时候,触发reactiveSetter,通知类Dep调用notify来触发全部Watcher的update更新对应视图。


专栏连接(免费):vue实现原理及运行机制分析

本文同步分享在 博客“行舟客”(CSDN)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索