ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

原生js 模拟Vue双向数据绑定原理(详细代码及注释)

2021-07-23 23:58:04  阅读:101  来源: 互联网

标签:node Vue console log 绑定 js attrName key data


数据劫持

先对data中的数据进行劫持并挂载到vue实例上,这时每个数据对象都可以模拟是一个订阅者,当数据发生改变,发布者会通知(调用notify方法)每一个订阅者去调用update方法进行更新,然后通过编译器编译渲染到视图上,当视图发生改变了,每个订阅者会向发布者进行订阅,并返回到进行数据更新,进行数据同步(注释仅个人理解,如有不对,请指教)

class Vue {
    constructor(options) {
        this.$options = options;
        this._data = options.data;
        this.$el = typeof options.el === "string" ? document.querySelector(options.el) : options.el

        // 数据劫持
        this._proxyData(this._data);
        // 观察者模式
        new Observer(this._data)
        // 编译
        new Complier(this);
    }

    _proxyData(data) {
        Object.keys(this._data).forEach(key => {
            Object.defineProperty(this, key, {
                get() {
                    return data[key]
                },
                set(nValue) {
                    if (data[key] === nValue) {
                        return
                    }
                    data[key] = nValue;
                }
            })
        })
    }
}

观察者模式

class Observer {
    constructor(data) {
        this.walk(data)
    }
    // 遍历
    walk(data) {
        // 判断data是否存在,data是不是对象  如果不是或不存在则不处理

        // 开始  传入data:{}  是对象
        if (!data || typeof data !== "object") {
            return
        }

        // 遍历data中的每一个属性名key  拿到key
        // object.keys(),将data中的属性名存在一个数组中   [msg,crq,...]
        Object.keys(data).forEach(key => {

            //  挂载到vue实例中,对拿到的key值进行代理 key:msg data[key]:"hello vue"
            this.defineReactive(data, key, data[key])
        })
    }
    // 定义响应式数据  将属性属性值挂载到vue实例上
    defineReactive(data, key, value) {
        // 对每一个key进行发布和监听,在这里实例化publisher
        let publisher = new Publisher();

        let that = this;
        // 调用walk判断传进来的 value 也就是walk中data遍历后的属性值data[key]是不是对象,如果是对象,继续遍历,不是则返回,例如遍历data后key:crq  data[key]:{agr:...,sex:...}
        this.walk(value);
        // 将属性值挂载到vue实例上  
        // 这里将key进行代理,为每一个key添加get和set方法
        Object.defineProperty(data, key, {

            get() {
                // 收集依赖   添加观察者
                // publisher.target 就相当于添加的观察者的形参
                Publisher.target && publisher.addSub(Publisher.target);

                return value
            },
            set(nValue) {
                if (value === nValue) {
                    return
                }
                value = nValue
                // 如果数据修改或新添加数据,重新调用walk判断是否是对象然后进行挂载
                that.walk(nValue)

                // 发布通知,监测到数据发生改变,通知观察者更新数据
                publisher.notify()
            }
        })
    }
}

发布者

class Publisher {
    constructor() {
        // 这里初始化subs空数组  准备用来储存观察者  
        this.subs = [];
    }
    // 将观察者添加到数组中
    addSub(sub) {
        // 判断 观察者是否存在,并且观察者上是否有更新方法
        // 如果有  添加到subs数组中
        if (sub && sub.update) {
            this.subs.push(sub);
        }
    }

    notify() {
        // 通知每一个观察者 调用update方法更新
        console.log("notify");
        this.subs.forEach(w => {
            w.update();
            console.log("w.up");
        });

    }
}

订阅者(观察者)

class Wathcher {

    constructor(data, key, cb) {
        this.data = data;
        this.key = key;
        this.cb = cb

        Publisher.target = this

        this.oldValue = data[key]
    }
    // 更新视图


    // 更新前提是数据一定要发生改变,数据要和之前对比,如果没有变化就不更新
    // 获取发布者通知的信息
    update() {
        console.log("watcher--update");
        let newValue = this.data[this.key];
        // console.log(newValue);

        if (newValue === this.oldValue) {
            return
        }


        // 如果数据改变,dom更新
        this.cb(newValue);

    }
}

