一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第25天,点击查看活动详情
此为简易版本
vue
class Vue{
constructor(options){
// 保存数据
this.$options = options
this.$data = options.data
this.$el = options.el
// 将data添加到响应式系统中
new Observer(this.$data)
// 代理this.$data的数据
Object.keys(this.$data).forEach(key=>{
this._proxy(key)
})
// 处理el
new Compiler(this.$el,this)
}
_proxy(key){
Object.defineProperty(this,key,{
configurable:true,
enumerable:true,
set(newValue){
this.$data[key]=newValue
},
get(){
return this.$data[key]
}
})
}
}
observer
首先双向绑定分为两条线 一条observer 一条 compile
observer
class Observer{
constructor(data){
this.data=data
Object.keys(data).forEach(key=>{
this.defineReactive(this.data,key,data[key])
})
}
defineReactive(data,key,val){
const dep = new Dep() // [watcher,watcher]
Object.defineProperty(data,key,{
enumerable:true,
configurable:true,
get(){
if(Dep.target){ //Dep.target指向Watcher的this
dep.addSub(Dep.target)
}
return val
},
set(newValue){
if(newValue === val){
return
}
val=newValue
dep.notify()
}
})
}
}
observer 其中的原理是通过 遍历data中的属性 然后利用 object.defineProperty中的setter 和getter 的方法进行监听 当数据被调用的时候会触发get 当数据被修改的时候会触发set 然后利用开发者订阅者模式 给每个对象一对一的生成一个dep对象管理数据依赖 然后把watcher添加到dep中
Dep
class Dep{
constructor(){
this.subs=[]
}
addSub(sub){
this.subs.push(sub)
}
notify(){
this.subs.forEach(sub =>{
sub.update()
})
}
}
watcher
class Watcher{
constructor(node,name,vm){
this.node = node
this.name = name
this.vm = vm
Dep.target = this
this.update()
Dep.target = null
}
update(){
this.node.nodeValue = this.vm[this.name] // 触发getter获取数据
}
}
compile
compile 是解析模板中的变量替换成数据 会触发getter获取数据,Dep.notify 给遍历后每个对象赋予update方法,当数据发送改变时会触发Dep.notify 通知watcher调用update方法更新视图
const reg = /\{\{(.+)\}\}/ //正则匹配节点
class Compiler { //el:this.$el, vm: this
constructor(el,vm){
this.el =document.querySelector(el)
this.vm = vm
this.frag = this._createFragment()
this.el.appendChild(this.frag)
}
_createFragment(){
const frag = document.createDocumentFragment() // 创建代码片段 操作dom树一次一次地添加会导致dom树多次重排重绘
// 创建代码片段后 先把要添加的 添加到该片段 后面由该片段统一添加到dom树上 只进行了一次重排重绘
let child
while (child = this.el.firstChild) {
this._compile(child) // 解析节点
frag.appendChild(child)
}
return frag
}
_compile(node){// 解析节点 node节点
if(node.nodeType === 1){ //标签节点
const attrs = node.attributes
if(attrs.hasOwnProperty('v-model')){
const name = attrs['v-model'].nodeValue
node.addEventListener('input',e=>{
this.vm[name] = e.target.value
})
}
}
if(node.nodeType === 3){ //文本节点
console.log(reg.test(node.nodeValue));
if(reg.test(node.nodeValue)){
const name = RegExp.$1.trim()
new Watcher(node,name,this.vm)
}
}
}
}
补充: 它会先 判断 节点是否为 标签节点还是文本节点 当节点的nodeType等于1 的时候 为 标签节点 当节点的nodeType 等于3的时候 为文本节点 当为文本节点的时候 会根据 正则去判断所写的内容