Vue源码分析(双向数据绑定)笔记02
vue是用什么方法来进行渲染的呢?
标准的Vue语法
div class="app"> <input type="text" class="txt" v-model="message"> {{ message }} </div> <script> let vm = new Vue({ el: '.app', data: { message: '我是vue' } }) </script>
其中el 获取挂载点里面所有的子节点,然后经过处理之后再将整体插入到挂载点中。文档片段上进行操作DOM,而不会影响到真实的DOM,操作完成之后,我们就可以添加到真实DOM上,这样的效率比直接在正式DOM上修
改要高很多 。
我们先来看个小例子:
<div id="app"></div>
<script>
var flag = document.createDocumentFragment(),
span = document.createElement('span'),
textNode = document.createTextNode('hello world');
span.appendChild(textNode);
flag.appendChild(span);
document.querySelector('#app').appendChild(flag)
</script>
这样,我们就可以得到下面的DOM树:
最终代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>双向绑定demo</title> <style> body{margin: 200px auto;width: 800px;} </style> </head> <body> <div id="app"> <input type="text" id="input" v-model="text"> {{ text }} </div> <script> // Vue构造函数 --- 第一个执行 function Vue(options){ this.data = options.data; //发布订阅模式,数据绑定 var data = this.data; observe(data,this); var id = options.el; var dom = nodeToFragment(document.querySelector(id),this); //编译完成后返回到app中 document.getElementById("app").appendChild(dom); } //遍历传入实例的data对象的属性,将其设置为Vue对象的访问器属性 function observe(obj,vm){ Object.keys(obj).forEach(function(key){ defineReactive(vm,key,obj[key]); }); } //访问器属性是对象中的一种特殊属性,它不能直接在对象中设置,而必须通过defineProperty()方法单独定义。 //访问器属性的"值"比较特殊,读取或设置访问器属性的值,实际上是调用其内部特性:get和set函数。 function defineReactive(obj,key,val){ //这里用到了观察者(订阅/发布)模式,它定义了一种一对多的关系,让多个观察者监听一个主题对象,这个主题对象的状态发生改变时会通知所有观察者对象,观察者对象就可以更新自己的状态。 //实例化一个主题对象,对象中有空的观察者列表 var dep = new Dep(); //将data的每一个属性都设置为Vue对象的访问器属性,属性名和data中相同 //所以每次修改Vue.data的时候,都会调用下边的get和set方法。然后会监听v-model的input事件,当改变了input的值,就相应的改变Vue.data的数据,然后触发这里的set方法 Object.defineProperty(obj,key,{ get: function(){ //Dep.target指针指向watcher,增加订阅者watcher到主体对象Dep if(Dep.target){ dep.addSub(Dep.target); } return val; }, set: function(newVal){ if(newVal === val){ return } val = newVal; // console.log(val); //给订阅者列表中的watchers发出通知 dep.notify(); } }); } //主题对象Dep构造函数 function Dep(){ this.subs = []; } //Dep有两个方法,增加观察者 和 发布消息 Dep.prototype = { addSub: function(sub){ this.subs.push(sub); }, notify: function(){ this.subs.forEach(function(sub){ sub.update(); }); } }; //DocumentFragment(文档片段)可以看作节点容器,它可以包含多个子节点,当我们将它插入到DOM中时,只有它的子节点会插入目标节点,所以把它看作一组节点的容器。使用DocumentFragment处理节点,速度和性能远远优于直接操作DOM。Vue进行编译时,就是将挂载目标的所有子节点劫持(真的是劫持)到DocumentFragment中,经过一番处理后,再将DocumentFragment整体返回插入挂载目标。 //var dom = nodeToFragment(document.getElementById("app")); //console.log(dom); //返回到app中 //document.getElementById("app").appendChild(dom); function nodeToFragment(node,vm){ var flag = document.createDocumentFragment(); var child; //劫持node的所有子节点(真的在dom树中消失了,所以要在下边重新返回搭到app中) while (child = node.firstChild){ //先编译所有的子节点,再劫持到文档片段中 compile(child,vm); flag.appendChild(child); } return flag; } //编译节点,初始化数据绑定 function compile(node,vm){ //该正则匹配的是 :{{任意内容}} var reg = /\{\{(.*)\}\}/; //节点类型为元素 if(node.nodeType === 1){ var attr = node.attributes; //解析属性,不同的属性不用的处理方式,这里只写了v-model属性 for(var i=0;i<attr.length;i++){ if (attr[i].nodeName == "v-model") { //获取节点中v-model属性的值,也就是绑定的属性名 var name = attr[i].nodeValue; node.addEventListener("input",function(e){ //当触发input事件时改变vue.data中相应的属性的值,进而触发该属性的set方法 vm[name] = e.target.value; }); //改变之后,通过属性名取得数据 node.value = vm.data[name]; //用完删,所以浏览器中编译之后的节点上没有v-model属性 node.removeAttribute("v-model"); } } } //节点类型为text if(node.nodeType === 3){ //text是否满足文本插值的写法:{{任意内容}} if(reg.test(node.nodeValue)){ //获取匹配到的字符串:这里的RegExp.$1是RegExp的一个属性 //该属性表示正则表达式reg中,第一个()里边的内容,也就是 //{{任意内容}} 中的 文本【任意内容】 var name = RegExp.$1; //去掉前后空格,并将处理后的数据写入节点 name = name.trim(); //node.nodeValue = vm.data[name]; //实例化一个新的订阅者watcher new Watcher(vm,node,name); return; } } } //观察者构造函数。 //上边实例化新的观察者的时候执行这个函数:通过get()取得Vue.data中对应的数据,然后通过update()方法把数据更新到节点中。 function Watcher(vm,node,name){ //让全局变量Dep的target属性的指针指向该watcher实例 Dep.target = this; this.vm = vm; this.node = node; this.name = name; //放入Dep.target才能update()????????????????????????????????????????? this.update(); Dep.target = null; } // 观察者使用update方法,实际上是 Watcher.prototype = { update: function(){ this.get(); this.node.nodeValue = this.value; }, //获取data中的属性值 get: function(){ this.value = this.vm[this.name]; //触发相应属性的get } }; var vm = new Vue({ el: "#app", data: { text: "Hello Vue" } }) </script> </body> </html>