vue03-vue工作原理

vue运行机制:

当我们new Vue的时候,实际上在构造函数里

1.执行了一个init方法,init方法会初始化data,props,computed,watch,事件监听,派发生命周期钩子等等,

2.得到Vue实例后进行挂载$mount(最终转换成render),查看$mount方法的源码,$mount函数再vue的原型上被定义(Vue.prototype.$mount),,函数的主要操作时,拿到el,将其转化为dom对象,接下来判断是否有render函数,没有的话判断是否有template,有templat就将其编译成render函数,如果没有template但是有el,则取el的outerHTML,然后编译陈render函数

3.挂载最重要的作用就是进行虚拟DOM的计算过程,虚拟DOM的获取执行的是render function(渲染函数)

4.第一条线是初始化的过程:render函数返回的是虚拟DOM,虚拟DOM通过patch()将虚拟DOM变为真实DOM;

4.第二条线是更新过程:初始化完成后,有个更新过程,render函数里有好多值的获取,这些值已经做了响应化处理,响应化其实就是拦截的过程defineProperty,当访问数据时,watcher做一个依赖收集,把当前访问的属性和render函数间建立一个关系,当改数据时,watcher会执行render函数,做update,这块涉及一个diff算法,更新时会出现一个全新的虚拟DOM,与初始化的虚拟DOM进行对比

说一下compile(编译):定义视图的时候会写template标签,webpack里会配一个vue-loader的加载器,执行一个编译过程,将template转换为render函数,平时写template其实是定义render函数,转换成的render函数会等到$mount的时候调用,中间complile的过程执不执行取决于运行环境,运行环境通常有两种,如果在浏览器中引入了一个带编译器的vue.js,在界面中写一些带字符串的模板,这些模板在浏览器中实时编译,这种称为待运行时的编译,还有一种是开发中的webpack的环境,webpack环境是预编译,(写的template在我打包的时候提前把template编译好了,程序运行时render函数已经存在了,不用经过实时编译,称为运行时编译

 写一个简版的vue.js来看一下双向数据绑定的原理

1.首先创建一个Vue类,通过监听器observe方法来实现数据的劫持与监听,如果有变动就通知订阅者(遍历data,通过Object.defineProperty方法中的get方法,监听到对数据的访问,然后将其push到对应的deps数组中,其中的set方法,可以监听到对数据的修改,从而调用Dep中的notify方法,通知更新

2.创建Dep类,用来与data中的数据一一对应:一个数据属性对应一个dep,所以在遍历data的时候,创建Dep的实例,与key做一一对应;再一个,属性在组件中访问或出现过一次,就会创建一个watcher,defineProperty的get方法可以监听到属性的访问,所以在get中,为dep添加一个watcher;set方法可以监听到数据的修改,数据修改就会调用Dep的notify方法,来通知deps中的所有数据进行更新

3.创建(订阅者)watcher类,负责创建data中key和更新函数的映射关系,当收到属性变化的通知会执行相应的函数,实现视图的更新

//watcher在做编译的时候初始化,实际在vue中是通过render函数在调用时触发的,今天我们在Vue中主动触发,来做验证
//dep已经通过get里面的收集工作保存了相关watcher,它通知他的更新
 
vue.js
//new Vue({data:{....}})  创建vue实例的写法,
class Vue {
    //1.数据响应化
    constructor (options) {
        this.$options = options;
        //处理传入data
        this.$data = options.data;
        //响应化
        this.observe(this.$data)
         //2.依赖收集
          new Watcher(this,'test');
          this.test    
    }

    observe(data){
        //遍历的必须是对象---我们只实现数据时对象的格式,数组的未实现
        if(!data || typeof data !== 'object'){
            return;
        }
        //遍历
        Object.keys(data).forEach(key => {
            //真正的响应化处理
             this.defineReactive(data,key,data[key])

             //代理data中的属性到vue实例上,在访问vue实例中的data属性时,省略了$data( vm.$data.test->vm.test)
             this.proxyData(key)
        })
    }

    defineReactive(data,key,val){ //尝试创建一个闭包,一直保存key与value值,保存应用程序状态
        //递归
        this.observe(val);
        //创建Dep的实例和key一一对应
        const dep = new Dep();//一个dep对应一个属性
        Object.defineProperty(data,key,{  //不能深层修改,所以上面加了递归
            get(){
                //一次访问对应一次watcher,每次访问就会创建一个watcher,此时将this绑定到里Dep.target上
                Dep.target && dep.addDep(Dep.target);//一个dep对应多个watcher
                return val
            },
            set(newVal){
                if(newVal === val){
                    return
                }
                val = newVal;
                dep.notify();//更新时dep通知所有watcher进行更新
                console.log(`${key}更新了`)
            }
        })
    }

    proxyData(key){
        //需要给vue实例定义属性
        Object.defineProperty(this,key,{  //不能深层修改,所以上面加了递归
            get(){
                return this.$data[key]
            },
            set(newVal){
                this.$data[key] = newVal
            }
        })
    }
}
//Dep:和data中的每一个key对应起来,主要负责管理相关watcher
class Dep {
    constructor(){
        this.deps = [];
    }
    addDep(dep){
        this.deps.push(dep)
    }
    notify(){
        this.deps.forEach(dep => {
            dep.update()
        })
    }
}

//Watcher:负责创建data中key和更新函数的映射关系
class Watcher{
    constructor(vm,key,cb){
        this.vm = vm;
        this.key = key;
        this.cb = cb;

        Dep.target = this;//把当前watcher实例附加到Dep静态属性上
       
    }

    update(){
        console.log(`${this.key}属性更新了`)
    }
}
<head>
    <meta charset="utf-8">
    <title></title>
    <script src="./vue.js"></script>
    <script>
        var vm = new Vue({
            data:{
                test:'i am a test'
            }
        })
        vm.test = 'change'
    </script>
</head>
<body>
</body>
</html>

 控制台会打印更新即为成功

--------------------------------------------------------------------------------------------------------------------------

总结:

1.数据响应化处理,仅仅实现了对象响应化,数组怎么实现?

2.自定义组件的实现?

3.没有出现虚拟DOM(此例子中不需要)因为此例子中是Vue1.0的实现,在界面中每出现一次数据的绑定,指令等每出现一个动态的值,都会创建一个watcher与之相对应,这是vue1.0最大的问题;vue2.0最大的改变是引入虚拟DOM,把watcher的力度变小了,小到组件级别,每个组件只有一个watcher;

vue1.0:

  一个数据属性对应一个dep  一个dep对应多个watcher(一个dep保存多个watcher),同一个属性,界面中用到了一次(访问一次),就会创建一个watcher,然后保存在对应的dep中
vue2.0:
  一个组件只有一个watcher,watcher数量大大减少(一个watcher对应多个dep),这样当组件中的数据发生变化是,我们不知道是哪个数据发生了变化,所以引入虚拟DOM机制,将所有变化记录下来,然后把变化前和变化后做比对,
原文地址:https://www.cnblogs.com/znLam/p/12879654.html