vue双向绑定原理实例分析(vue,编程语言)

时间:2024-05-05 10:29:13 作者 : 石家庄SEO 分类 : 编程语言
  • TAG :

vue双向绑定原理实例分析

自定义vue类

  • vue最少需要两个参数:模板和data。

  • 创建Compiler对象,将数据渲染到模板后,挂载到指定跟节点中。

classMyVue{//1,接收两个参数:模板(根节点),和数据对象constructor(options){//保存模板,和数据对象if(this.isElement(options.el)){this.$el=options.el;}else{this.$el=document.querySelector(options.el);}this.$data=options.data;//2.根据模板和数据对象,渲染到根节点if(this.$el){//监听data所有属性的get/setnewObserver(this.$data);newCompiler(this)}}//判断是否是一个dom元素isElement(node){returnnode.nodeType===1;}}

实现数据首次渲染到页面

Compiler

1,node2fragment函数将模板元素提取到内存中,方便将数据渲染到模板后,再一次性挂载到页面中

2,模板提取到内存后,使用buildTemplate函数遍历该模板元素

  • 元素节点

    • 使用buildElement函数检查元素上以v-开头的属性

  • 文本节点

    • 用buildText函数检查文本中有无{{}}内容

3,创建CompilerUtil类,用于处理vue指令和{{}},完成数据的渲染

4,到此就完成了首次数据渲染,接下来需要实现数据改变时,自动更新视图。

classCompiler{constructor(vm){this.vm=vm;//1.将网页上的元素放到内存中letfragment=this.node2fragment(this.vm.$el);//2.利用指定的数据编译内存中的元素this.buildTemplate(fragment);//3.将编译好的内容重新渲染会网页上this.vm.$el.appendChild(fragment);}node2fragment(app){//1.创建一个空的文档碎片对象letfragment=document.createDocumentFragment();//2.编译循环取到每一个元素letnode=app.firstChild;while(node){//注意点:只要将元素添加到了文档碎片对象中,那么这个元素就会自动从网页上消失fragment.appendChild(node);node=app.firstChild;}//3.返回存储了所有元素的文档碎片对象returnfragment;}buildTemplate(fragment){letnodeList=[...fragment.childNodes];nodeList.forEach(node=>{//需要判断当前遍历到的节点是一个元素还是一个文本if(this.vm.isElement(node)){//元素节点this.buildElement(node);//处理子元素this.buildTemplate(node);}else{//文本节点this.buildText(node);}})}buildElement(node){letattrs=[...node.attributes];attrs.forEach(attr=>{//v-model="name"=>{name:v-modelvalue:name}let{name,value}=attr;//v-model/v-html/v-text/v-xxxif(name.startsWith('v-')){//v-model->[v,model]let[_,directive]=name.split('-');CompilerUtil[directive](node,value,this.vm);}})}buildText(node){letcontent=node.textContent;letreg=/\{\{.+?\}\}/gi;if(reg.test(content)){CompilerUtil['content'](node,content,this.vm);}}}
letCompilerUtil={getValue(vm,value){//解析this.data.aaa.bbb.ccc这种属性returnvalue.split('.').reduce((data,currentKey)=>{returndata[currentKey.trim()];},vm.$data);},getContent(vm,value){//解析{{}}中的变量letreg=/\{\{(.+?)\}\}/gi;letval=value.replace(reg,(...args)=>{returnthis.getValue(vm,args[1]);});returnval;},//解析v-model指令model:function(node,value,vm){//在触发getter之前,为dom创建Wather,并为Watcher.target赋值newWatcher(vm,value,(newValue,oldValue)=>{node.value=newValue;});letval=this.getValue(vm,value);node.value=val;},//解析v-html指令html:function(node,value,vm){//在触发getter之前,为dom创建Wather,并为Watcher.target赋值newWatcher(vm,value,(newValue,oldValue)=>{node.innerHTML=newValue;});letval=this.getValue(vm,value);node.innerHTML=val;},//解析v-text指令text:function(node,value,vm){//在触发getter之前,为dom创建Wather,并为Watcher.target赋值newWatcher(vm,value,(newValue,oldValue)=>{node.innerText=newValue;});letval=this.getValue(vm,value);node.innerText=val;},//解析{{}}中的变量content:function(node,value,vm){letreg=/\{\{(.+?)\}\}/gi;letval=value.replace(reg,(...args)=>{//在触发getter之前,为dom创建Wather,并为Watcher.target赋值newWatcher(vm,args[1],(newValue,oldValue)=>{node.textContent=this.getContent(vm,value);});returnthis.getValue(vm,args[1]);});node.textContent=val;}}

实现数据驱动视图

Observer

1,使用defineRecative函数对data做Object.defineProperty处理,使得data中的每个数据都可以进行get/set监听

2,接下来将考虑如何在监听到data值改变后,更新视图内容呢?使用观察者设计模式,创建Dep和Wather类。

classObserver{constructor(data){this.observer(data);}observer(obj){if(obj&&typeofobj==='object'){//遍历取出传入对象的所有属性,给遍历到的属性都增加get/set方法for(letkeyinobj){this.defineRecative(obj,key,obj[key])}}}//obj:需要操作的对象//attr:需要新增get/set方法的属性//value:需要新增get/set方法属性的取值defineRecative(obj,attr,value){//如果属性的取值又是一个对象,那么也需要给这个对象的所有属性添加get/set方法this.observer(value);//第三步:将当前属性的所有观察者对象都放到当前属性的发布订阅对象中管理起来letdep=newDep();//创建了属于当前属性的发布订阅对象Object.defineProperty(obj,attr,{get(){//在这里收集依赖Dep.target&&dep.addSub(Dep.target);returnvalue;},set:(newValue)=>{if(value!==newValue){//如果给属性赋值的新值又是一个对象,那么也需要给这个对象的所有属性添加get/set方法this.observer(newValue);value=newValue;dep.notify();console.log('监听到数据的变化');}}})}}

使用观察者设计模式,创建Dep和Wather类

1,使用观察者设计模式的目的是:

  • 解析模板,收集data中某个数据在模板中被使用的dom节点集合,当该数据改变时,更新该dom节点集合就实现了数据更新。

  • Dep:用于收集某个data属性依赖的dom节点集合,并提供更新方法

  • Watcher:每个dom节点的包裹对象

    • attr:该dom使用的data属性

    • cb:修改该dom值的回调函数,在创建的时候会接收

2,到这里感觉思路是没问题了,已经是胜券在握了。那Dep和Watcher该怎么使用呢?

  • 为每个属性添加一个dep,用来收集依赖的dom

  • 因为页面首次渲染的时候会读取data数据,这时候会触发该data的getter,所以在此收集dom

  • 具体如何收集呢,在CompilerUtil类解析v-model,{{}}等命令时,会触发getter,我们在触发之前创建Wather,为Watcher添加一个静态属性,指向该dom,然后在getter函数里面获取该静态变量,并添加到依赖中,就完成了一次收集。因为每次触发getter之前都对该静态变量赋值,所以不存在收集错依赖的情况。

classDep{constructor(){//这个数组就是专门用于管理某个属性所有的观察者对象的this.subs=[];}//订阅观察的方法addSub(watcher){this.subs.push(watcher);}//发布订阅的方法notify(){this.subs.forEach(watcher=>watcher.update());}}
classWatcher{constructor(vm,attr,cb){this.vm=vm;this.attr=attr;this.cb=cb;//在创建观察者对象的时候就去获取当前的旧值this.oldValue=this.getOldValue();}getOldValue(){Dep.target=this;letoldValue=CompilerUtil.getValue(this.vm,this.attr);Dep.target=null;returnoldValue;}//定义一个更新的方法,用于判断新值和旧值是否相同update(){letnewValue=CompilerUtil.getValue(this.vm,this.attr);if(this.oldValue!==newValue){this.cb(newValue,this.oldValue);}}}

3,到这里就实现了数据绑定时,视图自动更新,本来想代码一步步实现的,但是发现不好处理,就把完整的class贴出来了。

实现视图驱动数据

其实就是监听输入框的input、change事件。修改CompilerUtil的model方法。具体代码如下

model:function(node,value,vm){newWatcher(vm,value,(newValue,oldValue)=>{node.value=newValue;});letval=this.getValue(vm,value);node.value=val; //看这里node.addEventListener('input',(e)=>{letnewValue=e.target.value;this.setValue(vm,value,newValue);})},

总结

vue双向绑定原理

vue接收一个模板和data参数。

1,首先将data中的数据进行递归遍历,对每个属性执行Object.defineProperty,定义get和set函数。并为每个属性添加一个dep数组。当get执行时,会为调用的dom节点创建一个watcher存放在该数组中。当set执行时,重新赋值,并调用dep数组的notify方法,通知所有使用了该属性watcher,并更新对应dom的内容。

2,将模板加载到内存中,递归模板中的元素,检测到元素有v-开头的命令或者双大括号的指令,就会从data中取对应的值去修改模板内容,这个时候就将该dom元素添加到了该属性的dep数组中。这就实现了数据驱动视图。在处理v-model指令的时候,为该dom添加input事件(或change),输入时就去修改对应的属性的值,实现了页面驱动数据。

3,将模板与数据进行绑定后,将模板添加到真实dom树中。

如何将watcher放在dep数组中?

在解析模板的时候,会根据v-指令获取对应data属性值,这个时候就会调用属性的get方法,我们先创建Watcher实例,并在其内部获取该属性值,作为旧值存放在watcher内部,我们在获取该值之前,在Watcher原型对象上添加属性Watcher.target = this;然后取值,将讲Watcher.target = null;这样get在被调用的时候就可以根据Watcher.target获取到watcher实例对象。

methods的原理

创建vue实例的时候,接收methods参数

在解析模板的时候遇到v-on的指令。会对该dom元素添加对应事件的监听,并使用call方法将vue绑定为该方法的this:vm.$methods[value].call(vm, e);

computed的原理

创建vue实例的时候,接收computed参数

初始化vue实例的时候,为computed的key进行Object.defineProperty处理,并添加get属性。

 </div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
本文:vue双向绑定原理实例分析的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:LVM中如何对xfs进行扩展下一篇:

7 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18