赞
踩
本文所有代码实现都收录于我的github,感兴趣的可以点击访问。
大家都知道vue.js遵循的是mvvm的设计理念,下面简要说明什么是mvvm。
采用分而治之思想,把不同的代码放到不同的模块当中,然后通过特定的逻辑联系到一起。
M 到 V(数据驱动视图):Data Bindings:通过数据绑定联系到一起。
V 到 M(视图影响数据):Dom Listeners:通过事件监听联系到一起。
只要数据进行了改变,同时视图也会同时更新。
理解了基本思想之后,我们要做什么才能实现VM呢?
目前几种主流的mvc(vm)框架都实现了单向数据绑定(例如react就是典型的数据单向绑定),简单的理解双向数据绑定无非就是在单向绑定的基础上给可输入元素(input、textare等)添加了change(input)事件,来动态修改model和 view。
一般通过sub, pub的方式实现数据和视图的绑定监听,更新数据方式通常做法是 vm.set(‘property’, value)。
这种方式现在毕竟太low了,我们更希望通过 vm.property = value 这种方式更新数据,同时自动更新视图,于是有了下面两种方式。
angular.js 是通过脏值检测的方式比对数据是否有变更,来决定是否更新视图,最简单的方式就是通过 setInterval() 定时轮询检测数据变动,当然Google不会这么low,angular只有在指定的事件触发时进入脏值检测,大致如下:
vue.js 则是采用数据劫持结合发布者-订阅者模式
的方式,通过Object.defineProperty()
来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
已经了解到vue是通过数据劫持的方式来做数据绑定的,其中最核心的方法便是通过Object.defineProperty()
来实现对属性的劫持,达到监听数据变动的目的,无疑这个方法是本文中最重要、最基础的内容之一。
整理了一下,要实现mvvm的双向绑定,就必须要实现以下几点:
指令解析器
的主要作用就是对指令进行解析。例如:v-text,v-html,v-on,v-bind等。解析指令之后,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图,如图所示:
在创建指令解析器之前,我们要提供入口类,也就是vm,用来接受配置,协调其它三者:
// 入口类
class Myvue{
constructor(options){
this.$el = options.el;
this.$data = options.data;
this.$options = options;
if(this.$el){
// 2.实现指令的解析器
new Compile(this.$el,this)
}
}
}
指令解析器的解析过程:
文档碎片对象
,会减少页面的回流和重绘。文档碎片的作用:将替换之后的内容放到缓存中,需要使用时会进行获取。// 指令解析器 class Compile{ constructor(el,vm){ // 当前传入的el是一个元素节点则赋值给当前类的el,否则自行获取元素节点 this.el = this.isElementNode(el) ? el : document.querySelector(el); this.vm = vm; /* 需要对根节点下的每一个节点进行编译,然后将页面中的数据(例如{ {person.name}})进行替换 频繁的编译和替换会导致页面的回流和重绘,会影响页面的性能 文档碎片的作用:将替换之后的内容放到缓存中,需要使用时会进行获取 */ // 1.获取文档碎片对象,会减少页面的回流和重绘 const fragment = this.node2Fragment(this.el); // 2.将文档碎片对象作为模板进行编译 this.compile(fragment); // 3.将文档碎片追加到根元素中 this.el.appendChild(fragment) } // 创建文档碎片对象 node2Fragment(el){ // 创建文档碎片对象 const f = document.createDocumentFragment(); let firstChild; // 遍历传入的DOM节点 while(firstChild = el.firstChild){ // 追加文档碎片 f.appendChild(firstChild); } return f; } // 编译模板:获取到的文档碎片内容 /** 内容如下: * <h2>{ {person.name}}--{ {person.age}}</h2> <h3>{ {person.fav}}</h3> <ul> <li>1</li> <li>2</li> <li>3</li> </ul> <h3>{ {msg}}</h3> <div v-text="msg"></div> <div v-html="msg"></div> <input type="text" v-model="msg"> */ compile(fragment){ // 1.获取子节点 const childNodes = fragment.childNodes; [...childNodes].forEach(child => { // 元素节点 if(this.isElementNode(child)){ // 编译元素节点 this.compileElement(child) // 文本节点 }else{ // 编译文本节点 主要处理 { {}} 形式的表达式 this.compileText(child) } // 递归遍历 if(child.childNodes && child.childNodes.length){ this.compile(child
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。