原生JavaScript之理解Vue原理

HTML部分

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="lib/Mao.js"></script>
</head>

<body>
    <div id="root">
        <input type="button" M-bind:value="str" M-on:click="fn">
        <input type="text" M-model="str">
        <div M-html="str">{{str}}</div>
        <div M-text="str">{{str}}</div>
        <div>{{a}}</div>

    </div>
</body>
<script>
    new Mao({
        el: "#root",
        data: {
            str: "今天是个好日子",
            a: 1
        },
        methods: {
            fn() {
                this.str = "量变引起质变"
                console.log(11111, this.str);
            }
        }
    });
</script>

</html>

原生js部分 理解Vue原理

/*
* 数据:--》data-->methods
* 数据监听器:observer.对数据的属性进行监听,如果发生变化,会告诉发布者。
* 发布者:通知订阅者数据发生变了。(发布者会通知订阅者更新视图)
* 订阅者:视图元素订阅的数据信息。data下面的每一个数据,都会有多个订阅者。
*        订阅者的职责:当数据发生变化时,会将与数据相关的元素属性进行更新(更新视图)
*       订阅者与数据的关系是多对一的。
* 编译器:对HTML进行解析的,分析HTML,然后生成相对应的事件以及订阅者。compile
* watcher:数据监听器与编译器的一个桥梁。 实际是封装函数 优化代码
* */
function Mao(options) {
    this.$options = options;
    //获得挂载元素
    if (options.el)
        this.$el = document.querySelector(options.el);
    //获取数据
    if (options.data)
        this.$data = options.data;

    // $binds是一个对象。确定视图与数据之间的关系 。  
    // 里面的属性均是咱们的数据属性。属性的值是一个数组,数组的每一个元素即是一个订阅者

    //$binds中 ---> 键-->this.$data中的键值-->相当于一个类型
    //$binds中 ---> 值  是与每一个this.$data数据的变量str或a或b产生关联的元素更新数据的方法   
    //   当数据发生变化时-->执行方法-->更新视图
    // <input type="button" M-bind:value="str" M-on:click="fn">
    // <input type="text" M-model="str">
    // <div M-html="str"></div>    
    //指令中 有str有多个元素--> 所以  $binds中 ---> 值-> 多对一 ->键    值是一个数组储存的方法
    this.$binds = {};

    //创建数据监视器  当数据发生变化时,告诉发布者 去通知订阅者更新视图
    this.obsever();

    //创建编译器      对HTML进行解析,生成相对应的事件以及订阅者
    this.comfile();
}

