实现思路:
- 实现一个observer劫持属性变化,在vue2中就是用的object.defineProperty方法劫持的属性
- 实现一个watcher,使用的方法是实现一个订阅发布函数方法
- 实现一个compiler,解析指令,渲染dom等操作 首先observer劫持了数据,如果数据变化就通知watcher,watcher再通知compiler执行dom更新等操作。compiler就是watcher和observer中间的桥梁,但是为什么不直接从oberser通知到compiler呢?这中间watcher又具体干神马呢?查一查原来是如果不用订阅发布这个模式,那么每次通知变化到compiler会更新所有的dom,哪怕这个属性没有发生改变,也会执行compiler所有的代码,不能实现精准更新,那多余的更新操作肯定是不需要的。
- 一步一步的来。
<input type="text" id="oInput" >
const data={
name:''
}
document.getElementById('oInput').addEventListener('input', function (e) {
data.name = e.target.value
})
console.log(data.name)
这里实现了数据的单向绑定,input的时候就会改变data.name,那么还需要初始化input的value为data.name,在data改变时需要改变input框的值。就初步实现了数据双向绑定。
<input type="text" id="oInput" >
const data={
name:''
}
document.getElementById('oInput').addEventListener('input', function (e) {
//改变data.name值
data.name = e.target.value
})
Object.defineProperty(data, 'name', {
get: function () {
//获取属性值会调用此方法,如果不返回值,那么使用data.name = 2值会赋值不上
console.info('get', initVal) //2
return initVal
},
set: function (val) {
// 设置属性值会调用此方法,改变input的值
document.getElementById('oInput').value = val
console.info('set', val) // 2
initVal = val
}
})
data.name = 2//这里访问了set
console.log(data.name); //2,这里访问了get
// 劫持了obj对象的a属性,并且改变了a属性的值
到这里初步实现了数据的双向绑定,下一步定义一个ovserver
var data = {
name: 'waang',
age: '10'
}
document.getElementById('oInput').addEventListener('input', function (e) {
//改变data.name值
data.name = e.target.value
})
Object.keys(data).map((key) => {
defineReactive(data, key, data[key])
})
function defineReactive(data, key, value) {
Object.defineProperty(data, key, {
get: function () {
//获取属性值会调用此方法
console.info('get', value) //2
// 如果这里不return 那么劫持的属性值不回发生变化
return value
},
set: function (val) {
if (value === val) {
return
}
value = val
// 设置属性值会调用此方法
console.info('set', val) // 2
// 通知解析器,执行对应的页面dom更新操作
document.getElementById('oInput').value = val
}
})
}
实现了ovserver之后应该把更新dom等操作抽离出来,实现一个complier解析器,解析dom节点上的指令,在observer的get方法里面就调用compile方法执行更新操作
function compile() {
let app = document.getElementById('app')
let childNodes = app.childNodes
// console.log(childNodes)
childNodes.forEach((node) => {
// console.log(node.nodeType)
if (node.nodeType === 1) {
// console.log(node)
// 拿到所有的标签属性
const attrs = node.attributes
// console.log(attrs)
Array.from(attrs).forEach(attr => {
console.log(attr)
const nodeName = attr.nodeName
const nodeValue = attr.nodeValue
// nodeName -> v-text 就是我们需要查找的标识
// nodeValue -> name data中对应数据的key
console.log(11, nodeName, nodeValue)
// 把data中的数据 放到满足标识的dom上
if (nodeName === 'v-text') {
console.log('设置值', node)
node.innerText = data[nodeValue]
}
if (nodeName === 'v-model') {
console.log('设置值', node)
node.value = data[nodeValue]
// 监听input事件 在事件回调中 拿到最新的输入值 赋值给绑定的属性
node.addEventListener('input', (e) => {
let newValue = e.target.value
// 反向赋值
data[nodeValue] = newValue
})
}
})
}
})
}
但是这样做的问题是每次属性又变化都会更新所有的指令上的数据,需要优化,那么就需要定义一个watcher精准更新
<input type="text" id="oInput" v-model='name'>
<p id="p" v-text='age'></p>
<p v-text='name'></p>
const Dep = {
map: {},
// 收集事件的方法
collect(eventName, fn) {
// 如果当前map中已经初始化好了
// 就直接往里面push 如果没有初始化首次添加 就先进行初始化
if (!this.map[eventName]) {
this.map[eventName] = []
}
this.map[eventName].push(fn)
},
// 触发事件的方法
trigger(eventName) {
this.map[eventName].forEach(fn => fn())
}
}
修改之后的完整代码如下:
var data = {
name: 'waang',
age: '10'
}
Object.keys(data).map((key) => {
defineReactive(data, key, data[key])
})
// 引入发布订阅模式
// watcher
const Dep = {
map: {},
// 收集事件的方法
collect(eventName, fn) {
// 如果当前map中已经初始化好了
// 就直接往里面push 如果没有初始化首次添加 就先进行初始化
if (!this.map[eventName]) {
this.map[eventName] = []
}
this.map[eventName].push(fn)
},
// 触发事件的方法
trigger(eventName) {
this.map[eventName].forEach(fn => fn())
}
}
// Observer
function defineReactive(data, key, value) {
Object.defineProperty(data, key, {
get: function () {
//获取属性值会调用此方法
console.info('get', value) //2
// 如果这里不return 那么劫持的属性值不回发生变化
return value
},
set: function (val) {
if (value === val) {
return
}
value = val
// 设置属性值会调用此方法
console.info('set', val) // 2
// 通知解析器,执行对应的页面dom更新操作
Dep.trigger(key)
}
})
}
function compile() {
let app = document.getElementById('app')
let childNodes = app.childNodes
// console.log(childNodes)
childNodes.forEach((node) => {
// console.log(node.nodeType)
if (node.nodeType === 1) {
// console.log(node)
// 拿到所有的标签属性
const attrs = node.attributes
// console.log(attrs)
Array.from(attrs).forEach(attr => {
console.log(attr)
const nodeName = attr.nodeName
const nodeValue = attr.nodeValue
// nodeName -> v-text 就是我们需要查找的标识
// nodeValue -> name data中对应数据的key
console.log(11, nodeName, nodeValue)
// 把data中的数据 放到满足标识的dom上
if (nodeName === 'v-text') {
console.log('设置值', node)
node.innerText = data[nodeValue]
Dep.collect(nodeValue, () => {
console.log(`当前您修改了属性${nodeValue}`)
node.innerText = data[nodeValue]
})
}
if (nodeName === 'v-model') {
console.log('设置值', node)
node.value = data[nodeValue]
Dep.collect(nodeValue, () => {
node.value = data[nodeValue]
})
// 监听input事件 在事件回调中 拿到最新的输入值 赋值给绑定的属性
node.addEventListener('input', (e) => {
let newValue = e.target.value
// 反向赋值
data[nodeValue] = newValue
})
}
})
}
})
}
compile()