一、写在前面
学习前端不得不学vue,学习vue不得不学习他的响应式原理,下面为我实现响应式的思路和代码,注释写在代码中。
二、实现
const CommandUtils = {
setValue(vm, attr, newValue) {
attr.split('.').reduce((data, currentAttr, index, arr) => {
if (index === arr.length - 1) {
data[currentAttr] = newValue;
}
return data[currentAttr];
}, vm.$data)
},
getContent(vm, value) {
let reg = /\{\{(.+?)\}\}/gi;
let val = value.replace(reg, (...args) => {
return this.getValue(vm, args[1]);
});
return val;
},
getValue: function (vm, value) {
return value.split(".").reduce((data, key) => {
return data[key.trim()]
}, vm.$data)
},
model: function (vm, node, value) { //vm为options, node为节点,value为 data中属性
const val = this.getValue(vm, value)
new Watcher(vm, value, (newValue, oldValue) => {
node.value = newValue
})
node.addEventListener("input", (e) => {
const newValue = e.target.value
this.setValue(vm, value, newValue);
})
node.value = val
},
html: function (vm, node, value) {
new Watcher(vm, value, (newValue, oldValue) => {
node.innerHTML = newValue
})
const val = this.getValue(vm, value)
node.innerHTML = val
},
text: function (vm, node, value) {
new Watcher(vm, value, (newValue, oldValue) => {
node.textContent = newValue
})
const val = this.getValue(vm, value)
node.textContent = val
},
content: function (vm, node) {
const reg = /\{\{(.+?)\}\}/gi
const content = node.textContent
const val = content.replace(reg, (...args) => {
new Watcher(vm, args[1], (newValue, oldValue) => {
node.textContent = this.getContent(vm, content);
});
return this.getValue(vm, args[1])
})
node.textContent = val
},
on: function (node, value, vm, type) {
node.addEventListener(type, (e) => {
vm.$methods[value].call(vm, e)
})
}
}
export default class minVue {
constructor(options) {
if (this.isElement(options.el)) {
this.$el = options.el
} else {
this.$el = document.querySelector(options.el)
}
this.$data = options.data
this.proxyData();
this.$methods = options.methods;
new Observer(this.$data)
if (this.$el) {
// 将模板进行编译
new Compiler(this)
}
}
// 判断是否是标签元素
isElement(node) {
return node.nodeType === 1
}
// 将data中的数据放到vue实例上
proxyData() {
for (let key in this.$data) {
Object.defineProperty(this, key, {
get: () => {
return this.$data[key]
}
})
}
}
}
// 封装对模板进行编译的类
class Compiler {
constructor(vm) {
this.$vm = vm
this.fragment = this.fragmentTemplate()
this.buildTemplate(this.fragment)
this.$vm.$el.appendChild(this.fragment)
}
fragmentTemplate() {
const fragment = document.createDocumentFragment()
const parentNode = this.$vm.$el
let childNode = parentNode.firstChild
while (childNode) {
fragment.appendChild(childNode)
childNode = parentNode.firstChild
}
return fragment
}
// 编译模板
buildTemplate(fragment) {
const fragmentChildNos = [...fragment.childNodes]
fragmentChildNos.forEach(node => {
// 元素节点类型
if (this.$vm.isElement(node)) {
this.buildNode(node)
this.buildTemplate(node)
} else {
// 文本节点
this.buildText(node)
}
})
}
// 编译node节点
buildNode(node) {
const attrs = node.attributes
const newAttrs = [...attrs]
newAttrs.forEach(item => {
const {
name,
value
} = item //name 为 v-model value 为 title
if (name.startsWith("v-")) {
const command = name.split("-")[1] //model
const typeEvent = command.split(":")
if (typeEvent.length === 1) {
CommandUtils[command](this.$vm, node, value)
} else {
CommandUtils[typeEvent[0]](node, value, this.$vm, typeEvent[1])
}
}
})
}
// 编译文本节点
buildText(node) {
const reg = /\{\{.+?\}\}/gi
if (reg.test(node.textContent)) {
CommandUtils["content"](this.$vm, node)
}
}
}
// 对全部的属性进行监听
class Observer {
constructor(obj) {
this.observer(obj)
}
observer(obj) {
if (obj && typeof obj === "object") {
Reflect.ownKeys(obj).forEach(key => {
this.defineReactive(obj, key, obj[key])
})
}
}
defineReactive(obj, attr, value) {
this.observer(value)
let dep = new Dep()
Object.defineProperty(obj, attr, {
get() {
Dep.target && dep.addWatchs(Dep.target)
return value
},
set: (newValue) => {
if (value !== newValue) {
this.observer(newValue);
value = newValue
dep.notify()
}
}
})
}
}
// 保存依赖的类
class Dep {
constructor() {
this.subs = []
}
addWatchs(watcher) {
this.subs.push(watcher)
}
notify() {
this.subs.forEach(watch => watch.update())
}
}
class Watcher {
constructor(vm, attr, cb) {
this.vm = vm
this.attr = attr
this.cb = cb
this.oldValue = this.getOldValue()
}
getOldValue() {
Dep.target = this
let val = CommandUtils.getValue(this.vm, this.attr)
Dep.target = null
return val
}
update() {
const newValue = CommandUtils.getValue(this.vm, this.attr)
if (this.oldValue !== newValue) {
this.cb(newValue, this.oldValue)
this.oldValue = newValue
}
}
}
html代码展示
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h1>大家好,我是{{ name }}</h1>
<h1>大家好,我的年龄是{{ age }}</h1>
<input type="text" v-model="time.H">
<input type="text" v-model="time.M">
<input type="text" v-model="time.S">
<input type="text" v-model="time.S">
<h1>{{ time.S }}</h1>
<button v-on:click="myFn">点击</button>
</div>
<script type="module">
import minVue from './minVue.js'
new minVue({
el:"#app",
data: {
name: "dmc",
age: 20,
time: {
H:"小时",
M: "分钟",
S: "秒钟"
}
},
methods: {
myFn:() => {
console.log("hhhhh")
}
},
})
</script>
</body>
</html>
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/weixin_47450807/article/details/123297634
内容来源于网络,如有侵权,请联系作者删除!