转载于 https://blog.csdn.net/RedLoVE0908/article/details/99749359
序
最近遇到了Vue的瓶颈,不知道怎么样才能得到提升,所以准备从源码和机理入手。
目录大纲
- 概述
- 初始化及挂载
- 响应式的实现
- 编译
- Virtual DOM和数据更新时的patch()
概述
- 初始化
- 初始化生命周期、事件、render、state
- Object.definePrototy() 响应式的定义
- 挂载 (运行时 + 编译器)
- template到render Function的编译
- 注册watcher
- 响应式的触发
- patch的diff算法更新机制
- nextTick的异步更新策略
初始化及挂载
Vue的实现首先是通过Vue类里面构造函数中所执行的init()。
_init的函数里面主要包括:
- 主要包括初始化生命周期、初始化事件、初始化render,初始化state(包括
props、data,method、computed、watchers
)。 - 执行
vm.$mount()
挂载组件
初始化以及挂载包括了Vue实例的整个前半的生命周期,在这个过程中,Vue完成了模板到真实DOM的显示,以及data与View的响应式绑定监控。
响应式的实现
关于Object.definePrototy()
- Object.definePrototy() :
- 概念:直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
- 参数:
obj
、prop
、descriptor
- 类型:数据描述符、存取描述符
- descriptor :
configureable
(数据描述符 & 存取描述符)
当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默 认为 false。emunerable
(数据描述符 & 存取描述符)
当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 false。writable
(数据描述符)
当且仅当该属性的writable为true时,value才能被赋值运算符改变。默认为 false。value
(数据描述符)
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。set
(存取描述符)
一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认undefined。get
(存取描述符)
一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。默认undefined。
依赖注入和更新视图
- 一个Vue类的构造函数中绑定data
- 为data注册 Observer
- 对data中的每个key值调用
Object.definePrototype
- 在
get
中执行dep.depend()
进行依赖绑定 - 在
set
中执行dep.notify()
通知观察者进行视图更新
- 对data中的每个key值调用
- 在$mouted执行挂载(运行中+编译器版本)
- 编译模板将data对象和vue语法声明的模板编译成浏览器可读的html
- 注册一个观察者
Watcher
,并将它的getter绑定为vm._update(vm._render())
- 在将
Dep.target
设置为自生观察者实例,执行getter操作,即为执行了vm._update()
- 在
vm._update()
中如果需要渲染某个数据就会触发本身的getter
,完成依赖收集
- 当某个值被改变的时候
* 触发set
中的dep.notify()
通知观察者进行视图更新
*notify
调用dep.subs
中的每一个watcher
的run()
进而触发watcher
的getter()
* 进而触发vm._update(vm._render())
进行重新渲染VNode与patch
* 在patch中将新老的VNode进行diff算法分析,找到最小结构,进而更新到真实的DOM上
简单代码示例
let uid=0
class Dep {
constructor(){
this.id = uid++
this.subs=[]
}
addSubs(){
this.subs.push(Dep.target)
}
depend(){
if(Dep.target){
this.addSubs()
}
}
/*通知所有订阅者*/
notify () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
/*
一个解析表达式,进行依赖收集的观察者,同时在表达式数据变更时触发回调函数。它被用于$watch api以及指令
*/
class Watcher{
constructor(expns){
Dep.target=this
this.getter = expns
this.value = this.get()
}
run () {
this.get()
}
update(){
this.run()
}
get(){
return this.getter.call(vm)
}
}
// 存取描述符(数据描述符)
function defineReactive(obj,key,val){
let dep = new Dep()
Object.defineProperty(obj,key,{
enumerable:true,//可以修改
configurable:true,//可以出现在对象枚举属性
get:()=>{
//依赖收集
if (Dep.target) {
/*进行依赖收集*/
dep.depend()
}
return val
},
set:newVal=>{
val=newVal
dep.notify()
}
})
}
function observe (value){
Object.keys(value).forEach(key => {
defineReactive(value,key,value[key])
});
}
/*代理*/
function _proxy (data) {
const that = this;
Object.keys(data).forEach(key => {
Object.defineProperty(that, key, {
configurable: true,
enumerable: true,
get: function proxyGetter () {
return that._data[key];
},
set: function proxySetter (val) {
that._data[key] = val;
}
})
});
}
let vm
class Vue{
constructor(options){
vm = this
//在源码中是通过代理的方式会将_data代理成vm.data
this._data=options.data
_proxy.call(this, options.data);/*构造函数中*/
observe(this._data)
// 挂载
this._mount()
}
_update(){
for (const key in this._data) {
if (this._data.hasOwnProperty(key)) {
console.log('_update渲染更新视图~',this._data[key]);
}
}
}
_mount(){
let updateComponent
updateComponent = () => {
vm._update()
}
//注册一个观察者
vm._Watcher=new Watcher(updateComponent)
}
}
let app = new Vue({
el:'#app',
data:{
text:'text1',
text1:'text2'
}
})
//修改
app.text1='0'
在响应式更新数据的过程中,如果一个数据的值在一段时间内频繁更新了很多次,会依次触发响应式setter->Dep->Watcher->update->patch,所以引入nextTick的异步更新策略,实现一个queue队列,会在下一个tick去执行一次上面的响应式更新操作,大大优化了性能。
编译
- baseCompile
parse
——分析optimize
——优化generate
——生成
- createCompileToFunctionFn
- 将编译结果放入缓存
- 同时
staticRenderFns
以及render
函数会被转换成Funtion对象
Virtual DOM和数据更新时的patch()
VNode
-
产生原因,对于大应用来说直接操作DOM来修改视图是一个很大的花销。
-
Vue.js将DOM抽象成一个以JavaScript对象为节点的虚拟DOM树,对这棵抽象树进行节点的增删查改。
-
优点:
- 不需要操作真实的DOM,只操作JavaScript对象
- 修改以后经过diff算法得出需修改的最小单位,更新映射到真实的DOM上面去,提高性能。
- 真实DOM的一层抽象,而不依赖某个平台,可在weex、浏览器平台、甚至是node平台使用
数据更新时的patch()
- 数据发生修改会触发
vm._update(vm._render())
vm._render()
返回最新的template的Vnode模板vm._update()
中获取到新旧VNode节点进行vm._patch__()
函数对比- 对比过程主要包括对数同层叶子节点的比较
- 进而对真实DOM进行一系列的增删查改