Mao.prototype.obsever = function () {
    // 声明_this的原因在56行;
    let _this = this;
    for (let key in this.$data) {
        //初始化$binds   遍历 this.$data时,先初始化类型 方便储存方法
        this.$binds[key] = [];
        //储存变量,下面需要使用
        let value = this.$data[key];
        //拦截    当this.$data中数据改变会执行 set    
        //获取this.$data中数据 会执行 get 返回值
        //当改变时 会执行set方法   在此改变数据的同时去通知订阅者更新视图
        Object.defineProperty(this.$data, key, {
            get() {
                return value
            },
            set(v) {
                value = v
                //该this指向的是this.$data  
                // ×  this.$binds[key].forEach(v => v.update())

                //$binds原因详解23-29行   update方法在编译器中声明创建
                //this.$data中的数据发生变化时,通知所有订阅者更新视图
                _this.$binds[key].forEach(v => v.update(false))

            }
        })
    }

}
// 对HTML进行解析。分析HTML当中是否包含框架的指令或者其它标识{{}}
// 查看属性是否有框架当中的指令。(标签是否与DATA有关联,如果有关联,可以生成一个订阅者)
Mao.prototype.comfile = function () {
    //获得挂载元素中的所有一级子元素  
    //  *****该编译器只适用于一级子元素  如想任意嵌套实现指令,需用递归实现
    let _this = this;
    const nodes = this.$el.children;
    for (let i = 0; i < nodes.length; i++) {
        const node = nodes[i];
        //当有M-on:click指令存在时 增加一个点击事件
        if (node.hasAttribute("M-on:click")) {
            //获取事件名
            const M_on = node.getAttribute("M-on:click")
            //增加事件   
            //this.$options.methods[M_on]指向的是该node 需要改变this指向 指向this.$data 
            node.addEventListener("click", this.$options.methods[M_on].bind(this.$data))

        }
        //当有M-bind:value指令存在时 增加一个订阅者 当this.$data数据发生改变时 视图改变
        if (node.hasAttribute("M-bind:value")) {
            //获取数据
            const M_bind = node.getAttribute("M-bind:value")  //str

            /*******************第一种写法****************/
            //增加一个订阅者  $binds{str:[订阅者]}
            // this.$binds[M_bind].push({
            //     //当数据发生变化时视图发生变化
            //     update() {
            //         node.value = _this.$data[M_bind]  _this 73行  指向构造函数
            //     }
            // })

            /*******************第二种写法**********第三种写法在最后面******/
            //因为 页面刷新的时候 绑定指令时  会直接显示  用这种方法比较好
            const watcher = {
                data: this.$data,
                key: M_bind,
                el: node,
                prop: "value",
                update() {
                    this.el[this.prop] = this.data[this.key]
                }
            }
            this.$binds[M_bind].push(watcher)
            watcher.update()

        }
        //当有M-model指令存在时 增加一个订阅者 实现双向绑定
        if (node.hasAttribute("M-model")) {
            const M_model = node.getAttribute("M-model")
            // console.log(this.$data[M_model], this.$data)
            const watcher = {
                data: this.$data,
                key: M_model,
                el: node,
                prop: "value",
                update() {
                    this.el[this.prop] = this.data[this.key]
                }
            }
            //增加一个订阅者 
            this.$binds[M_model].push(watcher);
            // console.log(watcher.update());
            watcher.update();

            //视图改变 数据改变
            node.oninput = function () {
                _this.$data[M_model] = node.value
            }
        }

        //当有M-html指令存在时 增加一个订阅者 实现双向绑定
        if (node.hasAttribute("M-html")) {
            const M_html = node.getAttribute("M-html");
            const watcher = {
                data: this.$data,
                key: M_html,
                el: node,
                prop: "innerHTML",
                update() {
                    /*解析双{{}}过程  此步可以省略*/
                    //把this.$data中的数据键值 转换成数组  方便遍历判断是否含有{{}}
                    let arrData = Object.keys(this.data)
                    let len = arrData.length
                    while (len--) {
                        //正则表达式   每次遍历 每次正则条件不同
                        const ele = new RegExp("{{" + arrData[len] + "}}")

                        // console.log(ele.test(this.el[this.prop]))
                        // 判断有没有{{}}  有就解析
                        if (ele.test(this.el[this.prop])) {
                            this.el[this.prop] = this.data[this.key]
                        }

                    }
                    //由于第一次有
                    this.el[this.prop] = this.data[this.key]
                }
            }
            this.$binds[M_html].push(watcher);
            watcher.update();
        }

        if (node.hasAttribute("M-text")) {
            const M_text = node.getAttribute("M-text");
            // const watcher = {
            //     data: this.$data,
            //     key: M_text,
            //     el: node,
            //     prop: "innerText",
            //     update() {
            //         //把this.$data的数据转成数组
            //         let arrData = Object.keys(this.data)
            //         let len = arrData.length
            //         while (len--) {
            //             const ele = new RegExp("{{" + arrData[len] + "}}")
            //             // 判断有没有{{}}  有就解析
            //             console.log(ele.test(this.el[this.prop]), this.el[this.prop])
            //             if (ele.test(this.el[this.prop])) {
            //                 this.el[this.prop] = this.data[this.key]
            //             }
            //         }
            //         this.el[this.prop] = this.data[this.key]
            //     }
            // }
            //第三种写法法  详情看204-231行
            const watcher = new Watcher(node, "innerText", this.$data, M_text)
            this.$binds[M_text].push(watcher);
            watcher.update();

        }
        //当没有指令时解析
        for (let k in this.$data) {
            const watcher = new Watcher(node, "innerText", this.$data, k)
            watcher.update();
        }


    }

}

function Watcher(el, prop, data, key) {
    this.el = el;
    this.prop = prop;
    this.data = data;
    this.key = key;
}
//默认第一次时解析{{}}  默认为true
//因为只要第一次才有{{}}   后面解析完成是没有的
Watcher.prototype.update = function (flag = true) {
    //把this.$data的数据转成数组
    if (flag) {
        let arrData = Object.keys(this.data)
        let len = arrData.length
        while (len--) {
            const ele = new RegExp("{{" + arrData[len] + "}}")
            //解析当前的数据
            const nowData = arrData[len];
            // 判断有没有{{}}  有就解析
            console.log(ele.test(this.el[this.prop]), this.el[this.prop])
            if (ele.test(this.el[this.prop])) {
                this.el[this.prop] = this.data[nowData]
            }
        }
        console.log(111)
    } else {
        this.el[this.prop] = this.data[this.key]
        console.log(222)
    }

}
  • 总结:

  • 1、实现一个数据监听器Observer,
    对数据对象的所有属性进行监听,当数据发生变化时 通过Object.defineProperty方法同时所有订阅者去更新视图

  • 2、实现一个HTML解析器Compile.
    对每个元素进行解析,根据其相对应的属性,为其增加订阅者或绑定事件

  • 3、watcher:生成订阅者–>构造函数 优化代码
    双向绑定:通过数据监听器,以及HTML解析器再加上一个订阅者生成器来完成的。
    在对数据监听时,可以根据数据的变化去通知相对应的数据订阅者,让你的订阅者更新视图。
    当视图影响到数据时,数据会发生变化。当数据变化,再次通知订阅者。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值