引言
首先vue是一个类,每次new Vue的时候会传入参数,参数是一个对象
data,el是他的固定属性,在Vue的构造函数中会把el和data作为data的属性值,每次new Vue就会得到一个Vue实例,得到一个实例的同时,也希望data中的数据能够实现数据劫持,利用Object.defineProperty(obj,key,{set(){},get(){}})为$data中的每一个属性做数据劫持,使属性在使用和赋值时都会触发get或set函数,实现随时更新页面的数据,尤其是set方法,当修改一个属性的值的时候,就会触发set方法,就会把这个属性的事件池中的每一个watcher实例拿到并执行update函数,实现用到这一属性的页面节点的更新
概述
最核心的点,就是他有两条主线,一条是对data数据的劫持,一条就是对页面的解析,在第一次渲染页面时会达到效果,但是如何实现,数据改变页面内容同样改变呢,页面解析的时候,每当有地方需要data中的值的时候,就会new 一个Watcher实例,并传入当前节点,和使用data中数据的属性名,Watcher中方法 update就是为当前节点更换值设定的函数,也就是说当data中相应的key的值变化的时候,就要调用这个watcher实例中的update方法,它可以直接为当前的节点更换内容,而如何在data中一个属性值变化时会让所有与这个key有关的node节点更新呢,就要用到Dep事件池,每次为属性设置劫持的时候,就创建一个Dep事件池对象,这个对象中的池子,只可以放与这个属性有关的watcher,要在get时候添加,每个watcher在创建对象之后,把watcher对象放到全局找的到的地方,一般Dep这个函数实例上,然后通过获取这个vm.$data[key]的值触发,这个get函数,然后在get函数中把这个watcher实例放到相应的事件池中
<body>
<div id="app">
<h1>{{name}}</h1>
<h1>{{age}}</h1>
{{gender}}
<input v-model="name">
<input v-model="age">
</div>
<script>
//以实现-model和插值表达式为例实现双向数据绑定m数据v视图vm视图与数据之间的纽带
//定义一个vue类
class Vue {
constructor(options) {
this.$el = document.querySelector(options.el)
this.$data = options.data
//只要一创建vue实例就要对数据进行劫持(data)
observe(this.$data)
nodeToFragment(this.$el, this)
}
}
//数据劫持的核心就是使用Object.definedProperty(obj,key,{set(){},get(){}})(vue2.0)
//vue(3.0)let newObj=new Proxy(obj,{set(){},get(){}})
function observe(data) {
if (Object.prototype.toString.call(data) !== "[object Object]") return
let keys = Object.keys(data)
keys.forEach(item => {
setData(data, item, data[item])
})
}
function setData(obj, key, value) {
observe(value)
let dep = new Dep
Object.defineProperty(obj, key, {
set(newValue) {
value = newValue
observe(newValue)
dep.notify()
},
get() {
Dep.target && dep.addTo(Dep.target)
return value
}
})
}
function nodeToFragment(el, vm) {
let child
let fragment = document.createDocumentFragment()
while (child = el.firstChild) {
complie(child, vm)
fragment.appendChild(child)
}
el.appendChild(fragment)
}
function complie(doc, vm) {
if (doc.nodeType === 1) {
//nodeType为1就是元素对象,得到他的所有属性
let attrs = doc.attributes
//遍历所有属性找到属性是v-开头的
;
[...attrs].forEach(item => {
if (/^v\-/.test(item.nodeName)) {
new Watcher(doc, vm, item.nodeValue)
doc.value = vm.$data[item.nodeValue]
doc.addEventListener("input", function() {
vm.$data[item.nodeValue] = this.value
console.log(vm.$data[item.nodeValue]);
})
}
})
} else if (doc.nodeType === 3) {
//如果得到的dom对象是文本节点,拿到他的值
let str = doc.textContent
str = str.replace(/\{\{(.+?)\}\}/g, (a, b) => {
new Watcher(doc, vm, b.trim())
return vm.$data[b.trim()]
})
doc.textContent = str
}
doc.childNodes.forEach(item => {
complie(item, vm)
})
}
class Dep {
constructor() {
this.pool = []
}
addTo(watch) {
watch && this.pool.push(watch)
}
notify() {
this.pool.forEach(item => {
item.update()
})
}
}
class Watcher {
constructor(node, vm, key) {
Dep.target = this
this.node = node
this.vm = vm
this.key = key
this.get()
Dep.target = null
}
update() {
this.get()
if (this.node.nodeType === 1) {
//this.node.value = this.value
} else if (this.node.nodeType === 3) {
this.node.textContent = this.value
}
}
get() {
this.value = this.vm.$data[this.key]
}
}
let vm = new Vue({
el: "#app",
data: {
name: "haha",
age: 18,
gender: "女"
}
})
</script>
</body>