Vue中的内部运行机制

转载于 https://blog.csdn.net/RedLoVE0908/article/details/99749359

最近遇到了Vue的瓶颈,不知道怎么样才能得到提升,所以准备从源码和机理入手。

目录大纲

  • 概述
  • 初始化及挂载
  • 响应式的实现
  • 编译
  • Virtual DOM和数据更新时的patch()

概述

Vue.js的内部运行机制

  • 初始化
    • 初始化生命周期、事件、render、state
    • Object.definePrototy() 响应式的定义
  • 挂载 (运行时 + 编译器)
    • template到render Function的编译
    • 注册watcher
    • 响应式的触发
  • patch的diff算法更新机制
  • nextTick的异步更新策略

初始化及挂载

初始化及挂载
Vue的实现首先是通过Vue类里面构造函数中所执行的init()
_init的函数里面主要包括

  1. 主要包括初始化生命周期、初始化事件、初始化render,初始化state(包括props、data,method、computed、watchers)。
  2. 执行vm.$mount()挂载组件

初始化以及挂载包括了Vue实例的整个前半的生命周期,在这个过程中,Vue完成了模板到真实DOM的显示,以及data与View的响应式绑定监控。

响应式的实现

关于Object.definePrototy()

  • Object.definePrototy() :
    • 概念:直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
    • 参数objpropdescriptor
    • 类型:数据描述符、存取描述符
    • 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()通知观察者进行视图更新
  • 在$mouted执行挂载(运行中+编译器版本)
    • 编译模板将data对象和vue语法声明的模板编译成浏览器可读的html
    • 注册一个观察者Watcher,并将它的getter绑定为vm._update(vm._render())
    • 在将Dep.target设置为自生观察者实例,执行getter操作,即为执行了vm._update()
    • vm._update()中如果需要渲染某个数据就会触发本身的getter,完成依赖收集
  • 当某个值被改变的时候
    * 触发set中的dep.notify()通知观察者进行视图更新
    * notify调用dep.subs中的每一个watcherrun()进而触发watchergetter()
    * 进而触发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

VNode

  • 产生原因,对于大应用来说直接操作DOM来修改视图是一个很大的花销。

  • Vue.js将DOM抽象成一个以JavaScript对象为节点的虚拟DOM树,对这棵抽象树进行节点的增删查改。

  • 优点

  1. 不需要操作真实的DOM,只操作JavaScript对象
  2. 修改以后经过diff算法得出需修改的最小单位,更新映射到真实的DOM上面去,提高性能。
  3. 真实DOM的一层抽象,而不依赖某个平台,可在weex、浏览器平台、甚至是node平台使用
    patch

数据更新时的patch()

  • 数据发生修改会触发vm._update(vm._render())
  • vm._render()返回最新的template的Vnode模板
  • vm._update()中获取到新旧VNode节点进行vm._patch__()函数对比
    • 对比过程主要包括对数同层叶子节点的比较
  • 进而对真实DOM进行一系列的增删查改
发布了90 篇原创文章 · 获赞 354 · 访问量 64万+

猜你喜欢

转载自blog.csdn.net/liuyifeng0000/article/details/105061093