废话不多说,直接上案例
如下所示封装的类方法:index.js
class myVue {
constructor(vm) {
this.$options = vm
this.$watchEvent = {}
this.lifeCycle(vm)//生命周期执行函数
console.log(vm, "vm");
}
//生命周期原理
lifeCycle(vm) {
if (typeof vm.beforeCreate == 'function') {
vm.beforeCreate.bind(this)();
}
this.$data = vm.data
this.observe()//监听数据变化
this.proxyData()//监听数据劫持--用于直接获取到data中的值,不需要this.$data.值,直接拿到this.值
if (typeof vm.created == 'function') {
vm.created.bind(this)();
}
if (typeof vm.beforeMount == 'function') {
vm.beforeMount.bind(this)();
}
this.$el = document.querySelector(vm.$el)
this.compile(this.$el)
if (typeof vm.mounted == 'function') {
vm.mounted.bind(this)();
}
}
proxyData() {
for (let key in this.$data) {
Object.defineProperty(this, key, {
get() {
return this.$data[key]
},
set(val) {
this.$data[key] = val
}
})
}
}
//触发data中的数据 发生变化来执行watch中的updata
observe() {
for (let key in this.$data) {
let value = this.$data[key];
let that = this;
Object.defineProperty(this.$data, key,{
get(){
return value //劫持到要更新的值
},
set(val){//修改更新后的值
console.log(val, key,"改了");
value = val
if(that.$watchEvent[key]){
that.$watchEvent[key].forEach((item,index)=>{
item.update()
})
}
}
})
}
}
//操作dom节点
compile(node) {
node.childNodes.forEach((item, index) => {
//元素节点
if (item.nodeType == 1) {
console.log(item.hasAttribute("@click"), "hasAttribute");
if (item.hasAttribute("@click")) {
//Element.hasAttribute()--返回布尔值,true说明该元素包含有指定的属性
let btnKey = item.getAttribute("@click").trim()
console.log(btnKey, "btnbtnbtn");
item.addEventListener("click", (event) => {
if (this.$options.methods) {
this.eventFn = this.$options.methods[btnKey].bind(this)//改this的指向问题
this.eventFn(event)//把点击事件自带的事件传递过去
}
})
}
//判断元素节点是否添加了v-model,用dom操作去判断自定义标签
if (item.hasAttribute("v-model")){
let vmKey = item.getAttribute("v-model").trim()
if (this.hasOwnProperty(vmKey)){
console.log(this, this[vmKey],"v-model");
item.value = this[vmKey]
}
item.addEventListener("input",(event)=>{
this[vmKey] = item.value
})
}
if (item.childNodes.length) {
this.compile(item)//递归的方式去找元素中的文本节点
}
}
//text文本节点
if (item.nodeType == 3) {
let reg = /\{\{((?:.|\r?\n)+?)\}\}/g; //双花括号的正则表达式
let text = item.textContent;//给节点赋值
item.textContent = text.replace(reg, (match, vmKey) => {
vmKey = vmKey.trim()
if (this.hasOwnProperty(vmKey)) {//判断data中是否有这个数据
let watchs = new watcher(this, vmKey, item, "textContent")
if (this.$watchEvent[vmKey]) {
this.$watchEvent[vmKey].push(watchs)
} else {
this.$watchEvent[vmKey] = []
this.$watchEvent[vmKey].push(watchs)
}
console.log(this.$watchEvent,"提前判断当前数据data中的值是几个");//
}
return this.$data[vmKey] //替换节点中双花括号中的值和data中赋值的值对应
})
}
})
}
}
class watcher {
constructor(vm, key, node, attr) {
//对象
this.vm = vm;
//属性名称
this.key = key;
//节点
this.node = node;
//改变文本节点内容的字符串
this.attr = attr;
}
update(){
this.node[this.attr] = this.vm[this.key]
}
}
2、调用页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue原理</title>
</head>
<body>
<div id="app">
<h1>文案:{{message}}</h1>
{{str}}
<p>{{ str }}</p>
<button @click="btn">按钮</button>
<input type="text" v-model="message">
</div>
</body>
<script src="./index.js"></script>
<script>
let vueDate = {
$el: '#app',
data: {
message: '这是一个幸福的开始',
str: "名字"
},
methods: {
//方法
btn(e){
this.str = this.str + 1
console.log(this.str,"btn");
}
},
beforeCreate() {
console.log("beforeCreate", this.$el,this.$data);
},
created() {
console.log("created", this.$el, this.$data);
},
mounted() {
console.log("mounted", this.$el, this.$data);
},
beforeMount() {
console.log("beforeMount", this.$el, this.$data);
},
}
new myVue(vueDate)
</script>
</html>
3、先说生命周期:
beforeCreate、created、beforeMount、mounted其实就是四个函数,我们在constructor中获取到调用者给我们的数据时候,我们在不同的阶段做的处理,
lifeCycle(vm) {
if (typeof vm.beforeCreate == 'function') {
vm.beforeCreate.bind(this)();
}
this.$data = vm.data
this.observe()//监听数据变化
this.proxyData()//监听数据劫持--用于直接获取到data中的值,不需要this.$data.值,直接拿到this.值
if (typeof vm.created == 'function') {
vm.created.bind(this)();
}
if (typeof vm.beforeMount == 'function') {
vm.beforeMount.bind(this)();
}
this.$el = document.querySelector(vm.$el)
this.compile(this.$el)
if (typeof vm.mounted == 'function') {
vm.mounted.bind(this)();
}
}
我们在beforeCreate和created函数之间进行赋值操作,this.$data = vm.data ,那么我们在回调给页面端beforeCreate和created的时候mounted那只有created可以获取到data中的数据,同理beforeMount和mounted中也是如此,在这之间进行dom操作那只有mouted中能获取到dom数据了 。
4、页面展示双花括号绑定原理:
获取当前dom中的节点
//操作dom节点
compile(node) {
node.childNodes.forEach((item, index) => {
//text文本节点
if (item.nodeType == 3) {
let reg = /\{\{((?:.|\r?\n)+?)\}\}/g; //双花括号的正则表达式
let text = item.textContent;//给节点赋值
item.textContent = text.replace(reg, (match, vmKey) => {
vmKey = vmKey.trim()
if (this.hasOwnProperty(vmKey)) {//判断data中是否有这个数据
let watchs = new watcher(this, vmKey, item, "textContent")
if (this.$watchEvent[vmKey]) {
this.$watchEvent[vmKey].push(watchs)
} else {
this.$watchEvent[vmKey] = []
this.$watchEvent[vmKey].push(watchs)
}
console.log(this.$watchEvent,"提前判断当前数据data中的值是几个");//
}
return this.$data[vmKey] //替换节点中双花括号中的值和data中赋值的值对应
})
}
})
}
双花括号在文本节点中 ,所有获取当前节点中是3的情况 ,用正则去判断内部是否有双花括号,然后把{{}}替换成data中的对应key的值即可
5、点击事件@click的绑定
//操作dom节点
compile(node) {
node.childNodes.forEach((item, index) => {
//元素节点
if (item.nodeType == 1) {
console.log(item.hasAttribute("@click"), "hasAttribute");
if (item.hasAttribute("@click")) {
//Element.hasAttribute()--返回布尔值,true说明该元素包含有指定的属性
let btnKey = item.getAttribute("@click").trim()
console.log(btnKey, "btnbtnbtn");
item.addEventListener("click", (event) => {
if (this.$options.methods) {
this.eventFn = this.$options.methods[btnKey].bind(this)//改this的指向问题
this.eventFn(event)//把点击事件自带的事件传递过去
}
})
}
}
})
}
我们也是在获取当前元素的时候进行判断item.hasAttribute获取当前的标签中自定义名字,如果是@ckick,那就用原生的addEventListener的方法给这个标签绑定点击事件 ,事件就是vm.methods中的方法,然后把点击方法传递出去。
6、v-model双向绑定原理
//触发data中的数据 发生变化来执行watch中的updata
observe() {
for (let key in this.$data) {
let value = this.$data[key];
let that = this;
Object.defineProperty(this.$data, key,{
get(){
return value //劫持到要更新的值
},
set(val){//修改更新后的值
console.log(val, key,"改了");
value = val
if(that.$watchEvent[key]){
that.$watchEvent[key].forEach((item,index)=>{
item.update()
})
}
}
})
}
}
//操作dom节点
compile(node) {
node.childNodes.forEach((item, index) => {
//元素节点
if (item.nodeType == 1) {
//判断元素节点是否添加了v-model,用dom操作去判断自定义标签
if (item.hasAttribute("v-model")){
let vmKey = item.getAttribute("v-model").trim()
if (this.hasOwnProperty(vmKey)){
console.log(this, this[vmKey],"v-model");
item.value = this[vmKey]
}
item.addEventListener("input",(event)=>{
this[vmKey] = item.value
})
}
if (item.childNodes.length) {
this.compile(item)//递归的方式去找元素中的文本节点
}
}
//text文本节点
if (item.nodeType == 3) {
let reg = /\{\{((?:.|\r?\n)+?)\}\}/g; //双花括号的正则表达式
let text = item.textContent;//给节点赋值
item.textContent = text.replace(reg, (match, vmKey) => {
vmKey = vmKey.trim()
if (this.hasOwnProperty(vmKey)) {//判断data中是否有这个数据
let watchs = new watcher(this, vmKey, item, "textContent")
if (this.$watchEvent[vmKey]) {
this.$watchEvent[vmKey].push(watchs)
} else {
this.$watchEvent[vmKey] = []
this.$watchEvent[vmKey].push(watchs)
}
console.log(this.$watchEvent,"提前判断当前数据data中的值是几个");//
}
return this.$data[vmKey] //替换节点中双花括号中的值和data中赋值的值对应
})
}
})
}
class watcher {
constructor(vm, key, node, attr) {
//对象
this.vm = vm;
//属性名称
this.key = key;
//节点
this.node = node;
//改变文本节点内容的字符串
this.attr = attr;
}
update(){
this.node[this.attr] = this.vm[this.key]
}
}
如上所示,observe方法就是监听事件,我们可以在获取传递过来的this.$data = vm.data后进行监听数值的变化,如果改变就进行更新操作,然后更改双花括号中的值