编译器

class Complier {
    constructor(vm) {
        this.el = vm.$el;
        this.data = vm._data;


        this.complie(this.el)

    }

    complie(el) {
        let childNodes = el.childNodes

        Array.from(childNodes).forEach(node => {
            // 代码分割
            if (this.isTextNode(node)) {
                // 文本处理
                this.complieText(node)
            } else if (this.isElementNode(node)) {
                // 元素处理
                this.complieElement(node)
            }
            // 当元素中包含文本和插值表达式时,需要再调用编译,编译出元素中嵌套的插值表达式和文本
            // 判断编译一遍后,里面是否还有节点,如果存在节点则再调用一遍编译  complie
            if (node.childNodes && node.childNodes.length > 0) {

                this.complie(node)
            }

        })
    }
    // 编译元素
    complieElement(node) {
        // 获取所有属性

        let attributes = node.attributes
        // 遍历所有属性
        Array.from(attributes).forEach(attr => {
            // 获取属性名
            // console.log(attr);
            let attrName = attr.name;
            // console.log(attrName);
            // 判断属性是否是  v-  开头 如果是  
            if (this.isDirective(attrName)) {


                // 获取v-后面部分   指令名  
                attrName = attrName.substring(2)
                console.log(attrName);

                // 属性值   data[key]
                let key = attr.value

                //  text - 映射方法,不同指令 不同函数
                this.update(node, attrName, key)
                console.log(this.data[key]);
            }

        })

    }

    // 更新   

    update(node, attrName, key) {

        // 判断  不同指令调用不同函数


        // if (attrName === "text") {
        //     this.textUpdate(node, attrName, key)
        // }

        // if (attrName === "model") {
        //     this.modelUpdate(node, attrName, key)
        // }

        //   fn 为声明的变量, 不能直接调用,要改变this指向  指向complier
        // 声明fn  代替各种判断

        let fn = this[attrName + 'Update'];
        fn && fn.call(this, node, attrName, key)
        console.log(key);
    }

    // 属性名为  text 时
    textUpdate(node, attrName, key) {

        console.log(key);
        // 更新节点文本内容    这里this.data[key]
        node.textContent = this.data[key];
        console.log(this.data[key]);

        //  将数据传入watcher在watcher里面判断,如果数据发生改变,则调用watcher中的update方法,更新视图中数据
        new Wathcher(this.data, key, (newValue) => {
            console.log("txtup");
            node.textContent = newValue
        })
    }


    // 属性名为  model 时   model 实现双向数据绑定
    modelUpdate(node, attrName, key) {
        console.log(key);
        // console.log(node);
        // input 里面文本是value
        node.value = this.data[key]
        console.log(this.data[key]);
        // key = node.value
        // console.log(node.value);

        new Wathcher(this.data, key, (newValue) => {
            node.value = newValue
        })

        node.addEventListener('input', () => {
            this.data[key] = node.value
        })
    }


    // 编译文本
    complieText(node) {
        // console.log(node);
        // console.log(node.textContent);
        let value = node.textContent  //内容
        let reg = /\{\{(.+?)\}\}/ // 正则规则

        // 判断 传入的文本内容中是否有正则表达式,如果有,则获取表达式中的值
        if (reg.test(value)) {

            // 获取:插值表达式的变量名
            let k = RegExp.$1.trim()
            console.log(k);

            //  替换
            node.textContent = value.replace(reg, this.data[k])


            // 数据变化 - 通知更新 - update - cb - dom
            new Wathcher(this.data, k, (newValue) => {
                node.textContent = newValue
            })

        }
    }




    // 节点判断



    // 判断属性名是否有v-
    isDirective(attrName) {
        // 返回值为boolen
        return attrName.startsWith("v-")
    }

    isTextNode(node) {
        return node.nodeType === 3
    }

    isElementNode(node) {
        return node.nodeType === 1
    }
    isAttrNode(node) {
        return node.nodeType === 2
    }

}

标签:node,Vue,console,log,绑定,js,attrName,key,data
来源: https://blog.csdn.net/C1rq99/article/details/119046310

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有