vue2,双向绑定及响应式原理

废话不多说,直接上案例

如下所示封装的类方法: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后进行监听数值的变化,如果改变就进行更新操作,然后更改双花括号中的值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值