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和更新函数的映射关系,当收到属性变化的通知会执行相应的函数,实现视图的更新
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: